diff --git a/Ide/src/DocEdit.cpp b/Ide/src/DocEdit.cpp --- a/Ide/src/DocEdit.cpp +++ b/Ide/src/DocEdit.cpp @@ -1,530 +1,530 @@ #include "lgi/common/Lgi.h" #include "lgi/common/LgiRes.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Menu.h" #include "LgiIde.h" #include "DocEdit.h" #include "IdeDocPrivate.h" #define EDIT_TRAY_HEIGHT (LSysFont->GetHeight() + 10) #define EDIT_LEFT_MARGIN 16 // gutter for debug break points int DocEdit::LeftMarginPx = EDIT_LEFT_MARGIN; LAutoPtr GlobalFindReplace; DocEdit::DocEdit(IdeDoc *d, LFontType *f) : LTextView3(IDC_EDIT, 0, 0, 100, 100, f), DocEditStyling(this) { RefreshSize = 0; RefreshEdges = NULL; FileType = SrcUnknown; Doc = d; CurLine = -1; if (!GlobalFindReplace) { GlobalFindReplace.Reset(CreateFindReplaceParams()); } SetFindReplaceParams(GlobalFindReplace); CanScrollX = true; GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, (float)(LeftMarginPx + 2))); if (!f) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = Type.Create(); if (f) { #if defined LINUX f->PointSize(9); #elif defined WIN32 f->PointSize(8); #endif SetFont(f); } } } - SetWrapType(TEXTED_WRAP_NONE); + SetWrapType(L_WRAP_NONE); SetEnv(this); } DocEdit::~DocEdit() { ParentState = KExiting; Event.Signal(); while (!IsExited()) LSleep(1); SetEnv(0); } void DocEdit::OnCreate() { LTextView3::OnCreate(); Run(); } bool DocEdit::AppendItems(LSubMenu *Menu, const char *Param, int Base) { LSubMenu *Insert = Menu->AppendSub("Insert..."); if (Insert) { Insert->AppendItem("File Comment", IDM_FILE_COMMENT, Doc->GetProject() != 0); Insert->AppendItem("Function Comment", IDM_FUNC_COMMENT, Doc->GetProject() != 0); } return true; } void DocEdit::DoGoto(std::function Callback) { LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); Dlg->DoModal([this, Dlg, App=Doc->GetApp(), Callback](auto d, auto code) { bool status = code && ValidStr(Dlg->GetStr()); if (status) { auto s = Dlg->GetStr(); LString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { LString file = p[0]; int line = (int)p[1].Int(); App->GotoReference(file, line, false, true); } else if (p.Length() == 1) { int line = (int)p[0].Int(); if (line > 0) SetLine(line); else // Probably a filename with no line number.. App->GotoReference(p[0], 1, false, true); } } if (Callback) Callback(status); delete Dlg; }); } bool DocEdit::SetPourEnabled(bool b) { bool e = PourEnabled; PourEnabled = b; if (PourEnabled) { PourText(0, Size); PourStyle(0, Size); } return e; } int DocEdit::GetTopPaddingPx() { return GetCss(true)->PaddingTop().ToPx(GetClient().Y(), GetFont()); } void DocEdit::InvalidateLine(int Idx) { LTextLine *Ln = LTextView3::Line[Idx]; if (Ln) { int PadPx = GetTopPaddingPx(); LRect r = Ln->r; r.Offset(0, -ScrollYPixel() + PadPx); // LgiTrace("%s:%i - r=%s\n", _FL, r.GetStr()); Invalidate(&r); } } void DocEdit::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); #if 0 LRect cli = GetClient(); pDC->Colour(LColour::Red); pDC->Box(&cli); for (LViewI *p = GetParent(); p; p = p->GetParent()) { if (p == (LViewI*)GetWindow()) break; auto pos = p->GetPos(); cli.Offset(pos.x1, pos.y1); } LgiTrace("cli=%s\n", cli.GetStr()); #endif } void DocEdit::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LColour GutterColour(0xfa, 0xfa, 0xfa); LTextView3::OnPaintLeftMargin(pDC, r, GutterColour); int Y = ScrollYLine(); int TopPaddingPx = GetTopPaddingPx(); pDC->Colour(LColour(200, 0, 0)); List::I it = LTextView3::Line.begin(Y); int DocOffset = (*it)->r.y1; for (LTextLine *l = *it; l; l = *++it, Y++) { if (Doc->d->BreakPoints.Find(Y+1)) { int r = l->r.Y() >> 1; pDC->FilledCircle(8, l->r.y1 + r + TopPaddingPx - DocOffset, r - 1); } } bool DocMatch = Doc->IsCurrentIp(); { // We have the current IP location it = LTextView3::Line.begin(); int Idx = 1; for (LTextLine *ln = *it; ln; ln = *++it, Idx++) { if (DocMatch && Idx == IdeDoc::CurIpLine) { ln->Back = LColour(L_DEBUG_CURRENT_LINE); } else { ln->Back.Empty(); } } } } void DocEdit::OnMouseClick(LMouse &m) { if (m.Button1()) { if (m.Down()) Doc->GetApp()->SeekHistory(-1); return; } else if (m.Button2()) { if (m.Down()) Doc->GetApp()->SeekHistory(1); return; } if (m.Down()) { if (HasSelection()) { MsClick.x = -100; MsClick.y = -100; } else { MsClick.x = m.x; MsClick.y = m.y; } } else if ( m.x < LeftMarginPx && abs(m.x - MsClick.x) < 5 && abs(m.y - MsClick.y) < 5 ) { // Margin click... work out the line int Y = (VScroll) ? (int)VScroll->Value() : 0; LFont *f = GetFont(); if (!f) return; LCss::Len PaddingTop = GetCss(true)->PaddingTop(); int TopPx = PaddingTop.ToPx(GetClient().Y(), f); int Idx = ((m.y - TopPx) / f->GetHeight()) + Y + 1; if (Idx > 0 && Idx <= LTextView3::Line.Length()) { Doc->OnMarginClick(Idx); } } LTextView3::OnMouseClick(m); } void DocEdit::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { LTextView3::SetCaret(i, Select, ForceFullUpdate); if (IsAttached()) { auto Line = (int)GetLine(); if (Line != CurLine) { Doc->OnLineChange(CurLine = Line); } } } char *DocEdit::TemplateMerge(const char *Template, const char *Name, List *Params) { // Parse template and insert into doc LStringPipe T; for (const char *t = Template; *t; ) { char *e = strstr((char*) t, "<%"); if (e) { // Push text before tag T.Push(t, e-t); char *TagStart = e; e += 2; skipws(e); char *Start = e; while (*e && isalpha(*e)) e++; // Get tag char *Tag = NewStr(Start, e-Start); if (Tag) { // Process tag if (Name && stricmp(Tag, "name") == 0) { T.Push(Name); } else if (Params && stricmp(Tag, "params") == 0) { char *Line = TagStart; while (Line > Template && Line[-1] != '\n') Line--; int i = 0; for (auto p: *Params) { if (i) T.Push(Line, TagStart-Line); T.Push(p); if (i < Params->Length()-1) T.Push("\n"); i++; } } DeleteArray(Tag); } e = strstr(e, "%>"); if (e) { t = e + 2; } else break; } else { T.Push(t); break; } } T.Push("\n"); return T.NewStr(); } bool DocEdit::GetVisible(LStyle &s) { LRect c = GetClient(); auto a = HitText(c.x1, c.y1, false); auto b = HitText(c.x2, c.y2, false); s.Start = a; s.Len = b - a + 1; return true; } bool DocEdit::Pour(LRegion &r) { LRect c = r.Bound(); c.y2 -= EDIT_TRAY_HEIGHT; SetPos(c); return true; } bool DocEdit::Insert(size_t At, const char16 *Data, ssize_t Len) { int Old = PourEnabled ? CountRefreshEdges(At, 0) : 0; bool Status = LTextView3::Insert(At, Data, Len); int New = PourEnabled ? CountRefreshEdges(At, Len) : 0; if (Old != New) Invalidate(); return Status; } bool DocEdit::Delete(size_t At, ssize_t Len) { int Old = CountRefreshEdges(At, Len); bool Status = LTextView3::Delete(At, Len); int New = CountRefreshEdges(At, 0); if (Old != New) Invalidate(); return Status; } bool DocEdit::OnKey(LKey &k) { #ifdef MAC if (k.Ctrl()) #else if (k.Alt()) #endif { // This allows the Alt+Left/Right to be processed by the prev/next navigator menu. if (k.vkey == LK_LEFT || k.vkey == LK_RIGHT) { return false; } } if (k.AltCmd()) { if (ToLower(k.c16) == 'm') { if (k.Down()) Doc->GotoSearch(IDC_METHOD_SEARCH); return true; } else if (ToLower(k.c16) == 'o' && k.Shift()) { if (k.Down()) Doc->GotoSearch(IDC_FILE_SEARCH); return true; } } return LTextView3::OnKey(k); } LMessage::Result DocEdit::OnEvent(LMessage *m) { switch (m->Msg()) { case M_STYLING_DONE: OnApplyStyles(); break; } return LTextView3::OnEvent(m); } bool DocEdit::OnMenu(LDocView *View, int Id, void *Context) { if (View) { switch (Id) { case IDM_FILE_COMMENT: { const char *Template = Doc->GetProject()->GetFileComment(); if (Template) { auto File = strrchr(Doc->GetFileName(), DIR_CHAR); if (File) { auto Comment = TemplateMerge(Template, File + 1, 0); if (Comment) { char16 *C16 = Utf8ToWide(Comment); DeleteArray(Comment); if (C16) { Insert(Cursor, C16, StrlenW(C16)); DeleteArray(C16); Invalidate(); } } } } break; } case IDM_FUNC_COMMENT: { const char *Template = Doc->GetProject()->GetFunctionComment(); if (ValidStr(Template)) { const char16 *n = NameW(); if (n) { List Tokens; char16 *s; char16 *p = (char16*)n + GetCaret(); char16 OpenBrac[] = { '(', 0 }; char16 CloseBrac[] = { ')', 0 }; ssize_t OpenBracketIndex = -1; // Parse from cursor to the end of the function defn while ((s = LexCpp(p, LexStrdup))) { if (StricmpW(s, OpenBrac) == 0) { OpenBracketIndex = Tokens.Length(); } Tokens.Insert(s); if (StricmpW(s, CloseBrac) == 0) { break; } } if (OpenBracketIndex > 0) { char *FuncName = WideToUtf8(Tokens[OpenBracketIndex-1]); if (FuncName) { // Get a list of parameter names List Params; for (auto i = OpenBracketIndex+1; (p = Tokens[i]); i++) { char16 Comma[] = { ',', 0 }; if (StricmpW(p, Comma) == 0 || StricmpW(p, CloseBrac) == 0) { char16 *Param = Tokens[i-1]; if (Param) { Params.Insert(WideToUtf8(Param)); } } } // Do insertion char *Comment = TemplateMerge(Template, FuncName, &Params); if (Comment) { char16 *C16 = Utf8ToWide(Comment); DeleteArray(Comment); if (C16) { Insert(Cursor, C16, StrlenW(C16)); DeleteArray(C16); Invalidate(); } } // Clean up DeleteArray(FuncName); Params.DeleteArrays(); } else LgiTrace("%s:%i - No function name.\n", _FL); } else LgiTrace("%s:%i - OpenBracketIndex not found.\n", _FL); Tokens.DeleteArrays(); } else LgiTrace("%s:%i - No input text.\n", _FL); } else LgiTrace("%s:%i - No template.\n", _FL); break; } } } return true; } diff --git a/Lvc/src/Main.cpp b/Lvc/src/Main.cpp --- a/Lvc/src/Main.cpp +++ b/Lvc/src/Main.cpp @@ -1,1733 +1,1733 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLog.h" #include "lgi/common/Button.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/Tree.h" #include "lgi/common/FileSelect.h" #include "lgi/common/StructuredLog.h" #include "Lvc.h" #include "resdefs.h" #ifdef WINDOWS #include "resource.h" #endif ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; #define DEFAULT_BUILD_FIX_MSG "Build fix." #define OPT_Hosts "Hosts" #define OPT_Host "Host" AppPriv::~AppPriv() { if (CurFolder) CurFolder->Empty(); } SshConnection *AppPriv::GetConnection(const char *Uri, bool Create) { LUri u(Uri); u.sPath.Empty(); auto s = u.ToString(); auto Conn = Connections.Find(s); if (!Conn && Create) Connections.Add(s, Conn = new SshConnection(Log, s, "matthew@*$ ")); return Conn; } VersionCtrl AppPriv::DetectVcs(VcFolder *Fld) { char p[MAX_PATH_LEN]; LUri u = Fld->GetUri(); if (!u.IsFile() || !u.sPath) { auto c = GetConnection(u.ToString()); if (!c) return VcNone; auto type = c->Types.Find(u.sPath); if (type) return type; c->DetectVcs(Fld); Fld->GetCss(true)->Color(LColour::Blue); Fld->Update(); return VcPending; } auto Path = u.sPath.Get(); #ifdef WINDOWS if (*Path == '/') Path++; #endif if (LMakePath(p, sizeof(p), Path, ".git") && LDirExists(p)) return VcGit; if (LMakePath(p, sizeof(p), Path, ".svn") && LDirExists(p)) return VcSvn; if (LMakePath(p, sizeof(p), Path, ".hg") && LDirExists(p)) return VcHg; if (LMakePath(p, sizeof(p), Path, "CVS") && LDirExists(p)) return VcCvs; return VcNone; } class DiffView : public LTextLog { public: DiffView(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto ln : LTextView3::Line) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; if (*t == '+') { ln->c = LColour::Green; ln->Back.Rgb(245, 255, 245); } else if (*t == '-') { ln->c = LColour::Red; ln->Back.Rgb(255, 245, 245); } else if (*t == '@') { ln->c.Rgb(128, 128, 128); ln->Back.Rgb(235, 235, 235); } else ln->c = LColour(L_TEXT); } } } }; class ToolBar : public LLayout, public LResourceLoad { public: ToolBar() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name)) { OnPosChange(); } else LAssert(!"Missing toolbar resource"); } void OnCreate() { AttachChildren(); } void OnPosChange() { LRect Cli = GetClient(); LTableLayout *v; if (GetViewById(IDC_TABLE, v)) { v->SetPos(Cli); v->OnPosChange(); auto r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); // printf("Used = %s\n", r.GetStr()); LCss::Len NewSz(LCss::LenPx, (float)r.Y()+3); auto OldSz = GetCss(true)->Height(); if (OldSz != NewSz) { GetCss(true)->Height(NewSz); SendNotify(LNotifyTableLayoutRefresh); } } else LAssert(!"Missing table ctrl"); } void OnPaint(LSurface *pDC) { pDC->Colour(LColour(L_MED)); pDC->Rectangle(); } }; class CommitCtrls : public LLayout, public LResourceLoad { public: CommitCtrls() { LAutoString Name; LRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); LRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)r.Y())); } else LAssert(!"Missing table ctrl"); } else LAssert(!"Missing toolbar resource"); } void OnPosChange() { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; LString::Array GetProgramsInPath(const char *Program) { LString::Array Bin; LString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif LString::Array a = LGetPath(); for (auto p : a) { LFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public LDialog, public LXmlTreeUi { LOptionsFile &Opts; public: OptionsDlg(LViewI *Parent, LOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("svn-limit", IDC_SVN_LIMIT); Map("git-path", IDC_GIT, GV_STRING); Map("git-limit", IDC_GIT_LIMIT); Map("hg-path", IDC_HG, GV_STRING); Map("hg-limit", IDC_HG_LIMIT); Map("cvs-path", IDC_CVS, GV_STRING); Map("cvs-limit", IDC_CVS_LIMIT); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { auto s = new LFileSelect; s->Parent(this); s->Open([this, EditId](auto s, auto status) { if (status) SetCtrlName(EditId, s->Name()); delete s; }); } void BrowseFiles(LViewI *Ctrl, const char *Bin, int EditId) { LRect Pos = Ctrl->GetPos(); LPoint Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); LSubMenu s; LString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { LString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return LDialog::OnNotify(Ctrl, n); } }; int CommitDataCmp(VcCommit **_a, VcCommit **_b) { auto a = *_a; auto b = *_b; return a->GetTs().Compare(&b->GetTs()); } LString::Array AppPriv::GetCommitRange() { LString::Array r; if (Commits) { LArray Sel; Commits->GetSelection(Sel); if (Sel.Length() > 1) { Sel.Sort(CommitDataCmp); r.Add(Sel[0]->GetRev()); r.Add(Sel.Last()->GetRev()); } else { r.Add(Sel[0]->GetRev()); } } else LAssert(!"No commit list ptr"); return r; } LArray AppPriv::GetRevs(LString::Array &Revs) { LArray a; for (auto i = Commits->begin(); i != Commits->end(); i++) { VcCommit *c = dynamic_cast(*i); if (c) { for (auto r: Revs) { if (r.Equals(c->GetRev())) { a.Add(c); break; } } } } return a; } class CommitList : public LList { public: CommitList(int id) : LList(id, 0, 0, 200, 200) { } void SelectRevisions(LString::Array &Revs, const char *BranchHint = NULL) { VcCommit *Scroll = NULL; LArray Matches; for (auto i: *this) { VcCommit *item = dynamic_cast(i); if (!item) continue; bool IsMatch = false; for (auto r: Revs) { if (item->IsRev(r)) { IsMatch = true; break; } } if (IsMatch) Matches.Add(item); else if (i->Select()) i->Select(false); } for (auto item: Matches) { auto b = item->GetBranch(); if (BranchHint) { if (!b || Stricmp(b, BranchHint)) continue; } else if (b) { continue; } if (!Scroll) Scroll = item; item->Select(true); } if (!Scroll && Matches.Length() > 0) { Scroll = Matches[0]; Scroll->Select(true); } if (Scroll) Scroll->ScrollTo(); } bool OnKey(LKey &k) { switch (k.c16) { case 'p': case 'P': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { auto first = Sel[0]; auto branch = first->GetBranch(); auto p = first->GetParents(); if (p->Length() == 0) break; for (auto c:Sel) c->Select(false); SelectRevisions(*p, branch); } } return true; } case 'c': case 'C': { if (k.Down()) { LArray Sel; GetSelection(Sel); if (Sel.Length()) { LHashTbl,VcCommit*> Map; for (auto s:Sel) Map.Add(s->GetRev(), s); LString::Array n; for (auto it = begin(); it != end(); it++) { VcCommit *c = dynamic_cast(*it); if (c) { for (auto r:*c->GetParents()) { if (Map.Find(r)) { n.Add(c->GetRev()); break; } } } } for (auto c:Sel) c->Select(false); SelectRevisions(n, Sel[0]->GetBranch()); } } return true; } } return LList::OnKey(k); } }; int LstCmp(LListItem *a, LListItem *b, int Col) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if (A == NULL || B == NULL) { return (A ? 1 : -1) - (B ? 1 : -1); } auto f = A->GetFolder(); auto flds = f->GetFields(); if (!flds.Length()) { LgiTrace("%s:%i - No fields?\n", _FL); return 0; } auto fld = flds[Col]; switch (fld) { case LGraph: case LIndex: case LParents: case LRevision: default: return (int) (B->GetIndex() - A->GetIndex()); case LBranch: case LAuthor: case LMessageTxt: return Stricmp(A->GetFieldText(fld), B->GetFieldText(fld)); case LTimeStamp: return B->GetTs().Compare(&A->GetTs()); } return 0; } struct TestThread : public LThread { public: TestThread() : LThread("test") { Run(); } int Main() { auto Path = LGetPath(); LSubProcess p("python", "/Users/matthew/CodeLib/test.py"); auto t = LString(LGI_PATH_SEPARATOR).Join(Path); for (auto s: Path) printf("s: %s\n", s.Get()); p.SetEnvironment("PATH", t); if (p.Start()) { LStringPipe s; p.Communicate(&s); printf("Test: %s\n", s.NewLStr().Get()); } return 0; } }; class RemoteFolderDlg : public LDialog { class App *app; LTree *tree; struct SshHost *root, *newhost; LXmlTreeUi Ui; public: LString Uri; RemoteFolderDlg(App *application); ~RemoteFolderDlg(); int OnNotify(LViewI *Ctrl, LNotification n); }; class VcDiffFile : public LTreeItem { AppPriv *d; LString File; public: VcDiffFile(AppPriv *priv, LString file) : d(priv), File(file) { } const char *GetText(int i = 0) override { return i ? NULL : File.Get(); } LString StripFirst(LString s) { return s.Replace("\\","/").SplitDelimit("/", 1).Last(); } void Select(bool selected) override { LTreeItem::Select(selected); if (!selected) return; d->Files->Empty(); d->Diff->Name(NULL); LFile in(File, O_READ); LString s = in.Read(); if (!s) return; LString NewLine("\n"); LString::Array a = s.Replace("\r").Split("\n"); LArray index; LString oldName, newName; LString::Array Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(NewLine.Join(Diff)); f->Select(false); } Diff.Empty(); oldName.Empty(); newName.Empty(); InDiff = false; InPreamble = true; } else if (!Strnicmp(Ln, "Index", 5)) { if (InPreamble) index = a[i].SplitDelimit(": ", 1).Slice(1); } else if (!strncmp(Ln, "--- ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) oldName = p[1]; } else if (!strncmp(Ln, "+++ ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) newName = p[1]; if (oldName && newName) { InDiff = true; InPreamble = false; f = d->FindFile(newName); if (!f) f = new VcFile(d, NULL, LString(), false); const char *nullFn = "dev/null"; auto Path = StripFirst(oldName); f->SetUri(LString("file:///") + Path); if (newName.Find(nullFn) >= 0) { // Delete f->SetText(Path, COL_FILENAME); f->SetText("D", COL_STATE); } else { f->SetText(Path, COL_FILENAME); if (oldName.Find(nullFn) >= 0) // Add f->SetText("A", COL_STATE); else // Modify f->SetText("M", COL_STATE); } f->GetStatus(); d->Files->Insert(f); } } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { Diff.Add(a[i]); } } if (f && Diff.Length()) { f->SetDiff(NewLine.Join(Diff)); Diff.Empty(); } } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; LBox *FoldersBox = NULL; bool CallMethod(const char *MethodName, LScriptArguments &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *Args.GetReturn() = (AppPriv*)this; return true; } return false; } public: App() { LString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); LRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { if ((Menu = new LMenu)) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } LBox *ToolsBox = new LBox(IDC_TOOLS_BOX, true, "ToolsBox"); FoldersBox = new LBox(IDC_FOLDERS_BOX, false, "FoldersBox"); LBox *CommitsBox = new LBox(IDC_COMMITS_BOX, true, "CommitsBox"); ToolBar *Tools = new ToolBar; ToolsBox->Attach(this); Tools->Attach(ToolsBox); FoldersBox->Attach(ToolsBox); auto FolderLayout = new LTableLayout(IDC_FOLDER_TBL); auto c = FolderLayout->GetCell(0, 0, true, 2); Tree = new LTree(IDC_TREE, 0, 0, 320, 200); Tree->SetImageList(ImgLst, false); Tree->ShowColumnHeader(true); Tree->AddColumn("Folder", 250); Tree->AddColumn("Counts", 50); c->Add(Tree); c = FolderLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_FOLDERS, 0, 0, -1, -1)); c = FolderLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_FOLDERS, 0, 0, -1, -1, "x")); FolderLayout->Attach(FoldersBox); CommitsBox->Attach(FoldersBox); auto CommitsLayout = new LTableLayout(IDC_COMMITS_TBL); c = CommitsLayout->GetCell(0, 0, true, 2); Commits = new CommitList(IDC_LIST); c->Add(Commits); c = CommitsLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_COMMITS, 0, 0, -1, -1)); c = CommitsLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_COMMITS, 0, 0, -1, -1, "x")); CommitsLayout->Attach(CommitsBox); CommitsLayout->GetCss(true)->Height("40%"); LBox *FilesBox = new LBox(IDC_FILES_BOX, false); FilesBox->Attach(CommitsBox); auto FilesLayout = new LTableLayout(IDC_FILES_TBL); c = FilesLayout->GetCell(0, 0, true, 2); Files = new LList(IDC_FILES, 0, 0, 200, 200); Files->AddColumn("[ ]", 30); Files->AddColumn("State", 100); Files->AddColumn("Name", 400); c->Add(Files); c = FilesLayout->GetCell(0, 1); c->Add(new LEdit(IDC_FILTER_FILES, 0, 0, -1, -1)); c = FilesLayout->GetCell(1, 1); c->Add(new LButton(IDC_CLEAR_FILTER_FILES, 0, 0, -1, -1, "x")); FilesLayout->GetCss(true)->Width("35%"); FilesLayout->Attach(FilesBox); LBox *MsgBox = new LBox(IDC_MSG_BOX, true); MsgBox->Attach(FilesBox); CommitCtrls *Commit = new CommitCtrls; Commit->Attach(MsgBox); Commit->GetCss(true)->Height("25%"); if (Commit->GetViewById(IDC_MSG, Msg)) { LTextView3 *Tv = dynamic_cast(Msg); if (Tv) { Tv->Sunken(true); - Tv->SetWrapType(TEXTED_WRAP_NONE); + Tv->SetWrapType(L_WRAP_NONE); } } else LAssert(!"No ctrl?"); Tabs = new LTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); LTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); - Diff->SetWrapType(TEXTED_WRAP_NONE); + Diff->SetWrapType(L_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new LTextLog(IDC_LOG)); // Log->Sunken(true); - Log->SetWrapType(TEXTED_WRAP_NONE); + Log->SetWrapType(L_WRAP_NONE); SetCtrlValue(IDC_UPDATE, true); AttachChildren(); Visible(true); } LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { bool Req[VcMax] = {0}; for (auto c: f->Children) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); if (!Req[f->GetType()]) { Req[f->GetType()] = true; f->GetVersion(); } } } Opts.Unlock(); LRect Large(0, 0, 2000, 200); Tree->SetPos(Large); Tree->ResizeColumnsToContent(); LItemColumn *c; int i = 0, px = 0; while ((c = Tree->ColumnAt(i++))) { px += c->Width(); } FoldersBox->Value(MAX(320, px + 20)); // new TestThread(); } SetPulse(200); DropTarget(true); } ~App() { SerializeState(&Opts, "WndPos", false); SaveFolders(); } void SaveFolders() { LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) return; f->EmptyChildren(); for (auto i: *Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) f->InsertTag(vcf->Save()); } Opts.Unlock(); Opts.SerializeFile(true); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESPONSE: { SshConnection::HandleMsg(Msg); break; } case M_HANDLE_CALLBACK: { LAutoPtr Pc((ProcessCallback*)Msg->A()); if (Pc) Pc->OnComplete(); break; } } return LWindow::OnEvent(Msg); } void OnReceiveFiles(LArray &Files) { for (auto f : Files) { if (LDirExists(f)) OpenLocalFolder(f); } } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_PATCH_VIEWER: { OpenPatchViewer(this, &Opts); break; } case IDM_OPEN_LOCAL: { OpenLocalFolder(); break; } case IDM_OPEN_REMOTE: { OpenRemoteFolder(); break; } case IDM_OPEN_DIFF: { auto s = new LFileSelect; s->Parent(this); s->Open([this](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } case IDM_OPTIONS: { auto Dlg = new OptionsDlg(this, Opts); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_FIND: { auto i = new LInput(this, "", "Search string:"); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId == IDOK) { LString::Array Revs; Revs.Add(i->GetStr()); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } delete dlg; }); break; } case IDM_UNTRACKED: { auto mi = GetMenu()->FindItem(IDM_UNTRACKED); if (!mi) break; mi->Checked(!mi->Checked()); LArray Flds; Tree->GetSelection(Flds); for (auto f : Flds) { f->Refresh(); } break; } case IDM_REFRESH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Refresh(); break; } case IDM_PULL: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Pull(); break; } case IDM_PUSH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Push(); break; } case IDM_STATUS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->FolderStatus(); break; } case IDM_UPDATE_SUBS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->UpdateSubs(); break; break; } case IDM_EXIT: { LCloseApp(); break; } } return 0; } void OnPulse() { for (auto i:*Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) vcf->OnPulse(); } } void OpenLocalFolder(const char *Fld = NULL) { auto Load = [this](const char *Fld) { // Check the folder isn't already loaded... bool Has = false; LArray Folders; Tree->GetAll(Folders); for (auto f: Folders) { if (f->GetUri().IsFile() && !Stricmp(f->LocalPath(), Fld)) { Has = true; break; } } if (!Has) { LUri u; u.SetFile(Fld); auto f = new VcFolder(this, u.ToString()); if (f) { Tree->Insert(f); SaveFolders(); } } }; if (!Fld) { auto s = new LFileSelect; s->Parent(this); s->OpenFolder([this, Load](auto s, auto status) { if (status) Load(s->Name()); delete s; }); } else Load(Fld); } void OpenRemoteFolder() { auto Dlg = new RemoteFolderDlg(this); Dlg->DoModal([this, Dlg](auto dlg, auto status) { if (status) { Tree->Insert(new VcFolder(this, Dlg->Uri)); SaveFolders(); } delete dlg; }); } void OpenDiff(const char *File) { Tree->Insert(new VcDiffFile(this, File)); } void OnFilterFolders() { if (!Tree) return; for (auto i = Tree->GetChild(); i; i = i->GetNext()) { auto n = i->GetText(); bool vis = !FolderFilter || Stristr(n, FolderFilter.Get()) != NULL; i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Tree->UpdateAllItems(); Tree->Invalidate(); } void OnFilterCommits() { if (!Commits) return; LArray a; if (!Commits->GetAll(a)) return; auto cols = Commits->GetColumns(); for (auto i: a) { bool vis = !CommitFilter; for (int c=1; !vis && cGetText(c); if (Stristr(txt, CommitFilter.Get())) vis = true; } i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Commits->UpdateAllItems(); Commits->Invalidate(); } void OnFilterFiles() { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->FilterCurrentFiles(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_CLEAR_FILTER_FOLDERS: { SetCtrlName(IDC_FILTER_FOLDERS, NULL); // Fall through } case IDC_FILTER_FOLDERS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FOLDERS, NULL); LString n = GetCtrlName(IDC_FILTER_FOLDERS); if (n != FolderFilter) { FolderFilter = n; OnFilterFolders(); } break; } case IDC_CLEAR_FILTER_COMMITS: { SetCtrlName(IDC_FILTER_COMMITS, NULL); // Fall through } case IDC_FILTER_COMMITS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_COMMITS, NULL); LString n = GetCtrlName(IDC_FILTER_COMMITS); if (n != CommitFilter) { CommitFilter = n; OnFilterCommits(); } break; } case IDC_CLEAR_FILTER_FILES: { SetCtrlName(IDC_FILTER_FILES, NULL); // Fall through } case IDC_FILTER_FILES: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FILES, NULL); LString n = GetCtrlName(IDC_FILTER_FILES); if (n != FileFilter) { FileFilter = n; OnFilterFiles(); } break; } case IDC_FILES: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { // Select / deselect all check boxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (auto f: n) Checked |= f->Checked() > 0; for (auto f: n) f->Checked(Checked ? 0 : 1); } } } break; } default: break; } break; } case IDC_OPEN: { OpenLocalFolder(); break; } case IDC_TREE: { switch (n.Type) { case LNotifyContainerClick: { LMouse m; c->GetMouse(m); if (m.Right()) { LSubMenu s; s.AppendItem("Add Local", IDM_ADD_LOCAL); s.AppendItem("Add Remote", IDM_ADD_REMOTE); s.AppendItem("Add Diff File", IDM_ADD_DIFF_FILE); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD_LOCAL: { OpenLocalFolder(); break; } case IDM_ADD_REMOTE: { OpenRemoteFolder(); break; } case IDM_ADD_DIFF_FILE: { auto s = new LFileSelect; s->Parent(this); s->Open([this](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } } } break; } case (LNotifyType)LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case (LNotifyType)LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } default: break; } break; } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { auto BuildFix = GetCtrlValue(IDC_BUILD_FIX); const char *Msg = GetCtrlName(IDC_MSG); if (BuildFix || ValidStr(Msg)) { auto Sel = Tree->Selection(); if (Sel) { VcFolder *f = dynamic_cast(Sel); if (!f) { for (auto p = Sel->GetParent(); p; p = p->GetParent()) { f = dynamic_cast(p); if (f) break; } } if (f) { auto Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(BuildFix ? DEFAULT_BUILD_FIX_MSG : Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { LArray Folders; Tree->GetAll(Folders); bool AndUpdate = GetCtrlValue(IDC_UPDATE) != 0; for (auto f : Folders) { f->Pull(AndUpdate, LogSilo); } break; } case IDC_STATUS: { LArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } case IDC_HEADS: { if (n.Type == LNotifyValueChanged) { auto Revs = LString(c->Name()).SplitDelimit(); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } break; } case IDC_LIST: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse Ms; Commits->GetColumnClickInfo(Col, Ms); Commits->Sort(LstCmp, Col); break; } case LNotifyItemDoubleClick: { VcFolder *f = dynamic_cast(Tree->Selection()); if (!f) break; LArray s; if (Commits->GetSelection(s) && s.Length() == 1) f->OnUpdate(s[0]->GetRev()); break; } default: break; } break; } } return 0; } }; struct SshHost : public LTreeItem { LXmlTag *t; LString Host, User, Pass; SshHost(LXmlTag *tag = NULL) { t = tag; if (t) { Serialize(false); SetText(Host); } } void Serialize(bool WriteToTag) { if (WriteToTag) { LUri u; u.sProtocol = "ssh"; u.sHost = Host; u.sUser = User; u.sPass = Pass; t->SetContent(u.ToString()); } else { LUri u(t->GetContent()); if (!Stricmp(u.sProtocol.Get(), "ssh")) { Host = u.sHost; User = u.sUser; Pass = u.sPass; } } } }; RemoteFolderDlg::RemoteFolderDlg(App *application) : app(application), root(NULL), newhost(NULL), tree(NULL) { SetParent(app); LoadFromResource(IDD_REMOTE_FOLDER); if (GetViewById(IDC_HOSTS, tree)) { printf("tree=%p\n", tree); tree->Insert(root = new SshHost()); root->SetText("Ssh Hosts"); } else return; LViewI *v; if (GetViewById(IDC_HOSTNAME, v)) v->Focus(true); Ui.Map("Host", IDC_HOSTNAME); Ui.Map("User", IDC_USER); Ui.Map("Password", IDC_PASS); LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (hosts) { SshHost *h; for (auto c: hosts->Children) if (c->IsTag(OPT_Host) && (h = new SshHost(c))) root->Insert(h); app->Opts.Unlock(); } root->Insert(newhost = new SshHost()); newhost->SetText("New Host"); root->Expanded(true); newhost->Select(true); } RemoteFolderDlg::~RemoteFolderDlg() { } int RemoteFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { SshHost *cur = tree ? dynamic_cast(tree->Selection()) : NULL; #define CHECK_SPECIAL() \ if (cur == newhost) \ { \ root->Insert(cur = new SshHost()); \ cur->Select(true); \ } \ if (cur == root) \ break; switch (Ctrl->GetId()) { case IDC_HOSTS: { switch (n.Type) { case LNotifyItemSelect: { bool isRoot = cur == root; SetCtrlEnabled(IDC_HOSTNAME, !isRoot); SetCtrlEnabled(IDC_USER, !isRoot); SetCtrlEnabled(IDC_PASS, !isRoot); SetCtrlEnabled(IDC_DELETE, !isRoot && !(cur == newhost)); SetCtrlName(IDC_HOSTNAME, cur ? cur->Host.Get() : NULL); SetCtrlName(IDC_USER, cur ? cur->User.Get() : NULL); SetCtrlName(IDC_PASS, cur ? cur->Pass.Get() : NULL); break; } default: break; } break; } case IDC_HOSTNAME: { CHECK_SPECIAL() if (cur) { cur->Host = Ctrl->Name(); cur->SetText(cur->Host ? cur->Host : ""); } break; } case IDC_DELETE: { auto sel = tree ? dynamic_cast(tree->Selection()) : NULL; if (!sel) break; LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (!hosts) { LAssert(!"Couldn't lock tag."); break; } if (hosts->Children.HasItem(sel->t)) { sel->t->RemoveTag(); DeleteObj(sel->t); delete sel; } app->Opts.Unlock(); break; } case IDC_USER: { CHECK_SPECIAL() if (cur) cur->User = Ctrl->Name(); break; } case IDC_PASS: { CHECK_SPECIAL() if (cur) cur->Pass = Ctrl->Name(); break; } case IDOK: { LXmlTag *hosts; if (!(hosts = app->Opts.LockTag(OPT_Hosts, _FL))) { if (!(app->Opts.CreateTag(OPT_Hosts) && (hosts = app->Opts.LockTag(OPT_Hosts, _FL)))) break; } LAssert(hosts != NULL); for (auto i = root->GetChild(); i; i = i->GetNext()) { SshHost *h = dynamic_cast(i); if (!h || h == newhost) continue; if (h->t) ; else if ((h->t = new LXmlTag(OPT_Host))) hosts->InsertTag(cur->t); else return false; h->Serialize(true); } app->Opts.Unlock(); LUri u; u.sProtocol = "ssh"; u.sHost = GetCtrlName(IDC_HOSTNAME); u.sUser = GetCtrlName(IDC_USER); u.sPass = GetCtrlName(IDC_PASS); u.sPath = GetCtrlName(IDC_REMOTE_PATH); Uri = u.ToString(); // Fall through } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { // LStructuredLog::UnitTest(); a.AppWnd = new App; a.Run(); DeleteObj(a.AppWnd); } LAssert(VcCommit::Instances == 0); return 0; } diff --git a/ResourceEditor/src/LgiResApp.cpp b/ResourceEditor/src/LgiResApp.cpp --- a/ResourceEditor/src/LgiResApp.cpp +++ b/ResourceEditor/src/LgiResApp.cpp @@ -1,4728 +1,4728 @@ /* ** FILE: LgiRes.cpp ** AUTHOR: Matthew Allen ** DATE: 3/8/99 ** DESCRIPTION: Lgi Resource Editor ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "LgiRes_Menu.h" #include "lgi/common/About.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextView3.h" #include "lgi/common/Token.h" #include "lgi/common/DataDlg.h" #include "lgi/common/Button.h" #include "lgi/common/Menu.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" char AppName[] = "Lgi Resource Editor"; char HelpFile[] = "Help.html"; char OptionsFileName[] = "Options.r"; char TranslationStrMagic[] = "LgiRes.String"; #define VIEW_PULSE_RATE 100 #ifndef DIALOG_X #define DIALOG_X 1.56 #define DIALOG_Y 1.85 #define CTRL_X 1.50 #define CTRL_Y 1.64 #endif enum Ctrls { IDC_HBOX = 100, IDC_VBOX, }; const char *TypeNames[] = { "", "Css", "Dialog", "String", "Menu", 0}; ////////////////////////////////////////////////////////////////////////////// ResFileFormat GetFormat(const char *File) { ResFileFormat Format = Lr8File; char *Ext = LGetExtension(File); if (Ext) { if (stricmp(Ext, "lr") == 0) Format = CodepageFile; else if (stricmp(Ext, "xml") == 0) Format = XmlFile; } return Format; } char *EncodeXml(const char *Str, int Len) { char *Ret = 0; if (Str) { LStringPipe p; const char *s = Str; for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '<': { p.Push(s, e-s); p.Push("<"); s = ++e; break; } case '>': { p.Push(s, e-s); p.Push(">"); s = ++e; break; } case '&': { p.Push(s, e-s); p.Push("&"); s = ++e; break; } case '\\': { if (e[1] == 'n') { // Newline p.Push(s, e-s); p.Push("\n"); s = (e += 2); break; } // fall thru } case '\'': case '\"': case '/': { // Convert to entity p.Push(s, e-s); char b[32]; snprintf(b, sizeof(b), "&#%i;", *e); p.Push(b); s = ++e; break; } default: { // Regular character e++; break; } } } p.Push(s); Ret = p.NewStr(); } return Ret; } char *DecodeXml(const char *Str, int Len) { if (Str) { LStringPipe p; const char *s = Str; for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); ) { switch (*e) { case '&': { // Store string up to here p.Push(s, e-s); e++; if (*e == '#') { // Numerical e++; if (*e == 'x' || *e == 'X') { // Hex e++; char16 c = htoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else if (isdigit(*e)) { // Decimal char16 c = atoi(e); char *c8 = WideToUtf8(&c, 1); if (c8) { p.Push(c8); DeleteArray(c8); } } else { LAssert(0); } while (*e && *e != ';') e++; } else if (isalpha(*e)) { // named entity const char *Name = e; while (*e && *e != ';') e++; auto Len = e - Name; if (Len == 3 && strnicmp(Name, "amp", Len) == 0) { p.Push("&"); } else if (Len == 2 && strnicmp(Name, "gt", Len) == 0) { p.Push(">"); } else if (Len == 2 && strnicmp(Name, "lt", Len) == 0) { p.Push("<"); } else { // Unsupported entity LAssert(0); } } else { LAssert(0); while (*e && *e != ';') e++; } s = ++e; break; } case '\n': { p.Push(s, e-s); p.Push("\\n"); s = ++e; break; } default: { e++; break; } } } p.Push(s); return p.NewStr(); } return 0; } ////////////////////////////////////////////////////////////////////////////// Resource::Resource(AppWnd *w, int t, bool enabled) { AppWindow = w; ResType = t; Item = 0; SysObject = false; LAssert(AppWindow); } Resource::~Resource() { AppWindow->OnResourceDelete(this); if (Item) { Item->Obj = 0; DeleteObj(Item); } } bool Resource::IsSelected() { return Item?Item->Select():false; } bool Resource::Attach(LViewI *Parent) { LView *w = Wnd(); if (w) { return w->Attach(Parent); } return false; } ////////////////////////////////////////////////////////////////////////////// ResFolder::ResFolder(AppWnd *w, int t, bool enabled) : Resource(w, t, enabled) { Wnd()->Name(""); Wnd()->Enabled(enabled); } ////////////////////////////////////////////////////////////////////////////// ObjTreeItem::ObjTreeItem(Resource *Object) { if ((Obj = Object)) { Obj->Item = this; if (dynamic_cast(Object)) SetImage(ICON_FOLDER); else { int t = Object->Type(); switch (t) { case TYPE_CSS: SetImage(ICON_CSS); break; case TYPE_DIALOG: SetImage(ICON_DIALOG); break; case TYPE_STRING: SetImage(ICON_STRING); break; case TYPE_MENU: SetImage(ICON_MENU); break; } } } } ObjTreeItem::~ObjTreeItem() { if (Obj) { Obj->Item = 0; DeleteObj(Obj); } } const char *ObjTreeItem::GetText(int i) { if (Obj) { int Type = Obj->Type(); if (Type > 0) return Obj->Wnd()->Name(); else return TypeNames[-Type]; } return "#NO_OBJ"; } void ObjTreeItem::OnSelect() { if (Obj) { Obj->App()->OnResourceSelect(Obj); } } void ObjTreeItem::OnMouseClick(LMouse &m) { if (!Obj) return; if (m.IsContextMenu()) { Tree->Select(this); LSubMenu RClick; if (Obj->Wnd()->Enabled()) { if (Obj->Type() > 0) { // Resource RClick.AppendItem("Delete", IDM_DELETE, !Obj->SystemObject()); RClick.AppendItem("Rename", IDM_RENAME, !Obj->SystemObject()); } else { // Folder RClick.AppendItem("New", IDM_NEW, true); RClick.AppendSeparator(); auto Insert = RClick.AppendSub("Import from..."); if (Insert) { Insert->AppendItem("Lgi File", IDM_IMPORT, true); Insert->AppendItem("Win32 Resource Script", IDM_IMPORT_WIN32, false); } } // Custom entries if (!Obj->SystemObject()) { Obj->OnRightClick(&RClick); } } else { RClick.AppendItem("Not implemented", 0, false); } if (Tree->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Tree, m.x, m.y)) { case IDM_NEW: { SerialiseContext Ctx; Obj->App()->NewObject(Ctx, 0, -Obj->Type()); break; } case IDM_DELETE: { Obj->App()->SetDirty(true, [this](auto ok) { if (ok) Obj->App()->DelObject(Obj); }); break; } case IDM_RENAME: { auto Dlg = new LInput(Tree, GetText(), "Enter the name for the object", "Object Name"); Dlg->DoModal([this, Dlg](auto dlg, auto id) { if (id) { Obj->Wnd()->Name(Dlg->GetStr()); Update(); Obj->App()->SetDirty(true, NULL); } delete dlg; }); break; } case IDM_IMPORT: { auto Select = new LFileSelect(Obj->App()); Select->Type("Text", "*.txt"); Select->Open([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(dlg->Name(), O_READ)) { SerialiseContext Ctx; Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type()); if (Res) { // TODO // Res->Read(); } } else { LgiMsg(Obj->App(), "Couldn't open file for reading."); } } delete dlg; }); break; } case IDM_IMPORT_WIN32: { /* List l; if (ImportWin32Dialogs(l, MainWnd)) { for (ResDialog *r = l.First(); r; r = l.Next()) { Obj->App()->InsertObject(TYPE_DIALOG, r); } } */ break; } default: { Obj->OnCommand(Cmd); break; } } } } } ////////////////////////////////////////////////////////////////////////////// FieldView::FieldView(AppWnd *app) : Fields(NextId, true) { NextId = 100; App = app; Source = 0; Ignore = true; SetTabStop(true); Sunken(true); #ifdef WIN32 SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); #endif } FieldView::~FieldView() { } void FieldView::Serialize(bool Write) { if (!Source) return; Ignore = !Write; Fields.SetMode(Write ? FieldTree::UiToObj : FieldTree::ObjToUi); Fields.SetView(this); Source->Serialize(Fields); /* for (DataDlgField *f=Fields.First(); f; f=Fields.Next()) { LViewI *v; if (GetViewById(f->GetCtrl(), v)) { switch (f->GetType()) { case DATA_STR: { if (Write) // Ctrl -> Options { char *s = v->Name(); Options->Set(f->GetOption(), s); } else // Options -> Ctrl { char *s = 0; Options->Get(f->GetOption(), s); v->Name(s?s:(char*)""); } break; } case DATA_BOOL: case DATA_INT: { if (Write) // Ctrl -> Options { char *s = v->Name(); if (s && (s = strchr(s, '\''))) { s++; char *e = strchr(s, '\''); int i = 0; if (e - s == 4) { memcpy(&i, s, 4); i = LgiSwap32(i); Options->Set(f->GetOption(), i); } } else { int i = v->Value(); Options->Set(f->GetOption(), i); } } else // Options -> Ctrl { int i = 0; Options->Get(f->GetOption(), i); if (i != -1 && (i & 0xff000000) != 0) { char m[8]; i = LgiSwap32(i); sprintf(m, "'%04.4s'", &i); v->Name(m); } else { v->Value(i); } } break; } case DATA_FLOAT: case DATA_PASSWORD: case DATA_STR_SYSTEM: default: { LAssert(0); break; } } } else LAssert(0); } */ Ignore = false; } class TextViewEdit : public LTextView3 { public: bool Multiline; TextViewEdit( int Id, int x, int y, int cx, int cy, LFontType *FontInfo = 0) : LTextView3(Id, x, y, cx, cy, FontInfo) { Multiline = false; #ifdef WIN32 SetDlgCode(DLGC_WANTARROWS | DLGC_WANTCHARS); #endif } bool OnKey(LKey &k) { if (!Multiline && (k.c16 == '\t' || k.c16 == LK_RETURN)) { return false; } return LTextView3::OnKey(k); } }; class Hr : public LView { public: Hr(int x1, int y, int x2) { LRect r(x1, y, x2, y+1); SetPos(r); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); LThinBorder(pDC, c, DefaultSunkenEdge); } bool OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min) Inf.Height.Min = Inf.Height.Max = 2; else Inf.Width.Min = Inf.Width.Max = -1; return true; } }; void FieldView::OnDelete(FieldSource *s) { if (Source != NULL && Source == s) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = NULL; } } void FieldView::OnSelect(FieldSource *s) { Ignore = true; OnDelete(Source); if (Source) { // Clear fields Source->_FieldView = 0; Fields.Empty(); // remove all children LViewI *c; while ((c = Children[0])) { c->Detach(); DeleteObj(c); } Source = 0; } if (s) { // Add new fields Source = s; Source->_FieldView = AddDispatch(); if (Source->GetFields(Fields)) { LFontType Sys; Sys.GetSystemFont("System"); LTableLayout *t = new LTableLayout(IDC_TABLE); int Row = 0; LLayoutCell *Cell; LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++, Row++) { FieldTree::Field *c = (*b)[n]; switch (c->Type) { case DATA_STR: case DATA_FLOAT: case DATA_INT: case DATA_FILENAME: { Cell = t->GetCell(0, Row); Cell->VerticalAlign(LCss::VerticalMiddle); Cell->Add(new LTextLabel(-1, 0, 0, -1, -1, c->Label)); TextViewEdit *Tv; Cell = t->GetCell(1, Row, true, c->Type == DATA_FILENAME ? 1 : 2); Cell->Add(Tv = new TextViewEdit(c->Id, 0, 0, 100, 20, &Sys)); if (Tv) { Tv->Multiline = c->Multiline; Tv->GetCss(true)->Height(LCss::Len(LCss::LenPx, c->Multiline ? LSysFont->GetHeight() * 8 : LSysFont->GetHeight() + 8)); - Tv->SetWrapType(TEXTED_WRAP_NONE); + Tv->SetWrapType(L_WRAP_NONE); Tv->Sunken(true); } if (c->Type == DATA_FILENAME) { Cell = t->GetCell(2, Row); Cell->Add(new LButton(-c->Id, 0, 0, 21, 21, "...")); } break; } case DATA_BOOL: { Cell = t->GetCell(1, Row, true, 2); Cell->Add(new LCheckBox(c->Id, 0, 0, -1, -1, c->Label)); break; } default: LAssert(!"Impl me."); break; } } if (i < a.Length() - 1) { Cell = t->GetCell(0, Row++, true, 3); Cell->Add(new Hr(0, 0, X()-1)); } } AddView(t); OnPosChange(); AttachChildren(); Invalidate(); } Serialize(false); Ignore = false; } } void FieldView::OnPosChange() { LRect c = GetClient(); c.Inset(6, 6); LViewI *v; if (GetViewById(IDC_TABLE, v)) v->SetPos(c); } LMessage::Result FieldView::OnEvent(LMessage *m) { switch (m->Msg()) { case M_OBJECT_CHANGED: { FieldSource *Src = (FieldSource*)m->A(); if (Src == Source) { Fields.SetMode(FieldTree::ObjToUi); Fields.SetView(this); Serialize(false); } else LAssert(0); break; } } return LLayout::OnEvent(m); } int FieldView::OnNotify(LViewI *Ctrl, LNotification n) { if (!Ignore) { LTextView3 *Tv = dynamic_cast(Ctrl); if (Tv && n.Type == LNotifyCursorChanged) { return 0; } LArray a; Fields.GetAll(a); for (int i=0; iLength(); n++) { FieldTree::Field *c = (*b)[n]; if (c->Id == Ctrl->GetId()) { // Write the value back to the objects Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); return 0; } else if (c->Id == -Ctrl->GetId()) { auto s = new LFileSelect(this); s->Open([&](auto dlg, auto status) { if (status) { auto File = App->GetCurFile(); if (File) { LFile::Path p = File; p--; auto Rel = LMakeRelativePath(p, dlg->Name()); if (Rel) SetCtrlName(c->Id, Rel); else SetCtrlName(c->Id, dlg->Name()); } else SetCtrlName(c->Id, dlg->Name()); Fields.SetMode(FieldTree::UiToObj); Fields.SetView(this); Source->Serialize(Fields); } delete dlg; }); } } } } return 0; } void FieldView::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } ////////////////////////////////////////////////////////////////////////////// ObjContainer::ObjContainer(AppWnd *w) : LTree(100, 0, 0, 100, 100, "LgiResObjTree") { Window = w; Sunken(true); Insert(Style = new ObjTreeItem( new ResFolder(Window, -TYPE_CSS))); Insert(Dialogs = new ObjTreeItem( new ResFolder(Window, -TYPE_DIALOG))); Insert(Strings = new ObjTreeItem( new ResFolder(Window, -TYPE_STRING))); Insert(Menus = new ObjTreeItem( new ResFolder(Window, -TYPE_MENU))); const char *IconFile = "_icons.gif"; auto f = LFindFile(IconFile); LAssert(f); if (f) { Images = LLoadImageList(f, 16, 16); LAssert(Images); if (Images) SetImageList(Images, false); else LgiTrace("%s:%i - failed to load '%s'\n", _FL, IconFile); } } ObjContainer::~ObjContainer() { DeleteObj(Images); } bool ObjContainer::AppendChildren(ObjTreeItem *Res, List &Lst) { bool Status = true; if (Res) { LTreeItem *Item = Res->GetChild(); while (Item) { ObjTreeItem *i = dynamic_cast(Item); if (i) Lst.Insert(i->GetObj()); else Status = false; Item = Item->GetNext(); } } return Status; } Resource *ObjContainer::CurrentResource() { ObjTreeItem *Item = dynamic_cast(Selection()); if (!Item) return NULL; return Item->GetObj(); } bool ObjContainer::ListObjects(List &Lst) { bool Status = AppendChildren(Style, Lst); Status &= AppendChildren(Dialogs, Lst); Status &= AppendChildren(Strings, Lst); Status &= AppendChildren(Menus, Lst); return Status; } ////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 int Icon = IDI_ICON1; #else const char *Icon = "icon64.png"; #endif AppWnd::AppWnd() : LDocApp(AppName, Icon) { ShowLanguages.Add("en", true); SetQuitOnClose(true); if (_Create()) { LVariant Langs; if (GetOptions()->GetValue(OPT_ShowLanguages, Langs)) { ShowLanguages.Empty(); LToken L(Langs.Str(), ","); for (int i=0; iEmpty(); _Destroy(); } void AppWnd::OnCreate() { if (_LoadMenu("IDM_MENU")) { if (_FileMenu) { int n = 6; _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Import Win32 Script", IDM_IMPORT_WIN32, true, n++); _FileMenu->AppendItem("Import LgiRes Language", IDM_IMPORT_LANG, true, n++); _FileMenu->AppendItem("Compare To File...", IDM_COMPARE, true, n++); _FileMenu->AppendSeparator(n++); _FileMenu->AppendItem("Properties", IDM_PROPERTIES, true, n++); } ViewMenu = Menu->FindSubMenu(IDM_VIEW); LAssert(ViewMenu); } else LgiTrace("%s:%i - _LoadMenu failed.\n", _FL); Status = 0; StatusInfo[0] = StatusInfo[1] = 0; HBox = new LBox(IDC_HBOX); if (HBox) { HBox->GetCss(true)->Padding("5px"); VBox = new LBox(IDC_VBOX, true); if (VBox) { HBox->AddView(VBox); VBox->AddView(Objs = new ObjContainer(this)); if (Objs) { Objs->AskImage(true); Objs->AskText(true); } VBox->AddView(Fields = new FieldView(this)); VBox->Value(200); } HBox->Value(240); HBox->Attach(this); } DropTarget(true); LString Open; if (LAppInst->GetOption("o", Open)) LoadLgi(Open); } void AppWnd::OnLanguagesChange(LLanguageId Lang, bool Add, bool Update) { bool Change = false; if (Lang) { // Update the list.... bool Has = false; for (int i=0; iId, Lang) == 0) { Has = true; if (!Add) { Languages.DeleteAt(i); Change = true; } break; } } if (Add && !Has) { Change = true; Languages.Add(LFindLang(Lang)); } } // Update the menu... if (ViewMenu && (Change || Update)) { // Remove existing language menu items while (ViewMenu->RemoveItem(2)); // Add new ones int n = 0; for (int i=0; iAppendItem(Lang->Name, IDM_LANG_BASE + n, true); if (Item) { if (CurLang == i) { Item->Checked(true); } } } } } } bool AppWnd::ShowLang(LLanguageId Lang) { return ShowLanguages.Find(Lang) != 0; } void AppWnd::ShowLang(LLanguageId Lang, bool Show) { // Apply change if (Show) { OnLanguagesChange(Lang, true); ShowLanguages.Add(Lang, true); } else { ShowLanguages.Delete(Lang); } // Store the setting for next time LStringPipe p; // const char *L; // for (bool i = ShowLanguages.First(&L); i; i = ShowLanguages.Next(&L)) for (auto i : ShowLanguages) { if (p.GetSize()) p.Push(","); p.Push(i.key); } char *Langs = p.NewStr(); if (Langs) { LVariant v; GetOptions()->SetValue(OPT_ShowLanguages, v = Langs); DeleteArray(Langs); } // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } } LLanguage *AppWnd::GetCurLang() { if (CurLang >= 0 && CurLang < Languages.Length()) return Languages[CurLang]; return LFindLang("en"); } void AppWnd::SetCurLang(LLanguage *L) { for (int i=0; iId == L->Id) { // Set new current CurLang = i; // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } break; } } } LArray *AppWnd::GetLanguages() { return &Languages; } class Test : public LView { COLOUR c; public: Test(COLOUR col, int x1, int y1, int x2, int y2) { c = col; LRect r(x1, y1, x2, y2); SetPos(r); _BorderSize = 1; Sunken(true); } void OnPaint(LSurface *pDC) { pDC->Colour(c, 24); pDC->Rectangle(); } }; LMessage::Result AppWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_CHANGE: { LAutoPtr note((LNotification*)m->B()); return OnNotify((LViewI*) m->A(), *note); } case M_DESCRIBE: { char *Text = (char*) m->A(); if (Text) { SetStatusText(Text, STATUS_NORMAL); } break; } } return LDocApp::OnEvent(m); } void _CountGroup(ResStringGroup *Grp, int &Words, int &Multi) { for (auto s: *Grp->GetStrs()) { if (s->Items.Length() > 1) { Multi++; char *e = s->Get("en"); if (e) { LToken t(e, " "); Words += t.Length(); } } } } int AppWnd::OnCommand(int Cmd, int Event, OsView Handle) { SerialiseContext Ctx; switch (Cmd) { case IDM_SHOW_LANG: { auto Dlg = new ShowLanguagesDlg(this); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_NEW_CSS: { NewObject(Ctx, 0, TYPE_CSS); break; } case IDM_NEW_DIALOG: { NewObject(Ctx, 0, TYPE_DIALOG); break; } case IDM_NEW_STRING_GRP: { NewObject(Ctx, 0, TYPE_STRING); break; } case IDM_NEW_MENU: { NewObject(Ctx, 0, TYPE_MENU); break; } case IDM_CLOSE: { Empty(); break; } case IDM_IMPORT_WIN32: { LoadWin32(); break; } case IDM_IMPORT_LANG: { ImportLang(); break; } case IDM_COMPARE: { Compare(); break; } case IDM_PROPERTIES: { List l; if (Objs->ListObjects(l)) { int Dialogs = 0; int Strings = 0; int Menus = 0; int Words = 0; int MultiLingual = 0; for (auto r: l) { switch (r->Type()) { case TYPE_DIALOG: { Dialogs++; break; } case TYPE_STRING: { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { Strings += Grp->GetStrs()->Length(); _CountGroup(Grp, Words, MultiLingual); } break; } case TYPE_MENU: { Menus++; ResMenu *Menu = dynamic_cast(r); if (Menu) { if (Menu->Group) { Strings += Menu->Group->GetStrs()->Length(); _CountGroup(Menu->Group, Words, MultiLingual); } } break; } } } LgiMsg( this, "This file contains:\n" "\n" " Dialogs: %i\n" " Menus: %i\n" " Strings: %i\n" " Multi-lingual: %i\n" " Words: %i", AppName, MB_OK, Dialogs, Menus, Strings, MultiLingual, Words); } break; } case IDM_EXIT: { LCloseApp(); break; } case IDM_FIND: { auto s = new Search(this); s->DoModal([&](auto dlg, auto id) { if (id) new Results(this, s); delete dlg; }); break; } case IDM_NEXT: { LgiMsg(this, "Not implemented :(", AppName); break; } case IDM_CUT: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_CUT); } else { Resource *r = Objs->CurrentResource(); if (r) r->Copy(true); } break; } case IDM_COPY: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_COPY); } else { Resource *r = Objs->CurrentResource(); if (r) r->Copy(false); } break; } case IDM_PASTE: { auto Focus = LAppInst->GetFocus(); if (Focus) { Focus->PostEvent(M_PASTE); } else { Resource *r = Objs->CurrentResource(); if (r) r->Paste(); } break; } case IDM_TABLELAYOUT_TEST: { OpenTableLayoutTest(this); break; } case IDM_HELP: { char ExeName[MAX_PATH_LEN]; sprintf_s(ExeName, sizeof(ExeName), "%s", LGetExePath().Get()); while (strchr(ExeName, DIR_CHAR) && strlen(ExeName) > 3) { char p[256]; LMakePath(p, sizeof(p), ExeName, "index.html"); if (!LFileExists(p)) { LMakePath(p, sizeof(p), ExeName, "help"); LMakePath(p, sizeof(p), p, "index.html"); } if (LFileExists(p)) { LExecute(HelpFile, NULL, ExeName); break; } LTrimDir(ExeName); } break; } case IDM_SHOW_SHORTCUTS: { if (!ShortCuts) { ShortCuts = new ShortCutView(this); } if (ShortCuts) { auto res = Objs->CurrentResource(); if (res) ShortCuts->OnResource(res); } break; } case IDM_ABOUT: { LAbout Dlg( this, AppName, APP_VER, "\nLgi Resource Editor (lr8 files).", "icon64.png", "http://www.memecode.com/lgi/res", "fret@memecode.com"); break; } default: { int Idx = Cmd - IDM_LANG_BASE; if (Idx >= 0 && Idx < Languages.Length()) { // Deselect the old lang auto Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(false); } // Set the current CurLang = Idx; // Set the new lang's menu item Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0; if (Item) { Item->Checked(true); } // Update everything List res; if (ListObjects(res)) { for (auto r: res) { r->OnShowLanguages(); } } } break; } } return LDocApp::OnCommand(Cmd, Event, Handle); } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { default: { break; } } return 0; } void AppWnd::FindStrings(List &Strs, char *Define, int *CtrlId) { if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { StringList *s = r->GetStrs(); if (s) { for (auto Str: *s) { if (Define && ValidStr(Str->GetDefine())) { if (strcmp(Define, Str->GetDefine()) == 0) { Strs.Insert(Str); continue; } } if (CtrlId) { if (*CtrlId == Str->GetId()) { Strs.Insert(Str); continue; } } } } } } } } int AppWnd::GetUniqueCtrlId() { int Max = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { LHashTbl, int> t; for (auto r: l) { StringList *sl = r->GetStrs(); if (sl) { for (auto s: *sl) { if (s->GetId() > 0 && !t.Find(s->GetId())) { t.Add(s->GetId(), s->GetId()); } Max = MAX(s->GetId(), Max); } } } int i = 500; while (true) { if (t.Find(i)) { i++; } else { return i; } } } } return Max + 1; } int AppWnd::GetUniqueStrRef(int Start) { if (!Objs) return -1; List l; if (!Objs->ListObjects(l)) return -1; LHashTbl, ResString*> Map; LArray Dupes; for (auto r: l) { ResStringGroup *Grp = r->GetStringGroup(); if (Grp) { List::I it = Grp->GetStrs()->begin(); for (ResString *s = *it; s; s = *++it) { if (s->GetRef()) { ResString *Existing = Map.Find(s->GetRef()); if (Existing) { // These get their ref's reset to a unique value as a side // effect of this function... Dupes.Add(s); } else { Map.Add(s->GetRef(), s); } } else { // auto Idx = Grp->GetStrs()->IndexOf(s); LAssert(!"No string ref?"); } } } } for (int i=Start; true; i++) { if (!Map.Find(i)) { if (Dupes.Length()) { ResString *s = Dupes[0]; Dupes.DeleteAt(0); s->SetRef(i); SetDirty(true, NULL); } else { return i; } } } return -1; } ResString *AppWnd::GetStrFromRef(int Ref) { ResString *Str = 0; if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { if ((Str = Grp->FindRef(Ref))) break; } } } } return Str; } ResStringGroup *AppWnd::GetDialogSymbols() { if (Objs) { List l; if (Objs->ListObjects(l)) { for (auto r: l) { ResStringGroup *Grp = dynamic_cast(r); if (Grp) { auto ObjName = Grp->Wnd()->Name(); if (ObjName && stricmp(ObjName, StrDialogSymbols) == 0) { return Grp; } } } } } return NULL; } void AppWnd::OnReceiveFiles(LArray &Files) { auto f = Files.Length() ? Files[0] : 0; if (f) { _OpenFile(f, false, NULL); } } void AppWnd::SetStatusText(char *Text, int Pane) { if (Pane >= 0 && Pane < STATUS_MAX && StatusInfo[Pane]) { StatusInfo[Pane]->Name(Text); } } Resource *AppWnd::NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select) { Resource *r = 0; ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { r = new ResCss(this); Dir = Objs->Style; break; } case TYPE_DIALOG: { r = new ResDialog(this); Dir = Objs->Dialogs; break; } case TYPE_STRING: { r = new ResStringGroup(this); Dir = Objs->Strings; break; } case TYPE_MENU: { r = new ResMenu(this); Dir = Objs->Menus; break; } } if (r) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { Dir->Insert(Item); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } } r->Create(load, &ctx); if (Item) { Item->Update(); } SetDirty(true, NULL); } return r; } bool AppWnd::InsertObject(int Type, Resource *r, bool Select) { bool Status = false; if (r) { ObjTreeItem *Dir = 0; switch (Type) { case TYPE_CSS: { Dir = Objs->Style; break; } case TYPE_DIALOG: { Dir = Objs->Dialogs; break; } case TYPE_STRING: { Dir = Objs->Strings; break; } case TYPE_MENU: { Dir = Objs->Menus; break; } } if (Dir) { ObjTreeItem *Item = new ObjTreeItem(r); if (Item) { const char *Name = Item->GetText(); r->Item = Item; Dir->Insert(Item, (Name && Name[0] == '_') ? 0 : -1); Dir->Update(); Dir->Expanded(true); if (Select) { Objs->Select(Item); } Status = true; } } } return Status; } void AppWnd::DelObject(Resource *r) { OnResourceSelect(0); DeleteObj(r); } ObjTreeItem *GetTreeItem(LTreeItem *ti, Resource *r) { for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } ObjTreeItem *GetTreeItem(LTree *ti, Resource *r) { for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext()) { ObjTreeItem *o = dynamic_cast(i); if (o) { if (o->GetObj() == r) return o; } o = GetTreeItem(i, r); if (o) return o; } return 0; } void AppWnd::GotoObject(ResString *s, ResStringGroup *g, ResDialog *d, ResMenuItem *m, ResDialogCtrl *c) { if (s) { Resource *Res = 0; if (g) { Res = g; } else if (d) { Res = d; } else if (m) { Res = m->GetMenu(); } if (Res) { ObjTreeItem *ti = GetTreeItem(Objs, Res); if (ti) { ti->Select(true); if (g) { s->GetList()->Select(0); s->ScrollTo(); s->Select(true); } else if (d) { d->SelectCtrl(c); } else if (m) { for (LTreeItem *i=m; i; i=i->GetParent()) { i->Expanded(true); } m->Select(true); m->ScrollTo(); } } else { printf("%s:%i - couldn't find resources tree item\n", _FL); } } } } bool AppWnd::ListObjects(List &Lst) { if (Objs) { return Objs->ListObjects(Lst); } return false; } void AppWnd::OnObjChange(FieldSource *r) { if (Fields) { Fields->Serialize(false); SetDirty(true, NULL); } } void AppWnd::OnObjSelect(FieldSource *r) { if (Fields) Fields->OnSelect(r); } void AppWnd::OnObjDelete(FieldSource *r) { if (Fields) { Fields->OnDelete(r); } } void AppWnd::OnResourceDelete(Resource *r) { auto v = GetShortCutView(); if (v) v->OnResource(NULL); } void AppWnd::OnResourceSelect(Resource *r) { if (LastRes) { OnObjSelect(NULL); if (ContentView) { ContentView->Detach(); DeleteObj(ContentView); } LastRes = NULL; } if (r) { ContentView = r->CreateUI(); if (ContentView) { if (HBox) ContentView->Attach(HBox); LastRes = r; } auto v = GetShortCutView(); if (v) v->OnResource(r); } } char *TagName(LXmlTag *t) { static char Buf[1024]; LArray Tags; for (; t; t = t->Parent) { Tags.AddAt(0, t); } Buf[0] = 0; for (int i=0; iGetTag()); } return Buf; } class ResCompare : public LWindow, public LResourceLoad { LList *Lst; public: ResCompare(const char *File1, const char *File2) { Lst = 0; LRect p; LAutoString n; if (LoadFromResource(IDD_COMPARE, this, &p, &n)) { SetPos(p); Name(n); MoveToCenter(); GetViewById(IDC_DIFFS, Lst); if (Attach(0)) { Visible(true); AttachChildren(); LXmlTag *t1 = new LXmlTag; LXmlTag *t2 = new LXmlTag; if (t1 && File1) { LFile f; if (f.Open(File1, O_READ)) { LXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t1, &f, 0)) { DeleteObj(t1); } } else { DeleteObj(t1); } } if (t2 && File2) { LFile f; if (f.Open(File2, O_READ)) { LXmlTree x(GXT_NO_ENTITIES); if (!x.Read(t2, &f, 0)) { DeleteObj(t2); } } else { DeleteObj(t2); } } if (Lst && t1 && t2) { Lst->Enabled(false); Compare(t1, t2); Lst->Enabled(true); } DeleteObj(t1); DeleteObj(t2); } } } void Compare(LXmlTag *t1, LXmlTag *t2) { char s[1024]; if (stricmp(t1->GetTag(), t2->GetTag()) != 0) { snprintf(s, sizeof(s), "Different Tag: '%s' <-> '%s'", t1->GetTag(), t2->GetTag()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } LHashTbl,LXmlAttr*> a; for (int i=0; iAttr.Length(); i++) { LXmlAttr *a1 = &t1->Attr[i]; a.Add(a1->GetName(), a1); } for (int n=0; nAttr.Length(); n++) { LXmlAttr *a2 = &t2->Attr[n]; LXmlAttr *a1 = (LXmlAttr*) a.Find(a2->GetName()); if (a1) { if (strcmp(a1->GetValue(), a2->GetValue()) != 0) { snprintf(s, sizeof(s), "Different Attr Value: '%s' <-> '%s'", a1->GetValue(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); snprintf(s, sizeof(s), "%s.%s", TagName(t1), a1->GetName()); i->SetText(s, 1); Lst->Insert(i); } } a.Delete(a2->GetName()); } else { snprintf(s, sizeof(s), "[Right] Missing Attr: '%s' = '%s'", a2->GetName(), a2->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } } // char *Key; // for (void *v = a.First(&Key); v; v = a.Next(&Key)) for (auto v : a) { LXmlAttr *a1 = v.value; snprintf(s, sizeof(s), "[Left] Missing Attr: '%s' = '%s'", a1->GetName(), a1->GetValue()); LListItem *i = new LListItem; if (i) { i->SetText(s); i->SetText(TagName(t1), 1); Lst->Insert(i); } } if (t1->IsTag("string-group")) { LArray r1, r2; for (auto t: t1->Children) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r1[r] = t; } } } for (auto t: t2->Children) { char *Ref; if ((Ref = t->GetAttr("ref"))) { int r = atoi(Ref); if (r) { r2[r] = t; } } } auto Max = MAX(r1.Length(), r2.Length()); for (int i = 0; iGetAttr("ref"), r1[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r1[i]), 1); Lst->Insert(n); } } else if (r2[i]) { snprintf(s, sizeof(s), "[Left] Missing String: Ref=%s, Def=%s", r2[i]->GetAttr("ref"), r2[i]->GetAttr("Define")); LListItem *n = new LListItem; if (n) { n->SetText(s); n->SetText(TagName(r2[i]), 1); Lst->Insert(n); } } } } else { LXmlTag *c1 = t1->Children[0]; LXmlTag *c2 = t2->Children[0]; while (c1 && c2) { Compare(c1, c2); c1 = t1->Children[0]; c2 = t2->Children[0]; } } } void OnPosChange() { LRect c = GetClient(); if (Lst) { c.Inset(7, 7); Lst->SetPos(c); } } }; void AppWnd::Compare() { auto s = new LFileSelect(this); s->Type("Lgi Resource", "*.lr8"); s->Open([&](auto dlg, auto status) { if (status) new ResCompare(GetCurFile(), dlg->Name()); delete dlg; }); } void AppWnd::ImportLang() { // open dialog auto Select = new LFileSelect(this); Select->Type("Lgi Resources", "*.lr8;*.xml"); Select->Open([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(dlg->Name(), O_READ)) { SerialiseContext Ctx; Ctx.Format = GetFormat(dlg->Name()); // convert file to Xml objects LXmlTag *Root = new LXmlTag; if (Root) { LXmlTree Tree(GXT_NO_ENTITIES); if (Tree.Read(Root, &F, 0)) { List Menus; List Groups; for (auto t: Root->Children) { if (t->IsTag("menu")) { ResMenu *Menu = new ResMenu(this); if (Menu && Menu->Read(t, Ctx)) { Menus.Insert(Menu); } else break; } else if (t->IsTag("string-group")) { ResStringGroup *g = new ResStringGroup(this); if (g && g->Read(t, Ctx)) { Groups.Insert(g); } else break; } } Ctx.PostLoad(this); bool HasData = false; for (auto g: Groups) { g->SetLanguages(); if (g->GetStrs()->Length() > 0 && g->GetLanguages() > 0) { HasData = true; } } if (HasData) { List Langs; for (auto g: Groups) { for (int i=0; iGetLanguages(); i++) { LLanguage *Lang = g->GetLanguage(i); if (Lang) { bool Has = false; for (auto l: Langs) { if (stricmp((char*)l, (char*)Lang) == 0) { Has = true; break; } } if (!Has) { Langs.Insert(Lang); } } } } auto Dlg = new LangDlg(this, Langs); Dlg->DoModal([&](auto dlg, auto id) { if (id == IDOK && Dlg->Lang) { LStringPipe Errors; int Matches = 0; int NotFound = 0; int Imported = 0; int Different = 0; for (auto g: Groups) { List::I Strings = g->GetStrs()->begin(); for (ResString *s=*Strings; s; s=*++Strings) { ResString *d = GetStrFromRef(s->GetRef()); if (d) { Matches++; char *Str = s->Get(Dlg->Lang->Id); char *Dst = d->Get(Dlg->Lang->Id); if ( ( Str && Dst && strcmp(Dst, Str) != 0 ) || ( (Str != 0) ^ (Dst != 0) ) ) { Different++; d->Set(Str, Dlg->Lang->Id); Imported++; } } else { NotFound++; char e[256]; snprintf(e, sizeof(e), "String ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } } List Lst; if (ListObjects(Lst)) { for (auto m: Menus) { // find matching menu in our list ResMenu *Match = 0; for (auto r: Lst) { ResMenu *n = dynamic_cast(r); if (n && stricmp(n->Name(), m->Name()) == 0) { Match = n; break; } } if (Match) { // match strings List *Src = m->GetStrs(); List *Dst = Match->GetStrs(); for (auto s: *Src) { bool FoundRef = false; for (auto d: *Dst) { if (s->GetRef() == d->GetRef()) { FoundRef = true; char *Str = s->Get(Dlg->Lang->Id); if (Str) { char *Dst = d->Get(Dlg->Lang->Id); if (!Dst || strcmp(Dst, Str)) { Different++; } d->Set(Str, Dlg->Lang->Id); Imported++; } break; } } if (!FoundRef) { NotFound++; char e[256]; snprintf(e, sizeof(e), "MenuString ref=%i (%s)\n", s->GetRef(), s->GetDefine()); Errors.Push(e); } } Match->SetLanguages(); } } for (auto r: Lst) { ResStringGroup *StrRes = dynamic_cast(r); if (StrRes) { StrRes->SetLanguages(); } } } char *ErrorStr = Errors.NewStr(); LgiMsg( this, "Imported: %i\n" "Matched: %i\n" "Not matched: %i\n" "Different: %i\n" "Total: %i\n" "\n" "Import complete.\n" "\n%s", AppName, MB_OK, Imported, Matches, NotFound, Different, Matches + NotFound, ErrorStr?ErrorStr:(char*)""); } delete dlg; }); } else { LgiMsg(this, "No language information to import", AppName, MB_OK); } // Groups.DeleteObjects(); // Menus.DeleteObjects(); } else { LgiMsg(this, "Failed to parse XML from file.\nError: %s", AppName, MB_OK, Tree.GetErrorMsg()); } DeleteObj(Root); } } } delete dlg; }); } bool AppWnd::Empty() { // Delete any existing objects List l; if (ListObjects(l)) { for (auto It = l.begin(); It != l.end(); ) { auto r = *It; if (r->SystemObject()) l.Delete(It); else It++; } for (auto r: l) { DelObject(r); } } return true; } void AppWnd::OpenFile(const char *FileName, bool Ro, std::function Callback) { bool Status = false; if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { Status = LoadLgi(FileName); } else if (stristr(FileName, ".rc")) { LoadWin32(FileName); Status = true; } if (Callback) Callback(Status); } void AppWnd::SaveFile(const char *FileName, std::function Callback) { if (stristr(FileName, ".lr8") || stristr(FileName, ".xml")) { auto r = SaveLgi(FileName); if (Callback) Callback(FileName, r); return; } if (Callback) Callback(FileName, false); } void AppWnd::GetFileTypes(LFileSelect *Dlg, bool Write) { Dlg->Type("Lgi Resources", "*.lr8;*.xml"); if (!Write) { Dlg->Type("All Files", LGI_ALL_FILES); } } // Lgi load/save bool AppWnd::TestLgi(bool Quite) { bool Status = true; List l; if (ListObjects(l)) { ErrorCollection Errors; for (auto r: l) { Status &= r->Test(&Errors); } if (Errors.StrErr.Length() > 0) { LStringPipe Sample; for (int i=0; iGetRef(), s->GetDefine(), Errors.StrErr[i].Msg.Get()); } char *Sam = Sample.NewStr(); LgiMsg(this, "%i strings have errors.\n\n%s", AppName, MB_OK, Errors.StrErr.Length(), Sam); DeleteArray(Sam); } else if (!Quite) { LgiMsg(this, "Object are all ok.", AppName); } } return Status; } #define PROFILE_LOAD 0 #if PROFILE_LOAD #define PROF(s) prof.Add(s) #else #define PROF(s) #endif bool AppWnd::LoadLgi(const char *FileName) { #if PROFILE_LOAD LProfile prof("LoadLgi"); #endif Empty(); if (!FileName) return false; PROF("fOpen"); LFile f; if (!f.Open(FileName, O_READ)) return false; PROF("prog"); LAutoPtr Progress(new LProgressDlg(this)); Progress->SetDescription("Initializing..."); Progress->SetType("Tags"); LAutoPtr Root(new LXmlTag); if (!Root) return false; // convert file to Xml objects LXmlTree Xml(0); Progress->SetDescription("Lexing..."); PROF("xml.read"); if (!Xml.Read(Root, &f, 0)) { LgiMsg(this, "Xml read failed: %s", AppName, MB_OK, Xml.GetErrorMsg()); return false; } Progress->SetRange(Root->Children.Length()); PROF("xml to objs"); // convert Xml list into objects SerialiseContext Ctx; for (auto t: Root->Children) { Progress->Value(Root->Children.IndexOf(t)); int RType = 0; if (t->IsTag("dialog")) RType = TYPE_DIALOG; else if (t->IsTag("string-group")) RType = TYPE_STRING; else if (t->IsTag("menu")) RType = TYPE_MENU; else if (t->IsTag("style")) RType = TYPE_CSS; else LAssert(!"Unexpected tag"); if (RType > 0) NewObject(Ctx, t, RType, false); } PROF("postload"); Ctx.PostLoad(this); PROF("sort"); SortDialogs(); PROF("test"); TestLgi(); PROF("scan langs"); // Scan for languages and update the view lang menu Languages.Length(0); LHashTbl, LLanguage*> Langs; if (!ViewMenu) return false; PROF("remove menu items"); // Remove existing language menu items while (ViewMenu->RemoveItem(1)); ViewMenu->AppendSeparator(); PROF("enum objs"); // Enumerate all languages List res; if (ListObjects(res)) { for (auto r: res) { ResStringGroup *Sg = r->IsStringGroup(); if (Sg) { for (int i=0; iGetLanguages(); i++) { LLanguage *Lang = Sg->GetLanguage(i); if (Lang) { Langs.Add(Lang->Id, Lang); } } } } } PROF("update langs"); // Update languages array int n = 0; for (auto i : Langs) { Languages.Add(i.value); auto Item = ViewMenu->AppendItem(i.value->Name, IDM_LANG_BASE + n, true); if (Item && i.value->IsEnglish()) { Item->Checked(true); CurLang = n; } n++; } PROF("none menu"); if (Languages.Length() == 0) ViewMenu->AppendItem("(none)", -1, false); return true; } void SerialiseContext::PostLoad(AppWnd *App) { for (int i=0; iGetUniqueCtrlId(); s->SetId(Id); Log.Print("Repaired CtrlId of string ref %i to %i\n", s->GetRef(), Id); } LAutoString a(Log.NewStr()); if (ValidStr(a)) { LgiMsg(App, "%s", "Load Warnings", MB_OK, a.Get()); } } int DialogNameCompare(ResDialog *a, ResDialog *b, NativeInt Data) { const char *A = (a)?a->Name():0; const char *B = (b)?b->Name():0; if (A && B) return stricmp(A, B); return -1; } void AppWnd::SortDialogs() { List Lst; if (ListObjects(Lst)) { List Dlgs; for (auto r: Lst) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) { Dlgs.Insert(Dlg); Dlg->Item->Remove(); } } Dlgs.Sort(DialogNameCompare); for (auto d: Dlgs) { Objs->Dialogs->Insert(d->Item); } } } class ResTreeNode { public: char *Str; ResTreeNode *a, *b; ResTreeNode(char *s) { a = b = 0; Str = s; } ~ResTreeNode() { DeleteArray(Str); DeleteObj(a); DeleteObj(b); } void Enum(List &l) { if (a) { a->Enum(l); } if (Str) { l.Insert(Str); } if (b) { b->Enum(l); } } bool Add(char *s) { int Comp = (Str && s) ? stricmp(Str, s) : -1; if (Comp == 0) { return false; } if (Comp < 0) { if (a) { return a->Add(s); } else { a = new ResTreeNode(s); } } if (Comp > 0) { if (b) { return b->Add(s); } else { b = new ResTreeNode(s); } } return true; } }; class ResTree { ResTreeNode *Root; public: ResTree() { Root = 0; } ~ResTree() { DeleteObj(Root); } bool Add(char *s) { if (s) { if (!Root) { Root = new ResTreeNode(NewStr(s)); return true; } else { return Root->Add(NewStr(s)); } } return false; } void Enum(List &l) { if (Root) { Root->Enum(l); } } }; const char *HeaderStr = "// This file generated by LgiRes\r\n\r\n"; struct DefinePair { char *Name; int Value; }; int PairCmp(DefinePair *a, DefinePair *b) { return a->Value - b->Value; } bool AppWnd::WriteDefines(LStream &Defs) { bool Status = false; ResTree Tree; // Empty file Defs.Write(HeaderStr, strlen(HeaderStr)); // make a unique list of #define's List Lst; if (ListObjects(Lst)) { LHashTbl,int> Def; LHashTbl,char*> Ident; for (auto r: Lst) { List *StrList = r->GetStrs(); if (StrList) { Status = true; List::I sl = StrList->begin(); for (ResString *s = *sl; s; s = *++sl) { if (ValidStr(s->GetDefine())) { if (stricmp(s->GetDefine(), "IDOK") == 0) { s->SetId(IDOK); } else if (stricmp(s->GetDefine(), "IDCANCEL") == 0) { s->SetId(IDCANCEL); } else if (stricmp(s->GetDefine(), "IDC_STATIC") == 0) { s->SetId(-1); } else if (stricmp(s->GetDefine(), "-1") == 0) { s->SetDefine(0); } else { // Remove dupe ID's char IdStr[32]; snprintf(IdStr, sizeof(IdStr), "%i", s->GetId()); char *Define; if ((Define = Ident.Find(IdStr))) { if (strcmp(Define, s->GetDefine())) { List n; FindStrings(n, s->GetDefine()); int NewId = GetUniqueCtrlId(); for (auto Ns: n) { Ns->SetId(NewId); } } } else { Ident.Add(IdStr, s->GetDefine()); } // Make all define's the same int CtrlId; if ((CtrlId = Def.Find(s->GetDefine()))) { // Already there... s->SetId(CtrlId); } else { // Add... LAssert(s->GetId()); if (s->GetId()) Def.Add(s->GetDefine(), s->GetId()); } } } } } } // write the list out LArray Pairs; // char *s = 0; // for (int i = Def.First(&s); i; i = Def.Next(&s)) for (auto i : Def) { if (ValidStr(i.key) && stricmp(i.key, "IDOK") != 0 && stricmp(i.key, "IDCANCEL") != 0 && stricmp(i.key, "IDC_STATIC") != 0 && stricmp(i.key, "-1") != 0) { DefinePair &p = Pairs.New(); p.Name = i.key; p.Value = i.value; } } Pairs.Sort(PairCmp); for (int n=0; n=' ' && (uint8_t)(c) <= 127) if (IsPrintable(s[0]) && IsPrintable(s[1]) && IsPrintable(s[2]) && IsPrintable(s[3])) { #ifndef __BIG_ENDIAN__ int32 i = LgiSwap32(p.Value); memcpy(s, &i, 4); #endif Defs.Print("#define %s%s'%04.4s'\r\n", p.Name, Tab, s); } else Defs.Print("#define %s%s%i\r\n", p.Name, Tab, p.Value); } } return Status; } bool AppWnd::SaveLgi(const char *FileName) { bool Status = false; if (!TestLgi()) { if (LgiMsg(this, "Do you want to save the file with errors?", AppName, MB_YESNO) == IDNO) return false; } // Rename the existing file to 'xxxxxx.bak' if (LFileExists(FileName)) { char Bak[MAX_PATH_LEN]; strcpy_s(Bak, sizeof(Bak), FileName); char *e = LGetExtension(Bak); if (e) { strcpy(e, "bak"); if (LFileExists(Bak)) FileDev->Delete(Bak, false); FileDev->Move(FileName, Bak); } } // Save the file to xml if (FileName) { LFile f; LFile::Path DefsName = FileName; DefsName += "../resdefs.h"; LStringPipe Defs; if (f.Open(FileName, O_WRITE)) { SerialiseContext Ctx; f.SetSize(0); Defs.SetSize(0); Defs.Print("// Generated by LgiRes\r\n\r\n"); List l; if (ListObjects(l)) { // Remove all duplicate symbol Id's from the dialogs for (auto r: l) { ResDialog *Dlg = dynamic_cast(r); if (Dlg) Dlg->CleanSymbols(); } // write defines WriteDefines(Defs); LXmlTag Root("resources"); // Write all string lists out first so that when we load objects // back in again the strings will already be loaded and can // be referenced for (auto r: l) { if (r->Type() == TYPE_STRING) { LXmlTag *c = new LXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { LAssert(0); DeleteObj(c); } } } // now write the rest of the objects out for (auto r: l) { if (r->Type() != TYPE_STRING) { LXmlTag *c = new LXmlTag; if (c && r->Write(c, Ctx)) { Root.InsertTag(c); } else { r->Write(c, Ctx); LAssert(0); DeleteObj(c); } } } // Set the offset type. // // Older versions of LgiRes stored the dialog's controls at a fixed // offset (3,17) from where they should've been. That was fixed, but // to differentiate between the 2 systems, we store a tag at the // root element. Root.SetAttr("Offset", 1); LXmlTree Tree(GXT_NO_ENTITIES); Status = Tree.Write(&Root, &f); if (Status) { // Also write the header... but only if it's changed... auto DefsContent = Defs.NewLStr(); LAutoString OldDefsContent(LReadTextFile(DefsName)); if (Strcmp(DefsContent.Get(), OldDefsContent.Get())) { LFile DefsFile; if (!DefsFile.Open(DefsName, O_WRITE)) goto FileErrorMsg; DefsFile.SetSize(0); DefsFile.Write(DefsContent); } } } } else { FileErrorMsg: LgiMsg(this, "Couldn't open these files for output:\n" "\t%s\n" "\t%s\n" "\n" "Maybe they are read only or locked by another application.", AppName, MB_OK, FileName, DefsName.GetFull().Get()); } } return Status; } // Win32 load/save #define ADJUST_CTRLS_X 2 #define ADJUST_CTRLS_Y 12 #define IMP_MODE_SEARCH 0 #define IMP_MODE_DIALOG 1 #define IMP_MODE_DLG_CTRLS 2 #define IMP_MODE_STRINGS 3 #define IMP_MODE_MENU 4 class ImportDefine { public: char *Name; char *Value; ImportDefine() { Name = Value = 0; } ImportDefine(char *Line) { Name = Value = 0; if (Line && *Line == '#') { Line++; if (strnicmp(Line, "define", 6) == 0) { Line += 6; Line = LSkipDelim(Line); char *Start = Line; const char *WhiteSpace = " \r\n\t"; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } Name = NewStr(Start, Line-Start); Line = LSkipDelim(Line); Start = Line; while (*Line && !strchr(WhiteSpace, *Line)) { Line++; } if (Start != Line) { Value = NewStr(Start, Line-Start); } } } } ~ImportDefine() { DeleteArray(Name); DeleteArray(Value); } }; class DefineList : public List { int NestLevel; public: bool Defined; List IncludeDirs; DefineList() { Defined = true; NestLevel = 0; } ~DefineList() { for (auto i: *this) { DeleteObj(i); } for (auto c: IncludeDirs) { DeleteArray(c); } } void DefineSymbol(const char *Name, const char *Value = 0) { ImportDefine *Def = new ImportDefine; if (Def) { Def->Name = NewStr(Name); if (Value) Def->Value = NewStr(Value); Insert(Def); } } ImportDefine *GetDefine(char *Name) { if (Name) { for (auto i: *this) { if (i->Name && stricmp(i->Name, Name) == 0) { return i; } } } return NULL; } void ProcessLine(char *Line) { if (NestLevel > 16) { return; } if (Line && *Line == '#') { Line++; LToken T(Line); if (T.Length() > 0) { if (stricmp(T[0], "define") == 0) // #define { ImportDefine *Def = new ImportDefine(Line-1); if (Def) { if (Def->Name) { Insert(Def); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "include") == 0) // #include { NestLevel++; LFile F; if (T.Length() > 1) { for (auto IncPath: IncludeDirs) { char FullPath[256]; strcpy(FullPath, IncPath); if (FullPath[strlen(FullPath)-1] != DIR_CHAR) { strcat(FullPath, DIR_STR); } strcat(FullPath, T[1]); if (F.Open(FullPath, O_READ)) { char Line[1024]; while (!F.Eof()) { F.ReadStr(Line, sizeof(Line)); char *p = LSkipDelim(Line); if (*p == '#') { ProcessLine(p); } } break; } } } NestLevel--; } else if (stricmp(T[0], "if") == 0) // #if { } else if (stricmp(T[0], "ifdef") == 0) // #if { if (T.Length() > 1) { Defined = GetDefine(T[1]) != 0; } } else if (stricmp(T[0], "endif") == 0) // #endif { Defined = true; } else if (stricmp(T[0], "pragma") == 0) { ImportDefine *Def = new ImportDefine; if (Def) { char *Str = Line + 7; char *First = strchr(Str, '('); char *Second = (First) ? strchr(First+1, ')') : 0; if (First && Second) { Insert(Def); Def->Name = NewStr(Str, First-Str); First++; Def->Value = NewStr(First, Second-First); } else { DeleteObj(Def); } } } else if (stricmp(T[0], "undef") == 0) { } } } // else it's not for us anyway } }; void TokLine(LArray &T, char *Line) { if (Line) { // Exclude comments for (int k=0; Line[k]; k++) { if (Line[k] == '/' && Line[k+1] == '/') { Line[k] = 0; break; } } // Break into tokens for (const char *s = Line; s && *s; ) { while (*s && strchr(" \t", *s)) s++; char *t = LTokStr(s); if (t) T.Add(t); else break; } } } void AppWnd::LoadWin32(const char *FileName) { bool Status = false; LHashTbl,bool> CtrlNames; CtrlNames.Add("LTEXT", true); CtrlNames.Add("EDITTEXT", true); CtrlNames.Add("COMBOBOX", true); CtrlNames.Add("SCROLLBAR", true); CtrlNames.Add("GROUPBOX", true); CtrlNames.Add("PUSHBUTTON", true); CtrlNames.Add("DEFPUSHBUTTON", true); CtrlNames.Add("CONTROL", true); CtrlNames.Add("ICON", true); CtrlNames.Add("LISTBOX", true); Empty(); auto Load = [&](const char *FileName) { if (!FileName) return; LProgressDlg Progress(this); Progress.SetDescription("Initializing..."); Progress.SetType("K"); Progress.SetScale(1.0/1024.0); char *FileTxt = LReadTextFile(FileName); if (FileTxt) { LToken Lines(FileTxt, "\r\n"); DeleteArray(FileTxt); DefineList Defines; ResStringGroup *String = new ResStringGroup(this); int Mode = IMP_MODE_SEARCH; // Language char *Language = 0; LLanguageId LanguageId = 0; // Dialogs List DlLList; CtrlDlg *Dlg = 0; // Menus ResDialog *Dialog = 0; ResMenu *Menu = 0; List Menus; ResMenuItem *MenuItem[32]; int MenuLevel = 0; bool MenuNewLang = false; int MenuNextItem = 0; // Include defines char IncPath[256]; strcpy(IncPath, FileName); LTrimDir(IncPath); Defines.IncludeDirs.Insert(NewStr(IncPath)); Defines.DefineSymbol("_WIN32"); Defines.DefineSymbol("IDC_STATIC", "-1"); DoEvery Ticker(200); Progress.SetDescription("Reading resources..."); Progress.SetRange(Lines.Length()); if (String) { InsertObject(TYPE_STRING, String, false); } for (int CurLine = 0; CurLine < Lines.Length(); CurLine++) { if (Ticker.DoNow()) { Progress.Value(CurLine); } // Skip white space char *Line = Lines[CurLine]; char *p = LSkipDelim(Line); Defines.ProcessLine(Line); // Tokenize LArray T; TokLine(T, Line); // Process line if (Defines.Defined) { switch (Mode) { case IMP_MODE_SEARCH: { DeleteObj(Dialog); Dlg = 0; if (*p != '#') { if (T.Length() > 1 && (stricmp(T[1], "DIALOG") == 0 || stricmp(T[1], "DIALOGEX") == 0)) { Mode = IMP_MODE_DIALOG; Dialog = new ResDialog(this); if (Dialog) { Dialog->Create(NULL, NULL); Dialog->Name(T[0]); auto It = Dialog->IterateViews(); Dlg = dynamic_cast(It[0]); if (Dlg) { int Pos[4] = {0, 0, 0, 0}; int i = 0; for (; iResDialogCtrl::SetPos(r); Dlg->GetStr()->SetDefine(T[0]); ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Dlg->GetStr()->SetId(atoi(Def->Value)); } } } break; } if (T.Length() > 1 && stricmp(T[1], "MENU") == 0) { ZeroObj(MenuItem); Mode = IMP_MODE_MENU; Menu = 0; // Check for preexisting menu in another language MenuNewLang = false; for (auto m: Menus) { if (stricmp(m->Name(), T[0]) == 0) { MenuNewLang = true; Menu = m; break; } } // If it doesn't preexist then create it if (!Menu) { Menu = new ResMenu(this); if (Menu) { Menus.Insert(Menu); Menu->Create(NULL, NULL); Menu->Name(T[0]); } } break; } if (T.Length() > 0 && stricmp(T[0], "STRINGTABLE") == 0) { Mode = IMP_MODE_STRINGS; if (String) { String->Name("_Win32 Imports_"); } break; } if (T.Length() > 2 && stricmp(T[0], "LANGUAGE") == 0) { LanguageId = 0; DeleteArray(Language); char *Language = NewStr(T[1]); if (Language) { LLanguage *Info = LFindLang(0, Language); if (Info) { LanguageId = Info->Id; ResDialog::AddLanguage(Info->Id); } } break; } } break; } case IMP_MODE_DIALOG: { if (T.Length() > 0 && Dlg) { if (stricmp(T[0], "CAPTION") == 0) { char *Caption = T[1]; if (Caption) { Dlg->GetStr()->Set(Caption, LanguageId); } } else if (stricmp(T[0], "BEGIN") == 0) { Mode = IMP_MODE_DLG_CTRLS; } } break; } case IMP_MODE_DLG_CTRLS: { char *Type = T[0]; if (!Type) break; if (stricmp(Type, "end") != 0) { // Add wrapped content to the token array. char *Next = Lines[CurLine+1]; if (Next) { Next = LSkipDelim(Next); char *NextTok = LTokStr((const char*&)Next); if (NextTok) { if (stricmp(NextTok, "END") != 0 && !CtrlNames.Find(NextTok)) { TokLine(T, Lines[++CurLine]); } DeleteArray(NextTok); } } } // Process controls if (stricmp(Type, "LTEXT") == 0) { if (T.Length() >= 7) { CtrlText *Ctrl = new CtrlText(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "EDITTEXT") == 0) { if (T.Length() >= 7) { CtrlEditbox *Ctrl = new CtrlEditbox(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "COMBOBOX") == 0) { if (T.Length() >= 6) { CtrlComboBox *Ctrl = new CtrlComboBox(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "SCROLLBAR") == 0) { if (T.Length() == 6) { CtrlScrollBar *Ctrl = new CtrlScrollBar(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "GROUPBOX") == 0) { if (T.Length() >= 7) { CtrlGroup *Ctrl = new CtrlGroup(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "PUSHBUTTON") == 0 || stricmp(Type, "DEFPUSHBUTTON") == 0) { if (T.Length() >= 7) { CtrlButton *Ctrl = new CtrlButton(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->Set(T[1], LanguageId); Ctrl->GetStr()->SetDefine(T[2]); ImportDefine *Def = Defines.GetDefine(T[2]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "CONTROL") == 0) { if (T.Length() >= 7) { char *Caption = T[1]; char *Id = T[2]; char *Type = T[3]; bool Checkbox = false; bool Radio = false; bool Done = false; // loop through styles int i; for (i=4; !Done && iSetPos(r); if (Caption) Ctrl->GetStr()->Set(Caption, LanguageId); if (Id) Ctrl->GetStr()->SetDefine(Id); ImportDefine *Def = Defines.GetDefine(Id); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } Dlg->AddView(Ctrl->View()); } } } } else if (stricmp(Type, "ICON") == 0) { if (T.Length() >= 7) { CtrlBitmap *Ctrl = new CtrlBitmap(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y); r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl->View()); } } } else if (stricmp(Type, "LISTBOX") == 0) { if (T.Length() >= 7) { CtrlList *Ctrl = new CtrlList(Dialog, 0); if (Ctrl) { Ctrl->GetStr()->SetDefine(T[1]); ImportDefine *Def = Defines.GetDefine(T[1]); if (Def) { Ctrl->GetStr()->SetId(atoi(Def->Value)); } LRect r; r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y); r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y); Ctrl->ResDialogCtrl::SetPos(r); Dlg->AddView(Ctrl); } } } else if (stricmp(Type, "END") == 0) { // search for an existing dialog resource in // another language ResDialog *Match = 0; CtrlDlg *MatchObj = 0; for (auto d: DlLList) { auto It = d->IterateViews(); LViewI *Wnd = It[0]; if (Wnd) { CtrlDlg *Obj = dynamic_cast(Wnd); if (Obj) { if (Obj->GetStr()->GetId() == Dlg->GetStr()->GetId()) { MatchObj = Obj; Match = d; break; } } } } if (Match) { // Merge the controls from "Dlg" to "MatchObj" List Old; List New; Dlg->ListChildren(New); MatchObj->ListChildren(Old); // add the language strings for the caption // without clobbering the languages already // present for (auto s: Dlg->GetStr()->Items) { if (!MatchObj->GetStr()->Get(s->GetLang())) { MatchObj->GetStr()->Set(s->GetStr(), s->GetLang()); } } for (auto c: New) { ResDialogCtrl *MatchCtrl = 0; // try matching by Id { for (auto Mc: Old) { if (Mc->GetStr()->GetId() == c->GetStr()->GetId() && Mc->GetStr()->GetId() > 0) { MatchCtrl = Mc; break; } } } // ok no Id match, match by location and type if (!MatchCtrl) { List Overlapping; for (auto Mc: Old) { LRect a = Mc->View()->GetPos(); LRect b = c->View()->GetPos(); LRect c = a; c.Bound(&b); if (c.Valid()) { int Sa = a.X() * a.Y(); int Sb = b.X() * b.Y(); int Sc = c.X() * c.Y(); int Total = Sa + Sb - Sc; double Amount = (double) Sc / (double) Total; if (Amount > 0.5) { // mostly similar in size Overlapping.Insert(Mc); } } } if (Overlapping.Length() == 1) { MatchCtrl = Overlapping[0]; } } if (MatchCtrl) { // woohoo we are cool for (auto s: c->GetStr()->Items) { MatchCtrl->GetStr()->Set(s->GetStr(), s->GetLang()); } } } // Delete the duplicate OnObjSelect(0); DeleteObj(Dialog); } else { // Insert the dialog InsertObject(TYPE_DIALOG, Dialog, false); DlLList.Insert(Dialog); } Dialog = 0; Dlg = 0; Status = true; Mode = IMP_MODE_SEARCH; } break; } case IMP_MODE_STRINGS: { if (stricmp(T[0], "BEGIN") == 0) { } else if (stricmp(T[0], "END") == 0) { Status = true; Mode = IMP_MODE_SEARCH; } else { if (T.Length() > 1) { ResString *Str = String->FindName(T[0]); if (!Str) { Str = String->CreateStr(); if (Str) { ImportDefine *Def = Defines.GetDefine(T[0]); if (Def) { Str->SetId(atoi(Def->Value)); } Str->SetDefine(T[0]); Str->UnDuplicate(); } } if (Str) { // get the language LLanguage *Lang = LFindLang(Language); LLanguageId SLang = (Lang) ? Lang->Id : (char*)"en"; StrLang *s = 0; // look for language present in string object for (auto ss: Str->Items) { if (*ss == SLang) { s = ss; break; } } // if not present then add it if (!s) { s = new StrLang; if (s) { Str->Items.Insert(s); s->SetLang(SLang); } } // set the value if (s) { s->SetStr(T[1]); } } } } break; } case IMP_MODE_MENU: { if (T.Length() >= 1) { if (stricmp(T[0], "BEGIN") == 0) { MenuLevel++; } else if (stricmp(T[0], "END") == 0) { MenuLevel--; if (MenuLevel == 0) { Status = true; Mode = IMP_MODE_SEARCH; Menu->SetLanguages(); if (!MenuNewLang) { InsertObject(TYPE_MENU, Menu, false); } Menu = 0; } } else { ResMenuItem *i = 0; char *Text = T[1]; if (Text) { if (stricmp(T[0], "POPUP") == 0) { if (MenuNewLang) { LTreeItem *Ri = 0; if (MenuItem[MenuLevel]) { Ri = MenuItem[MenuLevel]->GetNext(); } else { if (MenuLevel == 1) { Ri = Menu->ItemAt(0); } else if (MenuItem[MenuLevel-1]) { Ri = MenuItem[MenuLevel-1]->GetChild(); } } if (Ri) { // Seek up to the next submenu while (!Ri->GetChild()) { Ri = Ri->GetNext(); } i = dynamic_cast(Ri); // char *si = i->Str.Get("en"); if (i) { MenuItem[MenuLevel] = i; } } } else { MenuItem[MenuLevel] = i = new ResMenuItem(Menu); } if (i) { LLanguage *Lang = LFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } MenuNextItem = 0; } else if (stricmp(T[0], "MENUITEM") == 0) { if (MenuNewLang) { if (MenuItem[MenuLevel-1] && T.Length() > 2) { ImportDefine *id = Defines.GetDefine(T[2]); if (id) { int Id = atoi(id->Value); int n = 0; for (LTreeItem *o = MenuItem[MenuLevel-1]->GetChild(); o; o = o->GetNext(), n++) { ResMenuItem *Res = dynamic_cast(o); if (Res && Res->GetStr()->GetId() == Id) { i = Res; break; } } } } MenuNextItem++; } else { i = new ResMenuItem(Menu); } if (i) { if (stricmp(Text, "SEPARATOR") == 0) { // Set separator i->Separator(true); } else { if (!MenuNewLang) { // Set Id i->GetStr()->SetDefine(T[2]); if (i->GetStr()->GetDefine()) { ImportDefine *id = Defines.GetDefine(i->GetStr()->GetDefine()); if (id) { i->GetStr()->SetId(atoi(id->Value)); i->GetStr()->UnDuplicate(); } } } // Set Text LLanguage *Lang = LFindLang(Language); i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en"); } } } } if (i && !MenuNewLang) { if (MenuLevel == 1) { Menu->Insert(i); } else if (MenuItem[MenuLevel-1]) { MenuItem[MenuLevel-1]->Insert(i); } } } } break; } } } T.DeleteArrays(); } DeleteObj(Dialog); if (String->Length() > 0) { String->SetLanguages(); } } Invalidate(); }; if (FileName) Load(FileName); else { auto Select = new LFileSelect(this); Select->Type("Win32 Resource Script", "*.rc"); Select->Open([&](auto dlg, auto status) { if (status) Load(dlg->Name()); delete dlg; }); } } bool AppWnd::SaveWin32() { return false; } ///////////////////////////////////////////////////////////////////////// ResFrame::ResFrame(Resource *child) { Child = child; Name("ResFrame"); } ResFrame::~ResFrame() { if (Child) { Child->App()->OnObjSelect(NULL); Child->Wnd()->Detach(); } } void ResFrame::OnFocus(bool b) { Child->Wnd()->Invalidate(); } bool ResFrame::Attach(LViewI *p) { bool Status = LLayout::Attach(p); if (Status && Child) { Child->Attach(this); Child->Wnd()->Visible(true); } return Status; } bool ResFrame::Pour(LRegion &r) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } return false; } bool ResFrame::OnKey(LKey &k) { bool Status = false; if (k.Down() && Child) { switch (k.c16) { case LK_DELETE: { if (k.Shift()) { Child->Copy(true); } else { Child->Delete(); } Status = true; break; } case 'x': case 'X': { if (k.Ctrl()) { Child->Copy(true); Status = true; } break; } case 'c': case 'C': { if (k.Ctrl()) { Child->Copy(); Status = true; } break; } case LK_INSERT: { if (k.Ctrl()) { Child->Copy(); } else if (k.Shift()) { Child->Paste(); } Status = true; break; } case 'v': case 'V': { if (k.Ctrl()) { Child->Paste(); Status = true; } break; } } } return Child->Wnd()->OnKey(k) || Status; } void ResFrame::OnPaint(LSurface *pDC) { // Draw nice frame LRect r(0, 0, X()-1, Y()-1); LThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); LFlatBorder(pDC, r, 4); LWideBorder(pDC, r, DefaultSunkenEdge); // Set the child to the client area Child->Wnd()->SetPos(r); // Draw the dialog & controls LView::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// LgiFunc char *_LgiGenLangLookup(); #include "lgi/common/AutoPtr.h" #include "lgi/common/Variant.h" #include "lgi/common/Css.h" #include "lgi/common/TableLayout.h" class Foo : public LLayoutCell { public: Foo() { TextAlign(AlignLeft); } bool Add(LView *v) { return false; } bool Remove(LView *v) { return false; } }; ////////////////////////////////////////////////////////////////////// ShortCutView::ShortCutView(AppWnd *app) { App = app; LRect r(0, 0, 500, 600); SetPos(r); MoveSameScreen(App); Name("Dialog Shortcuts"); if (Attach(0)) { Lst = new LList(100, 0, 0, 100, 100); Lst->Attach(this); Lst->SetPourLargest(true); Lst->AddColumn("Key", 50); Lst->AddColumn("Ref", 80); Lst->AddColumn("CtrlId", 80); Lst->AddColumn("Name", 150); Visible(true); } } ShortCutView::~ShortCutView() { App->OnCloseView(this); } enum ShortCutCol { ColKey, ColRefId, ColCtrlId, ColName }; void FindMenuKeys(LList *out, ResMenu *menu) { if (!out || !menu) return; LArray items; menu->EnumItems(items); for (auto i: items) { auto sc = i->Shortcut(); if (sc) { auto item = new LListItem(sc); LString ref, ctrl; ref.Printf("%i", i->GetStr()->GetRef()); ctrl.Printf("%i", i->GetStr()->GetId()); item->SetText(ref, ColRefId); item->SetText(ctrl, ColCtrlId); item->SetText(i->GetStr()->Get(), ColName); item->_UserPtr = i; out->Insert(item); } } } void FindShortCuts(LList *Out, LViewI *In) { for (auto c: In->IterateViews()) { auto rdc = dynamic_cast(c); if (!rdc || !rdc->GetStr()) continue; auto n = rdc->GetStr()->Get(); if (n) { char *a = strchr(n, '&'); if (a && a[1] != '&') { LListItem *li = new LListItem; LString s(++a, 1); LString ref, ctrl; ref.Printf("%i", rdc->GetStr()->GetRef()); ctrl.Printf("%i", rdc->GetStr()->GetId()); li->SetText(s.Upper(), ColKey); li->SetText(ref, ColRefId); li->SetText(ctrl, ColCtrlId); li->SetText(rdc->GetClass(), ColName); li->_UserPtr = rdc; Out->Insert(li); } } FindShortCuts(Out, c); } } int ShortCutView::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == Lst->GetId()) { switch (n.Type) { case LNotifyItemClick: { auto li = Lst->GetSelected(); if (!li) break; LString s = li->GetText(1); ResObject *c = (ResObject*) li->_UserPtr; if (!c) break; if (auto ctrl = dynamic_cast(c)) App->GotoObject(ctrl->GetStr(), NULL, ctrl->GetDlg(), NULL, ctrl); else if (auto mi = dynamic_cast(c)) App->GotoObject(mi->GetStr(), NULL, NULL, mi, NULL); else LAssert(!"Impl me."); break; } default: break; } } return LWindow::OnNotify(Ctrl, n); } void ShortCutView::OnResource(Resource *r) { Lst->Empty(); if (!r) return; if (auto dlg = dynamic_cast(r)) FindShortCuts(Lst, dlg); else if (auto menu = dynamic_cast(r)) FindMenuKeys(Lst, menu); Lst->Sort(0); Lst->ResizeColumnsToContent(); } ShortCutView *AppWnd::GetShortCutView() { return ShortCuts; } void AppWnd::OnCloseView(ShortCutView *v) { if (v == ShortCuts) ShortCuts = NULL; } ////////////////////////////////////////////////////////////////////// void TestFunc() { /* Foo c; uint32 *p = (uint32*)&c; p++; p = (uint32*)(*p); void *method = (void*)p[2]; for (int i=0; i<16; i++) { printf("[%i]=%p\n", i, p[i]); } c.OnChange(LCss::PropBackground); */ } int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, "LgiRes"); if (a.IsOk()) { if ((a.AppWnd = new AppWnd)) { TestFunc(); a.AppWnd->Visible(true); a.Run(); } } return 0; } diff --git a/include/lgi/common/ColourSpace.h b/include/lgi/common/ColourSpace.h --- a/include/lgi/common/ColourSpace.h +++ b/include/lgi/common/ColourSpace.h @@ -1,591 +1,591 @@ #ifndef _G_COLOUR_SPACE_H_ #define _G_COLOUR_SPACE_H_ /// Colour component type enum LComponentType { CtNone, // 0 CtIndex, // 1 CtRed, // 2 CtGreen, // 3 CtBlue, // 4 CtAlpha, // 5 CtPad, // 6 CtHue, // 7 CtSaturation, // 8 CtLuminance, // 9 CtCyan, // 10 CtMagenta, // 11 CtYellow, // 12 CtBlack // 13 }; // Component construction: 4bits type, 4bits size. 8 bits per component. #define GDC_COLOUR_SPACE_1(type, size) \ ((type << 4) | (size)) #define GDC_COLOUR_SPACE_3(t1, s1, t2, s2, t3, s3) \ ( \ ((t1 << 20) | (s1 << 16)) | \ ((t2 << 12) | (s2 << 8)) | \ ((t3 << 4) | (s3 << 0)) \ ) #define GDC_COLOUR_SPACE_4(t1, s1, t2, s2, t3, s3, t4, s4) \ ( \ ((t1 << 28) | (s1 << 24)) | \ ((t2 << 20) | (s2 << 16)) | \ ((t3 << 12) | (s3 << 8)) | \ ((t4 << 4) | (s4 << 0)) \ ) /// Defines a specific colour space enum LColourSpace { CsNone = 0, // uint8_t types CsIndex1 = GDC_COLOUR_SPACE_1(CtIndex, 1), // Monochrome CsIndex4 = GDC_COLOUR_SPACE_1(CtIndex, 4), // 16 colour CsIndex8 = GDC_COLOUR_SPACE_1(CtIndex, 8), // 256 colour CsAlpha8 = GDC_COLOUR_SPACE_1(CtAlpha, 8), // Alpha channel only // uint16 types CsArgb15 = GDC_COLOUR_SPACE_4(CtAlpha, 1, CtRed, 5, CtGreen, 5, CtBlue, 5), CsRgb15 = GDC_COLOUR_SPACE_3(CtRed, 5, CtGreen, 5, CtBlue, 5), CsAbgr15 = GDC_COLOUR_SPACE_4(CtAlpha, 1, CtBlue, 5, CtGreen, 5, CtRed, 5), CsBgr15 = GDC_COLOUR_SPACE_3(CtBlue, 5, CtGreen, 5, CtRed, 5), CsRgb16 = GDC_COLOUR_SPACE_3(CtRed, 5, CtGreen, 6, CtBlue, 5), CsBgr16 = GDC_COLOUR_SPACE_3(CtBlue, 5, CtGreen, 6, CtRed, 5), // 24 bit types CsRgb24 = GDC_COLOUR_SPACE_3(CtRed, 8, CtGreen, 8, CtBlue, 8), CsBgr24 = GDC_COLOUR_SPACE_3(CtBlue, 8, CtGreen, 8, CtRed, 8), // 24 bits with padding CsRgbx32 = GDC_COLOUR_SPACE_4(CtRed, 8, CtGreen, 8, CtBlue, 8, CtPad, 8), CsBgrx32 = GDC_COLOUR_SPACE_4(CtBlue, 8, CtGreen, 8, CtRed, 8, CtPad, 8), CsXrgb32 = GDC_COLOUR_SPACE_4(CtPad, 8, CtRed, 8, CtGreen, 8, CtBlue, 8), CsXbgr32 = GDC_COLOUR_SPACE_4(CtPad, 8, CtBlue, 8, CtGreen, 8, CtRed, 8), // 32 bit types CsRgba32 = GDC_COLOUR_SPACE_4(CtRed, 8, CtGreen, 8, CtBlue, 8, CtAlpha, 8), CsBgra32 = GDC_COLOUR_SPACE_4(CtBlue, 8, CtGreen, 8, CtRed, 8, CtAlpha, 8), CsArgb32 = GDC_COLOUR_SPACE_4(CtAlpha, 8, CtRed, 8, CtGreen, 8, CtBlue, 8), CsAbgr32 = GDC_COLOUR_SPACE_4(CtAlpha, 8, CtBlue, 8, CtGreen, 8, CtRed, 8), // 16 bit component depth (size==0 means 16 bit) CsBgr48 = GDC_COLOUR_SPACE_3(CtBlue, 0, CtGreen, 0, CtRed, 0), CsRgb48 = GDC_COLOUR_SPACE_3(CtRed, 0, CtGreen, 0, CtBlue, 0), CsBgra64 = GDC_COLOUR_SPACE_4(CtBlue, 0, CtGreen, 0, CtRed, 0, CtAlpha, 0), CsRgba64 = GDC_COLOUR_SPACE_4(CtRed, 0, CtGreen, 0, CtBlue, 0, CtAlpha, 0), CsAbgr64 = GDC_COLOUR_SPACE_4(CtAlpha, 0, CtBlue, 0, CtGreen, 0, CtRed, 0), CsArgb64 = GDC_COLOUR_SPACE_4(CtAlpha, 0, CtRed, 0, CtGreen, 0, CtBlue, 0), // other colour spaces CsHls32 = GDC_COLOUR_SPACE_4(CtHue, 0 /*16*/, CtRed, 8, CtGreen, 8, CtBlue, 8), CsCmyk32 = GDC_COLOUR_SPACE_4(CtCyan, 8, CtMagenta, 8, CtYellow, 8, CtBlack, 8), }; #ifdef WIN32 #pragma pack(push, before_pack) #pragma pack(1) #endif #define LEAST_SIG_BIT_FIRST 1 #define LEAST_SIG_BYTE_FIRST 1 struct LRgb15 { #if LEAST_SIG_BIT_FIRST uint16_t b : 5; uint16_t g : 5; uint16_t r : 5; uint16_t pad : 1; #else uint16 pad : 1; uint16 r : 5; uint16 g : 5; uint16 b : 5; #endif }; struct LArgb15 { #if LEAST_SIG_BIT_FIRST uint16_t b : 5; uint16_t g : 5; uint16_t r : 5; uint16_t a : 1; #else uint16 a : 1; uint16 r : 5; uint16 g : 5; uint16 b : 5; #endif }; struct LBgr15 { #if LEAST_SIG_BIT_FIRST uint16_t r : 5; uint16_t g : 5; uint16_t b : 5; uint16_t pad : 1; #else uint16 pad : 1; uint16 b : 5; uint16 g : 5; uint16 r : 5; #endif }; struct LAbgr15 { #if LEAST_SIG_BIT_FIRST uint16_t r : 5; uint16_t g : 5; uint16_t b : 5; uint16_t a : 1; #else uint16 a : 1; uint16 b : 5; uint16 g : 5; uint16 r : 5; #endif }; struct LRgb16 { #if LEAST_SIG_BIT_FIRST uint16_t b : 5; uint16_t g : 6; uint16_t r : 5; #else uint16 r : 5; uint16 g : 6; uint16 b : 5; #endif }; struct LBgr16 { #if LEAST_SIG_BIT_FIRST uint16_t r : 5; uint16_t g : 6; uint16_t b : 5; #else uint16 b : 5; uint16 g : 6; uint16 r : 5; #endif }; struct LRgb24 { #if LEAST_SIG_BYTE_FIRST uint8_t r, g, b; #else uint8_t b, g, r; #endif }; struct LBgr24 { #if LEAST_SIG_BYTE_FIRST uint8_t b, g, r; #else uint8_t r, g, b; #endif }; struct LRgba32 { #if LEAST_SIG_BYTE_FIRST uint8_t r, g, b, a; #else uint8_t a, b, g, r; #endif }; struct LBgra32 { #if LEAST_SIG_BYTE_FIRST uint8_t b, g, r, a; #else uint8_t a, r, g, b; #endif }; struct LArgb32 { #if LEAST_SIG_BYTE_FIRST uint8_t a, r, g, b; #else uint8_t b, g, r, a; #endif }; struct LAbgr32 { #if LEAST_SIG_BYTE_FIRST uint8_t a, b, g, r; #else uint8_t r, g, b, a; #endif }; struct LXrgb32 { #if LEAST_SIG_BYTE_FIRST uint8_t pad, r, g, b; #else uint8_t b, g, r, pad; #endif }; struct LRgbx32 { #if LEAST_SIG_BYTE_FIRST uint8_t r, g, b, pad; #else uint8_t pad, b, g, r; #endif }; struct LXbgr32 { #if LEAST_SIG_BYTE_FIRST uint8_t pad, b, g, r; #else uint8_t r, g, b, pad; #endif }; struct LBgrx32 { #if LEAST_SIG_BYTE_FIRST uint8_t b, g, r, pad; #else uint8_t pad, r, g, b; #endif }; #ifdef WIN32 #pragma pack(push, 2) #endif struct LRgb48 { #if LEAST_SIG_BYTE_FIRST uint16_t r, g, b; #else uint16 b, g, r; #endif }; struct LBgr48 { #if LEAST_SIG_BYTE_FIRST uint16_t b, g, r; #else uint16 r, g, b; #endif }; struct LRgba64 { #if LEAST_SIG_BYTE_FIRST uint16_t r, g, b, a; #else uint16 a, b, g, r; #endif }; struct LBgra64 { #if LEAST_SIG_BYTE_FIRST uint16_t b, g, r, a; #else uint16 a, r, g, b; #endif }; struct LArgb64 { #if LEAST_SIG_BYTE_FIRST uint16_t a, r, g, b; #else uint16 b, g, r, a; #endif }; struct LAbgr64 { #if LEAST_SIG_BYTE_FIRST uint16_t a, b, g, r; #else uint16 r, g, b, a; #endif }; #ifdef WIN32 #pragma pack(pop) #endif struct LHls32 { uint16_t h; uint8_t l, s; }; struct LCmyk32 { #if LEAST_SIG_BYTE_FIRST uint8_t c, m, y, k; #else uint8_t k, y, m, c; #endif }; struct LColourComponent { // ((type << 4) | (size)) uint8_t Bits; LComponentType Type() { return (LComponentType) ((Bits >> 4) & 0xf); } void Type(LComponentType t) { Bits = (Bits & 0xf) | ((uint8_t)t << 4); } uint8_t Size() { return Bits & 0xf; } void Size(uint8_t t) { Bits = (Bits & 0xf0) | (t & 0xf); } }; union LColourSpaceBits { uint32_t All; LColourComponent Bits[4]; LColourComponent &operator[](int i) { static int Reverse = -1; if (Reverse < 0) { LColourSpaceBits t; t.All = 0; t.Bits[0].Type(CtIndex); if (t.All == 0x10) Reverse = 0; else if (t.All == (0x10<<24)) Reverse = 1; else LAssert(0); } if (Reverse == 1) return Bits[3-i]; return Bits[i]; } }; #ifdef WIN32 #pragma pack(pop, before_pack) #endif /// Converts a colour space into a string for debugging/reporting. LgiFunc const char *LColourSpaceToString(LColourSpace cs); /// Works out how many bits required for a pixel in a particular colour space. LgiFunc int LColourSpaceToBits(LColourSpace ColourSpace); /// /returns the number of channels in the colour space LgiFunc int LColourSpaceChannels(LColourSpace Cs); /// /returns true if the colour space has an alpha channel LgiFunc bool LColourSpaceHasAlpha(LColourSpace Cs); /// Converts a bit-depth into the default colour space for that depth. Used mostly /// in interfacing old bit-depth based code to newer colour space code. LgiFunc LColourSpace LBitsToColourSpace(int Bits); /// Converts a string representation into a colour space. LgiFunc LColourSpace LStringToColourSpace(const char *c); /// Tests that the bit and byte ordering of the pixel structures are compiled correctly. /// \return true if #LEAST_SIG_BIT_FIRST and #LEAST_SIG_BYTE_FIRST are correct for this platform. LgiFunc bool LColourSpaceTest(); #ifdef __GTK_H__ /// Converts a GTK visual to a Lgi colour space. LgiFunc LColourSpace GdkVisualToColourSpace(Gtk::GdkVisual *v, int output_bits); #endif // These definitions are provide as a convenience when converting old code to the // LColourSpace system. However you should not use them for new code, as some systems // can have different colour spaces depending on OS version or hardware configuration. #if defined(WIN32) || defined(LINUX) || defined(HAIKU) #define System15BitColourSpace CsBgr15 typedef LBgr15 System15BitPixel; #define System16BitColourSpace CsBgr16 typedef LBgr16 System16BitPixel; #define System24BitColourSpace CsBgr24 typedef LBgr24 System24BitPixel; #define System32BitColourSpace CsBgra32 typedef LBgra32 System32BitPixel; #else #define System15BitColourSpace CsRgb15 typedef LRgb15 System15BitPixel; #define System16BitColourSpace CsRgb16 typedef LRgb16 System16BitPixel; #define System24BitColourSpace CsRgb24 typedef LRgb24 System24BitPixel; #ifdef __GTK_H__ #define System32BitColourSpace CsBgra32 typedef LBgra32 System32BitPixel; #else #define System32BitColourSpace CsRgba32 typedef LRgba32 System32BitPixel; #endif #endif typedef union { uint8_t *u8; uint16_t *u16; uint32_t *u32; int i; LRgb15 *rgb15; LBgr15 *bgr15; LRgb16 *rgb16; LBgr16 *bgr16; LRgb24 *rgb24; LBgr24 *bgr24; LRgba32 *rgba32; LArgb32 *argb32; LHls32 *hls32; LCmyk32 *cmyk32; System15BitPixel *s15; System16BitPixel *s16; System24BitPixel *s24; System32BitPixel *s32; -} GPixelPtr; +} LPixelPtr; /** \brief 32bit colour of varing bit depth. If no associated depth is given, 32 bits is assumed. \code COLOUR typedef description 31 24 23 16 15 8 7 0 |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| Standard: 8bit mode: |------------------------ unused ---------------------| |---- index ----| 15bit mode: |----------------- unused ------------|T|-- red --|-- green ---|-- blue -| 16bit mode: |------------- unused -------------| |-- red --|--- green ----|-- blue -| 24bit mode: |---- unused ---| |----- blue ----| |---- green ----| |----- red -----| 32bit mode: |---- alpha ----| |----- blue ----| |---- green ----| |----- red -----| \endcode Note: In 15bit mode the T bit can toggle between 8-bit indexing and RGB, or it can also just be a 1 bit alpha channel. */ typedef uint32_t COLOUR; /// Alpha value, 0 - transparent, 255 - solid typedef uint8_t ALPHA; // colour conversion defines // RGB colour space #define BitWidth(bits, cropbits) ( (((bits)+(cropbits)-1)/(cropbits)) * 4 ) #define C24R 2 #define C24G 1 #define C24B 0 #define C32A 3 #define C32R 2 #define C32G 1 #define C32B 0 /// Create a 15bit COLOUR from components #define Rgb15(r, g, b) ( ((r&0xF8)<<7) | ((g&0xF8)<<2) | ((b&0xF8)>>3)) #define Rgb32To15(c32) ( ((c32&0xF8)>>3) | ((c32&0xF800)>>6) | ((c32&0xF80000)>>9) ) #define Rgb24To15(c24) ( (B24(c24)>>3) | ((G24(c24)<<2)&0x3E0) | ((R24(c24)<<7)&0x7C00) ) #define Rgb16To15(c16) ( ((c16&0xFFC0)>>1) | (c16&0x1F) ) /// Get the red component of a 15bit COLOUR #define R15(c15) ( (uchar) ((c15>>10)&0x1F) ) /// Get the green component of a 15bit COLOUR #define G15(c15) ( (uchar) ((c15>>5)&0x1F) ) /// Get the blue component of a 15bit COLOUR #define B15(c15) ( (uchar) ((c15)&0x1F) ) #define Rc15(c) ( (((c) & 0x7C00) >> 7) | (((c) & 0x7C00) >> 12) ) #define Gc15(c) ( (((c) & 0x03E0) >> 2) | (((c) & 0x03E0) >> 7) ) #define Bc15(c) ( (((c) & 0x001F) << 3) | (((c) & 0x001F) >> 2) ) /// Create a 16bit COLOUR from components #define Rgb16(r, g, b) ( ((r&0xF8)<<8) | ((g&0xFC)<<3) | ((b&0xF8)>>3)) #define Rgb32To16(c32) ( ((c32&0xF8)>>3) | ((c32&0xFC00)>>5) | ((c32&0xF80000)>>8) ) #define Rgb24To16(c24) ( (B24(c24)>>3) | ((G24(c24)<<3)&0x7E0) | ((R24(c24)<<8)&0xF800) ) #define Rgb15To16(c15) ( ((c15&0x7FE0)<<1) | (c15&0x1F) ) /// Get the red component of a 16bit COLOUR #define R16(c16) ( (uchar) ((c16>>11)&0x1F) ) /// Get the green component of a 16bit COLOUR #define G16(c16) ( (uchar) ((c16>>5)&0x3F) ) /// Get the blue component of a 16bit COLOUR #define B16(c16) ( (uchar) ((c16)&0x1F) ) #define Rc16(c) ( (((c) & 0xF800) >> 8) | (((c) & 0xF800) >> 13) ) #define Gc16(c) ( (((c) & 0x07E0) >> 3) | (((c) & 0x07E0) >> 9) ) #define Bc16(c) ( (((c) & 0x001F) << 3) | (((c) & 0x001F) >> 2) ) #define G5bitTo8bit(c) (uint8_t) ( ((c) << 3) | ((c) >> 2) ) #define G6bitTo8bit(c) (uint8_t) ( ((c) << 2) | ((c) >> 4) ) #define G8bitTo5bit(c) ( (c) >> 3 ) #define G8bitTo6bit(c) ( (c) >> 2 ) #define G8bitTo16bit(c) ( ((uint16)(c) << 8) | ((c) & 0xff) ) /// Get the red component of a 24bit COLOUR #define R24(c24) ( ((c24)>>(C24R*8)) & 0xff ) /// Get the green component of a 24bit COLOUR #define G24(c24) ( ((c24)>>(C24G*8)) & 0xff ) /// Get the blue component of a 24bit COLOUR #define B24(c24) ( ((c24)>>(C24B*8)) & 0xff ) /// Create a 24bit COLOUR from components #define Rgb24(r, g, b) ( (((r)&0xFF) << (C24R*8)) | (((g)&0xFF) << (C24G*8)) | (((b)&0xFF) << (C24B*8)) ) #define Rgb32To24(c32) Rgb24(R32(c32), G32(c32), B32(c32)) #define Rgb15To24(c15) ( ((c15&0x7C00)>>7) | ((c15&0x3E0)<<6) | ((c15&0x1F)<<19) ) #define Rgb16To24(c16) ( ((c16&0xF800)>>8) | ((c16&0x7E0)<<5) | ((c16&0x1F)<<19) ) // 32 // 31 24 23 16 15 8 7 0 // |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| |-|-|-|-|-|-|-|-| // Windows |---- alpha ----| |----- red -----| |---- green ----| |----- blue ----| // Linux |----- blue ----| |---- green ----| |----- red -----| |---- alpha ----| /// Get the red component of a 32bit COLOUR #define R32(c32) ( (uchar) (c32 >> (C32R << 3)) ) /// Get the green component of a 32bit COLOUR #define G32(c32) ( (uchar) (c32 >> (C32G << 3)) ) /// Get the blue component of a 32bit COLOUR #define B32(c32) ( (uchar) (c32 >> (C32B << 3)) ) /// Get the opacity/alpha component of a 32bit COLOUR #define A32(c32) ( (uchar) (c32 >> (C32A << 3)) ) #define RgbPreMul(c, a) ( Div255Lut[(c)*a] ) #define Rgb32(r, g, b) ( ((((uint32_t)r)&0xFF)<<(C32R<<3)) | (((g)&0xFF)<<(C32G<<3)) | (((b)&0xFF)<<(C32B<<3)) | (0xFF<<(C32A<<3)) ) #define Rgba32(r, g, b, a) ( ((((unsigned)r)&0xFF)<<(C32R<<3)) | (((g)&0xFF)<<(C32G<<3)) | (((b)&0xFF)<<(C32B<<3)) | (((a)&0xFF)<<(C32A<<3)) ) #define Rgbpa32(r, g, b, a) ( (RgbPreMul(r, a)<<(C32R<<3)) | (RgbPreMul(g, a)<<(C32G<<3)) | (RgbPreMul(b, a)<<(C32B<<3)) | ((a&0xFF)<<(C32A<<3)) ) #define Rgb24To32(c24) Rgba32( R24(c24), G24(c24), B24(c24), 255 ) #define Rgb15To32(c15) ( ((c15&0x7C00)<<9) | ((c15&0x3E0)<<6) | ((c15&0x1F)<<3) | (0xFF<<(C32A*8)) ) #define Rgb16To32(c16) ( ((c16&0xF800)<<8) | ((c16&0x7E0)<<5) | ((c16&0x1F)<<3) | (0xFF<<(C32A*8)) ) // HLS colour space tools /// Builds a complete 32bit HLS colour from it's components /// /// The hue (H) component varies from 0 to 359 /// The lightness (L) component varies from 0 (black), thru 128 (colour) to 255 (white) /// The saturation (S) component varies from 0 (grey) thru to 255 (full colour) /// /// \sa HlsToRgb(), RgbToHls() #define Hls32(h, l, s) ( ((h)<<16) | ((l)<<8) | (s) ) /// Extracts the hue component from the 32-bit HLS colour /// \sa #Hls32 #define H32(c) ( ((c)>>16)&0xFFFF ) /// Extracts the lightness component from the 32-bit HLS colour /// \sa #Hls32 #define L32(c) ( ((c)>>8)&0xFF ) /// Extracts the saturation component from the 32-bit HLS colour /// \sa #Hls32 #define S32(c) ( (c)&0xFF ) /// The value of an undefined hue /// \sa #Hls32 #define HUE_UNDEFINED 1024 #endif diff --git a/include/lgi/common/DateTime.h b/include/lgi/common/DateTime.h --- a/include/lgi/common/DateTime.h +++ b/include/lgi/common/DateTime.h @@ -1,425 +1,425 @@ /// \file /// \author Matthew Allen /** * \defgroup Time Time and date handling * \ingroup Lgi */ #ifndef __DATE_TIME_H #define __DATE_TIME_H #include #include "lgi/common/StringClass.h" #define GDTF_DEFAULT 0 /// Format the date as DD/MM/YYYY /// \ingroup Time #define GDTF_DAY_MONTH_YEAR 0x001 /// Format the date as MM/DD/YYYY /// \ingroup Time #define GDTF_MONTH_DAY_YEAR 0x002 /// Format the date as YYYY/MM/DD /// \ingroup Time #define GDTF_YEAR_MONTH_DAY 0x004 /// The bit mask for the date /// \ingroup Time #define GDTF_DATE_MASK 0x00f /// Format the time as HH:MM and an am/pm marker /// \ingroup Time #define GDTF_12HOUR 0x010 /// Format the time as 24 hour time /// \ingroup Time #define GDTF_24HOUR 0x020 /// The bit mask for the time /// \ingroup Time #define GDTF_TIME_MASK 0x0f0 /// Format the date with a leading zero /// \ingroup Time #define GDTF_DAY_LEADINGZ 0x100 /// Format the month with a leading zero /// \ingroup Time #define GDTF_MONTH_LEADINGZ 0x200 /// A date/time class /// /// This class interacts with system times represented as 64bit ints. The various OS support different /// formats for that 64bit int values. On windows the system times are in 100-nanosecond intervals since /// January 1, 1601 (UTC), as per the FILETIME structure, on Posix systems (Linux/Mac) the 64bit values /// are in milliseconds since January 1, 1970 UTC. This is just unix time * 1000. If you are serializing /// these 64bit values you should take that into account, they are NOT cross platform. The LDirectory class /// uses the same 64bit values as accepted here for the file's last modified timestamp etc. To convert the /// 64bit values to seconds, divide by LDateTime::Second64Bit, useful for calculating the time in seconds /// between 2 LDateTime objects. /// /// \ingroup Time class LgiClass LDateTime // This class can't have a virtual table, because it's used in // LArray's which initialize with all zero bytes. { /// 1 - DaysInMonth int16 _Day; /// #### int16 _Year; /// Milliseconds: 0-999 int16 _Thousands; /// 1-12 int16 _Month; /// 0-59 int16 _Seconds; /// 0-59 int16 _Minutes; /// 0-23 (24hr) int16 _Hours; /// The current timezone of this object, defaults to the system timezone int16 _Tz; // in minutes (+10 == 600 etc) /// Combination of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 _Format; /// The default formatting of datetimes static uint16 DefaultFormat; /// The default date separator character static char DefaultSeparator; public: LDateTime(const char *Init = NULL); LDateTime(uint64 Ts); LDateTime(const LDateTime &dt) { *this = dt; } ~LDateTime(); /// Resolution of a second when using 64 bit int's /// \sa LDateTime::Get(int64), LDateTime::Set(int64) #ifdef WIN32 static constexpr int64_t Second64Bit = 10000000; #else static constexpr int64_t Second64Bit = 1000; #endif static constexpr int64_t MinuteLength = 60; // seconds static constexpr int64_t HourLength = MinuteLength * 60; // seconds static constexpr int64_t DayLength = HourLength * 24; // seconds static constexpr const char *WeekdaysShort[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static constexpr const char *WeekdaysLong[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; static constexpr const char *MonthsShort[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static constexpr const char *MonthsLong[12] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; // On Posix systems this allows representation of times before 1/1/1970. // The Lgi epoch is considered to be 1/1/1800 instead and this offset converts // from one to the other. static constexpr int64_t Offset1800 = 5364662400; // Seconds from 1/1/1800 to 1/1/1970 /// Returns true if all the components are in a valid range bool IsValid() const; /// Sets the date to an NULL state void Empty(); /// Returns the day int Day() const { return _Day; } /// Sets the day void Day(int d) { _Day = d; } /// Returns the month int Month() const { return _Month; } /// Sets the month void Month(int m) { _Month = m; } /// Sets the month by it's name void Month(char *m); /// Returns the year int Year() const { return _Year; } /// Sets the year void Year(int y) { _Year = y; } /// Returns the millisecond part of the time int Thousands() const { return _Thousands; } /// Sets the millisecond part of the time void Thousands(int t) { _Thousands = t; } /// Returns the seconds part of the time int Seconds() const { return _Seconds; } /// Sets the seconds part of the time void Seconds(int s) { _Seconds = s; } /// Returns the minutes part of the time int Minutes() const { return _Minutes; } /// Sets the minutes part of the time void Minutes(int m) { _Minutes = m; } /// Returns the hours part of the time int Hours() const { return _Hours; } /// Sets the hours part of the time void Hours(int h) { _Hours = h; } /// Returns the timezone of this current date time object in minutes (+10 = 600) int GetTimeZone() const { return _Tz; } /// Returns the timezone in hours double GetTimeZoneHours() const { return (double)_Tz / 60.0; } /// Sets the timezone of this current object.in minutes (+10 = 600) void SetTimeZone ( /// The new timezone int Tz, /// True if you want to convert the date and time to the new zone, /// False if you want to leave the date/time as it is. bool ConvertTime ); /// Set this object to UTC timezone, changing the other members as /// needed LDateTime &ToUtc(bool AssumeLocal = false) { if (AssumeLocal) _Tz = SystemTimeZone(); SetTimeZone(0, true); return *this; } /// \returns the UTC version of this object. LDateTime Utc() const { LDateTime dt = *this; return dt.ToUtc(); } /// Changes the timezone to the local zone, changing other members /// as needed. LDateTime &ToLocal(bool AssumeUtc = false) { if (AssumeUtc) _Tz = 0; SetTimeZone(SystemTimeZone(), true); return *this; } /// \returns the local time version of this object. LDateTime Local() const { LDateTime dt = *this; return dt.ToLocal(); } /// Gets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string. /// \returns a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 GetFormat() const { return _Format; } /// Sets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string void SetFormat ( /// a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 f ) { _Format = f; } /// \returns zero based index of weekday, or -1 if not found. static int IsWeekDay(const char *s); /// \returns zero based index of month, or -1 if not found. static int IsMonth(const char *s); /// The default format for the date when formatted as a string static uint16 GetDefaultFormat(); /// Sets the default format for the date when formatted as a string static void SetDefaultFormat(uint16 f) { DefaultFormat = f; } /// Gets the data and time as a LString LString Get() const; /// Gets the date and time as a string /// \sa LDateTime::GetFormat() void Get(char *Str, size_t SLen) const; /// Gets the data and time as a 64 bit int (os specific) bool Get(uint64 &s) const; /// Gets just the date as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetDate(char *Str, size_t SLen) const; /// Gets just the date as a LString /// \sa LDateTime::GetFormat() LString GetDate() const; /// Gets just the time as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetTime(char *Str, size_t SLen) const; /// Gets just the time as a LString /// \sa LDateTime::GetFormat() LString GetTime() const; /// Returns the 64bit timestamp. uint64 Ts() const; /// Get the timestamp in a format compatible with the current operating system APIs. /// \returns the timestamp or zero on error. uint64_t OsTime() const; bool OsTime(uint64_t ts); /// Get the current time... static LDateTime Now(); /// Sets the date and time to the system clock LDateTime &SetNow(); /// Parses a date time from a string /// \sa LDateTime::GetFormat() bool Set(const char *Str); /// Sets the date and time from a 64 bit int (os specific) bool Set(uint64 s); bool SetUnix(uint64 s); // Assume unix timestamp (seconds since 1/1/1970) /// Sets the time from a time_t bool Set(time_t tt); /// Parses the date from a string /// \sa LDateTime::GetFormat() bool SetDate(const char *Str); /// Parses the time from a string /// \sa LDateTime::GetFormat() bool SetTime(const char *Str); /// Parses the date time from a free form string bool Parse(LString s); /// Describes the perios between this and 'to' in the form: /// ##d ##h ##m ##s /// Order of the dates isn't important. LString DescribePeriod(LDateTime to); static LString DescribePeriod(double seconds); /// \returns true if 'd' is on the same day as this object bool IsSameDay(LDateTime &d) const; /// \returns true if 'd' is on the same month as this object bool IsSameMonth(LDateTime &d) const; /// \returns true if 'd' is on the same year as this object bool IsSameYear(LDateTime &d) const; /// \returns the start of day, same date but time: 00:00:00 LDateTime StartOfDay() const; /// \returns the end of day, same date but time: 23:59:59 LDateTime EndOfDay() const; /// \returns whether a year is a leap year or not bool IsLeapYear ( /// Pass a specific year here, or ignore to return if the current Date/Time is in a leap year. int Year = -1 ) const; /// Returns the day of the week as an index, 0=sun, 1=mon, 2=teus etc int DayOfWeek() const; /// \returns the number of days in the current month int DaysInMonth() const; /// Adds a number of seconds to the current date/time void AddSeconds(int64 Seconds); /// Adds a number of minutes to the current date/time void AddMinutes(int64 Minutes); /// Adds a number of hours to the current date/time void AddHours(int64 Hours); /// Adds a number of days to the current date/time bool AddDays(int64 Days); /// Adds a number of months to the current date/time void AddMonths(int64 Months); /// The system timezone including daylight savings offset in minutes, +10 would return 600 static int SystemTimeZone(bool ForceUpdate = false); /// Any daylight savings offset applied to TimeZone(), in minutes. e.g. to retreive the /// timezone uneffected by DST use TimeZone() - TimeZoneOffset(). static int SystemTimeZoneOffset(); /// Daylight savings info record struct LgiClass GDstInfo { /// Timestamp where the DST timezone changes to 'Offset' uint64 UtcTimeStamp; /// The new offset in minutes (e.g. 600 = +10 hours) int Offset; LDateTime GetLocal(); }; /// Retreives daylight savings start and end events for a given period. One event will be emitted /// for the current DST/TZ setting at the datetime specified by 'Start', followed by any changes that occur /// between that and the 'End' datetime. To retreive just the DST info for start, use NULL for end. static bool GetDaylightSavingsInfo ( /// [Out] The array to receive DST info. At minimum one record will be returned /// matching the TZ in place for the start datetime. LArray &Out, /// [In] The start date that you want DST info for. LDateTime &Start, /// [Optional In] The end of the period you want DST info for. LDateTime *End = 0 ); /// Using the DST info this will convert 'dt' from UTC to local static bool DstToLocal(LArray &Dst, LDateTime &dt); /// Decodes an email date into the current instance bool Decode(const char *In); /// Returns a month index from a month name static int MonthFromName(const char *Name); // File int Sizeof(); bool Serialize(class LFile &f, bool Write); bool Serialize(class LDom *Props, char *Name, bool Write); // operators bool operator <(const LDateTime &dt) const; bool operator <=(const LDateTime &dt) const; bool operator >(const LDateTime &dt) const; bool operator >=(const LDateTime &dt) const; bool operator ==(const LDateTime &dt) const; bool operator !=(const LDateTime &dt) const; int Compare(const LDateTime *d) const; LDateTime operator -(const LDateTime &dt); LDateTime operator +(const LDateTime &dt); int DiffMonths(const LDateTime &dt); operator uint64() { uint64 ts = 0; Get(ts); return ts; } LDateTime &operator =(uint64 ts) { Set(ts); return *this; } LDateTime &operator =(struct tm *t); LDateTime &operator =(const LDateTime &t); LDateTime &operator =(LDateTime const *t) { if (t) *this = *t; return *this; } /// LDom interface. /// /// Even though we don't inherit from a LDom class this class supports the same /// interface for ease of use. Currently there are cases where LDateTime is used /// in LArray's which don't implement calling a constructor (they init with all /// zeros). bool GetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool CallMethod(const char *Name, class LVariant *ReturnValue, LArray &Args); }; /// Time zone information -struct GTimeZone +struct LTimeZone { public: /// The offset from UTC float Offset; /// The name of the zone const char *Text; }; /// A list of all known timezones. -extern GTimeZone GTimeZones[]; +extern LTimeZone GTimeZones[]; #ifdef _DEBUG LgiFunc bool LDateTime_Test(); #endif #endif diff --git a/include/lgi/common/DocView.h b/include/lgi/common/DocView.h --- a/include/lgi/common/DocView.h +++ b/include/lgi/common/DocView.h @@ -1,524 +1,524 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief This is the base data and code for all the text controls (inc. HTML) #pragma once #include #include "lgi/common/Variant.h" #include "lgi/common/Notifications.h" #include "lgi/common/Thread.h" #include "lgi/common/Layout.h" // Word wrap enum LDocWrapType { /// No word wrapping - TEXTED_WRAP_NONE = 0, + L_WRAP_NONE = 0, /// Dynamically wrap line to editor width - TEXTED_WRAP_REFLOW = 1, + L_WRAP_REFLOW = 1, }; // Util macros /// Returns true if 'c' is whitespace #define IsWhiteSpace(c) ((c) < 126 && strchr(LDocView::WhiteSpace, c) != 0) /// Returns true if 'c' is a delimiter #define IsDelimiter(c) ((c) < 126 && strchr(LDocView::Delimiters, c) != 0) /// Returns true if 'c' is a letter or number #define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_') /// Returns true if 'c' is word boundry #define IsWordBoundry(c) (strchr(LDocView::WhiteSpace, c) || strchr(LDocView::Delimiters, c)) /// Returns true if 'c' is alphanumeric or a digit #define AlphaOrDigit(c) (IsDigit(c) || IsAlpha(c)) /// Returns true if 'c' is a valid URL character #define UrlChar(c) ( \ strchr(LDocView::UrlDelim, (c)) || \ AlphaOrDigit((c)) || \ ((c) >= 256) \ ) /// Returns true if 'c' is email address character #define EmailChar(c) (strchr("._-:+", (c)) || AlphaOrDigit((c))) LgiFunc char16 *ConvertToCrLf(char16 *Text); /// This class contains information about a link. /// \sa LDetectLinks struct LLinkInfo { ssize_t Start; ssize_t Len; bool Email; void Set(ssize_t start, ssize_t len, bool email) { Start = start; Len = len; Email = email; } }; // Call back class to handle viewer events class LDocView; /// An environment class to handle requests from the text view to the outside world. class LgiClass LDocumentEnv : public LThreadOwner { LArray Viewers; public: LDocumentEnv(LDocView *v = 0); virtual ~LDocumentEnv(); enum LoadType { LoadError, LoadNotImpl, LoadImmediate, LoadDeferred, }; struct #ifdef MAC LgiClass #endif LoadJob : public LThreadJob { enum PrefFormat { FmtNone, FmtStream, FmtSurface, FmtFilename, }; enum JobStatus { JobInit, JobOk, JobErr_Uri, JobErr_Path, JobErr_FileOpen, JobErr_GetUri, JobErr_NoCachedFile, JobErr_ImageFilter, JobErr_NoMem, }; // View data LDocumentEnv *Env; void *UserData; uint32_t UserUid; PrefFormat Pref; // Input data LAutoString Uri; LAutoString PostData; // Output data LAutoPtr Stream; LAutoPtr pDC; LString Filename; LString Error; JobStatus Status; LString MimeType, ContentId; LoadJob(LThreadTarget *o) : LThreadJob(o) { Env = NULL; UserUid = 0; UserData = NULL; Pref = FmtNone; Status = JobInit; } LStreamI *GetStream() { if (!Stream && Filename) { LFile *file = new LFile; if (file && file->Open(Filename, O_READ)) Stream.Reset(file); else DeleteObj(file); } return Stream; } }; LoadJob *NewJob() { return new LoadJob(this); } bool AttachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(!Viewers.HasItem(v)); Viewers.Add(v); Unlock(); return true; } bool DetachView(LDocView *v) { if (!v) return false; if (!Lock(_FL)) return false; LAssert(Viewers.HasItem(v)); Viewers.Delete(v); Unlock(); return true; } int NextUid(); /// Creating a context menu, usually when the user right clicks on the /// document. virtual bool AppendItems(LSubMenu *Menu, const char *Param, int Base = 1000) { return false; } /// Do something when the menu items created by LDocumentEnv::AppendItems /// are clicked. virtual bool OnMenu(LDocView *View, int Id, void *Context) { return false; } /// Asks the env to get some data linked from the document, e.g. a css file or an iframe source etc. /// If the GetContent implementation takes ownership of the job pointer then it should set 'j' to NULL. virtual LoadType GetContent(LoadJob *&j) { return LoadNotImpl; } /// After the env's thread loads the resource it calls this to pass it to the doc void OnDone(LAutoPtr j); /// Handle a click on URI virtual bool OnNavigate(LDocView *Parent, const char *Uri) { return false; } /// Handle a form post virtual bool OnPostForm(LDocView *Parent, const char *Uri, const char *Data) { return false; } /// Process dynamic content, returning a dynamically allocated string /// for the result of the executed script. Dynamic content is enclosed /// between <? and ?>. virtual LString OnDynamicContent(LDocView *Parent, const char *Code) { return LString(); } /// Some script was received, the owner should compile it virtual bool OnCompileScript(LDocView *Parent, char *Script, const char *Language, const char *MimeType) { return false; } /// Some script needs to be executed, the owner should compile it virtual bool OnExecuteScript(LDocView *Parent, char *Script) { return false; } }; /// Default text view environment /// /// This class defines the default behavior of the environment, /// However you will need to instantiate this yourself and call /// SetEnv with your instance. i.e. it's not automatic. class LgiClass LDefaultDocumentEnv : public LDocumentEnv { public: LoadType GetContent(LoadJob *&j); bool OnNavigate(LDocView *Parent, const char *Uri); }; /// Find params class LDocFindReplaceParams { public: virtual ~LDocFindReplaceParams() {} }; /// TextView class is a base for all text controls class LgiClass LDocView : public LLayout, virtual public LDom { friend class LDocumentEnv; protected: LDocumentEnv *Environment = NULL; LString Charset; public: // Static static const char *WhiteSpace; static const char *Delimiters; static const char *UrlDelim; /////////////////////////////////////////////////////////////////////// // Properties #define DocViewProp(Type, Name, Default) \ protected: \ Type Name = Default; \ public: \ virtual void Set##Name(Type i) { Name=i; } \ Type Get##Name() { return Name; } DocViewProp(uint16, WrapAtCol, 0) DocViewProp(bool, UrlDetect, true) DocViewProp(bool, ReadOnly, false) - DocViewProp(LDocWrapType, WrapType, TEXTED_WRAP_REFLOW) + DocViewProp(LDocWrapType, WrapType, L_WRAP_REFLOW) DocViewProp(uint8_t, TabSize, 4) DocViewProp(uint8_t, IndentSize, 4) DocViewProp(bool, HardTabs, true) DocViewProp(bool, ShowWhiteSpace, false) DocViewProp(bool, ObscurePassword, false) DocViewProp(bool, CrLf, false) DocViewProp(bool, AutoIndent, true) DocViewProp(bool, FixedWidthFont, false) DocViewProp(bool, LoadImages, false) DocViewProp(bool, OverideDocCharset, false) // This UID is used to match data load events with their source document. // Sometimes data will arrive after the document that asked for it has // already been unloaded. So by assigned each document an UID we can check // the job UID against it and discard old data. DocViewProp(int, DocumentUid, 0) #undef DocViewProp virtual const char *GetCharset() { return Charset.Get() ? Charset.Get() : "utf-8"; } virtual void SetCharset(const char *s) { Charset = s; } virtual const char *GetMimeType() = 0; /////////////////////////////////////////////////////////////////////// // Object LDocView(LDocumentEnv *e = NULL) { SetEnv(e); } virtual ~LDocView() { SetEnv(NULL); } const char *GetClass() { return "LDocView"; } /// Open a file handler virtual bool Open(const char *Name, const char *Cs = 0) { return false; } /// Save a file handler virtual bool Save(const char *Name, const char *Cs = 0) { return false; } /////////////////////////////////////////////////////////////////////// /// Find window handler virtual void DoFind(std::function Callback) { if (Callback) Callback(false); } /// Replace window handler virtual void DoReplace(std::function Callback) { if (Callback) Callback(false); } virtual LDocFindReplaceParams *CreateFindReplaceParams() { return 0; } virtual void SetFindReplaceParams(LDocFindReplaceParams *Params) { } /////////////////////////////////////////////////////////////////////// /// Get the current environment virtual LDocumentEnv *GetEnv() { return Environment; } /// Set the current environment virtual void SetEnv(LDocumentEnv *e) { if (Environment) Environment->DetachView(this); Environment = e; if (Environment) Environment->AttachView(this); } /// When the env has loaded a resource it can pass it to the doc control via this method. /// It MUST be thread safe. Often an environment will call this function directly from /// it's worker thread. virtual void OnContent(LDocumentEnv::LoadJob *Res) {} /////////////////////////////////////////////////////////////////////// // State / Selection /// Set the cursor position, to select an area, move the cursor with Select=false /// then set the other end of the region with Select=true. virtual void SetCaret(size_t i, bool Select, bool ForceFullUpdate = false) {} /// Cursor=false means the other end of the selection if any. The cursor is alwasy /// at one end of the selection. virtual ssize_t GetCaret(bool Cursor = true) { return 0; } /// True if there is a selection virtual bool HasSelection() { return false; } /// Unselect all the text virtual void UnSelectAll() {} /// Select the word from index 'From' virtual void SelectWord(size_t From) {} /// Select all the text in the control virtual void SelectAll() {} /// Get the selection as a dynamicially allocated utf-8 string virtual char *GetSelection() { return 0; } /// Returns the character index at the x,y location virtual ssize_t IndexAt(int x, int y) { return 0; } /// Index=-1 returns the x,y of the cursor, Index >=0 returns the specified x,y virtual bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) { return false; } /// True if the document has changed virtual bool IsDirty() { return false; } /// Gets the number of lines of text virtual size_t GetLines() { return 0; } /// Gets the pixels required to display all the text virtual void GetTextExtent(int &x, int &y) {} /////////////////////////////////////////////////////////////////////// /// Cuts the selection from the document and puts it on the clipboard virtual bool Cut() { return false; } /// Copies the selection from the document to the clipboard virtual bool Copy() { return false; } /// Pastes the current contents of the clipboard into the document virtual bool Paste() { return false; } /////////////////////////////////////////////////////////////////////// /// Called when the user hits the escape key virtual void OnEscape(LKey &K) {} /// Called when the user hits the enter key virtual void OnEnter(LKey &k) {} /// Called when the user clicks a URL virtual void OnUrl(char *Url) {} /// Called to add styling to the document virtual void OnAddStyle(const char *MimeType, const char *Styles) {} /////////////////////////////////////////////////////////////////////// struct ContentMedia { LString Id; LString FileName; LString MimeType; LVariant Data; LAutoPtr Stream; bool Valid() { return MimeType.Get() != NULL && FileName.Get() != NULL && ( (Data.Type == GV_BINARY && Data.Value.Binary.Data != NULL) || (Stream.Get() != NULL) ); } }; /// Gets the document in format of a desired MIME type virtual bool GetFormattedContent ( /// [In] The desired mime type of the content const char *MimeType, /// [Out] The content in the specified mime type LString &Out, /// [Out/Optional] Any attached media files that the content references LArray *Media = NULL ) { return false; } }; /// Detects links in text, returning their location and type template bool LDetectLinks(LArray &Links, T *Text, ssize_t TextCharLen = -1) { if (!Text) return false; if (TextCharLen < 0) TextCharLen = Strlen(Text); T *End = Text + TextCharLen; static T Http[] = {'h', 't', 't', 'p', ':', '/', '/', 0 }; static T Https[] = {'h', 't', 't', 'p', 's', ':', '/', '/', 0}; for (int64 i=0; i= 7 && ( Strnicmp(Text+i, Http, 6) == 0 || Strnicmp(Text+i, Https, 7) == 0 ) ) { // find end T *s = Text + i; T *e = s + 6; for ( ; e < End && UrlChar(*e); e++) ; while ( e > s && ! ( IsAlpha(e[-1]) || IsDigit(e[-1]) || e[-1] == '/' ) ) e--; Links.New().Set(s - Text, e - s, false); i = e - Text; } break; } case '@': { // find start T *s = Text + (MAX(i, 1) - 1); for ( ; s > Text && EmailChar(*s); s--) ; if (s < Text + i) { if (!EmailChar(*s)) s++; bool FoundDot = false; T *Start = Text + i + 1; T *e = Start; for ( ; e < End && EmailChar(*e); e++) { if (*e == '.') FoundDot = true; } while (e > Start && e[-1] == '.') e--; if (FoundDot) { Links.New().Set(s - Text, e - s, true); i = e - Text; } } break; } } } return true; } diff --git a/include/lgi/common/FilterUi.h b/include/lgi/common/FilterUi.h --- a/include/lgi/common/FilterUi.h +++ b/include/lgi/common/FilterUi.h @@ -1,93 +1,93 @@ /// \file /// \author Matthew Allen #ifndef _GFILTER_UI_H_ #define _GFILTER_UI_H_ #include "lgi/common/Tree.h" class LFilterViewPrivate; -enum GFilterNode +enum LFilterNode { LNODE_NULL, LNODE_AND, LNODE_OR, LNODE_COND, LNODE_NEW }; -enum GFilterMenu +enum LFilterMenu { FMENU_FIELD, FMENU_OP, FMENU_VALUE }; class LFilterView; class LFilterItem; typedef int (*FilterUi_Menu)(LFilterView *View, LFilterItem *Item, - GFilterMenu Menu, + LFilterMenu Menu, LRect &r, LArray *GetList, void *UserData); class LFilterItem : public LTreeItem, public LDragDropSource { class LFilterItemPrivate *d; protected: void _PourText(LPoint &Size); void _PaintText(LItem::ItemPaintCtx &Ctx); void ShowControls(bool s); public: - LFilterItem(LFilterViewPrivate *Data, GFilterNode Node = LNODE_NEW); + LFilterItem(LFilterViewPrivate *Data, LFilterNode Node = LNODE_NEW); ~LFilterItem(); bool GetNot(); const char *GetField(); int GetOp(); const char *GetValue(); void SetNot(bool b); void SetField(const char *s); void SetOp(int i); void SetValue(char *s); - GFilterNode GetNode(); - void SetNode(GFilterNode n); + LFilterNode GetNode(); + void SetNode(LFilterNode n); bool OnKey(LKey &k); void OnMouseClick(LMouse &m); void OnExpand(bool b); void OptionsMenu(); // Dnd - bool OnBeginDrag(LMouse &m); - bool GetFormats(LDragFormats &Formats); - bool GetData(LArray &Data); + bool OnBeginDrag(LMouse &m); + bool GetFormats(LDragFormats &Formats); + bool GetData(LArray &Data); }; /// Filter user interface using a tree structure. class LFilterView : public LLayout { class LFilterViewPrivate *d; public: LFilterView(FilterUi_Menu Callback = 0, void *UserData = 0); ~LFilterView(); void SetDefault(); bool ShowLegend(); void ShowLegend(bool b); void Empty(); - LFilterItem *Create(GFilterNode Node = LNODE_NEW); + LFilterItem *Create(LFilterNode Node = LNODE_NEW); LTreeNode *GetRootNode(); void OnPaint(LSurface *pDC); void OnPosChange(); void OnCreate(); }; #endif \ No newline at end of file diff --git a/include/lgi/common/LgiClasses.h b/include/lgi/common/LgiClasses.h --- a/include/lgi/common/LgiClasses.h +++ b/include/lgi/common/LgiClasses.h @@ -1,197 +1,197 @@ /** \file \author Matthew Allen \date 19/12/1997 \brief Gui class definitions Copyright (C) 1997-2004, Matthew Allen */ ///////////////////////////////////////////////////////////////////////////////////// // Includes #ifndef _LGICLASSES_H_ #define _LGICLASSES_H_ #include "lgi/common/Mutex.h" #include "LgiOsClasses.h" #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/DragAndDrop.h" ///////////////////////////////////////////////////////////////////////////////////// LgiFunc bool LPostEvent(OsView Wnd, int Event, LMessage::Param a = 0, LMessage::Param b = 0); LgiFunc LViewI *GetNextTabStop(LViewI *v, bool Back); class LgiClass LCommand : public LBase { int Flags = GWF_VISIBLE; bool PrevValue = false; public: int Id = 0; LToolButton *ToolButton = NULL; LMenuItem *MenuItem = NULL; LAutoPtr Accelerator; LString TipHelp; LCommand(); ~LCommand(); bool Enabled(); void Enabled(bool e); bool Value(); void Value(bool v); }; #include "lgi/common/TrayIcon.h" #include "lgi/common/Input.h" #ifndef LGI_STATIC /// \brief A BeOS style alert window, kinda like a Win32 MessageBox /// /// The best thing about this class is you can name the buttons very specifically. /// It's always non-intuitive to word a question to the user in such a way so thats /// it's obvious to answer with "Ok" or "Cancel". But if the user gets a question /// with customized "actions" as buttons they'll love you. /// /// The button pressed is returned as a index from the DoModal() function. Starting /// at '1'. i.e. Btn2 -> returns 2. class LgiClass LAlert : public LDialog { LArray> Callbacks; public: /// Constructor LAlert ( /// The parent view LViewI *parent, /// The dialog title const char *Title, /// The body of the message const char *Text, /// The first button text const char *Btn1, /// The [optional] 2nd buttons text const char *Btn2 = NULL, /// The [optional] 3rd buttons text const char *Btn3 = NULL ); void SetAppModal(); int OnNotify(LViewI *Ctrl, LNotification n); void SetButtonCallback(int ButtonIdx, std::function Callback); }; #endif /// Timer class to help do something every so often class LgiClass DoEvery { int64 LastTime; int64 Period; public: /// Constructor DoEvery ( /// Timeout in ms int p = 1000 ); /// Reset the timer void Init ( /// Timeout in ms int p = -1 ); /// Returns true when the time has expired. Resets automatically. bool DoNow(); }; ////////////////////////////////////////////////////////// // Graphics LgiFunc void LDrawBox(LSurface *pDC, LRect &r, bool Sunken, bool Fill); LgiFunc void LWideBorder(LSurface *pDC, LRect &r, LEdge Type); LgiFunc void LThinBorder(LSurface *pDC, LRect &r, LEdge Type); LgiFunc void LFlatBorder(LSurface *pDC, LRect &r, int Width = -1); // Helpers #ifdef __GTK_H__ extern Gtk::gboolean GtkViewCallback(Gtk::GtkWidget *widget, Gtk::GdkEvent *event, LView *This); #endif #ifdef LINUX /// Ends a x windows startup session LgiFunc void LFinishXWindowsStartup(class LViewI *Wnd); #endif /// \brief Displays a message box /// \returns The button clicked. The return value is one of #IDOK, #IDCANCEL, #IDYES or #IDNO. LgiFunc int LgiMsg ( /// The parent view or NULL if none available LViewI *Parent, /// The message's text. This is a printf format string that you can pass arguments to const char *Msg, /// The title of the message box window const char *Title = 0, /// The type of buttons below the message. Can be one of: /// #MB_OK, #MB_OKCANCEL, #MB_YESNO or #MB_YESNOCANCEL. int Type = MB_OK, ... ); /// This is like LgiMsg but displays the text in a scrollable view. LgiFunc void LDialogTextMsg(LViewI *Parent, const char *Title, LString Txt); /// Contains all the information about a display/monitor attached to the system. /// \sa LGetDisplays -struct GDisplayInfo +struct LDisplayInfo { /// The position and dimensions of the display. On windows the left/upper /// most display will be positioned at 0,0 and each further display will have /// co-ordinates that join to one edge of that initial rectangle. LRect r; /// The number of bits per pixel int BitDepth; /// The refresh rate int Refresh; /// The device's path, system specific LString Device; /// A descriptive name of the device, usually the video card LString Name; /// The name of any attached monitor LString Monitor; /// The dots per inch of the display LPoint Dpi; - GDisplayInfo() + LDisplayInfo() { r.ZOff(-1, -1); BitDepth = 0; Refresh = 0; } LPointF Scale(); }; /// Returns infomation about the displays attached to the system. /// \returns non-zero on success. LgiFunc bool LGetDisplays ( /// [out] The array of display info structures. The caller should free these /// objects using Displays.DeleteObjects(). - LArray &Displays, + LArray &Displays, /// [out] Optional bounding rectangle of all displays. Can be NULL if your don't /// need that information. LRect *AllDisplays = 0 ); #include "lgi/common/Profile.h" #endif diff --git a/include/lgi/common/Matrix.h b/include/lgi/common/Matrix.h --- a/include/lgi/common/Matrix.h +++ b/include/lgi/common/Matrix.h @@ -1,272 +1,272 @@ #ifndef _GMATRIX_H_ #define _GMATRIX_H_ #define MATRIX_DOUBLE_EPSILON 1e-10 #define Abs(x) ((x) < 0 ? -(x) : (x)) template -struct GMatrix +struct LMatrix { T m[Ys][Xs]; - GMatrix() + LMatrix() { ZeroObj(m); } int X() { return Xs; } int Y() { return Ys; } T *operator[](int i) { LAssert(i >= 0 && i < Ys); return m[i]; } template - GMatrix operator *(const GMatrix &b) + LMatrix operator *(const LMatrix &b) { // a * b where ax == by, and the output matrix is - GMatrix r; + LMatrix r; for (int y=0; y &operator *(T scalar) + LMatrix &operator *(T scalar) { for (int y=0; y &mat) + bool operator ==(const LMatrix &mat) { for (int y=0; y &mat) + bool operator !=(const LMatrix &mat) { bool eq = *this == mat; return !eq; } void SetIdentity() { for (int y=0; y b; + LMatrix b; for (y=0; y Abs(b.m[maxrow][y]) ) { maxrow = y2; } } b.SwapRow(y, maxrow); if (Equal(zero, b.m[y][y])) { return false; } for (y2 = y+1; y2=0; y--) // Backsubstitute { T c = b.m[y][y]; for (y2=0; y2=y; x--) { b.m[y2][x] -= b.m[y][x] * b.m[y2][y] / c; } } b.m[y][y] /= c; for (x=Ys; x #include "lgi/common/Matrix.h" #define LPATH_DBG 1 class LSeg; class LVector; extern bool _Disable_ActiveList; extern bool _Disable_XSort; extern bool _Disable_Alpha; extern bool _Disable_Rops; enum LPathFillRule { FILLRULE_ODDEVEN, FILLRULE_NONZERO }; #include "lgi/common/Point.h" #include "lgi/common/RectF.h" class LgiClass LBrush { friend class LPath; protected: uchar AlphaLut[65]; class LRopArgs { public: uchar *Pixels; uchar *EndOfMem; uchar *Alpha; int Len; int x, y; LColourSpace Cs; int BytesPerPixel; LSurface *pDC; }; virtual bool Start(LRopArgs &a) { return true; } virtual void Rop(LRopArgs &a) = 0; virtual int Alpha() { return 255; } void MakeAlphaLut(); public: LBrush() {} virtual ~LBrush() {} }; class LgiClass LEraseBrush : public LBrush { void Rop(LRopArgs &a); public: LEraseBrush() { MakeAlphaLut(); } }; // In premultiplied: // Dca' = Sca + Dca.(1 - Sa) // -or- // In non-premultiplied: // Dc' = (Sc.Sa + Dc.Da.(1 - Sa))/Da' #define NonPreMulOver16(c, sh) d->c = (((s.c * sa) + (DivLut[(d->c << sh) * 255] * o)) / 255) >> sh #define NonPreMulOver32(c) d->c = ((s.c * sa) + (DivLut[d->c * da] * o)) / d->a #define NonPreMulOver24(c) d->c = ((s.c * sa) + (DivLut[d->c * 255] * o)) / 255 #define NonPreMulAlpha d->a = (d->a + sa) - DivLut[d->a * sa] // Define 'uint8 dc, sc;' first. Takes 16bit source and 8bit dest. #define Rgb16to8PreMul(c) \ sc = DivLut[(s->c >> 8) * (s->a >> 8)]; \ dc = DivLut[d->c * d->a]; \ d->c = sc + DivLut[dc * o] class LgiClass LSolidBrush : public LBrush { COLOUR c32; void Rop(LRopArgs &a); int Alpha() { return A32(c32); } public: LSolidBrush(COLOUR c) { c32 = c; MakeAlphaLut(); } LSolidBrush(LColour c) { c32 = c.c32(); MakeAlphaLut(); } LSolidBrush(int r, int g, int b, int a = 255) { c32 = Rgba32(r, g, b, a); MakeAlphaLut(); } template void SolidRop16(T *d, int Len, uint8_t *Alpha, COLOUR c32) { uchar *DivLut = Div255Lut; T *end = d + Len; LRgb24 s; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); while (d < end) { uchar sa = AlphaLut[*Alpha++]; if (sa == 255) { d->r = s.r >> 3; d->g = s.g >> 2; d->b = s.b >> 3; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver16(r, 3); NonPreMulOver16(g, 2); NonPreMulOver16(b, 3); } d++; } } template void SolidRop24(T *d, int Len, uint8_t *Alpha, COLOUR c32) { uchar *DivLut = Div255Lut; T *end = d + Len; T s; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); while (d < end) { uchar sa = AlphaLut[*Alpha++]; if (sa == 255) { *d = s; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver24(r); NonPreMulOver24(g); NonPreMulOver24(b); } d++; } } template void SolidRop32(T *d, int Len, uint8_t *Alpha, COLOUR c32) { uchar *DivLut = Div255Lut; T *end = d + Len; T s; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); s.a = A32(c32); while (d < end) { uchar sa = DivLut[s.a * AlphaLut[*Alpha++]]; if (sa == 255) { *d = s; } else if (sa) { int o = 0xff - sa; int da = d->a; NonPreMulAlpha; NonPreMulOver32(r); NonPreMulOver32(g); NonPreMulOver32(b); } d++; } } }; struct LBlendStop { double Pos; COLOUR c32; }; class LgiClass LBlendBrush : public LBrush { protected: int Stops; LBlendStop *Stop; COLOUR Lut[256]; double Base, IncX, IncY; LPointF p[2]; bool Start(LRopArgs &a); public: LBlendBrush(int stops, LBlendStop *stop) { MakeAlphaLut(); Stops = 0; Stop = 0; if (stop) { SetStops(stops, stop); } } ~LBlendBrush() { DeleteArray(Stop); } int GetStops(LBlendStop **stop = 0) { if (stop) *stop = Stop; return Stops; } void SetStops(int stops, LBlendStop *stop) { Stops = stops; DeleteArray(Stop); Stop = new LBlendStop[Stops]; if (Stop) memcpy(Stop, stop, sizeof(*Stop) * Stops); } }; class LgiClass LLinearBlendBrush : public LBlendBrush { bool Start(LRopArgs &Args); void Rop(LRopArgs &Args); public: LLinearBlendBrush(LPointF a, LPointF b, int stops = 0, LBlendStop *stop = 0) : LBlendBrush(stops, stop) { p[0] = a; p[1] = b; } template void Linear16(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double DPos = Base + (Args.x * IncX) + (Args.y * IncY); double Scale = (double)0x10000; int Pos = (int) (DPos * Scale); int Inc = (int) (IncX * Scale); T *e = d + Args.Len; LRgb24 s; while (d < e) { if (*Alpha) { // work out colour COLOUR c32; int Ci = ((Pos << 8) - Pos) >> 16; if (Ci < 0) c32 = Lut[0]; else if (Ci > 255) c32 = Lut[255]; else c32 = Lut[Ci]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); // assign pixel uchar sa = DivLut[AlphaLut[*Alpha] * A32(c32)]; if (sa == 0xff) { d->r = s.r >> 3; d->g = s.g >> 2; d->b = s.b >> 3; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver16(r, 3); NonPreMulOver16(g, 2); NonPreMulOver16(b, 3); } } d++; Alpha++; Pos += Inc; } } template void Linear24(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double DPos = Base + (Args.x * IncX) + (Args.y * IncY); double Scale = (double)0x10000; int Pos = (int) (DPos * Scale); int Inc = (int) (IncX * Scale); T *e = d + Args.Len; T s; while (d < e) { if (*Alpha) { // work out colour COLOUR c32; int Ci = ((Pos << 8) - Pos) >> 16; if (Ci < 0) c32 = Lut[0]; else if (Ci > 255) c32 = Lut[255]; else c32 = Lut[Ci]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); // assign pixel uchar sa = DivLut[AlphaLut[*Alpha] * A32(c32)]; if (sa == 0xff) { *d = s; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver24(r); NonPreMulOver24(g); NonPreMulOver24(b); } } d++; Alpha++; Pos += Inc; } } template void Linear32(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double DPos = Base + (Args.x * IncX) + (Args.y * IncY); double Scale = (double)0x10000; int Pos = (int) (DPos * Scale); int Inc = (int) (IncX * Scale); T *End = d + Args.Len; T s; while (d < End) { if (*Alpha) { // work out colour COLOUR c; int Ci = ((Pos << 8) - Pos) >> 16; if (Ci < 0) c = Lut[0]; else if (Ci > 255) c = Lut[255]; else c = Lut[Ci]; s.r = R32(c); s.g = G32(c); s.b = B32(c); s.a = A32(c); // assign pixel uchar sa = DivLut[AlphaLut[*Alpha] * A32(c)]; if (sa == 0xff) { *d = s; } else if (sa) { uchar o = 0xff - sa; int da = d->a; NonPreMulAlpha; NonPreMulOver32(r); NonPreMulOver32(g); NonPreMulOver32(b); } } d++; Alpha++; Pos += Inc; } } }; class LgiClass LRadialBlendBrush : public LBlendBrush { void Rop(LRopArgs &Args); public: LRadialBlendBrush(LPointF center, LPointF rim, int stops = 0, LBlendStop *stop = 0) : LBlendBrush(stops, stop) { p[0] = center; p[1] = rim; } template void Radial16(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double dx = p[1].x - p[0].x; double dy = p[1].y - p[0].y; double Radius = sqrt((dx*dx)+(dy*dy)) / 255; // scaled by 255 to index into the Lut int dysq = (int) (Args.y - p[0].y); dysq *= dysq; int x = (int) (Args.x - p[0].x); int dxsq = x * x; // forward difference the (x * x) calculation int xd = 2 * x + 1; // to remove a MUL from the inner loop int Ci = 0; COLOUR c32; uchar sa; T *End = (T*) d + Args.Len; LRgb24 s; if (Radius < 0.0000000001) { // No blend... just the end colour c32 = Lut[255]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); for (; dr = s.r >> 3; d->g = s.g >> 2; d->b = s.b >> 3; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver16(r, 3); NonPreMulOver16(g, 2); NonPreMulOver16(b, 3); } } d++; } } else { // Radial blend for (; d 255) c32 = Lut[255]; else c32 = Lut[Ci]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); // composite the pixel sa = DivLut[AlphaLut[*Alpha] * A32(c32)]; if (sa == 0xff) { d->r = s.r >> 3; d->g = s.g >> 2; d->b = s.b >> 3; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver16(r, 3); NonPreMulOver16(g, 2); NonPreMulOver16(b, 3); } } // apply forward difference to the x values x++; dxsq += xd; xd += 2; d++; } } } template void Radial24(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double dx = p[1].x - p[0].x; double dy = p[1].y - p[0].y; double Radius = sqrt((dx*dx)+(dy*dy)) / 255; // scaled by 255 to index into the Lut int dysq = (int) (Args.y - p[0].y); dysq *= dysq; int x = (int) (Args.x - p[0].x); int dxsq = x * x; // forward difference the (x * x) calculation int xd = 2 * x + 1; // to remove a MUL from the inner loop int Ci = 0; COLOUR c32; uchar sa; T *End = (T*) d + Args.Len; T s; if (Radius < 0.0000000001) { // No blend... just the end colour c32 = Lut[255]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); for (; d 255) c32 = Lut[255]; else c32 = Lut[Ci]; s.r = R32(c32); s.g = G32(c32); s.b = B32(c32); // composite the pixel sa = DivLut[AlphaLut[*Alpha] * A32(c32)]; if (sa == 0xff) { *d = s; } else if (sa) { uchar o = 0xff - sa; NonPreMulOver24(r); NonPreMulOver24(g); NonPreMulOver24(b); } } // apply forward difference to the x values x++; dxsq += xd; xd += 2; d++; } } } template void Radial32(T *d, LRopArgs &Args) { uchar *DivLut = Div255Lut; uchar *Alpha = Args.Alpha; double dx = p[1].x - p[0].x; double dy = p[1].y - p[0].y; double Radius = sqrt((dx*dx)+(dy*dy)) / 255; // scaled by 255 to index into the Lut int dysq = (int) (Args.y - p[0].y); dysq *= dysq; int x = (int) (Args.x - p[0].x); int dxsq = x * x; // forward difference the (x * x) calculation int xd = 2 * x + 1; // to remove a MUL from the inner loop int Ci = 0; COLOUR c; uchar sa; T *End = d + Args.Len; T s; if (Radius < 0.0000000001) { // No blend... just the end colour c = Lut[255]; s.r = R32(c); s.g = G32(c); s.b = B32(c); s.a = A32(c); for (; da; NonPreMulAlpha; NonPreMulOver32(r); NonPreMulOver32(g); NonPreMulOver32(b); } } } } else { // Radial blend for (; d 255) c = Lut[255]; else c = Lut[Ci]; s.r = R32(c); s.g = G32(c); s.b = B32(c); s.a = A32(c); // composite the pixel sa = DivLut[AlphaLut[*Alpha] * A32(c)]; if (sa == 0xff) { *d = s; } else if (sa) { uchar o = 0xff - sa; int da = d->a; NonPreMulAlpha; NonPreMulOver32(r); NonPreMulOver32(g); NonPreMulOver32(b); } } // apply forward difference to the x values x++; dxsq += xd; xd += 2; } } } }; class LgiClass LPath { public: - class Matrix : public GMatrix + class Matrix : public LMatrix { public: Matrix() { SetIdentity(); } Matrix &Translate(double x, double y) { m[2][0] = x; m[2][1] = y; return *this; } Matrix &Rotate(double angle, bool anti_clockwise = false) { m[0][0] = m[1][1] = cos(angle); m[0][1] = (anti_clockwise ? -1 : 1) * sin(angle); m[1][0] = (anti_clockwise ? 1 : -1) * sin(angle); return *this; } }; protected: // Data List Segs; List Vecs; LRectF Bounds; bool Aa; LPathFillRule FillRule; Matrix Mat; // Flattened representation int Points; LArray Outline; LPointF *Point; // Methods void Unflatten(); void Append(LPath &p); public: LPath(bool aa = true); virtual ~LPath(); // Primitives void MoveTo( double x, double y); void MoveTo( LPointF &pt); void LineTo( double x, double y); void LineTo( LPointF &pt); void QuadBezierTo( double cx, double cy, double px, double py); void QuadBezierTo( LPointF &c, LPointF &p); void CubicBezierTo( double c1x, double c1y, double c2x, double c2y, double px, double py); void CubicBezierTo( LPointF &c1, LPointF &c2, LPointF &p); void Rectangle( double x1, double y1, double x2, double y2); void Rectangle( LPointF &tl, LPointF &rb); void Rectangle( LRectF &r); void RoundRect( LRectF &b, double r); void Circle( double cx, double cy, double radius); void Circle( LPointF &c, double radius); void Ellipse( double cx, double cy, double x, double y); void Ellipse( LPointF &c, double x, double y); bool Text( LFont *Font, double x, double y, char *Utf8, int Bytes = -1); // Properties int Segments(); void GetBounds(LRectF *b); LPathFillRule GetFillRule() { return FillRule; } void SetFillRule(LPathFillRule r) { FillRule = r; } // Methods bool IsClosed(); void Close(); bool IsFlattened(); bool Flatten(); bool IsEmpty(); void Empty(); void Transform(Matrix &m); void DeleteSeg(int i); // Colouring (windows: need to call ConvertPreMulAlpha(true) on the pDC after using these) void Fill(LSurface *pDC, LBrush &Brush); void Stroke(LSurface *pDC, LBrush &Brush, double Width); #if LPATH_DBG LMemDC DbgDsp; #endif }; void FlattenQuadratic(LPointF *&Out, LPointF &p1, LPointF &p2, LPointF &p3, int Steps); void FlattenCubic(LPointF *&Out, LPointF &p1, LPointF &p2, LPointF &p3, LPointF &p4, int Steps); diff --git a/include/lgi/common/RichTextEdit.h b/include/lgi/common/RichTextEdit.h --- a/include/lgi/common/RichTextEdit.h +++ b/include/lgi/common/RichTextEdit.h @@ -1,238 +1,238 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef _RICH_TEXT_EDIT_H_ #define _RICH_TEXT_EDIT_H_ #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Capabilities.h" #include "lgi/common/FindReplaceDlg.h" #if _DEBUG #include "lgi/common/Tree.h" #endif enum RichEditMsgs { M_BLOCK_MSG = M_USER + 0x1000, M_IMAGE_LOAD_FILE, M_IMAGE_SET_SURFACE, M_IMAGE_ERROR, M_IMAGE_COMPONENT_MISSING, M_IMAGE_PROGRESS, M_IMAGE_RESAMPLE, M_IMAGE_FINISHED, M_IMAGE_COMPRESS, M_IMAGE_ROTATE, M_IMAGE_FLIP, M_IMAGE_LOAD_STREAM, M_COMPONENT_INSTALLED, // A = LString *ComponentName }; extern char Delimiters[]; /// Styled unicode text editor control. class #if defined(MAC) LgiClass #endif LRichTextEdit : public LDocView, public ResObject, public LDragDropTarget, public LCapabilityClient { friend bool RichText_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum LTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; protected: class LRichTextPriv *d; friend class LRichTextPriv; bool IndexAt(int x, int y, ssize_t &Off, int &LineHint); // Overridables virtual void PourText(ssize_t Start, ssize_t Length); virtual void PourStyle(ssize_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); public: // Construction LRichTextEdit( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = 0); ~LRichTextEdit(); const char *GetClass() { return "LRichTextEdit"; } // Data const char *Name(); bool Name(const char *s); const char16 *NameW(); bool NameW(const char16 *s); int64 Value(); void Value(int64 i); const char *GetMimeType() { return "text/html"; } int GetSize(); const char *GetCharset(); void SetCharset(const char *s); ssize_t HitTest(int x, int y); bool DeleteSelection(char16 **Cut = 0); bool SetSpellCheck(class LSpellCheck *sp); bool GetFormattedContent(const char *MimeType, LString &Out, LArray *Media = NULL); // Dom bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); // Font LFont *GetFont(); void SetFont(LFont *f, bool OwnIt = false); void SetFixedWidthFont(bool i); // Options void SetTabSize(uint8_t i); void SetReadOnly(bool i); bool ShowStyleTools(); void ShowStyleTools(bool b); enum RectType { ContentArea, ToolsArea, // CapabilityArea, // CapabilityBtn, FontFamilyBtn, FontSizeBtn, BoldBtn, ItalicBtn, UnderlineBtn, ForegroundColourBtn, BackgroundColourBtn, MakeLinkBtn, RemoveLinkBtn, RemoveStyleBtn, EmojiBtn, HorzRuleBtn, MaxArea }; LRect GetArea(RectType Type); - /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW + /// Sets the wrapping on the control, use #L_WRAP_NONE or #L_WRAP_REFLOW void SetWrapType(LDocWrapType i); // State / Selection void SetCursor(int i, bool Select, bool ForceFullUpdate = false); ssize_t IndexAt(int x, int y); bool IsDirty(); void IsDirty(bool d); bool HasSelection(); void UnSelectAll(); void SelectWord(size_t From); void SelectAll(); ssize_t GetCaret(bool Cursor = true); bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1); size_t GetLines(); void GetTextExtent(int &x, int &y); char *GetSelection(); void SetStylePrefix(LString s); bool IsBusy(bool Stop = false); // File IO bool Open(const char *Name, const char *Cs = 0); bool Save(const char *Name, const char *Cs = 0); // Clipboard IO bool Cut(); bool Copy(); bool Paste(); // Undo/Redo void Undo(); void Redo(); bool GetUndoOn(); void SetUndoOn(bool b); // Action UI virtual void DoGoto(std::function Callback); virtual void DoCase(std::function Callback, bool Upper); virtual void DoFind(std::function Callback); virtual void DoFindNext(std::function Callback); virtual void DoReplace(std::function Callback); // Action Processing bool ClearDirty(bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); int GetLine(); void SetLine(int Line); LDocFindReplaceParams *CreateFindReplaceParams(); void SetFindReplaceParams(LDocFindReplaceParams *Params); void OnAddStyle(const char *MimeType, const char *Styles); // Object Events bool OnFind(LFindReplaceCommon *Params); bool OnReplace(LFindReplaceCommon *Params); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange(); void OnCreate(); void OnEscape(LKey &K); bool OnMouseWheel(double Lines); // Capability target stuff // bool NeedsCapability(const char *Name, const char *Param = NULL); // void OnInstall(CapsHash *Caps, bool Status); // void OnCloseInstaller(); // Window Events void OnFocus(bool f); void OnMouseClick(LMouse &m); void OnMouseMove(LMouse &m); bool OnKey(LKey &k); void OnPaint(LSurface *pDC); LMessage::Result OnEvent(LMessage *Msg); int OnNotify(LViewI *Ctrl, LNotification n); void OnPulse(); int OnHitTest(int x, int y); bool OnLayout(LViewLayoutInfo &Inf); // D'n'd target int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState); int OnDrop(LArray &Data, LPoint Pt, int KeyState); // Virtuals virtual bool Insert(int At, char16 *Data, int Len); virtual bool Delete(int At, int Len); virtual void OnEnter(LKey &k); virtual void OnUrl(char *Url); virtual void DoContextMenu(LMouse &m); #if _DEBUG void DumpNodes(LTree *Root); void SelectNode(LString Param); #endif }; #endif diff --git a/include/lgi/common/StructuredIo.h b/include/lgi/common/StructuredIo.h --- a/include/lgi/common/StructuredIo.h +++ b/include/lgi/common/StructuredIo.h @@ -1,238 +1,335 @@ #ifndef _STRUCTURED_IO_H_ #define _STRUCTURED_IO_H_ #include #include "lgi/common/Variant.h" #include "lgi/common/LMallocArray.h" #define DEBUG_STRUCT_IO 0 /* Generic base data field: (where size_t == EncSize/DecSize field) uint8_t type; // LVariantType size_t name_len; char name[name_len]; size_t data_size; uint8_t data[data_size]; Objects are wrapped with generic fields of the type GV_CUSTOM // start of object... ...array of member fields.... GV_VOID_PTR // end of object */ class LStructuredIo : public LMallocArray { bool Write = true; size_t Pos = 0; protected: bool CheckSpace(size_t bytes) { auto l = Pos + bytes; if (l > Length()) { if (!Length((l + 255) & ~0xff)) return false; LAssert(Length() >= l); } return true; } constexpr static int SevenMask = 0x7f; template void EncSize(LPointer &p, T sz) { if (!sz) *p.u8++ = 0; else while (sz) { uint8_t bits = sz & SevenMask; sz >>= 7; *p.u8++ = bits | (sz ? 0x80 : 0); } } template void DecSize(LPointer &p, T &sz) { sz = 0; uint8_t bits, shift = 0; do { bits = *p.u8++; sz |= ((T)bits & SevenMask) << shift; shift += 7; } while (bits & 0x80); } bool Encode(uint8_t type, const void *obj = NULL, size_t sz = 0, const char *name = NULL) { LPointer p; LAssert(Write); auto name_len = Strlen(name); if (!CheckSpace(1 + 8 + name_len + 8 + sz)) return false; p.u8 = AddressOf(Pos); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif *p.u8++ = type; EncSize(p, name_len); if (name) { memcpy(p.u8, name, name_len); p.u8 += name_len; } EncSize(p, sz); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif if (obj) { memcpy(p.u8, obj, sz); p.u8 += sz; } else LAssert(sz == 0); Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Encode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)sz, (int)Pos); #endif return true; } public: constexpr static LVariantType StartObject = GV_CUSTOM; constexpr static LVariantType EndObject = GV_VOID_PTR; constexpr static LVariantType EndRow = GV_NULL; LStructuredIo(bool write) : Write(write) { } bool GetWrite() { return Write; } size_t GetPos() { return Pos; } void Bool(bool &b, const char *name = NULL) { Encode(GV_BOOL, &b, sizeof(b), name); } template void Int(T &i, const char *name = NULL) { Encode(GV_INT64, &i, sizeof(i), name); } template void Float(T &f, const char *name = NULL) { Encode(GV_DOUBLE, &f, sizeof(f), name); } template void String(T *str, ssize_t sz = -1, const char *name = NULL) { if (sz < 0) sz = Strlen(str); - Encode(GV_STRING, str, sz * sizeof(T), name); + Encode(sizeof(*str) > 1 ? GV_WSTRING : GV_STRING, str, sz * sizeof(T), name); + } + + void String(LString &str, const char *name = NULL) + { + Encode(GV_STRING, str.Get(), str.Length(), name); + } + + template + void Binary(T *data, size_t elements, const char *name = NULL) + { + if (!data || elements == 0) + return; + Encode(GV_BINARY, data, sizeof(*data)*elements, name); } struct ObjRef { LStructuredIo *io; ObjRef(ObjRef &&r) : io(NULL) { LSwap(io, r.io); } ObjRef(LStructuredIo *parent) : io(parent) { } ~ObjRef() { if (io) io->Encode(EndObject); } }; ObjRef StartObj(const char *name) { ObjRef r(this); Encode(StartObject, NULL, 0, name); return r; } bool Decode(std::function callback, Progress *prog = NULL) { if (Length() == 0) return false; LPointer p; auto end = AddressOf()+Length(); p.u8 = AddressOf(Pos); #define CHECK_EOF(sz) if (p.u8 + sz > end) return false CHECK_EOF(1); #if DEBUG_STRUCT_IO auto type_addr = p.u8 - AddressOf(); #endif LVariantType type = (LVariantType)(*p.u8++); if (type >= GV_MAX) return false; if (type == EndRow) { callback(type, 0, NULL, NULL); Pos = p.u8 - AddressOf(); return true; } size_t name_len, data_size; DecSize(p, name_len); CHECK_EOF(name_len); LString name(p.c, name_len); p.s8 += name_len; DecSize(p, data_size); CHECK_EOF(data_size); #if DEBUG_STRUCT_IO auto data_addr = p.u8 - AddressOf(); #endif callback(type, data_size, p.u8, name); p.u8 += data_size; Pos = p.u8 - AddressOf(); #if DEBUG_STRUCT_IO LgiTrace("Decode(%i @ %i,%i sz=%i) after=%i\n", type, (int)type_addr, (int)data_addr, (int)data_size, (int)Pos); #endif return true; } bool Flush(LStream *s) { - if (!s) + if (!s || !Write) return false; (*this)[Pos++] = EndRow; bool Status = s->Write(AddressOf(), Pos) == Pos; Pos = 0; return Status; } }; +#define IntIo(type) inline void StructIo(LStructuredIo &io, type i) { io.Int(i); } +#define StrIo(type) inline void StructIo(LStructuredIo &io, type i) { io.String(i); } + +IntIo(char) +IntIo(unsigned char) +IntIo(short) +IntIo(unsigned short) +IntIo(int) +IntIo(unsigned int) +IntIo(int64_t) +IntIo(uint64_t) + +StrIo(char*); +StrIo(const char*); +StrIo(wchar_t*); +StrIo(const wchar_t*); + +inline void StructIo(LStructuredIo &io, LString &s) +{ + if (io.GetWrite()) + io.String(s.Get(), s.Length()); + else + io.Decode([&s](auto type, auto sz, auto ptr, auto name) + { + if (type == GV_STRING && ptr && sz > 0) + s.Set((char*)ptr, sz); + }); +} + +inline void StructIo(LStructuredIo &io, LStringPipe &p) +{ + // auto obj = io.StartObj("LStringPipe"); + if (io.GetWrite()) + { + p.Iterate([&io](auto ptr, auto bytes) + { + io.String(ptr, bytes); + return true; + }); + } + else + { + io.Decode([&p](auto type, auto sz, auto ptr, auto name) + { + if (type == GV_STRING && ptr && sz > 0) + p.Write(ptr, sz); + }); + } +} + +inline void StructIo(LStructuredIo &io, LRect &r) +{ + auto obj = io.StartObj("LRect"); + io.Int(r.x1, "x1"); + io.Int(r.y1, "y1"); + io.Int(r.x2, "x2"); + io.Int(r.y2, "y2"); +} + +inline void StructIo(LStructuredIo &io, LColour &c) +{ + auto obj = io.StartObj("LColour"); + LString cs; + uint8_t r, g, b, a; + + if (io.GetWrite()) + { + cs = LColourSpaceToString(c.GetColourSpace()); + r = c.r(); g = c.g(); b = c.b(); a = c.a(); + } + + io.String(cs, "colourSpace"); + io.Int(r, "r"); + io.Int(g, "g"); + io.Int(b, "b"); + io.Int(a, "a"); + + if (!io.GetWrite()) + { + c.SetColourSpace(LStringToColourSpace(cs)); + c.r(r); c.g(g); c.b(b); c.a(a); + } +} + #endif \ No newline at end of file diff --git a/include/lgi/common/StructuredLog.h b/include/lgi/common/StructuredLog.h --- a/include/lgi/common/StructuredLog.h +++ b/include/lgi/common/StructuredLog.h @@ -1,223 +1,167 @@ #pragma once #include #include "lgi/common/File.h" #ifndef _STRUCTURED_IO_H_ #include "lgi/common/StructuredIo.h" #endif -#define IntIo(type) inline void StructIo(LStructuredIo &io, type i) { io.Int(i); } -#define StrIo(type) inline void StructIo(LStructuredIo &io, type i) { io.String(i); } - -IntIo(char) -IntIo(unsigned char) -IntIo(short) -IntIo(unsigned short) -IntIo(int) -IntIo(unsigned int) -IntIo(int64_t) -IntIo(uint64_t) - -StrIo(char*); -StrIo(const char*); -StrIo(wchar_t*); -StrIo(const wchar_t*); - -inline void StructIo(LStructuredIo &io, LString &s) -{ - if (io.GetWrite()) - io.String(s.Get(), s.Length()); - else - io.Decode([&s](auto type, auto sz, auto ptr, auto name) - { - if (type == GV_STRING && ptr && sz > 0) - s.Set((char*)ptr, sz); - }); -} - -inline void StructIo(LStructuredIo &io, LStringPipe &p) -{ - // auto obj = io.StartObj("LStringPipe"); - if (io.GetWrite()) - { - p.Iterate([&io](auto ptr, auto bytes) - { - io.String(ptr, bytes); - return true; - }); - } - else - { - io.Decode([&p](auto type, auto sz, auto ptr, auto name) - { - if (type == GV_STRING && ptr && sz > 0) - p.Write(ptr, sz); - }); - } -} - -inline void StructIo(LStructuredIo &io, LRect &r) -{ - auto obj = io.StartObj("LRect"); - io.Int(r.x1, "x1"); - io.Int(r.y1, "y1"); - io.Int(r.x2, "x2"); - io.Int(r.y2, "y2"); -} - class LStructuredLog { LFile f; LStructuredIo io; template void Store(T &t) { StructIo(io, t); } public: LStructuredLog(const char *FileName, bool write = true) : io(write) { LString fn; if (LIsRelativePath(FileName)) { LFile::Path p(LSP_APP_INSTALL); p += FileName; fn = p.GetFull(); } else fn = FileName; if (f.Open(fn, write ? O_WRITE : O_READ) && write) f.SetSize(0); } virtual ~LStructuredLog() { + io.Flush(&f); } + LStructuredIo &GetIo() { return io; } + // Write objects to the log. Custom types will need to have a StructIo implementation template void Log(Args&&... args) { if (!io.GetWrite()) { LAssert(!"Not open for writing."); return; } int dummy[] = { 0, ( (void) Store(std::forward(args)), 0) ... }; io.Flush(&f); } // Read and decode to string objects. bool Read(std::function callback, Progress *prog = NULL) { if (io.GetWrite()) { LAssert(!"Not open for reading."); return false; } if (io.GetPos() >= io.Length()) { if (!io.Length(f.GetSize())) return false; if (prog) prog->SetRange(f.GetSize()); for (int64_t i=0; iValue(i); if (prog->IsCancelled()) return false; } } } if (prog && prog->IsCancelled()) return false; return io.Decode(callback, prog); } // Read and convert to a string. void Read(std::function callback) { LStringPipe p; while (Read([&p](auto type, auto sz, auto ptr, auto name) { LString prefix; if (type == LStructuredIo::EndRow) return; if (p.GetSize()) prefix = " "; if (name) { prefix += name; prefix += "="; } switch (type) { case GV_STRING: { p.Print("%s%.*s", prefix ? prefix.Get() : "", (int)sz, ptr); break; } case GV_INT64: { if (sz > 4) p.Print("%s" LPrintfInt64, prefix ? prefix.Get() : "", *((int64*)ptr)); else p.Print("%s%i", prefix ? prefix.Get() : "", *((int*)ptr)); break; } case LStructuredIo::StartObject: { p.Print("%s {", name); break; } case LStructuredIo::EndObject: { p.Print(" }"); break; } default: { LAssert(!"Impl me."); break; } } })); callback(p.NewLStr()); } static void UnitTest() { auto fn = "my-test-log.struct"; { LStructuredLog Log(fn); LString asd = LString("1234568,"); int ert = 123; LRect rc(10, 10, 200, 100); Log.Log("asd:", asd, rc, ert); } { LStructuredLog Rd(fn, false); Rd.Read([](LString s) { LgiTrace("%s\n", s.Get()); }); } } }; diff --git a/include/lgi/common/Text.h b/include/lgi/common/Text.h --- a/include/lgi/common/Text.h +++ b/include/lgi/common/Text.h @@ -1,321 +1,321 @@ /*hdr ** FILE: Text.h ** AUTHOR: Matthew Allen ** DATE: 27/6/97 ** DESCRIPTION: Generic text document handler ** ** Copyright (C) 1997, Matthew Allen ** fret@memecode.com */ #ifndef __TEXT_H #define __TEXT_H #include "Lgi.h" // word wrap -#define TEXTED_WRAP_NONE 0 // no word wrap +#define L_WRAP_NONE 0 // no word wrap #define TEXTED_WRAP_NONREFLOW 1 // insert LF when you hit the end -#define TEXTED_WRAP_REFLOW 2 // dynamically wrap line to editor width +#define L_WRAP_REFLOW 2 // dynamically wrap line to editor width // Code pages #define TVCP_US_ASCII 0 #define TVCP_ISO_8859_2 1 #define TVCP_WIN_1250 2 #define TVCP_MAX 3 // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define IsWhite(c) (strchr(WhiteSpace, c) != 0) #define IsDelimiter(c) (strchr(Delimiters, c) != 0) #define IsDigit(c) ((c) >= '0' AND (c) <= '9') #define IsAlpha(c) (((c) >= 'a' AND (c) <= 'z') || ((c) >= 'A' AND (c) <= 'Z')) #define IsText(c) (IsDigit(c) || IsAlpha(c) || (c) == '_') extern char Delimiters[]; extern int StrLen(char *c); extern int RevStrLen(char *c); // Document class Document { protected: char *File; LFile F; bool Open(char *FileName, int Attrib); public: Document(); virtual ~Document(); virtual bool Load(char *File) { return FALSE; } virtual bool Save(char *File) { return FALSE; } char *GetName() { return File; } }; // Text class TextDocument; class LCursor { friend class TextDocument; TextDocument *Parent; int x, y; // location of cursor int Length; // size of the current line int Offset; // offset to the start of the line void GoUpLine(); void GoDownLine(); public: LCursor(); ~LCursor(); int X() { return x; } int Y() { return y; } int LineLength() { return Length; } int GetOffset() { return Offset; } operator char*(); char *GetStr(); bool AtEndOfLine(); bool AtBeginningOfLine(); int CursorX(int TabSize); void SetX(int X); void SetY(int Y); void MoveX(int Dx); void MoveY(int Dy); bool operator ==(LCursor &c) { return (x == c.x) AND (y == c.y) AND (Parent == c.Parent); } bool operator !=(LCursor &c) { return (x != c.x) || (y != c.y) || (Parent != c.Parent); } bool operator <(LCursor &c) { return (y < c.y) || ((y == c.y) AND (x < c.x)); } bool operator <=(LCursor &c) { return (y < c.y) || ((y == c.y) AND (x <= c.x)); } bool operator >(LCursor &c) { return (y > c.y) || ((y == c.y) AND (x > c.x)); } bool operator >=(LCursor &c) { return (y > c.y) || ((y == c.y) AND (x >= c.x)); } int operator -(LCursor &c) { return (Offset + x) - (c.Offset + c.x); } }; class TextLock { friend class TextDocument; int StartLine; int Lines; char **Line; ushort **LineW; public: TextLock(); ~TextLock(); int Start() { return StartLine; } char *operator [](int i); ushort *GetLineW(int i); }; class UserAction { public: char *Text; int x, y; bool Insert; UserAction(); ~UserAction(); }; #define TEXT_BLOCK 0x4000 #define TEXT_MASK 0x3FFF class TextDocument : public Document { public: friend LCursor; int Flags; int LockCount; bool Dirty; bool CrLf; bool Editable; // Undo stuff int IgnoreUndo; int UndoPos; List Queue; void TruncateQueue(); void ApplyAction(UserAction *a, LCursor &c, bool Reverse = false); // Data int Lines; // Total lines of text int Length; // Bytes used by doc int Alloc; // Allocated memory char *Data; // Pointer to start of doc // Methods bool SetLength(int Len); int CountLines(char *c, int Len = -1); char *FindLine(int i); public: TextDocument(); virtual ~TextDocument(); bool UseCrLf() { return CrLf; } void UseCrLf(bool Use) { CrLf = Use; } bool AcceptEdits() { return Editable; } void AcceptEdits(bool i) { Editable = i; } // size int GetLines() { return Lines; } int GetLength() { return Length; } // serialization bool Load(char *File); bool Save(char *File); bool IsDirty() { return Dirty; } bool Import(char *s, int size = -1); bool Export(char *&s, int &size); // data access bool Lock(TextLock *Lock, int StartLine, int Lines, int CodePage = TVCP_US_ASCII); void UnLock(TextLock *Lock); bool MoveLock(TextLock *Lock, int Dy); // cursor support bool CursorCreate(LCursor *c, int X, int Y); // data io bool Insert(LCursor *At, char *Text, int Len = -1); bool Delete(LCursor *From, int Len, char *Buffer = NULL); // undo void Undo(LCursor &c); void Redo(LCursor &c); void ClearUndoQueue(); bool UndoAvailable(bool Redo = false); }; #define TVF_SELECTION 0x00000001 #define TVF_DIRTY_CURSOR 0x00000010 #define TVF_DIRTY_TO_EOL 0x00000020 #define TVF_DIRTY_TO_EOP 0x00000040 #define TVF_DIRTY_SELECTION 0x00000080 #define TVF_DIRTY_ALL 0x00000100 #define TVF_DIRTY_MASK (TVF_DIRTY_CURSOR | TVF_DIRTY_TO_EOL | TVF_DIRTY_TO_EOP | TVF_DIRTY_SELECTION | TVF_DIRTY_ALL) #define TVF_SHIFT 0x00000200 #define TVF_GOTO_START 0x00000400 // of selection #define TVF_GOTO_END 0x00000800 // of selection #define TVF_EAT_MOVE 0x00001000 // don't move cursor class TextView { protected: // Misc int Flags; bool IsDirty(); virtual void Dirty(int Type); void Clean(); ushort *GetCodePageMap(int Page = -1); char *StatusMsg; void SetStatus(char *Msg); // Clipboard char *ClipData; virtual bool ClipText(char *Str, int Len = -1); virtual char *ClipText(); virtual bool Cut(); virtual bool Copy(); virtual bool Paste(); // Data storage TextDocument Doc; bool OnInsertText(char *Text, int Len = -1); bool OnDeleteText(LCursor *c, int Len, bool Clip); // Current cursor location int HiddenLines; int DisplayLines; LCursor User; virtual bool UpdateHiddenCheck(); virtual bool OnMoveCursor(int Dx, int Dy = 0, bool NoSelect = FALSE); virtual void OnSetHidden(int Hidden); virtual void OnSetCursor(int X, int Y); virtual bool OnMultiLineTab(bool In) { return FALSE; } // return TRUE if processed // Selection LCursor Start, End; void OnStartSelection(LCursor *c); void OnEndSelection(); void OnDeleteSelection(bool Clip); // Events virtual void AfterInsertText(char *c, int len) {} virtual void AfterDeleteText() {} // Properties int WrapType; int CodePage; public: TextView(); virtual ~TextView(); virtual bool Open(char *Name); virtual bool Save(char *Name); virtual int ProcessKey(LKey &K); virtual int Paint() { return FALSE; } virtual int GetWrapType() { return WrapType; } virtual void SetWrapType(int i) { WrapType = i; } virtual int GetCodePage() { return CodePage; } virtual void SetCodePage(int i) { CodePage = i; } virtual bool ClearDirty(bool Ask); virtual void OnSave(); virtual void OnGotoLine(); virtual void OnEscape(LKey &K); virtual void OnEnter(LKey &K); }; #endif diff --git a/include/lgi/common/TextLog.h b/include/lgi/common/TextLog.h --- a/include/lgi/common/TextLog.h +++ b/include/lgi/common/TextLog.h @@ -1,169 +1,169 @@ /// \file /// \author Matthew Allen #ifndef _LTEXTLOG_H_ #define _LTEXTLOG_H_ #include "lgi/common/TextView3.h" #include "lgi/common/Net.h" template class LThreadSafeTextView : public TView, public LStream { protected: bool ProcessReturns; size_t Pos; LMutex Sem; LArray Txt; void ProcessTxt() { if (Txt.Length() == 0) return; LArray Local; if (Sem.LockWithTimeout(250, _FL)) { // Minimize time locked by moving the text to a local var Local.Swap(Txt); Sem.Unlock(); } else { TView::PostEvent(M_LOG_TEXT); // Try again later... return; } if (Local.Length()) { LString msg; msg.Printf("LTextLog::ProcessTxt(%" PRIu64 ")", (uint64)Txt.Length()); LProfile p(msg, 200); Add(Local.AddressOf(), Local.Length()); } } public: LThreadSafeTextView(int id) : TView(id, 0, 0, 2000, 1000), Sem("LThreadSafeTextView") { ProcessReturns = true; Pos = 0; TView::Sunken(true); TView::SetPourLargest(true); TView::SetUndoOn(false); - TView::SetWrapType(TEXTED_WRAP_NONE); + TView::SetWrapType(L_WRAP_NONE); } void OnCreate() { TView::OnCreate(); // ProcessTxt(); } void OnPulse() { ProcessTxt(); } virtual void Add(char16 *w, ssize_t chars = -1) { ssize_t Len = chars >= 0 ? chars : StrlenW(w); bool AtEnd = TView::GetCaret() == TView::Size; if (ProcessReturns) { auto *s = w, *prev = w; auto Ins = [this](char16 *s, ssize_t len) { if (len > 0) { auto del = MIN(TView::Size - (ssize_t)Pos, len); if (del > 0) TView::Delete(Pos, del); TView::Insert(Pos, s, len); Pos += len; } }; for (s = w; chars >= 0 ? s < w + chars : *s; s++) { if (*s == '\r') { Ins(prev, s - prev); prev = s + 1; while (Pos > 0 && TView::Text[Pos-1] != '\n') Pos--; } else if (*s == '\n') { Pos = TView::Size; } } Ins(prev, s - prev); } else { TView::Insert(TView::Size, w, Len); } TView::Invalidate(); if (AtEnd) TView::SetCaret(TView::Size, false); } int64 SetSize(int64 s) { TView::Name(0); return 0; } bool Write(const LString &s) { return Write(s.Get(), s.Length()) == s.Length(); } ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) { LAutoWString w(Utf8ToWide((char*)Buffer, Size)); if (!w) return 0; size_t OldLen = Txt.Length(); if (Sem.Lock(_FL)) { Txt.Add(w.Get(), Strlen(w.Get())); Sem.Unlock(); } if (!OldLen #if LGI_VIEW_HANDLE && TView::Handle() #endif ) { TView::PostEvent(M_LOG_TEXT); } return Size; } LMessage::Result OnEvent(LMessage *m) { if (m->Msg() == M_LOG_TEXT) { ProcessTxt(); } return TView::OnEvent(m); } }; typedef LThreadSafeTextView LTextLog; #ifdef _LTEXTVIEW4_H typedef LThreadSafeTextView LTextLog4; #endif #endif diff --git a/include/lgi/common/TextView3.h b/include/lgi/common/TextView3.h --- a/include/lgi/common/TextView3.h +++ b/include/lgi/common/TextView3.h @@ -1,439 +1,439 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef __GTEXTVIEW3_H #define __GTEXTVIEW3_H #include #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Css.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/FindReplaceDlg.h" // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str extern char Delimiters[]; class LTextView3; /// Unicode text editor control. class LgiClass LTextView3 : public LDocView, public ResObject, public LDragDropTarget { friend struct LTextView3Undo; friend bool Text3_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: enum Messages { M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421, M_TEXTVIEW_FIND, M_TEXTVIEW_REPLACE, M_TEXT_POUR_CONTINUE, }; enum StyleOwners { STYLE_NONE, STYLE_IDE, STYLE_SPELLING, STYLE_FIND_MATCHES, STYLE_ADDRESS, STYLE_URL, }; class LStyle { protected: void RefreshLayout(size_t Start, ssize_t Len); public: /// The view the style is for LTextView3 *View; /// When you write several bits of code to do styling assign them /// different owner id's so that they can manage the lifespan of their /// own styles. LTextView3::PourStyle is owner '0', anything else it /// will leave alone. StyleOwners Owner; /// The start index into the text buffer of the region to style. ssize_t Start; /// The length of the styled region ssize_t Len; /// The font to draw the styled text in LFont *Font; /// The colour to draw with. If transparent, then the default /// line colour is used. LColour Fore, Back; /// Cursor LCursor Cursor; /// Optional extra decor not supported by the fonts LCss::TextDecorType Decor; /// Colour for the optional decor. LColour DecorColour; /// Application base data LVariant Data; LStyle(StyleOwners owner = STYLE_NONE) { Owner = owner; View = NULL; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; } LStyle(const LStyle &s) { Owner = s.Owner; View = s.View; Font = s.Font; Start = s.Start; Len = s.Len; Decor = s.Decor; DecorColour = s.DecorColour; Fore = s.Fore; Back = s.Back; Data = s.Data; Cursor = s.Cursor; } LStyle &Construct(LTextView3 *view, StyleOwners owner) { View = view; Owner = owner; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; return *this; } void Empty() { Start = -1; Len = 0; } bool Valid() { return Start >= 0 && Len > 0; } size_t End() const { return Start + Len; } /// \returns true if style is the same bool operator ==(const LStyle &s) { return Owner == s.Owner && Start == s.Start && Len == s.Len && Fore == s.Fore && Back == s.Back && Decor == s.Decor; } /// Returns true if this style overlaps the position of 's' bool Overlap(LStyle &s) { return Overlap(s.Start, s.Len); } /// Returns true if this style overlaps the position of 's' bool Overlap(ssize_t sStart, ssize_t sLen) { if (sStart + sLen - 1 < Start || sStart >= Start + Len) return false; return true; } void Union(const LStyle &s) { if (Start < 0) { Start = s.Start; Len = s.Len; } else { Start = MIN(Start, s.Start); Len = MAX(End(), s.End()) - Start; } } }; friend class LTextView3::LStyle; protected: // Internal classes enum LTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; class LTextLine : public LRange { public: LRect r; // Screen location LColour c; // Colour of line... transparent = default colour LColour Back; // Background colour or transparent LTextLine() { Start = -1; Len = 0; r.ZOff(-1, -1); } virtual ~LTextLine() {} bool Overlap(ssize_t i) { return i>=Start && i<=Start+Len; } size_t CalcLen(char16 *Text) { char16 *c = Text + Start, *e = c; while (*e && *e != '\n') e++; return Len = e - c; } }; class LTextView3Private *d; friend class LTextView3Private; // Options bool Dirty = false; bool CanScrollX = false; // Display LFont *Font = NULL; LFont *Bold = NULL; // Bold variant of 'Font' LFont *Underline = NULL; // Underline variant of 'Font' LFont *FixedFont = NULL; int LineY = 1; ssize_t SelStart = -1, SelEnd = -1; int DocOffset = 0; int MaxX = 0; bool Blink = true; uint64 BlinkTs = 0; int ScrollX = 0; LRect CursorPos; /// true if the text pour process is still ongoing bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots // of related edits at the same time. And then manually pour once // finished. bool PartialPour = false; // True if the pour is happening in the background. It's not threaded // but taking place in the GUI thread via timer. size_t PartialPourLines = 0;// Partial pour max lines, if we restart this tracks the max we saw... bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true) List Line; LUnrolledList Style; // sorted in 'Start' order typedef LUnrolledList::Iter StyleIter; // For ::Name(...) char *TextCache = NULL; // Data char16 *Text; ssize_t Cursor; ssize_t Size; ssize_t Alloc; // Undo stuff bool UndoOn = true; LUndo UndoQue; struct LTextView3Undo *UndoCur = NULL; // private methods List::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0); LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { return *GetTextLineIt(Offset, Index); } ssize_t SeekLine(ssize_t Offset, LTextViewSeek Where); int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin); bool ScrollToOffset(size_t Off); int ScrollYLine(); int ScrollYPixel(); LRect DocToScreen(LRect r); ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); void InternalPulse(); // styles bool InsertStyle(LAutoPtr s); LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1); LStyle *HitStyle(ssize_t i); int GetColumn(); int SpaceDepth(char16 *Start, char16 *End); int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false); // Overridables virtual void PourText(size_t Start, ssize_t Length); virtual void PourStyle(size_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false); #ifdef _DEBUG // debug uint64 _PourTime; uint64 _StyleTime; uint64 _PaintTime; #endif void LogLines(); bool ValidateLines(bool CheckBox = false); public: // Construction LTextView3( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = NULL); ~LTextView3(); const char *GetClass() override { return "LTextView3"; } // Data const char *Name() override; bool Name(const char *s) override; const char16 *NameW() override; bool NameW(const char16 *s) override; int64 Value() override; void Value(int64 i) override; const char *GetMimeType() override { return "text/plain"; } size_t Length() { return Size; } LString operator[](ssize_t LineIdx); const char16 *TextAtLine(size_t Index); ssize_t HitText(int x, int y, bool Nearest); void DeleteSelection(char16 **Cut = 0); // Font LFont *GetFont() override; LFont *GetBold(); void SetFont(LFont *f, bool OwnIt = false) override; void SetFixedWidthFont(bool i) override; // Options void SetTabSize(uint8_t i) override; void SetBorder(int b); void SetReadOnly(bool i) override; void SetCrLf(bool crlf) override; - /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW + /// Sets the wrapping on the control, use #L_WRAP_NONE or #L_WRAP_REFLOW void SetWrapType(LDocWrapType i) override; // State / Selection ssize_t GetCaret(bool Cursor = true) override; virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override; ssize_t IndexAt(int x, int y) override; bool IsDirty() override { return Dirty; } void IsDirty(bool d) { Dirty = d; } bool HasSelection() override; void UnSelectAll() override; void SelectWord(size_t From) override; void SelectAll() override; bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override; size_t GetLines() override; void GetTextExtent(int &x, int &y) override; char *GetSelection() override; LRange GetSelectionRange(); // File IO bool Open(const char *Name, const char *Cs = NULL) override; bool Save(const char *Name, const char *Cs = NULL) override; const char *GetLastError(); // Clipboard IO bool Cut() override; bool Copy() override; bool Paste() override; // Undo/Redo void Undo(); void Redo(); bool GetUndoOn() { return UndoOn; } void SetUndoOn(bool b) { UndoOn = b; } // Action UI virtual void DoGoto(std::function Callback); virtual void DoCase(std::function Callback, bool Upper); virtual void DoFind(std::function Callback) override; virtual void DoFindNext(std::function Callback); virtual void DoReplace(std::function Callback) override; // Action Processing void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); ssize_t GetLine(); void SetLine(int64_t Line, bool select = false); LDocFindReplaceParams *CreateFindReplaceParams() override; void SetFindReplaceParams(LDocFindReplaceParams *Params) override; // Object Events virtual bool OnFind( const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); virtual bool OnReplace( const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange() override; void OnCreate() override; void OnEscape(LKey &K) override; bool OnMouseWheel(double Lines) override; // Window Events void OnFocus(bool f) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPulse() override; int OnHitTest(int x, int y) override; bool OnLayout(LViewLayoutInfo &Inf) override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; LCursor GetCursor(int x, int y) override; // Virtuals virtual bool Insert(size_t At, const char16 *Data, ssize_t Len); virtual bool Delete(size_t At, ssize_t Len); virtual void OnEnter(LKey &k) override; virtual void OnUrl(char *Url) override; virtual void DoContextMenu(LMouse &m); virtual bool OnStyleClick(LStyle *style, LMouse *m); virtual bool OnStyleMenu(LStyle *style, LSubMenu *m); virtual void OnStyleMenuClick(LStyle *style, int i); }; #endif diff --git a/include/lgi/common/TextView4.h b/include/lgi/common/TextView4.h --- a/include/lgi/common/TextView4.h +++ b/include/lgi/common/TextView4.h @@ -1,448 +1,473 @@ /// \file /// \author Matthew Allen /// \brief A unicode text editor #ifndef _LTEXTVIEW4_H #define _LTEXTVIEW4_H #include "lgi/common/DocView.h" #include "lgi/common/Undo.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Css.h" #include "lgi/common/UnrolledList.h" #include "lgi/common/FindReplaceDlg.h" +#include "lgi/common/StructuredIo.h" // use CRLF as opposed to just LF // internally it uses LF only... this is just to remember what to // save out as. #define TEXTED_USES_CR 0x00000001 #define TAB_SIZE 4 #define DEBUG_TIMES_MSG 8000 // a=0 b=(char*)Str +#define DEBUG_EDIT_LOG 1 extern char Delimiters[]; class LTextView4; /// Unicode text editor control. class LgiClass LTextView4 : public LDocView, public ResObject, public LDragDropTarget { friend struct LTextView4Undo; friend bool Text4_FindCallback(LFindReplaceCommon *Dlg, bool Replace, void *User); public: constexpr static int ALLOC_BLOCK = 64; enum Messages { M_TEXTVIEW_DEBUG_TEXT = M_USER + 0x3421, M_TEXTVIEW_FIND, M_TEXTVIEW_REPLACE, M_TEXT_POUR_CONTINUE, }; enum StyleOwners { STYLE_NONE, STYLE_IDE, STYLE_SPELLING, STYLE_FIND_MATCHES, STYLE_ADDRESS, STYLE_URL, }; class LStyle : public LRange { protected: void RefreshLayout(size_t Start, ssize_t Len); public: /// The view the style is for LTextView4 *View = NULL; /// When you write several bits of code to do styling assign them /// different owner id's so that they can manage the lifespan of their /// own styles. LTextView4::PourStyle is owner '0', anything else it /// will leave alone. StyleOwners Owner; /// The font to draw the styled text in LFont *Font = NULL; /// The colour to draw with. If transparent, then the default /// line colour is used. LColour Fore, Back; /// Cursor LCursor Cursor; /// Optional extra decor not supported by the fonts LCss::TextDecorType Decor; /// Colour for the optional decor. LColour DecorColour; /// Application base data LVariant Data; LStyle(StyleOwners owner = STYLE_NONE) { Owner = owner; View = NULL; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; } LStyle(const LStyle &s) { Owner = s.Owner; View = s.View; Font = s.Font; Start = s.Start; Len = s.Len; Decor = s.Decor; DecorColour = s.DecorColour; Fore = s.Fore; Back = s.Back; Data = s.Data; Cursor = s.Cursor; } LStyle &Construct(LTextView4 *view, StyleOwners owner) { View = view; Owner = owner; Font = NULL; Empty(); Cursor = LCUR_Normal; Decor = LCss::TextDecorNone; return *this; } void Empty() { Start = -1; Len = 0; } bool Valid() { return Start >= 0 && Len > 0; } size_t End() const { return Start + Len; } /// \returns true if style is the same bool operator ==(const LStyle &s) { return Owner == s.Owner && Start == s.Start && Len == s.Len && Fore == s.Fore && Back == s.Back && Decor == s.Decor; } /// Returns true if this style overlaps the position of 's' bool Overlap(LStyle &s) { return Overlap(s.Start, s.Len); } /// Returns true if this style overlaps the position of 's' bool Overlap(ssize_t sStart, ssize_t sLen) { if (sStart + sLen - 1 < Start || sStart >= Start + Len) return false; return true; } void Union(const LStyle &s) { if (Start < 0) { Start = s.Start; Len = s.Len; } else { Start = MIN(Start, s.Start); Len = MAX(End(), s.End()) - Start; } } }; friend class LTextView4::LStyle; protected: + + #if DEBUG_EDIT_LOG + enum EditType + { + EditInsert, + EditDelete + }; + + struct EditLog + { + EditType Type; + LArray Str; + size_t Length; + }; + + friend inline void StructIo(LStructuredIo &io, LTextView4::EditLog &s); + LArray Edits; + bool FirstErrorLog = true; + void SaveLog(const char *File); + void LoadLog(const char *File); + #endif + // Internal classes enum LTextViewSeek { PrevLine, NextLine, StartLine, EndLine }; class LTextLine : public LRange { public: LRect r; // Screen location LColour c; // Colour of line... transparent = default colour LColour Back; // Background colour or transparent bool NewLine = false; const char *File = NULL; int Line = -1; LTextLine(const char *file, int line) { File = file; Line = line; Start = -1; r.ZOff(-1, -1); } virtual ~LTextLine() {} size_t CalcLen(char16 *Text) { char16 *c = Text + Start, *e = c; while (*e && *e != '\n') e++; NewLine = *e == '\n'; return Len = e - c; } bool Overlap(ssize_t Val) { return (Val == Start) || (Val >= Start && Val < (End() + NewLine)); } ssize_t EndNewLine() { return End() + NewLine; } }; class LTextView4Private *d; friend class LTextView4Private; + friend inline void StructIo(LStructuredIo &io, LTextView4::LTextLine &s); // Options bool Dirty = false; bool CanScrollX = false; // Display LFont *Font = NULL; LFont *Bold = NULL; // Bold variant of 'Font' LFont *Underline = NULL; // Underline variant of 'Font' LFont *FixedFont = NULL; int LineY = -1; ssize_t SelStart = -1, SelEnd = -1; int DocOffset = 0; int MaxX = 0; bool Blink = true; uint64 BlinkTs = 0; int ScrollX = 0; LRect CursorPos; /// true if the text pour process is still ongoing bool PourEnabled = true; // True if pouring the text happens on edit. Turn off if doing lots // of related edits at the same time. And then manually pour once // finished. bool PartialPour = false; // True if the pour is happening in the background. It's not threaded // but taking place in the GUI thread via timer. bool AdjustStylePos = true; // Insert/Delete moved styles automatically to match (default: true) LArray Line; LUnrolledList Style; // sorted in 'Start' order typedef LUnrolledList::Iter StyleIter; // For ::Name(...) char *TextCache = NULL; // Data char16 *Text = NULL; ssize_t Cursor = 0; ssize_t Size = 0; ssize_t Alloc = ALLOC_BLOCK; // Undo stuff bool UndoOn = true; LUndo UndoQue; struct LTextView4Undo *UndoCur = NULL; // private methods LArray::I GetTextLineIt(ssize_t Offset, ssize_t *Index = 0); LTextLine *GetTextLine(ssize_t Offset, ssize_t *Index = 0) { auto it = GetTextLineIt(Offset, Index); return it != Line.end() ? *it : NULL; } ssize_t SeekLine(ssize_t Offset, LTextViewSeek Where); int TextWidth(LFont *f, char16 *s, int Len, int x, int Origin); bool ScrollToOffset(size_t Off); int ScrollYLine(); int ScrollYPixel(); LRect DocToScreen(LRect r); ptrdiff_t MatchText(const char16 *Text, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); void InternalPulse(); // styles bool InsertStyle(LAutoPtr s); LStyle *GetNextStyle(StyleIter &it, ssize_t Where = -1); LStyle *HitStyle(ssize_t i); int GetColumn(); int SpaceDepth(char16 *Start, char16 *End); int AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle = false); // Overridables virtual void PourText(size_t Start, ssize_t Length); virtual void PourStyle(size_t Start, ssize_t Length); virtual void OnFontChange(); virtual void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour); virtual char16 *MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace = false); #ifdef _DEBUG // debug uint64 _PourTime = 0; uint64 _StyleTime = 0; uint64 _PaintTime = 0; #endif - void LogLines(); + LString LogLines(); bool ValidateLines(bool CheckBox = false); public: // Construction LTextView4( int Id, int x = 0, int y = 0, int cx = 100, int cy = 100, LFontType *FontInfo = NULL); ~LTextView4(); const char *GetClass() override { return "LTextView4"; } // Data const char *Name() override; bool Name(const char *s) override; const char16 *NameW() override; bool NameW(const char16 *s) override; int64 Value() override; void Value(int64 i) override; const char *GetMimeType() override { return "text/plain"; } size_t Length() const { return Size; } LString operator[](ssize_t LineIdx); ssize_t HitText(int x, int y, bool Nearest); void DeleteSelection(char16 **Cut = 0); // Font LFont *GetFont() override; LFont *GetBold(); void SetFont(LFont *f, bool OwnIt = false) override; void SetFixedWidthFont(bool i) override; // Options void SetTabSize(uint8_t i) override; void SetBorder(int b); void SetReadOnly(bool i) override; void SetCrLf(bool crlf) override; - /// Sets the wrapping on the control, use #TEXTED_WRAP_NONE or #TEXTED_WRAP_REFLOW + /// Sets the wrapping on the control, use #L_WRAP_NONE or #L_WRAP_REFLOW void SetWrapType(LDocWrapType i) override; // State / Selection ssize_t GetCaret(bool Cursor = true) override; virtual void SetCaret(size_t i, bool Select = false, bool ForceFullUpdate = false) override; ssize_t IndexAt(int x, int y) override; bool IsDirty() override { return Dirty; } void IsDirty(bool d) { Dirty = d; } bool HasSelection() override; void UnSelectAll() override; void SelectWord(size_t From) override; void SelectAll() override; bool GetLineColumnAtIndex(LPoint &Pt, ssize_t Index = -1) override; size_t GetLines() override; void GetTextExtent(int &x, int &y) override; char *GetSelection() override; LRange GetSelectionRange(); // File IO bool Open(const char *Name, const char *Cs = 0) override; bool Save(const char *Name, const char *Cs = 0) override; const char *GetLastError(); // Clipboard IO bool Cut() override; bool Copy() override; bool Paste() override; // Undo/Redo void Undo(); void Redo(); bool GetUndoOn() { return UndoOn; } void SetUndoOn(bool b) { UndoOn = b; } // Action UI virtual void DoGoto(std::function Callback); virtual void DoCase(std::function Callback, bool Upper); virtual void DoFind(std::function Callback) override; virtual void DoFindNext(std::function Callback); virtual void DoReplace(std::function Callback) override; // Action Processing void ClearDirty(std::function OnStatus, bool Ask, const char *FileName = 0); void UpdateScrollBars(bool Reset = false); ssize_t GetLine(); void SetLine(int64_t Line); LDocFindReplaceParams *CreateFindReplaceParams() override; void SetFindReplaceParams(LDocFindReplaceParams *Params) override; // Object Events virtual bool OnFind( const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); virtual bool OnReplace( const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards); bool OnMultiLineTab(bool In); void OnSetHidden(int Hidden); void OnPosChange() override; void OnCreate() override; void OnEscape(LKey &K) override; bool OnMouseWheel(double Lines) override; // Window Events void OnFocus(bool f) override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnPaint(LSurface *pDC) override; LMessage::Result OnEvent(LMessage *Msg) override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPulse() override; int OnHitTest(int x, int y) override; bool OnLayout(LViewLayoutInfo &Inf) override; int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; LCursor GetCursor(int x, int y) override; // Virtuals virtual bool Insert(size_t At, const char16 *Data, ssize_t Len); virtual bool Delete(size_t At, ssize_t Len); virtual void OnEnter(LKey &k) override; virtual void OnUrl(char *Url) override; virtual void DoContextMenu(LMouse &m); virtual bool OnStyleClick(LStyle *style, LMouse *m); virtual bool OnStyleMenu(LStyle *style, LSubMenu *m); virtual void OnStyleMenuClick(LStyle *style, int i); }; #endif // _GTEXTVIEW4_H diff --git a/src/common/Coding/ScriptVM.cpp b/src/common/Coding/ScriptVM.cpp --- a/src/common/Coding/ScriptVM.cpp +++ b/src/common/Coding/ScriptVM.cpp @@ -1,2160 +1,2160 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/Box.h" #include "lgi/common/TabView.h" #include "lgi/common/TextLog.h" #include "lgi/common/List.h" #include "lgi/common/ToolBar.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Matrix.h" #include "lgi/common/Menu.h" #include "ScriptingPriv.h" #define TIME_INSTRUCTIONS 0 #define POST_EXECUTE_STATE 0 // #define BREAK_POINT 0x0000009F #define ExitScriptExecution c.u8 = e #define SetScriptError c.u8 = e; Status = ScriptError #define CurrentScriptAddress (c.u8 - Base) #define CheckParam(ptr) if (!(ptr)) \ { \ OnException(_FL, CurrentScriptAddress-1, #ptr); \ c.u8 = e; \ Status = ScriptError; \ break; \ } #define AddLocalSize(NewSize) \ size_t LocalsBase = Locals.Length(); \ Locals.SetFixedLength(false); \ /* LgiTrace("%s:%i - Locals %i -> %i\n", _FL, LocalsBase, LocalsBase + NewSize); */ \ Locals.Length(LocalsBase + NewSize); \ Scope[SCOPE_LOCAL] = &Locals[LocalsBase]; \ Locals.SetFixedLength(); #ifdef WIN32 extern "C" uint64 __cdecl CallExtern64(void *FuncAddr, NativeInt *Ret, uint32_t Args, void *Arg); #elif defined(LINUX) #include #endif int LVariantCmp(LVariant *a, LVariant *b, NativeInt Data) { LVariant *Param = (LVariant*) Data; if (!a || !b) return 0; if (a->Type == GV_STRING && b->Type == GV_STRING) { const char *Empty = ""; const char *as = a->Str(); const char *bs = b->Str(); return _stricmp(as?as:Empty, bs?bs:Empty); } else if (a->Type == GV_DOM && b->Type == GV_DOM && Param) { const char *Fld = Param->Str(); int Dir = 1; if (Fld && *Fld == '-') { Fld++; Dir = -1; } LVariant av, bv; if (a->Value.Dom->GetValue(Fld, av) && b->Value.Dom->GetValue(Fld, bv)) { return LVariantCmp(&av, &bv, 0) * Dir; } } else if (a->Type == GV_INT32 && b->Type == GV_INT32) { return a->CastInt32() - b->CastInt32(); } else if (a->Type == GV_DATETIME && b->Type == GV_DATETIME) { return a->Value.Date->Compare(b->Value.Date); } else { LAssert(!"Impl a handler for this type."); } return 0; } inline LVariantType DecidePrecision(LVariantType a, LVariantType b) { if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline LVariantType ComparePrecision(LVariantType a, LVariantType b) { if (a == GV_NULL || b == GV_NULL) return GV_NULL; if (a == GV_DATETIME && b == GV_DATETIME) return GV_DATETIME; if (a == GV_DOUBLE || b == GV_DOUBLE) return GV_DOUBLE; if (a == GV_STRING || b == GV_STRING) return GV_STRING; if (a == GV_INT64 || b == GV_INT64) return GV_INT64; return GV_INT32; } inline char *CastString(LVariant *v, LVariant &cache) { if (v->Type == GV_STRING) return v->Str(); cache = *v; return cache.CastString(); } inline int CompareVariants(LVariant *a, LVariant *b) { // Calculates "a - b" switch (ComparePrecision(a->Type, b->Type)) { case GV_DATETIME: return a->Value.Date->Compare(b->Value.Date); break; case GV_DOUBLE: { double d = a->CastDouble() - b->CastDouble(); if (d < -MATRIX_DOUBLE_EPSILON) return -1; return d > MATRIX_DOUBLE_EPSILON; } case GV_STRING: { LVariant as, bs; char *A = CastString(a, as); char *B = CastString(b, bs); if (!A || !B) return -1; else return strcmp(A, B); break; } case GV_INT64: { int64 d = a->CastInt64() - b->CastInt64(); if (d < 0) return -1; return d > 0; } case GV_NULL: { // One or more values is NULL if (a->IsNull() && b->IsNull()) return 0; // The same.. LVariant *Val = a->IsNull() ? b : a; if (Val->IsNull()) { LAssert(0); return 0; } switch (Val->Type) { case GV_INT32: case GV_INT64: return Val->CastInt64() != 0; case GV_STRING: return Val->Str() != NULL; default: return Val->CastVoidPtr() != NULL; } break; } default: return a->CastInt32() - b->CastInt32(); break; } } LExecutionStatus LExternFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { if (!Lib || !Method) return ScriptError; LStream *Log = Ctx ? Ctx->GetLog() : NULL; if (Args.Length() != ArgType.Length()) { if (Log) Log->Print("Error: Extern '%s.%s' expecting %i arguments, not %i given.\n", Lib.Get(), Method.Get(), ArgType.Length(), Args.Length()); return ScriptError; } LArray Val; LArray Mem; bool UnsupportedArg = false; Val.Length(Args.Length() << 1); LPointer Ptr; Ptr.ni = &Val[0]; for (unsigned i=0; !UnsupportedArg && iCastVoidPtr(); *Ptr.vp++ = cp; } else { char *s = NewStr(v->CastString()); if (!s) { UnsupportedArg = true; break; } Mem.Add(s); *Ptr.vp++ = s; } break; } case GV_VOID_PTR: { *Ptr.vp++ = v->CastVoidPtr(); break; } default: { UnsupportedArg = true; break; } } } else { // Plain type switch (t.Base) { case GV_INT32: { #if defined(_WIN64) *Ptr.s64++ = v->CastInt32(); #else *Ptr.s32++ = v->CastInt32(); #endif break; } case GV_INT64: { *Ptr.s64++ = v->CastInt64(); break; } default: { UnsupportedArg = true; break; } } } } LLibrary Library(Lib); if (!Library.IsLoaded()) { if (Log) Log->Print("Error: Extern library '%s' missing.\n", Lib.Get()); return ScriptError; } void *c = Library.GetAddress(Method); if (!c) { if (Log) Log->Print("Error: Extern library '%s' has no method '%s'.\n", Lib.Get(), Method.Get()); return ScriptError; } #if defined(_MSC_VER) || (defined(MAC) && defined(LGI_32BIT) && !LGI_COCOA) ssize_t a = Ptr.ni - &Val[0]; #endif NativeInt r = 0; #if defined(_MSC_VER) #if defined(_WIN64) // 64bit... boooo no inline asm! void *b = &Val[0]; r = CallExtern64(c, &r, (uint32_t)a, b); #else // 32bit... yay inline asm! void *b = Ptr.ni - 1; _asm { mov ecx, a mov ebx, b } label1: _asm { push [ebx] sub ebx, 4 loop label1 mov ebx, c call ebx mov r, eax } #endif #elif defined(MAC) #if LGI_COCOA #warning FIXME #elif LGI_32BIT // 32bit only void *b = Ptr.ni - 1; asm ( "movl %2, %%ecx;" "movl %3, %%ebx;" "label1:" "pushl (%%ebx);" "subl %%ebx, 4;" "loop label1;" "call *%1;" :"=a"(r) /* output */ :"r"(c), "r"(a), "r"(b) /* input */ :/*"%eax",*/ "%ecx", "%ebx" /* clobbered register */ ); #endif #else // Not implemented, gcc??? LAssert(0); #endif *Args.GetReturn() = (int) r; for (unsigned i=0; iType == GV_BINARY && v->Value.Binary.Data != NULL && t.Base == GV_STRING) { // Cast the type back to t.Base char *p = (char*)v->Value.Binary.Data; v->Type = t.Base; v->Value.String = p; } } } Mem.DeleteArrays(); return ScriptSuccess; } struct CodeBlock { unsigned SrcLine; LArray AsmAddr; unsigned ViewLine; LAutoString Source; int SrcLines; LAutoString Asm; int AsmLines; }; class LVirtualMachinePriv : public LRefCount { LVariant ArrayTemp; char *CastArrayIndex(LVariant *Idx) { if (Idx == NULL || Idx->Type == GV_NULL) return NULL; if (Idx->Type == GV_STRING) return Idx->Str(); ArrayTemp = *Idx; return ArrayTemp.CastString(); } public: struct StackFrame { uint32_t CurrentFrameSize; ssize_t PrevFrameStart; size_t ReturnIp; LVarRef ReturnValue; }; enum RunType { RunContinue, RunStepInstruction, RunStepLine, RunStepOut }; LVirtualMachine *Vm; LStream *Log = NULL; LCompiledCode *Code = NULL; LExecutionStatus Status = ScriptNotStarted; LScriptPtr c; LVariant Reg[MAX_REGISTER]; LArray Locals; LVariant *Scope[SCOPE_MAX]; LArray Frames; RunType StepType; LVmCallback *Callback = NULL; LVmDebugger *Debugger = NULL; LScriptArguments *ArgsOutput = NULL; LArray BreakPts; LString TempPath; bool DebuggerEnabled = false; bool BreakCpp = false; LVirtualMachinePriv(LVirtualMachine *vm, LVmCallback *callback) { Vm = vm; Callback = callback; ZeroObj(Scope); } ~LVirtualMachinePriv() { } void DumpVariant(LStream *Log, LVariant &v) { if (!Log) return; switch (v.Type) { case GV_INT32: Log->Print("(int) %i", v.Value.Int); break; case GV_INT64: Log->Print("(int64) %I64i", v.Value.Int64); break; case GV_STRING: { char *nl = strchr(v.Value.String, '\n'); if (nl) Log->Print("(string) '%.*s...' (%i bytes)", nl - v.Value.String, v.Value.String, strlen(v.Value.String)); else Log->Print("(string) '%s'", v.Value.String); break; } case GV_DOUBLE: Log->Print("(double) %g", v.Value.Dbl); break; case GV_BOOL: Log->Print("(bool) %s", v.Value.Bool ? "true" : "false"); break; case GV_DOM: Log->Print("(LDom*) %p", v.Value.Dom); break; case GV_HASHTABLE: { Log->Print("(GHashTable*) %p {", v.Value.Hash); int n = 0; // const char *k; // for (LVariant *p = v.Value.Hash->First(&k); p; p = v.Value.Hash->Next(&k), n++) for (auto it : *v.Value.Hash) { Log->Print("%s\"%s\"=", n?",":"", it.key); DumpVariant(Log, *it.value); } Log->Print("}"); break; } case GV_LIST: { Log->Print("(LList*) %p {", v.Value.Lst); int n=0; for (auto i: *v.Value.Lst) { Log->Print("%s%i=", n?",":"", n); DumpVariant(Log, *i); n++; } Log->Print("}"); break; } case GV_NULL: { Log->Print("null"); break; } case GV_BINARY: { Log->Print("(Binary[%i])", v.Value.Binary.Length); if (v.Value.Binary.Data) { int i; for (i=0; i<16 && i < v.Value.Binary.Length; i++) Log->Print(" %.2x", ((uint8_t*)v.Value.Binary.Data)[i]); if (i < v.Value.Binary.Length) Log->Print("..."); } break; } default: Log->Print("(Type-%i) ????", v.Type); break; } } void DumpVariables(LVariant *v, int len) { if (!Log) return; for (int i=0; iPrint("[%i] = ", i); DumpVariant(Log, v[i]); Log->Print("\n"); } } } void OnException(const char *File, int Line, ssize_t Address, const char *Msg) { if (!Code) { LgiTrace("%s:%i - Exception without Code object: %s:%i, %s\n", _FL, File, Line, Msg); return; } if (Address < 0) { uint8_t *Base = &Code->ByteCode[0]; Address = c.u8 - Base; } if (!File || Line < 0) { // Extract the file / line from the current script location File = Code->GetFileName(); Line = Code->ObjectToSourceAddress(Address); } if (Log) { char *Last = strrchr((char*)File, DIR_CHAR); Log->Print("%s Exception: %s (%s:%i)\n", Code->AddrToSourceRef(Address), Msg, Last?Last+1:File, Line); } else { LgiTrace("%s:%i - Exception @ %i: %s\n", File, Line, Address, Msg); } LStringPipe AsmBuf; Decompile(Code->UserContext, Code, &AsmBuf); auto Asm = AsmBuf.NewLStr(); if (Vm && Vm->OpenDebugger(Code, Asm)) { LAssert(Debugger->GetCode()); // It should have taken a copy of the Code we passed in Debugger->OnAddress(Address); LString m; m.Printf("%s (%s:%i)", Msg, LGetLeaf(File), Line); Debugger->OnError(m); Debugger->Run(); } else { // Set the script return value to FALSE if (Frames.Length()) { StackFrame Sf = Frames[0]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; *RetVar = false; } // Exit the script c.u8 = Code->ByteCode.AddressOf() + Code->ByteCode.Length(); // Set the script status... Status = ScriptError; } } LExecutionStatus Decompile(LScriptContext *Context, LCompiledCode *Code, LStream *log) { LExecutionStatus Status = ScriptSuccess; LAssert(sizeof(LVarRef) == 4); LScriptPtr c; uint8_t *Base = &Code->ByteCode[0]; c.u8 = Base; uint8_t *e = c.u8 + Code->ByteCode.Length(); LStream *OldLog = Log; if (log) Log = log; for (unsigned k=0; kGlobals.Length(); k++) { Log->Print("G%i = ", k); DumpVariant(Log, Code->Globals[k]); Log->Print("\n"); } Log->Print("\n"); LHashTbl, char*> Fn; for (unsigned m=0; mMethods.Length(); m++) { LFunctionInfo *Info = Code->Methods[m]; if (Info->ValidStartAddr()) Fn.Add(Info->GetStartAddr(), Info->GetName()); else LAssert(!"Method not defined."); } int OldLineNum = 0; while (c.u8 < e) { char *Meth = Fn.Find(CurrentScriptAddress); if (Meth) { Log->Print("%s:\n", Meth); } int LineNum = Code->ObjectToSourceAddress(CurrentScriptAddress); if (LineNum >= 0 && LineNum != OldLineNum) { Log->Print(" %i:\n", OldLineNum = LineNum); } switch (*c.u8++) { #define VM_DECOMP 1 #include "Instructions.h" #undef VM_DECOMP } } if (log) Log = OldLog; return Status; } LExecutionStatus Setup(LCompiledCode *code, uint32_t StartOffset, LStream *log, LFunctionInfo *Func, LScriptArguments *Args) { Status = ScriptSuccess; Code = code; if (!Code) return ScriptError; if (log) Log = log; else if (Code->SysContext && Code->SysContext->GetLog()) Log = Code->SysContext->GetLog(); else if (Code->UserContext && Code->UserContext->GetLog()) Log = Code->UserContext->GetLog(); // else LgiTrace("%s:%i - Execution without a log?\n", _FL); LAssert(sizeof(LVarRef) == 4); uint8_t *Base = c.u8 = &Code->ByteCode[0]; uint8_t *e = c.u8 + Code->ByteCode.Length(); Scope[SCOPE_REGISTER] = Reg; Scope[SCOPE_LOCAL] = NULL; Scope[SCOPE_GLOBAL] = &Code->Globals[0]; Scope[SCOPE_OBJECT] = NULL; Scope[SCOPE_RETURN] = Args->GetReturn(); #ifdef _DEBUG const char *SourceFileName = Code->GetFileName(); char Obj[MAX_PATH_LEN]; if (SourceFileName) { if (strchr(SourceFileName, DIR_CHAR)) strcpy_s(Obj, sizeof(Obj), SourceFileName); else if (TempPath != NULL) LMakePath(Obj, sizeof(Obj), TempPath, SourceFileName); else { LGetSystemPath(LSP_TEMP, Obj, sizeof(Obj)); LMakePath(Obj, sizeof(Obj), Obj, SourceFileName); } char *Ext = LGetExtension(Obj); if (Ext) strcpy_s(Ext, sizeof(Obj)-(Ext-Obj), "asm"); else strcat_s(Obj, sizeof(Obj), ".asm"); } else { LAutoString DataPath; if (Code->UserContext) DataPath = Code->UserContext->GetDataFolder(); if (!DataPath) { char p[256]; if (LGetSystemPath(LSP_APP_INSTALL, p, sizeof(p))) DataPath.Reset(NewStr(p)); } LMakePath(Obj, sizeof(Obj), DataPath, "Script.asm"); } { LDirectory SrcD, ObjD; bool OutOfDate = true; if (LFileExists(SourceFileName) && SrcD.First(SourceFileName, NULL) != 0 && ObjD.First(Obj, NULL) != 0) { OutOfDate = ObjD.GetLastWriteTime() < SrcD.GetLastWriteTime(); } if (OutOfDate || Debugger) { LFile f; LStringPipe p; LStream *Out = NULL; if (Debugger) { Out = &p; } else if (f.Open(Obj, O_WRITE)) { f.SetSize(0); Out = &f; } if (Out) { LExecutionStatus Decomp = Decompile(Code->UserContext, Code, Out); f.Close(); if (Decomp != ScriptSuccess) { LAssert(!"Decompilation failed."); return ScriptError; } if (Debugger) { auto a = p.NewLStr(); Debugger->OnAddress(CurrentScriptAddress); Debugger->SetSource(a); } } } } #endif #if TIME_INSTRUCTIONS LARGE_INTEGER freq = {0}, start, end; QueryPerformanceFrequency(&freq); LHashTbl, int64> Timings; LHashTbl, int> TimingFreq; #endif // Calling a function only, not the whole script StackFrame &Sf = Frames.New(); Sf.ReturnIp = e - c.u8; Sf.PrevFrameStart = 0; Sf.ReturnValue.Scope = SCOPE_RETURN; Sf.ReturnValue.Index = 0; // array is only one item long anyway if (Func) { // Set up stack for function call if (!Func->ValidFrameSize()) { Log->Print("%s:%i - Function '%s' has an invalid frame size. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } Sf.CurrentFrameSize = Func->GetFrameSize(); AddLocalSize(Sf.CurrentFrameSize); if (Args) { // Check the local frame size is at least big enough for the args... if (Args->Length() > Sf.CurrentFrameSize) { Log->Print("%s:%i - Arg count mismatch, Supplied: %i, FrameSize: %i (Script: %s).\n", _FL, (int)Args->Length(), (int)Sf.CurrentFrameSize, Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Put the arguments of the function call into the local array for (unsigned i=0; iLength(); i++) { Locals[LocalsBase+i] = *(*Args)[i]; } } if (!Func->ValidStartAddr()) { Log->Print("%s:%i - Function '%s' is not defined. (Script: %s).\n", _FL, Func->GetName(), Code->AddrToSourceRef(Func->GetStartAddr())); return ScriptError; } // Set IP to start of function c.u8 = Base + Func->GetStartAddr(); } else { // Executing body of script Sf.CurrentFrameSize = 0; if (StartOffset > 0) c.u8 = Base + StartOffset; } return Status; } int NearestLine(size_t Addr) { int l = Code->Debug.Find(Addr); if (l >= 0) return l; for (int Off = 1; Off < 20; Off++) { int l = Code->Debug.Find(Addr + Off); if (l >= 0) { return l; } l = Code->Debug.Find(Addr - Off); if (l >= 0) { return l; } } return -1; } LExecutionStatus Run(RunType Type) { LAssert(Code != NULL); uint8_t *Base = &Code->ByteCode[0]; uint8_t *e = Base + Code->ByteCode.Length(); #define ON_EXCEPTION(args) \ args.Length(0); \ args.Address = -1; \ if (args.HasException()) \ { \ Status = ScriptError; \ goto ExitExecutionLoop; \ } if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #endif switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } #if TIME_INSTRUCTIONS QueryPerformanceCounter(&end); int Ticks = end.QuadPart - start.QuadPart; int64 i = Timings.Find(TimedOpCode); Timings.Add(TimedOpCode, i + Ticks); i = TimingFreq.Find(TimedOpCode); TimingFreq.Add(TimedOpCode, i + 1); #endif } if (Log) { #if TIME_INSTRUCTIONS Log->Print("\nTimings:\n"); Log->Print("%-20s%-10s%-10s%-10s\n", "Instr", "Total", "Freq", "Ave"); int Op; for (int64 t=Timings.First(&Op); t; t=Timings.Next(&Op)) { int Frq = TimingFreq.Find(Op); int MilliSec = t * 1000000 / freq.QuadPart; Log->Print("%-20s%-10i%-10i%-10i\n", InstToString((GInstruction)Op), MilliSec, Frq, MilliSec / Frq); } Log->Print("\n"); #endif #if POST_EXECUTE_STATE Log->Print("Stack:\n"); char *v; for (void *i=Code->Globals.Lut.First(&v); i; i=Code->Globals.Lut.Next(&v)) { int Idx = (int)i - 1; if (Idx >= 0 && Idx < Code->Globals.Length()) { Log->Print("%s = ", v); DumpVariant(Log, Code->Globals[Idx]); Log->Print("\n"); } } Log->Print("\nRegisters:\n"); DumpVariables(Reg, MAX_REGISTER); #endif } } else { // Stepping through code int Param = 0; switch (Type) { case RunStepLine: Param = NearestLine(CurrentScriptAddress); break; case RunStepOut: Param = (int)Frames.Length(); break; default: break; } if (BreakCpp) #if defined(WIN32) && !defined(_WIN64) _asm int 3 #else assert(!"BreakPoint"); #endif while (c.u8 < e) { if (Type == RunContinue && BreakPts.HasItem(c.u8 - Base)) break; switch (*c.u8++) { #define VM_EXECUTE 1 #include "Instructions.h" #undef VM_EXECUTE } if (Type == RunContinue) continue; if (Type == RunStepLine) { int CurLine = NearestLine(CurrentScriptAddress); if (CurLine && CurLine != Param) break; } else if (Type == RunStepOut) { if ((int)Frames.Length() < Param) break; } else if (Type == RunStepInstruction) { break; } else LAssert(!"Invalid Type."); } } ExitExecutionLoop: if (Debugger && Status != ScriptError) Debugger->OnAddress(CurrentScriptAddress); return Status; } }; bool LVirtualMachine::BreakOnWarning = false; LVirtualMachine::LVirtualMachine(LVmCallback *callback) { d = new LVirtualMachinePriv(this, callback); d->IncRef(); } LVirtualMachine::LVirtualMachine(Context ctx) { d = new LVirtualMachinePriv(this, ctx.Callback); d->IncRef(); if ((d->Code = ctx.Code)) { if (d->Code->ByteCode.IdxCheck(ctx.Addr)) d->c.u8 = d->Code->ByteCode.AddressOf() + ctx.Addr; } else d->c.u8 = NULL; } LVirtualMachine::LVirtualMachine(LVirtualMachine *vm) { d = vm->d; d->IncRef(); } LVirtualMachine::~LVirtualMachine() { if (d->Vm == this) d->Vm = NULL; d->DecRef(); } void LVirtualMachine::OnException(const char *File, int Line, ssize_t Address, const char *Msg) { d->OnException(File, Line, Address, Msg); } LExecutionStatus LVirtualMachine::Execute(LCompiledCode *Code, uint32_t StartOffset, LStream *Log, bool StartImmediately, LVariant *Return) { if (!Code) return ScriptError; LScriptArguments Args(this, Return); LExecutionStatus s = d->Setup(Code, StartOffset, Log, NULL, &Args); if (s != ScriptSuccess || !StartImmediately) return s; return d->Run(LVirtualMachinePriv::RunContinue); } LExecutionStatus LVirtualMachine::ExecuteFunction(LCompiledCode *Code, LFunctionInfo *Func, LScriptArguments &Args, LStream *Log, LScriptArguments *ArgsOut) { LCompiledCode *Cc = dynamic_cast(Code); if (!Cc || !Func) return ScriptError; LExecutionStatus s = d->Setup(Cc, 0, Log, Func, &Args); if (s != ScriptSuccess) return s; d->ArgsOutput = ArgsOut; auto Prev = Args.Vm; Args.Vm = this; LExecutionStatus r = d->Run(LVirtualMachinePriv::RunContinue); Args.Vm = Prev; return r; } void LVirtualMachine::SetDebuggerEnabled(bool b) { d->DebuggerEnabled = b; } LVmDebugger *LVirtualMachine::OpenDebugger(LCompiledCode *Code, const char *Assembly) { if (d->DebuggerEnabled) { if (!d->Callback) return NULL; d->Debugger = d->Callback->AttachVm(this, Code, Assembly); } return d->Debugger; } bool LVirtualMachine::StepInstruction() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepInstruction); return s != ScriptError; } bool LVirtualMachine::StepLine() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepLine); return s != ScriptError; } bool LVirtualMachine::StepOut() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunStepOut); return s != ScriptError; } bool LVirtualMachine::BreakExecution() { return false; } bool LVirtualMachine::Continue() { LExecutionStatus s = d->Run(LVirtualMachinePriv::RunContinue); return s != ScriptError; } bool LVirtualMachine::BreakPoint(const char *File, int Line, bool Add) { return false; } bool LVirtualMachine::BreakPoint(int Addr, bool Add) { if (Add) d->BreakPts.Add(Addr); else d->BreakPts.Delete(Addr); return true; } void LVirtualMachine::SetBreakCpp(bool Brk) { d->BreakCpp = Brk; } LVirtualMachine::Context LVirtualMachine::SaveContext() { LVirtualMachine::Context ctx; ctx.Callback = d->Callback; if ((ctx.Code = d->Code) && ctx.Code->ByteCode.PtrCheck(d->c.u8)) { ctx.Addr = d->c.u8 - ctx.Code->ByteCode.AddressOf(); } return ctx; } LVmCallback *LVirtualMachine::GetCallback() { return d->Callback; } void LVirtualMachine::SetTempPath(const char *Path) { d->TempPath = Path; } //////////////////////////////////////////////////////////////////// /* bool GTypeDef::GetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { Value = *p.i32; break; } case GV_DOUBLE: { Value = *p.dbl; break; } case GV_STRING: { Value = p.i8; break; } case GV_CUSTOM: { Value.Empty(); Value.Type = GV_CUSTOM; Value.Value.Custom.Dom = m->Nest; Value.Value.Custom.Data = p.i8; break; } default: { return false; } } return true; } bool GTypeDef::SetVariant(const char *Name, LVariant &Value, char *Arr) { GMember *m = Members.Find(Name); if (!m || !Object) { LAssert(!"No member?"); return false; } GPtr p; p.i8 = Object; p.i8 += m->Offset; switch (m->Type) { case GV_INT32: { *p.i32 = Value.CastInt32(); break; } case GV_DOUBLE: { *p.dbl = Value.CastDouble(); break; } case GV_STRING: { char *s = Value.CastString(); if (!s) return false; int i; for (i = 0; *s && i < m->Size - 1; i++) { *p.i8++ = *s++; } if (i < m->Size - 1) *p.i8 = 0; break; } case GV_CUSTOM: { GTypeDef *t = dynamic_cast(Value.Value.Custom.Dom); if (m->Nest == t) { memcpy(p.i8, Value.Value.Custom.Data, t->Sizeof()); } break; } default: { return false; } } return true; } */ /////////////////////////////////////////////////////////////////////////////////////////////////////////////// uint32_t IconsData[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D9CCEBE, 0x3B166419, 0x74594357, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x543CF81F, 0xCEDE647C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7C998D1B, 0xF81FB61C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBFF81F, 0x43DB4C1C, 0xDF1E955B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8C0CF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D5CF81F, 0x43595C1A, 0x3AF74338, 0x8CFA4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69D6C39, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x647BADFD, 0x543C53FB, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F64CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0xF81F83AB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CBB8D5C, 0xF81FB63D, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5BD8, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x959D647C, 0xCEBDF81F, 0x32913AD3, 0xB61B5353, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3BA564CB, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x74DC8D9F, 0x8D9FF81F, 0x74DC8D9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9F8D9F, 0x855E857E, 0x7CFD7D1D, 0x74BC74DC, 0xF81F74DC, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0xAE1EB65E, 0xD71FA5FE, 0x853D8D7D, 0x6CBD7CFD, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8BEBF81F, 0x7329FF98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE9E5C1A, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F6C7C, 0x5BD6F81F, 0xD6DD5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB6D45CAA, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8D9FF81F, 0x2AB5D71F, 0x8D9FF81F, 0x2AB5D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F8D9F, 0xC6DFD71F, 0xB65FBE7F, 0x9DDEAE3F, 0xF81F2AB5, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x9DDEAE1E, 0xFFFFDF3F, 0x74FD853D, 0x647C6CBC, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x9C6CF81F, 0x944D944C, 0x944D8C0C, 0xFF54FF76, 0xF81F62A8, 0xF81FF81F, 0xF81FF81F, 0xBE5D4B99, 0x543CF81F, 0xC6BE5C7C, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F5398, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F53DB, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED5489, 0x3BA5B6D4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x857EF81F, 0x2274DF3F, 0x857EF81F, 0x2274DF3F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xDF3F857E, 0xB65FCEDF, 0x9DBEAE1F, 0x851B959E, 0xF81F2274, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0xE75F9DDE, 0xFFFFFFFF, 0x6CBC74DD, 0x543C647C, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x944BF81F, 0xFFD9F756, 0xFF53FF97, 0xFEEEFF31, 0x5226F66A, 0xF81FF81F, 0xF81FF81F, 0x84DA6C3A, 0xBE7EADDC, 0x43DB4C1C, 0xF81F953B, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4B77, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0x9D7C5C3B, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x75ED4C48, 0x5D0A75ED, 0xF81F3BA5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x855EF81F, 0x1A33D71F, 0x855EF81F, 0x1A33D71F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xD71F855E, 0xA5FFBE7F, 0x855E959E, 0x6C7A7CFD, 0xF81F1A33, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0xFFFFDF1E, 0xFFFFFFFF, 0xFFFFFFFF, 0x4BFCC69D, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x940AF81F, 0xFF75FF97, 0xFEEEFF31, 0xF6ABFECD, 0xBCA7E5E8, 0xF81F49C5, 0xF81FF81F, 0x7CBAB61C, 0x541C53FA, 0x3B1553FB, 0x329132B2, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4B57, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C9CB63D, 0x43584BBA, 0x3AD53B16, 0x743742F4, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6D8B4407, 0x3C055D0A, 0x1B212B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7D1DF81F, 0x11F2CEBF, 0x7D1DF81F, 0x11F2CEBF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCEBF7D1D, 0x9DBEAE3F, 0x7CFD8D5E, 0x53B76CBC, 0xF81F11F2, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0xCEBD853D, 0xFFFFFFFF, 0x541C5C5C, 0x43BBFFFF, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x83A9F81F, 0xFF31FF74, 0xF6ACF6CF, 0xDDEAF68B, 0x41A4B467, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC69DF81F, 0x32913AD3, 0xB5FB4B53, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5D0A33E5, 0x2B843C05, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x7CFDF81F, 0x09B1BE9F, 0x7CFDF81F, 0x09B1BE9F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xBE9F7CFD, 0x959EA5FF, 0x6C9C7CFD, 0x4B565C3B, 0xF81F09B1, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x6CBC74FD, 0xFFFFBE3B, 0x43DC541C, 0x337BFFFF, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x8389F81F, 0x6AA472E7, 0x72C56264, 0xB487DDA9, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5BD6F81F, 0xF81F5BB5, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F4336, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3C052BA4, 0x0B002B84, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x0990AE5F, 0x74DCF81F, 0x0990AE5F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xAE5F74DC, 0x7D1D95BE, 0x5C3B6CBC, 0x43354BD9, 0xF81F0990, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x647C6CBC, 0x9D7A543C, 0x3B9B43DB, 0x2B5BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5A45F81F, 0x41A4AC26, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81F5377, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x2B842363, 0xF81F0B00, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74BCF81F, 0x0170A5FE, 0x74BCF81F, 0x0170A5FE, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xA5FE74BC, 0x6C79851A, 0x4B555BF8, 0x32D34314, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x543C5C7C, 0x43BB4BFC, 0x335B3B9B, 0x231BFFFF, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x49E4F81F, 0xF81F41A4, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9D7A53B7, 0x4BBAF81F, 0xB61C6459, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0B001B42, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x01700170, 0x74DCF81F, 0x01700170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x3B1674DC, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0x74DCF81F, 0x3B163B16, 0x2A9432F6, 0x11F22253, 0x017009B1, 0xF81F0170, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x41A4F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x747863F7, 0x953BB61C, 0x4BB843B9, 0xB61B7C98, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F1301, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x6C18ADBA, 0x53D953B7, 0x3B144B98, 0x32503291, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB61BF81F, 0x32503291, 0xA5794B12, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x5B95F81F, 0xB61A5373, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, }; LInlineBmp DbgIcons = {128, 16, 16, IconsData}; enum DbgCtrls { IDC_STATIC = -1, IDC_TABS = 300, IDC_BOX, IDC_BOX2, IDC_TEXT, IDC_LOCALS, IDC_GLOBALS, IDC_REGISTERS, IDC_STACK, IDC_LOG, IDC_RUN, IDC_PAUSE, IDC_STOP, IDC_RESTART, IDC_GOTO, IDC_STEP_INSTR, IDC_STEP_LINE, IDC_STEP_OUT, IDC_SOURCE_LST, IDC_BREAK_POINT, IDC_BREAK_CPP, IDC_VARS_TBL }; struct LScriptVmDebuggerPriv; class LDebugView : public LTextView3 { LScriptVmDebuggerPriv *d; int CurLine; int ErrorLine; LString Error; LArray BreakPts; public: LDebugView(LScriptVmDebuggerPriv *priv); ~LDebugView(); void SetError(const char *Err); int GetCurLine() { return CurLine; } int GetAddr(); void ScrollToCurLine(); void PourText(size_t Start, ssize_t Length) override; void OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) override; void OnPaint(LSurface *pDC) override; bool Breakpoint(int Addr); }; struct LScriptVmDebuggerPriv { // Current script LAutoPtr Vm; LAutoPtr Obj; LVmCallback *Callback = NULL; LString Script, Assembly; LArray Blocks; ssize_t CurrentAddr = -1; LArray LineIsAsm; LVariant Return; bool AcceptNotify = false; // Ui LView *Parent = NULL; LBox *Main = NULL; LBox *Sub = NULL; LList *SourceLst = NULL; LTabView *Tabs = NULL; LDebugView *Text = NULL; LList *Locals = NULL, *Globals = NULL, *Registers = NULL, *Stack = NULL; LTextLog *Log = NULL; LToolBar *Tools = NULL; LTableLayout *VarsTbl = NULL; }; LDebugView::LDebugView(LScriptVmDebuggerPriv *priv) : LTextView3(IDC_TEXT, 0, 0, 100, 100) { d = priv; ErrorLine = -1; - SetWrapType(TEXTED_WRAP_NONE); + SetWrapType(L_WRAP_NONE); GetCss(true)->PaddingLeft(LCss::Len(LCss::LenPx, 18)); } LDebugView::~LDebugView() { } void LDebugView::SetError(const char *Err) { ErrorLine = CurLine; Error = Err; } #define IsHexChar(c) \ ( \ IsDigit(c) \ || \ ((c) >= 'a' && (c) <= 'f') \ || \ ((c) >= 'A' && (c) <= 'F') \ ) int IsAddr(char16 *Ln) { int Addr = 0; for (char16 *s = Ln; *s && *s != '\n' && s < Ln + 8; s++) { Addr += IsHexChar(*s); } if (Addr != 8) return -1; return HtoiW(Ln); } int LDebugView::GetAddr() { ssize_t Index; LTextLine *t = GetTextLine(Cursor, &Index); if (!t) return -1; int Addr = IsAddr(Text + t->Start); return Addr; } void LDebugView::ScrollToCurLine() { SetLine(CurLine); } bool LDebugView::Breakpoint(int Addr) { if (BreakPts.HasItem(Addr)) { BreakPts.Delete(Addr); Invalidate(); return false; } else { BreakPts.Add(Addr); Invalidate(); return true; } } void LDebugView::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { LTextView3::OnPaintLeftMargin(pDC, r, colour); pDC->Colour(LColour(192, 0, 0)); LFont *f = GetFont(); f->Colour(L_LOW, L_WORKSPACE); f->Transparent(true); int Fy = f->GetHeight(); int Start = VScroll ? (int)VScroll->Value() : 0; int Page = (r.Y() + Fy - 1) / Fy; int Ln = Start; int Rad = (Fy >> 1) - 1; int PadY = GetCss(true)->PaddingTop().ToPx(Y(), f) + ((Fy - Rad) >> 1); auto It = Line.begin(Start); for (auto i = *It; i && Ln <= Start + Page; i = *(++It), Ln++) { int OffY = (Ln - Start) * f->GetHeight(); /* LString Num; Num.Printf("%i", Ln); LDisplayString Ds(f, Num); Ds.Draw(pDC, 0, r.y1+OffY); */ char16 *s = Text + i->Start; int Addr = IsAddr(s); if (BreakPts.HasItem(Addr)) { pDC->FilledCircle(r.x1 + Rad + 2, OffY + PadY + Rad, Rad); } } f->Transparent(false); f->Colour(L_TEXT, L_WORKSPACE); } void LDebugView::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); if (Error) { LTextLine *Ln = Line[ErrorLine]; LFont *f = GetFont(); LRect c = GetClient(); int Pad = 3; LDisplayString Ds(f, Error); LRect r(0, 0, Ds.X()-1, Ds.Y()-1); r.Inset(-Pad, -Pad); r.Offset(c.X()-r.X(), Ln ? Ln->r.y1 - ScrollYPixel(): 0); f->Transparent(false); f->Colour(LColour::White, LColour::Red); Ds.Draw(pDC, r.x1 + Pad, r.y1 + Pad, &r); } } void LDebugView::PourText(size_t Start, ssize_t Len) { LTextView3::PourText(Start, Len); CurLine = -1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; for (unsigned n=0; nCurrentAddr=%i b.AsmAddr[%i,%i]=%u\n", (int)d->CurrentAddr, i, n, b.AsmAddr[n]); if (d->CurrentAddr >= b.AsmAddr[n]) { LgiTrace("b.ViewLine=%i b.SrcLines=%i n=%i\n", b.ViewLine, b.SrcLines, n); CurLine = b.ViewLine + b.SrcLines + n - 1; } } } unsigned Idx = 0; for (auto l: Line) { // char16 *t = Text + l->Start; // char16 *e = t + l->Len; if (CurLine == Idx) { l->c.Rgb(0, 0, 0); l->Back = LColour(L_DEBUG_CURRENT_LINE); } else { bool IsAsm = Idx < d->LineIsAsm.Length() ? d->LineIsAsm[Idx] : false; if (IsAsm) { l->c.Rgb(0, 0, 255); l->Back.Rgb(0xf0, 0xf0, 0xf0); } } Idx++; } } LVmDebuggerWnd::LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LAutoPtr Vm, LAutoPtr Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; d->Vm = Vm; d->Callback = Callback; d->Obj = Code; if (d->Obj) d->Script = d->Obj->GetSource(); d->Assembly = Assembly; LRect r(0, 0, 1000, 900); SetPos(r); if (Parent) MoveSameScreen(Parent); else MoveToCenter(); Name("Script Debugger"); if (Attach(NULL)) { if ((Menu = new LMenu)) { Menu->Attach(this); LSubMenu *s = Menu->AppendSub("Debug"); s->AppendItem("Run", IDC_RUN, true, -1, "F5"); s->AppendItem("Pause", IDC_PAUSE, true, -1, NULL); s->AppendItem("Stop", IDC_STOP, true, -1, "Ctrl+Break"); s->AppendItem("Restart", IDC_RESTART, true, -1, NULL); s->AppendItem("Goto", IDC_GOTO, true, -1, NULL); s->AppendSeparator(); s->AppendItem("Step Instruction", IDC_STEP_INSTR, true, -1, "F11"); s->AppendItem("Step Line", IDC_STEP_LINE, true, -1, "F10"); s->AppendItem("Step Out", IDC_STEP_OUT, true, -1, "Shift+F11"); s->AppendSeparator(); s->AppendItem("Breakpoint", IDC_BREAK_POINT, true, -1, "F9"); s->AppendItem("Break Into C++", IDC_BREAK_CPP, true, -1, "Ctrl+F9"); } AddView(d->Tools = new LToolBar); uint16 *Px = (uint16*) DbgIcons.Data; LImageList *il = new LImageList(16, 16, DbgIcons.Create(*Px)); if (il) d->Tools->SetImageList(il, 16, 16, true); d->Tools->AppendButton("Run", IDC_RUN); d->Tools->AppendButton("Pause", IDC_PAUSE); d->Tools->AppendButton("Stop", IDC_STOP); d->Tools->AppendButton("Restart", IDC_RESTART); d->Tools->AppendButton("Goto", IDC_GOTO); d->Tools->AppendSeparator(); d->Tools->AppendButton("Step Instruction", IDC_STEP_INSTR); d->Tools->AppendButton("Step Line", IDC_STEP_LINE); d->Tools->AppendButton("Step Out", IDC_STEP_OUT); AddView(d->Main = new LBox(IDC_BOX)); d->Main->SetVertical(true); d->Main->AddView(d->Sub = new LBox(IDC_BOX2)); d->Sub->SetVertical(false); d->Sub->AddView(d->SourceLst = new LList(IDC_SOURCE_LST, 0, 0, 100, 100)); d->SourceLst->GetCss(true)->Width(LCss::Len("200px")); d->SourceLst->AddColumn("Source", 200); d->Sub->AddView(d->Text = new LDebugView(d)); d->Main->AddView(d->Tabs = new LTabView(IDC_TABS)); d->Tabs->GetCss(true)->Height(LCss::Len("250px")); LTabPage *p = d->Tabs->Append("Variables"); p->Append(d->VarsTbl = new LTableLayout(IDC_VARS_TBL)); int x = 0, y = 0; auto *c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Globals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Locals:")); c = d->VarsTbl->GetCell(x++, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Registers:")); x = 0; y++; c = d->VarsTbl->GetCell(x++, y); c->Add(d->Globals = new LList(IDC_GLOBALS, 0, 0, 100, 100)); d->Globals->AddColumn("Name",100); d->Globals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Locals = new LList(IDC_LOCALS, 0, 0, 100, 100)); d->Locals->AddColumn("Name",100); d->Locals->AddColumn("Value",400); c = d->VarsTbl->GetCell(x++, y); c->Add(d->Registers = new LList(IDC_REGISTERS, 0, 0, 100, 100)); d->Registers->AddColumn("Name",100); d->Registers->AddColumn("Value",400); p = d->Tabs->Append("Stack"); p->Append(d->Stack = new LList(IDC_STACK, 0, 0, 100, 100)); d->Stack->SetPourLargest(true); d->Stack->AddColumn("Address", 100); d->Stack->AddColumn("Function", 300); p = d->Tabs->Append("Log"); p->Append(d->Log = new LTextLog(IDC_LOG)); AttachChildren(); Visible(true); { char p[MAX_PATH_LEN]; LMakePath(p, sizeof(p), LGetExePath(), "../Scripts"); LDirectory dir; LListItem *Match = NULL; d->SourceLst->MultiSelect(false); for (int b = dir.First(p); b; b = dir.Next()) { if (!dir.IsDir()) { char *n = dir.GetName(); if (stristr(n, ".script") && dir.Path(p, sizeof(p))) { LListItem *it = new LListItem; it->SetText(dir.GetName(), 0); it->SetText(p, 1); if (d->Obj && d->Obj->GetFileName()) { if (_stricmp(p, d->Obj->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } if (!Match && d->Obj) { LListItem *it = new LListItem; if (it) { it->SetText(LGetLeaf(d->Obj->GetFileName()), 0); it->SetText(d->Obj->GetFileName(), 1); d->SourceLst->Insert(it); it->Select(true); } } } } d->AcceptNotify = true; if (d->Assembly) SetSource(d->Assembly); } LVmDebuggerWnd::~LVmDebuggerWnd() { } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { return LWindow::OnRequestClose(OsShuttingDown); } void LVmDebuggerWnd::Run() { Visible(true); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } void LVmDebuggerWnd::SetCode(LAutoPtr Cc) { d->Obj = Cc; } LCompiledCode *LVmDebuggerWnd::GetCode() { return d->Obj; } void LVmDebuggerWnd::SetSource(const char *Mixed) { #if 1 LStringPipe Glob(256); LStringPipe Tmp(256); d->Blocks.Length(0); CodeBlock *Cur = &d->Blocks.New(); // Parse the mixed source auto t = LString(Mixed).SplitDelimit("\n", -1, false); bool InGlobals = true; int InAsm = -1; for (unsigned i=0; i Code Cur->Asm.Reset(Tmp.NewStr()); Cur = &d->Blocks.New(); } else { // Code -> Asm Tmp.Empty(); } InAsm = IsAsm; } Tmp.Print("%s\n", l); if (InAsm) { Cur->AsmLines++; Cur->AsmAddr.Add(htoi(l)); } else if (!Cur->SrcLine) { while (*l == ' ') l++; if (IsDigit(*l)) Cur->SrcLine = atoi(l); } } if (InAsm) Cur->Asm.Reset(Tmp.NewStr()); Tmp.Empty(); LStringPipe Txt; auto Src = d->Script.SplitDelimit("\n", -1, false); unsigned SrcLine = 1; unsigned ViewLine = 1; for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; if (b.SrcLine > 0) { while (SrcLine <= b.SrcLine) { char *s = Src[SrcLine-1]; Tmp.Print("%i: %s\n", SrcLine, s ? s : ""); b.SrcLines++; SrcLine++; } b.Source.Reset(Tmp.NewStr()); } if (b.Source && b.Asm) { b.ViewLine = ViewLine; ViewLine += b.SrcLines + b.AsmLines; Txt.Print("%s%s", b.Source.Get(), b.Asm.Get()); } else if (b.Source) { b.ViewLine = ViewLine; ViewLine += b.SrcLines; Txt.Print("%s", b.Source.Get()); } else if (b.Asm) { b.ViewLine = ViewLine; ViewLine += b.AsmLines; Txt.Print("%s", b.Asm.Get()); } } while (SrcLine <= Src.Length()) { Txt.Print("%i: %s\n", SrcLine, Src[SrcLine-1].Get()); SrcLine++; } for (unsigned i=0; iBlocks.Length(); i++) { CodeBlock &b = d->Blocks[i]; int Base = b.ViewLine + b.SrcLines; for (int n = Base; nLineIsAsm[n-1] = true; } LAutoString a(Txt.NewStr()); d->Text->Name(a); #else d->Text->Name(Mixed); #endif } void LVmDebuggerWnd::UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix) { if (!d->Vm || !Lst || !Arr) return; List all; Lst->GetAll(all); LListItem *it; for (ssize_t i=0; iVm->d->DumpVariant(&p, *v); LAutoString a(p.NewStr()); char nm[32]; sprintf_s(nm, sizeof(nm), "%c" LPrintfSSizeT, Prefix, i); if (i >= (ssize_t)all.Length()) { it = new LListItem; all.Insert(it); Lst->Insert(it); } it = i < (ssize_t)all.Length() ? all[i] : NULL; if (it) { it->SetText(nm, 0); it->SetText(a, 1); } } Lst->ResizeColumnsToContent(); } void LVmDebuggerWnd::OnAddress(size_t Addr) { d->CurrentAddr = Addr; if (d->Text) { ssize_t Sz = d->Text->Length(); d->Text->PourText(0, Sz); d->Text->ScrollToCurLine(); d->Text->Invalidate(); } OnNotify(d->Tabs, LNotifyValueChanged); } void LVmDebuggerWnd::OnError(const char *Msg) { if (Msg) d->Text->SetError(Msg); } void LVmDebuggerWnd::OnRun(bool Running) { } void LVmDebuggerWnd::LoadFile(const char *File) { if (!d->Vm || !d->Callback) { LAssert(0); return; } LFile f; if (f.Open(File, O_READ)) d->Script = f.Read(); else d->Script.Empty(); d->Obj.Reset(); if (d->Callback->CompileScript(d->Obj, File, d->Script)) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) { d->Return.Empty(); d->Vm->d->Frames.Length(0); LScriptArguments Args(d->Vm, &d->Return); d->Vm->d->Setup(Code, 0, d->Log, NULL, &Args); } } } int LVmDebuggerWnd::OnCommand(int Cmd, int Event, OsView Wnd) { if (d->Vm && d->Vm->d->Vm == NULL) { // This happens when the original VM decides to go away and leave // our copy of the VM as the only one left. This means we have to // update the pointer in the VM's private data to point to us. d->Vm->d->Vm = d->Vm; } switch (Cmd) { case IDC_RUN: { if (d->Vm) d->Vm->Continue(); break; } case IDC_PAUSE: { if (d->Vm) d->Vm->BreakExecution(); break; } case IDC_STOP: { Quit(); break; } case IDC_RESTART: { if (d->Vm && d->Obj) { LCompiledCode *Code = dynamic_cast(d->Obj.Get()); if (Code) d->Vm->Execute(Code, 0, d->Log, false); } break; } case IDC_GOTO: { break; } case IDC_STEP_INSTR: { if (d->Vm) d->Vm->StepInstruction(); break; } case IDC_STEP_LINE: { if (d->Vm) d->Vm->StepLine(); break; } case IDC_STEP_OUT: { if (d->Vm) d->Vm->StepOut(); break; } case IDC_BREAK_POINT: { int Addr = d->Text->GetAddr(); if (Addr >= 0) d->Vm->BreakPoint(Addr, d->Text->Breakpoint(Addr)); break; } case IDC_BREAK_CPP: { if (!d->Vm) { LAssert(0); break; } LMenuItem *i = Menu->FindItem(IDC_BREAK_CPP); if (!i) { LAssert(0); break; } bool b = !i->Checked(); i->Checked(b); d->Vm->SetBreakCpp(b); break; } } return LWindow::OnCommand(Cmd, Event, Wnd); } int LVmDebuggerWnd::OnNotify(LViewI *Ctrl, LNotification n) { if (!d->AcceptNotify) return 0; switch (Ctrl->GetId()) { case IDC_TABS: { switch (Ctrl->Value()) { case 0: // Variables { if (d->Obj) { UpdateVariables(d->Globals, d->Vm->d->Scope[SCOPE_GLOBAL], d->Obj->Globals.Length(), 'G'); } if (d->Vm->d->Frames.Length()) { LVirtualMachinePriv::StackFrame &frm = d->Vm->d->Frames.Last(); UpdateVariables(d->Locals, d->Vm->d->Scope[SCOPE_LOCAL], frm.CurrentFrameSize, 'L'); } else d->Locals->Empty(); UpdateVariables(d->Registers, d->Vm->d->Scope[SCOPE_REGISTER], MAX_REGISTER, 'R'); break; } case 1: // Call stack { d->Stack->Empty(); LArray &Frames = d->Vm->d->Frames; for (int i=(int)Frames.Length()-1; i>=0; i--) { LVirtualMachinePriv::StackFrame &Sf = Frames[i]; LListItem *li = new LListItem; LString s; s.Printf("%p/%i", Sf.ReturnIp, Sf.ReturnIp); li->SetText(s, 0); const char *Src = d->Vm->d->Code->AddrToSourceRef(Sf.ReturnIp); li->SetText(Src, 1); d->Stack->Insert(li); } break; } case 2: // Log { break; } } break; } case IDC_SOURCE_LST: { if (n.Type == LNotifyItemSelect) { LListItem *it = d->SourceLst->GetSelected(); if (!it) break; const char *full = it->GetText(1); if (!LFileExists(full)) break; LoadFile(full); } break; } } return LWindow::OnNotify(Ctrl, n); } LMessage::Param LVmDebuggerWnd::OnEvent(LMessage *Msg) { return LWindow::OnEvent(Msg); } diff --git a/src/common/Gdc2/GdcCommon.cpp b/src/common/Gdc2/GdcCommon.cpp --- a/src/common/Gdc2/GdcCommon.cpp +++ b/src/common/Gdc2/GdcCommon.cpp @@ -1,1217 +1,1220 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Palette.h" #ifdef HAIKU #include "ControlLook.h" #endif ///////////////////////////////////////////////////////////////////////////// // Mem ops void Add64(ulong *DestH, ulong *DestL, ulong SrcH, ulong SrcL) { *DestH += SrcH + (*DestL & SrcL & 0x80000000) ? 1 : 0; *DestL += SrcL; } void MemSet(void *d, int c, uint s) { if (d && s > 0) memset(d, c, s); } void MemCpy(void *d, void *s, uint l) { if (d && s && l > 0) memcpy(d, s, l); } void MemAnd(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ &= *S++; } } } void MemXor(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ ^= *S++; } } } void MemOr(void *d, void *s, uint l) { uchar *D = (uchar*) d; uchar *S = (uchar*) s; if (D && S) { for (; l > 0; l--) { *D++ |= *S++; } } } ////////////////////////////////////////////////////////////////////// bool LFindBounds(LSurface *pDC, LRect *rc) { if (!pDC || ! rc) return false; LAssert(pDC->GetColourSpace() == System32BitColourSpace); // Move top border down to image while (rc->y1 < rc->y2) { System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y1]; if (!p) return false; p += rc->x1; System32BitPixel *e = p + rc->X(); bool IsTrans = true; while (p < e) { if (p->a != 0) { IsTrans = false; break; } p++; } if (IsTrans) rc->y1++; else break; } // Move bottom border up to image while (rc->y2 >= rc->y1) { System32BitPixel *p = (System32BitPixel*)(*pDC)[rc->y2]; if (!p) return false; p += rc->x1; System32BitPixel *e = p + rc->X(); bool IsTrans = true; while (p < e) { if (p->a != 0) { IsTrans = false; break; } p++; } if (IsTrans) rc->y2--; else break; } // Do the left and right edges too int x1 = rc->x2; int x2 = rc->x1; for (int y=rc->y1; y<=rc->y2; y++) { System32BitPixel *p = (System32BitPixel*)(*pDC)[y]; if (!p) return false; System32BitPixel *px1 = p + rc->x1; System32BitPixel *px2 = p + rc->x2; while (px1 < px2 && px1->a == 0) px1++; x1 = MIN(x1, (int) (px1 - p)); while (px2 >= px1 && px2->a == 0) px2--; x2 = MAX(x2, (int) (px2 - p)); } rc->x1 = x1; rc->x2 = x2; if (rc->Valid()) return true; // No data? rc->ZOff(-1, -1); return false; } ////////////////////////////////////////////////////////////////////// // Drawing functions void LDrawBox(LSurface *pDC, LRect &r, bool Sunken, bool Fill) { if (Fill) { pDC->Colour(LColour(L_MED)); pDC->Rectangle(r.x1+1, r.y1+1, r.x2-1, r.y2-1); } pDC->Colour((Sunken) ? LColour(L_LIGHT) : LColour(L_LOW)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour((Sunken) ? LColour(L_LOW) : LColour(L_LIGHT)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); } void LWideBorder(LSurface *pDC, LRect &r, LEdge Type) { if (!pDC) return; COLOUR Old = pDC->Colour(); LColour VLow = LColour(L_SHADOW); LColour Low = LColour(L_LOW); LColour High = LColour(L_HIGH); LColour VHigh = LColour(L_LIGHT); switch (Type) { case EdgeXpSunken: { // XP theme pDC->Colour(Low); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(VLow); pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2); pDC->Colour(High); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeXpRaised: { pDC->Colour(VHigh); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(High); pDC->Line(r.x1+1, r.y1+1, r.x2-1, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-1); pDC->Colour(Low); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VLow); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeXpChisel: { pDC->Colour(Low); pDC->Line(r.x1, r.y1, r.x2-1, r.y1); pDC->Line(r.x1, r.y1, r.x1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x1+1, r.y1+1, r.x2-2, r.y1+1); pDC->Line(r.x1+1, r.y1+1, r.x1+1, r.y2-2); pDC->Colour(Low); pDC->Line(r.x2-1, r.y2-1, r.x2-1, r.y1+1); pDC->Line(r.x2-1, r.y2-1, r.x1+1, r.y2-1); pDC->Colour(VHigh); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); break; } case EdgeWin7Sunken: case EdgeWin7FocusSunken: { bool Focus = Type == EdgeWin7FocusSunken; // Win7 theme LColour Med(L_MED), Ws(L_WORKSPACE); LColour Mixer = Med.GetGray() >= 128 ? LColour::Black : LColour::White; LColour TopLeft, RightBottom; if (Focus) { LColour Focus(L_FOCUS_SEL_BACK); TopLeft = Med.Mix(Focus, 0.4f); RightBottom = Ws.Mix(Focus, 0.2f); } else { TopLeft = Med.Mix(Mixer, 0.4f); RightBottom = Ws.Mix(Mixer, 0.2f); } // Top corners pDC->Colour(Med); pDC->Set(r.x1, r.y1); pDC->Set(r.x2, r.y1); pDC->Colour(Ws.Mix(TopLeft)); pDC->Set(r.x1+1, r.y1+1); pDC->Set(r.x2-1, r.y1+1); pDC->Set(r.x1+1, r.y2-1); // Edges pDC->Colour(TopLeft); pDC->Line(r.x1, r.y1+1, r.x1, r.y2-1); // left pDC->Line(r.x1+1, r.y1, r.x2-1, r.y1); // top // Bottom edge pDC->Colour(RightBottom); pDC->Line(r.x1+1, r.y2, r.x2-1, r.y2); // bottom pDC->Line(r.x2, r.y1+1, r.x2, r.y2-1); // right // Inner workspace rect pDC->Colour(Ws); pDC->Line(r.x1+2, r.y1+1, r.x2-2, r.y1+1); // top pDC->Line(r.x1+1, r.y1+2, r.x1+1, r.y2-2); // left pDC->Line(r.x2-1, r.y1+2, r.x2-1, r.y2-2); // right pDC->Line(r.x1+2, r.y2-1, r.x2-1, r.y2-1); // bottom break; } default: { return; } } r.Inset(2, 2); pDC->Colour(Old); } void LThinBorder(LSurface *pDC, LRect &r, LEdge Type) { if (!pDC) return; COLOUR Old = pDC->Colour(); switch (Type) { case EdgeXpSunken: case EdgeWin7FocusSunken: case EdgeWin7Sunken: { pDC->Colour(LColour(L_LIGHT)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour(LColour(L_LOW)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); r.Inset(1, 1); break; } case EdgeXpRaised: { pDC->Colour(LColour(L_LOW)); pDC->Line(r.x2, r.y2, r.x2, r.y1); pDC->Line(r.x2, r.y2, r.x1, r.y2); pDC->Colour(LColour(L_LIGHT)); pDC->Line(r.x1, r.y1, r.x1, r.y2); pDC->Line(r.x1, r.y1, r.x2, r.y1); r.Inset(1, 1); break; } default: { LAssert(0); break; } } pDC->Colour(Old); } void LFlatBorder(LSurface *pDC, LRect &r, int Width) { pDC->Colour(LColour(L_MED)); if (Width < 1 || r.X() < (2 * Width) || r.Y() < (2 * Width)) { pDC->Rectangle(&r); r.ZOff(-1, -1); } else { pDC->Rectangle(r.x1, r.y1, r.x2, r.y1+Width-1); pDC->Rectangle(r.x1, r.y2-Width+1, r.x2, r.y2); pDC->Rectangle(r.x1, r.y1+Width, r.x1+Width-1, r.y2-Width); pDC->Rectangle(r.x2-Width+1, r.y1+Width, r.x2, r.y2-Width); r.Inset(Width, Width); } } void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray &Stops) { int CurStop = 0; LColourStop *This = Stops.Length() > CurStop ? &Stops[CurStop] : 0; CurStop++; LColourStop *Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0; int Limit = Vert ? r.Y() : r.X(); for (int n=0; nPos) { // Just this c = This->Colour; } else if (p > This->Pos && p < Next->Pos) { // Interpolate between this and next float d = Next->Pos - This->Pos; float t = (Next->Pos - p) / d; float n = (p - This->Pos) / d; uint8_t r = (uint8_t) ((This->Colour.r() * t) + (Next->Colour.r() * n)); uint8_t g = (uint8_t) ((This->Colour.g() * t) + (Next->Colour.g() * n)); uint8_t b = (uint8_t) ((This->Colour.b() * t) + (Next->Colour.b() * n)); uint8_t a = (uint8_t) ((This->Colour.a() * t) + (Next->Colour.a() * n)); c.Rgb(r, g, b, a); } else if (p >= Next->Pos) { // Get next colour stops This = Stops.Length() > CurStop ? &Stops[CurStop] : 0; CurStop++; Next = Stops.Length() > CurStop ? &Stops[CurStop] : 0; goto DoStop; } pDC->Colour(c); if (Vert) pDC->Line(r.x1, r.y1 + n, r.x2, r.y1 + n); else pDC->Line(r.x1 + n, r.y1, r.x1 + n, r.y2); } } } ////////////////////////////////////////////////////////////////////////////////// // Other Gdc Stuff LSurface *ConvertDC(LSurface *pDC, int Bits) { LSurface *pNew = new LMemDC; if (pNew && pNew->Create(pDC->X(), pDC->Y(), LBitsToColourSpace(Bits))) { pNew->Blt(0, 0, pDC); DeleteObj(pDC); return pNew; } return pDC; } LColour GdcMixColour(LColour c1, LColour c2, float HowMuchC1) { float HowMuchC2 = 1.0f - HowMuchC1; uint8_t r = (uint8_t) ((c1.r()*HowMuchC1) + (c2.r()*HowMuchC2)); uint8_t g = (uint8_t) ((c1.g()*HowMuchC1) + (c2.g()*HowMuchC2)); uint8_t b = (uint8_t) ((c1.b()*HowMuchC1) + (c2.b()*HowMuchC2)); uint8_t a = (uint8_t) ((c1.a()*HowMuchC1) + (c2.a()*HowMuchC2)); return LColour(r, g, b, a); } COLOUR CBit(int DstBits, COLOUR c, int SrcBits, LPalette *Pal) { if (SrcBits == DstBits) { return c; } else { switch (SrcBits) { case 8: { GdcRGB Grey, *p = 0; if (!Pal || !(p = (*Pal)[c])) { Grey.r = Grey.g = Grey.b = c & 0xFF; p = &Grey; } switch (DstBits) { case 16: { return Rgb16(p->r, p->g, p->b); } case 24: { return Rgb24(p->r, p->g, p->b); } case 32: { return Rgb32(p->r, p->g, p->b); } } break; } case 15: { int R = R15(c); int G = G15(c); int B = B15(c); R = R | (R >> 5); G = G | (G >> 5); B = B | (B >> 5); switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb24(R, G, B)); } break; } case 16: { return Rgb16(R, G, B); } case 24: { return Rgb24(R, G, B); } case 32: { return Rgb32(R, G, B); } } break; } case 16: { /* int R = ((c>>8)&0xF8) | ((c>>13)&0x7); int G = ((c>>3)&0xFC) | ((c>>9)&0x3); int B = ((c<<3)&0xF8) | ((c>>2)&0x7); */ int R = (c >> 8) & 0xF8; int G = (c >> 3) & 0xFC; int B = (c << 3) & 0xF8; switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb24(R, G, B)); } break; } case 15: { return Rgb16To15(c); } case 24: { return Rgb24(R, G, B); } case 32: { return Rgb32(R, G, B); } } break; } case 24: { switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(c); } break; } case 15: { return Rgb24To15(c); } case 16: { if (LGetOs() == LGI_OS_WIN32 || LGetOs() == LGI_OS_WIN64) { return Rgb24To16(c); } int r = MAX(R24(c) - 1, 0) >> 3; int g = MAX(G24(c) - 1, 0) >> 2; int b = MAX(B24(c) - 1, 0) >> 3; return (r << 11) | (g << 5) | (b); } case 24: case 48: { return c; } case 32: case 64: { return Rgb24To32(c); } default: LAssert(0); break; } break; } case 32: { switch (DstBits) { case 8: { if (Pal) { return Pal->MatchRgb(Rgb32To24(c)); } break; } case 15: { return Rgb32To15(c); } case 16: { return Rgb32To16(c); } case 24: { return Rgb32To24(c); } } break; } } } return c; } ///////////////////////////////////////////////////////////////////////////// const char *LColourSpaceToString(LColourSpace cs) { #define CS_STR_BUF 4 static int Cur = 0; static char Buf[CS_STR_BUF][16]; static const char *CompTypes[] = { "N", // None "I", // Index "R", // Red "G", // Green "B", // Blue "A", // Alpha "X", // Pad "H", // Hue "S", // Sat "L", // Lum "C", // Cyan "M", // Magenta "Y", // Yellow "B", // Black "?", "?", }; + if (cs == CsNone) + return "CsNone"; + char *start = Buf[Cur++], *s = start; int total = 0; bool first = true; if (Cur >= CS_STR_BUF) Cur = 0; *s++ = 'C'; *s++ = 's'; // printf("Converting Cs to String: 0x%x\n", cs); for (int i=3; i>=0; i--) { int c = (((uint32_t)cs) >> (i << 3)) & 0xff; // printf(" c[%i] = 0x%x\n", i, c); if (c) { LComponentType type = (LComponentType)(c >> 4); int size = c & 0xf; if (first) { *s++ = CompTypes[type][0]; first = false; } else { *s++ = tolower(CompTypes[type][0]); } total += size ? size : 16; } } s += sprintf_s(s, 4, "%i", total); return start; } int LColourSpaceChannels(LColourSpace Cs) { int Channels = 0; while (Cs) { uint8_t c = Cs & 0xff; if (!c) break; Channels++; ((int&)Cs) >>= 8; } return Channels; } bool LColourSpaceHasAlpha(LColourSpace Cs) { while (Cs) { uint8_t c = Cs & 0xff; LComponentType type = (LComponentType)(c >> 4); if (type == CtAlpha) return true; ((int&)Cs) >>= 8; } return false; } int LColourSpaceToBits(LColourSpace ColourSpace) { uint32_t c = ColourSpace; int bits = 0; while (c) { if (c & 0xf0) { int n = c & 0xf; bits += n ? n : 16; } c >>= 8; } return bits; } LColourSpace LStringToColourSpace(const char *c) { if (!c) return CsNone; if (!_strnicmp(c, "Cs", 2)) { // "Cs" style colour space c += 2; const char *n = c; while (*n && !IsDigit(*n)) n++; int Depth = ::atoi(n); if (!_strnicmp(c, "Index", 5)) { if (Depth == 8) return CsIndex8; } else { LArray Comp; while (*c) { switch (tolower(*c)) { case 'r': Comp.Add(CtRed); break; case 'g': Comp.Add(CtGreen); break; case 'b': Comp.Add(CtBlue); break; case 'a': Comp.Add(CtAlpha); break; case 'x': Comp.Add(CtPad); break; case 'c': Comp.Add(CtCyan); break; case 'm': Comp.Add(CtMagenta); break; case 'y': Comp.Add(CtYellow); break; case 'k': Comp.Add(CtBlack); break; case 'h': Comp.Add(CtHue); break; case 'l': Comp.Add(CtLuminance); break; case 's': Comp.Add(CtSaturation); break; } c++; } LColourSpaceBits a; ZeroObj(a); if (Comp.Length() == 3) { a[0].Type(Comp[0]); a[1].Type(Comp[1]); a[2].Type(Comp[2]); if (Depth == 24) { a[0].Size(8); a[1].Size(8); a[2].Size(8); } else if (Depth == 16) { a[0].Size(5); a[1].Size(6); a[2].Size(5); } else if (Depth == 15) { a[0].Size(5); a[0].Size(5); a[0].Size(5); } else if (Depth == 48) { a[0].Size(0); a[1].Size(0); a[2].Size(0); } else return CsNone; } else if (Comp.Length() == 4) { a[0].Type(Comp[0]); a[1].Type(Comp[1]); a[2].Type(Comp[2]); a[3].Type(Comp[3]); if (Depth == 32) { a[0].Size(8); a[1].Size(8); a[2].Size(8); a[3].Size(8); } else if (Depth == 64) { a[0].Size(0); a[1].Size(0); a[2].Size(0); a[3].Size(0); } else return CsNone; } LColourSpace Cs = (LColourSpace)a.All; return Cs; } } else if (!_strnicmp(c, "System", 6)) { // "System" colour space c += 6; int Depth = ::atoi(c); if (Depth) return LBitsToColourSpace(Depth); } return CsNone; } LColourSpace LBitsToColourSpace(int Bits) { switch (Bits) { case 8: return CsIndex8; case 15: return System15BitColourSpace; case 16: return System16BitColourSpace; case 24: return System24BitColourSpace; case 32: return System32BitColourSpace; default: LAssert(!"Unknown colour space."); break; } return CsNone; } bool LColourSpaceTest() { union { uint8_t b4[4]; uint32_t u32; LBgrx32 bgrx32; LXrgb32 xrgb32; LRgba32 rgba32; }; b4[0] = 1; b4[1] = 2; b4[2] = 3; b4[3] = 4; #if 0 // def LGI_SDL printf("LeastSigBit=%i, LeastSigByte=%i, u32=%08x\n", LeastSigBit, LeastSigByte, u32); printf("bgrx32=%i,%i,%i,%i\n", bgrx32.b, bgrx32.g, bgrx32.r, bgrx32.pad); printf("xrgb32=%i,%i,%i,%i\n", xrgb32.pad, xrgb32.r, xrgb32.g, xrgb32.b); #endif if (bgrx32.b != 1 || bgrx32.pad != 4 || xrgb32.pad != 1 || xrgb32.b != 4) { LAssert(!"32bit colour space byte ordering wrong. Flip the value of LEAST_SIG_BYTE_FIRST"); return false; } union { uint16 u16; LRgb16 rgb16; }; rgba32.r = 0xff; rgba32.g = 0; rgba32.b = 0; rgba32.a = 0; u16 = 0xf800; #if 0 // def LGI_SDL printf("rgba32=%i,%i,%i,%i or %08x\n", rgba32.r, rgba32.g, rgba32.b, rgba32.a, u32); printf("rgb16=%i,%i,%i or %04x\n", rgb16.r, rgb16.g, rgb16.b, u16); #endif if (rgb16.r != 0x1f || rgb16.b != 0x0) { LAssert(!"16bit colour space bit ordering wrong. Flip the value of LEAST_SIG_BIT_FIRST"); return false; } return true; } //////////////////////////////////////////////////////////////////////// LSurface *LInlineBmp::Create(uint32_t TransparentPx) { LSurface *pDC = new LMemDC; if (pDC->Create(X, Y, System32BitColourSpace, LSurface::SurfaceRequireExactCs)) { LBmpMem Src, Dst; Src.Base = (uint8_t*)Data; Src.Line = X * Bits >> 3; Src.x = X; Src.y = Y; switch (Bits) { case 8: Src.Cs = CsIndex8; break; case 15: Src.Cs = CsRgb15; break; case 16: Src.Cs = CsRgb16; break; case 24: Src.Cs = CsRgb24; break; case 32: Src.Cs = CsRgba32; break; default: Src.Cs = CsNone; break; } Dst.Base = (*pDC)[0]; Dst.Line = pDC->GetRowStep(); Dst.x = pDC->X(); Dst.y = pDC->Y(); Dst.Cs = pDC->GetColourSpace(); LRopUniversal(&Dst, &Src, false); if (TransparentPx != 0xffffffff) { for (int y=0; y> 3) { case 1: { for (int x=0; xr == r && px->g == g && px->b == b) { *d = 0; } d++; } break; } case 4: { for (int x=0; x %s\n", _FL, LColourSpaceToString(SrcCs), LColourSpaceToString(DstCs)); LAssert(!"Unsupported pixel conversion."); return false; } } return true; } /// Universal bit blt method bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite) { if (!Dst || !Src) return false; // Work out conversion area... int Cx = MIN(Dst->x, Src->x); int Cy = MIN(Dst->y, Src->y); // Size of src and dst pixels: int SrcBits = LColourSpaceToBits(Src->Cs); int DstBits = LColourSpaceToBits(Dst->Cs); if (Dst->Cs == Src->Cs && !Composite) { // No conversion idiom uint8_t *d = Dst->Base; uint8_t *s = Src->Base; int BytesPerPx = (SrcBits + 7) / 8; int Bytes = BytesPerPx * Cx; for (int y=0; yLine; s += Src->Line; } return true; } if (SrcBits > 8 && DstBits > 8) { uint8_t *d = Dst->Base; uint8_t *s = Src->Base; for (int y=0; yCs, s, Src->Cs, Cx, Composite)) return false; d += Dst->Line; s += Src->Line; } return true; } else { } // LAssert(!"Unsupported pixel conversion."); return false; } LPoint LScreenDpi() { static LPoint Dpi; #if LGI_COCOA if (!Dpi.x) { NSScreen *screen = [NSScreen mainScreen]; NSDictionary *description = [screen deviceDescription]; NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; CGSize displayPhysicalSize = CGDisplayScreenSize( [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); Dpi.x = (int) ((displayPixelSize.width / displayPhysicalSize.width) * 25.4f); Dpi.y = (int) ((displayPixelSize.height / displayPhysicalSize.height) * 25.4f); } #elif defined(__GTK_H__) if (!Dpi.x) { auto dpi = Gtk::gdk_screen_get_resolution(Gtk::gdk_screen_get_default()); if (dpi > 0) Dpi.Set((int)dpi, (int)dpi); } #elif defined(HAIKU) if (!Dpi.x) { double scaling = std::max(1.0f, be_plain_font->Size() / 12.0f); Dpi.x = (int)(96.0 * scaling); Dpi.y = Dpi.x; // printf("scaling=%g dpi=%i\n", scaling, Dpi.x); } #elif defined(WINDOWS) if (!Dpi.x) { SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE); auto dpi = GetDpiForSystem(); Dpi.Set(dpi, dpi); } #endif if (Dpi.x <= 0) Dpi.Set(96, 96); return Dpi; } bool LMemDC::SwapRedAndBlue() { switch (GetColourSpace()) { #define ROP(cs) \ case Cs##cs: \ { \ for (int y=0; yr, p->b); \ p++; \ } \ break; \ } \ break; \ } ROP(Rgb24) ROP(Bgr24) ROP(Rgbx32) ROP(Bgrx32) ROP(Xrgb32) ROP(Xbgr32) ROP(Argb32) ROP(Abgr32) ROP(Rgba32) ROP(Bgra32) default: return false; } return true; } diff --git a/src/common/General/TimeZone.cpp b/src/common/General/TimeZone.cpp --- a/src/common/General/TimeZone.cpp +++ b/src/common/General/TimeZone.cpp @@ -1,84 +1,84 @@ #include "lgi/common/Mem.h" #include "lgi/common/File.h" #include "lgi/common/Properties.h" #include "lgi/common/DateTime.h" // From HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones -GTimeZone GTimeZones[] = +LTimeZone GTimeZones[] = { {-12.00, "Dateline (Eniwetok, Kwajalein)"}, {-11.00, "Samoa (Midway Island, Samoa)"}, {-10.00, "Hawaiian (Hawaii)"}, {-9.00, "Alaskan (Alaska)"}, {-8.00, "Pacific (Pacific Time (US & Canada); Tijuana)"}, {-7.00, "Mountain (Mountain Time (US & Canada))"}, {-7.00, "US Mountain (Arizona)"}, {-6.00, "Canada Central (Saskatchewan)"}, {-6.00, "Central America (Central America)"}, {-6.00, "Central (Central Time (US & Canada))"}, {-6.00, "Mexico (Mexico City)"}, {-5.00, "Eastern (Eastern Time (US & Canada))"}, {-5.00, "SA Pacific (Bogota, Lima, Quito)"}, {-5.00, "US Eastern (Indiana (East))"}, {-4.00, "Atlantic (Atlantic Time (Canada))"}, {-4.00, "Pacific SA (Santiago)"}, {-4.00, "SA Western (Caracas, La Paz)"}, {-3.00, "East South America (Brasilia)"}, {-3.00, "Greenland (Greenland)"}, {-3.00, "SA Eastern (Buenos Aires, Georgetown)"}, {-2.50, "Newfoundland (Newfoundland)"}, {-2.00, "Mid-Atlantic (Mid-Atlantic)"}, {-1.00, "Azores (Azores)"}, {-1.00, "Cape Verde (Cape Verde Is.)"}, {0.00, "GMT"}, {1.00, "Central Europe (Belgrade, Bratislava, Budapest, Ljubljana, Prague)"}, {1.00, "Central European (Sarajevo, Skopje, Sofija, Vilnius, Warsaw, Zagreb)"}, {1.00, "Romance (Brussels, Copenhagen, Madrid, Paris)"}, {1.00, "West Central Africa (West Central Africa)"}, {1.00, "West Europe (Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna)"}, {2.00, "East Europe (Bucharest)"}, {2.00, "Egypt (Cairo)"}, {2.00, "FLE (Helsinki, Riga, Tallin, Vilnius)"}, {2.00, "GTB (Athens, Istanbul, Minsk)"}, {2.00, "Jerusalem (Jerusalem)"}, {2.00, "South Africa (Harare, Pretoria)"}, {3.00, "Arab (Kuwait, Riyadh)"}, {3.00, "Arabic (Baghdad)"}, {3.00, "East Africa (Nairobi)"}, {3.00, "Russian (Moscow, St. Petersburg, Volgograd)"}, {3.50, "Iran (Tehran)"}, {4.00, "Arabian (Abu Dhabi, Muscat)"}, {4.00, "Caucasus (Baku, Tbilisi, Yerevan)"}, {4.50, "Afghanistan (Kabul)"}, {5.00, "Ekaterinburg (Ekaterinburg)"}, {5.00, "West Asia (Islamabad, Karachi, Tashkent)"}, {5.50, "India (Calcutta, Chennai, Mumbai, New Delhi)"}, {5.75, "Nepal (Kathmandu)"}, {6.00, "Central Asia (Astana, Dhaka)"}, {6.00, "N. Central Asia (Almaty, Novosibirsk)"}, {6.00, "Sri Lanka (Sri Jayawardenepura)"}, {6.50, "Myanmar (Rangoon)"}, {7.00, "North Asia (Krasnoyarsk)"}, {7.00, "SE Asia (Bangkok, Hanoi, Jakarta)"}, {8.00, "China (Beijing, Chongqing, Hong Kong, Urumqi)"}, {8.00, "North Asia East (Irkutsk, Ulaan Bataar)"}, {8.00, "Malay Peninsula (Kuala Lumpur, Singapore)"}, {8.00, "Taipei (Taipei)"}, {8.00, "West Australia (Perth)"}, {9.00, "Korea (Seoul)"}, {9.00, "Tokyo (Osaka, Sapporo, Tokyo)"}, {9.00, "Yakutsk (Yakutsk)"}, {9.50, "AUS Central (Darwin)"}, {9.50, "Cen. Australia (Adelaide)"}, {10.00, "AUS Eastern (Canberra, Melbourne, Sydney)"}, {10.00, "East Australia (Brisbane)"}, {10.00, "Tasmania (Hobart)"}, {10.00, "Vladivostok (Vladivostok)"}, {10.00, "West Pacific (Guam, Port Moresby)"}, {11.00, "Central Pacific (Magadan, Solomon Is., New Caledonia)"}, {12.00, "Fiji (Fiji, Kamchatka, Marshall Is.)"}, {12.00, "New Zealand (Auckland, Wellington)"}, {13.00, "Tonga (Nuku'alofa)"}, {0, 0} }; diff --git a/src/common/Lgi/GuiUtils.cpp b/src/common/Lgi/GuiUtils.cpp --- a/src/common/Lgi/GuiUtils.cpp +++ b/src/common/Lgi/GuiUtils.cpp @@ -1,294 +1,294 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #endif ////////////////////////////////////////////////////////////////////////////////// #if !defined(LK_CONTEXTKEY) // LK_CONTEXTKEY is the key that brings up the context menu #if defined(WINDOWS) #define LK_CONTEXTKEY 0x5d #elif defined(MAC) #define LK_CONTEXTKEY 0x6e #elif defined(__GTK_H__) #define LK_CONTEXTKEY GDK_KEY_Menu #else #define LK_CONTEXTKEY 0x5d #warning "Check local platform def for app menu key." #endif #endif LKey::LKey(int Vkey, uint32_t flags) { c16 = vkey = Vkey; #ifdef WINDOWS Data = flags; #else Flags = flags; #endif } bool LKey::IsContextMenu() const { return !IsChar && vkey == LK_CONTEXTKEY; } ////////////////////////////////////////////////////////////////////////////////// LString LMouse::ToString() const { LString s; s.Printf("LMouse(pos=%i,%i view=%p/%s btns=%i/%i/%i/%i/%i dwn=%i dbl=%i " "ctrl=%i alt=%i sh=%i sys=%i)", x, y, // pos Target, Target?Target->GetClass():NULL, // view Left(), Middle(), Right(), Button1(), Button2(), // btns Down(), Double(), // dwn Ctrl(), Alt(), Shift(), System()); // mod keys return s; } bool LMouse::IsContextMenu() const { if (Right()) return true; #if defined(MAC) if (Left() && Ctrl()) return true; #endif return false; } bool LMouse::ToScreen() { if (ViewCoords) { if (!Target) { printf("%s:%i - ToScreen Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToScreen(p); x = p.x; y = p.y; ViewCoords = false; } return true; } bool LMouse::ToView() { if (!ViewCoords) { if (!Target) { printf("%s:%i - ToView Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToView(p); x = p.x; y = p.y; ViewCoords = true; } return true; } #if WINNATIVE #if !defined(DM_POSITION) #define DM_POSITION 0x00000020L #endif typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesA)(PVOID, DWORD, PDISPLAY_DEVICEA, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesW)(PVOID, DWORD, PDISPLAY_DEVICEW, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsA)(LPCSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEA lpDevMode); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsW)(LPCWSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEW lpDevMode); #endif -LPointF GDisplayInfo::Scale() +LPointF LDisplayInfo::Scale() { LPointF p((double)Dpi.x / 96.0, (double)Dpi.y / 96.0); return p; } -bool LGetDisplays(::LArray &Displays, LRect *AllDisplays) +bool LGetDisplays(::LArray &Displays, LRect *AllDisplays) { #if WINNATIVE if (AllDisplays) AllDisplays->ZOff(-1, -1); LLibrary User32("User32"); DISPLAY_DEVICEW disp; ZeroObj(disp); disp.cb = sizeof(disp); pEnumDisplayDevicesW EnumDisplayDevicesW = (pEnumDisplayDevicesW) User32.GetAddress("EnumDisplayDevicesW"); pEnumDisplaySettingsW EnumDisplaySettingsW = (pEnumDisplaySettingsW) User32.GetAddress("EnumDisplaySettingsW"); for (int i=0; EnumDisplayDevicesW(0, i, &disp, 0); i++) { DEVMODEW mode; ZeroObj(mode); mode.dmSize = sizeof(mode); mode.dmDriverExtra = sizeof(mode); if (EnumDisplaySettingsW(disp.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) { - GDisplayInfo *Dsp = new GDisplayInfo; + LDisplayInfo *Dsp = new LDisplayInfo; if (Dsp) { Dsp->r.ZOff(mode.dmPelsWidth-1, mode.dmPelsHeight-1); if (mode.dmFields & DM_POSITION) { Dsp->r.Offset(mode.dmPosition.x, mode.dmPosition.y); } if (AllDisplays) { if (AllDisplays->Valid()) AllDisplays->Union(&Dsp->r); else *AllDisplays = Dsp->r; } disp.cb = sizeof(disp); Dsp->BitDepth = mode.dmBitsPerPel; Dsp->Refresh = mode.dmDisplayFrequency; Dsp->Name = disp.DeviceString; Dsp->Device = disp.DeviceName; Dsp->Dpi.x = Dsp->Dpi.y = mode.dmLogPixels; DISPLAY_DEVICEW temp = disp; if (EnumDisplayDevicesW(temp.DeviceName, 0, &disp, 0)) Dsp->Monitor = disp.DeviceString; Displays.Add(Dsp); } } disp.cb = sizeof(disp); } #elif defined __GTK_H__ Gtk::GdkDisplay *Dsp = Gtk::gdk_display_get_default(); #if GtkVer(3, 22) int monitors = Gtk::gdk_display_get_n_monitors(Dsp); for (int i=0; ir = geometry; di->Device = Gtk::gdk_monitor_get_manufacturer(m); di->Name = Gtk::gdk_monitor_get_model(m); di->Refresh = Gtk::gdk_monitor_get_refresh_rate(m); Displays.Add(di); } #endif #elif LGI_COCOA for (NSScreen *s in [NSScreen screens]) { - GDisplayInfo *di = new GDisplayInfo; + LDisplayInfo *di = new LDisplayInfo; di->r = s.frame; Displays.Add(di); } #endif return Displays.Length() > 0; } void GetChildrenList(LViewI *w, List &l) { if (!w) return; for (auto v: w->IterateViews()) { #if WINNATIVE int Style = GetWindowLong(v->Handle(), GWL_STYLE); if (TestFlag(Style, WS_VISIBLE) && !TestFlag(Style, WS_DISABLED)) { if (TestFlag(Style, WS_TABSTOP)) { l.Insert(v); } GetChildrenList(v, l); } #else if (v->Visible() && v->Enabled()) { if (v->GetTabStop()) { l.Insert(v); } GetChildrenList(v, l); } #endif } } LViewI *GetNextTabStop(LViewI *v, bool Back) { if (v) { LWindow *Wnd = v->GetWindow(); if (Wnd) { List All; GetChildrenList(Wnd, All); ssize_t MyIndex = All.IndexOf(v); if (MyIndex >= 0) { int Inc = Back ? -1 : 1; size_t NewIndex = (MyIndex + All.Length() + Inc) % All.Length(); return All.ItemAt(NewIndex); } else { return All[0]; } } } return 0; } diff --git a/src/common/Lgi/WindowCommon.cpp b/src/common/Lgi/WindowCommon.cpp --- a/src/common/Lgi/WindowCommon.cpp +++ b/src/common/Lgi/WindowCommon.cpp @@ -1,206 +1,206 @@ // // LWindowCommon.cpp // LgiCarbon // // Created by Matthew Allen on 11/09/14. // // #include "lgi/common/Lgi.h" #include "lgi/common/Net.h" #include "lgi/common/DropFiles.h" void LWindow::ScaleSizeToDpi() { auto s = GetDpiScale(); auto p = GetPos(); p.SetSize( (int) (p.X() * s.x), (int) (p.Y() * s.y) ); SetPos(p); } void LWindow::BuildShortcuts(ShortcutMap &Map, LViewI *v) { if (!v) v = this; for (auto c: v->IterateViews()) { const char *n = c->Name(), *amp; if (n && (amp = strchr(n, '&'))) { amp++; if (*amp && *amp != '&') { uint8_t *i = (uint8_t*)amp; ssize_t sz = Strlen(amp); int32 ch = LgiUtf8To32(i, sz); if (ch) Map.Add(ToUpper(ch), c); } } BuildShortcuts(Map, c); } } void LWindow::MoveOnScreen() { LRect p = GetPos(); - LArray Displays; + LArray Displays; LRect Screen(0, 0, -1, -1); if ( #if WINNATIVE !IsZoomed(Handle()) && !IsIconic(Handle()) && #endif LGetDisplays(Displays)) { int Best = -1; int Pixels = 0; int Close = 0x7fffffff; for (int i=0; ir); if (o.Valid()) { int Pix = o.X()*o.Y(); if (Best < 0 || Pix > Pixels) { Best = i; Pixels = Pix; } } else if (Pixels == 0) { int n = Displays[i]->r.Near(p); if (n < Close) { Best = i; Close = n; } } } if (Best >= 0) Screen = Displays[Best]->r; } if (!Screen.Valid()) Screen.Set(0, 0, GdcD->X()-1, GdcD->Y()-1); if (p.x2 >= Screen.x2) p.Offset(Screen.x2 - p.x2, 0); if (p.y2 >= Screen.y2) p.Offset(0, Screen.y2 - p.y2); if (p.x1 < Screen.x1) p.Offset(Screen.x1 - p.x1, 0); if (p.y1 < Screen.y1) p.Offset(0, Screen.y1 - p.y1); SetPos(p, true); Displays.DeleteObjects(); } void LWindow::MoveToCenter() { LRect Screen(0, 0, GdcD->X()-1, GdcD->Y()-1); - LArray Displays; + LArray Displays; LRect p = GetPos(); p.Offset(-p.x1, -p.y1); if (LGetDisplays(Displays, &Screen) && Displays.Length() > 0) { - GDisplayInfo *Dsp = NULL; + LDisplayInfo *Dsp = NULL; for (auto d: Displays) { if (d->r.Overlap(&p)) { Dsp = d; break; } } if (!Dsp) goto ScreenPos; p.Offset(Dsp->r.x1 + ((Dsp->r.X() - p.X()) / 2), Dsp->r.y1 + ((Dsp->r.Y() - p.Y()) / 2)); } else { ScreenPos: p.Offset((Screen.X() - p.X()) / 2, (Screen.Y() - p.Y()) / 2); } SetPos(p, true); Displays.DeleteObjects(); } void LWindow::MoveToMouse() { LMouse m; if (GetMouse(m, true)) { LRect p = GetPos(); p.Offset(-p.x1, -p.y1); p.Offset(m.x-(p.X()/2), m.y-(p.Y()/2)); SetPos(p, true); MoveOnScreen(); } } bool LWindow::MoveSameScreen(LViewI *View) { if (!View) { LAssert(0); return false; } auto Wnd = View->GetWindow(); LRect p = Wnd ? Wnd->GetPos() : View->GetPos(); int cx = p.x1 + (p.X() >> 4); int cy = p.y1 + (p.Y() >> 4); LRect np = GetPos(); np.Offset(cx - np.x1, cy - np.y1); SetPos(np); MoveOnScreen(); return true; } int LWindow::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.SupportsFileDrops(); return true; } int LWindow::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; #ifdef DEBUG_DND LgiTrace("%s:%i - OnDrop Data=%i Pt=%i,%i Key=0x%x\n", _FL, Data.Length(), Pt.x, Pt.y, KeyState); #endif for (auto &dd: Data) { #ifdef DEBUG_DND LgiTrace("\tFmt=%s\n", dd.Format.Get()); #endif if (dd.IsFileDrop()) { LDropFiles Files(dd); if (Files.Length()) { Status = DROPEFFECT_COPY; OnReceiveFiles(Files); break; } } } return Status; } diff --git a/src/common/Resource/LgiRes.cpp b/src/common/Resource/LgiRes.cpp --- a/src/common/Resource/LgiRes.cpp +++ b/src/common/Resource/LgiRes.cpp @@ -1,1647 +1,1647 @@ /*hdr ** FILE: LgiRes.cpp ** AUTHOR: Matthew Allen ** DATE: 12/3/2000 ** DESCRIPTION: Xp resource interfaces ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "lgi/common/TableLayout.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #endif #include "lgi/common/Variant.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #include "lgi/common/ProgressView.h" // If it is defined it will use the cross platform // "res" library distributed with the LGI library. #define DEBUG_RES_FILE 0 #define CastToLView(RObj) ((RObj != 0) ? dynamic_cast(RObj) : 0) class TagHash : public LHashTbl,bool>, public ResReadCtx { LString::Array Toks; public: TagHash(const char *TagList) : Toks(LString(TagList).SplitDelimit()) { for (int i=0; iGetAttr("Tag"); if (Tag) return Check(Tag); return true; } }; const char *LStringRes::CodePage = 0; LLanguage *LStringRes::CurLang = 0; LStringRes::LStringRes(LResources *res) { Res = res; Ref = 0; Id = 0; Str = 0; Tag = 0; // IsString = false; } LStringRes::~LStringRes() { DeleteArray(Str); DeleteArray(Tag); } bool LStringRes::Read(LXmlTag *t, ResFileFormat Format) { if (LStringRes::CurLang && t && stricmp(t->GetTag(), "string") == 0) { char *n = 0; if ((n = t->GetAttr("Cid")) || (n = t->GetAttr("Id")) ) { Id = atoi(n); } if ((n = t->GetAttr("Ref"))) { Ref = atoi(n); } if ((n = t->GetAttr("Define"))) { if (strcmp(n, "IDOK") == 0) { Id = IDOK; } else if (strcmp(n, "IDCANCEL") == 0) { Id = IDCANCEL; } else if (strcmp(n, "IDC_STATIC") == 0) { Id = -1; } } if ((n = t->GetAttr("Tag"))) { Tag = NewStr(n); } const char *Cp = LStringRes::CodePage; char Name[256]; strcpy_s(Name, sizeof(Name), LStringRes::CurLang->Id); n = 0; if ((n = t->GetAttr(Name)) && strlen(n) > 0) { // Language string ok // Lang = LangFind(0, CurLang, 0); } else if (LStringRes::CurLang->OldId && sprintf_s(Name, sizeof(Name), "Text(%i)", LStringRes::CurLang->OldId) && (n = t->GetAttr(Name)) && strlen(n) > 0) { // Old style language string ok } else if (!LStringRes::CurLang->IsEnglish()) { // no string, try english n = t->GetAttr("en"); LLanguage *Lang = LFindLang("en"); if (Lang) { Cp = Lang->Charset; } } if (n) { DeleteArray(Str); if (Cp) { Str = (char*)LNewConvertCp("utf-8", n, Cp); } else { Str = NewStr(n); } if (Str) { char *d = Str; for (char *s = Str; s && *s;) { if (*s == '\\') { if (*(s+1) == 'n') { *d++ = '\n'; } else if (*(s+1) == 't') { *d++ = '\t'; } s += 2; } else { *d++ = *s++; } } *d++ = 0; } } if (Res) { for (int a=0; aAttr.Length(); a++) { LXmlAttr *v = &t->Attr[a]; char *Name = v->GetName(); if (LFindLang(Name)) { Res->AddLang(Name); } /* else if ( Name[0] == 'T' && Name[1] == 'e' && Name[2] == 'x' && Name[3] == 't' && Name[4] == '(') { int Old = atoi(Name + 5); if (Old) { LLanguage *OldLang = GFindOldLang(Old); if (OldLang) { LAssert(OldLang->OldId == Old); Res->AddLang(OldLang->Id); } } } */ } } return true; } return false; } /////////////////////////////////////////////////////////////////// // Lgi resources class LResourcesPrivate { public: bool Ok; ResFileFormat Format; LString File; LString ThemeFolder; LHashTbl, LStringRes*> StrRef; LHashTbl, LStringRes*> Strings; LHashTbl, LStringRes*> DlgStrings; LResourcesPrivate() { Ok = false; Format = Lr8File; } ~LResourcesPrivate() { StrRef.DeleteObjects(); } }; /// A collection of resources /// \ingroup Resources class LResourceContainer : public LArray { public: ~LResourceContainer() { DeleteObjects(); } }; static LResourceContainer ResourceOwner; bool LResources::LoadStyles = false; LResources::LResources(const char *FileName, bool Warn, const char *ThemeFolder) { d = new LResourcesPrivate; ScriptEngine = 0; // global pointer list ResourceOwner.Add(this); LString File; LString FullPath; #if DEBUG_RES_FILE LgiTrace("%s:%i - Filename='%s'\n", _FL, FileName); #endif if (LFileExists(FileName)) { if (Load(FileName)) return; } if (FileName) { // We're given the file name, and we should find the path. const char *f = strrchr(FileName, DIR_CHAR); #if DEBUG_RES_FILE LgiTrace("%s:%i - f='%s'\n", _FL, f); #endif File = f ? f + 1 : FileName; #if DEBUG_RES_FILE LgiTrace("%s:%i - File='%s'\n", _FL, File.Get()); #endif } else { // Need to look up the file associated by name with the current exe auto Exe = LGetExeFile(); if (Exe) { #if DEBUG_RES_FILE LgiTrace("%s:%i - Exe='%s'\n", _FL, Exe.Get()); #endif auto p = Exe.SplitDelimit(DIR_STR); File = p.Last(); #if defined WINDOWS auto DotExe = File.RFind(".exe"); if (DotExe >= 0) File.Length(DotExe); #elif defined MAC auto DotApp = File.RFind(".app"); if (DotApp >= 0) File.Length(DotApp); #endif #if DEBUG_RES_FILE LgiTrace("%s:%i - File='%s'\n", _FL, File.Get()); #endif } else { LgiMsg(0, LLoadString(L_ERROR_RES_NO_EXE_PATH, "Fatal error: Couldn't get the path of the running\nexecutable. Can't find resource file."), "LResources::LResources"); LgiTrace("%s:%i - Fatal error: Couldn't get the path of the running\nexecutable. Can't find resource file.\n", _FL); LExitApp(); } } #if DEBUG_RES_FILE LgiTrace("%s:%i - File='%s'\n", _FL, File.Get()); #endif LString BaseFile = File; LString AltFile = File.Replace("."); if (Stricmp(LGetExtension(BaseFile), "lr8")) BaseFile += ".lr8"; if (Stricmp(LGetExtension(AltFile), "lr8")) AltFile += ".lr8"; #if DEBUG_RES_FILE LgiTrace("%s:%i - File='%s'\n", _FL, BaseFile.Get()); #endif FullPath = LFindFile(BaseFile); if (!FullPath) FullPath = LFindFile(AltFile); #if !WINDOWS if (!FullPath) { auto leaf = LGetLeaf(BaseFile); if (leaf && *leaf >= 'a' && *leaf <= 'z') { // Try upper case? *leaf = ToUpper(*leaf); FullPath = LFindFile(BaseFile); } } #endif #if DEBUG_RES_FILE LgiTrace("%s:%i - FullPath='%s'\n", _FL, FullPath.Get()); #endif if (FullPath) { Load(FullPath); if (ThemeFolder) SetThemeFolder(ThemeFolder); } else { char Msg[256]; auto Exe = LGetExeFile(); // Prepare data sprintf_s(Msg, sizeof(Msg), LLoadString(L_ERROR_RES_NO_LR8_FILE, "Couldn't find the file '%s' required to run this application\n(Exe='%s')"), BaseFile.Get(), Exe.Get()); // Dialog printf("%s\n", Msg); if (Warn) { LgiMsg(0, Msg, "LResources::LResources"); // Exit LExitApp(); } } } LResources::~LResources() { ResourceOwner.Delete(this); LanguageNames.DeleteArrays(); Dialogs.DeleteObjects(); Menus.DeleteObjects(); DeleteObj(d); } const char *LResources::GetThemeFolder() { return d->ThemeFolder; } bool LResources::DefaultColours = true; void LResources::SetThemeFolder(const char *f) { d->ThemeFolder = f; LFile::Path Colours(f, "colours.json"); if (Colours.Exists()) DefaultColours = !LColourLoad(Colours.GetFull()); LFile::Path Css(f, "styles.css"); if (Css.Exists()) { LFile in(Css, O_READ); if (in.IsOpen()) { auto s = in.Read(); const char *css = s; CssStore.Parse(css); } } } bool LResources::StyleElement(LViewI *v) { if (!v) return false; if (!LoadStyles) return true; LCss::SelArray Selectors; for (auto r: ResourceOwner) { LViewCssCb Ctx; r->CssStore.Match(Selectors, &Ctx, v); } LCss *Css = v->GetCss(true); for (auto *Sel: Selectors) { const char *Defs = Sel->Style; if (Css && Defs) Css->Parse(Defs, LCss::ParseRelaxed); } auto ElemStyles = v->CssStyles(); if (ElemStyles) { const char *Defs = ElemStyles; Css->Parse(Defs, LCss::ParseRelaxed); } return true; } ResFileFormat LResources::GetFormat() { return d->Format; } bool LResources::IsOk() { return d->Ok; } void LResources::AddLang(LLanguageId id) { // search through id's... for (int i=0; iFile; } bool LResources::Load(const char *FileName) { if (!FileName) { LgiTrace("%s:%i - No filename.\n", _FL); LAssert(0); return false; } LFile F; if (!F.Open(FileName, O_READ)) { LgiTrace("%s:%i - Couldn't open '%s'.\n", _FL, FileName); LAssert(0); return false; } d->File = FileName; d->Format = Lr8File; char *Ext = LGetExtension(FileName); if (Ext && stricmp(Ext, "lr") == 0) { d->Format = CodepageFile; } else if (Ext && stricmp(Ext, "xml") == 0) { d->Format = XmlFile; } LStringRes::CurLang = LGetLanguageId(); if (d->Format != CodepageFile) { LStringRes::CodePage = 0; } else if (LStringRes::CurLang) { LStringRes::CodePage = LStringRes::CurLang->Charset; } LXmlTree x(0); LAutoPtr Root(new LXmlTag); if (!x.Read(Root, &F, 0)) { LgiTrace("%s:%i - ParseXmlFile failed: %s\n", _FL, x.GetErrorMsg()); // LAssert(0); return false; } if (Root->Children.Length() == 0) return false; for (auto It = Root->Children.begin(); It != Root->Children.end(); ) { auto t = *It; if (t->IsTag("string-group")) { bool IsString = true; char *Name = 0; if ((Name = t->GetAttr("Name"))) { IsString = stricmp("_Dialog Symbols_", Name) != 0; } for (auto c: t->Children) { LStringRes *s = new LStringRes(this); if (s && s->Read(c, d->Format)) { // This code saves the names of the languages if specified in the LR8 files. char *Def = c->GetAttr("define"); if (Def && !stricmp(Def, "IDS_LANGUAGE")) { for (int i=0; iAttr.Length(); i++) { LXmlAttr &a = c->Attr[i]; LanguageNames.Add(a.GetName(), NewStr(a.GetValue())); } } // Save the string for later. d->StrRef.Add(s->Ref, s); if (s->Id != -1) { if (IsString) d->Strings.Add(s->Id, s); else d->DlgStrings.Add(s->Id, s); } d->Ok = true; } else { LgiTrace("%s:%i - string read failed\n", _FL); DeleteObj(s); } } } else if (t->IsTag("dialog")) { LDialogRes *n = new LDialogRes(this); if (n && n->Read(t, d->Format)) { Dialogs.Insert(n); d->Ok = true; // This allows the menu to take ownership of the XML tag t->RemoveTag(); t = NULL; } else { LgiTrace("%s:%i - dialog read failed\n", _FL); DeleteObj(n); } } else if (t->IsTag("menu")) { LMenuRes *m = new LMenuRes(this); if (m && m->Read(t, d->Format)) { Menus.Insert(m); d->Ok = true; // This allows the menu to take ownership of the XML tag t->RemoveTag(); t = NULL; } else { LgiTrace("%s:%i - menu read failed\n", _FL); DeleteObj(m); } } else if (t->IsTag("style")) { const char *c = t->GetContent(); CssStore.Parse(c); } if (t) It++; } return true; } LStringRes *LResources::StrFromRef(int Ref) { return d->StrRef.Find(Ref); } char *LResources::StringFromId(int Id) { LStringRes *NotStr = 0; LStringRes *sr; if ((sr = d->Strings.Find(Id))) return sr->Str; if ((sr = d->DlgStrings.Find(Id))) return sr->Str; return NotStr ? NotStr->Str : 0; } char *LResources::StringFromRef(int Ref) { LStringRes *s = d->StrRef.Find(Ref); return s ? s->Str : 0; } #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Button.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/TabView.h" #include "lgi/common/Combo.h" #include "lgi/common/Bitmap.h" #include "lgi/common/Slider.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Tree.h" class LMissingCtrl : public LLayout, public ResObject { LAutoString n; public: LMissingCtrl(char *name) : ResObject(Res_Custom) { n.Reset(NewStr(name)); } void OnPaint(LSurface *pDC) { LRect c = GetClient(); pDC->Colour(LColour(0xcc, 0xcc, 0xcc)); pDC->Rectangle(); pDC->Colour(LColour(0xff, 0, 0)); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x2, c.y1, c.x1, c.y2); LDisplayString ds(LSysFont, n); LSysFont->Transparent(true); LSysFont->Fore(LColour(L_TEXT)); ds.Draw(pDC, 3, 0); } }; ResObject *LResources::CreateObject(LXmlTag *t, ResObject *Parent) { ResObject *Wnd = 0; if (t && t->GetTag()) { char *Control = 0; if (stricmp(t->GetTag(), Res_StaticText) == 0) { Wnd = new LTextLabel(0, 0, 0, -1, -1, NULL); } else if (stricmp(t->GetTag(), Res_EditBox) == 0) { Wnd = new LEdit(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_CheckBox) == 0) { Wnd = new LCheckBox(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_Button) == 0) { Wnd = new LButton(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_Group) == 0) { Wnd = new LRadioGroup(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_RadioBox) == 0) { Wnd = new LRadioButton(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_TabView) == 0) { LTabView *Tv = new LTabView(0, 10, 10, 100, 100, "LTabView"); Wnd = Tv; if (Tv) { Tv->SetPourLargest(false); Tv->SetPourChildren(false); } } else if (stricmp(t->GetTag(), Res_Tab) == 0) { Wnd = new LTabPage(0); } else if (stricmp(t->GetTag(), Res_ListView) == 0) { LList *w; Wnd = w = new LList(0, 0, 0, -1, -1, ""); if (w) { w->Sunken(true); } } else if (stricmp(t->GetTag(), Res_Column) == 0) { if (Parent) { LList *Lst = dynamic_cast(Parent); LAssert(Lst != NULL); if (Lst) { Wnd = Lst->AddColumn(""); } } } else if (stricmp(t->GetTag(), Res_ComboBox) == 0) { Wnd = new LCombo(0, 0, 0, 100, 20, ""); } else if (stricmp(t->GetTag(), Res_Bitmap) == 0) { Wnd = new LBitmap(0, 0, 0, 0); } else if (stricmp(t->GetTag(), Res_Progress) == 0) { Wnd = new LProgressView(0, 0, 0, -1, -1, ""); } else if (stricmp(t->GetTag(), Res_Slider) == 0) { Wnd = new LSlider(0, 0, 0, -1, -1, "", false); } else if (stricmp(t->GetTag(), Res_ScrollBar) == 0) { Wnd = new LScrollBar(0, 0, 0, 20, 100, ""); } else if (stricmp(t->GetTag(), Res_Progress) == 0) { Wnd = new LProgressView(0, 0, 0, 1, 1, ""); } else if (stricmp(t->GetTag(), Res_TreeView) == 0) { Wnd = new LTree(0, 0, 0, 1, 1, ""); } else if (stricmp(t->GetTag(), Res_ControlTree) == 0) { LView *v = LViewFactory::Create("LControlTree"); if (!(Wnd = dynamic_cast(v))) { DeleteObj(v); } } else if (stricmp(t->GetTag(), Res_Custom) == 0) { Control = t->GetAttr("ctrl"); LView *v = LViewFactory::Create(Control); if (!v) v = new LMissingCtrl(Control); if (v) { Wnd = dynamic_cast(v); if (!Wnd) { // Not a "ResObject" LAssert(!"Not a ResObject"); DeleteObj(v); } } } else if (stricmp(t->GetTag(), Res_Table) == 0) { Wnd = new LTableLayout; } if (!Wnd) { printf(LLoadString(L_ERROR_RES_CREATE_OBJECT_FAILED, "LResources::CreateObject(%s) failed. (Ctrl=%s)\n"), t->GetTag(), Control); } } LAssert(Wnd != NULL); return Wnd; } void LResources::Res_SetPos(ResObject *Obj, int x1, int y1, int x2, int y2) { LItemColumn *Col = dynamic_cast(Obj); if (Col) { Col->Width(x2-x1); } else { LView *w = CastToLView(Obj); if (w) { LRect n(x1, y1, x2, y2); w->SetPos(n); } } } void LResources::Res_SetPos(ResObject *Obj, char *s) { if (Obj && s) { auto T = LString(s).SplitDelimit(","); if (T.Length() == 4) { Res_SetPos(Obj, (int)T[0].Int(), (int)T[1].Int(), (int)T[2].Int(), (int)T[3].Int()); } } } bool LResources::Res_GetProperties(ResObject *Obj, LDom *Props) { // this is a read-only system... return false; } struct ResObjectCallback : public LCss::ElementCallback { LDom *Props; LVariant v; ResObjectCallback(LDom *props) { Props = props; } const char *GetElement(ResObject *obj) { return obj->GetObjectName(); } const char *GetAttr(ResObject *obj, const char *Attr) { if (!Props->GetValue(Attr, v)) v.Empty(); return v.Str(); } bool GetClasses(LString::Array &Classes, ResObject *obj) { if (Props->GetValue("class", v)) Classes.New() = v.Str(); return Classes.Length() > 0; } ResObject *GetParent(ResObject *obj) { LAssert(0); return NULL; } LArray GetChildren(ResObject *obj) { LArray a; return a; } }; bool LResources::Res_SetProperties(ResObject *Obj, LDom *Props) { LView *v = dynamic_cast(Obj); if (!v || !Props) return false; LVariant i; if (Props->GetValue("enabled", i)) v->Enabled(i.CastInt32() != 0); if (Props->GetValue("visible", i)) v->Visible(i.CastInt32() != 0); if (Props->GetValue("style", i)) v->CssStyles(i.Str()); if (Props->GetValue("class", i)) { LString::Array *a = v->CssClasses(); if (a) (*a) = LString(i.Str()).SplitDelimit(" \t"); } if (Props->GetValue("image", i)) v->GetCss(true)->BackgroundImage(LCss::ImageDef(i.Str())); auto e = dynamic_cast(v); if (e) { if (Props->GetValue("pw", i)) e->Password(i.CastInt32() != 0); if (Props->GetValue("multiline", i)) e->MultiLine(i.CastInt32() != 0); } auto b = dynamic_cast(v); if (b) { if (Props->GetValue("image", i)) b->GetCss(true)->BackgroundImage(LCss::ImageDef(i.Str())); if (Props->GetValue("toggle", i)) b->SetIsToggle(i.CastInt32()!=0); } return true; } LRect LResources::Res_GetPos(ResObject *Obj) { LView *w = CastToLView(Obj); if (w) { return w->GetPos(); } return LRect(0, 0, 0, 0); } int LResources::Res_GetStrRef(ResObject *Obj) { return 0; } bool LResources::Res_SetStrRef(ResObject *Obj, int Ref, ResReadCtx *Ctx) { LStringRes *s = d->StrRef.Find(Ref); if (!s) return false; if (Ctx && !Ctx->Check(s->Tag)) return false; LView *w = CastToLView(Obj); if (w) { w->SetId(s->Id); if (ValidStr(s->Str)) { w->Name(s->Str); } } else if (Obj) { LItemColumn *Col = dynamic_cast(Obj); if (Col) { Col->Name(s->Str); } else { LTabPage *Page = dynamic_cast(Obj); if (Page) { Page->Name(s->Str); } } } return true; } void LResources::Res_Attach(ResObject *Obj, ResObject *Parent) { LView *o = CastToLView(Obj); LView *p = CastToLView(Parent); LTabPage *Tab = dynamic_cast(Parent); if (o) { if (Tab) { if (Tab) { LRect r = o->GetPos(); r.Offset(-4, -24); o->SetPos(r); Tab->Append(o); } } else if (p) { if (!p->IsAttached() || !o->Attach(p)) { p->AddView(o); o->SetParent(p); } } else { LAssert(p != NULL); } } } bool LResources::Res_GetChildren(ResObject *Obj, List *l, bool Deep) { LView *o = CastToLView(Obj); if (o && l) { for (LViewI *w: o->IterateViews()) { ResObject *n = dynamic_cast(w); if (n) l->Insert(n); } return true; } return false; } void LResources::Res_Append(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { LItemColumn *Col = dynamic_cast(Obj); LList *Lst = dynamic_cast(Parent); if (Lst && Col) { Lst->AddColumn(Col); } LTabView *Tab = dynamic_cast(Obj); LTabPage *Page = dynamic_cast(Parent); if (Tab && Page) { Tab->Append(Page); } } } bool LResources::Res_GetItems(ResObject *Obj, List *l) { if (Obj && l) { LList *Lst = dynamic_cast(Obj); if (Lst) { for (int i=0; iGetColumns(); i++) { l->Insert(Lst->ColumnAt(i)); } return true; } LTabView *Tabs = dynamic_cast(Obj); if (Tabs) { for (int i=0; iGetTabs(); i++) { l->Insert(Tabs->TabAt(i)); } return true; } } return false; } LDom *LResources::Res_GetDom(ResObject *Obj) { return dynamic_cast(Obj); } /////////////////////////////////////////////////////// LDialogRes::LDialogRes(LResources *res) { Res = res; Dialog = 0; Str = 0; } LDialogRes::~LDialogRes() { DeleteObj(Dialog); } bool LDialogRes::Read(LXmlTag *t, ResFileFormat Format) { if ((Dialog = t)) { char *n = 0; if ((n = Dialog->GetAttr("ref"))) { int Ref = atoi(n); Str = Res->StrFromRef(Ref); } if ((n = Dialog->GetAttr("pos"))) { auto T = LString(n).SplitDelimit(","); if (T.Length() == 4) { Pos.x1 = atoi(T[0]); Pos.y1 = atoi(T[1]); Pos.x2 = atoi(T[2]); Pos.y2 = atoi(T[3]); } } } return true; } ///////////////////////////////////////////////////////////////////////////////////////// LMenuRes::LMenuRes(LResources *res) { Res = res; Tag = 0; } LMenuRes::~LMenuRes() { Strings.DeleteObjects(); DeleteObj(Tag); } bool LMenuRes::Read(LXmlTag *t, ResFileFormat Format) { Tag = t; if (t && stricmp(t->GetTag(), "menu") == 0) { char *n; if ((n = t->GetAttr("name"))) { LBase::Name(n); } for (auto c: t->Children) { if (stricmp(c->GetTag(), "string-group") == 0) { for (auto i: c->Children) { LStringRes *s = new LStringRes(Res); if (s && s->Read(i, Format)) { LAssert(!Strings.Find(s->Ref)); // If this fires the string has a dupe ref Strings.Add(s->Ref, s); } else { DeleteObj(s); } } break; } } return true; } return false; } ///////////////////////////////////////////////////////////////////////////////////////// // Dialog bool LResourceLoad::LoadFromResource(int Resource, LViewI *Parent, LRect *Pos, LAutoString *Name, char *TagList) { LgiGetResObj(); for (int i=0; iLoadDialog(Resource, Parent, Pos, Name, 0, TagList)) return true; } return false; } //////////////////////////////////////////////////////////////////////// // Get language LLanguage *LGetLanguageId() { // Check for command line override char Buf[64]; if (LAppInst->GetOption("i", Buf)) { LLanguage *i = LFindLang(Buf); if (i) return i; } // Check for config setting auto LangId = LAppInst->GetConfig("Language"); if (LangId) { LLanguage *l = LFindLang(LangId); if (l) return l; } // Use a system setting #if defined WIN32 int Lang = GetSystemDefaultLCID(); TCHAR b[256]; if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTLANGUAGE, b, CountOf(b)) > 0) { Lang = htoi(b); } int Primary = PRIMARYLANGID(Lang); int Sub = SUBLANGID(Lang); LLanguage *i, *English = 0; // Search for exact match for (i = LFindLang(NULL); i->Id; i++) { if (i->Win32Id == Lang) return i; } // Search for PRIMARYLANGID match for (i = LFindLang(NULL); i->Id; i++) { if (PRIMARYLANGID(i->Win32Id) == PRIMARYLANGID(Lang)) return i; } #elif defined(LINUX) && !defined(LGI_SDL) LLibrary *WmLib = LAppInst->GetWindowManagerLib(); if (WmLib) { Proc_LgiWmGetLanguage GetLanguage = (Proc_LgiWmGetLanguage) WmLib->GetAddress("LgiWmGetLanguage"); char Lang[256]; if (GetLanguage && GetLanguage(Lang)) { LLanguage *l = LFindLang(Lang); if (l) return l; } } return LFindLang("en"); /* // Read in KDE language setting char Path[256]; if (LgiGetSystemPath(LSP_HOME, Path, sizeof(Path))) { LMakePath(Path, sizeof(Path), Path, ".kde/share/config/kdeglobals"); char *Txt = LReadTextFile(Path); if (Txt) { extern bool _GetIniField(char *Grp, char *Field, char *In, char *Out, int OutSize); char Lang[256] = ""; LLanguage *Ret = 0; if (_GetIniField("Locale", "Language", Txt, Lang, sizeof(Lang))) { LToken Langs(Lang, ":,; \t"); for (int i=0; iStringFromId(Res); } if (s) return s; return Default; } bool LResources::LoadDialog(int Resource, LViewI *Parent, LRect *Pos, LAutoString *Name, LEventsI *Engine, char *TagList) { bool Status = false; if (Resource) { ScriptEngine = Engine; TagHash Tags(TagList); for (auto Dlg: Dialogs) { if (Dlg->Id() == ((int) Resource)) { // LProfile p("LoadDialog"); // found the dialog to load, set properties if (Dlg->Name()) { if (Name) Name->Reset(NewStr(Dlg->Name())); else if (Parent) Parent->Name(Dlg->Name()); } // p.Add("1"); int x = Dlg->X(); int y = Dlg->Y(); x += LAppInst->GetMetric(LGI_MET_DECOR_X) - 4; y += LAppInst->GetMetric(LGI_MET_DECOR_Y) - 18; if (Pos) { Pos->ZOff(x, y); // Do some scaling for the monitor's DPI LPoint Dpi; auto *Wnd = Parent ? Parent->GetWindow() : NULL; if (Wnd) Dpi = Wnd->GetDpi(); else { - LArray Displays; + LArray Displays; if (LGetDisplays(Displays)) { for (auto d: Displays) { if (d->r.Overlap(Pos) && d->Dpi.x) Dpi = d->Dpi; } Displays.DeleteObjects(); } } if (Dpi.x) { LPointF s((double)Dpi.x / 96.0, (double)Dpi.y / 96.0); Pos->SetSize( (int) (s.x * Pos->X()), (int) (s.y * Pos->Y())); } } else if (Parent && stricmp(Parent->GetClass(), "LTabPage")) { LRect r = Parent->GetPos(); r.SetSize(x, y); Parent->SetPos(r); } // instantiate control list // p.Add("2"); for (auto t: Dlg->Dialog->Children) { ResObject *Obj = CreateObject(t, 0); if (Obj) { // p.Add("3"); if (Tags.Check(t)) { if (Res_Read(Obj, t, Tags)) { LView *w = dynamic_cast(Obj); if (w) Parent->AddView(w); } else { LgiMsg( NULL, LLoadString( L_ERROR_RES_RESOURCE_READ_FAILED, "Resource read error, tag: %s"), "LResources::LoadDialog", MB_OK, t->GetTag()); break; } } } else { LAssert(0); } } Status = true; break; } } } return Status; } ////////////////////////////////////////////////////////////////////// // Menus LStringRes *LMenuRes::GetString(LXmlTag *Tag) { if (Tag) { char *n = Tag->GetAttr("ref"); if (n) { int Ref = atoi(n); LStringRes *s = Strings.Find(Ref); if (s) return s; } } return 0; } bool LMenuLoader::Load(LMenuRes *MenuRes, LXmlTag *Tag, ResFileFormat Format, TagHash *TagList) { bool Status = false; if (!Tag) { LgiTrace("%s:%i - No tag?", _FL); return false; } if (Tag->IsTag("menu")) { #if WINNATIVE if (!Info) { Info = ::CreateMenu(); } #endif } #if WINNATIVE if (Info) #endif { Status = true; for (auto t: Tag->Children) { if (!Status) break; if (t->IsTag("submenu")) { LStringRes *Str = MenuRes->GetString(t); if (Str && Str->Str) { bool Add = !TagList || TagList->Check(Str->Tag); LSubMenu *Sub = AppendSub(Str->Str); if (Sub) { auto p = Sub->GetParent(); if (p) p->Id(Str->Id); else LgiTrace("%s:%i - No Parent to set menu item ID.\n", _FL); Status = Sub->Load(MenuRes, t, Format, TagList); if (!Add) { // printf("Sub->GetParent()=%p this=%p\n", Sub->GetParent(), this); Sub->GetParent()->Remove(); delete Sub->GetParent(); } } } else { LAssert(0); } } else if (t->IsTag("menuitem")) { if (t->GetAsInt("sep") > 0) { AppendSeparator(); } else { LStringRes *Str = MenuRes->GetString(t); if (Str && Str->Str) { if (!TagList || TagList->Check(Str->Tag)) { int Enabled = t->GetAsInt("enabled"); char *Shortcut = t->GetAttr("shortcut"); Status = AppendItem(Str->Str, Str->Id, Enabled != 0, -1, Shortcut) != 0; } else Status = true; } else { LAssert(0); } } } } } return Status; } bool LMenu::Load(LView *w, const char *Res, const char *TagList) { bool Status = false; LResources *r = LgiGetResObj(); if (r) { TagHash Tags(TagList); for (auto m: r->Menus) { if (stricmp(m->Name(), Res) == 0) { #if WINNATIVE Status = LSubMenu::Load(m, m->Tag, r->GetFormat(), &Tags); #else Status = LMenuLoader::Load(m, m->Tag, r->GetFormat(), &Tags); #endif break; } } } return Status; } LResources *LgiGetResObj(bool Warn, const char *filename, bool LoadOnDemand) { #if DEBUG_RES_FILE LgiTrace("LgiGetResObj(%i,%s,%i)\n", Warn, filename, LoadOnDemand); #endif // Look for existing file? if (filename && LoadOnDemand) { for (auto r: ResourceOwner) if (!Stricmp(filename, r->GetFileName())) return r; // Load the new file... return new LResources(filename, Warn); } if (ResourceOwner.Length()) return ResourceOwner[0]; if (!LoadOnDemand) return NULL; return new LResources(filename, Warn); } diff --git a/src/common/Text/Text.cpp b/src/common/Text/Text.cpp --- a/src/common/Text/Text.cpp +++ b/src/common/Text/Text.cpp @@ -1,1596 +1,1596 @@ /*hdr ** FILE: Text.cpp ** AUTHOR: Matthew Allen ** DATE: 2/7/97 ** DESCRIPTION: Generic text document handler ** ** Copyright (C) 1997, Matthew Allen ** fret@memecode.com */ /****************************** Includes ************************************************************************************/ #include #include #include #include "Gdc2.h" #include "Text.h" /****************************** Defines *************************************************************************************/ // Cursor type // if the cursor goes between charaters (like in graphics mode) #define CURSOR_BETWEEN // else the cursor is at or under a charater (like in text mode) //#define CURSOR_UNDER /****************************** Locals **************************************************************************************/ char Delimiters[] = "!@#$%^&*()'\":;,.<>/?[]{}-=+\\|`~"; bool bAutoIndent = TRUE; ushort CodePage_ISO_8859_2[] = { /* 0x80-8F */ 0x20AC, 0, 0x201A, 402, 0x201E, 0x2026, 0x2020, 0x2021, 0x5e, 0x2030, 0x160, 0x2039, 338, 0, 0, 0, /* 0x90-9F */ 0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x7E, 0x2122, 0x161, 0x203A, 339, 0, 0, 376, /* 0xA0-AF */ 0xA0, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, /* 0xB0-BF */ 176, 177, 178, 179, 180, 318, 347, 728, 184, 353, 351, 357, 378, 732, 382, 380, /* 0xC0-CF */ 340, 195, 194 /* 0xD0-DF */ /* 0xE0-EF */ /* 0xF0-FF */ }; ushort CodePage_WIN_1250[] = { /* 0x80-8F */ 0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179, /* 0x90-9F */ 0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A, /* 0xA0-AF */ 0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B, /* 0xB0-BF */ 0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C, /* 0xC0-CF */ 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, /* 0xD0-DF */ 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, /* 0xE0-EF */ 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, /* 0xF0-FF */ 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 }; ushort *CodePages[] = {0, 0, CodePage_WIN_1250}; /****************************** Helper Functions ****************************************************************************/ int StrLen(char *c) { int i = 0; if (c) for (i=0; *c AND *c != '\n'; i++, c++); return i; } int RevStrLen(char *c) { int i = 0; if (c) for (i=0; *c AND *c != '\n'; i++, c--); return i; } void CountLinesAndLen(char *c, int Len, int &EndLen, int &Lines) { EndLen = 0; Lines = 0; if (c) { for (; Len>0; Len--, c++) { if (*c == '\n') { EndLen = 0; Lines++; } else { EndLen++; } } } } /****************************** Document Class ******************************************************************************/ Document::Document() { File = 0; } Document::~Document() { DeleteArray(File); } bool Document::Open(char *FileName, int Attrib) { bool Status = FALSE; if (FileName) { char *FName = NewStr(FileName); DeleteObj(File); File = FName; Status = F.Open(File, Attrib); } return Status; } /****************************** UserAction Class ****************************************************************************/ UserAction::UserAction() { Text = 0; x = y = 0; Insert = false; } UserAction::~UserAction() { DeleteArray(Text); } /****************************** Cursor Class ********************************************************************************/ LCursor::LCursor() { x = 0; y = 0; Parent = 0; Offset = 0; } LCursor::~LCursor() { } LCursor::operator char*() { return Parent->Data + (Offset + x); } char *LCursor::GetStr() { if (Parent) { return Parent->Data + Offset; } return NULL; } void LCursor::GoUpLine() { if (y > 0) { char *c = Parent->Data + Offset; if (y > 1) { y--; c -= 2; x = Length = RevStrLen(c); Offset = ((long) c - (long) Parent->Data) - x + 1; } else { Offset = 0; x = Length = StrLen(Parent->Data); y = 0; } } else { x = 0; } } void LCursor::GoDownLine() { if (y < Parent->Lines-1) { char *c = Parent->Data + (Offset + Length); y++; x = 0; c++; Length = StrLen(c); Offset = (long) c - (long) Parent->Data; } else { x = Length; } } bool LCursor::AtEndOfLine() { return x == Length; } bool LCursor::AtBeginningOfLine() { return x == 0; } void LCursor::SetX(int X) { x = limit(X, 0, Length); } void LCursor::SetY(int Y) { Y = limit(Y, 0, Parent->Lines-1); char *c = Parent->FindLine(Y); if (c) { Offset = (long) c - (long) Parent->Data; Length = StrLen(c); y = Y; } } void LCursor::MoveX(int Dx) { char *c = Parent->Data + Offset; while (Dx) { if (Dx < 0) { if (x) { x--; } else { GoUpLine(); } } else { if (c[x]) { if (c[x] != '\n') { x++; } else { GoDownLine(); } } else { return; } } Dx += (Dx > 0) ? -1 : 1; } } void LCursor::MoveY(int Dy) { while (Dy) { if (Dy < 0) { GoUpLine(); Dy++; } else { GoDownLine(); Dy--; } } } int LCursor::CursorX(int TabSize) { char *c = Parent->Data + Offset; int cx = 0; for (int i=0; i= StartLine AND i < StartLine + Lines) { return LineW[i - StartLine]; } return 0; } char *TextLock::operator [](int i) { if (Line AND i >= StartLine AND i < StartLine + Lines) { return Line[i - StartLine]; } return 0; } /****************************** TextDocument Class **************************************************************************/ TextDocument::TextDocument() { Flags = 0; LockCount = 0; Dirty = false; Editable = true; Lines = 1; Length = 0; Alloc = 1; Data = new char[1]; if (Data) *Data = 0; IgnoreUndo = 0; UndoPos = 0; } TextDocument::~TextDocument() { DeleteArray(Data); ClearUndoQueue(); } int TextDocument::CountLines(char *c, int Len) { int L = 0; if (Length > 0) { if (Len >= 0) { while (*c AND Len) { L += (*c++ == '\n'); Len--; } L += (Len > 0 AND *c == 0) ? 1 : 0; } else { while (*c) { L += (*c++ == '\n'); } L++; } } return L; } char *TextDocument::FindLine(int i) { char *c = 0; if (Length > 0) { c = Data; while (i) { for (; *c AND *c != '\n'; c++); if (*c) c++; else return 0; i--; } } return c; } bool TextDocument::SetLength(int Len) { bool Status = TRUE; if (Len + 1 > Alloc) { int NewAlloc = (Len + TEXT_BLOCK) & (~TEXT_MASK); char *Temp = new char[NewAlloc]; if (Temp) { memset(Temp + Length, 0, NewAlloc - Length); memcpy(Temp, Data, Length); DeleteArray(Data); Data = Temp; Length = Len; Alloc = NewAlloc; } else { Status = FALSE; } } else { // TODO: resize Data to be smaller if theres a // considerable size change Length = Len; } return Status; } bool TextDocument::Load(char *File) { bool Status = FALSE; if (Open(File, O_READ)) { int FileSize = F.GetSize(); if (SetLength(FileSize)) { F.Read(Data, FileSize); CrLf = FALSE; char *In = Data, *Out = Data; int Size = 0; for (int i=0; iStartLine = limit(StartLine, 0, Lines); Lock->Lines = min(Ls, Lines - StartLine); ushort *CodePage = (CodePageId < TVCP_MAX) ? CodePages[CodePageId] : 0; if (CodePage) { Lock->LineW = new ushort*[Lock->Lines]; } else { Lock->Line = new char*[Lock->Lines]; } char *c = FindLine(StartLine); if (Lock->Line) { for (int i=0; iLines; i++) { if (c) { if (CodePage) { char *Eol = c + StrLen(c); int Len = (int)Eol-(int)c; Lock->LineW[i] = new ushort[Len + 1]; if (Lock->LineW[i]) { for (int n=0; nLineW[i][n] = (c[n] & 0x80) ? CodePage[n-0x80] : c[n]; } Lock->LineW[i][Len] = 0; } } else { Lock->Line[i] = (*c) ? c : 0; } for (; *c AND *c != '\n'; c++); if (*c) c++; } else { if (CodePage) { Lock->LineW[i] = 0; } else { Lock->Line[i] = 0; } } } LockCount++; Status = TRUE; } } return Status; } void TextDocument::UnLock(TextLock *Lock) { LockCount = max(LockCount-1, 0); } bool TextDocument::MoveLock(TextLock *Lock, int Dy) { bool Status = FALSE; if (Lock) { } return Status; } bool TextDocument::CursorCreate(LCursor *c, int X, int Y) { bool Status = FALSE; if (c) { char *StartOfLine = FindLine(Y); c->Parent = this; c->y = Y; c->Length = StrLen(StartOfLine); c->Offset = (StartOfLine) ? ((long) StartOfLine - (long) Data) : 0; c->x = min(X, c->Length); Status = TRUE; } return Status; } bool TextDocument::Insert(LCursor *At, char *Text, int Len) { bool Status = FALSE; if (Text AND Editable) { if (Len < 0) { Len = strlen(Text); } int OldLength = Length; if (SetLength(Length + Len)) { int Offset = (At) ? At->Offset + At->x : 0; // save undo info if (!IgnoreUndo) { TruncateQueue(); // add new undo info in if (UserAction *a = new UserAction) { a->Text = NewStr(Text, Len); a->x = At->x; a->y = At->y; a->Insert = true; Queue.Insert(a); UndoPos = Queue.GetItems(); } } memmove(Data + Offset + Len, Data + Offset, OldLength - Offset + 1); memcpy(Data + Offset, Text, Len); if (At) { At->Length = StrLen(Data + At->Offset); } Lines += CountLines(Text, Len); Status = true; Dirty = true; } } return Status; } bool TextDocument::Delete(LCursor *From, int Len, char *Buffer) { bool Status = FALSE; if (From AND Editable) { int Offset = From->Offset + From->x; Len = min(Length - Offset, Len); // -1 is for NULL terminator if (Len > 0) { char *c = Data + Offset; if (!IgnoreUndo) { TruncateQueue(); // add new undo info in if (UserAction *a = new UserAction) { a->Text = NewStr(c, Len); a->x = From->x; a->y = From->y; a->Insert = false; Queue.Insert(a); UndoPos = Queue.GetItems(); } } if (Buffer) { memcpy(Buffer, c, Len); } Lines -= CountLines(c, Len); memmove(c, c + Len, Length - Offset - Len + 1); Status = SetLength(Length - Len); From->Length = StrLen(Data + From->Offset); Dirty = TRUE; } } return Status; } void TextDocument::TruncateQueue() { // delete undo information after current pos int Num = Queue.GetItems() - UndoPos; for (int i=0; ix, a->y)) { if (a->Insert ^ Reverse) { // delete text Delete(&c, strlen(a->Text)); } else { // insert text Insert(&c, a->Text); } } IgnoreUndo--; } } void TextDocument::Undo(LCursor &c) { if (UndoPos > 0) { ApplyAction(Queue.ItemAt(UndoPos - 1), c, false); UndoPos--; } } void TextDocument::Redo(LCursor &c) { if (UndoPos < Queue.GetItems()) { ApplyAction(Queue.ItemAt(UndoPos), c, true); UndoPos++; } } void TextDocument::ClearUndoQueue() { for (UserAction *a = Queue.First(); a; a = Queue.Next()) { DeleteObj(a); } Queue.Empty(); UndoPos = 0; } bool TextDocument::UndoAvailable(bool Redo) { return false; } /****************************** TextView Class ******************************************************************************/ TextView::TextView() { Flags = 0; HiddenLines = 0; DisplayLines = 1; ClipData = NULL; StatusMsg = NULL; - WrapType = TEXTED_WRAP_NONE; + WrapType = L_WRAP_NONE; CodePage = TVCP_US_ASCII; Doc.CursorCreate(&User, 0, 0); } TextView::~TextView() { DeleteArray(ClipData); DeleteArray(StatusMsg); } void TextView::SetStatus(char *Msg) { if (Msg) { DeleteArray(StatusMsg); StatusMsg = new char[strlen(Msg)+1]; if (StatusMsg) { strcpy(StatusMsg, Msg); } } } bool TextView::ClipText(char *Str, int Len) { bool Status = FALSE; if (Str) { if (Len < 0) { Len = strlen(Str); } DeleteArray(ClipData); ClipData = new char[Len+1]; if (ClipData) { memcpy(ClipData, Str, Len); ClipData[Len] = 0; Status = TRUE; } } return Status; } char *TextView::ClipText() { return ClipData; } int TextView::ProcessKey(LKey &K) { Flags &= ~(TVF_GOTO_START | TVF_GOTO_END | TVF_EAT_MOVE); if (!K.IsChar) { if (K.Shift()) { Flags |= TVF_SHIFT; } else { Flags &= ~(TVF_SHIFT); } if (K.Down()) { switch (K.c) { case VK_ESCAPE: { OnEscape(K); break; } case VK_RETURN: { OnEnter(K); break; } case VK_BACK: { if (K.Alt()) { if (K.Ctrl()) { Doc.Redo(User); } else { Doc.Undo(User); } Dirty(TVF_DIRTY_ALL); } else { if (Flags & TVF_SELECTION) { OnDeleteSelection(FALSE); UpdateHiddenCheck(); } else if (User.Y() > 0 || User.X() > 0) { Flags &= ~(TVF_SHIFT); OnMoveCursor(-1, 0); OnDeleteText(&User, 1, FALSE); } } break; } case VK_RIGHT: case VK_LEFT: { bool bLeft = K.c == VK_LEFT; int Inc = (bLeft) ? -1 : 1; if (bLeft) { Flags |= TVF_GOTO_START; } else { Flags |= TVF_GOTO_END; } Flags |= TVF_EAT_MOVE; if (K.Ctrl()) { char *c = User; if (bLeft) { OnMoveCursor(Inc, 0); c = User; while ( strchr(WhiteSpace, *c) AND OnMoveCursor(Inc, 0)) { c = User; } } if (strchr(Delimiters, *c)) { while ( strchr(Delimiters, *c) AND OnMoveCursor(Inc, 0)) { c = User; } } else { // IsText(*c) while ( !strchr(WhiteSpace, *c) AND OnMoveCursor(Inc, 0)) { c = User; } } if (bLeft) { if (User.Y() > 0 || User.X() > 0) { OnMoveCursor(-Inc, 0); } } else { while ( strchr(WhiteSpace, *c) AND OnMoveCursor(Inc, 0)) { c = User; } } } else { OnMoveCursor(Inc, 0); } break; } case VK_UP: { Flags |= TVF_GOTO_START; OnMoveCursor(0, -1); break; } case VK_DOWN: { Flags |= TVF_GOTO_END; OnMoveCursor(0, 1); break; } case VK_PRIOR: { int Move = 1-DisplayLines; OnSetHidden(HiddenLines+Move); OnMoveCursor(0, Move); break; } case VK_NEXT: { int Move = DisplayLines-1; OnSetHidden(HiddenLines+Move); OnMoveCursor(0, Move); break; } case VK_HOME: { if (K.Ctrl()) { OnSetCursor(0, 0); } else { OnMoveCursor(-User.X(), 0); } break; } case VK_END: { if (K.Ctrl()) { OnSetCursor(1024, Doc.GetLines()); } else { OnMoveCursor(User.LineLength()-User.X(), 0); } break; } case VK_DELETE: { if ( K.Shift() AND (Flags & TVF_SELECTION)) { Cut(); } else { if (Flags & TVF_SELECTION) { OnDeleteSelection(FALSE); UpdateHiddenCheck(); } else if ( User.Y() < Doc.GetLines() || User.X() < User.LineLength()) { OnDeleteText(&User, 1, FALSE); } } break; } case VK_INSERT: { if (Flags & TVF_SELECTION) { if (K.Ctrl()) { Copy(); } } if (K.Shift()) { Paste(); } break; } } } } else { // IsChar #define EXTENDED (1<<24) // if ( !(K.Data & EXTENDED)) { bool InsertChar = K.c >= ' '; // AND K.c < 128 if (K.c == 9) { if ((Flags & TVF_SELECTION) AND Start.Y() != End.Y()) { if (Flags & TVF_SHIFT) { InsertChar = !OnMultiLineTab(false); } else { InsertChar = !OnMultiLineTab(true); } } else { InsertChar = true; } } if (InsertChar) { char Char[2] = " "; Char[0] = K.c; if (OnInsertText(Char)) { OnMoveCursor(1, 0, true); } } } } return TRUE; } bool TextView::IsDirty() { return Flags & TVF_DIRTY_MASK; } void TextView::Dirty(int Type) { Flags |= Type; } void TextView::Clean() { Flags &= ~TVF_DIRTY_MASK; } bool TextView::OnInsertText(char *Text, int Len) { bool Status = false; OnDeleteSelection(FALSE); int OldLines = Doc.GetLines(); Status = Doc.Insert(&User, Text, Len); if (Status) { if (OldLines != Doc.GetLines()) { Dirty(TVF_DIRTY_TO_EOP); } else { Dirty(TVF_DIRTY_TO_EOL); } AfterInsertText(Text, Len); } return Status; } bool TextView::OnDeleteText(LCursor *c, int Len, bool Clip) { bool Status = false; if (Clip) { ClipText((char*) *c, Len); } int OldLines = Doc.GetLines(); Status = Doc.Delete(c, Len, (Clip) ? ClipText() : 0); if (Status) { if (Clip) { Dirty(TVF_DIRTY_ALL); } else { if (OldLines != Doc.GetLines()) { Dirty(TVF_DIRTY_TO_EOP); } else { Dirty(TVF_DIRTY_TO_EOL); } } AfterDeleteText(); } return Status; } bool TextView::UpdateHiddenCheck() { bool Status = FALSE; if (User.Y() < HiddenLines) { HiddenLines = User.Y(); Dirty(TVF_DIRTY_ALL); Status = TRUE; } if (User.Y() >= HiddenLines + DisplayLines) { HiddenLines = User.Y() - DisplayLines + 1; Dirty(TVF_DIRTY_ALL); Status = TRUE; } return Status; } bool TextView::OnMoveCursor(int Dx, int Dy, bool NoSelect) { bool Status = FALSE; bool StartSelect = (Flags & TVF_SHIFT) AND !(Flags & TVF_SELECTION); bool EndSelect = !(Flags & TVF_SHIFT) AND (Flags & TVF_SELECTION); LCursor Old = User; if (EndSelect) { LCursor S, E; if (Start < End) { S = Start; E = End; } else { S = End; E = Start; } if (Flags & TVF_GOTO_START) { User = S; } if (Flags & TVF_GOTO_END) { User = E; } if (Flags & TVF_EAT_MOVE) { goto AfterMove; } } if (Dy) { int OldX = User.X(); User.MoveY(Dy); User.SetX(OldX); } else { User.MoveX(Dx); } if (Old != User || EndSelect) { AfterMove: if (!NoSelect) { if (StartSelect) { OnStartSelection(&Old); } if (EndSelect) { OnEndSelection(); } } Dirty(TVF_DIRTY_CURSOR); UpdateHiddenCheck(); if (Flags & TVF_SELECTION) { Dirty(TVF_DIRTY_SELECTION); End = User; } Status = TRUE; } return Status; } void TextView::OnSetHidden(int Hidden) { int NewHidden = limit(Hidden, 0, Doc.GetLines()-1); if (NewHidden != HiddenLines) { HiddenLines = NewHidden; Dirty(TVF_DIRTY_ALL); } } void TextView::OnSetCursor(int X, int Y) { LCursor Old = User; User.SetY(Y); User.SetX(X); if (Old != User) { Dirty(TVF_DIRTY_CURSOR); UpdateHiddenCheck(); if (Flags & TVF_SELECTION) { Dirty(TVF_DIRTY_SELECTION); End = User; } } } void TextView::OnStartSelection(LCursor *c) { Start = *c; End = *c; Flags |= TVF_SELECTION; Dirty(TVF_DIRTY_SELECTION); } void TextView::OnEndSelection() { Flags &= ~TVF_SELECTION; Dirty(TVF_DIRTY_ALL); } void TextView::OnDeleteSelection(bool Clip) { if (Flags & TVF_SELECTION) { LCursor S; int Len; if (Start < End) { S = Start; Len = End - Start; } else { S = End; #ifdef CURSOR_UNDER S.MoveX(1); #endif Len = Start - End; } if (Len > 0) { User = S; OnDeleteText(&User, Len, Clip); } OnEndSelection(); } } bool TextView::Open(char *Name) { Doc.Load(Name); return Doc.CursorCreate(&User, 0, 0); } bool TextView::Save(char *Name) { return Doc.Save(Name); } bool TextView::Cut() { if (Flags & TVF_SELECTION) { OnDeleteSelection(TRUE); UpdateHiddenCheck(); return TRUE; } return FALSE; } bool TextView::Copy() { if (Flags & TVF_SELECTION) { LCursor S; int Len; if (Start < End) { S = Start; Len = End - Start; } else { S = End; Len = Start - End; } ClipText((char*) S, Len); return TRUE; } return FALSE; } bool TextView::Paste() { if (ClipText()) { char *Text = ClipText(); // Insert text into document OnInsertText(Text); // Calculate the end point of the inserted text int X, Y; CountLinesAndLen(Text, strlen(Text), X, Y); // Make the cursor point to the end of the inserted text if (Y > 0) { User.MoveY(Y); User.SetX(X); } else { User.MoveX(X); } } OnEndSelection(); UpdateHiddenCheck(); return TRUE; } bool TextView::ClearDirty(bool Ask) { return FALSE; } void TextView::OnSave() { ClearDirty(FALSE); } void TextView::OnGotoLine() { } void TextView::OnEscape(LKey &K) { LCloseApp(); } void TextView::OnEnter(LKey &K) { OnInsertText("\n"); OnMoveCursor(1, 0, TRUE); if (bAutoIndent) { LCursor S = User; S.MoveY(-1); S.SetX(0); char *c = S; int Len = 0; while (IsWhite(*c) AND Len < S.LineLength()) { Len++; c++; } if (Len > 0) { OnInsertText(S, Len); OnMoveCursor(Len, 0, TRUE); } } } ushort *TextView::GetCodePageMap(int Page) { if (Page < 0) Page = CodePage; if (Page >= 0 AND Page < TVCP_MAX) { return CodePages[Page]; } return 0; } diff --git a/src/common/Text/TextView3.cpp b/src/common/Text/TextView3.cpp --- a/src/common/Text/TextView3.cpp +++ b/src/common/Text/TextView3.cpp @@ -1,5440 +1,5440 @@ #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView3.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #ifdef WIN32 #include #endif #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #undef max #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms #define PULSE_TIMEOUT 500 // ms #define CURSOR_BLINK 1000 // ms #define ALLOC_BLOCK 64 #define IDC_VS 1000 #ifdef WINDOWS #define DOUBLE_BUFFER_PAINT 1 #endif enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class LDocFindReplaceParams3 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase; bool MatchWord; bool SelectionOnly; bool SearchUpwards; LDocFindReplaceParams3() : LMutex("LDocFindReplaceParams3") { MatchCase = false; MatchWord = false; SelectionOnly = false; SearchUpwards = false; } }; class LTextView3Private : public LCss, public LMutex { public: LTextView3 *View; LRect rPadding; int PourX; bool LayoutDirty; ssize_t DirtyStart, DirtyLen; LColour UrlColour; bool CenterCursor; ssize_t WordSelectMode; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache; // Find/Replace Params bool OwnFindReplaceParams; LDocFindReplaceParams3 *FindReplaceParams; // Map buffer ssize_t MapLen; char16 *MapBuf; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView3Private(LTextView3 *view) : LMutex("LTextView3Private") { View = view; WordSelectMode = -1; PourX = -1; VScrollCache = -1; DirtyStart = DirtyLen = 0; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); CenterCursor = false; LayoutDirty = true; rPadding.ZOff(0, 0); MapBuf = 0; MapLen = 0; OwnFindReplaceParams = true; FindReplaceParams = new LDocFindReplaceParams3; } ~LTextView3Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView3Undo : public LUndoEvent { LTextView3 *View; LArray Changes; LTextView3Undo(LTextView3 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView3::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView3::LTextView3( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView3Private(this)); TabSize = TAB_SIZE; IndentSize = TAB_SIZE; // setup window SetId(Id); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #else #endif d->Padding(LCss::Len(LCss::LenPx, 2)); #ifdef _DEBUG // debug times _PourTime = 0; _StyleTime = 0; _PaintTime = 0; #endif // Data Alloc = ALLOC_BLOCK; Text = new char16[Alloc]; if (Text) *Text = 0; Cursor = 0; Size = 0; // Display if (FontType) Font = FontType->Create(); else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView3::~LTextView3() { #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView3::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView3::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView3::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView3::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView3::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView3::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); - CanScrollX = i != TEXTED_WRAP_REFLOW; + CanScrollX = i != L_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView3::GetFont() { return Font; } LFont *LTextView3::GetBold() { return Bold; } void LTextView3::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView3::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } void LTextView3::LogLines() { int Idx = 0; LgiTrace("DocSize: %i\n", (int)Size); for (auto i : Line) { LgiTrace(" [%i]=%p, %i+%i, %s\n", Idx, i, (int)i->Start, (int)i->Len, i->r.GetStr()); Idx++; } #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif } bool LTextView3::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto i : Line) { LTextLine *l = i; if (l->Start != Pos) { LogLines(); LAssert(!"Incorrect start."); return false; } char16 *e = c; - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { LogLines(); LAssert(!"Incorrect length."); return false; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { LogLines(); LAssert(!"Lines not joined vertically"); } if (*e) { if (*e == '\n') e++; - else if (WrapType == TEXTED_WRAP_REFLOW) + else if (WrapType == L_WRAP_REFLOW) e++; } Pos = e - Text; c = e; Idx++; Prev = l; } - if (WrapType == TEXTED_WRAP_NONE && + if (WrapType == L_WRAP_NONE && Pos != Size) { LogLines(); LAssert(!"Last line != end of doc"); return false; } return true; } int LTextView3::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView3::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif #if !defined(HAIKU) LAssert(InThread()); #endif LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); // LgiTrace("Pour %i:%i Cur=%p Idx=%i\n", (int)Start, (int)Length, (int)Cur, (int)Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (auto i = Idx >= 0 ? Line.begin(Idx) : Line.rbegin(); *i; i--, Idx--) { Cur = *i; if (Cur->r.Valid()) { Cy = Cur->r.y1; if (Idx < 0) Idx = Line.IndexOf(Cur); break; } } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; // LgiTrace("Reset start to %i:%i because Cur!=NULL\n", (int)Start, (int)Length); } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; //int LastX = 0; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (auto i = Line.begin(Idx); *i; i++, Idx++) { LTextLine *l = *i; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine; l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } Line.Insert(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewLStr(); #endif PartialPour = false; PartialPourLines = 0; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); Cur = NULL; } int Cx = 0; ssize_t i; for (i=Start; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine; if (l) { l->Start = i; l->Len = e - i; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; PartialPourLines = std::max(PartialPourLines, Line.Length()); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) { PartialPour = false; PartialPourLines = 0; } SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG // ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || Last->Start + Last->Len < Size) { LTextLine *l = new LTextLine; if (l) { l->Start = Size; l->Len = 0; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Insert(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); - d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; + d->LayoutDirty = WrapType != L_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { #if 0 LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); #endif SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView3::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView3::LStyle *LTextView3::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView3::LStyle *LTextView3::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView3::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; // Url->Email = Inf.Email; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView3::Insert(size_t At, const char16 *Data, ssize_t Len) { LProfile Prof("LTextView3::Insert"); Prof.HideResultsIfBelow(1000); LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } Prof.Add("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... memmove(Text+(At+Len), Text+At, (Size-At) * sizeof(char16)); Prof.Add("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate Prof.Add("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Insert(Cur = new LTextLine); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); Prof.Add("NoWrap add lines"); // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; // Create a new line... Cur = new LTextLine(); if (!Cur) return false; Cur->Start = Pos + 1; Line.Insert(Cur, ++Idx); } } Prof.Add("CalcLen"); // Make sure the last Line's length is set.. Cur->CalcLen(Text); Prof.Add("UpdatePos"); // Now update all the positions of the following lines... for (auto i = Line.begin(++Idx); *i; i++) (*i)->Start += Len; } else { // Clear all lines to the end of the doc... for (auto i = Line.begin(Idx); *i; i++) delete *i; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG // Prof.Add("Validate"); // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { Prof.Add("PourText"); PourText(At, Len); Prof.Add("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView3::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (auto i = Line.begin(Idx + 1); *i; i++) (*i)->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (auto i = Line.begin(Index); *i; i++) delete *i; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG // ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change - if (WrapType == TEXTED_WRAP_REFLOW) + if (WrapType == L_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView3::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } List::I LTextView3::GetTextLineIt(ssize_t Offset, ssize_t *Index) { int i = 0; for (auto It = Line.begin(); It != Line.end(); It++) { auto l = *It; if (Offset >= l->Start && Offset <= l->Start+l->Len) { if (Index) *Index = i; return It; } i++; } return Line.end(); } int64 LTextView3::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView3::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView3::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView3::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView3::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView3::NameW() { return Text; } const char16 *LTextView3::TextAtLine(size_t Index) { if (Index >= Line.Length()) return NULL; auto ln = Line[Index]; return Text + ln->Start; } bool LTextView3::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView3::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView3::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView3::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView3::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView3::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView3::GetLines() { return Line.Length(); } void LTextView3::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView3::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView3::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView3::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView3::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView3::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; auto SLine = GetTextLine(Start); auto ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView3::SetBorder(int b) { } bool LTextView3::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView3::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView3::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } void LTextView3::ClearDirty(std::function OnStatus, bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { auto DoSave = [this, OnStatus](bool ok, const char *FileName) { Save(FileName); if (OnStatus) OnStatus(ok); }; if (!FileName) { LFileSelect *Select = new LFileSelect; Select->Parent(this); Select->Save([FileName=LString(FileName), DoSave](auto s, auto ok) { if (ok) DoSave(ok, s->Name()); else DoSave(ok, FileName); delete s; }); } else DoSave(true, FileName); } else if (Answer == IDCANCEL) { if (OnStatus) OnStatus(false); return; } } if (OnStatus) OnStatus(true); } bool LTextView3::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView3::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView3::GetLastError() { return d->LastError; } void LTextView3::UpdateScrollBars(bool Reset) { if (!VScroll) return; LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = std::max(PartialPourLines, Line.Length()); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } void LTextView3::DoCase(std::function Callback, bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView3Undo(this)); LTextView3Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } if (Callback) Callback(Text != NULL); } ssize_t LTextView3::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } void LTextView3::SetLine(int64_t i, bool select) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, select); d->CenterCursor = false; } } void LTextView3::DoGoto(std::function Callback) { LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); Dlg->DoModal([this, Dlg, Callback](auto d, auto code) { auto ok = code == IDOK && Dlg->GetStr(); if (ok) SetLine(Dlg->GetStr().Int()); if (Callback) Callback(ok); delete Dlg; }); } LDocFindReplaceParams *LTextView3::CreateFindReplaceParams() { return new LDocFindReplaceParams3; } void LTextView3::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (LDocFindReplaceParams3*) Params; } } void LTextView3::DoFindNext(std::function OnStatus) { bool Status = false; if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } if (OnStatus) OnStatus(Status); } void LTextView3::DoFind(std::function Callback) { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto Action) { if (Params && Params->Lock(_FL)) { Params->MatchWord = Dlg->MatchWord; Params->MatchCase = Dlg->MatchCase; Params->SelectionOnly = Dlg->SelectionOnly; Params->SearchUpwards = Dlg->SearchUpwards; Params->LastFind.Reset(Utf8ToWide(Dlg->Find)); Params->Unlock(); } DoFindNext([this, Callback](bool ok) { Focus(true); if (Callback) Callback(ok); }); }, u); Dlg->DoModal(NULL); } void LTextView3::DoReplace(std::function Callback) { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } auto LastFind8 = SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind); auto LastReplace8 = WideToUtf8(d->FindReplaceParams->LastReplace); auto Dlg = new LReplaceDlg(this, [this, LastFind8, LastReplace8](auto Dlg, auto Action) { LReplaceDlg *Replace = dynamic_cast(Dlg); LAssert(Replace != NULL); LAutoString FindMem(LastFind8); LAutoString ReplaceMem(LastReplace8); if (Action == IDCANCEL) return; if (d->FindReplaceParams->Lock(_FL)) { d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find)); d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace)); d->FindReplaceParams->MatchWord = Replace->MatchWord; d->FindReplaceParams->MatchCase = Replace->MatchCase; d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly; switch (Action) { case IDC_FR_FIND: { OnFind( d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } case IDOK: case IDC_FR_REPLACE: { OnReplace( d->FindReplaceParams->LastFind, d->FindReplaceParams->LastReplace, Action == IDOK, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } } d->FindReplaceParams->Unlock(); } }, LastFind8, LastReplace8); Dlg->MatchWord = d->FindReplaceParams->MatchWord; Dlg->MatchCase = d->FindReplaceParams->MatchCase; Dlg->SelectionOnly = HasSelection(); Dlg->DoModal(NULL); } void LTextView3::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView3::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } /* What was this even supposed to do? LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) */ return i; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView3::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView3::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView3::SeekLine(ssize_t Offset, LTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView3::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView3::OnSetHidden(int Hidden) { } void LTextView3::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (std::max(PartialPourLines, Line.Length()) * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { #if 0 auto Client = GetClient(); LgiTrace("%s:%i - %p::SetScrollBars(%i) cliy=%i content=%i partial=%i\n", _FL, this, ScrollYNeeded, Client.Y(), (Line.Length() * LineY), PartialPour); #endif SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView3::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView3::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView3::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS if (Ctrls.Length() == 0) SetPulse(PULSE_TIMEOUT); Ctrls.Add(this); #else SetPulse(PULSE_TIMEOUT); #endif } void LTextView3::OnEscape(LKey &K) { } bool LTextView3::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView3::OnFocus(bool f) { Invalidate(); } ssize_t LTextView3::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; int Y = (VScroll) ? (int)VScroll->Value() : 0; auto It = Line.begin(Y); if (It != Line.end()) y += (*It)->r.y1; while (It != Line.end()) { auto l = *It; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) { return l->Start; } else if (x > l->r.x2) { return l->Start + l->Len; } } if (Down) It++; else It--; Y++; } // outside text area if (Down) { It = Line.rbegin(); if (It != Line.end()) { if (y > (*It)->r.y2) { // end of document return Size; } } } return 0; } void LTextView3::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView3::Redo() { UndoQue.Redo(); } void LTextView3::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); LInput *i = new LInput(this, s, "Indent Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) IndentSize = atoi(i->GetStr()); delete i; }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); LInput *i = new LInput(this, s, "Tab Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) SetTabSize((uint8_t)Atoi(i->GetStr().Get())); delete i; }); break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView3::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool LTextView3::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView3::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView3::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (m.IsContextMenu()) { DoContextMenu(m); return; } else if (m.Left()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } } if (!Processed) { Capture(m.Down()); } } int LTextView3::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView3::OnMouseMove(LMouse &m) { m.x += ScrollX; ssize_t Hit = HitText(m.x, m.y, true); if (IsCapturing()) { if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } } LCursor LTextView3::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView3::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { DoFindNext(NULL); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView3_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView3_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView3_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) DoFind(NULL); return true; break; } case 'g': case 'G': { if (k.Down()) { DoGoto(NULL); } return true; break; } case 'h': case 'H': { if (k.Down()) { DoReplace(NULL); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { DoCase(NULL, k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView3::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView3::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView3::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView3::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView3::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView3::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView3::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if DOUBLE_BUFFER_PAINT LDoubleBuffer MemBuf(pDC); #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } if (Text && Font) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); auto It = Line.begin(k); LTextLine *l = NULL; int Dy = 0; if (It != Line.end()) Dy = -(*It)->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (It != Line.end() && (l = *It) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ((l = *It) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; Tr.Offset(0, y - Tr.y1); //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif y += LineY; It++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView3::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView3::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) DoFindNext(NULL); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView3::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView3::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView3::OnPulse() { #ifdef WINDOWS InternalPulse(); #else for (auto c: Ctrls) c->InternalPulse(); #endif } void LTextView3::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView3::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } /////////////////////////////////////////////////////////////////////////////// class LTextView3_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView3") == 0) { return new LTextView3(-1, 0, 0, 2000, 2000); } return 0; } } TextView3_Factory; diff --git a/src/common/Text/TextView4.cpp b/src/common/Text/TextView4.cpp --- a/src/common/Text/TextView4.cpp +++ b/src/common/Text/TextView4.cpp @@ -1,5478 +1,5588 @@ #ifdef WIN32 #include #include #endif #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/TextView4.h" #include "lgi/common/Input.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Mail.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/DropFiles.h" #include "ViewPriv.h" #ifdef _DEBUG #define FEATURE_HILIGHT_ALL_MATCHES 1 #else #define FEATURE_HILIGHT_ALL_MATCHES 0 #endif #define DefaultCharset "utf-8" #define SubtractPtr(a, b) ((a) - (b)) #define GDCF_UTF8 -1 #define POUR_DEBUG 0 #define PROFILE_POUR 0 #define PROFILE_PAINT 0 #define DRAW_LINE_BOXES 0 #define WRAP_POUR_TIMEOUT 90 // ms #define PULSE_TIMEOUT 250 // ms #define CURSOR_BLINK 1000 // ms #define IDC_VS 1000 enum Cmds { IDM_COPY_URL = 100, IDM_AUTO_INDENT, IDM_UTF8, IDM_PASTE_NO_CONVERT, IDM_FIXED, IDM_SHOW_WHITE, IDM_HARD_TABS, IDM_INDENT_SIZE, IDM_TAB_SIZE, IDM_DUMP, IDM_RTL, IDM_COPY_ALL, IDM_SELECT_ALL, #ifndef IDM_OPEN IDM_OPEN, #endif #ifndef IDM_NEW IDM_NEW, #endif #ifndef IDM_COPY IDM_COPY, #endif #ifndef IDM_CUT IDM_CUT, #endif #ifndef IDM_PASTE IDM_PASTE, #endif #ifndef IDM_UNDO IDM_UNDO, #endif #ifndef IDM_REDO IDM_REDO, #endif }; #define PAINT_BORDER Back #if DRAW_LINE_BOXES #define PAINT_AFTER_LINE LColour(240, 240, 240) #else #define PAINT_AFTER_LINE Back #endif #define CODEPAGE_BASE 100 #define CONVERT_CODEPAGE_BASE 200 #if !defined(WIN32) && !defined(toupper) #define toupper(c) (((c)>='a'&&(c)<='z') ? (c)-'a'+'A' : (c)) #endif #define THREAD_CHECK() \ if (!InThread()) \ { \ LgiTrace("%s:%i - %s called out of thread.\n", _FL, __FUNCTION__); \ return false; \ } static char SelectWordDelim[] = " \t\n.,()[]<>=?/\\{}\"\';:+=-|!@#$%^&*"; #ifndef WINDOWS static LArray Ctrls; #endif ////////////////////////////////////////////////////////////////////// class LDocFindReplaceParams4 : public LDocFindReplaceParams, public LMutex { public: // Find/Replace History LAutoWString LastFind; LAutoWString LastReplace; bool MatchCase = false; bool MatchWord = false; bool SelectionOnly = false; bool SearchUpwards = false; LDocFindReplaceParams4() : LMutex("LDocFindReplaceParams4") { } }; class LTextView4Private : public LCss, public LMutex { public: LTextView4 *View = NULL; LRect rPadding; int PourX = -1; bool LayoutDirty = true; ssize_t DirtyStart = 0, DirtyLen = 0; LColour UrlColour; bool CenterCursor = false; ssize_t WordSelectMode = -1; LString Eol; LString LastError; // If the scroll position is set before we get a scroll bar, store the index // here and set it when the LNotifyScrollBarCreate arrives. ssize_t VScrollCache = -1; // Find/Replace Params bool OwnFindReplaceParams = true; LDocFindReplaceParams4 *FindReplaceParams = NULL; // Map buffer ssize_t MapLen = 0; char16 *MapBuf = NULL; // // Thread safe Name(char*) impl LString SetName; // #ifdef _DEBUG LString PourLog; #endif LTextView4Private(LTextView4 *view) : LMutex("LTextView4Private") { View = view; UrlColour.Rgb(0, 0, 255); LColour::GetConfigColour("colour.L_URL", UrlColour); rPadding.ZOff(0, 0); FindReplaceParams = new LDocFindReplaceParams4; } ~LTextView4Private() { if (OwnFindReplaceParams) { DeleteObj(FindReplaceParams); } DeleteArray(MapBuf); } void SetDirty(ssize_t Start, ssize_t Len = 0) { LayoutDirty = true; DirtyStart = Start; DirtyLen = Len; } void OnChange(PropType Prop) { if (Prop == LCss::PropPadding || Prop == LCss::PropPaddingLeft || Prop == LCss::PropPaddingRight || Prop == LCss::PropPaddingTop || Prop == LCss::PropPaddingBottom) { LCssTools t(this, View->GetFont()); rPadding.ZOff(0, 0); rPadding = t.ApplyPadding(rPadding); } } }; ////////////////////////////////////////////////////////////////////// enum UndoType { UndoDelete, UndoInsert, UndoChange }; struct Change : public LRange { UndoType Type; LArray Txt; }; struct LTextView4Undo : public LUndoEvent { LTextView4 *View; LArray Changes; LTextView4Undo(LTextView4 *view) { View = view; } void AddChange(ssize_t At, ssize_t Len, UndoType Type) { Change &c = Changes.New(); c.Start = At; c.Len = Len; c.Txt.Add(View->Text + At, Len); c.Type = Type; } void OnChange() { for (auto &c : Changes) { size_t Len = c.Len; if (View->Text) { char16 *t = View->Text + c.Start; for (size_t i=0; id->SetDirty(c.Start, c.Len); } } // LUndoEvent void ApplyChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); View->Cursor = c.Start + c.Len; break; } case UndoDelete: { View->Delete(c.Start, c.Len); View->Cursor = c.Start; break; } case UndoChange: { OnChange(); break; } } } View->UndoOn = true; View->Invalidate(); } void RemoveChange() { View->UndoOn = false; for (auto &c : Changes) { switch (c.Type) { case UndoInsert: { View->Delete(c.Start, c.Len); break; } case UndoDelete: { View->Insert(c.Start, c.Txt.AddressOf(), c.Len); break; } case UndoChange: { OnChange(); break; } } View->Cursor = c.Start; } View->UndoOn = true; View->Invalidate(); } }; void LTextView4::LStyle::RefreshLayout(size_t Start, ssize_t Len) { View->PourText(Start, Len); View->PourStyle(Start, Len); } ////////////////////////////////////////////////////////////////////// LTextView4::LTextView4( int Id, int x, int y, int cx, int cy, LFontType *FontType) : ResObject(Res_Custom) { // init vars LView::d->Css.Reset(d = new LTextView4Private(this)); TabSize = TAB_SIZE; IndentSize = TAB_SIZE; // setup window SetId(Id); // default options #if WINNATIVE CrLf = true; SetDlgCode(DLGC_WANTALLKEYS); #endif d->Padding(LCss::Len(LCss::LenPx, 2)); // Data Text = new char16[Alloc]; if (Text) *Text = 0; // Display if (FontType) { Font = FontType->Create(); } else { LFontType Type; if (Type.GetSystemFont("Fixed")) Font = Type.Create(); else printf("%s:%i - failed to create font.\n", _FL); } if (Font) { SetTabStop(true); Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); Underline->Create(); } Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } OnFontChange(); } else { LgiTrace("%s:%i - Failed to create font, FontType=%p\n", _FL, FontType); Font = LSysFont; } CursorPos.ZOff(1, LineY-1); CursorPos.Offset(d->rPadding.x1, d->rPadding.y1); LRect r; r.ZOff(cx-1, cy-1); r.Offset(x, y); SetPos(r); LResources::StyleElement(this); } LTextView4::~LTextView4() { + #if DEBUG_EDIT_LOG + Edits.DeleteObjects(); + #endif #ifndef WINDOWS Ctrls.Delete(this); #endif Line.DeleteObjects(); Style.Empty(); DeleteArray(TextCache); DeleteArray(Text); if (Font != LSysFont) DeleteObj(Font); DeleteObj(FixedFont); DeleteObj(Underline); DeleteObj(Bold); // 'd' is owned by the LView::Css auto ptr } char16 *LTextView4::MapText(char16 *Str, ssize_t Len, bool RtlTrailingSpace) { if (ObscurePassword /*|| ShowWhiteSpace*/ || RtlTrailingSpace) { if (Len > d->MapLen) { DeleteArray(d->MapBuf); d->MapBuf = new char16[Len + RtlTrailingSpace]; d->MapLen = Len; } if (d->MapBuf) { int n = 0; if (RtlTrailingSpace) { d->MapBuf[n++] = ' '; for (int i=0; iMapBuf[n++] = Str[i]; } } else if (ObscurePassword) { for (int i=0; iMapBuf[n++] = '*'; } } /* else if (ShowWhiteSpace) { for (int i=0; iMapBuf[n++] = 0xb7; } else if (Str[i] == '\t') { d->MapBuf[n++] = 0x2192; } else { d->MapBuf[n++] = Str[i]; } } } */ return d->MapBuf; } } return Str; } void LTextView4::SetFixedWidthFont(bool i) { if (FixedWidthFont ^ i) { if (i) { LFontType Type; if (Type.GetSystemFont("Fixed")) { LFont *f = FixedFont; FixedFont = Font; Font = f; if (!Font) { Font = Type.Create(); if (Font) { Font->PointSize(FixedFont->PointSize()); } } LDocView::SetFixedWidthFont(i); } } else if (FixedFont) { LFont *f = FixedFont; FixedFont = Font; Font = f; LDocView::SetFixedWidthFont(i); } OnFontChange(); Invalidate(); } } void LTextView4::SetReadOnly(bool i) { LDocView::SetReadOnly(i); #if WINNATIVE SetDlgCode(i ? DLGC_WANTARROWS : DLGC_WANTALLKEYS); #endif } void LTextView4::SetCrLf(bool crlf) { CrLf = crlf; } void LTextView4::SetTabSize(uint8_t i) { TabSize = limit(i, 2, 32); OnFontChange(); OnPosChange(); Invalidate(); } void LTextView4::SetWrapType(LDocWrapType i) { LDocView::SetWrapType(i); - CanScrollX = i != TEXTED_WRAP_REFLOW; + CanScrollX = i != L_WRAP_REFLOW; OnPosChange(); Invalidate(); } LFont *LTextView4::GetFont() { return Font; } LFont *LTextView4::GetBold() { return Bold; } void LTextView4::SetFont(LFont *f, bool OwnIt) { if (!f) return; if (OwnIt) { if (Font != LSysFont) DeleteObj(Font); Font = f; } else if (!Font || Font == LSysFont) { Font = new LFont(*f); } else { *Font = *f; } if (Font) { if (!Underline) Underline = new LFont; if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); if (d->UrlColour.IsValid()) Underline->Fore(d->UrlColour); } if (!Bold) Bold = new LFont; if (Bold) { *Bold = *Font; Bold->Bold(true); Bold->Create(); } } OnFontChange(); } void LTextView4::OnFontChange() { if (Font) { // get line height // int OldLineY = LineY; if (!Font->Handle()) Font->Create(); LineY = Font->GetHeight(); if (LineY < 1) LineY = 1; // get tab size char Spaces[32]; memset(Spaces, 'A', TabSize); Spaces[TabSize] = 0; LDisplayString ds(Font, Spaces); Font->TabSize(ds.X()); // repour doc d->SetDirty(0, Size); // validate blue underline font if (Underline) { *Underline = *Font; Underline->Underline(true); Underline->Create(); } #if WINNATIVE // Set the IME font. HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; LOGFONT FontInfo; GetObject(Font->Handle(), sizeof(FontInfo), &FontInfo); ImmSetCompositionFont(hIMC, &FontInfo); ImmReleaseContext(Handle(), hIMC); } #endif } } -void LTextView4::LogLines() +LString LTextView4::LogLines() { int Idx = 0; LStringPipe p; p.Print("DocSize: %i\n", (int)Size); for (auto i : Line) { p.Print(" [%i] alloc.ln=%i, %i+%i+%i=%i, %s\n", Idx, i->Line, (int)i->Start, (int)i->Len, i->NewLine, (int)(i->Start + i->Len + i->NewLine), i->r.GetStr()); Idx++; } - LgiTrace(p.NewLStr()); + + auto r = p.NewLStr(); + LgiTrace(r); #ifdef _DEBUG if (d->PourLog) LgiTrace("%s", d->PourLog.Get()); #endif + + return r; } bool LTextView4::ValidateLines(bool CheckBox) { size_t Pos = 0; char16 *c = Text; size_t Idx = 0; LTextLine *Prev = NULL; for (auto l: Line) { if (l->Start != Pos) { - LogLines(); - LAssert(!"Incorrect start."); - return false; + d->LastError.Printf("%s:%i - Incorrect start.", _FL); + goto OnError; } char16 *e = c; - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { while (*e && *e != '\n') e++; } else { char16 *end = Text + l->Start + l->Len; while (*e && *e != '\n' && e < end) e++; } ssize_t Len = e - c; if (l->Len != Len) { - LogLines(); - LAssert(!"Incorrect length."); - return false; + d->LastError.Printf("%s:%i - Incorrect length: " + LPrintfSSizeT " != " LPrintfSSizeT ", Idx=" LPrintfSizeT, + _FL, l->Len, Len, + Idx); + goto OnError; } if (CheckBox && Prev && Prev->r.y2 != l->r.y1 - 1) { - LogLines(); - LAssert(!"Lines not joined vertically"); + d->LastError.Printf("%s:%i - Lines not joined vertically", _FL); + goto OnError; } if (l->NewLine) { if (*e == '\n') { e++; } else { - LAssert(!"Expecting new line."); - return false; + d->LastError.Printf("%s:%i - Expecting new line.", _FL); + goto OnError; } } Pos = e - Text; c = e; Idx++; Prev = l; } - if (WrapType == TEXTED_WRAP_NONE && + if (WrapType == L_WRAP_NONE && Pos != Size) { - LogLines(); - LAssert(!"Last line != end of doc"); - return false; + d->LastError.Printf("%s:%i - Last line != end of doc", _FL); + goto OnError; } return true; + +OnError: + #if DEBUG_EDIT_LOG + SaveLog("C:\\Code\\TextView4.slog"); + #endif + return false; } int LTextView4::AdjustStyles(ssize_t Start, ssize_t Diff, bool ExtendStyle) { int Changes = 0; for (auto &s : Style) { if (s.Start == Start) { if (Diff < 0 || ExtendStyle) s.Len += Diff; else s.Start += Diff; Changes++; } else if (s.Start > Start) { s.Start += Diff; Changes++; } } return Changes; } // break array, break out of loop when we hit these chars #define ExitLoop(c) ( (c) == 0 || \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' \ ) // extra breaking opportunities #define ExtraBreak(c) ( ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) /* Prerequisite: The Line list must have either the objects with the correct Start/Len or be missing the lines altogether... */ void LTextView4::PourText(size_t Start, ssize_t Length /* == 0 means it's a delete */) { #if PROFILE_POUR char _txt[256]; sprintf_s(_txt, sizeof(_txt), "%p::PourText Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(_txt); #endif LAssert(InThread()); LRect Client = GetClient(); int Mx = Client.X() - d->rPadding.x1 - d->rPadding.x2; int Cy = 0; MaxX = 0; ssize_t Idx = -1; LTextLine *Cur = GetTextLine(Start, &Idx); if (!Cur || !Cur->r.Valid()) { // Find the last line that has a valid position... for (int64_t mid, s = 0, e = Idx >= 0 ? Idx : Line.Length() - 1; s < e; ) { if (e - s <= 1) { if (Line[e]->r.Valid()) mid = e; else mid = s; Cur = Line[mid]; Cy = Cur->r.y1; Idx = mid; break; } else mid = s + ((e - s) >> 1); auto sItem = Line[mid]; if (sItem->r.Valid()) s = mid + 1; // Search Mid->e else e = mid - 1; // Search s->Mid } } if (Cur && !Cur->r.Valid()) Cur = NULL; if (Cur) { Cy = Cur->r.y1; Start = Cur->Start; Length = Size - Start; } else { Idx = 0; Start = 0; Length = Size; } if (!Text || !Font || Mx <= 0) return; // Tracking vars ssize_t e; int WrapCol = GetWrapAtCol(); LDisplayString Sp(Font, " ", 1); int WidthOfSpace = Sp.X(); if (WidthOfSpace < 1) { printf("%s:%i - WidthOfSpace test failed.\n", _FL); return; } // Alright... lets pour! uint64 StartTs = LCurrentTime(); - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { // Find the dimensions of each line that is missing a rect #if PROFILE_POUR Prof.Add("NoWrap: ExistingLines"); #endif #ifdef _DEGBUG LStringPipe Log(1024); Log.Printf("Pour: " LPrintfSizeT ", " LPrintfSSizeT ", partial=%i\n", Start, Length, PartialPour); #endif ssize_t Pos = 0; for (; Idx < (ssize_t)Line.Length(); Idx++) { LTextLine *l = Line[Idx]; #ifdef _DEGBUG Log.Printf(" [%i] exist: r.val=%i\n", Idx, l->r.Valid()); #endif if (!l->r.Valid()) // If the layout is not valid... { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + ds.X(); MaxX = MAX(MaxX, l->r.X()); } // Adjust the y position anyway... it's free. l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Cy = l->r.y2 + 1; Pos = l->Start + l->Len; if (Text[Pos] == '\n') Pos++; } // Now if we are missing lines as well, create them and lay them out #if PROFILE_POUR Prof.Add("NoWrap: NewLines"); #endif while (Pos < Size) { LTextLine *l = new LTextLine(_FL); l->Start = Pos; char16 *c = Text + Pos; char16 *e = c; while (*e && *e != '\n') e++; l->Len = e - c; l->NewLine = *e == '\n'; #ifdef _DEGBUG Log.Printf(" [%i] new: start=" LPrintfSSizeT ", len=" LPrintfSSizeT "\n", Idx, l->Start, l->Len); #endif l->r.x1 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; if (l->Len) { LDisplayString ds(Font, Text + l->Start, l->Len); l->r.x2 = l->r.x1 + ds.X(); } else { l->r.x2 = l->r.x1; } LAssert(l->Len > 0); Line.Add(l); if (*e == '\n') e++; MaxX = MAX(MaxX, l->r.X()); Cy = l->r.y2 + 1; Pos = e - Text; Idx++; } #ifdef _DEGBUG d->PourLog = Log.NewLStr(); #endif PartialPour = false; } else // Wrap text { int DisplayStart = ScrollYLine(); int DisplayLines = (Client.Y() + LineY - 1) / LineY; int DisplayEnd = DisplayStart + DisplayLines; // Pouring is split into 2 parts... // 1) pouring to the end of the displayed text. // 2) pouring from there to the end of the document. // potentially taking several goes to complete the full pour // This allows the document to display and edit faster.. bool PourToDisplayEnd = Line.Length() < DisplayEnd; #if 0 LgiTrace("Idx=%i, DisplayStart=%i, DisplayLines=%i, DisplayEnd=%i, PourToDisplayEnd=%i\n", Idx, DisplayStart, DisplayLines, DisplayEnd, PourToDisplayEnd); #endif if ((ssize_t)Line.Length() > Idx) { for (size_t i=Idx; i= Size || Text[e] == '\n' || (e-i) >= WrapCol) { break; } e++; } // Seek back some characters if we are mid word size_t OldE = e; if (e < Size && Text[e] != '\n') { while (e > i) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) { break; } e--; } } if (e == i) { // No line break at all, so seek forward instead for (e=OldE; e < Size && Text[e] != '\n'; e++) { if (ExitLoop(Text[e]) || ExtraBreak(Text[e])) break; } } // Calc the width LDisplayString ds(Font, Text + i, e - i); Width = ds.X(); } else { // Wrap to edge of screen ssize_t PrevExitChar = -1; int PrevX = -1; while (true) { if (e >= Size || ExitLoop(Text[e]) || ExtraBreak(Text[e])) { LDisplayString ds(Font, Text + i, e - i); if (ds.X() + Cx > Mx) { if (PrevExitChar > 0) { e = PrevExitChar; Width = PrevX; } else { Width = ds.X(); } break; } else if (e >= Size || Text[e] == '\n') { Width = ds.X(); break; } PrevExitChar = e; PrevX = ds.X(); } e++; } } // Create layout line LTextLine *l = new LTextLine(_FL); if (l) { l->Start = i; l->Len = e - i; l->NewLine = Text[e] == '\n'; l->r.x1 = d->rPadding.x1; l->r.x2 = l->r.x1 + Width - 1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; LAssert(l->Len > 0); Line.Add(l); if (PourToDisplayEnd) { if (Line.Length() > DisplayEnd) { // We have reached the end of the displayed area... so // exit out temporarily to display the layout to the user PartialPour = true; break; } } else { // Otherwise check if we are taking too long... if (Line.Length() % 20 == 0) { uint64 Now = LCurrentTime(); if (Now - StartTs > WRAP_POUR_TIMEOUT) { PartialPour = true; // LgiTrace("Pour timeout...\n"); break; } } } MaxX = MAX(MaxX, l->r.X()); Cy += LineY; if (e < Size) e++; } } if (i >= Size) PartialPour = false; SendNotify(LNotifyCursorChanged); } #ifdef _DEBUG ValidateLines(true); #endif #if PROFILE_POUR Prof.Add("LastLine"); #endif if (!PartialPour) { auto It = Line.rbegin(); LTextLine *Last = It != Line.end() ? *It : NULL; if (!Last || - Last->Start + Last->Len < Size) + Last->EndNewLine() < Size) { auto LastEnd = Last ? Last->End() : 0; LTextLine *l = new LTextLine(_FL); if (l) { l->Start = LastEnd; l->Len = Size - LastEnd; l->r.x1 = l->r.x2 = d->rPadding.x1; l->r.y1 = Cy; l->r.y2 = l->r.y1 + LineY - 1; Line.Add(l); MaxX = MAX(MaxX, l->r.X()); Cy += LineY; } } } bool ScrollYNeeded = Client.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); - d->LayoutDirty = WrapType != TEXTED_WRAP_NONE && ScrollChange; + d->LayoutDirty = WrapType != L_WRAP_NONE && ScrollChange; #if PROFILE_POUR static LString _s; _s.Printf("ScrollBars dirty=%i", d->LayoutDirty); Prof.Add(_s); #endif if (ScrollChange) { // LgiTrace("%s:%i - SetScrollBars(%i) %i %i\n", _FL, ScrollYNeeded, Client.Y(), (Line.Length() * LineY)); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); #if 0 // def _DEBUG if (GetWindow()) { static char s[256]; sprintf_s(s, sizeof(s), "Pour: %.2f sec", (double)_PourTime / 1000); GetWindow()->PostEvent(M_TEXTVIEW_DEBUG_TEXT, (LMessage::Param)s); } #endif #if POUR_DEBUG printf("Lines=%i\n", Line.Length()); int Index = 0; for (LTextLine *l=Line.First(); l; l=Line.Next(), Index++) { printf("\t[%i] %i,%i (%s)\n", Index, l->Start, l->Len, l->r.Describe()); } #endif } bool LTextView4::InsertStyle(LAutoPtr s) { if (!s) return false; LAssert(s->Start >= 0); LAssert(s->Len > 0); ssize_t Last = 0; // int n = 0; // LgiTrace("StartStyle=%i,%i(%i) %s\n", (int)s->Start, (int)s->Len, (int)(s->Start+s->Len), s->Fore.GetStr()); if (Style.Length() > 0) { // Optimize for last in the list auto Last = Style.rbegin(); if (s->Start >= (ssize_t)Last->End()) { Style.Insert(*s); return true; } } for (auto i = Style.begin(); i != Style.end(); i++) { if (s->Overlap(*i)) { if (s->Owner > i->Owner) { // Fail the insert return false; } else { // Replace mode... *i = *s; return true; } } if (s->Start >= Last && s->Start < i->Start) { Style.Insert(*s, i); return true; } } Style.Insert(*s); return true; } LTextView4::LStyle *LTextView4::GetNextStyle(StyleIter &s, ssize_t Where) { if (Where >= 0) s = Style.begin(); else s++; while (s != Style.end()) { // determine whether style is relevant.. // styles in the selected region are ignored ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (SelStart >= 0 && s->Start >= Min && s->Start+s->Len < Max) { // style is completely inside selection: ignore s++; } else if (Where >= 0 && s->Start+s->Len < Where) { s++; } else { return &(*s); } } return NULL; } #if 0 CURSOR_CHAR GetCursor() { #ifdef WIN32 LArray Ver; int Os = LGetOs(&Ver); if ((Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64) && Ver[0] >= 5) { return MAKEINTRESOURCE(32649); // hand } else { return IDC_ARROW; } #endif return 0; } #endif LTextView4::LStyle *LTextView4::HitStyle(ssize_t i) { for (auto &s : Style) { if (i >= s.Start && i < (ssize_t)s.End()) { return &s; } } return NULL; } void LTextView4::PourStyle(size_t Start, ssize_t EditSize) { #ifdef _DEBUG int64 StartTime = LCurrentTime(); #endif LAssert(InThread()); if (!Text || Size < 1) return; ssize_t Length = MAX(EditSize, 0); if ((ssize_t)Start + Length >= Size) Length = Size - Start; // For deletes, this sizes the edit length within bounds. // Expand re-style are to word boundaries before and after the area of change while (Start > 0 && UrlChar(Text[Start-1])) { // Move the start back Start--; Length++; } while ((ssize_t)Start + Length < Size && UrlChar(Text[Start+Length])) { // Move the end back Length++; } // Delete all the styles that we own inside the changed area for (StyleIter s = Style.begin(); s != Style.end();) { if (s->Owner == STYLE_NONE) { if (EditSize > 0) { if (s->Overlap(Start, EditSize < 0 ? -EditSize : EditSize)) { Style.Delete(s); continue; } } else { if (s->Overlap(Start, -EditSize)) { Style.Delete(s); continue; } } } s++; } if (UrlDetect) { LArray Links; LAssert((ssize_t)Start + Length <= Size); if (LDetectLinks(Links, Text + Start, Length)) { for (uint32_t i=0; i Url(new LStyle(STYLE_URL)); if (Url) { Url->View = this; Url->Start = Inf.Start + Start; Url->Len = Inf.Len; Url->Font = Underline; Url->Fore = d->UrlColour; InsertStyle(Url); } } } } #ifdef _DEBUG _StyleTime = LCurrentTime() - StartTime; #endif } bool LTextView4::Insert(size_t At, const char16 *Data, ssize_t Len) { static int count = -1; count++; + #if DEBUG_EDIT_LOG + { + EditLog *e = new EditLog; + e->Length = Len; + e->Type = EditInsert; + e->Str.Add(Data, Len); + Edits.Add(e); + } + #endif + + #if 0 LProfile Prof("LTextView4::Insert"); Prof.HideResultsIfBelow(1000); + #define PROF(s) Prof.Add(s) + #else + #define PROF(s) + #endif LAssert(InThread()); if (!ReadOnly && Len > 0) { if (!Data) return false; // limit input to valid data At = MIN(Size, (ssize_t)At); // make sure we have enough memory size_t NewAlloc = Size + Len + 1; NewAlloc += ALLOC_BLOCK - (NewAlloc % ALLOC_BLOCK); if (NewAlloc != Alloc) { char16 *NewText = new char16[NewAlloc]; if (NewText) { if (Text) { // copy any existing data across memcpy(NewText, Text, (Size + 1) * sizeof(char16)); } DeleteArray(Text); Text = NewText; Alloc = NewAlloc; } else { // memory allocation error return false; } } - Prof.Add("MemChk"); + PROF("MemChk"); if (Text) { // Insert the data // Move the section after the insert to make space... auto After = Size - At; if (After > 0) memmove(Text+(At+Len), Text+At, After * sizeof(char16)); - Prof.Add("Cpy"); + PROF("Cpy"); // Copy new data in... memcpy(Text+At, Data, Len * sizeof(char16)); Size += Len; Text[Size] = 0; // NULL terminate - Prof.Add("Undo"); + PROF("Undo"); // Add the undo object... if (UndoOn) { LAutoPtr Obj; if (!UndoCur) Obj.Reset(new LTextView4Undo(this)); auto u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoInsert); else LAssert(!"No undo obj?"); if (Obj) UndoQue += Obj.Release(); } // Clear layout info for the new text ssize_t Idx = -1; LTextLine *Cur = NULL; if (Line.Length() == 0) { // Empty doc... set up the first line Line.Add(Cur = new LTextLine(_FL)); Idx = 0; Cur->Start = 0; } else { Cur = GetTextLine(At, &Idx); } if (Cur) { - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { // Clear layout for current line... Cur->r.ZOff(-1, -1); - Prof.Add("NoWrap add lines"); + PROF("NoWrap add lines"); + LgiTrace("Insert '%S' at %i, size=%i\n", Data, (int)At, (int)Size); + // Add any new lines that we need... char16 *e = Text + At + Len; char16 *c; for (c = Text + At; c < e; c++) { if (*c == '\n') { // Set the size of the current line... size_t Pos = c - Text; Cur->Len = Pos - Cur->Start; - Cur->NewLine = Text[Cur->End()] == '\n'; + Cur->NewLine = true; + LgiTrace(" LF at %i, Idx=%i\n", (int)Pos, (int)Idx); + + if (!*++c) + { + Cur = NULL; + break; + } // Create a new line... Cur = new LTextLine(_FL); if (!Cur) return false; - Cur->Start = Pos + 1; + Cur->Start = c - Text; Line.AddAt(++Idx, Cur); + LgiTrace(" newLine at %i, Idx=%i\n", (int)Cur->Start, Idx); } } - Prof.Add("CalcLen"); - - // Make sure the last Line's length is set.. - Cur->CalcLen(Text); - printf("CalcLen, size=%i, start=%i, len=%i\n", - (int)Size, (int)Cur->Start, (int)Cur->Len); - - Prof.Add("UpdatePos"); + PROF("CalcLen"); + + if (Cur) + { + // Make sure the last Line's length is set.. + Cur->CalcLen(Text); + LgiTrace(" CalcLen, size=%i, start=%i, len=%i\n", + (int)Size, (int)Cur->Start, (int)Cur->Len); + } + + PROF("UpdatePos"); // Now update all the positions of the following lines... for (size_t i = ++Idx; i < Line.Length(); i++) Line[i]->Start += Len; } else { // Clear all lines to the end of the doc... LgiTrace("ClearLines %i\n", (int)Idx+1); for (size_t i = ++Idx; i < Line.Length(); i++) delete Line[i]; Line.Length(Idx); } } else { // If wrap is on then this can happen when an Insert happens before the // OnPulse event has laid out the new text. Probably not a good thing in // non-wrap mode - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { LTextLine *l = *Line.rbegin(); printf("%s:%i - Insert error: no cur, At=%i, Size=%i, Lines=%i, WrapType=%i\n", _FL, (int)At, (int)Size, (int)Line.Length(), (int)WrapType); if (l) printf("Last=%i, %i\n", (int)l->Start, (int)l->Len); } } #ifdef _DEBUG - Prof.Add("Validate"); + PROF("Validate"); ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, Len); Dirty = true; if (PourEnabled) { - Prof.Add("PourText"); + PROF("PourText"); PourText(At, Len); - Prof.Add("PourStyle"); + PROF("PourStyle"); auto Start = LCurrentTime(); PourStyle(At, Len); auto End = LCurrentTime(); if (End - Start > 1000) { PourStyle(At, Len); } } SendNotify(LNotifyDocChanged); return true; } } return false; } bool LTextView4::Delete(size_t At, ssize_t Len) { bool Status = false; LAssert(InThread()); if (!ReadOnly) { // limit input At = MAX(At, 0); At = MIN((ssize_t)At, Size); Len = MIN(Size-(ssize_t)At, Len); if (Len > 0) { int HasNewLine = 0; for (int i=0; i Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(At, Len, UndoDelete); if (Obj) UndoQue += Obj.Release(); } memmove(Text+At, Text+(At+Len), (Size-At-Len) * sizeof(char16)); Size -= Len; Text[Size] = 0; - if (WrapType == TEXTED_WRAP_NONE) + if (WrapType == L_WRAP_NONE) { ssize_t Idx = -1; LTextLine *Cur = GetTextLine(At, &Idx); if (Cur) { Cur->r.ZOff(-1, -1); // Delete some lines... for (int i=0; iCalcLen(Text); // Shift all further lines down... for (size_t i = Idx + 1; i < Line.Length(); i++) Line[i]->Start -= Len; } } else { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { for (size_t i = Index; i < Line.Length(); i++) delete Line[i]; Line.Length(Index); } } Dirty = true; Status = true; #ifdef _DEBUG ValidateLines(); #endif if (AdjustStylePos) AdjustStyles(At, -Len); if (PourEnabled) { PourText(At, -Len); PourStyle(At, -Len); } if (Cursor >= (ssize_t)At && Cursor <= (ssize_t)At + Len) { SetCaret(At, false, HasNewLine != 0); } // Handle repainting in flowed mode, when the line starts change - if (WrapType == TEXTED_WRAP_REFLOW) + if (WrapType == L_WRAP_REFLOW) { ssize_t Index; LTextLine *Cur = GetTextLine(At, &Index); if (Cur) { LRect r = Cur->r; r.x2 = GetClient().x2; r.y2 = GetClient().y2; Invalidate(&r); } } SendNotify(LNotifyDocChanged); Status = true; } } return Status; } void LTextView4::DeleteSelection(char16 **Cut) { if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Cut) { *Cut = NewStrW(Text + Min, Max - Min); } Delete(Min, Max - Min); SetCaret(Min, false, true); } } LArray::I LTextView4::GetTextLineIt(ssize_t Offset, ssize_t *Index) { if (Line.Length() == 0) { if (Index) *Index = 0; return Line.end(); } if (Offset <= 0) { if (Index) *Index = 0; return Line.begin(); } else if (Line.Length()) { auto l = Line.Last(); if (Offset > l->End()) { if (Index) *Index = Line.Length() - 1; return Line.begin(Line.Length()-1); } } size_t mid = 0, s = 0, e = Line.Length() - 1; while (s < e) { if (e - s <= 1) { if (Line[s]->Overlap(Offset)) mid = s; else if (Line[e]->Overlap(Offset)) mid = e; else goto OnError; } else mid = s + ((e - s) >> 1); auto l = Line[mid]; auto end = l->EndNewLine(); if (Offset < l->Start) e = mid - 1; else if (Offset > end) s = mid + 1; else { if (!Line[mid]->Overlap(Offset)) goto OnError; if (Index) *Index = mid; return Line.begin(mid); } } if (!Line[s]->Overlap(Offset)) goto OnError; if (Index) *Index = s; return Line.begin(s); OnError: return Line.end(); } int64 LTextView4::Value() { auto n = Name(); #ifdef _MSC_VER return (n) ? _atoi64(n) : 0; #else return (n) ? atoll(n) : 0; #endif } void LTextView4::Value(int64 i) { char Str[32]; sprintf_s(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } LString LTextView4::operator[](ssize_t LineIdx) { if (LineIdx <= 0 || LineIdx > (ssize_t)GetLines()) return LString(); LTextLine *Ln = Line[LineIdx-1]; if (!Ln) return LString(); LString s(Text + Ln->Start, Ln->Len); return s; } const char *LTextView4::Name() { UndoQue.Empty(); DeleteArray(TextCache); TextCache = WideToUtf8(Text); return TextCache; } bool LTextView4::Name(const char *s) { if (InThread()) { UndoQue.Empty(); DeleteArray(TextCache); DeleteArray(Text); Line.DeleteObjects(); Style.Empty(); LAssert(LIsUtf8(s)); Text = Utf8ToWide(s); if (!Text) { Text = new char16[1]; if (Text) *Text = 0; } Size = Text ? StrlenW(Text) : 0; Alloc = Size + 1; Cursor = MIN(Cursor, Size); if (Text) { // Remove '\r's char16 *o = Text; for (char16 *i=Text; *i; i++) { if (*i != '\r') { *o++ = *i; } else Size--; } *o++ = 0; } // update everything else d->SetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); } else if (d->Lock(_FL)) { if (IsAttached()) { d->SetName = s; PostEvent(M_TEXT_UPDATE_NAME); } else LAssert(!"Can't post event to detached/virtual window."); d->Unlock(); } return true; } const char16 *LTextView4::NameW() { return Text; } bool LTextView4::NameW(const char16 *s) { DeleteArray(Text); Size = s ? StrlenW(s) : 0; Alloc = Size + 1; Text = new char16[Alloc]; Cursor = MIN(Cursor, Size); if (Text) { memcpy(Text, s, Size * sizeof(char16)); // remove LF's int In = 0, Out = 0; CrLf = false; for (; InSetDirty(0, Size); PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(); Invalidate(); return true; } LRange LTextView4::GetSelectionRange() { LRange r; if (HasSelection()) { r.Start = MIN(SelStart, SelEnd); ssize_t End = MAX(SelStart, SelEnd); r.Len = End - r.Start; } return r; } char *LTextView4::GetSelection() { LRange s = GetSelectionRange(); if (s.Len > 0) { return (char*)LNewConvertCp("utf-8", Text + s.Start, LGI_WideCharset, s.Len*sizeof(Text[0]) ); } return 0; } bool LTextView4::HasSelection() { return (SelStart >= 0) && (SelStart != SelEnd); } void LTextView4::SelectAll() { SelStart = 0; SelEnd = Size; Invalidate(); } void LTextView4::UnSelectAll() { bool Update = HasSelection(); SelStart = -1; SelEnd = -1; if (Update) { Invalidate(); } } size_t LTextView4::GetLines() { return Line.Length(); } void LTextView4::GetTextExtent(int &x, int &y) { PourText(0, Size); x = MaxX + d->rPadding.x1; y = (int)(Line.Length() * LineY); } bool LTextView4::GetLineColumnAtIndex(LPoint &Pt, ssize_t Index) { ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Index < 0 ? Cursor : Index, &FromIndex); if (!From) return false; Pt.x = (int) (Cursor - From->Start); Pt.y = (int) FromIndex; return true; } ssize_t LTextView4::GetCaret(bool Cur) { if (Cur) { return Cursor; } return 0; } ssize_t LTextView4::IndexAt(int x, int y) { LTextLine *l = Line.ItemAt(y); if (l) { return l->Start + MIN(x, l->Len); } return 0; } bool LTextView4::ScrollToOffset(size_t Off) { bool ForceFullUpdate = false; ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Off, &ToIndex); if (To) { LRect Client = GetClient(); int DisplayLines = Client.Y() / LineY; if (VScroll) { if (ToIndex < VScroll->Value()) { // Above the visible region... if (d->CenterCursor) { ssize_t i = ToIndex - (DisplayLines >> 1); VScroll->Value(MAX(0, i)); } else { VScroll->Value(ToIndex); } ForceFullUpdate = true; } if (ToIndex >= VScroll->Value() + DisplayLines) { int YOff = d->CenterCursor ? DisplayLines >> 1 : DisplayLines; ssize_t v = MIN(ToIndex - YOff + 1, (ssize_t)Line.Length() - DisplayLines); if (v != VScroll->Value()) { // Below the visible region VScroll->Value(v); ForceFullUpdate = true; } } } else { d->VScrollCache = ToIndex; } } return ForceFullUpdate; } void LTextView4::SetCaret(size_t i, bool Select, bool ForceFullUpdate) { // int _Start = LCurrentTime(); Blink = true; // Bound the new cursor position to the document if ((ssize_t)i > Size) i = Size; // Store the old selection and cursor ssize_t s = SelStart, e = SelEnd, c = Cursor; // If there is going to be a selected area if (Select && i != SelStart) { // Then set the start if (SelStart < 0) { // We are starting a new selection SelStart = Cursor; } // And end SelEnd = i; } else { // Clear the selection SelStart = SelEnd = -1; } ssize_t FromIndex = 0; LTextLine *From = GetTextLine(Cursor, &FromIndex); Cursor = i; // check the cursor is on the screen ForceFullUpdate |= ScrollToOffset(Cursor); // check whether we need to update the screen ssize_t ToIndex = 0; LTextLine *To = GetTextLine(Cursor, &ToIndex); if (ForceFullUpdate || !To || !From) { // need full update Invalidate(); } else if ( ( SelStart != s || SelEnd != e ) ) { // Update just the selection bounds LRect Client = GetClient(); size_t Start, End; if (SelStart >= 0 && s >= 0) { // Selection has changed, union the before and after regions Start = MIN(Cursor, c); End = MAX(Cursor, c); } else if (SelStart >= 0) { // Selection created... Start = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else if (s >= 0) { // Selection removed... Start = MIN(s, e); End = MAX(s, e); } else return; LTextLine *SLine = GetTextLine(Start); LTextLine *ELine = GetTextLine(End); LRect u; if (SLine && ELine) { if (SLine->r.Valid()) { u = DocToScreen(SLine->r); } else u.Set(0, 0, Client.X()-1, 1); // Start of visible page LRect b(0, Client.Y()-1, Client.X()-1, Client.Y()-1); if (ELine->r.Valid()) { b = DocToScreen(ELine->r); } else { b.Set(0, Client.Y()-1, Client.X()-1, Client.Y()-1); } u.Union(&b); u.x1 = 0; u.x2 = X(); } else { /* printf("%s,%i - Couldn't get SLine and ELine: %i->%p, %i->%p\n", _FL, (int)Start, SLine, (int)End, ELine); */ u = Client; } Invalidate(&u); } else if (Cursor != c) { // just the cursor has moved // update the line the cursor moved to LRect r = To->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); if (To != From) { // update the line the cursor came from, // if it's a different line from the "to" r = From->r; r.Offset(-ScrollX, d->rPadding.y1-DocOffset); r.x2 = X(); Invalidate(&r); } } if (c != Cursor) { // Send off notify SendNotify(LNotifyCursorChanged); } //int _Time = LCurrentTime() - _Start; //printf("Setcursor=%ims\n", _Time); } void LTextView4::SetBorder(int b) { } bool LTextView4::Cut() { bool Status = false; char16 *Txt16 = 0; DeleteSelection(&Txt16); if (Txt16) { #ifdef WIN32 Txt16 = ConvertToCrLf(Txt16); #endif char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); LClipBoard Clip(this); Clip.Text(Txt8); Status = Clip.TextW(Txt16, false); DeleteArray(Txt8); DeleteArray(Txt16); } return Status; } bool LTextView4::Copy() { bool Status = true; if (SelStart >= 0) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); #ifdef WIN32 char16 *Txt16 = NewStrW(Text+Min, Max-Min); Txt16 = ConvertToCrLf(Txt16); char *Txt8 = (char*)LNewConvertCp(LAnsiToLgiCp(), Txt16, LGI_WideCharset); #else char *Txt8 = (char*)LNewConvertCp("utf-8", Text+Min, LGI_WideCharset, (Max-Min)*sizeof(*Text)); #endif LClipBoard Clip(this); Clip.Text(Txt8); #ifdef WIN32 Clip.TextW(Txt16, false); DeleteArray(Txt16); #endif DeleteArray(Txt8); } else LgiTrace("%s:%i - No selection.\n", _FL); return Status; } bool LTextView4::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *s = t, *d = t; for (; *s; s++) { if (*s != '\r') { *d++ = *s; } } *d++ = 0; // insert text ssize_t Len = StrlenW(t); Insert(Cursor, t, Len); SetCaret(Cursor+Len, false, true); // Multiline return true; } void LTextView4::ClearDirty(std::function OnStatus, bool Ask, const char *FileName) { if (Dirty) { int Answer = (Ask) ? LgiMsg(this, LLoadString(L_TEXTCTRL_ASK_SAVE, "Do you want to save your changes to this document?"), LLoadString(L_TEXTCTRL_SAVE, "Save"), MB_YESNOCANCEL) : IDYES; if (Answer == IDYES) { auto DoSave = [this, OnStatus](bool ok, const char *FileName) { Save(FileName); if (OnStatus) OnStatus(ok); }; if (!FileName) { LFileSelect *Select = new LFileSelect; Select->Parent(this); Select->Save([FileName=LString(FileName), DoSave](auto Select, auto ok) { if (ok) DoSave(ok, Select->Name()); else DoSave(ok, FileName); delete Select; }); } else DoSave(true, FileName); } else if (Answer == IDCANCEL) { if (OnStatus) OnStatus(false); return; } } if (OnStatus) OnStatus(true); } bool LTextView4::Open(const char *Name, const char *CharSet) { bool Status = false; LFile f; if (f.Open(Name, O_READ|O_SHARE)) { DeleteArray(Text); int64 Bytes = f.GetSize(); if (Bytes < 0 || Bytes & 0xffff000000000000LL) { LgiTrace("%s:%i - Invalid file size: " LPrintfInt64 "\n", _FL, Bytes); return false; } SetCaret(0, false); Line.DeleteObjects(); char *c8 = new char[Bytes + 4]; if (c8) { if (f.Read(c8, (int)Bytes) == Bytes) { char *DataStart = c8; c8[Bytes] = 0; c8[Bytes+1] = 0; c8[Bytes+2] = 0; c8[Bytes+3] = 0; if ((uchar)c8[0] == 0xff && (uchar)c8[1] == 0xfe) { // utf-16 if (!CharSet) { CharSet = "utf-16"; DataStart += 2; } } // Convert to unicode first.... if (Bytes == 0) { Text = new char16[1]; if (Text) Text[0] = 0; } else { Text = (char16*)LNewConvertCp(LGI_WideCharset, DataStart, CharSet ? CharSet : DefaultCharset); } if (Text) { // Remove LF's char16 *In = Text, *Out = Text; CrLf = false; Size = 0; while (*In) { if (*In >= ' ' || *In == '\t' || *In == '\n') { *Out++ = *In; Size++; } else if (*In == '\r') { CrLf = true; } In++; } Size = (int) (Out - Text); *Out = 0; Alloc = Size + 1; Dirty = false; if (Text && Text[0] == 0xfeff) // unicode byte order mark { memmove(Text, Text+1, Size * sizeof(*Text)); Size--; } PourText(0, Size); PourStyle(0, Size); UpdateScrollBars(true); Status = true; } } DeleteArray(c8); } else { Alloc = Size = 0; } Invalidate(); } return Status; } template bool WriteToStream(LFile &out, T *in, size_t len, bool CrLf) { if (!in) return false; if (CrLf) { int BufLen = 1 << 20; LAutoPtr Buf(new T[BufLen]); T *b = Buf; T *e = Buf + BufLen; T *c = in; T *end = c + len; while (c < end) { if (b > e - 16) { auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; b = Buf; } if (*c == '\n') { *b++ = '\r'; *b++ = '\n'; } else { *b++ = *c; } c++; } auto Bytes = (b - Buf) * sizeof(T); if (out.Write(Buf, Bytes) != Bytes) return false; } else { auto Bytes = len * sizeof(T); if (out.Write(in, Bytes) != Bytes) return false; } return true; } bool LTextView4::Save(const char *Name, const char *CharSet) { LFile f; LString TmpName; bool Status = false; d->LastError.Empty(); if (f.Open(Name, O_WRITE)) { if (f.SetSize(0) != 0) { // Can't resize file, fall back to renaming it and // writing a new file... f.Close(); TmpName = Name; TmpName += ".tmp"; if (!FileDev->Move(Name, TmpName)) { LgiTrace("%s:%i - Failed to move '%s'.\n", _FL, Name); return false; } if (!f.Open(Name, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Name); return false; } } if (Text) { auto InSize = Size * sizeof(char16); if (CharSet && !Stricmp(CharSet, "utf-16")) { if (sizeof(*Text) == 2) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 32->16 convert LAutoPtr c16((uint16_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c16) Status = WriteToStream(f, c16.Get(), Strlen(c16.Get()), CrLf); } } else if (CharSet && !Stricmp(CharSet, "utf-32")) { if (sizeof(*Text) == 4) { // No conversion needed... Status = WriteToStream(f, Text, Size, CrLf); } else { // 16->32 convert LAutoPtr c32((uint32_t*)LNewConvertCp(CharSet, Text, LGI_WideCharset, InSize)); if (c32) Status = WriteToStream(f, c32.Get(), Strlen(c32.Get()), CrLf); } } else { LAutoString c8((char*)LNewConvertCp(CharSet ? CharSet : DefaultCharset, Text, LGI_WideCharset, InSize)); if (c8) Status = WriteToStream(f, c8.Get(), strlen(c8), CrLf); } if (Status) Dirty = false; } } else { int Err = f.GetError(); LString sErr = LErrorCodeToString(Err); d->LastError.Printf("Failed to open '%s' for writing: %i - %s\n", Name, Err, sErr.Get()); } if (TmpName) FileDev->Delete(TmpName); return Status; } const char *LTextView4::GetLastError() { return d->LastError; } void LTextView4::UpdateScrollBars(bool Reset) { if (VScroll) { LRect Before = GetClient(); int DisplayLines = Y() / LineY; ssize_t Lines = GetLines(); VScroll->SetRange(Lines); if (VScroll) { VScroll->SetPage(DisplayLines); ssize_t Max = Lines - DisplayLines + 1; bool Inval = false; if (VScroll->Value() > Max) { VScroll->Value(Max); Inval = true; } if (Reset) { VScroll->Value(0); SelStart = SelEnd = -1; } else if (d->VScrollCache >= 0) { VScroll->Value(d->VScrollCache); d->VScrollCache = -1; SelStart = SelEnd = -1; } LRect After = GetClient(); if (Before != After && GetWrapType()) { d->SetDirty(0, Size); Inval = true; } if (Inval) { Invalidate(); } } } } void LTextView4::DoCase(std::function Callback, bool Upper) { if (Text) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); if (Min < Max) { if (UndoOn) { LAutoPtr Obj(new LTextView4Undo(this)); LTextView4Undo *u = UndoCur ? UndoCur : Obj; if (u) u->AddChange(Min, Max - Min, UndoChange); if (Obj) UndoQue += Obj.Release(); } for (ssize_t i=Min; i= 'a' && Text[i] <= 'z') Text[i] = Text[i] - 'a' + 'A'; } else { if (Text[i] >= 'A' && Text[i] <= 'Z') Text[i] = Text[i] - 'A' + 'a'; } } Dirty = true; d->SetDirty(Min, 0); Invalidate(); SendNotify(LNotifyDocChanged); } } if (Callback) Callback(Text != NULL); } ssize_t LTextView4::GetLine() { ssize_t Idx = 0; GetTextLine(Cursor, &Idx); return Idx + 1; } void LTextView4::SetLine(int64_t i) { LTextLine *l = Line.ItemAt(i - 1); if (l) { d->CenterCursor = true; SetCaret(l->Start, false); d->CenterCursor = false; } } void LTextView4::DoGoto(std::function Callback) { LInput *Dlg = new LInput(this, "", LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto line:"), "Text"); Dlg->DoModal([this, Dlg, Callback](auto d, auto code) { auto ok = code == IDOK && Dlg->GetStr(); if (ok) SetLine(Dlg->GetStr().Int()); if (Callback) Callback(ok); delete Dlg; }); } LDocFindReplaceParams *LTextView4::CreateFindReplaceParams() { return new LDocFindReplaceParams4; } void LTextView4::SetFindReplaceParams(LDocFindReplaceParams *Params) { if (Params) { if (d->OwnFindReplaceParams) { DeleteObj(d->FindReplaceParams); } d->OwnFindReplaceParams = false; d->FindReplaceParams = (LDocFindReplaceParams4*) Params; } } void LTextView4::DoFindNext(std::function Callback) { bool Status = false; if (InThread()) { if (d->FindReplaceParams->Lock(_FL)) { if (d->FindReplaceParams->LastFind) Status = OnFind(d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); d->FindReplaceParams->Unlock(); } } else if (IsAttached()) { Status = PostEvent(M_TEXTVIEW_FIND); } if (Callback) Callback(Status); } void LTextView4::DoFind(std::function Callback) { LString u; if (HasSelection()) { ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd); u = LString(Text + Min, Max - Min); } else { u = d->FindReplaceParams->LastFind.Get(); } auto Dlg = new LFindDlg(this, [this, Params=d->FindReplaceParams, Callback](auto Dlg, auto Action) { if (Params && Params->Lock(_FL)) { Params->MatchWord = Dlg->MatchWord; Params->MatchCase = Dlg->MatchCase; Params->SelectionOnly = Dlg->SelectionOnly; Params->SearchUpwards = Dlg->SearchUpwards; Params->LastFind.Reset(Utf8ToWide(Dlg->Find)); Params->Unlock(); } DoFindNext([this, Callback](bool ok) { Focus(true); if (Callback) Callback(ok); }); }, u); Dlg->DoModal(NULL); } void LTextView4::DoReplace(std::function Callback) { bool SingleLineSelection = false; SingleLineSelection = HasSelection(); if (SingleLineSelection) { LRange Sel = GetSelectionRange(); for (ssize_t i = Sel.Start; i < Sel.End(); i++) { if (Text[i] == '\n') { SingleLineSelection = false; break; } } } LAutoString LastFind8(SingleLineSelection ? GetSelection() : WideToUtf8(d->FindReplaceParams->LastFind)); LAutoString LastReplace8(WideToUtf8(d->FindReplaceParams->LastReplace)); auto Dlg = new LReplaceDlg(this, [this](auto Dlg, auto Action) { LReplaceDlg *Replace = dynamic_cast(Dlg); LAssert(Replace != NULL); if (Action == IDCANCEL) return; if (d->FindReplaceParams->Lock(_FL)) { d->FindReplaceParams->LastFind.Reset(Utf8ToWide(Replace->Find)); d->FindReplaceParams->LastReplace.Reset(Utf8ToWide(Replace->Replace)); d->FindReplaceParams->MatchWord = Replace->MatchWord; d->FindReplaceParams->MatchCase = Replace->MatchCase; d->FindReplaceParams->SelectionOnly = Replace->SelectionOnly; switch (Action) { case IDC_FR_FIND: { OnFind( d->FindReplaceParams->LastFind, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } case IDOK: case IDC_FR_REPLACE: { OnReplace( d->FindReplaceParams->LastFind, d->FindReplaceParams->LastReplace, Action == IDOK, d->FindReplaceParams->MatchWord, d->FindReplaceParams->MatchCase, d->FindReplaceParams->SelectionOnly, d->FindReplaceParams->SearchUpwards); break; } } d->FindReplaceParams->Unlock(); } }, LastFind8, LastReplace8); Dlg->MatchWord = d->FindReplaceParams->MatchWord; Dlg->MatchCase = d->FindReplaceParams->MatchCase; Dlg->SelectionOnly = HasSelection(); Dlg->DoModal(NULL); } void LTextView4::SelectWord(size_t From) { for (SelStart = From; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = From; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Invalidate(); } typedef int (*StringCompareFn)(const char16 *a, const char16 *b, ssize_t n); ptrdiff_t LTextView4::MatchText(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { if (!ValidStrW(Find)) return -1; ssize_t FindLen = StrlenW(Find); // Setup range to search ssize_t Begin, End; if (SelectionOnly && HasSelection()) { Begin = MIN(SelStart, SelEnd); End = MAX(SelStart, SelEnd); } else { Begin = 0; End = Size; } // Look through text... ssize_t i; bool Wrap = false; if (Cursor > End - FindLen) { Wrap = true; if (SearchUpwards) i = End - FindLen; else i = Begin; } else { i = Cursor; } if (i < Begin) i = Begin; if (i > End) i = End; StringCompareFn CmpFn = MatchCase ? StrncmpW : StrnicmpW; char16 FindCh = MatchCase ? Find[0] : toupper(Find[0]); for (; SearchUpwards ? i >= Begin : i <= End - FindLen; i += SearchUpwards ? -1 : 1) { if ( (MatchCase ? Text[i] : toupper(Text[i])) == FindCh ) { char16 *Possible = Text + i; if (CmpFn(Possible, Find, FindLen) == 0) { if (MatchWord) { // Check boundaries if (Possible > Text) // Check off the start { if (!IsWordBoundry(Possible[-1])) continue; } if (i + FindLen < Size) // Check off the end { if (!IsWordBoundry(Possible[FindLen])) continue; } } LRange r(Possible - Text, FindLen); if (!r.Overlap(Cursor)) return r.Start; } } if (!Wrap && (i + 1 > End - FindLen)) { Wrap = true; i = Begin; End = Cursor; } } return -1; } bool LTextView4::OnFind(const char16 *Find, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); // Not sure what this is doing??? if (HasSelection() && SelEnd < SelStart) { Cursor = SelStart; } #if FEATURE_HILIGHT_ALL_MATCHES // Clear existing styles for matches for (StyleIter s = Style.begin(); s != Style.end(); ) { if (s->Owner == STYLE_FIND_MATCHES) Style.Delete(s); else s++; } ssize_t FindLen = StrlenW(Find); ssize_t FirstLoc = MatchText(Find, MatchWord, MatchCase, false, SearchUpwards), Loc; if (FirstLoc >= 0) { SetCaret(FirstLoc, false); SetCaret(FirstLoc + FindLen, true); } ssize_t Old = Cursor; if (!SearchUpwards) Cursor += FindLen; while ((Loc = MatchText(Find, MatchWord, MatchCase, false, false)) != FirstLoc) { LAutoPtr s(new LStyle(STYLE_FIND_MATCHES)); s->Start = Loc; s->Len = FindLen; s->Fore = LColour(L_FOCUS_SEL_FORE); s->Back = LColour(L_FOCUS_SEL_BACK).Mix(LColour(L_WORKSPACE)); InsertStyle(s); Cursor = Loc + FindLen; } Cursor = Old; ScrollToOffset(Cursor); Invalidate(); #else ssize_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (Loc >= 0) { SetCaret(Loc, false); SetCaret(Loc + StrlenW(Find), true); return true; } #endif return false; } bool LTextView4::OnReplace(const char16 *Find, const char16 *Replace, bool All, bool MatchWord, bool MatchCase, bool SelectionOnly, bool SearchUpwards) { THREAD_CHECK(); if (ValidStrW(Find)) { // int Max = -1; ssize_t FindLen = StrlenW(Find); ssize_t ReplaceLen = StrlenW(Replace); // size_t OldCursor = Cursor; ptrdiff_t First = -1; while (true) { ptrdiff_t Loc = MatchText(Find, MatchWord, MatchCase, SelectionOnly, SearchUpwards); if (First < 0) { First = Loc; } else if (Loc == First) { break; } if (Loc >= 0) { ssize_t OldSelStart = SelStart; ssize_t OldSelEnd = SelEnd; Delete(Loc, FindLen); Insert(Loc, Replace, ReplaceLen); SelStart = OldSelStart; SelEnd = OldSelEnd - FindLen + ReplaceLen; Cursor = Loc + ReplaceLen; } if (!All) { return Loc >= 0; } if (Loc < 0) break; } } return false; } ssize_t LTextView4::SeekLine(ssize_t Offset, LTextViewSeek Where) { THREAD_CHECK(); switch (Where) { case PrevLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset--; for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case NextLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; Offset++; break; } case StartLine: { for (; Offset > 0 && Text[Offset] != '\n'; Offset--) ; if (Offset > 0) Offset++; break; } case EndLine: { for (; Offset < Size && Text[Offset] != '\n'; Offset++) ; break; } default: { LAssert(false); break; } } return Offset; } bool LTextView4::OnMultiLineTab(bool In) { bool Status = false; ssize_t Min = MIN(SelStart, SelEnd); ssize_t Max = MAX(SelStart, SelEnd), i; Min = SeekLine(Min, StartLine); int Ls = 0; LArray p; for (i=Min; i=0; i--) { if (In) { // <- ssize_t n = Indexes[i], Space = 0; for (; Space ssize_t Len = Indexes[i]; for (; Text[Len] != '\n' && Len Indexes[i]) { if (HardTabs) { char16 Tab[] = {'\t', 0}; Insert(Indexes[i], Tab, 1); Max++; } else { char16 *Sp = new char16[IndentSize]; if (Sp) { for (int n=0; nChanges.Length()) { UndoQue += UndoCur; UndoCur = NULL; } else { DeleteObj(UndoCur); } SelStart = Min; SelEnd = Cursor = Max; PourEnabled = true; PourText(Min, Max - Min); PourStyle(Min, Max - Min); d->SetDirty(Min, Max-Min); Invalidate(); Status = true; return Status; } void LTextView4::OnSetHidden(int Hidden) { } void LTextView4::OnPosChange() { static bool Processing = false; if (!Processing) { Processing = true; LLayout::OnPosChange(); LRect c = GetClient(); bool ScrollYNeeded = c.Y() < (Line.Length() * LineY); bool ScrollChange = ScrollYNeeded ^ (VScroll != NULL); if (ScrollChange) { // printf("%s:%i - SetScrollBars(%i)\n", _FL, ScrollYNeeded); SetScrollBars(false, ScrollYNeeded); } UpdateScrollBars(); if (GetWrapType() && d->PourX != X()) { d->PourX = X(); d->SetDirty(0, Size); } Processing = false; } } int LTextView4::WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { Formats.Supports("text/uri-list"); Formats.Supports("text/html"); Formats.Supports("UniformResourceLocatorW"); return Formats.Length() ? DROPEFFECT_COPY : DROPEFFECT_NONE; } int LTextView4::OnDrop(LArray &Data, LPoint Pt, int KeyState) { int Status = DROPEFFECT_NONE; for (unsigned i=0; iIsBinary()) { OsChar *e = (OsChar*) ((char*)Data->Value.Binary.Data + Data->Value.Binary.Length); OsChar *s = (OsChar*) Data->Value.Binary.Data; int len = 0; while (s < e && s[len]) { len++; } LAutoWString w ( (char16*)LNewConvertCp ( LGI_WideCharset, s, ( sizeof(OsChar) == 1 ? "utf-8" : LGI_WideCharset ), len * sizeof(*s) ) ); Insert(Cursor, w, len); Invalidate(); return DROPEFFECT_COPY; } } else if (dd.IsFileDrop()) { // We don't directly handle file drops... pass up to the parent bool FoundTarget = false; for (LViewI *p = GetParent(); p; p = p->GetParent()) { LDragDropTarget *t = p->DropTarget(); if (t) { Status = t->OnDrop(Data, Pt, KeyState); if (Status != DROPEFFECT_NONE) { FoundTarget = true; break; } } } if (!FoundTarget) { auto Wnd = GetWindow(); if (Wnd) { LDropFiles files(dd); Wnd->OnReceiveFiles(files); } } } } return Status; } void LTextView4::OnCreate() { SetWindow(this); DropTarget(true); #ifndef WINDOWS if (Ctrls.Length() == 0) #endif SetPulse(PULSE_TIMEOUT); #ifndef WINDOWS Ctrls.Add(this); #endif } void LTextView4::OnEscape(LKey &K) { } bool LTextView4::OnMouseWheel(double l) { if (VScroll) { int64 NewPos = VScroll->Value() + (int)l; NewPos = limit(NewPos, 0, (ssize_t)GetLines()); VScroll->Value(NewPos); Invalidate(); } return true; } void LTextView4::OnFocus(bool f) { Invalidate(); } ssize_t LTextView4::HitText(int x, int y, bool Nearest) { if (!Text) return 0; bool Down = y >= 0; auto Y = VScroll ? VScroll->Value() : 0; if (Y < (ssize_t)Line.Length()) y += Line[Y]->r.y1; while (Y>=0 && Y<(ssize_t)Line.Length()) { auto l = Line[Y]; if (l->r.Overlap(x, y)) { // Over a line int At = x - l->r.x1; ssize_t Char = 0; LDisplayString Ds(Font, MapText(Text + l->Start, l->Len), l->Len, 0); Char = Ds.CharAt(At, Nearest ? LgiNearest : LgiTruncate); return l->Start + Char; } else if (y >= l->r.y1 && y <= l->r.y2) { // Click horizontally before of after line if (x < l->r.x1) return l->Start; else if (x > l->r.x2) return l->Start + l->Len; } if (Down) Y++; else Y--; } // outside text area if (Down) { if (Line.Length()) { if (y > Line.Last()->r.y2) { // end of document return Size; } } } return 0; } void LTextView4::Undo() { int Old = UndoQue.GetPos(); UndoQue.Undo(); if (Old && !UndoQue.GetPos()) { Dirty = false; SendNotify(LNotifyDocChanged); } } void LTextView4::Redo() { UndoQue.Redo(); } void LTextView4::DoContextMenu(LMouse &m) { LSubMenu RClick; LAutoString ClipText; { LClipBoard Clip(this); ClipText.Reset(NewStr(Clip.Text())); } LStyle *s = HitStyle(HitText(m.x, m.y, true)); if (s) { if (OnStyleMenu(s, &RClick)) { RClick.AppendSeparator(); } } RClick.AppendItem(LLoadString(L_TEXTCTRL_CUT, "Cut"), IDM_CUT, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); RClick.AppendItem(LLoadString(L_TEXTCTRL_PASTE, "Paste"), IDM_PASTE, ClipText != 0); RClick.AppendSeparator(); RClick.AppendItem("Copy All", IDM_COPY_ALL, true); RClick.AppendItem("Select All", IDM_SELECT_ALL, true); RClick.AppendSeparator(); RClick.AppendItem(LLoadString(L_TEXTCTRL_UNDO, "Undo"), IDM_UNDO, UndoQue.CanUndo()); RClick.AppendItem(LLoadString(L_TEXTCTRL_REDO, "Redo"), IDM_REDO, UndoQue.CanRedo()); RClick.AppendSeparator(); auto i = RClick.AppendItem(LLoadString(L_TEXTCTRL_FIXED, "Fixed Width Font"), IDM_FIXED, true); if (i) i->Checked(GetFixedWidthFont()); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_AUTO_INDENT, "Auto Indent"), IDM_AUTO_INDENT, true); if (i) i->Checked(AutoIndent); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_SHOW_WHITESPACE, "Show Whitespace"), IDM_SHOW_WHITE, true); if (i) i->Checked(ShowWhiteSpace); i = RClick.AppendItem(LLoadString(L_TEXTCTRL_HARD_TABS, "Hard Tabs"), IDM_HARD_TABS, true); if (i) i->Checked(HardTabs); RClick.AppendItem(LLoadString(L_TEXTCTRL_INDENT_SIZE, "Indent Size"), IDM_INDENT_SIZE, true); RClick.AppendItem(LLoadString(L_TEXTCTRL_TAB_SIZE, "Tab Size"), IDM_TAB_SIZE, true); if (Environment) Environment->AppendItems(&RClick, NULL); int Id = 0; m.ToScreen(); switch (Id = RClick.Float(this, m)) { case IDM_FIXED: { SetFixedWidthFont(!GetFixedWidthFont()); SendNotify(LNotifyFixedWidthChanged); break; } case IDM_CUT: { Cut(); break; } case IDM_COPY: { Copy(); break; } case IDM_PASTE: { Paste(); break; } case IDM_COPY_ALL: { SelectAll(); Copy(); break; } case IDM_SELECT_ALL: { SelectAll(); break; } case IDM_UNDO: { Undo(); break; } case IDM_REDO: { Redo(); break; } case IDM_AUTO_INDENT: { AutoIndent = !AutoIndent; break; } case IDM_SHOW_WHITE: { ShowWhiteSpace = !ShowWhiteSpace; Invalidate(); break; } case IDM_HARD_TABS: { HardTabs = !HardTabs; break; } case IDM_INDENT_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", IndentSize); LInput *i = new LInput(this, s, "Indent Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) IndentSize = atoi(i->GetStr()); delete i; }); break; } case IDM_TAB_SIZE: { char s[32]; sprintf_s(s, sizeof(s), "%i", TabSize); LInput *i = new LInput(this, s, "Tab Size:", "Text"); i->DoModal([this, i](auto dlg, auto code) { if (code) SetTabSize((uint8_t)Atoi(i->GetStr().Get())); delete i; }); break; } default: { if (s) { OnStyleMenuClick(s, Id); } if (Environment) { Environment->OnMenu(this, Id, 0); } break; } } } bool LTextView4::OnStyleClick(LStyle *style, LMouse *m) { switch (style->Owner) { case STYLE_URL: { if ( (!m) || (m->Left() && m->Down() && m->Double()) ) { LString s(Text + style->Start, style->Len); if (s) OnUrl(s); return true; } break; } default: break; } return false; } bool LTextView4::OnStyleMenu(LStyle *style, LSubMenu *m) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); if (LIsValidEmail(s)) m->AppendItem(LLoadString(L_TEXTCTRL_EMAIL_TO, "New Email to..."), IDM_NEW, true); else m->AppendItem(LLoadString(L_TEXTCTRL_OPENURL, "Open URL"), IDM_OPEN, true); m->AppendItem(LLoadString(L_TEXTCTRL_COPYLINK, "Copy link location"), IDM_COPY_URL, true); return true; } default: break; } return false; } void LTextView4::OnStyleMenuClick(LStyle *style, int i) { switch (style->Owner) { case STYLE_URL: { LString s(Text + style->Start, style->Len); switch (i) { case IDM_NEW: case IDM_OPEN: { if (s) OnUrl(s); break; } case IDM_COPY_URL: { if (s) { LClipBoard Clip(this); Clip.Text(s); } break; } } break; } default: break; } } void LTextView4::OnMouseClick(LMouse &m) { bool Processed = false; m.x += ScrollX; if (m.Down()) { if (!m.IsContextMenu()) { Focus(true); ssize_t Hit = HitText(m.x, m.y, true); if (Hit >= 0) { SetCaret(Hit, m.Shift()); LStyle *s = HitStyle(Hit); if (s) Processed = OnStyleClick(s, &m); } if (!Processed && m.Double()) { d->WordSelectMode = Cursor; SelectWord(Cursor); } else { d->WordSelectMode = -1; } } else { DoContextMenu(m); return; } } if (!Processed) { Capture(m.Down()); } } int LTextView4::OnHitTest(int x, int y) { #ifdef WIN32 if (GetClient().Overlap(x, y)) { return HTCLIENT; } #endif return LView::OnHitTest(x, y); } void LTextView4::OnMouseMove(LMouse &m) { m.x += ScrollX; ssize_t Hit = HitText(m.x, m.y, true); if (IsCapturing()) { if (d->WordSelectMode < 0) { SetCaret(Hit, m.Left()); } else { ssize_t Min = Hit < d->WordSelectMode ? Hit : d->WordSelectMode; ssize_t Max = Hit > d->WordSelectMode ? Hit : d->WordSelectMode; for (SelStart = Min; SelStart > 0; SelStart--) { if (strchr(SelectWordDelim, Text[SelStart])) { SelStart++; break; } } for (SelEnd = Max; SelEnd < Size; SelEnd++) { if (strchr(SelectWordDelim, Text[SelEnd])) { break; } } Cursor = SelEnd; Invalidate(); } } } LCursor LTextView4::GetCursor(int x, int y) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); LStyle *s = NULL; if (c.Overlap(x, y)) { ssize_t Hit = HitText(x, y, true); s = HitStyle(Hit); } return s ? s->Cursor : LCUR_Ibeam; } int LTextView4::GetColumn() { int x = 0; LTextLine *l = GetTextLine(Cursor); if (l) { for (ssize_t i=l->Start; i> 1); m.Target = this; DoContextMenu(m); } else if (k.IsChar) { switch (k.vkey) { default: { // process single char input if ( !GetReadOnly() && ( (k.c16 >= ' ' || k.vkey == LK_TAB) && k.c16 != 127 ) ) { if (k.Down()) { // letter/number etc if (SelStart >= 0) { bool MultiLine = false; if (k.vkey == LK_TAB) { size_t Min = MIN(SelStart, SelEnd), Max = MAX(SelStart, SelEnd); for (size_t i=Min; iLen : 0; if (l && k.vkey == LK_TAB && (!HardTabs || IndentSize != TabSize)) { int x = GetColumn(); int Add = IndentSize - (x % IndentSize); if (HardTabs && ((x + Add) % TabSize) == 0) { int Rx = x; size_t Remove; for (Remove = Cursor; Text[Remove - 1] == ' ' && Rx % TabSize != 0; Remove--, Rx--); ssize_t Chars = (ssize_t)Cursor - Remove; Delete(Remove, Chars); Insert(Remove, &k.c16, 1); Cursor = Remove + 1; Invalidate(); } else { char16 *Sp = new char16[Add]; if (Sp) { for (int n=0; nLen : 0; SetCaret(Cursor + Add, false, Len != NewLen - 1); } DeleteArray(Sp); } } } else { char16 In = k.GetChar(); if (In == '\t' && k.Shift() && Cursor > 0) { l = GetTextLine(Cursor); if (Cursor > l->Start) { if (Text[Cursor-1] == '\t') { Delete(Cursor - 1, 1); SetCaret(Cursor, false, false); } else if (Text[Cursor-1] == ' ') { ssize_t Start = (ssize_t)Cursor - 1; while (Start >= l->Start && strchr(" \t", Text[Start-1])) Start--; int Depth = SpaceDepth(Text + Start, Text + Cursor); int NewDepth = Depth - (Depth % IndentSize); if (NewDepth == Depth && NewDepth > 0) NewDepth -= IndentSize; int Use = 0; while (SpaceDepth(Text + Start, Text + Start + Use + 1) < NewDepth) Use++; Delete(Start + Use, Cursor - Start - Use); SetCaret(Start + Use, false, false); } } } else if (In && Insert(Cursor, &In, 1)) { l = GetTextLine(Cursor); size_t NewLen = (l) ? l->Len : 0; SetCaret(Cursor + 1, false, Len != NewLen - 1); } } } return true; } break; } case LK_RETURN: #if defined MAC case LK_KEYPADENTER: #endif { if (GetReadOnly()) break; if (k.Down() && k.IsChar) { OnEnter(k); } return true; break; } case LK_BACKSPACE: { if (GetReadOnly()) break; if (k.Ctrl()) { // Ctrl+H } else if (k.Down()) { if (SelStart >= 0) { // delete selection DeleteSelection(); } else { char Del = Cursor > 0 ? Text[Cursor-1] : 0; if (Del == ' ' && (!HardTabs || IndentSize != TabSize)) { // Delete soft tab int x = GetColumn(); int Max = x % IndentSize; if (Max == 0) Max = IndentSize; ssize_t i; for (i=Cursor-1; i>=0; i--) { if (Max-- <= 0 || Text[i] != ' ') { i++; break; } } if (i < 0) i = 0; if (i < Cursor - 1) { ssize_t Del = (ssize_t)Cursor - i; Delete(i, Del); // SetCursor(i, false, false); // Invalidate(); break; } } else if (Del == '\t' && HardTabs && IndentSize != TabSize) { int x = GetColumn(); Delete(--Cursor, 1); for (int c=GetColumn(); c 0) { Delete(Cursor - 1, 1); } } } return true; break; } } } else // not a char { switch (k.vkey) { case LK_TAB: return true; case LK_RETURN: { return !GetReadOnly(); } case LK_BACKSPACE: { if (!GetReadOnly()) { if (k.Alt()) { if (k.Down()) { if (k.Ctrl()) { Redo(); } else { Undo(); } } } else if (k.Ctrl()) { if (k.Down()) { ssize_t Start = Cursor; while (IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; while (!IsWhiteSpace(Text[Cursor-1]) && Cursor > 0) Cursor--; Delete(Cursor, Start - Cursor); Invalidate(); } } return true; } break; } case LK_F3: { if (k.Down()) { DoFindNext(NULL); } return true; break; } case LK_LEFT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MIN(SelStart, SelEnd), false); } else if (Cursor > 0) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_StartOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select bool StartWhiteSpace = IsWhiteSpace(Text[n]); bool LeftWhiteSpace = n > 0 && IsWhiteSpace(Text[n-1]); if (!StartWhiteSpace || Text[n] == '\n') { n--; } // Skip ws for (; n > 0 && strchr(" \t", Text[n]); n--) ; if (Text[n] == '\n') { n--; } else if (!StartWhiteSpace || !LeftWhiteSpace) { if (IsDelimiter(Text[n])) { for (; n > 0 && IsDelimiter(Text[n]); n--); } else { for (; n > 0; n--) { //IsWordBoundry(Text[n]) if (IsWhiteSpace(Text[n]) || IsDelimiter(Text[n])) { break; } } } } if (n > 0) n++; } else { // single char n--; } SetCaret(n, k.Shift()); } } return true; break; } case LK_RIGHT: { if (k.Down()) { if (SelStart >= 0 && !k.Shift()) { SetCaret(MAX(SelStart, SelEnd), false); } else if (Cursor < Size) { ssize_t n = Cursor; #ifdef MAC if (k.System()) { goto Jump_EndOfLine; } else if (k.Alt()) #else if (k.Ctrl()) #endif { // word move/select if (IsWhiteSpace(Text[n])) { for (; nStart, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString PrevLine(Font, Text + Prev->Start, Prev->Len); ssize_t CharX = PrevLine.CharAt(ScreenX); SetCaret(Prev->Start + MIN(CharX, Prev->Len), k.Shift()); } } } return true; break; } case LK_DOWN: { if (k.Alt()) return false; if (k.Down()) { #ifdef MAC if (k.Ctrl()) goto LTextView4_PageDown; #endif auto It = GetTextLineIt(Cursor); if (It != Line.end()) { auto l = *It; It++; if (It != Line.end()) { LTextLine *Next = *It; LDisplayString CurLine(Font, Text + l->Start, Cursor-l->Start); int ScreenX = CurLine.X(); LDisplayString NextLine(Font, Text + Next->Start, Next->Len); ssize_t CharX = NextLine.CharAt(ScreenX); SetCaret(Next->Start + MIN(CharX, Next->Len), k.Shift()); } } } return true; break; } case LK_END: { if (k.Down()) { if (k.Ctrl()) { SetCaret(Size, k.Shift()); } else { #ifdef MAC Jump_EndOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { SetCaret(l->Start + l->Len, k.Shift()); } } } return true; break; } case LK_HOME: { if (k.Down()) { if (k.Ctrl()) { SetCaret(0, k.Shift()); } else { #ifdef MAC Jump_StartOfLine: #endif LTextLine *l = GetTextLine(Cursor); if (l) { char16 *Line = Text + l->Start; char16 *s; char16 SpTab[] = {' ', '\t', 0}; for (s = Line; (SubtractPtr(s,Line) < l->Len) && StrchrW(SpTab, *s); s++); ssize_t Whitespace = SubtractPtr(s, Line); if (l->Start + Whitespace == Cursor) { SetCaret(l->Start, k.Shift()); } else { SetCaret(l->Start + Whitespace, k.Shift()); } } } } return true; break; } case LK_PAGEUP: { #ifdef MAC LTextView4_PageUp: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MAX(CurLine - DisplayLines, 0)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_PAGEDOWN: { #ifdef MAC LTextView4_PageDown: #endif if (k.Down()) { LTextLine *l = GetTextLine(Cursor); if (l) { int DisplayLines = Y() / LineY; ssize_t CurLine = Line.IndexOf(l); LTextLine *New = Line.ItemAt(MIN(CurLine + DisplayLines, (ssize_t)GetLines()-1)); if (New) { SetCaret(New->Start + MIN(Cursor - l->Start, New->Len), k.Shift()); } } } return true; break; } case LK_INSERT: { if (k.Down()) { if (k.Ctrl()) { Copy(); } else if (k.Shift()) { if (!GetReadOnly()) { Paste(); } } } return true; break; } case LK_DELETE: { if (!GetReadOnly()) { if (k.Down()) { if (SelStart >= 0) { if (k.Shift()) { Cut(); } else { DeleteSelection(); } } else if (Cursor < Size && Delete(Cursor, 1)) { Invalidate(); } } return true; } break; } default: { if (k.c16 == 17) break; if (k.CtrlCmd() && !k.Alt()) { switch (k.GetChar()) { case 0xbd: // Ctrl+'-' { if (k.Down() && Font->PointSize() > 1) { Font->PointSize(Font->PointSize() - 1); OnFontChange(); Invalidate(); } break; } case 0xbb: // Ctrl+'+' { if (k.Down() && Font->PointSize() < 100) { Font->PointSize(Font->PointSize() + 1); OnFontChange(); Invalidate(); } break; } case 'a': case 'A': { if (k.Down()) { // select all SelStart = 0; SelEnd = Size; Invalidate(); } return true; break; } case 'y': case 'Y': { if (!GetReadOnly()) { if (k.Down()) { Redo(); } return true; } break; } case 'z': case 'Z': { if (!GetReadOnly()) { if (k.Down()) { if (k.Shift()) { Redo(); } else { Undo(); } } return true; } break; } case 'x': case 'X': { if (!GetReadOnly()) { if (k.Down()) { Cut(); } return true; } break; } case 'c': case 'C': { if (k.Shift()) return false; if (k.Down()) Copy(); return true; break; } case 'v': case 'V': { if (!k.Shift() && !GetReadOnly()) { if (k.Down()) { Paste(); } return true; } break; } case 'f': { if (k.Down()) { DoFind(NULL); } return true; break; } case 'g': case 'G': { if (k.Down()) { DoGoto(NULL); } return true; break; } case 'h': case 'H': { if (k.Down()) { DoReplace(NULL); } return true; break; } case 'u': case 'U': { if (!GetReadOnly()) { if (k.Down()) { DoCase(NULL, k.Shift()); } return true; } break; } case LK_RETURN: { if (!GetReadOnly() && !k.Shift()) { if (k.Down()) { OnEnter(k); } return true; } break; } } } break; } } } return false; } void LTextView4::OnEnter(LKey &k) { // enter if (SelStart >= 0) { DeleteSelection(); } char16 InsertStr[256] = {'\n', 0}; LTextLine *CurLine = GetTextLine(Cursor); if (CurLine && AutoIndent) { int WsLen = 0; for (; WsLen < CurLine->Len && WsLen < (Cursor - CurLine->Start) && strchr(" \t", Text[CurLine->Start + WsLen]); WsLen++); if (WsLen > 0) { memcpy(InsertStr+1, Text+CurLine->Start, WsLen * sizeof(char16)); InsertStr[WsLen+1] = 0; } } if (Insert(Cursor, InsertStr, StrlenW(InsertStr))) { SetCaret(Cursor + StrlenW(InsertStr), false, true); } } int LTextView4::TextWidth(LFont *f, char16 *s, int Len, int x, int Origin) { int w = x; int Size = f->TabSize(); for (char16 *c = s; SubtractPtr(c, s) < Len; ) { if (*c == 9) { w = ((((w-Origin) + Size) / Size) * Size) + Origin; c++; } else { char16 *e; for (e = c; SubtractPtr(e, s) < Len && *e != 9; e++); LDisplayString ds(f, c, SubtractPtr(e, c)); w += ds.X(); c = e; } } return w - x; } int LTextView4::ScrollYLine() { return (VScroll) ? (int)VScroll->Value() : 0; } int LTextView4::ScrollYPixel() { return ScrollYLine() * LineY; } LRect LTextView4::DocToScreen(LRect r) { r.Offset(0, d->rPadding.y1 - ScrollYPixel()); return r; } void LTextView4::OnPaintLeftMargin(LSurface *pDC, LRect &r, LColour &colour) { pDC->Colour(colour); pDC->Rectangle(&r); } void LTextView4::OnPaint(LSurface *pDC) { #if LGI_EXCEPTIONS try { #endif #if PROFILE_PAINT char s[256]; sprintf_s(s, sizeof(s), "%p::OnPaint Lines=%i Sz=%i", this, (int)Line.Length(), (int)Size); LProfile Prof(s); #endif if (d->LayoutDirty) { #if PROFILE_PAINT Prof.Add("PourText"); #endif PourText(d->DirtyStart, d->DirtyLen); #if PROFILE_PAINT Prof.Add("PourStyle"); #endif PourStyle(d->DirtyStart, d->DirtyLen); d->LayoutDirty = false; } #if PROFILE_PAINT Prof.Add("Setup"); #endif LRect r = GetClient(); r.x2 += ScrollX; int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox+ScrollX, Oy); #if 0 // Coverage testing... pDC->Colour(Rgb24(255, 0, 255), 24); pDC->Rectangle(); #endif LSurface *pOut = pDC; bool DrawSel = false; bool HasFocus = Focus(); // printf("%s:%i - HasFocus = %i\n", _FL, HasFocus); LColour SelectedText(HasFocus ? LColour(L_FOCUS_SEL_FORE) : LColour(L_NON_FOCUS_SEL_FORE)); LColour SelectedBack(HasFocus ? LColour(L_FOCUS_SEL_BACK) : LColour(L_NON_FOCUS_SEL_BACK)); LCss::ColorDef ForeDef, BkDef; if (GetCss()) { ForeDef = GetCss()->Color(); BkDef = GetCss()->BackgroundColor(); } LColour Fore(ForeDef.Type == LCss::ColorRgb ? LColour(ForeDef.Rgb32, 32) : LColour(L_TEXT)); LColour Back ( /*!ReadOnly &&*/ BkDef.Type == LCss::ColorRgb ? LColour(BkDef.Rgb32, 32) : Enabled() ? LColour(L_WORKSPACE) : LColour(L_MED) ); // LColour Whitespace = Fore.Mix(Back, 0.85f); if (!Enabled()) { Fore = LColour(L_LOW); Back = LColour(L_MED); } #ifdef DOUBLE_BUFFER_PAINT LMemDC *pMem = new LMemDC; pOut = pMem; #endif if (Text && Font #ifdef DOUBLE_BUFFER_PAINT && pMem && pMem->Create(r.X()-d->rPadding.x1, LineY, GdcD->GetBits()) #endif ) { ssize_t SelMin = MIN(SelStart, SelEnd); ssize_t SelMax = MAX(SelStart, SelEnd); // font properties Font->Colour(Fore, Back); // Font->WhitespaceColour(Whitespace); Font->Transparent(false); // draw margins pDC->Colour(PAINT_BORDER); // top margin pDC->Rectangle(0, 0, r.x2, d->rPadding.y1-1); // left margin { LRect LeftMargin(0, d->rPadding.y1, d->rPadding.x1-1, r.y2); OnPaintLeftMargin(pDC, LeftMargin, PAINT_BORDER); } // draw lines of text int k = ScrollYLine(); LTextLine *l = NULL; int Dy = 0; if (k < Line.Length()) Dy = -Line[k]->r.y1; ssize_t NextSelection = (SelStart != SelEnd) ? SelMin : -1; // offset where selection next changes if (k < Line.Length() && (l = Line[k]) && SelStart >= 0 && SelStart < l->Start && SelEnd > l->Start) { // start of visible area is in selection // init to selection colour DrawSel = true; Font->Colour(SelectedText, SelectedBack); NextSelection = SelMax; } StyleIter Si = Style.begin(); LStyle *NextStyle = GetNextStyle(Si, (l) ? l->Start : 0); DocOffset = (l) ? l->r.y1 : 0; #if PROFILE_PAINT Prof.Add("foreach Line loop"); #endif // loop through all visible lines int y = d->rPadding.y1; while ( k < Line.Length() && (l = Line[k]) && l->r.y1+Dy < r.Y()) { LRect Tr = l->r; #ifdef DOUBLE_BUFFER_PAINT Tr.Offset(-Tr.x1, -Tr.y1); #else Tr.Offset(0, y - Tr.y1); #endif //LRect OldTr = Tr; // deal with selection change on beginning of line if (NextSelection == l->Start) { // selection change DrawSel = !DrawSel; NextSelection = (NextSelection == SelMin) ? SelMax : -1; } if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } // How many chars on this line have we // processed so far: ssize_t Done = 0; bool LineHasSelection = NextSelection >= l->Start && NextSelection < l->Start + l->Len; // Fractional pixels we have moved so far: int MarginF = d->rPadding.x1 << LDisplayString::FShift; int FX = MarginF; int FY = Tr.y1 << LDisplayString::FShift; // loop through all sections of similar text on a line while (Done < l->Len) { // decide how big this block is int RtlTrailingSpace = 0; ssize_t Cur = l->Start + Done; ssize_t Block = l->Len - Done; // check for style change if (NextStyle && (ssize_t)NextStyle->End() <= l->Start) NextStyle = GetNextStyle(Si); if (NextStyle) { // start if (l->Overlap(NextStyle->Start) && NextStyle->Start > Cur && NextStyle->Start - Cur < Block) { Block = NextStyle->Start - Cur; } // end ssize_t StyleEnd = NextStyle->Start + NextStyle->Len; if (l->Overlap(StyleEnd) && StyleEnd > Cur && StyleEnd - Cur < Block) { Block = StyleEnd - Cur; } } // check for next selection change // this may truncate the style if (NextSelection > Cur && NextSelection - Cur < Block) { Block = NextSelection - Cur; } LAssert(Block != 0); // sanity check if (NextStyle && // There is a style (Cur < SelMin || Cur >= SelMax) && // && we're not drawing a selection block Cur >= NextStyle->Start && // && we're inside the styled area Cur < NextStyle->Start+NextStyle->Len) { LFont *Sf = NextStyle->Font ? NextStyle->Font : Font; if (Sf) { // draw styled text if (NextStyle->Fore.IsValid()) Sf->Fore(NextStyle->Fore); if (NextStyle->Back.IsValid()) Sf->Back(NextStyle->Back); else if (l->Back.IsValid()) Sf->Back(l->Back); else Sf->Back(Back); Sf->Transparent(false); LAssert(l->Start + Done >= 0); LDisplayString Ds( Sf, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); if (NextStyle->Decor == LCss::TextDecorSquiggle) { pOut->Colour(NextStyle->DecorColour); int x = FX >> LDisplayString::FShift; int End = x + Ds.X(); while (x < End) { pOut->Set(x, Tr.y2-(x%2)); x++; } } FX += Ds.FX(); LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Sf->Colour(fore, back); } else LAssert(0); } else { // draw a block of normal text LAssert(l->Start + Done >= 0); LDisplayString Ds( Font, MapText(Text + (l->Start + Done), Block, RtlTrailingSpace != 0), Block + RtlTrailingSpace); Ds.SetDrawOffsetF(FX - MarginF); Ds.ShowVisibleTab(ShowWhiteSpace); Ds.FDraw(pOut, FX, FY, 0, LineHasSelection); FX += Ds.FX(); } if (NextStyle && Cur+Block >= NextStyle->Start+NextStyle->Len) { // end of this styled block NextStyle = GetNextStyle(Si); } if (NextSelection == Cur+Block) { // selection change DrawSel = !DrawSel; if (DrawSel) { Font->Colour(SelectedText, SelectedBack); } else { LColour fore = l->c.IsValid() ? l->c : Fore; LColour back = l->Back.IsValid() ? l->Back : Back; Font->Colour(fore, back); } NextSelection = (NextSelection == SelMin) ? SelMax : -1; } Done += Block + RtlTrailingSpace; } // end block loop Tr.x1 = FX >> LDisplayString::FShift; // eol processing ssize_t EndOfLine = l->Start+l->Len; if (EndOfLine >= SelMin && EndOfLine < SelMax) { // draw the '\n' at the end of the line as selected // LColour bk = Font->Back(); pOut->Colour(Font->Back()); pOut->Rectangle(Tr.x2, Tr.y1, Tr.x2+7, Tr.y2); Tr.x2 += 7; } else Tr.x2 = Tr.x1; // draw any space after text pOut->Colour(PAINT_AFTER_LINE); pOut->Rectangle(Tr.x2, Tr.y1, r.x2, Tr.y2); // cursor? if (HasFocus) { // draw the cursor if on this line if (Cursor >= l->Start && Cursor <= l->Start+l->Len) { CursorPos.ZOff(1, LineY-1); ssize_t At = Cursor-l->Start; LDisplayString Ds(Font, MapText(Text+l->Start, At), At); Ds.ShowVisibleTab(ShowWhiteSpace); int CursorX = Ds.X(); CursorPos.Offset(d->rPadding.x1 + CursorX, Tr.y1); if (CanScrollX) { // Cursor on screen check LRect Scr = GetClient(); Scr.Offset(ScrollX, 0); LRect Cur = CursorPos; if (Cur.x2 > Scr.x2 - 5) // right edge check { ScrollX = ScrollX + Cur.x2 - Scr.x2 + 40; Invalidate(); } else if (Cur.x1 < Scr.x1 && ScrollX > 0) { ScrollX = MAX(0, Cur.x1 - 40); Invalidate(); } } if (Blink) { LRect c = CursorPos; #ifdef DOUBLE_BUFFER_PAINT c.Offset(-d->rPadding.x1, -y); #endif pOut->Colour(!ReadOnly ? Fore : LColour(192, 192, 192)); pOut->Rectangle(&c); } #if WINNATIVE HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { COMPOSITIONFORM Cf; Cf.dwStyle = CFS_POINT; Cf.ptCurrentPos.x = CursorPos.x1; Cf.ptCurrentPos.y = CursorPos.y1; ImmSetCompositionWindow(hIMC, &Cf); ImmReleaseContext(Handle(), hIMC); } #endif } } #if DRAW_LINE_BOXES { uint Style = pDC->LineStyle(LSurface::LineAlternate); LColour Old = pDC->Colour(LColour::Red); pDC->Box(&OldTr); pDC->Colour(Old); pDC->LineStyle(Style); LString s; s.Printf("%i, %i", Line.IndexOf(l), l->Start); LDisplayString ds(LSysFont, s); LSysFont->Transparent(true); ds.Draw(pDC, OldTr.x2 + 2, OldTr.y1); } #endif #ifdef DOUBLE_BUFFER_PAINT // dump to screen pDC->Blt(d->rPadding.x1, y, pOut); #endif y += LineY; k++; } // end of line loop // draw any space under the lines if (y <= r.y2) { pDC->Colour(Back); // pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(d->rPadding.x1, y, r.x2, r.y2); } #ifdef DOUBLE_BUFFER_PAINT DeleteObj(pMem); #endif } else { // default drawing: nothing pDC->Colour(Back); pDC->Rectangle(&r); } // _PaintTime = LCurrentTime() - StartTime; #ifdef PAINT_DEBUG if (GetNotify()) { char s[256]; sprintf_s(s, sizeof(s), "Pour:%i Style:%i Paint:%i ms", _PourTime, _StyleTime, _PaintTime); LMessage m = CreateMsg(DEBUG_TIMES_MSG, 0, (int)s); GetNotify()->OnEvent(&m); } #endif // printf("PaintTime: %ims\n", _PaintTime); #if LGI_EXCEPTIONS } catch (...) { LgiMsg(this, "LTextView4::OnPaint crashed.", "Lgi"); } #endif } LMessage::Result LTextView4::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_TEXT_UPDATE_NAME: { if (d->Lock(_FL)) { Name(d->SetName); d->SetName.Empty(); d->Unlock(); } break; } case M_TEXTVIEW_FIND: { if (InThread()) DoFindNext(NULL); else LgiTrace("%s:%i - Not in thread.\n", _FL); break; } case M_TEXTVIEW_REPLACE: { // DoReplace(); break; } case M_CUT: { Cut(); break; } case M_COPY: { Copy(); break; } case M_PASTE: { Paste(); break; } #if defined WIN32 case WM_GETTEXTLENGTH: { return Size; } case WM_GETTEXT: { int Chars = (int)Msg->A(); char *Out = (char*)Msg->B(); if (Out) { char *In = (char*)LNewConvertCp(LAnsiToLgiCp(), NameW(), LGI_WideCharset, Chars); if (In) { int Len = (int)strlen(In); memcpy(Out, In, Len); DeleteArray(In); return Len; } } return 0; } /* This is broken... the IME returns garbage in the buffer. :( case WM_IME_COMPOSITION: { if (Msg->b & GCS_RESULTSTR) { HIMC hIMC = ImmGetContext(Handle()); if (hIMC) { int Size = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0); char *Buf = new char[Size]; if (Buf) { ImmGetCompositionString(hIMC, GCS_RESULTSTR, Buf, Size); char16 *Utf = (char16*)LNewConvertCp(LGI_WideCharset, Buf, LAnsiToLgiCp(), Size); if (Utf) { Insert(Cursor, Utf, StrlenW(Utf)); DeleteArray(Utf); } DeleteArray(Buf); } ImmReleaseContext(Handle(), hIMC); } return 0; } break; } */ #endif } return LLayout::OnEvent(Msg); } int LTextView4::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_VSCROLL && VScroll) { if (n.Type == LNotifyScrollBarCreate) { UpdateScrollBars(); } Invalidate(); } return 0; } void LTextView4::InternalPulse() { if (!ReadOnly) { uint64 Now = LCurrentTime(); if (!BlinkTs) BlinkTs = Now; else if (Now - BlinkTs > CURSOR_BLINK) { Blink = !Blink; LRect p = CursorPos; p.Offset(-ScrollX, 0); Invalidate(&p); BlinkTs = Now; } } if (PartialPour) PourText(Size, 0); } void LTextView4::OnPulse() { #ifdef WINDOWS InternalPulse(); #else for (auto c: Ctrls) c->InternalPulse(); #endif } void LTextView4::OnUrl(char *Url) { if (Environment) Environment->OnNavigate(this, Url); else { LUri u(Url); bool Email = LIsValidEmail(Url); const char *Proto = Email ? "mailto" : u.sProtocol; LString App = LGetAppForProtocol(Proto); if (App) LExecute(App, Url); else LgiMsg(this, "Failed to find application for protocol '%s'", "Error", MB_OK, Proto); } } bool LTextView4::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = 32; Inf.Width.Max = -1; Inf.Height.Min = (Font ? Font->GetHeight() : 18) + 4; Inf.Height.Max = -1; return true; } +#if DEBUG_EDIT_LOG +#include "lgi/common/StructuredIo.h" + +inline void StructIo(LStructuredIo &io, LTextView4::EditLog &s) +{ + auto obj = io.StartObj("LTextView4::EditLog"); + + io.Int(s.Type, "type"); + io.Int(s.Length, "len"); + if (io.GetWrite()) + { + io.String(s.Str.AddressOf(), s.Str.Length(), "str"); + } + else + { + io.Decode([&s](auto type, auto sz, auto ptr, auto name) + { + if (type == GV_WSTRING && ptr && sz > 0) + s.Str.Add((char16*)ptr, sz/sizeof(char16)); + }); + } +} + +inline void StructIo(LStructuredIo &io, LTextView4::LTextLine &s) +{ + auto obj = io.StartObj("LTextView4::LTextLine"); + + io.Int(s.Start, "start"); + io.Int(s.Len, "len"); + StructIo(io, s.r); + StructIo(io, s.c); + StructIo(io, s.Back); + io.Int(s.NewLine, "newLine"); +} + +#include "lgi/common/StructuredLog.h" + +void LTextView4::SaveLog(const char *File) +{ + if (!FirstErrorLog) + return; + + LStructuredLog log(File, true); + auto &io = log.GetIo(); + + for (auto e: Edits) + log.Log(*e); + + for (auto ln: Line) + log.Log(*ln); + + log.Log("Size:", Size); + io.String(Text, Size, "Text"); + io.String(d->LastError, "LastError"); + io.String(LogLines(), "Lines"); + FirstErrorLog = false; +} + +void LTextView4::LoadLog(const char *File) +{ + LStructuredLog log(File, false); + Edits.DeleteObjects(); + log.Read([this](auto type, auto size, auto ptr, auto msg) + { + int asd=0; + }); +} +#endif + /////////////////////////////////////////////////////////////////////////////// class LTextView4_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LTextView4") == 0) { return new LTextView4(-1, 0, 0, 2000, 2000); } return 0; } } TextView4_Factory; diff --git a/src/common/Widgets/Edit.cpp b/src/common/Widgets/Edit.cpp --- a/src/common/Widgets/Edit.cpp +++ b/src/common/Widgets/Edit.cpp @@ -1,281 +1,281 @@ // \file // \author Matthew Allen (fret@memecode.com) #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" /////////////////////////////////////////////////////////////////////////////////////////// // Edit #include "lgi/common/TextView3.h" class _OsFontType : public LFontType { public: LFont *Create(LSurface *pDC = NULL) { return LSysFont; } }; static _OsFontType SysFontType; class LEditPrivate { public: bool FocusOnCreate; bool Multiline; bool Password; bool NotificationProcessed; LAutoString EmptyTxt; LEditPrivate() { FocusOnCreate = false; NotificationProcessed = false; } }; void LEdit::KeyProcessed() { d->NotificationProcessed = true; } LEdit::LEdit(int id, int x, int y, int cx, int cy, const char *name) : #if WINNATIVE ResObject(Res_EditBox) #else LTextView3(id, x, y, cx, cy, &SysFontType) #endif { #if !WINNATIVE SetObjectName(Res_EditBox); SetUrlDetect(false); - SetWrapType(TEXTED_WRAP_NONE); + SetWrapType(L_WRAP_NONE); #endif _OsFontType Type; d = new LEditPrivate; LDisplayString Ds(LSysFont, (char*)(name?name:"A")); if (cx < 0) cx = Ds.X() + 6; if (cy < 0) cy = Ds.Y() + 4; d->Multiline = false; d->Password = false; Sunken(true); if (name) Name(name); LRect r(x, y, x+MAX(cx, 10), y+MAX(cy, 10)); SetPos(r); LResources::StyleElement(this); } LEdit::~LEdit() { DeleteObj(d); } void LEdit::SetEmptyText(const char *EmptyText) { d->EmptyTxt.Reset(NewStr(EmptyText)); Invalidate(); } void LEdit::SendNotify(LNotification n) { if (n.Type == LNotifyDocChanged) return LTextView3::SendNotify(n); else if (n.Type == LNotifyReturnKey || n.Type == LNotifyEscapeKey) return LTextView3::SendNotify(n); } LRange LEdit::GetSelectionRange() { LRange r; ssize_t Sel = LTextView3::GetCaret(false); ssize_t Cur = LTextView3::GetCaret(); if (Sel < Cur) { r.Start = Sel; r.Len = Cur - Sel; } else { r.Start = Cur; r.Len = Sel - Cur; } return r; } void LEdit::Select(int Start, int Len) { LTextView3::SetCaret(Start, false); LTextView3::SetCaret(Start + (Len > 0 ? Len : 0x7fffffff) - 1, true); } ssize_t LEdit::GetCaret(bool Cursor) { return LTextView3::GetCaret(Cursor); } void LEdit::SetCaret(size_t Pos, bool Select, bool ForceFullUpdate) { LTextView3::SetCaret(Pos, Select, ForceFullUpdate); } void LEdit::Value(int64 i) { char Str[32]; snprintf(Str, sizeof(Str), LPrintfInt64, i); Name(Str); } int64 LEdit::Value() { auto n = Name(); return (n) ? atoi(n) : 0; } bool LEdit::MultiLine() { return d->Multiline; } void LEdit::MultiLine(bool m) { d->Multiline = m; } bool LEdit::Password() { return d->Password; } void LEdit::Password(bool m) { SetObscurePassword(d->Password = m); } bool LEdit::SetScrollBars(bool x, bool y) { // Prevent scrollbars if the control is not multiline. return LTextView3::SetScrollBars(x, y && d->Multiline); } void LEdit::OnPaint(LSurface *pDC) { LTextView3::OnPaint(pDC); if (!ValidStr(Name()) && d->EmptyTxt && !Focus()) { LFont *f = GetFont(); LDisplayString ds(f, d->EmptyTxt); static int diff = -1; if (diff < 0) diff = abs(LColour(L_MED).GetGray()-LColour(L_WORKSPACE).GetGray()); f->Colour(diff < 30 ? L_LOW : L_MED, L_WORKSPACE); f->Transparent(true); ds.Draw(pDC, 2, 2); } } bool LEdit::OnKey(LKey &k) { d->NotificationProcessed = false; switch (k.vkey) { case LK_RETURN: #ifndef LINUX case LK_KEYPADENTER: #endif case LK_ESCAPE: case LK_BACKSPACE: case LK_DELETE: { if (k.Down()) SendNotify(LNotification(k)); break; } } if ( !d->Multiline && ( k.vkey == LK_TAB || k.vkey == LK_RETURN || #ifndef LINUX k.vkey == LK_KEYPADENTER || #endif k.vkey == LK_ESCAPE ) ) { return d->NotificationProcessed; } return LTextView3::OnKey(k); } void LEdit::OnEnter(LKey &k) { if (d->Multiline) LTextView3::OnEnter(k); else SendNotify(LNotification(k)); } bool LEdit::Paste() { LClipBoard Clip(this); LAutoWString Mem; char16 *t = Clip.TextW(); if (!t) // ala Win9x { char *s = Clip.Text(); if (s) { Mem.Reset(Utf8ToWide(s)); t = Mem; } } if (!t) return false; if (SelStart >= 0) { DeleteSelection(); } // remove '\r's char16 *in = t, *out = t; for (; *in; in++) { if (*in == '\n') { if (d->Multiline) *out++ = *in; } else if (*in != '\r') { *out++ = *in; } } *out++ = 0; // insert text size_t Len = StrlenW(t); Insert(Cursor, t, Len); LTextView3::SetCaret(Cursor+Len, false, true); // Multiline return true; } diff --git a/src/common/Widgets/FilterUi.cpp b/src/common/Widgets/FilterUi.cpp --- a/src/common/Widgets/FilterUi.cpp +++ b/src/common/Widgets/FilterUi.cpp @@ -1,1286 +1,1286 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/FilterUi.h" #include "lgi/common/Path.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #define FILTER_DRAG_FORMAT "Scribe.FilterItem" #define IconSize 15 static LColour TransparentBlk(0, 0, 0, 0); static LColour White(255, 255, 255); static LColour Black(0, 0, 0); static LColour ContentColour(0, 0, 0, 160); // Field size #define SIZE_NOT 45 #define SIZE_FIELD_NAME 130 #ifdef MAC #define SIZE_OP 85 #else #define SIZE_OP 75 #endif #define SIZE_VALUE 150 enum FilterIcon { IconNewCond, IconNewAnd, IconNewOr, IconDelete, IconMoveUp, IconMoveDown, IconOptions, IconDropDown, IconBoolFalse, IconBoolTrue, IconMax, }; #define IconAlpha 255 static LColour IconColour[IconMax] = { LColour(192, 192, 192, IconAlpha), // new condition LColour(118, 160, 230, IconAlpha), // new and LColour(32, 224, 32, IconAlpha), // new or LColour(230, 90, 65, IconAlpha), // delete LColour(192, 192, 192, IconAlpha), // up LColour(192, 192, 192, IconAlpha), // down LColour(192, 192, 192, IconAlpha), // options LColour(192, 192, 192, IconAlpha), // drop down LColour(255, 192, 0, IconAlpha), // bool false LColour(255, 192, 0, IconAlpha), // bool true }; static const char *IconName[IconMax]; static const char **GetIconNames() { if (!IconName[0]) { int i = 0; IconName[i++] = LLoadString(L_FUI_NEW_CONDITION, "New Condition"); IconName[i++] = LLoadString(L_FUI_NEW_AND, "New And"); IconName[i++] = LLoadString(L_FUI_NEW_OR, "New Or"); IconName[i++] = LLoadString(L_FUI_DELETE, "Delete"); IconName[i++] = LLoadString(L_FUI_MOVE_UP, "Move Up"); IconName[i++] = LLoadString(L_FUI_MOVE_DOWN, "Move Down"); i++; IconName[i++] = LLoadString(L_FUI_OPTIONS, "Options"); i++; IconName[i++] = LLoadString(L_FUI_NOT, "Not"); } return IconName; } class LFilterTree : public LTree, public LDragDropTarget { friend class LFilterItem; public: LFilterTree() : LTree(-1, 0, 0, 100, 100) { } void OnCreate() { SetWindow(this); } void OnFocus(bool f) { if (f && !Selection()) { LTreeItem *n = GetChild(); if (n) n->Select(true); } } // Dnd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) { LFilterItem *i = dynamic_cast(ItemAtPoint(Pt.x, Pt.y)); if (!i || i->GetNode() == LNODE_NEW) return DROPEFFECT_NONE; SelectDropTarget(i); Formats.Supports(FILTER_DRAG_FORMAT); return DROPEFFECT_MOVE; } int OnDrop(LArray &Data, LPoint Pt, int KeyState) { SelectDropTarget(NULL); LFilterItem *Target = dynamic_cast(ItemAtPoint(Pt.x, Pt.y)); if (!Target) return DROPEFFECT_NONE; if (Target->GetNode() == LNODE_COND) { Target = dynamic_cast(Target->GetParent()); if (!Target) return DROPEFFECT_NONE; } for (unsigned i=0; iGetNode() == LNODE_OR || Target->GetNode() == LNODE_AND ) ) { (*Item)->Remove(); int Idx = 0; for (LFilterItem *ti = dynamic_cast(Target->GetChild()); ti && ti->GetNode() != LNODE_NEW; ti = dynamic_cast(ti->GetNext())) { Idx++; } Target->Insert(*Item, Idx); } } } } return DROPEFFECT_NONE; } }; template void HalveAlpha(T *p, int width) { T *e = p + width; while (p < e) { p->a >>= 1; p++; } } class LFilterViewPrivate { public: LFilterView *View; LAutoPtr Tree; bool ShowLegend; LRect Info; LArray Icons; FilterUi_Menu Callback; void *CallbackData; LAutoPtr dsNot; LAutoPtr dsAnd; LAutoPtr dsOr; LAutoPtr dsNew; LAutoPtr dsLegend; LArray OpNames; LFilterViewPrivate(LFilterView *v) { View = v; dsNot.Reset(new LDisplayString(LSysFont, (char*)LLoadString(L_FUI_NOT, "Not"))); dsNew.Reset(new LDisplayString(LSysBold, (char*)LLoadString(L_FUI_NEW, "New"))); dsAnd.Reset(new LDisplayString(LSysBold, (char*)LLoadString(L_FUI_AND, "And"))); dsOr.Reset(new LDisplayString(LSysBold, (char*)LLoadString(L_FUI_OR, "Or"))); dsLegend.Reset(new LDisplayString(LSysBold, (char*)LLoadString(L_FUI_LEGEND, "Legend:"))); ShowLegend = true; Callback = 0; CallbackData = 0; for (int i=0; iCreate(IconSize, IconSize, System32BitColourSpace)) { Draw(Icons[i], IconColour[i], (FilterIcon)i); } } } ~LFilterViewPrivate() { Icons.DeleteObjects(); OpNames.DeleteArrays(); } void Draw(LSurface *pDC, LColour c, FilterIcon Icon) { // Clear to transparent black pDC->Colour(TransparentBlk); pDC->Rectangle(); // Draw the icon backrgound double n = pDC->X() - 2; LPointF Ctr(n/2, n), Rim(n/2, 0); if (1) { // Shadow LPath p; double z = ((double)pDC->X()-0.5) / 2; p.Circle(z, z, z); LSolidBrush b(Rgba32(0, 0, 0, 40)); p.Fill(pDC, b); } if (1) { LPath p; p.Circle(n/2, n/2, n/2); LBlendStop r[] = { {0.0, White.Mix(c, 0.1f).c32()}, {0.3, White.Mix(c, 0.3f).c32()}, {0.6, c.c32()}, {1.0, Black.Mix(c, 0.8f).c32()}, }; LRadialBlendBrush b(Ctr, Rim, CountOf(r), r); p.Fill(pDC, b); } if (1) { // Draw the highlight LPath p; p.Circle(n/2, n/2, n/2); LBlendStop Highlight[] = { {0.0, TransparentBlk.c32()}, {0.7, TransparentBlk.c32()}, {0.9, White.c32()}, }; LLinearBlendBrush b2(Ctr, Rim, CountOf(Highlight), Highlight); p.Fill(pDC, b2); } double k[4] = { n * 3 / 14, n * 3 / 7, n * 4 / 7, n * 11 / 14 }; int Plus[][2] = { {0, 1}, {0, 2}, {1, 2}, {1, 3}, {2, 3}, {2, 2}, {3, 2}, {3, 1}, {2, 1}, {2, 0}, {1, 0}, {1, 1}, {0, 1} }; // Draw the icon content switch (Icon) { default: break; case IconNewCond: case IconBoolTrue: { LPath p; p.Circle(n/2, n/2, n/6); LSolidBrush b(ContentColour); p.Fill(pDC, b); break; } case IconNewAnd: case IconDelete: { LPath p; LPointF Ctr(n/2, n/2); for (int i=0; iY(); y++) { switch (pDC->GetColourSpace()) { #define HalveAlphaCase(name) \ case Cs##name: HalveAlpha((L##name*)(*pDC)[y], pDC->X()); break HalveAlphaCase(Rgba32); HalveAlphaCase(Bgra32); HalveAlphaCase(Argb32); HalveAlphaCase(Abgr32); default: LAssert(pDC->GetBits() < 32); break; } } break; } } } if (pDC->IsPreMultipliedAlpha()) { pDC->ConvertPreMulAlpha(true); } } }; /////////////////////////////////////////////////////////////////////////////// class LFilterItemPrivate { public: - GFilterNode Node; + LFilterNode Node; LFilterViewPrivate *Data; LRect Btns[IconMax]; LRect NotBtn; LRect FieldBtn, FieldDropBtn; LRect OpBtn, OpDropBtn; LRect ValueBtn; bool Not; char *Field, *Value; int Op; LEdit *FieldEd, *ValueEd; LCombo *OpCbo; LFilterItemPrivate() { Not = false; Data = 0; Node = LNODE_NULL; EmptyRects(); Field = Value = 0; Op = 0; FieldEd = ValueEd = 0; OpCbo = 0; } ~LFilterItemPrivate() { DeleteArray(Field); DeleteArray(Value); DeleteObj(FieldEd); DeleteObj(OpCbo); DeleteObj(ValueEd); } void EmptyRects() { for (int i=0; iData = Data; SetText("Node"); SetNode(Node); } LFilterItem::~LFilterItem() { DeleteObj(d); } bool LFilterItem::GetNot() { return d->Not; } const char *LFilterItem::GetField() { if (d->FieldEd) return d->FieldEd->Name(); return d->Field; } int LFilterItem::GetOp() { if (d->OpCbo != NULL) return (int)d->OpCbo->Value(); return d->Op; } const char *LFilterItem::GetValue() { if (d->ValueEd) return d->ValueEd->Name(); return d->Value; } void LFilterItem::SetNot(bool b) { d->Not = b; Update(); } void LFilterItem::SetField(const char *s) { DeleteArray(d->Field); d->Field = NewStr(s); if (d->FieldEd) d->FieldEd->Name(s); else Update(); } void LFilterItem::SetOp(int s) { d->Op = s; if (d->OpCbo) d->OpCbo->Value(s); else Update(); } void LFilterItem::SetValue(char *s) { DeleteArray(d->Value); d->Value = NewStr(s); if (d->ValueEd) d->ValueEd->Name(s); else Update(); } #define StartCtrl(Rc, Ed, Name, Obj) \ if (!d->Ed) \ { \ LPoint sc = d->Data->Tree->_ScrollPos(); \ d->Ed = new Obj(n++, c.x1 + Pos->x1 + Rc.x1 - sc.x, c.y1 + Pos->y1 + Rc.y1 - sc.y, \ Rc.X(), Rc.Y(), \ Name); \ if (d->Ed) \ { \ d->Ed->Attach(GetTree()); \ } \ } \ else \ { \ LRect r = Rc; \ LPoint sc = d->Data->Tree->_ScrollPos(); \ r.Offset(c.x1 + Pos->x1 - sc.x, c.y1 + Pos->y1 - sc.y); \ d->Ed->SetPos(r); \ } #define EndEdit(Rc, Ed, Var) \ if (d->Ed) \ { \ DeleteArray(d->Var); \ d->Var = NewStr(d->Ed->Name()); \ DeleteObj(d->Ed); \ } void LFilterItem::_PourText(LPoint &Size) { Size.y = LSysFont->GetHeight() + #ifdef MAC 14; // Not sure what the deal is here... it just looks better. #else 10; #endif switch (d->Node) { case LNODE_NEW: case LNODE_AND: case LNODE_OR: Size.x = 120; break; default: Size.x = 126 + SIZE_NOT + SIZE_FIELD_NAME + SIZE_OP + SIZE_VALUE; break; } } void LFilterItem::_PaintText(LItem::ItemPaintCtx &Ctx) { LRect *Pos = _GetRect(TreeItemText); // Create a memory context LMemDC Buf(Pos->X(), Pos->Y(), System32BitColourSpace); Buf.Colour(L_WORKSPACE); Buf.Rectangle(0); // Draw the background double ox = 1, oy = 1; double r = (double)(Pos->Y() - (oy * 2)) / 2; LColour BackCol = TransparentBlk; LColour Workspace(L_WORKSPACE); switch (d->Node) { case LNODE_NEW: BackCol.Rgb(0xd0, 0xd0, 0xd0); break; case LNODE_COND: default: BackCol = LColour(L_MED); break; case LNODE_AND: case LNODE_OR: BackCol = White.Mix(IconColour[d->Node], 0.3f); break; } bool IsTarget = IsDropTarget(); if (Select() || IsTarget) { LPath p; LSolidBrush b(LSysColour(L_FOCUS_SEL_BACK)); LRectF PosF(0, 0, Pos->X()-1, Pos->Y()); p.RoundRect(PosF, PosF.Y()/2); p.Fill(&Buf, b); } if (!IsTarget) { LPath p; LSolidBrush b(BackCol); LRectF PosF(0, 0, Pos->X()-(ox*2)-1, Pos->Y()-(oy*2)); PosF.Offset(ox, oy); p.RoundRect(PosF, r); p.Fill(&Buf, b); } if (d->Node == LNODE_NEW) { LPath p; LSolidBrush b(Workspace); LRectF PosF(0, 0, Pos->X()-(ox*2)-3, Pos->Y()-(oy*2)-2); PosF.Offset(ox+1, oy+1); p.RoundRect(PosF, r); p.Fill(&Buf, b); } // Clear the button rects d->EmptyRects(); // Draw the content int IconY = (int)(oy + r) - (IconSize / 2); switch (d->Node) { case LNODE_NEW: { Buf.Op(GDC_ALPHA); int x = Pos->X() - (int)r - IconSize + 4; int y = (int)(oy + r) - (IconSize / 2); // New OR d->Btns[IconNewOr].ZOff(IconSize-1, IconSize-1); d->Btns[IconNewOr].Offset(x, y); Buf.Blt(x, y, d->Data->Icons[IconNewOr]); x -= 2 + IconSize; // New AND d->Btns[IconNewAnd].ZOff(IconSize-1, IconSize-1); d->Btns[IconNewAnd].Offset(x, y); Buf.Blt(x, y, d->Data->Icons[IconNewAnd]); x -= 2 + IconSize; if (!IsRoot()) { // New Condition d->Btns[IconNewCond].ZOff(IconSize-1, IconSize-1); d->Btns[IconNewCond].Offset(x, y); Buf.Blt(x, y, d->Data->Icons[IconNewCond]); } break; } default: { Buf.Op(GDC_ALPHA); int x = (int)((double)Pos->X() - r - (IconSize / 2) - 2); // Delete d->Btns[IconDelete].ZOff(IconSize-1, IconSize-1); d->Btns[IconDelete].Offset(x, IconY); Buf.Blt(x, IconY, d->Data->Icons[IconDelete]); x -= 2 + IconSize; if (!IsRoot()) { // Move down d->Btns[IconMoveDown].ZOff(IconSize-1, IconSize-1); d->Btns[IconMoveDown].Offset(x, IconY); Buf.Blt(x, IconY, d->Data->Icons[IconMoveDown]); x -= 2 + IconSize; // Move up d->Btns[IconMoveUp].ZOff(IconSize-1, IconSize-1); d->Btns[IconMoveUp].Offset(x, IconY); Buf.Blt(x, IconY, d->Data->Icons[IconMoveUp]); x -= 2 + IconSize; } if (d->Node == LNODE_AND || d->Node == LNODE_OR) { // Configure d->Btns[IconOptions].ZOff(IconSize-1, IconSize-1); d->Btns[IconOptions].Offset(x, IconY); Buf.Blt(x, IconY, d->Data->Icons[IconOptions]); x -= 2 + IconSize; } break; } } switch (d->Node) { default: break; case LNODE_COND: { // Layout stuff d->FieldBtn.ZOff(SIZE_FIELD_NAME, (int)(r * 2) - 5); d->FieldBtn.Offset((int)(ox + r)+SIZE_NOT, (int)oy + 2); d->FieldDropBtn.ZOff(IconSize-1, IconSize-1); d->FieldDropBtn.Offset(d->FieldBtn.x2 + 1, d->FieldBtn.y1 + ((d->FieldBtn.Y()-d->FieldDropBtn.Y())/2)); d->NotBtn.ZOff(IconSize-1, IconSize-1); d->NotBtn.Offset((int)r, IconY); d->OpBtn.ZOff(SIZE_OP, d->FieldBtn.Y()+1); d->OpBtn.Offset(d->FieldDropBtn.x2 + 8, d->FieldBtn.y1-1); d->OpDropBtn.ZOff(IconSize-1, IconSize-1); d->OpDropBtn.Offset(d->OpBtn.x2 + 1, d->FieldDropBtn.y1); d->ValueBtn.ZOff(SIZE_VALUE, d->FieldBtn.Y()-1); d->ValueBtn.Offset(d->OpDropBtn.x2 + 8, d->FieldBtn.y1); // Draw stuff Buf.Op(GDC_ALPHA); Buf.Blt(d->NotBtn.x1, d->NotBtn.y1, d->Data->Icons[d->Not ? IconBoolTrue : IconBoolFalse]); Buf.Blt(d->OpDropBtn.x1, d->OpDropBtn.y1, d->Data->Icons[IconDropDown]); Buf.Blt(d->FieldDropBtn.x1, d->FieldDropBtn.y1, d->Data->Icons[IconDropDown]); Buf.Op(GDC_SET); LSysFont->Transparent(true); LSysFont->Colour(LColour(L_TEXT), Ctx.Back); d->Data->dsNot->Draw(&Buf, d->NotBtn.x2 + 3, d->NotBtn.y1); ShowControls(Select()); if (!Select()) { Buf.Colour(L_WORKSPACE); Buf.Rectangle(&d->FieldBtn); Buf.Rectangle(&d->OpBtn); Buf.Rectangle(&d->ValueBtn); int Tx = 6; int Ty = 2; if (d->Field) { LDisplayString ds(LSysFont, d->Field); ds.Draw(&Buf, d->FieldBtn.x1+Tx, d->FieldBtn.y1+Ty, &d->FieldBtn); } if (d->Op >= 0) { LDisplayString ds(LSysFont, d->Data->OpNames[d->Op]); ds.Draw(&Buf, d->OpBtn.x1+Tx, d->OpBtn.y1+Ty, &d->OpBtn); } if (d->Value) { LDisplayString ds(LSysFont, d->Value); ds.Draw(&Buf, d->ValueBtn.x1+Tx, d->ValueBtn.y1+Ty, &d->ValueBtn); } } break; } case LNODE_NEW: { LSysBold->Transparent(true); LSysBold->Colour(BackCol, Workspace); d->Data->dsNew->Draw(&Buf, (int)r - 3, (int)(oy + r) - (LSysBold->GetHeight() / 2)); break; } case LNODE_AND: { LSysBold->Transparent(true); LSysBold->Colour(IconColour[d->Node], BackCol); d->Data->dsAnd->Draw(&Buf, (int)r - 3, (int)(oy + r) - (LSysBold->GetHeight() / 2)); break; } case LNODE_OR: { LSysBold->Transparent(true); LSysBold->Colour(IconColour[d->Node], BackCol); d->Data->dsOr->Draw(&Buf, (int)r - 3, (int)(oy + r) - (LSysBold->GetHeight() / 2)); break; } } // Blt result to the screen Ctx.pDC->Blt(Pos->x1, Pos->y1, &Buf); // Paint to the right of the item, filling the column. if (Pos->x2 < Ctx.x2) { Ctx.pDC->Colour(L_WORKSPACE); Ctx.pDC->Rectangle(Pos->x2 + 1, Ctx.y1, Ctx.x2, Ctx.y2); } } void LFilterItem::ShowControls(bool s) { if (!GetTree() || d->Node != LNODE_COND) return; if (s) { LRect *Pos = _GetRect(TreeItemText); LRect c = GetTree()->GetClient(); int n = 1; LRect Cbo = d->OpBtn; Cbo.Union(&d->OpDropBtn); StartCtrl(d->FieldBtn, FieldEd, d->Field, LEdit); StartCtrl(Cbo, OpCbo, 0, LCombo); StartCtrl(d->ValueBtn, ValueEd, d->Value, LEdit); if (d->OpCbo && !d->OpCbo->Length()) { LArray Ops; if (d->Data->Callback( (LFilterView*) GetTree(), this, FMENU_OP, d->OpBtn, &Ops, d->Data->CallbackData) > 0) { for (unsigned i=0; iOpCbo->Insert(Ops[i]); Ops.DeleteArrays(); } if (d->Op) d->OpCbo->Value(d->Op); } } else { EndEdit(FieldBtn, FieldEd, Field); if (d->OpCbo) { d->Op = (int)d->OpCbo->Value(); DeleteObj(d->OpCbo); } EndEdit(ValueBtn, ValueEd, Value); } } void LFilterItem::OnExpand(bool b) { ShowControls(Select() && b); for (LTreeNode *c = GetChild(); c; c = c->GetNext()) { LFilterItem *i = dynamic_cast(c); if (i) i->OnExpand(b); } } bool LFilterItem::OnBeginDrag(LMouse &m) { Drag(GetTree(), m.Event, DROPEFFECT_MOVE); return true; } bool LFilterItem::GetFormats(LDragFormats &Formats) { Formats.Supports(FILTER_DRAG_FORMAT); return true; } bool LFilterItem::GetData(LArray &Data) { LDragData &dd = Data[0]; dd.Format = FILTER_DRAG_FORMAT; LVariant &v = dd.Data[0]; v.Type = GV_VOID_PTR; v.Value.Ptr = this; return true; } bool LFilterItem::OnKey(LKey &k) { if (k.IsChar) { if (k.vkey == LK_SPACE) { if (k.Down()) { SetNode(LNODE_COND); } return true; } } else if (k.IsContextMenu()) { if (k.Down()) { OptionsMenu(); } return true; } else if (k.vkey == LK_DELETE) { if (k.Down() && GetParent() && d->Node != LNODE_NEW) { LTreeItem *p = GetNext(); if (!p) p = GetPrev(); if (!p) p = GetParent(); delete this; if (p) p->Select(true); } return true; } return false; } void LFilterItem::OnMouseClick(LMouse &m) { if (m.Down() && m.Left()) { LRect *Pos = _GetRect(TreeItemText); if (!Pos) return; int Hit = -1; for (int i=0; iBtns[i].Overlap(m.x - Pos->x1, m.y - Pos->y1)) { Hit = i; break; } } LRect Rc, Client = GetTree()->GetClient(); if (d->NotBtn.Overlap(m.x - Pos->x1, m.y - Pos->y1)) { d->Not = !d->Not; Update(); } if (d->Data->Callback) { if (d->FieldDropBtn.Overlap(m.x - Pos->x1, m.y - Pos->y1)) { Rc = d->FieldDropBtn; Rc.Offset(Pos->x1 + Client.x1, Pos->y1 + Client.y1); d->Data->Callback( (LFilterView*) GetTree(), this, FMENU_FIELD, Rc, 0, d->Data->CallbackData); } else if (d->OpDropBtn.Overlap(m.x - Pos->x1, m.y - Pos->y1)) { Rc = d->OpDropBtn; Rc.Offset(Pos->x1 + Client.x1, Pos->y1 + Client.y1); d->Data->Callback( (LFilterView*) GetTree(), this, FMENU_OP, Rc, 0, d->Data->CallbackData); } } int Delta = 1; switch (Hit) { case IconMoveUp: { Delta = -1; // Fall thru } case IconMoveDown: { LFilterItem *p = dynamic_cast(GetParent()); if (p) { LTreeItem *m = this; size_t Count = p->Items.Length(); ssize_t Idx = p->Items.IndexOf(m); if (Idx + Delta >= 0 && Idx + Delta < (ssize_t)Count - 1) { p->Items.Delete(m); p->Items.Insert(m, Idx + Delta); p->_RePour(); GetTree()->Invalidate(); } } break; } case IconNewCond: { SetNode(LNODE_COND); break; } case IconNewAnd: { SetNode(LNODE_AND); break; } case IconNewOr: { SetNode(LNODE_OR); break; } case IconDelete: { if (IsRoot() && GetTree()) { GetTree()->Insert(new LFilterItem(d->Data)); } delete this; break; } case IconOptions: { OptionsMenu(); break; } } } } void LFilterItem::OptionsMenu() { LRect *Pos = _GetRect(TreeItemText); if (!Pos) return; LRect Client = GetTree()->GetClient(); LSubMenu s; if (d->Node == LNODE_NEW) s.AppendItem(LLoadString(L_FUI_CONDITION, "Condition"), 3, true); s.AppendItem(LLoadString(L_FUI_AND, "And"), 1, true); s.AppendItem(LLoadString(L_FUI_OR, "Or"), 2, true); LRect r = d->Btns[d->Node == LNODE_NEW ? IconNewCond : IconOptions]; r.Offset(Pos->x1 + Client.x1, Pos->y1 + Client.y1); LPoint p(r.x1, r.y2+1); GetTree()->PointToScreen(p); int Cmd = s.Float(GetTree(), p.x, p.y, true); switch (Cmd) { case 1: d->Node = LNODE_AND; break; case 2: d->Node = LNODE_OR; break; case 3: d->Node = LNODE_COND; break; } Update(); } -GFilterNode LFilterItem::GetNode() +LFilterNode LFilterItem::GetNode() { return d->Node; } -void LFilterItem::SetNode(GFilterNode n) +void LFilterItem::SetNode(LFilterNode n) { if (d->Node != n) { if (d->Node == LNODE_NEW && GetParent() && !IsRoot()) { GetParent()->Insert(new LFilterItem(d->Data)); } d->Node = n; Update(); if (d->Node == LNODE_AND || d->Node == LNODE_OR) { Insert(new LFilterItem(d->Data)); Expanded(true); } } } /////////////////////////////////////////////////////////////////////////////// LFilterView::LFilterView(FilterUi_Menu Callback, void *Data) { d = new LFilterViewPrivate(this); d->Callback = Callback; d->CallbackData = Data; d->Tree.Reset(new LFilterTree); Sunken(true); if (d->Callback) { LRect r; d->Callback(this, 0, FMENU_OP, r, &d->OpNames, d->CallbackData); } d->Tree->Sunken(false); d->Tree->Insert(new LFilterItem(d)); SetDefault(); } LFilterView::~LFilterView() { d->Tree->Empty(); DelView(d->Tree); DeleteObj(d); } void LFilterView::OnCreate() { OnPosChange(); d->Tree->Attach(this); } void LFilterView::SetDefault() { d->Tree->Empty(); d->Tree->Insert(new LFilterItem(d)); } -LFilterItem *LFilterView::Create(GFilterNode Node) +LFilterItem *LFilterView::Create(LFilterNode Node) { return new LFilterItem(d, Node); } void LFilterView::OnPosChange() { LRect c = LLayout::GetClient(); if (d->ShowLegend) { d->Info = c; d->Info.y2 = d->Info.y1 + LSysFont->GetHeight() + 8; c.y1 += d->Info.y2 + 1; } d->Tree->SetPos(c); } /* LRect &LFilterView::GetClient(bool ClientCoods) { static LRect c; c = LView::GetClient(ClientCoods); if (d->ShowLegend) { d->Info = c; d->Info.y2 = d->Info.y1 + LSysFont->GetHeight() + 8; c.y1 += d->Info.y2 + 1; } else { d->Info.ZOff(-1, -1); } return c; } */ void LFilterView::OnPaint(LSurface *pDC) { LLayout::OnPaint(pDC); if (d->ShowLegend) { LMemDC Buf(d->Info.X(), d->Info.Y(), System32BitColourSpace); Buf.Colour(L_MED); Buf.Rectangle(0, 0, Buf.X()-1, Buf.Y()-2); Buf.Colour(L_LOW); Buf.Line(0, Buf.Y()-1, Buf.X()-1, Buf.Y()-1); LSysFont->Transparent(true); LSysFont->Colour(L_TEXT, L_MED); LSysBold->Transparent(true); LSysBold->Colour(L_TEXT, L_MED); int x = 4, y = 4; d->dsLegend->Draw(&Buf, x, y); x += 8 + d->dsLegend->X(); const char **Ico = GetIconNames(); for (int i=0; iIcons[i]); x += 3 + d->Icons[i]->X(); Buf.Op(GDC_SET); LDisplayString ds(LSysFont, (char*)IconName[i]); ds.Draw(&Buf, x, y); x += 12 + ds.X(); } } pDC->Blt(d->Info.x1, d->Info.y1, &Buf); } } LTreeNode *LFilterView::GetRootNode() { return d->Tree; } void LFilterView::Empty() { d->Tree->Empty(); } diff --git a/src/win/Gdc2/MemDC.cpp b/src/win/Gdc2/MemDC.cpp --- a/src/win/Gdc2/MemDC.cpp +++ b/src/win/Gdc2/MemDC.cpp @@ -1,800 +1,800 @@ /*hdr ** FILE: LMemDC.h ** AUTHOR: Matthew Allen ** DATE: 27/11/2001 ** DESCRIPTION: GDC v2.xx header ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Palette.h" ///////////////////////////////////////////////////////////////////////////////////////////////////// class LMemDCPrivate { public: void *pBits; PBITMAPINFO Info; HPALETTE OldPal; bool UpsideDown; LRect Client; int ConstAlpha; LMemDCPrivate() { pBits = 0; Info = 0; OldPal = 0; Client.ZOff(-1, -1); UpsideDown = false; ConstAlpha = 255; } ~LMemDCPrivate() { DeleteArray(((char*&)Info)); } }; LMemDC::LMemDC(int x, int y, LColourSpace cs, int Flags) { d = new LMemDCPrivate; ColourSpace = CsNone; hBmp = 0; hDC = 0; pMem = 0; if (x > 0 && y > 0) { Create(x, y, cs, Flags); } } LMemDC::LMemDC(LSurface *pDC) { d = new LMemDCPrivate; ColourSpace = CsNone; hBmp = 0; hDC = 0; pMem = 0; if (pDC && Create(pDC->X(), pDC->Y(), pDC->GetColourSpace()) ) { if (pDC->Palette()) { Palette(new LPalette(pDC->Palette())); } Blt(0, 0, pDC); if (pDC->AlphaDC() && HasAlpha(true)) { pAlphaDC->Blt(0, 0, pDC->AlphaDC()); } } } LMemDC::~LMemDC() { Empty(); DeleteObj(d); } void LMemDC::SetClient(LRect *c) { if (c) { if (hDC) { SetWindowOrgEx(hDC, 0, 0, NULL); SelectClipRgn(hDC, 0); HRGN hRgn = CreateRectRgn(c->x1, c->y1, c->x2+1, c->y2+1); if (hRgn) { SelectClipRgn(hDC, hRgn); DeleteObject(hRgn); } SetWindowOrgEx(hDC, -c->x1, -c->y1, NULL); } LRect Doc(0, 0, pMem->x-1, pMem->y-1); Clip = d->Client = *c; Clip.Bound(&Doc); OriginX = -c->x1; OriginY = -c->y1; // LgiTrace("SetClient(%s) clip=%s, ori=%i,%i\n", c->GetStr(), Clip.GetStr(), OriginX, OriginY); } else { d->Client.ZOff(-1, -1); if (hDC) { SetWindowOrgEx(hDC, 0, 0, NULL); SelectClipRgn(hDC, 0); } OriginX = 0; OriginY = 0; Clip.ZOff(pMem->x-1, pMem->y-1); // LgiTrace("SetClient(NULL) clip=%s, ori=%i,%i\n", Clip.GetStr(), OriginX, OriginY); } } void LMemDC::UpsideDown(bool upsidedown) { d->UpsideDown = upsidedown; } bool LMemDC::Lock() { return true; } bool LMemDC::Unlock() { return true; } PBITMAPINFO LMemDC::GetInfo() { return d->Info; } void LMemDC::Update(int Flags) { if (d->Info && pPalette && (Flags & GDC_PAL_CHANGE)) { GdcRGB *p = (*pPalette)[0]; if (p) { for (int i=0; iGetSize(); i++, p++) { d->Info->bmiColors[i].rgbRed = p->r; d->Info->bmiColors[i].rgbGreen = p->g; d->Info->bmiColors[i].rgbBlue = p->b; } if (hDC) { SetDIBColorTable(hDC, 0, 256, d->Info->bmiColors); } } } } HDC LMemDC::StartDC() { if (!hBmp) return NULL; if (hDC) { LgiTrace("%s:%i - MemDC already started?\n", _FL); } else { hDC = CreateCompatibleDC(NULL); if (hDC) { hBmp = (HBITMAP) SelectObject(hDC, hBmp); SetWindowOrgEx(hDC, OriginX, OriginY, NULL); } } return hDC; } void LMemDC::EndDC() { if (hDC) { hBmp = (HBITMAP) SelectObject(hDC, hBmp); DeleteDC(hDC); hDC = NULL; } } LRect LMemDC::ClipRgn(LRect *Rgn) { LRect Prev = Clip; if (Rgn) { POINT Origin = {0, 0}; if (hDC) GetWindowOrgEx(hDC, &Origin); else { Origin.x = OriginX; Origin.y = OriginY; } Clip.x1 = max(Rgn->x1 - Origin.x, 0); Clip.y1 = max(Rgn->y1 - Origin.y, 0); Clip.x2 = min(Rgn->x2 - Origin.x, pMem->x-1); Clip.y2 = min(Rgn->y2 - Origin.y, pMem->y-1); HRGN hRgn = CreateRectRgn(Clip.x1, Clip.y1, Clip.x2+1, Clip.y2+1); if (hRgn) { SelectClipRgn(hDC, hRgn); DeleteObject(hRgn); } } else { Clip.x1 = 0; Clip.y1 = 0; Clip.x2 = X()-1; Clip.y2 = Y()-1; SelectClipRgn(hDC, NULL); } return Prev; } bool LMemDC::SupportsAlphaCompositing() { return true; } enum BmpComp { BmpRed, BmpGrn, BmpBlu, BmpAlp }; bool LMemDC::Create(int x, int y, LColourSpace Cs, int Flags) { bool Status = FALSE; HBITMAP hOldBmp = hBmp; BITMAPINFO *OldInfo = d->Info; LBmpMem *pOldMem = pMem; DrawOnAlpha(FALSE); DeleteObj(pAlphaDC); hBmp = NULL; pMem = NULL; d->Info = NULL; int Bits = LColourSpaceToBits(Cs); int LineLen = (((x * Bits) + 31) / 32) * 4; if (x > 0 && y > 0) { int Colours = Bits <= 8 ? 1 << Bits : 0; int SizeOf = sizeof(BITMAPINFO)+(3*sizeof(uint32_t))+(sizeof(RGBQUAD)*Colours); d->Info = (PBITMAPINFO) new char[SizeOf]; if (d->Info) { d->Info->bmiHeader.biSize = sizeof(d->Info->bmiHeader); d->Info->bmiHeader.biWidth = x; d->Info->bmiHeader.biHeight = d->UpsideDown ? y : -y; d->Info->bmiHeader.biPlanes = 1; d->Info->bmiHeader.biSizeImage = LineLen * y; d->Info->bmiHeader.biXPelsPerMeter = 3000; d->Info->bmiHeader.biYPelsPerMeter = 3000; d->Info->bmiHeader.biClrUsed = 0; d->Info->bmiHeader.biClrImportant = 0; uint32_t *BitFeilds = (uint32_t*) d->Info->bmiColors; switch (Cs) { case CsIndex1: case CsIndex4: case CsIndex8: { ColourSpace = Cs; d->Info->bmiHeader.biBitCount = Bits; d->Info->bmiHeader.biCompression = BI_RGB; break; } case System15BitColourSpace: { ColourSpace = Cs; #if 1 union { uint32_t u16; System15BitPixel p; }; u16 = 0; p.r = -1; BitFeilds[BmpRed] = u16; u16 = 0; p.g = -1; BitFeilds[BmpGrn] = u16; u16 = 0; p.b = -1; BitFeilds[BmpBlu] = u16; #else BitFeilds[BmpRed] = 0x001F; BitFeilds[BmpGrn] = 0x03E0; BitFeilds[BmpBlu] = 0x7C00; #endif BitFeilds[BmpAlp] = 0; d->Info->bmiHeader.biBitCount = 16; d->Info->bmiHeader.biCompression = BI_BITFIELDS; d->Info->bmiHeader.biSize += sizeof(*BitFeilds) * 4; break; } case System16BitColourSpace: { ColourSpace = Cs; #if 1 union { uint32_t u16; System16BitPixel p; }; u16 = 0; p.r = -1; BitFeilds[BmpRed] = u16; u16 = 0; p.g = -1; BitFeilds[BmpGrn] = u16; u16 = 0; p.b = -1; BitFeilds[BmpBlu] = u16; #else BitFeilds[BmpRed] = 0x001F; BitFeilds[BmpGrn] = 0x07E0; BitFeilds[BmpBlu] = 0xF800; #endif BitFeilds[BmpAlp] = 0; d->Info->bmiHeader.biBitCount = 16; d->Info->bmiHeader.biCompression = BI_BITFIELDS; d->Info->bmiHeader.biSize += sizeof(*BitFeilds) * 4; break; } case System24BitColourSpace: { ColourSpace = Cs; d->Info->bmiHeader.biBitCount = 24; d->Info->bmiHeader.biCompression = BI_RGB; break; } case System32BitColourSpace: { ColourSpace = Cs; union { uint32_t u32; System32BitPixel p; }; u32 = 0; p.r = -1; BitFeilds[BmpRed] = u32; u32 = 0; p.g = -1; BitFeilds[BmpGrn] = u32; u32 = 0; p.b = -1; BitFeilds[BmpBlu] = u32; u32 = 0; p.a = -1; BitFeilds[BmpAlp] = u32; d->Info->bmiHeader.biBitCount = 32; d->Info->bmiHeader.biCompression = BI_BITFIELDS; d->Info->bmiHeader.biSize += sizeof(*BitFeilds) * 4; break; } default: { // Non-native colour space support... break; } } if (ColourSpace) { // Native colour space support... HDC hDC = GetDC(NULL); if (hDC) { if (Colours > 0 && GdcD->GetBits()) { PALETTEENTRY Pal[256]; GetSystemPaletteEntries(hDC, 0, Colours, Pal); for (int i=0; iInfo->bmiColors[i].rgbReserved = 0; d->Info->bmiColors[i].rgbRed = Pal[i].peRed; d->Info->bmiColors[i].rgbGreen = Pal[i].peGreen; d->Info->bmiColors[i].rgbBlue = Pal[i].peBlue; } } hBmp = CreateDIBSection(hDC, d->Info, DIB_RGB_COLORS, &d->pBits, NULL, 0); if (hBmp) { pMem = new LBmpMem; if (pMem) { if (d->UpsideDown) { pMem->Base = ((uchar*) d->pBits) + (LineLen * (y - 1)); } else { pMem->Base = (uchar*) d->pBits; } pMem->x = x; pMem->y = y; pMem->Cs = ColourSpace; pMem->Line = d->UpsideDown ? -LineLen : LineLen; pMem->Flags = 0; Status = TRUE; } } else { DWORD err = GetLastError(); #if 1 LAssert(!"Create bmp failed."); #endif } } ReleaseDC(NULL, hDC); } else { // Non-native colour space support... ColourSpace = Cs; if (ColourSpace) { // Non-native image data pMem = new LBmpMem; if (pMem) { pMem->x = x; pMem->y = y; pMem->Line = ((x * Bits + 31) / 32) << 2; pMem->Cs = ColourSpace; pMem->Flags = LBmpMem::BmpOwnMemory; pMem->Base = new uchar[pMem->y * pMem->Line]; Status = pMem->Base != NULL; } } } } if (Status) { int NewOp = (pApp) ? Op() : GDC_SET; if ( (Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; iInfo); DeleteObj(pMem) } } if (hOldBmp) { DeleteObject(hOldBmp); hOldBmp = 0; } DeleteObj(OldInfo); DeleteObj(pOldMem); return Status; } void LMemDC::Empty() { if (hBmp) { DeleteObject(hBmp); hBmp = 0; } DeleteObj(pMem); } void LMemDC::Blt(int x, int y, LSurface *Src, LRect *a) { LAssert(Src != 0); if (!Src) return; if (Src->IsScreen()) { LRect b; if (a) { b = *a; } else { - LArray Displays; + LArray Displays; LGetDisplays(Displays, &b); } int RowOp; switch (Op()) { case GDC_SET: { RowOp = SRCCOPY; break; } case GDC_AND: { RowOp = SRCAND; break; } case GDC_OR: { RowOp = SRCPAINT; break; } case GDC_XOR: { RowOp = SRCINVERT; break; } default: { return; } } HDC hSrcDC = GetWindowDC(GetDesktopWindow()); if (hSrcDC) { // Get the screen DC and blt from there to our own memory HDC hDstDC = StartDC(); BitBlt(hDstDC, x, y, b.X(), b.Y(), hSrcDC, b.x1, b.y1, RowOp); // Overlay any effects between the screen and cursor layers... OnCaptureScreen(); // Do we need to capture the cursor as well? if (TestFlag(Flags, GDC_CAPTURE_CURSOR)) { // Capture the cursor as well.. CURSORINFO ci; ZeroObj(ci); ci.cbSize = sizeof(ci); GetCursorInfo(&ci); if (ci.flags == CURSOR_SHOWING) { HICON hicon = CopyIcon(ci.hCursor); ICONINFO icInfo; ZeroObj(icInfo); if (GetIconInfo(hicon, &icInfo)) { int cx = ci.ptScreenPos.x - ((int)icInfo.xHotspot); int cy = ci.ptScreenPos.y - ((int)icInfo.yHotspot); DrawIcon(hDstDC, cx - b.x1, cy - b.y1, hicon); } DestroyIcon(hicon); } } EndDC(); ReleaseDC(0, hSrcDC); } } else { LSurface::Blt(x, y, Src, a); } } void LMemDC::StretchBlt(LRect *Dest, LSurface *Src, LRect *s) { if (Src) { LRect DestR; if (Dest) { DestR = *Dest; } else { DestR.ZOff(X()-1, Y()-1); } LRect SrcR; if (s) { SrcR = *s; } else { SrcR.ZOff(Src->X()-1, Src->Y()-1); } int RowOp = SRCCOPY; switch (Op()) { case GDC_AND: { RowOp = SRCAND; break; } case GDC_OR: { RowOp = SRCPAINT; break; } case GDC_XOR: { RowOp = SRCINVERT; break; } case GDC_ALPHA: { if (GdcD->AlphaBlend) { HDC hDestDC = StartDC(); HDC hSrcDC = Src->StartDC(); BLENDFUNCTION Blend; Blend.BlendOp = AC_SRC_OVER; Blend.BlendFlags = 0; Blend.SourceConstantAlpha = d->ConstAlpha >= 0 && d->ConstAlpha <= 255 ? d->ConstAlpha : 255; Blend.AlphaFormat = Src->GetBits() == 32 ? AC_SRC_ALPHA : 0; if (!GdcD->AlphaBlend( hDestDC, DestR.x1, DestR.y1, DestR.X(), DestR.Y(), hSrcDC, SrcR.x1, SrcR.y1, SrcR.X(), SrcR.Y(), Blend)) { static bool First = true; if (First) { First = false; LgiTrace("%s:%i - AlphaBlend(%p, %s, %p, %s) failed.\n", _FL, hDestDC, DestR.GetStr(), hSrcDC, SrcR.GetStr()); } } Src->EndDC(); EndDC(); return; } break; } default: { break; } } HDC hDestDC = StartDC(); HDC hSrcDC = Src->StartDC(); ::StretchBlt(hDestDC, DestR.x1, DestR.y1, DestR.X(), DestR.Y(), hSrcDC, SrcR.x1, SrcR.y1, SrcR.X(), SrcR.Y(), RowOp); Src->EndDC(); EndDC(); } } void LMemDC::HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b) { if (x1 > x2) LSwap(x1, x2); if (x1 < Clip.x1) x1 = Clip.x1; if (x2 > Clip.x2) x2 = Clip.x2; if ( x1 <= x2 && y >= Clip.y1 && y <= Clip.y2) { COLOUR Prev = pApp->c; pApp->SetPtr(x1, y); for (; x1 <= x2; x1++) { if (x1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncX(); } pApp->c = Prev; } } void LMemDC::VertLine(int x, int y1, int y2, COLOUR a, COLOUR b) { if (y1 > y2) LSwap(y1, y2); if (y1 < Clip.y1) y1 = Clip.y1; if (y2 > Clip.y2) y2 = Clip.y2; if ( y1 <= y2 && x >= Clip.x1 && x <= Clip.x2) { COLOUR Prev = pApp->c; pApp->SetPtr(x, y1); for (; y1 <= y2; y1++) { if (y1 & 1) { pApp->c = a; } else { pApp->c = b; } pApp->Set(); pApp->IncY(); } pApp->c = Prev; } } void LMemDC::SetOrigin(int x, int y) { LSurface::SetOrigin(x, y); if (hDC) { SetWindowOrgEx(hDC, OriginX, OriginY, NULL); } } \ No newline at end of file diff --git a/src/win/Lgi/Window.cpp b/src/win/Lgi/Window.cpp --- a/src/win/Lgi/Window.cpp +++ b/src/win/Lgi/Window.cpp @@ -1,1423 +1,1423 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/ToolBar.h" #include "lgi/common/Panel.h" #include "lgi/common/Variant.h" #include "lgi/common/Button.h" #include "lgi/common/Notifications.h" #include "lgi/common/CssTools.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" #define DEBUG_WINDOW_PLACEMENT 0 #define DEBUG_HANDLE_VIEW_KEY 0 #define DEBUG_HANDLE_VIEW_MOUSE 0 #define DEBUG_SERIALIZE_STATE 0 #define DEBUG_SETFOCUS 0 extern bool In_SetWindowPos; typedef UINT (WINAPI *ProcGetDpiForWindow)(_In_ HWND hwnd); typedef UINT (WINAPI *ProcGetDpiForSystem)(VOID); LLibrary User32("User32"); LPoint LGetDpiForWindow(HWND hwnd) { static bool init = false; static ProcGetDpiForWindow pGetDpiForWindow = NULL; static ProcGetDpiForSystem pGetDpiForSystem = NULL; if (!init) { init = true; pGetDpiForWindow = (ProcGetDpiForWindow) User32.GetAddress("GetDpiForWindow"); pGetDpiForSystem = (ProcGetDpiForSystem) User32.GetAddress("GetDpiForSystem"); } if (pGetDpiForWindow && pGetDpiForSystem) { auto Dpi = hwnd ? pGetDpiForWindow(hwnd) : pGetDpiForSystem(); return LPoint(Dpi, Dpi); } return LScreenDpi(); } /////////////////////////////////////////////////////////////////////////////////////////////// class HookInfo { public: int Flags; LView *Target; }; class LWindowPrivate { public: LArray Hooks; bool SnapToEdge; bool AlwaysOnTop; LWindowZoom Show; bool InCreate; LAutoPtr Wp; LPoint Dpi; int ShowCmd = SW_NORMAL; // Focus stuff LViewI *Focus; LWindowPrivate() { Focus = NULL; InCreate = true; Show = LZoomNormal; SnapToEdge = false; AlwaysOnTop = false; } ~LWindowPrivate() { } ssize_t GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = 0; return (ssize_t)Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LWindow::LWindow() : LView(0) { _Window = this; d = new LWindowPrivate; SetStyle(GetStyle() | WS_TILEDWINDOW | WS_CLIPCHILDREN); SetStyle(GetStyle() & ~WS_CHILD); SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT); LWindowsClass *c = LWindowsClass::Create(GetClass()); if (c) c->Register(); Visible(false); _Lock = new LMutex("LWindow"); } LWindow::~LWindow() { if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (Menu) { Menu->Detach(); DeleteObj(Menu); } DeleteObj(_Lock); DeleteObj(d); } int LWindow::WaitThread() { // No thread to wait on... return 0; } bool LWindow::SetTitleBar(bool ShowTitleBar) { if (ShowTitleBar) { SetStyle(GetStyle() | WS_TILEDWINDOW); } else { SetStyle(GetStyle() & ~WS_TILEDWINDOW); SetStyle(GetStyle() | WS_POPUP); } return true; } bool LWindow::SetIcon(const char *Icon) { return CreateClassW32(LAppInst->Name(), LoadIcon(LProcessInst(), (LPCWSTR)Icon)) != 0; } LViewI *LWindow::GetFocus() { return d->Focus; } static LAutoString DescribeView(LViewI *v) { if (!v) return LAutoString(NewStr("NULL")); char s[512]; int ch = 0; ::LArray p; for (LViewI *i = v; i; i = i->GetParent()) { p.Add(i); } for (auto n=MIN(3, (ssize_t)p.Length()-1); n>=0; n--) { v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, ">%s", v->GetClass()); } return LAutoString(NewStr(s)); } static bool HasParentPopup(LViewI *v) { for (; v; v = v->GetParent()) { if (dynamic_cast(v)) return true; } return false; } bool LWindow::SetWillFocus(bool f) { d->ShowCmd = f ? SW_NORMAL : SW_SHOWNOACTIVATE; return true; } void LWindow::SetFocus(LViewI *ctrl, FocusType type) { const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } switch (type) { case GainFocus: { LViewI *This = this; if (ctrl == This && d->Focus) { // The main LWindow is getting focus. // Check if we can re-focus the previous child focus... LView *v = d->Focus->GetGView(); if (v && !HasParentPopup(v)) { // We should never return focus to a popup, or it's child. if (!(v->WndFlags & GWF_FOCUS)) { // Yes, the child view doesn't think it has focus... // So re-focus it... if (v->Handle()) { // Non-virtual window... ::SetFocus(v->Handle()); } v->WndFlags |= GWF_FOCUS; v->OnFocus(true); v->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) refocusing: %s\n", _set.Get(), TypeName, _foc.Get()); #endif return; } } } // Check if the control already has focus if (d->Focus == ctrl) return; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus: %s\n", _foc.Get()); #endif } d->Focus = ctrl; if (d->Focus) { LView *v = d->Focus->GetGView(); if (v) v->WndFlags |= GWF_FOCUS; d->Focus->OnFocus(true); d->Focus->Invalidate(); #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) focusing\n", _set.Get(), TypeName); #endif } break; } case LoseFocus: { if (ctrl == d->Focus) { LView *v = d->Focus->GetGView(); if (v) { if (v->WndFlags & GWF_FOCUS) { // View thinks it has focus v->WndFlags &= ~GWF_FOCUS; d->Focus->OnFocus(false); // keep d->Focus pointer, as we want to be able to re-focus the child // view when we get focus again #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LAutoString _foc = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) keep_focus: %s\n", _ctrl.Get(), TypeName, _foc.Get()); #endif } // else view doesn't think it has focus anyway... } else { // Non LView handler d->Focus->OnFocus(false); d->Focus->Invalidate(); d->Focus = NULL; } } else { /* LgiTrace("LWindow::SetFocus(%p.%s, %s) error on losefocus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); */ } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LgiTrace("LWindow::SetFocus(%p.%s, %s) delete_focus: %p(%s)\n", ctrl, ctrl ? ctrl->GetClass() : NULL, TypeName, d->Focus, d->Focus ? d->Focus->GetClass() : NULL); #endif d->Focus = NULL; } break; } } } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool b) { d->SnapToEdge = b; } bool LWindow::GetAlwaysOnTop() { return d->AlwaysOnTop; } void LWindow::SetAlwaysOnTop(bool b) { d->AlwaysOnTop = b; if (_View) SetWindowPos(_View, b ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); } void LWindow::Raise() { if (_View) { DWORD dwFGProcessId; DWORD dwFGThreadId = GetWindowThreadProcessId(_View, &dwFGProcessId); DWORD dwThisThreadId = GetCurrentThreadId(); AttachThreadInput(dwThisThreadId, dwFGThreadId, true); SetForegroundWindow(_View); BringWindowToTop(_View); if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", __FILE__, __LINE__, _View); } ::SetFocus(_View); AttachThreadInput(dwThisThreadId, dwFGThreadId, false); } } LWindowZoom LWindow::GetZoom() { if (IsZoomed(Handle())) { return LZoomMax; } else if (IsIconic(Handle())) { return LZoomMin; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (_View && IsWindowVisible(_View)) { switch (i) { case LZoomMax: { ShowWindow(Handle(), SW_MAXIMIZE); break; } case LZoomMin: { ShowWindow(Handle(), SW_MINIMIZE); break; } case LZoomNormal: { if (!Visible()) { Visible(true); } if (IsIconic(Handle()) || IsZoomed(Handle())) { ShowWindow(Handle(), d->ShowCmd); } // Process some messages: is this needed still? MSG Msg = {0}; while ( PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) && Msg.message != WM_QUIT) { TranslateMessage(&Msg); DispatchMessage(&Msg); } RECT r; GetWindowRect(Handle(), &r); if (r.left != Pos.x1 || r.top != Pos.y1) { int Shadow = WINDOWS_SHADOW_AMOUNT; SetWindowPos(Handle(), 0, Pos.x1, Pos.y1, Pos.X() + Shadow, Pos.Y() + Shadow, SWP_NOZORDER); } break; } } } d->Show = i; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return true; } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { #if DEBUG_HANDLE_VIEW_MOUSE m.Trace("HandleViewMouse"); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { LView *t = d->Hooks[i].Target; if (!t->OnViewMouse(v, m)) { #if DEBUG_HANDLE_VIEW_MOUSE if (m.IsMove()) LgiTrace(" Hook %i of %i ate mouse event: '%s'\n", i, d->Hooks.Length(), d->Hooks[i].Target->GetClass()); #endif return false; } } } #if DEBUG_HANDLE_VIEW_MOUSE if (!m.IsMove()) LgiTrace(" Passing mouse event to '%s'\n", v->GetClass()); #endif return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { #if DEBUG_HANDLE_VIEW_KEY char msg[256]; sprintf_s(msg, sizeof(msg), "HandleViewKey, v=%s", v ? v->GetClass() : "NULL"); k.Trace(msg); #endif // Any window in a pop up always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Popup %s handling key.\n", p->GetClass()); #endif return v->OnKey(k); } } // Allow any hooks to see the key... #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Hooks.Length()=%i.\n", (int)d->Hooks.Length()); #endif for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LKeyEvents) { if (d->Hooks[i].Target->OnViewKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Hook[%i] %s handling key.\n", i, d->Hooks[i].Target->GetClass()); #endif return true; } } } // Give the key to the focused window... if (d->Focus && d->Focus->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" d->Focus %s handling key.\n", d->Focus->GetClass()); #endif return true; } // Check default controls p = 0; if (k.c16 == VK_RETURN) { if (!_Default) p = _Default = FindControl(IDOK); else p = _Default; #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using _Default ctrl (%s).\n", p ? p->GetClass() : "NULL"); #endif } else if (k.c16 == VK_ESCAPE) { p = FindControl(IDCANCEL); if (p) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Using IDCANCEL ctrl (%s).\n", p->GetClass()); #endif } } if (p && p->OnKey(k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Default control %s handled key.\n", p->GetClass()); #endif return true; } // Menu shortcut? if (Menu && Menu->OnKey(v, k)) { #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" Menu handled key.\n"); #endif return true; } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } #if DEBUG_HANDLE_VIEW_KEY LgiTrace(" No one handled key.\n"); #endif return false; } void LWindow::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } bool LWindow::Obscured() { RECT tRect; bool isObscured = false; if (GetWindowRect(_View, &tRect)) { RECT nRect; HWND walker = _View; while (walker = ::GetNextWindow(walker, GW_HWNDPREV)) { if (IsWindowVisible(walker)) { if ((::GetWindowRect(walker, &nRect))) { RECT iRect; IntersectRect(&iRect, &tRect, &nRect); if (iRect.bottom || iRect.top || iRect.left || iRect.right) { isObscured = true; break; } } } } } return isObscured; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool v) { if (v) PourAll(); if (v) { SetStyle(GetStyle() | WS_VISIBLE); if (_View) { LWindowZoom z = d->Show; char *Cmd = 0; LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(*Wp); Wp->flags = 2; Wp->ptMaxPosition.x = -1; Wp->ptMaxPosition.y = -1; if (d->Show == LZoomMax) { Wp->showCmd = SW_MAXIMIZE; Cmd = "SW_MAXIMIZE"; } else if (d->Show == LZoomMin) { Wp->showCmd = SW_MINIMIZE; Cmd = "SW_MINIMIZE"; } else { Wp->showCmd = d->ShowCmd; Cmd = "SW_NORMAL"; } Wp->rcNormalPosition = Pos; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(_View, Wp); if (d->InCreate) d->Wp = Wp; } } } else { #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - Visible(%i)\n", __FILE__, __LINE__, v); #endif LView::Visible(v); } if (v) { OnZoom(d->Show); } } static bool IsAppWnd(HWND h) { if (!IsWindowVisible(h)) return false; auto flags = GetWindowLong(h, GWL_STYLE); if (flags & WS_POPUP) return false; return true; } bool LWindow::IsActive() { auto top = GetTopWindow(GetDesktopWindow()); while (top && !IsAppWnd(top)) top = ::GetWindow(top, GW_HWNDNEXT); return top == _View; } bool LWindow::SetActive() { if (!_View) return false; return SetWindowPos(_View, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE) != 0; } void LWindow::PourAll() { LRegion Client(GetClient()); LRegion Update; bool HasTools = false; { LRegion Tools; for (auto v: Children) { LView *k = dynamic_cast(v); if (k && k->_IsToolBar) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (HasTools) { // 2nd and later toolbars if (v->Pour(Tools)) { if (!v->Visible()) { v->Visible(true); } auto vpos = v->GetPos(); if (OldPos != vpos) { // position has changed update... v->Invalidate(); } // Has it increased the size of the toolbar area? auto b = Tools.Bound(); if (vpos.y2 >= b.y2) { LRect Bar = Client; Bar.y2 = vpos.y2; Client.Subtract(&Bar); // LgiTrace("IncreaseToolbar=%s\n", Bar.GetStr()); } Tools.Subtract(&vpos); Update.Subtract(&vpos); // LgiTrace("vpos=%s\n", vpos.GetStr()); } } else { // First toolbar if (v->Pour(Client)) { HasTools = true; if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { v->Invalidate(); } LRect Bar(v->GetPos()); Bar.x2 = GetClient().x2; Tools = Bar; Tools.Subtract(&v->GetPos()); Client.Subtract(&Bar); Update.Subtract(&Bar); } } } } } // LgiTrace("Client=%s\n", Client.Bound().GetStr()); for (auto v: Children) { LView *k = dynamic_cast(v); if (!(k && k->_IsToolBar)) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible // v->Visible(FALSE); } } } for (int i=0; iMsg()) { case WM_DPICHANGED: { d->Dpi.x = HIWORD(Msg->A()); d->Dpi.y = LOWORD(Msg->A()); OnPosChange(); break; } case M_ASSERT_UI: { LAutoPtr Str((LString*)Msg->A()); extern void LAssertDlg(LString Msg, std::function Callback); if (Str) LAssertDlg(Str ? *Str : "Error: no msg.", NULL); break; } case M_SET_WINDOW_PLACEMENT: { /* Apparently if you use SetWindowPlacement inside the WM_CREATE handler, then the restored rect doesn't "stick", it gets stomped on by windows. So this code... RESETS it to be what we set earlier. Windows sucks. */ if (!d->Wp || !_View) break; LRect r = d->Wp->rcNormalPosition; if (!LView::Visible()) d->Wp->showCmd = SW_HIDE; #if DEBUG_WINDOW_PLACEMENT LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", __FILE__, __LINE__, r.GetStr(), d->Wp->showCmd); #endif SetWindowPlacement(_View, d->Wp); d->Wp.Reset(); break; } case WM_SYSCOLORCHANGE: { LColour::OnChange(); break; } case WM_WINDOWPOSCHANGING: { bool Icon = IsIconic(Handle()) != 0; bool Zoom = IsZoomed(Handle()) != 0; if (!Icon && (_Dialog || !Zoom)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (!Info) break; if (Info->flags == (SWP_NOSIZE | SWP_NOMOVE) && _Dialog) { // Info->flags |= SWP_NOZORDER; Info->hwndInsertAfter = _Dialog->Handle(); } if (GetMinimumSize().x && GetMinimumSize().x > Info->cx) { Info->cx = GetMinimumSize().x; } if (GetMinimumSize().y && GetMinimumSize().y > Info->cy) { Info->cy = GetMinimumSize().y; } /* This is broken on windows 10... windows get stuck on the edge of the desktop. RECT Rc; if (d->SnapToEdge && SystemParametersInfo(SPI_GETWORKAREA, 0, &Rc, SPIF_SENDCHANGE)) { LRect r = Rc; LRect p(Info->x, Info->y, Info->x + Info->cx - 1, Info->y + Info->cy - 1); if (r.Valid() && p.Valid()) { int Snap = 12; if (abs(p.x1 - r.x1) <= Snap) { // Snap left edge Info->x = r.x1; } else if (abs(p.x2 - r.x2) <= Snap) { // Snap right edge Info->x = r.x2 - Info->cx + 1; } if (abs(p.y1 - r.y1) <= Snap) { // Snap top edge Info->y = r.y1; } else if (abs(p.y2 - r.y2) <= Snap) { // Snap bottom edge Info->y = r.y2 - Info->cy + 1; } } } */ } break; } case WM_SIZE: { if (Visible()) { LWindowZoom z = d->Show; switch (Msg->a) { case SIZE_MINIMIZED: { z = LZoomMin; break; } case SIZE_MAXIMIZED: { z = LZoomMax; break; } case SIZE_RESTORED: { z = LZoomNormal; break; } } if (z != d->Show) { OnZoom(d->Show = z); } } Status = LView::OnEvent(Msg) != 0; break; } case WM_CREATE: { if (d->AlwaysOnTop) SetAlwaysOnTop(true); PourAll(); OnCreate(); if (!_Default) { _Default = FindControl(IDOK); if (_Default) _Default->Invalidate(); } d->InCreate = false; if (d->Wp) { PostEvent(M_SET_WINDOW_PLACEMENT); } break; } case WM_WINDOWPOSCHANGED: { d->Wp.Reset(); Status = LView::OnEvent(Msg) != 0; break; } case WM_QUERYENDSESSION: case WM_CLOSE: { bool QuitApp; bool OsShuttingDown = Msg->Msg() == WM_QUERYENDSESSION; if (QuitApp = OnRequestClose(OsShuttingDown)) { Quit(); } if (Msg->Msg() == WM_CLOSE) { return 0; } else { return QuitApp; } break; } case WM_SYSCOMMAND: { if (Msg->a == SC_CLOSE) { if (OnRequestClose(false)) { Quit(); } return 0; } else { Status = LView::OnEvent(Msg) != 0; } break; } case WM_DROPFILES: { HDROP hDrop = (HDROP) Msg->a; if (hDrop) { LArray FileNames; int Count = 0; Count = DragQueryFileW(hDrop, -1, NULL, 0); for (int i=0; i 0) { FileNames.Add(WideToUtf8(FileName)); } } OnReceiveFiles(FileNames); FileNames.DeleteArrays(); } break; } case M_HANDLEMOUSEMOVE: { // This receives events fired from the LMouseHookPrivate class so that // non-LGI windows create mouse hook events as well. LTempView v((OsView)Msg->B()); LMouse m; m.x = LOWORD(Msg->A()); m.y = HIWORD(Msg->A()); HandleViewMouse(&v, m); break; } case M_COMMAND: { HWND OurWnd = Handle(); // copy onto the stack, because // we might lose the 'this' object in the // OnCommand handler which would delete // the memory containing the handle. Status = OnCommand((int) Msg->a, 0, (OsView) Msg->b); if (!IsWindow(OurWnd)) { // The window was deleted so break out now break; } // otherwise fall thru to the LView handler } default: { Status = (int) LView::OnEvent(Msg); break; } } return Status; } LPoint LWindow::GetDpi() { if (!d->Dpi.x) d->Dpi = LGetDpiForWindow(_View); return d->Dpi; } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); LPointF r( Dpi.x / 96.0, Dpi.y / 96.0 ); return r; } LRect &LWindow::GetPos() { if (_View && IsZoomed(_View)) { static LRect r; RECT rc; GetWindowRect(_View, &rc); r = rc; return r; } return Pos; } void LWindow::OnPosChange() { PourAll(); } bool LWindow::RegisterHook(LView *Target, LWindowHookType EventType, int Priority) { bool Status = false; if (Target && EventType) { auto i = d->GetHookIndex(Target, true); if (i >= 0) { d->Hooks[i].Flags = EventType; Status = true; } } return Status; } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { #if WINNATIVE LButton *Btn; if (Btn = dynamic_cast(_Default)) Btn->Default(false); #endif _Default = v; #if WINNATIVE if (Btn = dynamic_cast(_Default)) Btn->Default(true); #endif } bool LWindow::UnregisterHook(LView *Target) { auto i = d->GetHookIndex(Target); if (i >= 0) { d->Hooks.DeleteAt(i); return true; } return false; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; #if DEBUG_SERIALIZE_STATE LgiTrace("LWindow::SerializeState(%p, %s, %i)\n", Store, FieldName, Load); #endif if (Load) { LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i v=%s\n", __LINE__, v.Str()); #endif for (auto Var: v.LStr().SplitDelimit(";")) { auto v = Var.SplitDelimit("=", 1); if (v.Length() == 2) { if (v[0].Equals("State")) State = (LWindowZoom)v[1].Int(); else if (v[0].Equals("Pos")) { LRect r; r.SetStr(v[1]); if (r.Valid()) Position = r; } } else return false; } #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i State=%i, Pos=%s\n", __LINE__, State, Position.GetStr()); #endif // Apply any shortcut override int Show = LAppInst->GetShow(); if (Show == SW_SHOWMINIMIZED || Show == SW_SHOWMINNOACTIVE || Show == SW_MINIMIZE) { State = LZoomMin; } else if (Show == SW_SHOWMAXIMIZED || Show == SW_MAXIMIZE) { State = LZoomMax; } LAutoPtr Wp(new WINDOWPLACEMENT); if (Wp) { ZeroObj(*Wp.Get()); Wp->length = sizeof(WINDOWPLACEMENT); if (Visible()) { if (State == LZoomMax) { Wp->showCmd = SW_SHOWMAXIMIZED; } else if (State == LZoomMin) { Wp->showCmd = SW_MINIMIZE; } else { Wp->showCmd = d->ShowCmd; } } else { Wp->showCmd = SW_HIDE; d->Show = State; } LRect DefaultPos(100, 100, 900, 700); if (Position.Valid()) { - LArray Displays; + LArray Displays; LRect AllDisplays; bool PosOk = true; if (LGetDisplays(Displays, &AllDisplays)) { // Check that the position is on one of the screens PosOk = false; for (unsigned i=0; ir; Int.Intersection(&Position); if (Int.Valid() && Int.X() > 20 && Int.Y() > 20) { PosOk = true; break; } } Displays.DeleteObjects(); } if (PosOk) Pos = Position; else Pos = DefaultPos; } else Pos = DefaultPos; Wp->rcNormalPosition = Pos; #if DEBUG_SERIALIZE_STATE LgiTrace("%s:%i - SetWindowPlacement, pos=%s, show=%i\n", _FL, Pos.GetStr(), Wp->showCmd); #endif SetWindowPlacement(Handle(), Wp); if (d->InCreate) d->Wp = Wp; } } else return false; } else { char s[256]; LWindowZoom State = GetZoom(); LRect Position; if (Handle()) { WINDOWPLACEMENT Wp; ZeroObj(Wp); Wp.length = sizeof(Wp); GetWindowPlacement(Handle(), &Wp); Position = Wp.rcNormalPosition; } else { // A reasonable fall back if we don't have a window... Position = GetPos(); } sprintf_s(s, sizeof(s), "State=%i;Pos=%s", State, Position.GetStr()); #if DEBUG_SERIALIZE_STATE LgiTrace("\t::SerializeState:%i s='%s'\n", __LINE__, s); #endif LVariant v = s; if (!Store->SetValue(FieldName, v)) return false; } return true; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } diff --git a/test/UnitTests/src/MatrixTest.cpp b/test/UnitTests/src/MatrixTest.cpp --- a/test/UnitTests/src/MatrixTest.cpp +++ b/test/UnitTests/src/MatrixTest.cpp @@ -1,77 +1,77 @@ #include "lgi/common/Lgi.h" #include "UnitTests.h" #include "lgi/common/Matrix.h" class PrivLMatrixTest { public: bool Error(char *Fmt, ...) { va_list Args; va_start(Args, Fmt); vprintf(Fmt, Args); va_end(Args); return false; } bool Test1() { // Identity testing - GMatrix m1; + LMatrix m1; m1.SetIdentity(); for (int y=0; y a; + LMatrix a; a.SetStr( "1 2 3\n" "4 5 6\n"); - GMatrix b; + LMatrix b; b.SetStr( "7 8\n" "9 10\n" "11 12\n"); - GMatrix c, r; + LMatrix c, r; c = a * b; r.SetStr( "58 64\n" "139 154\n"); if (c != r) return false; // Inverse test - GMatrix m2, m3, m4; + LMatrix m2, m3, m4; m2.SetStr( "1 5 2\n" "1 1 7\n" "0 -3 4\n"); m3 = m2; if (!m3.Inverse()) return false; m4 = m2 * m3; if (!m4.IsIdentity()) return false; return true; } }; LMatrixTest::LMatrixTest() : UnitTest("LMatrixTest") { d = new PrivLMatrixTest; } LMatrixTest::~LMatrixTest() { DeleteObj(d); } bool LMatrixTest::Run() { return d->Test1(); } \ No newline at end of file diff --git a/utils/SlogViewer/src/SlogViewerMain.cpp b/utils/SlogViewer/src/SlogViewerMain.cpp --- a/utils/SlogViewer/src/SlogViewerMain.cpp +++ b/utils/SlogViewer/src/SlogViewerMain.cpp @@ -1,432 +1,556 @@ #define _CRT_SECURE_NO_WARNINGS #include "lgi/common/Lgi.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/DocApp.h" #include "lgi/common/Box.h" #include "lgi/common/List.h" #include "lgi/common/TextView3.h" #include "lgi/common/TabView.h" #include "lgi/common/StructuredLog.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" ////////////////////////////////////////////////////////////////// const char *AppName = "SlogViewer"; enum DisplayMode { DisplayHex, DisplayEscaped }; enum Ctrls { IDC_BOX = 100, IDC_LOG, IDC_HEX, IDC_ESCAPED, IDC_TABS, IDC_STATUS, }; struct Context { LList *log = NULL; LTabView *tabs = NULL; LTextView3 *hex = NULL; LTextView3 *escaped = NULL; }; class Entry : public LListItem { Context *c; struct Value { LVariantType type; LArray data; LString name; }; LArray values; LString cache; public: Entry(Context *ctx) : c(ctx) { } void add(LVariantType type, size_t sz, void *ptr, const char *name) { auto &v = values.New(); v.type = type; v.data.Add((uint8_t*)ptr, sz); v.name = name; } const char *GetText(int i) { if (!cache) { LStringPipe p; for (auto &v: values) { switch (v.type) { case GV_INT32: case GV_INT64: { if (v.data.Length() == 4) - p.Print("%i", *(int*)v.data.AddressOf()); + p.Print(" %i", *(int*)v.data.AddressOf()); else if (v.data.Length() == 8) - p.Print(LPrintfInt64, *(int64_t*)v.data.AddressOf()); + p.Print(" " LPrintfInt64, *(int64_t*)v.data.AddressOf()); else LAssert(!"Unknown int size."); break; } case GV_STRING: { + p.Print(" %.*s", (int)MIN(32,v.data.Length()), v.data.AddressOf()); if (v.data.Length() >= 32) - p.Print(" (...)"); - else - p.Print("%.*s", (int)v.data.Length(), v.data.AddressOf()); + p.Print("..."); + break; + } + case GV_WSTRING: + { + p.Print(" %.*S", (int)MIN(32,v.data.Length()), v.data.AddressOf()); + if (v.data.Length() >= 32) + p.Print("..."); break; } case GV_CUSTOM: { - p.Print("Object: %s", v.name.Get()); + p.Print(" Object:%s", v.name.Get()); break; } case GV_VOID_PTR: { - p.Print("/Object"); + // p.Print("/Obj"); + break; + } + case GV_BINARY: + { + p.Print(" (Bin)"); break; } default: { LAssert(!"Unknown type."); break; } } if (p.GetSize() > 64) break; } cache = p.NewLStr(); } return cache; } + template + void PrintEscaped(LStringPipe &p, T *s, T *e) + { + for (auto c = s; c < e; c++) + { + switch (*c) + { + case '\\': p.Print("\\\\"); break; + case '\t': p.Print("\\t"); break; + case '\r': p.Print("\\r"); break; + case '\n': p.Print("\\n\n"); break; + case 0x07: p.Print("\\b"); break; + case 0x1b: p.Print("\\e"); break; + default: + if (*c < ' ' || *c >= 128) + { + LString hex; + hex.Printf("\\x%x", typename std::make_unsigned::type(*c)); + p.Write(hex); + } + else + { + char ch = (char)*c; + p.Write(&ch, 1); + } + break; + + } + } + } + + LString IntToString(Value &v, bool showHex) + { + LString s; + + if (v.data.Length() == 1) + { + auto i = (uint8_t*) v.data.AddressOf(); + if (showHex) + s.Printf("%i 0x%x", *i, *i); + else + s.Printf("%i", *i); + } + else if (v.data.Length() == 2) + { + auto i = (uint16_t*) v.data.AddressOf(); + if (showHex) + s.Printf("%i 0x%x", *i, *i); + else + s.Printf("%i", *i); + } + else if (v.data.Length() == 4) + { + auto i = (uint32_t*) v.data.AddressOf(); + if (showHex) + s.Printf("%i 0x%x", *i, *i); + else + s.Printf("%i", *i); + } + else if (v.data.Length() == 8) + { + auto i = (int64_t*) v.data.AddressOf(); + if (showHex) + s.Printf(LPrintfInt64 " 0x" LPrintfHex64, *i, *i); + else + s.Printf(LPrintfInt64, *i); + } + else + LAssert(!"Impl me."); + + return s; + } + void Select(bool b) override { LListItem::Select(b); auto mode = (DisplayMode)c->tabs->Value(); auto ctrl = mode ? c->escaped : c->hex; - if (b) + if (!b) + return; + + LStringPipe p; + LString Obj; + int Idx = 0; + bool shorthand = false; + + for (auto &v: values) { - LStringPipe p; - LString Obj; - int Idx = 0; + auto sType = LVariant::TypeToString(v.type); + + switch (v.type) + { + case GV_CUSTOM: + { + shorthand = v.name.Equals("LRect") || + v.name.Equals("LColour"); - auto PrintEscaped = [&](char *s, char *e) - { - for (auto c = s; c < e; c++) + if (shorthand) + p.Print("object %s {", v.name.Get()); + else + p.Print("object %s {\n\n", v.name.Get()); + Obj = v.name; + Idx = 0; + + continue; + } + case GV_VOID_PTR: { - switch (*c) + if (shorthand) + p.Print(" }\n"); + else + p.Print("}\n"); + Obj.Empty(); + + shorthand = false; + continue; + } + case GV_STRING: + { + if (Obj == "LConsole") { - case '\\': p.Print("\\\\"); break; - case '\t': p.Print("\\t"); break; - case '\r': p.Print("\\r"); break; - case '\n': p.Print("\\n\n"); break; - case 0x07: p.Print("\\b"); break; - case 0x1b: p.Print("\\e"); break; - default: - if (*c < ' ' || *c >= 128) - { - LString hex; - hex.Printf("\\x%x", (uint8_t)*c); - p.Write(hex); - } - else p.Write(c, 1); - break; - + auto s = (char*)v.data.AddressOf(); + auto e = s + v.data.Length(); + p.Print("[%i]=", Idx++); + PrintEscaped(p, s, e); + p.Print("\n"); + continue; } + break; } - }; - - for (auto &v: values) + } + + if (shorthand) { - auto sType = LVariant::TypeToString(v.type); - switch (v.type) { - case GV_CUSTOM: - { - p.Print("object %s {\n\n", v.name.Get()); - Obj = v.name; - Idx = 0; - continue; - } - case GV_VOID_PTR: - { - p.Print("}\n"); - Obj.Empty(); - continue; - } + case GV_INT32: + case GV_INT64: + p.Print(" %s=%s", v.name.Get(), IntToString(v, false).Get()); + break; case GV_STRING: - { - if (Obj == "LConsole") - { - auto s = (char*)v.data.AddressOf(); - auto e = s + v.data.Length(); - p.Print("[%i]=", Idx++); - PrintEscaped(s, e); - p.Print("\n"); - continue; - } + p.Print(" %s=%.*s", v.name.Get(), (int)v.data.Length(), v.data.AddressOf()); + break; + default: + p.Print(" %s=#impl(%s)", v.name.Get(), LVariant::TypeToString(v.type)); break; - } - } - - p.Print("%s, %i bytes%s%s:\n", + } + } + else + { + p.Print("%s, %i bytes%s%s: ", sType, (int)v.data.Length(), v.name ? ", " : "", v.name ? v.name.Get() : ""); switch (v.type) { case GV_INT32: case GV_INT64: { - if (v.data.Length() == 4) - { - auto i = (int*) v.data.AddressOf(); - p.Print("%i 0x%x\n", *i, *i); - } - else if (v.data.Length() == 8) - { - auto i = (int64_t*) v.data.AddressOf(); - p.Print(LPrintfInt64 " 0x" LPrintfHex64 "\n", *i, *i); - } - else - LAssert(!"Impl me."); + p.Print("%s\n", IntToString(v, true).Get()); break; } + case GV_BINARY: case GV_STRING: { + p.Print("\n"); if (mode == DisplayHex) { char line[300]; int ch = 0; const int rowSize = 16; const int colHex = 10; const int colAscii = colHex + (rowSize * 3) + 2; const int colEnd = colAscii + rowSize; for (size_t addr = 0; addr < v.data.Length() ; addr += rowSize) { ZeroObj(line); sprintf(line, "%8.8x", (int)addr); auto rowBytes = MIN(v.data.Length() - addr, rowSize); LAssert(rowBytes <= rowSize); auto rowPtr = v.data.AddressOf(addr); for (int i=0; i= ' ' && rowPtr[i] < 128 ? rowPtr[i] : '.'; } for (int i=0; i s && e[-1] != '\n') + p.Write("\n"); + } + break; + } + case GV_WSTRING: + { + p.Print("\n"); + if (mode == DisplayHex) + { + char line[300]; + + const int rowSize = 16; + const int colHex = 10; + const int colAscii = colHex + (rowSize / 2 * 5) + 2; + const int colEnd = colAscii + rowSize; + + for (size_t addr = 0; addr < v.data.Length() ; addr += rowSize) + { + ZeroObj(line); + sprintf(line, "%8.8x", (int)addr); + auto rowBytes = MIN(v.data.Length() - addr, rowSize); + auto rowWords = rowBytes / sizeof(char16); + LAssert(rowBytes <= rowSize); + auto rowPtr = (char16*) v.data.AddressOf(addr); + for (int i=0; i= ' ' && rowPtr[i] < 128 ? rowPtr[i] : '.'; + } + for (int i=0; i s && e[-1] != '\n') p.Write("\n"); } break; } default: { LAssert(!"Impl me."); break; } } p.Print("\n"); } + } - ctrl->Name(p.NewLStr()); - } + ctrl->Name(p.NewLStr()); } }; class ReaderThread : public LThread { Context *Ctx; LString FileName; LList *Lst = NULL; Progress *Prog = NULL; public: ReaderThread(Context *ctx, const char *filename, LList *lst, Progress *prog) : LThread("ReaderThread") { Ctx = ctx; FileName = filename; Lst = lst; Prog = prog; Run(); } ~ReaderThread() { Prog->Cancel(); WaitForExit(10000); } int Main() { LStructuredLog file(FileName, false); LAutoPtr cur; List items; while (file.Read([this, &cur, &items](auto type, auto size, auto ptr, auto name) { if (type == LStructuredIo::EndRow) { items.Insert(cur.Release()); if (items.Length() > 100) { Lst->Insert(items); items.Empty(); } return; } if (!cur && !cur.Reset(new Entry(Ctx))) return; cur->add(type, size, ptr, name); }, Prog)) ; Lst->Insert(items); return 0; } }; class App : public LDocApp, public Context { LBox *box = NULL; LAutoPtr Reader; LStatusBar *Status = NULL; LProgressStatus *Prog = NULL; public: App() : LDocApp(AppName) { Name(AppName); - LRect r(0, 0, 1000, 800); + LRect r(0, 0, 1100, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); if (_Create()) { _LoadMenu(); AddView(Status = new LStatusBar(IDC_STATUS)); Status->AppendPane("Some text"); Status->AppendPane(Prog = new LProgressStatus()); Prog->GetCss(true)->Width("200px"); Prog->GetCss()->TextAlign(LCss::AlignRight); AddView(box = new LBox(IDC_BOX)); box->AddView(log = new LList(IDC_LOG, 0, 0, 200, 200)); log->GetCss(true)->Width("40%"); log->ShowColumnHeader(false); log->AddColumn("Items", 1000); box->AddView(tabs = new LTabView(IDC_TABS)); auto tab = tabs->Append("Hex"); tab->Append(hex = new LTextView3(IDC_HEX)); hex->Sunken(true); hex->SetPourLargest(true); tab = tabs->Append("Escaped"); tab->Append(escaped = new LTextView3(IDC_ESCAPED)); escaped->Sunken(true); escaped->SetPourLargest(true); AttachChildren(); Visible(true); } } void OnReceiveFiles(LArray &Files) { if (Files.Length()) OpenFile(Files[0], false, NULL); } void OpenFile(const char *FileName, bool ReadOnly, std::function Callback) { auto ok = Reader.Reset(new ReaderThread(this, FileName, log, Prog)); if (Callback) Callback(ok); } void SaveFile(const char *FileName, std::function Callback) { if (Callback) Callback(FileName, false); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_TABS) { auto item = log->GetSelected(); if (item) item->Select(true); } return 0; } }; ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { a.AppWnd = new App; a.Run(); } return 0; }