diff --git a/Lvc/src/Main.cpp b/Lvc/src/Main.cpp --- a/Lvc/src/Main.cpp +++ b/Lvc/src/Main.cpp @@ -1,1718 +1,1718 @@ #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 s) override { LTreeItem::Select(s); if (s) { 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"; if (newName.Find(nullFn) >= 0) { // Delete f->SetText(StripFirst(oldName), COL_FILENAME); f->SetText("D", COL_STATE); } else { f->SetText(StripFirst(newName), 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, LVariant *ReturnValue, LArray &Args) + bool CallMethod(const char *MethodName, LScriptArguments &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { - *ReturnValue = (AppPriv*)this; + *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); } } 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); p = Tabs->Append("Log"); p->Append(Log = new LTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(TEXTED_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); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD_LOCAL: { OpenLocalFolder(); break; } case IDM_ADD_REMOTE: { OpenRemoteFolder(); 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/Lvc/src/SshConnection.cpp b/Lvc/src/SshConnection.cpp --- a/Lvc/src/SshConnection.cpp +++ b/Lvc/src/SshConnection.cpp @@ -1,475 +1,474 @@ #include "lgi/common/Lgi.h" #include "Lvc.h" #include "SshConnection.h" #define TIMEOUT_PROMPT 1000 #define PROFILE_WaitPrompt 0 #define PROFILE_OnEvent 0 #define DEBUG_SSH_LOGGING 0 #if DEBUG_SSH_LOGGING #define SSH_LOG(...) d->sLog.Log(__VA_ARGS__) #else #define SSH_LOG(...) #endif ////////////////////////////////////////////////////////////////// SshConnection::SshConnection(LTextLog *log, const char *uri, const char *prompt) : LSsh(log), LEventTargetThread("SshConnection") { auto Wnd = log->GetWindow(); GuiHnd = Wnd->AddDispatch(); Prompt = prompt; Host.Set(Uri = uri); d = NULL; - LVariant Ret; - LArray Args; - if (Wnd->CallMethod(METHOD_GetContext, &Ret, Args)) + LScriptArguments Args(NULL); + if (Wnd->CallMethod(METHOD_GetContext, Args)) { - if (Ret.Type == GV_VOID_PTR) - d = (AppPriv*) Ret.Value.Ptr; + if (Args.GetReturn()->Type == GV_VOID_PTR) + d = (AppPriv*) Args.GetReturn()->Value.Ptr; } } bool SshConnection::DetectVcs(VcFolder *Fld) { LAutoPtr p(new LString(Fld->GetUri().sPath)); TypeNotify.Add(Fld); return PostObject(GetHandle(), M_DETECT_VCS, p); } bool SshConnection::Command(VcFolder *Fld, LString Exe, LString Args, ParseFn Parser, ParseParams *Params) { if (!Fld || !Exe || !Parser) return false; LAutoPtr p(new SshParams(this)); p->f = Fld; p->Exe = Exe; p->Args = Args; p->Parser = Parser; p->Params = Params; p->Path = Fld->GetUri().sPath; return PostObject(GetHandle(), M_RUN_CMD, p); } LStream *SshConnection::GetConsole() { if (!Connected) { auto r = Open(Host.sHost, Host.sUser, Host.sPass, true); Log->Print("Ssh: %s open: %i\n", Host.sHost.Get(), r); } if (Connected && !c) { c = CreateConsole(); WaitPrompt(c); } return c; } class ProgressListItem : public LListItem { int64_t v, maximum; public: ProgressListItem(int64_t mx = 100) : maximum(mx) { v = 0; } int64_t Value() { return v; } void Value(int64_t val) { v = val; Update(); } void OnPaint(LItem::ItemPaintCtx &Ctx) { auto pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(&Ctx); auto Fnt = GetList()->GetFont(); LDisplayString ds(Fnt, LFormatSize(v)); Fnt->Transparent(true); Fnt->Colour(Ctx.Fore, Ctx.Back); ds.Draw(pDC, Ctx.x1 + 10, Ctx.y1 + ((Ctx.Y() - ds.Y()) >> 1)); pDC->Colour(LProgressView::cNormal); int x1 = 120; int prog = Ctx.X() - x1; int x2 = (int) (v * prog / maximum); pDC->Rectangle(Ctx.x1 + x1, Ctx.y1 + 1, Ctx.x1 + x1 + x2, Ctx.y2 - 1); } }; #if PROFILE_WaitPrompt #define PROFILE(name) prof.Add(name) #else #define PROFILE(name) #endif LString LastLine(LString &input) { #define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') ) char *e = input.Get() + input.Length(); while (e > input.Get() && Ws(e[-1])) e--; char *s = e; while (s > input.Get() && !Ws(s[-1])) s--; return LString(s, e - s); } LString LastLine(LStringPipe &input) { #define Ws(ch) ( ((ch) == '\r') || ((ch) == '\n') || ((ch) == '\b') ) LString s, ln; input.Iterate([&s, &ln](auto ptr, auto bytes) { s = LString((char*)ptr, bytes) + s; auto end = s.Get() + s.Length(); for (auto p = end - 1; p >= s.Get(); p--) { if (Ws(*p)) { while (p < end && (*p == 0 || Ws(*p))) p++; ln = p; break; } } return ln.Get() == NULL; }, true); // if (!ln.Get()) // ln = s; LAssert(ln.Find("\n") < 0); DeEscape(ln); return ln; } bool SshConnection::WaitPrompt(LStream *con, LString *Data, const char *Debug) { LStringPipe out(4 << 10); auto Ts = LCurrentTime(); auto LastReadTs = Ts; ProgressListItem *Prog = NULL; #if PROFILE_WaitPrompt LProfile prof("WaitPrompt", 100); #endif size_t BytesRead = 0; bool CheckLast = true; while (!LSsh::Cancel->IsCancelled()) { PROFILE("read"); auto buf = out.GetBuffer(); if (!buf.ptr) { LAssert(!"Alloc failed."); LgiTrace("WaitPrompt.%s alloc failed.\n", Debug); return false; } auto rd = con->Read(buf.ptr, buf.len); if (rd < 0) { // Error case if (Debug) LgiTrace("WaitPrompt.%s rd=%i\n", Debug, rd); return false; } if (rd > 0) { // Got some data... keep asking for more: LString tmp((char*)buf.ptr, rd); SSH_LOG("waitPrompt data:", rd, tmp); BytesRead += rd; buf.Commit(rd); CheckLast = true; LastReadTs = LCurrentTime(); continue; } if (LCurrentTime() - LastReadTs > 4000) { auto sz = out.GetSize(); SSH_LOG("waitPrompt out:", sz, out); auto last = LastLine(out); // Does the buffer end with a ':' on a line by itself? // Various version control CLI's do that to pageinate data. // Obviously we're not going to deal with that directly, // but the developer will need to know that's happened. if (out.GetSize() > 2) { auto last = LastLine(out); if (last == ":") return false; } } if (!CheckLast) { // We've already checked the buffer for the prompt... LSleep(10); // Don't use too much CPU continue; } PROFILE("LastLine"); CheckLast = false; auto last = LastLine(out); LgiTrace("last='%s'\n", last.Get()); PROFILE("matchstr"); auto result = MatchStr(Prompt, last); SSH_LOG("waitPrompt result:", result, Prompt, last); if (Debug) { LgiTrace("WaitPrompt.%s match='%s' with '%s' = %i\n", Debug, Prompt.Get(), last.Get(), result); } if (result) { if (Data) { PROFILE("data process"); auto response = out.NewLStr(); if (response) { DeEscape(response); // Strip first line off the start.. it's the command... // And the last line... it's the prompt auto start = response.Get(); auto end = response.Get() + response.Length(); while (start < end && *start != '\n') start++; while (start < end && (*start == '\n' || *start == 0)) start++; while (end > start && end[-1] != '\n') end--; Data->Set(start, end - start); } SSH_LOG("waitPrompt data:", *Data); } if (Debug) LgiTrace("WaitPrompt.%s Prompt data=%i\n", Debug, Data?(int)Data->Length():0); break; } auto Now = LCurrentTime(); if (Now - Ts >= TIMEOUT_PROMPT) { if (!Prog && d->Commits) { Prog = new ProgressListItem(1 << 20); d->Commits->Insert(Prog); } if (Prog) Prog->Value(out.GetSize()); Log->Print("...reading: %s\n", LFormatSize(BytesRead).Get()); BytesRead = 0; Ts = Now; } } DeleteObj(Prog); return true; } bool SshConnection::HandleMsg(LMessage *m) { if (m->Msg() != M_RESPONSE) return false; LAutoPtr u((SshParams*)m->A()); if (!u || !u->c) return false; SshConnection &c = *u->c; AppPriv *d = c.d; if (!d) return false; if (u->Vcs) // Check the VCS type.. { c.Types.Add(u->Path, u->Vcs); for (auto f: c.TypeNotify) { if (d->Tree->HasItem(f)) f->OnVcsType(u->Output); else LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL); } } else { if (d && d->Tree->HasItem(u->f)) u->f->OnSshCmd(u); else LgiTrace("%s:%i - Folder no longer in tree (recently deleted?).\n", _FL); } return true; } LString PathFilter(LString s) { auto parts = s.SplitDelimit("/"); if ( (parts[0].Equals("~") || parts[0].Equals(".")) && s(0) == '/' ) { return s(1, -1).Replace(" ", "\\ "); } return s.Replace(" ", "\\ "); } #if PROFILE_OnEvent #define PROF(name) prof.Add(name) #else #define PROF(name) #endif LMessage::Result SshConnection::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_DETECT_VCS: { LAutoPtr p; if (!ReceiveA(p, Msg)) { LAssert(!"Incorrect param."); break; } LAutoPtr r(new SshParams(this)); LString ls, out; LString::Array lines; VersionCtrl Vcs = VcNone; LString path = PathFilter(*p); LStream *con = GetConsole(); if (!con) { r->Output = "Error: Failed to get console."; r->Vcs = VcError; } else { ls.Printf("find %s -maxdepth 1 -printf \"%%f\n\"\n", path.Get()); SSH_LOG("detectVcs:", ls); con->Write(ls, ls.Length()); auto pr = WaitPrompt(con, &out); lines = out.SplitDelimit("\r\n"); for (auto ln: lines) { if (ln.Equals(".svn")) Vcs = VcSvn; else if (ln.Equals("CVS")) Vcs = VcCvs; else if (ln.Equals(".hg")) Vcs = VcHg; else if (ln.Equals(".git")) Vcs = VcGit; } } r->Path = *p; printf("r->Output=%s\n", r->Output.Get()); if (Vcs == VcError) ; else if (Vcs != VcNone) { r->Vcs = Vcs; r->ExitCode = 0; } else { r->Vcs = VcError; r->Output.Printf("Error: no VCS detected.\n%s\n%s", ls.Get(), lines.Length() ? lines.Last().Get() : "#nodata"); } PostObject(GuiHnd, M_RESPONSE, r); break; } case M_RUN_CMD: { #if PROFILE_OnEvent LProfile prof("OnEvent"); #endif LAutoPtr p; if (!ReceiveA(p, Msg)) break; PROF("get console"); LString path = PathFilter(p->Path); LStream *con = GetConsole(); if (!con) break; auto Debug = p->Params && p->Params->Debug; PROF("cd"); LString cmd; cmd.Printf("cd %s\n", path.Get()); SSH_LOG(">>>> cd:", path); auto wr = con->Write(cmd, cmd.Length()); PROF("cd wait"); auto pr = WaitPrompt(con, NULL, Debug?"Cd":NULL); PROF("cmd"); cmd.Printf("%s %s\n", p->Exe.Get(), p->Args.Get()); SSH_LOG(">>>> cmd:", cmd); if (Log) Log->Print("%s", cmd.Get()); wr = con->Write(cmd, cmd.Length()); PROF("cmd wait"); pr = WaitPrompt(con, &p->Output, Debug?"Cmd":NULL); PROF("result"); LString result; cmd = "echo $?\n"; SSH_LOG(">>>> result:", cmd); wr = con->Write(cmd, cmd.Length()); PROF("result wait"); pr = WaitPrompt(con, &result, Debug?"Echo":NULL); if (pr) { p->ExitCode = (int)result.Int(); if (Log) Log->Print("... result=%i\n", p->ExitCode); } else if (Log) Log->Print("... result=failed\n"); PostObject(GuiHnd, M_RESPONSE, p); break; } default: { LAssert(!"Unhandled msg."); break; } } return 0; } diff --git a/include/lgi/common/ControlTree.h b/include/lgi/common/ControlTree.h --- a/include/lgi/common/ControlTree.h +++ b/include/lgi/common/ControlTree.h @@ -1,78 +1,78 @@ #pragma once #include "lgi/common/Tree.h" #include "lgi/common/Variant.h" #include "lgi/common/Button.h" class LControlTree : public LTree, public LDom { public: struct EnumValue { LString Name; LVariant Value; void Set(const char *n, int val) { Name = n; Value = val; } }; class Item : public LTreeItem { public: typedef LArray EnumArr; private: int CtrlId; LAutoString Opt; LVariantType Type; LVariant Value; LViewI *Ctrl; LButton *Browse; LAutoPtr Enum; void Save(); public: enum ItemFlags { TYPE_FILE = 0x1, }; int Flags; Item(int ctrlId, char *Txt, const char *opt, LVariantType type, LArray *pEnum); ~Item(); Item *Find(const char *opt); bool Serialize(LDom *Store, bool Write); void SetValue(LVariant &v); LRect &GetRect(); void Select(bool b); void OnPaint(ItemPaintCtx &Ctx); void SetEnum(LAutoPtr e); void OnVisible(bool v); void PositionControls(); }; protected: class LControlTreePriv *d; class Item *Resolve(bool Create, const char *Path, int CtrlId, LVariantType Type = GV_NULL, LArray *Enum = 0); void ReadTree(LXmlTag *t, LTreeNode *n); public: LControlTree(); ~LControlTree(); const char *GetClass() override { return "LControlTree"; } Item *Find(const char *opt); LTreeItem *Insert(const char *DomPath, int CtrlId, LVariantType Type, LVariant *Value = 0, LArray *Enum = 0); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool Serialize(LDom *Store, bool Write); int OnNotify(LViewI *c, LNotification n) override; - bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) override; + bool CallMethod(const char *MethodName, LScriptArguments &Args) override; }; diff --git a/include/lgi/common/Dom.h b/include/lgi/common/Dom.h --- a/include/lgi/common/Dom.h +++ b/include/lgi/common/Dom.h @@ -1,61 +1,63 @@ /** \file \author Matthew Allen \brief Document object model class.\n Copyright (C), Matthew Allen */ #pragma once class LVariant; #include "lgi/common/LgiInterfaces.h" #include "lgi/common/Mem.h" #include "lgi/common/Array.h" + + /// API for reading and writing properties in objects. class LgiClass LDom : virtual public LDomI { friend class LScriptEnginePrivate; friend struct LDomRef; friend class LVirtualMachinePriv; protected: LDom *ResolveObject(const char *Var, LString &Name, LString &Array); virtual bool _OnAccess(bool Start) { return true; } virtual bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) { return false; } virtual bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) { return false; } public: /// Gets an object's property bool GetValue ( /// The string describing the property const char *Var, /// The value returned LVariant &Value ); /// Sets an object's property bool SetValue ( /// The string describing the property const char *Var, /// The value to set the property to LVariant &Value ); }; /// This is a global map between strings and enum values for fast lookup of /// object properties inside the SetVariant, GetVariant and CallMethod handlers. enum LDomProperty { #undef _ #define _(symbol, txt) symbol, #include "lgi/common/DomFields.h" #undef _ }; LgiFunc LDomProperty LStringToDomProp(const char *Str); LgiFunc const char *LDomPropToString(LDomProperty Prop); diff --git a/include/lgi/common/File.h b/include/lgi/common/File.h --- a/include/lgi/common/File.h +++ b/include/lgi/common/File.h @@ -1,738 +1,738 @@ /** \file \author Matthew Allen \date 24/5/2002 \brief Common file system header Copyright (C) 1995-2002, Matthew Allen */ #pragma once #include #include "lgi/common/Mem.h" #include "lgi/common/Stream.h" #include "lgi/common/Array.h" #include "lgi/common/RefCount.h" #include "lgi/common/StringClass.h" #include "lgi/common/Error.h" #ifdef WIN32 typedef HANDLE OsFile; #define INVALID_HANDLE INVALID_HANDLE_VALUE #define ValidHandle(hnd) ((hnd) != INVALID_HANDLE_VALUE) #define DIR_PATH_SIZE 512 #define LFileCompare _stricmp #define O_READ GENERIC_READ #define O_WRITE GENERIC_WRITE #define O_READWRITE (GENERIC_READ | GENERIC_WRITE) #define O_SHARE 0x01000000 #define O_NO_CACHE 0x00800000 #else #include #include #include #include typedef int OsFile; #define INVALID_HANDLE -1 #define ValidHandle(hnd) ((hnd) >= 0) #define LFileCompare strcmp #define O_READ O_RDONLY #define O_WRITE O_WRONLY #ifdef MAC #define O_SHARE O_SHLOCK #else #define O_SHARE 0 #endif #define O_READWRITE O_RDWR #endif ///////////////////////////////////////////////////////////////////// // Defines #define FileDev (LFileSystem::GetInstance()) #define MAX_PATH_LEN 512 // File system types (used by LDirectory and LVolume) enum LVolumeTypes { VT_NONE, VT_FLOPPY, VT_HARDDISK, VT_CDROM, VT_RAMDISK, VT_FOLDER, VT_FILE, VT_DESKTOP, VT_MUSIC, VT_PICTURES, VT_DOWNLOADS, VT_TRASH, VT_USB_FLASH, VT_APPLICATIONS, VT_NETWORK_NEIGHBOURHOOD, VT_NETWORK_MACHINE, VT_NETWORK_SHARE, VT_NETWORK_PRINTER, VT_NETWORK_GROUP, VT_MAX, }; // Volume attributes #define VA_CASE_SENSITIVE 0x0001 #define VA_CASE_PRESERVED 0x0002 #define VA_UNICODE_ON_DISK 0x0004 #define VA_LFN_API 0x4000 #define VA_COMPRESSED 0x8000 // File attributes #define FA_NORMAL 0x0000 #define FA_READONLY 0x0001 #define FA_HIDDEN 0x0002 #define FA_SYSTEM 0x0004 #define FA_VOLUME 0x0008 #define FA_DIRECTORY 0x0010 #define FA_ARCHIVE 0x0020 #define FA_COMPUTER 0x0040 ///////////////////////////////////////////////////////////////////// // Abstract classes /// Generic directory iterator class LgiClass LDirectory { struct LDirectoryPriv *d; public: constexpr static int MaxPathLen = 512; LDirectory(); virtual ~LDirectory(); /// \brief Starts the search. The entries '.' and '..' are never returned. /// The default pattern returns all files. /// \return Non zero on success virtual int First ( /// The path of the directory const char *Name, /// The pattern to match files against. /// \sa The default LGI_ALL_FILES matchs all files. const char *Pattern = LGI_ALL_FILES ); /// \brief Get the next match /// \return Non zero on success virtual int Next(); /// \brief Finish the search /// \return Non zero on success virtual int Close(); /// \brief Constructs the full path of the current directory entry /// \return Non zero on success virtual bool Path ( // The buffer to write to char *s, // The size of the output buffer in bytes int BufSize ) const; virtual const char *FullPath(); /// Gets the current entries attributes (platform specific) virtual long GetAttributes() const; /// Gets the name of the current entry. (Doesn't include the path). virtual char *GetName() const; virtual LString FileName() const; /// Gets the user id of the current entry. (Doesn't have any meaning on Win32). virtual int GetUser ( /// If true gets the group id instead of the user id. bool Group ) const; /// Gets the entries creation time. You can convert this to an easy to read for using LDateTime. virtual uint64 GetCreationTime() const; /// Gets the entries last access time. You can convert this to an easy to read for using LDateTime. virtual uint64 GetLastAccessTime() const; /// Gets the entries last modified time. You can convert this to an easy to read for using LDateTime. virtual uint64 GetLastWriteTime() const; /// Returns the uncompressed size of the entry. virtual uint64 GetSize() const; /// Returns the size of file on disk. This can be both larger and smaller than the logical size. virtual int64 GetSizeOnDisk(); /// Returns true if the entry is a sub-directory. virtual bool IsDir() const; /// Returns true if the entry is a symbolic link. virtual bool IsSymLink() const; /// Returns true if the entry is read only. virtual bool IsReadOnly() const; /// \brief Returns true if the entry is hidden. /// This is equivilant to a attribute flag on win32 and a leading '.' on unix. virtual bool IsHidden() const; /// Creates an copy of this type of LDirectory class. virtual LDirectory *Clone(); /// Gets the type code of the current entry. See the VT_?? defines for possible values. virtual int GetType() const; /// Converts a string to the 64-bit value returned from the date functions. bool ConvertToTime(char *Str, int SLen, uint64 Time) const; /// Converts the 64-bit value returned from the date functions to a string. bool ConvertToDate(char *Str, int SLen, uint64 Time) const; }; /// Describes a volume connected to the system class LgiClass LVolume { friend class LFileSystem; friend struct LVolumePriv; protected: struct LVolumePriv *d; public: LVolume(const char *Path = NULL); LVolume(LSystemPath SysPath, const char *Name); virtual ~LVolume(); const char *Name() const; const char *Path() const; int Type() const; int Flags() const; uint64 Size() const; uint64 Free() const; LSurface *Icon() const; virtual bool IsMounted() const; virtual bool SetMounted(bool Mount); virtual LVolume *First(); virtual LVolume *Next(); virtual LDirectory *GetContents(); virtual void Insert(LAutoPtr v); }; typedef int (*CopyFileCallback)(void *token, int64 Done, int64 Total); /// A singleton class for accessing the file system class LgiClass LFileSystem { friend class LFile; static LFileSystem *Instance; class LFileSystemPrivate *d; LVolume *Root = NULL; public: LFileSystem(); ~LFileSystem(); /// Return the current instance of the file system. The shorthand for this is "FileDev". static LFileSystem *GetInstance() { return Instance; } /// Call this when the devices on the system change. For instance on windows /// when you receive WM_DEVICECHANGE. void OnDeviceChange(char *Reserved = 0); /// Gets the root volume of the system. LVolume *GetRootVolume(); /// Copies a file bool Copy ( /// The file to copy from... const char *From, /// The file to copy to. Any existing file there will be overwritten without warning. const char *To, /// The error code or zero on success LError *Status = 0, /// Optional callback when some data is copied. CopyFileCallback Callback = 0, /// A user defined token passed to the callback function void *Token = 0 ); /// Delete file bool Delete(const char *FileName, bool ToTrash = true); /// Delete files bool Delete ( /// The list of files to delete LArray &Files, /// A list of status codes where 0 means success and non-zero is an error code, usually an OS error code. NULL if not required. LArray *Status = NULL, /// true if you want the files moved to the trash folder, false if you want them deleted directly bool ToTrash = true ); /// Create a directory bool CreateFolder(const char *PathName, bool CreateParentFoldersIfNeeded = false, LError *Err = NULL); /// Remove's a directory bool RemoveFolder ( /// The path to remove const char *PathName, /// True if you want this function to recursively delete all contents of the path passing in. bool Recurse = false ); LString GetCurrentFolder(); bool SetCurrentFolder(const char *PathName); /// Moves a file to a new location. Only works on the same device. bool Move(const char *OldName, const char *NewName, LError *Err = NULL); }; #if defined(WINDOWS) #define GFileOps() \ GFileOp(char) \ GFileOp(int8_t) \ GFileOp(uint8_t) \ GFileOp(int16_t) \ GFileOp(uint16_t) \ GFileOp(int32_t) \ GFileOp(uint32_t) \ GFileOp(int64_t) \ GFileOp(uint64_t) \ GFileOp(long) \ GFileOp(ulong) \ GFileOp(float) \ GFileOp(double) #elif defined(LINUX) || defined(HAIKU) #define GFileOps() \ GFileOp(char) \ GFileOp(int8_t) \ GFileOp(uint8_t) \ GFileOp(int16_t) \ GFileOp(uint16_t) \ GFileOp(int32_t) \ GFileOp(uint32_t) \ GFileOp(int64_t) \ GFileOp(uint64_t) \ GFileOp(float) \ GFileOp(double) /* GFileOp(long) \ GFileOp(ulong) \ */ #else #define GFileOps() \ GFileOp(char) \ GFileOp(int8_t) \ GFileOp(uint8_t) \ GFileOp(int16_t) \ GFileOp(uint16_t) \ GFileOp(int32_t) \ GFileOp(uint32_t) \ GFileOp(int64_t) \ GFileOp(uint64_t) \ GFileOp(size_t) \ GFileOp(ssize_t) \ GFileOp(float) \ GFileOp(double) #endif /// Generic file access class class LgiClass LFile : public LStream, public LRefCount { protected: class LFilePrivate *d; ssize_t SwapRead(uchar *Buf, ssize_t Size); ssize_t SwapWrite(uchar *Buf, ssize_t Size); public: LFile(const char *Path = NULL, int Mode = O_READ); virtual ~LFile(); OsFile Handle(); void ChangeThread() override; operator bool() { return IsOpen(); } /// \brief Opens a file /// \return Non zero on success int Open ( /// The path of the file to open const char *Name, /// The mode to open the file with. One of O_READ, O_WRITE or O_READWRITE. int Attrib ) override; /// Returns non zero if the class is associated with an open file handle. bool IsOpen() override; /// Returns the most recent error code encountered. int GetError(); /// Closes the file. int Close() override; /// Gets the mode that the file was opened with. int GetOpenMode(); /// Gets the block size int GetBlockSize(); /// \brief Gets the current file pointer. /// \return The file pointer or -1 on error. int64 GetPos() override; /// \brief Sets the current file pointer. /// \return The new file pointer or -1 on error. int64 SetPos(int64 Pos) override; /// \brief Gets the file size. /// \return The file size or -1 on error. int64 GetSize() override; /// \brief Sets the file size. /// \return The new file size or -1 on error. int64 SetSize(int64 Size) override; /// \brief Reads bytes into memory from the current file pointer. /// \return The number of bytes read or <= 0. ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) override; /// \brief Writes bytes from memory to the current file pointer. /// \return The number of bytes written or <= 0. ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) override; /// Modified time functions uint64_t GetModifiedTime(); bool SetModifiedTime(uint64_t dt); /// Gets the path used to open the file virtual const char *GetName(); /// Moves the current file pointer. virtual int64 Seek(int64 To, int Whence); /// Returns true if the current file pointer is at the end of the file. virtual bool Eof(); /// Resets the status value. virtual void SetStatus(bool s = false); /// \brief Returns true if all operations were successful since the file was openned or SetStatus /// was used to reset the file's status. virtual bool GetStatus(); /// \brief Sets the swap option. When switched on all integer reads/writes will have their bytes /// swaped. virtual void SetSwap(bool s); /// Gets the current swap setting. virtual bool GetSwap(); // String virtual ssize_t ReadStr(char *Buf, ssize_t Size); virtual ssize_t WriteStr(char *Buf, ssize_t Size); // Operators #define GFileOp(type) virtual LFile &operator >> (type &i); GFileOps(); #undef GFileOp #define GFileOp(type) virtual LFile &operator << (type i); GFileOps(); #undef GFileOp // LDom impl bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL) override; - bool CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) override; + bool CallMethod(const char *Name, LScriptArguments &Args) override; // Path handling class LgiClass Path : public LString::Array { LString Full; public: enum State { TypeNone = 0, TypeFolder = 1, TypeFile = 2, }; static LString Sep; Path(const char *init = NULL, const char *join = NULL) { SetFixedLength(false); if (init) *this = init; if (join) *this += join; } Path(LAutoString Init) { SetFixedLength(false); if (Init) *this = Init.Get(); } Path(LString Init) { SetFixedLength(false); if (Init) *this = Init; } Path(LSystemPath Which, int WordSize = 0) { SetFixedLength(false); *this = GetSystem(Which, WordSize); } Path &operator =(const char *p) { LString s(p); *((LString::Array*)this) = s.SplitDelimit("\\/"); SetFixedLength(false); return *this; } Path &operator =(const LString &s) { *((LString::Array*)this) = s.SplitDelimit("\\/"); SetFixedLength(false); return *this; } Path &operator =(const LArray &p) { *((LArray*)this) = p; Full.Empty(); SetFixedLength(false); return *this; } Path &operator +=(Path &a) { SetFixedLength(false); if (a.Length() == 0) return *this; for (unsigned i=0; i 0 && nsz == sz && s.Set(NULL, nsz)) { int Block = 1 << 30; NativeInt i; for (i = 0; i < nsz; ) { int Len = MIN(Block, (int) (nsz - i)); ssize_t rd = Read(s.Get() + i, Len); if (rd <= 0) break; i += rd; } s.Get()[i] = 0; } return s; } /// Write a string bool Write(const LString &s) { return Write(s.Get(), s.Length()) == s.Length(); } /// Sets the file to zero size. /// Useful for this sort of thing: /// LFile(MyPath, O_WRITE).Empty().Write(MyData); LFile &Empty() { SetSize(0); return *this; } }; // Functions LgiFunc int64 LFileSize(const char *FileName); /// This function checks for the existence of a file (will return false for a folder). LgiFunc bool LFileExists(const char *File, char *CorrectCase = NULL); /// This function checks for the existence of a directory. LgiFunc bool LDirExists(const char *Dir, char *CorrectCase = NULL); /// Looks up the target of a link or shortcut file. LgiFunc bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len); /// Reads in a text file to a dynamically allocated string LgiFunc char *LReadTextFile(const char *File); /// Trims off a path segment LgiFunc bool LTrimDir(char *Path); /// Gets the file name part of the path LgiExtern const char *LGetLeaf(const char *Path); /// Gets the file name part of the path LgiExtern char *LGetLeaf(char *Path); /// /returns true if the path is relative as opposed to absolute. LgiFunc bool LIsRelativePath(const char *Path); /// Creates a relative path LgiClass LString LMakeRelativePath(const char *Base, const char *Path); /// Appends 'File' to 'Dir' and puts the result in 'Str'. Dir and Str can be the same buffer. LgiFunc bool LMakePath(char *Str, int StrBufLen, const char *Dir, const char *File); /// Gets the file name's extension. LgiFunc char *LGetExtension(const char *File); /// \returns true if 'FileName' is an executable of some kind (looks at file name only). LgiFunc bool LIsFileNameExecutable(const char *FileName); /// \returns true if 'FileName' is an executable of some kind (looks at content). LgiFunc bool LIsFileExecutable(const char *FileName, LStreamI *f, int64 Start, int64 Len); /// Get information about the disk that a file resides on. LgiFunc bool LGetDriveInfo(char *Path, uint64 *Free, uint64 *Size = 0, uint64 *Available = 0); /// Shows the file's properties dialog LgiFunc void LShowFileProperties(OsView Parent, const char *Filename); /// Opens to the file or folder in the OS file browser (Explorer/Finder etc) LgiFunc bool LBrowseToFile(const char *Filename); /// Returns the physical device a file resides on LgiExtern LString LGetPhysicalDevice(const char *Path); diff --git a/include/lgi/common/Gdc2.h b/include/lgi/common/Gdc2.h --- a/include/lgi/common/Gdc2.h +++ b/include/lgi/common/Gdc2.h @@ -1,1474 +1,1474 @@ /** \file \author Matthew Allen \date 20/2/1997 \brief GDC v2.xx header */ #ifndef __GDC2_H_ #define __GDC2_H_ #include #include "LgiOsDefs.h" // Platform specific // Alpha Bliting #ifdef WINNATIVE #include #include #pragma warning(disable:4263) #include #pragma warning(error:4263) #pragma comment (lib,"Gdiplus.lib") #endif // sub-system headers #include "lgi/common/LgiInc.h" #include "lgi/common/LgiUiBase.h" #include "lgi/common/File.h" #include "lgi/common/Mem.h" #include "lgi/common/Core.h" #include "lgi/common/Containers.h" #include "lgi/common/Capabilities.h" #include "lgi/common/RefCount.h" #include "lgi/common/Palette.h" #include "lgi/common/ColourSpace.h" #include "lgi/common/Rect.h" #include "lgi/common/Point.h" #include "lgi/common/Colour.h" #ifndef AC_SRC_OVER #define AC_SRC_OVER 0 #endif #ifndef AC_SRC_ALPHA #define AC_SRC_ALPHA 1 #endif #include "lgi/common/Library.h" // Defines /// The default gamma curve (none) used by the gamma LUT GdcDevice::GetGamma #define LGI_DEFAULT_GAMMA 1.0 #ifndef LGI_PI /// The value of PI #define LGI_PI 3.141592654 #endif /// Converts degrees to radians #define LGI_DegToRad(i) ((i)*LGI_PI/180) /// Converts radians to degrees #define LGI_RadToDeg(i) ((i)*180/LGI_PI) #if defined(WIN32) && !defined(_WIN64) /// Use intel assembly instructions, comment out for porting #define GDC_USE_ASM #endif /// Blending mode: overwrite #define GDC_SET 0 /// Blending mode: bitwise AND with background #define GDC_AND 1 /// Blending mode: bitwise OR with background #define GDC_OR 2 /// Blending mode: bitwise XOR with background #define GDC_XOR 3 /// Blending mode: alpha blend with background #define GDC_ALPHA 4 #define GDC_REMAP 5 #define GDC_MAXOP 6 #define GDC_CACHE_SIZE 4 // Channel types #define GDCC_MONO 0 #define GDCC_GREY 1 #define GDCC_INDEX 2 #define GDCC_R 3 #define GDCC_G 4 #define GDCC_B 5 #define GDCC_ALPHA 6 // Native data formats #define GDC_8BIT 0 #define GDC_16BIT 1 #define GDC_24BIT 2 #define GDC_32BIT 3 #define GDC_MAXFMT 4 // Colour spaces #define GDC_8I 0 // 8 bit paletted #define GDC_5R6G5B 1 // 16 bit #define GDC_8B8G8B 2 // 24 bit #define GDC_8A8R8G8B 3 // 32 bit #define GDC_MAXSPACE 4 // Update types #define GDC_PAL_CHANGE 0x1 #define GDC_BITS_CHANGE 0x2 // Flood fill types /// LSurface::FloodFill to a different colour #define GDC_FILL_TO_DIFFERENT 0 /// LSurface::FloodFill to a certain colour #define GDC_FILL_TO_BORDER 1 /// LSurface::FloodFill while colour is near to the seed colour #define GDC_FILL_NEAR 2 // Gdc options /// Used in GdcApp8Set::Blt when doing colour depth reduction to 8 bit. #define GDC_REDUCE_TYPE 0 /// No conversion #define REDUCE_NONE 0 /// Nearest colour pixels #define REDUCE_NEAREST 1 /// Halftone the pixels #define REDUCE_HALFTONE 2 /// Error diffuse the pixels #define REDUCE_ERROR_DIFFUSION 3 /// Not used. #define REDUCE_DL1 4 /// Not used. #define GDC_HALFTONE_BASE_INDEX 1 /// When in 8-bit defined the behaviour of GdcDevice::GetColour #define GDC_PALETTE_TYPE 2 /// Allocate colours from the palette #define PALTYPE_ALLOC 0 /// Use an RGB cube #define PALTYPE_RGB_CUBE 1 /// Use a HSL palette #define PALTYPE_HSL 2 /// Converts images to the specified bit-depth on load, does nothing if 0 #define GDC_PROMOTE_ON_LOAD 3 #define GDC_MAX_OPTION 4 // LSurface Flags #define GDC_ON_SCREEN 0x0002 #define GDC_ALPHA_CHANNEL 0x0004 #define GDC_UPDATED_PALETTE 0x0008 #define GDC_CAPTURE_CURSOR 0x0010 #define GDC_OWN_APPLICATOR 0x0020 #define GDC_CACHED_APPLICATOR 0x0040 #define GDC_OWN_PALETTE 0x0080 #define GDC_DRAW_ON_ALPHA 0x0100 #define GDC_ANTI_ALIAS 0x0200 // Region types #define GDC_RGN_NONE 0 // No clipping #define GDC_RGN_SIMPLE 1 // Single rectangle #define GDC_RGN_COMPLEX 2 // Many rectangles // Error codes #define GDCERR_NONE 0 #define GDCERR_ERROR 1 #define GDCERR_CANT_SET_SCAN_WIDTH 2 #define GDC_INVALIDMODE -1 // Font display flags #define GDCFNT_TRANSPARENT 0x0001 // not set - SOLID #define GDCFNT_UNDERLINE 0x0002 // Palette file types #define GDCPAL_JASC 1 #define GDCPAL_MICROSOFT 2 // Misc #define BMPWIDTH(bits) ((((bits)+31)/32)<<2) // Look up tables #define Div255Lut (GdcDevice::GetInst()->GetDiv255()) // Classes class LFilter; class LSurface; class LgiClass LBmpMem { public: enum GdcMemFlags { BmpOwnMemory = 0x1, BmpPreMulAlpha = 0x2, }; uchar *Base; int x, y; ssize_t Line; LColourSpace Cs; int Flags; LBmpMem(); ~LBmpMem(); int GetBits() { return LColourSpaceToBits(Cs); } int BytesPerPx() { return LColourSpaceToBits(Cs) >> 3; } uchar *AddressOf(int ox, int oy) { LAssert(ox >= 0 && ox < x && oy >= 0 && oy < y); return Base + (oy * Line) + (ox * BytesPerPx()); } bool PreMul() { return (Flags & BmpPreMulAlpha) != 0; } bool PreMul(bool set) { if (set) Flags |= BmpPreMulAlpha; else Flags &= ~BmpPreMulAlpha; return PreMul(); } bool OwnMem() { return (Flags & BmpOwnMemory) != 0; } bool OwnMem(bool set) { if (set) Flags |= BmpOwnMemory; else Flags &= ~BmpOwnMemory; return OwnMem(); } void GetMemoryExtents(uchar *&Start, uchar *&End) { if (Line < 0) { Start = Base + (y * Line); End = Base + Line; } else { Start = Base; End = Base + (y * Line); } } bool Overlap(LBmpMem *Mem) { uchar *ThisStart, *ThisEnd; GetMemoryExtents(ThisStart, ThisEnd); uchar *MemStart, *MemEnd; GetMemoryExtents(MemStart, MemEnd); if (ThisEnd < MemStart) return false; if (ThisStart > MemEnd) return false; return true; } }; #define GAPP_ALPHA_A 1 #define GAPP_ALPHA_PAL 2 #define GAPP_BACKGROUND 3 #define GAPP_ANGLE 4 #define GAPP_BOUNDS 5 /// \brief Class to draw onto a memory bitmap /// /// This class assumes that all clipping is done by the layer above. /// It can then implement very simple loops to do the work of filling /// pixels class LgiClass LApplicator { protected: LBmpMem *Dest; LBmpMem *Alpha; LPalette *Pal; int Op; public: union { COLOUR c; // main colour System24BitPixel p24; System32BitPixel p32; }; LApplicator() { c = 0; Dest = NULL; Alpha = NULL; Pal = NULL; } LApplicator(COLOUR Colour) { c = Colour; } virtual ~LApplicator() { } virtual const char *GetClass() { return "LApplicator"; } /// Get a parameter virtual int GetVar(int Var) { return 0; } /// Set a parameter virtual int SetVar(int Var, NativeInt Value) { return 0; } LColourSpace GetColourSpace() { return Dest ? Dest->Cs : CsNone; } /// Sets the operator void SetOp(int o, int Param = -1) { Op = o; } /// Gets the operator int GetOp() { return Op; } /// Gets the bit depth int GetBits() { return (Dest) ? LColourSpaceToBits(Dest->Cs) : 0; } /// Gets the flags in operation int GetFlags() { return (Dest) ? Dest->Flags : 0; } /// Gets the palette LPalette *GetPal() { return Pal; } /// Sets the bitmap to write onto virtual bool SetSurface(LBmpMem *d, LPalette *p = 0, LBmpMem *a = 0) = 0; // sets Dest, returns FALSE on error /// Sets the current position to an x,y virtual void SetPtr(int x, int y) = 0; // calculates Ptr from x, y /// Moves the current position one pixel left virtual void IncX() = 0; /// Moves the current position one scanline down virtual void IncY() = 0; /// Offset the current position virtual void IncPtr(int X, int Y) = 0; /// Sets the pixel at the current location with the current colour virtual void Set() = 0; /// Gets the colour of the pixel at the current location virtual COLOUR Get() = 0; /// Draws a vertical line from the current position down 'height' scanlines virtual void VLine(int height) = 0; /// Draws a rectangle starting from the current position, 'x' pixels across and 'y' pixels down virtual void Rectangle(int x, int y) = 0; /// Copies bitmap data to the current position virtual bool Blt(LBmpMem *Src, LPalette *SPal, LBmpMem *SrcAlpha = 0) = 0; }; /// Creates applications from parameters. class LgiClass LApplicatorFactory { public: LApplicatorFactory(); virtual ~LApplicatorFactory(); /// Find the application factory and create the appropriate object. static LApplicator *NewApp(LColourSpace Cs, int Op); virtual LApplicator *Create(LColourSpace Cs, int Op) = 0; }; class LgiClass LApp15 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp16 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp24 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp32 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LgiClass LApp8 : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; class LAlphaFactory : public LApplicatorFactory { public: LApplicator *Create(LColourSpace Cs, int Op); }; #define OrgX(x) x -= OriginX #define OrgY(y) y -= OriginY #define OrgXy(x, y) x -= OriginX; y -= OriginY #define OrgPt(p) p.x -= OriginX; p.y -= OriginY #define OrgRgn(r) r.Offset(-OriginX, -OriginY) /// Base class API for graphics operations class LgiClass LSurface : public LRefCount, public LDom { friend class LFilter; friend class LView; friend class LWindow; friend class LVariant; friend class LRegionClipDC; friend class LMemDC; void Init(); protected: int Flags; int PrevOp; LRect Clip; LColourSpace ColourSpace; LBmpMem *pMem; LSurface *pAlphaDC; LPalette *pPalette; LApplicator *pApp; LApplicator *pAppCache[GDC_CACHE_SIZE]; int OriginX, OriginY; // Protected functions LApplicator *CreateApplicator(int Op, LColourSpace Cs = CsNone); uint32_t LineBits; uint32_t LineMask; uint32_t LineReset; #if WINNATIVE OsPainter hDC; OsBitmap hBmp; LAutoPtr GdiplusGfx; #endif public: LSurface(); LSurface(LSurface *pDC); virtual ~LSurface(); // Win32 #if defined(__GTK_H__) /// Gets the drawable size, regardless of clipping or client rect virtual LPoint GetSize() { LPoint p; return p; } virtual Gtk::GtkPrintContext *GetPrintContext() { return NULL; } virtual Gtk::GdkPixbuf *CreatePixBuf() { return NULL; } #elif defined(WINNATIVE) virtual HDC StartDC() { return hDC; } virtual void EndDC() {} Gdiplus::Graphics *GetGfx(); Gdiplus::Color GdiColour(); #elif defined MAC virtual CGColorSpaceRef GetColourSpaceRef() { return 0; } #endif virtual const char *GetClass() { return "LSurface"; } virtual OsBitmap GetBitmap(); virtual OsPainter Handle(); virtual void SetClient(LRect *c) {} virtual bool GetClient(LRect *c) { return false; } // Creation enum SurfaceCreateFlags { SurfaceCreateNone, SurfaceRequireNative, SurfaceRequireExactCs, }; virtual bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone) { return false; } virtual void Update(int Flags) {} // Alpha channel /// Returns true if this Surface has an alpha channel virtual bool HasAlpha() { return pAlphaDC != 0; } /// Creates or destroys the alpha channel for this surface virtual bool HasAlpha(bool b); /// Returns true if we are drawing on the alpha channel bool DrawOnAlpha() { return ((Flags & GDC_DRAW_ON_ALPHA) != 0); } /// True if you want to edit the alpha channel rather than the colour bits bool DrawOnAlpha(bool Draw); /// Returns the surface of the alpha channel. LSurface *AlphaDC() { return pAlphaDC; } /// \returns the anti-alias setting bool AntiAlias(); /// Set the anti-alias setting bool AntiAlias(bool antiAlias); /// Lowers the alpha of the whole image to Alpha/255.0. /// Only works on bitmaps with an alpha channel (i.e. CsRgba32 or it's variants) bool SetConstantAlpha(uint8_t Alpha); // Create sub-images (that reference the memory of this object) LSurface *SubImage(LRect r); LSurface *SubImage(int x1, int y1, int x2, int y2) { LRect r(x1, y1, x2, y2); return SubImage(r); } // Applicator virtual bool Applicator(LApplicator *pApp); virtual LApplicator *Applicator(); // Palette virtual LPalette *Palette(); virtual void Palette(LPalette *pPal, bool bOwnIt = true); // Clip region virtual LRect ClipRgn(LRect *Rgn); virtual LRect ClipRgn(); /// Gets the current colour virtual COLOUR Colour() { return pApp->c; } /// Sets the current colour virtual COLOUR Colour ( /// The new colour COLOUR c, /// The bit depth of the new colour or 0 to indicate the depth is the same as the current Surface int Bits = 0 ); /// Sets the current colour virtual LColour Colour ( /// The new colour LColour c ); /// Sets the colour to a system colour virtual LColour Colour(LSystemColour SysCol); /// Gets the current blending mode in operation virtual int Op() { return (pApp) ? pApp->GetOp() : GDC_SET; } /// Sets the current blending mode in operation /// \sa GDC_SET, GDC_AND, GDC_OR, GDC_XOR and GDC_ALPHA virtual int Op(int Op, NativeInt Param = -1); /// Gets the width in pixels virtual int X() { return (pMem) ? pMem->x : 0; } /// Gets the height in pixels virtual int Y() { return (pMem) ? pMem->y : 0; } /// Gets the bounds of the image as a LRect LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } /// Gets the length of a scanline in bytes virtual ssize_t GetRowStep() { return (pMem) ? pMem->Line : 0; } /// Returns the resolution of the device virtual LPoint GetDpi() { return LPoint(96,96); } /// Gets the bits per pixel virtual int GetBits() { return (pMem) ? LColourSpaceToBits(pMem->Cs) : 0; } /// Gets the colour space of the pixels virtual LColourSpace GetColourSpace() { return ColourSpace; } /// Gets any flags associated with the surface virtual int GetFlags() { return Flags; } /// Returns true if the surface is on the screen virtual class LScreenDC *IsScreen() { return 0; } /// Returns true if the surface is for printing virtual bool IsPrint() { return false; } /// Returns a pointer to the start of a scanline, or NULL if not available virtual uchar *operator[](int y); /// Dumps various debug information virtual LString Dump() { return LString("Not implemented."); } /// Returns true if this surface supports alpha compositing when using Blt virtual bool SupportsAlphaCompositing() { return false; } /// \returns whether if pixel data is pre-multiplied alpha virtual bool IsPreMultipliedAlpha(); /// Converts the pixel data between pre-mul alpha or non-pre-mul alpha virtual bool ConvertPreMulAlpha(bool ToPreMul); /// Makes the alpha channel opaque virtual bool MakeOpaque(); /// Gets the surface origin virtual void GetOrigin(int &x, int &y) { x = OriginX; y = OriginY; } /// Gets the surface origin LPoint GetOrigin() { int x, y; GetOrigin(x, y); return LPoint(x, y); } /// Sets the surface origin virtual void SetOrigin(int x, int y) { OriginX = x; OriginY = y; } /// Sets the surface origin void SetOrigin(LPoint p) { SetOrigin(p.x, p.y); } /// Sets a pixel with the current colour virtual void Set(int x, int y); /// Gets a pixel (doesn't work on some types of image, i.e. LScreenDC) virtual COLOUR Get(int x, int y); // Line /// Draw a horizontal line in the current colour virtual void HLine(int x1, int x2, int y); /// Draw a vertical line in the current colour virtual void VLine(int x, int y1, int y2); /// Draw a line in the current colour virtual void Line(int x1, int y1, int x2, int y2); /// Some surfaces only support specific line styles (e.g. GDI/Win) enum LineStyles { LineNone = 0x0, LineSolid = 0xffffffff, LineAlternate = 0xaaaaaaaa, LineDash = 0xf0f0f0f0, LineDot = 0xcccccccc, LineDashDot = 0xF33CCF30, LineDashDotDot = 0xf0ccf0cc, }; virtual uint LineStyle(uint32_t Bits, uint32_t Reset = 0x80000000) { uint32_t B = LineBits; LineBits = Bits; LineMask = LineReset = Reset; return B; } virtual uint LineStyle() { return LineBits; } // Curve /// Stroke a circle in the current colour virtual void Circle(double cx, double cy, double radius); /// Fill a circle in the current colour virtual void FilledCircle(double cx, double cy, double radius); /// Stroke an arc in the current colour virtual void Arc(double cx, double cy, double radius, double start, double end); /// Fill an arc in the current colour virtual void FilledArc(double cx, double cy, double radius, double start, double end); /// Stroke an ellipse in the current colour virtual void Ellipse(double cx, double cy, double x, double y); /// Fill an ellipse in the current colour virtual void FilledEllipse(double cx, double cy, double x, double y); // Rectangular /// Stroke a rectangle in the current colour virtual void Box(int x1, int y1, int x2, int y2); /// Stroke a rectangle in the current colour virtual void Box ( /// The rectangle, or NULL to stroke the edge of the entire surface LRect *a = NULL ); /// Fill a rectangle in the current colour virtual void Rectangle(int x1, int y1, int x2, int y2); /// Fill a rectangle in the current colour virtual void Rectangle ( /// The rectangle, or NULL to fill the entire surface LRect *a = NULL ); /// Copy an image onto the surface virtual void Blt ( /// The destination x coord int x, /// The destination y coord int y, /// The source surface LSurface *Src, /// The optional area of the source to use, if not specified the whole source is used LRect *a = NULL ); void Blt(int x, int y, LSurface *Src, LRect a) { Blt(x, y, Src, &a); } /// Not implemented virtual void StretchBlt(LRect *d, LSurface *Src, LRect *s); // Other /// Fill a polygon in the current colour virtual void Polygon(int Points, LPoint *Data); /// Stroke a bezier in the current colour virtual void Bezier(int Threshold, LPoint *Pt); /// Flood fill in the current colour (doesn't work on a LScreenDC) virtual void FloodFill ( /// Start x coordinate int x, /// Start y coordinate int y, /// Use #GDC_FILL_TO_DIFFERENT, #GDC_FILL_TO_BORDER or #GDC_FILL_NEAR int Mode, /// Fill colour COLOUR Border = 0, /// The bounds of the filled area or NULL if you don't care LRect *Bounds = NULL ); /// Describes the image virtual LString GetStr(); // LDom interface bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); - bool CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args); + bool CallMethod(const char *Name, LScriptArguments &Args); }; #if defined(MAC) && !defined(__GTK_H__) struct LPrintDcParams { #if LGI_COCOA #else PMRect Page; CGContextRef Ctx; PMResolution Dpi; #endif }; #endif #if defined(__GTK_H__) typedef Gtk::GdkWindow OsDrawable; #endif /// \brief An implemenation of LSurface to draw onto the screen. /// /// This is the class given to LView::OnPaint() most of the time. Which most of /// the time doesn't matter unless your doing something unusual. class LgiClass LScreenDC : public LSurface { class LScreenPrivate *d; public: LScreenDC(); virtual ~LScreenDC(); const char *GetClass() override { return "LScreenDC"; } // OS Sepcific #if WINNATIVE LScreenDC(LViewI *view); LScreenDC(HWND hwnd); LScreenDC(HDC hdc, HWND hwnd, bool Release = false); LScreenDC(HBITMAP hBmp, int Sx, int Sy); bool CreateFromHandle(HDC hdc); void SetSize(int x, int y); #else /// Construct a wrapper to draw on a window LScreenDC(LView *view, void *Param = 0); #if defined(LGI_SDL) #elif defined(__GTK_H__) /// Constructs a server size pixmap LScreenDC(int x, int y, int bits); /// Constructs a wrapper around a drawable LScreenDC(OsDrawable *Drawable); /// Constructs a DC for drawing on a cairo context LScreenDC(Gtk::cairo_t *cr, int x, int y); // Gtk::cairo_surface_t *GetSurface(bool Render); LPoint GetSize(); #elif defined(MAC) LScreenDC(LWindow *wnd, void *Param = 0); LScreenDC(LPrintDcParams *Params); // Used by LPrintDC LRect GetPos(); void PushState(); void PopState(); #endif OsPainter Handle() override; LView *GetView(); int GetFlags() override; LRect *GetClient(); #endif // Properties bool GetClient(LRect *c) override; void SetClient(LRect *c) override; int X() override; int Y() override; LPalette *Palette() override; void Palette(LPalette *pPal, bool bOwnIt = true) override; uint LineStyle() override; uint LineStyle(uint Bits, uint32_t Reset = 0x80000000) override; int GetBits() override; LScreenDC *IsScreen() override { return this; } bool SupportsAlphaCompositing() override; LPoint GetDpi() override; #ifndef LGI_SDL uchar *operator[](int y) override { return NULL; } void GetOrigin(int &x, int &y) override; void SetOrigin(int x, int y) override; LRect ClipRgn() override; LRect ClipRgn(LRect *Rgn) override; COLOUR Colour() override; COLOUR Colour(COLOUR c, int Bits = 0) override; LColour Colour(LColour c) override; LString Dump() override; int Op() override; int Op(int Op, NativeInt Param = -1) override; // Primitives void Set(int x, int y) override; COLOUR Get(int x, int y) override; void HLine(int x1, int x2, int y) override; void VLine(int x, int y1, int y2) override; void Line(int x1, int y1, int x2, int y2) override; void Circle(double cx, double cy, double radius) override; void FilledCircle(double cx, double cy, double radius) override; void Arc(double cx, double cy, double radius, double start, double end) override; void FilledArc(double cx, double cy, double radius, double start, double end) override; void Ellipse(double cx, double cy, double x, double y) override; void FilledEllipse(double cx, double cy, double x, double y) override; void Box(int x1, int y1, int x2, int y2) override; void Box(LRect *a) override; void Rectangle(int x1, int y1, int x2, int y2) override; void Rectangle(LRect *a = NULL) override; void Blt(int x, int y, LSurface *Src, LRect *a = NULL) override; void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL) override; void Polygon(int Points, LPoint *Data) override; void Bezier(int Threshold, LPoint *Pt) override; void FloodFill(int x, int y, int Mode, COLOUR Border = 0, LRect *Bounds = NULL) override; #endif }; /// \brief Blitting region helper class, can calculate the right source and dest rectangles /// for a blt operation including propagating clipping back to the source rect. class LBlitRegions { // Raw image bounds LRect SrcBounds; LRect DstBounds; // Unclipped blit regions LRect SrcBlt; LRect DstBlt; public: /// Clipped blit region in destination co-ords LRect SrcClip; /// Clipped blit region in source co-ords LRect DstClip; /// Calculate the rectangles. LBlitRegions ( /// Destination surface LSurface *Dst, /// Destination blt x offset int x1, /// Destination blt y offset int y1, /// Source surface LSurface *Src, /// [Optional] Crop the source surface first, else whole surface is blt LRect *SrcRc = 0 ) { // Calc full image bounds if (Src) SrcBounds.Set(0, 0, Src->X()-1, Src->Y()-1); else SrcBounds.ZOff(-1, -1); if (Dst) { DstBounds = Dst->Bounds(); int x = 0, y = 0; Dst->GetOrigin(x, y); DstBounds.Offset(x, y); } else DstBounds.ZOff(-1, -1); // Calc full sized blt regions if (SrcRc) { SrcBlt = *SrcRc; SrcBlt.Bound(&SrcBounds); } else SrcBlt = SrcBounds; DstBlt = SrcBlt; DstBlt.Offset(x1-DstBlt.x1, y1-DstBlt.y1); // Dest clipped to dest bounds DstClip = DstBlt; DstClip.Bound(&DstBounds); // Now map the dest clipping back to the source SrcClip = SrcBlt; SrcClip.x1 += DstClip.x1 - DstBlt.x1; SrcClip.y1 += DstClip.y1 - DstBlt.y1; SrcClip.x2 -= DstBlt.x2 - DstClip.x2; SrcClip.y2 -= DstBlt.y2 - DstClip.y2; } /// Returns non-zero if both clipped rectangles are valid. bool Valid() { return DstClip.Valid() && SrcClip.Valid(); } void Dump() { printf("SrcBounds: %s\n", SrcBounds.GetStr()); printf("DstBounds: %s\n", DstBounds.GetStr()); printf("SrcBlt: %s\n", SrcBlt.GetStr()); printf("DstBlt: %s\n", DstBlt.GetStr()); printf("SrcClip: %s\n", SrcClip.GetStr()); printf("DstClip: %s\n", DstClip.GetStr()); } }; #if defined(MAC) && !defined(__GTK_H__) class CGImg { class CGImgPriv *d; void Create(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r); public: CGImg(int x, int y, int Bits, ssize_t Line, uchar *data, uchar *palette, LRect *r, int Debug = 0); CGImg(LSurface *pDC); ~CGImg(); operator CGImageRef(); void Release(); }; #endif #ifdef __GTK_H__ #include "lgi/common/CairoSurface.h" #endif /// \brief An implemenation of LSurface to draw into a memory bitmap. /// /// This class uses a block of memory to represent an image. You have direct /// pixel access as well as higher level functions to manipulate the bits. class LgiClass LMemDC : public LSurface { protected: class LMemDCPrivate *d; #if defined WINNATIVE PBITMAPINFO GetInfo(); #endif // This is called between capturing the screen and overlaying the // cursor in LMemDC::Blt(x, y, ScreenDC, Src). It can be used to // overlay effects between the screen and cursor layers. virtual void OnCaptureScreen() {} public: /// Creates a memory bitmap LMemDC ( /// The width int x = 0, /// The height int y = 0, /// The colour space to use. CsNone will default to the /// current screen colour space. LColourSpace cs = CsNone, /// Optional creation flags int Flags = SurfaceCreateNone ); LMemDC(LSurface *pDC); virtual ~LMemDC(); const char *GetClass() { return "LMemDC"; } #if WINNATIVE HDC StartDC(); void EndDC(); void Update(int Flags); void UpsideDown(bool upsidedown); #else LRect ClipRgn() { return Clip; } #if defined(__GTK_H__) LPoint GetSize(); /// This returns the surface owned by the LMemDC Gtk::cairo_surface_t *GetSurface(); /// This returns a sub-image, caller is responsible to free via /// calling cairo_surface_destroy LCairoSurfaceT GetSubImage(LRect &r); LColourSpace GetCreateCs(); Gtk::GdkPixbuf *CreatePixBuf(); #elif defined MAC OsBitmap GetBitmap(); #if LGI_COCOA && defined(__OBJC__) LMemDC(NSImage *img); NSImage *NsImage(LRect *rc = NULL); #endif #if !defined(LGI_SDL) CGColorSpaceRef GetColourSpaceRef(); CGImg *GetImg(LRect *Sub = 0, int Debug = 0); #endif #elif defined(LGI_SDL) || defined(HAIKU) OsBitmap GetBitmap(); #endif OsPainter Handle(); #endif // Set new clipping region LRect ClipRgn(LRect *Rgn); void SetClient(LRect *c); /// Locks the bits for access. LMemDC's start in the locked state. bool Lock(); /// Unlocks the bits to optimize for display. While the bitmap is unlocked you /// can't access the data for read or write. On linux this converts the XImage /// to pixmap. On other systems it doesn't do much. As a general rule if you /// don't need access to a bitmap after creating / loading it then unlock it. bool Unlock(); #if !WINNATIVE && !LGI_CARBON && !LGI_COCOA void GetOrigin(int &x, int &y); #endif void SetOrigin(int x, int y); void Empty(); bool SupportsAlphaCompositing(); bool SwapRedAndBlue(); bool Create(int x, int y, LColourSpace Cs, int Flags = SurfaceCreateNone); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s = NULL); void HorzLine(int x1, int x2, int y, COLOUR a, COLOUR b); void VertLine(int x, int y1, int y2, COLOUR a, COLOUR b); }; /// \brief An implemenation of LSurface to print to a printer. /// /// This class redirects standard graphics calls to print a page. /// /// \sa GPrinter class LgiClass LPrintDC #if defined(WIN32) || defined(MAC) : public LScreenDC #else : public LSurface #endif { class LPrintDCPrivate *d; public: LPrintDC(void *Handle, const char *PrintJobName, const char *PrinterName = NULL); ~LPrintDC(); const char *GetClass() override { return "LPrintDC"; } bool IsPrint() override { return true; } const char *GetOutputFileName(); int X() override; int Y() override; int GetBits() override; /// Returns the DPI of the printer or 0,0 on error LPoint GetDpi() override; #if defined __GTK_H__ Gtk::GtkPrintContext *GetPrintContext(); int Op() { return GDC_SET; } int Op(int Op, NativeInt Param = -1) { return GDC_SET; } LRect ClipRgn(LRect *Rgn); LRect ClipRgn(); COLOUR Colour(); COLOUR Colour(COLOUR c, int Bits = 0); LColour Colour(LColour c); void Set(int x, int y); void HLine(int x1, int x2, int y); void VLine(int x, int y1, int y2); void Line(int x1, int y1, int x2, int y2); void Circle(double cx, double cy, double radius); void FilledCircle(double cx, double cy, double radius); void Arc(double cx, double cy, double radius, double start, double end); void FilledArc(double cx, double cy, double radius, double start, double end); void Ellipse(double cx, double cy, double x, double y); void FilledEllipse(double cx, double cy, double x, double y); void Box(int x1, int y1, int x2, int y2); void Box(LRect *a = NULL); void Rectangle(int x1, int y1, int x2, int y2); void Rectangle(LRect *a = NULL); void Blt(int x, int y, LSurface *Src, LRect *a = NULL); void StretchBlt(LRect *d, LSurface *Src, LRect *s); void Polygon(int Points, LPoint *Data); void Bezier(int Threshold, LPoint *Pt); #endif }; ////////////////////////////////////////////////////////////////////////////// class LgiClass LGlobalColour { class LGlobalColourPrivate *d; public: LGlobalColour(); ~LGlobalColour(); // Add all the colours first COLOUR AddColour(COLOUR c24); bool AddBitmap(LSurface *pDC); bool AddBitmap(LImageList *il); // Then call this bool MakeGlobalPalette(); // Which will give you a palette that // includes everything LPalette *GetPalette(); // Convert a bitmap to the global palette COLOUR GetColour(COLOUR c24); bool RemapBitmap(LSurface *pDC); }; /// This class is useful for double buffering in an OnPaint handler... class LDoubleBuffer { LSurface **In; LSurface *Screen; LMemDC Mem; LRect Rgn; bool Valid; public: LDoubleBuffer(LSurface *&pDC, LRect *Sub = NULL) : In(&pDC) { Rgn = Sub ? *Sub : pDC->Bounds(); Screen = pDC; Valid = pDC && Mem.Create(Rgn.X(), Rgn.Y(), pDC->GetColourSpace()); if (Valid) { *In = &Mem; if (Sub) pDC->SetOrigin(Sub->x1, Sub->y1); } } ~LDoubleBuffer() { if (Valid) { #if WINDOWS if (Mem.Handle()) Mem.EndDC(); #endif Mem.SetOrigin(0, 0); Screen->Blt(Rgn.x1, Rgn.y1, &Mem); } // Restore state *In = Screen; } LMemDC *GetMem() { return &Mem; } }; #ifdef WIN32 typedef int (__stdcall *MsImg32_AlphaBlend)(HDC,int,int,int,int,HDC,int,int,int,int,BLENDFUNCTION); #endif /// Main singleton graphics device class. Holds all global data for graphics rendering. class LgiClass GdcDevice : public LCapabilityClient { friend class LScreenDC; friend class LMemDC; friend class LImageList; static GdcDevice *pInstance; class GdcDevicePrivate *d; #ifdef WIN32 MsImg32_AlphaBlend AlphaBlend; #endif public: GdcDevice(); ~GdcDevice(); static GdcDevice *GetInst() { return pInstance; } /// Returns the colour space of the screen LColourSpace GetColourSpace(); /// Returns the current screen bit depth int GetBits(); /// Returns the current screen width int X(); /// Returns the current screen height int Y(); /// Returns the size of the screen as a rectangle. LRect Bounds() { return LRect(0, 0, X()-1, Y()-1); } LGlobalColour *GetGlobalColour(); /// Set a global graphics option int GetOption(int Opt); /// Get a global graphics option int SetOption(int Opt, int Value); /// 256 lut for squares ulong *GetCharSquares(); /// Divide by 255 lut, 64k entries long. uchar *GetDiv255(); // Palette/Colour void SetGamma(double Gamma); double GetGamma(); // Palette void SetSystemPalette(int Start, int Size, LPalette *Pal); LPalette *GetSystemPalette(); void SetColourPaletteType(int Type); // Type = PALTYPE_xxx define COLOUR GetColour(COLOUR Rgb24, LSurface *pDC = NULL); // File I/O /// \brief Loads a image from a file /// /// This function uses the compiled in codecs, some of which require external /// shared libraries / DLL's to function. Other just need the right source to /// be compiled in. /// /// Lgi comes with the following image codecs: ///
    ///
  • Windows or OS/2 Bitmap: GdcBmp (LFilter.cpp) ///
  • PCX: GdcPcx (Pcx.cpp) ///
  • GIF: GdcGif (Gif.cpp and Lzw.cpp) ///
  • JPEG: GdcJpeg (Jpeg.cpp + libjpeg library) ///
  • PNG: GdcPng (Png.cpp + libpng library) ///
/// LSurface *Load ( /// The full path of the file const char *FileName, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// The stream version of the file loader... LSurface *Load ( /// The full path of the file LStream *In, /// [Optional] File name hint for selecting a filter const char *Name = NULL, /// [Optional] Enable OS based loaders bool UseOSLoader = true ); /// Save an image to a stream. bool Save ( /// The file to write to LStream *Out, /// The pixels to store LSurface *In, /// Dummy file name to determine the file type, eg: "img.jpg" const char *FileType ); /// Save an image to a file. bool Save ( /// The file to write to const char *Name, /// The pixels to store LSurface *pDC ); #if LGI_SDL SDL_Surface *Handle(); #endif }; /// \brief Defines a bitmap inline in C++ code. /// /// The easiest way I know of create the raw data for an LInlineBmp /// is to use i.Mage to /// load a file or create a image and then use the Edit->Copy As Code /// menu. Then paste into your C++ and put a uint32 array declaration /// around it. Then point the Data member to the uint32 array. Just be /// sure to get the dimensions right. /// /// I use this for embeding resource images directly into the code so /// that a) they load instantly and b) they can't get lost as a separate file. class LgiClass LInlineBmp { public: /// The width of the image. int X; /// The height of the image. int Y; /// The bitdepth of the image (8, 15, 16, 24, 32). int Bits; /// Pointer to the raw data. uint32_t *Data; /// Creates a memory DC of the image. LSurface *Create(uint32_t TransparentPx = 0xffffffff); }; // file filter support #include "lgi/common/Filter.h" // globals #define GdcD GdcDevice::GetInst() /// Converts a context to a different bit depth LgiFunc LSurface *ConvertDC ( /// The source image LSurface *pDC, /// The destination bit depth int Bits ); /// Converts a colour to a different bit depth LgiFunc COLOUR CBit(int DstBits, COLOUR c, int SrcBits = 24, LPalette *Pal = 0); #ifdef __cplusplus /// blends 2 colours by the amount specified LgiClass LColour GdcMixColour(LColour a, LColour b, float HowMuchA = 0.5); #endif /// Colour reduction option to define what palette to go to enum LColourReducePalette { CR_PAL_NONE = -1, CR_PAL_CUBE = 0, CR_PAL_OPT, CR_PAL_FILE }; /// Colour reduction option to define how to deal with reduction error enum LColourReduceMatch { CR_MATCH_NONE = -1, CR_MATCH_NEAR = 0, CR_MATCH_HALFTONE, CR_MATCH_ERROR }; /// Colour reduction options class LReduceOptions { public: /// Type of palette LColourReducePalette PalType; /// Reduction error handling LColourReduceMatch MatchType; /// 1-256 int Colours; /// Specific palette to reduce to LPalette *Palette; LReduceOptions() { Palette = 0; Colours = 256; PalType = CR_PAL_NONE; MatchType = CR_MATCH_NONE; } }; /// Reduces a images colour depth LgiFunc bool LReduceBitDepth(LSurface *pDC, int Bits, LPalette *Pal = 0, LReduceOptions *Reduce = 0); struct LColourStop { LColour Colour; float Pos; void Set(float p, LColour c) { Pos = p; Colour = c; } }; /// Draws a horizontal or vertical gradient LgiFunc void LFillGradient(LSurface *pDC, LRect &r, bool Vert, LArray &Stops); #ifdef WIN32 /// Draws a windows HICON onto a surface at Dx, Dy LgiFunc void LDrawIcon(LSurface *pDC, int Dx, int Dy, HICON ico); #endif /// Row copy operator for full RGB (8 bit components) LgiFunc bool LRopRgb ( // Pointer to destination pixel buffer uint8_t *Dst, // Destination colour space (must be 8bit components) LColourSpace DstCs, // Pointer to source pixel buffer (if this overlaps 'Dst', set 'Overlap' to true) uint8_t *Src, // Source colour space (must be 8bit components) LColourSpace SrcCs, // Number of pixels to convert int Px, // Whether to composite using alpha or copy blt bool Composite ); /// Universal bit blt method LgiFunc bool LRopUniversal(LBmpMem *Dst, LBmpMem *Src, bool Composite); /// Gets the screens DPI LgiClass LPoint LScreenDpi(); /// Find the bounds of an image. /// \return true if there is some non-transparent image in 'rc' LgiFunc bool LFindBounds ( /// [in] The image LSurface *pDC, /// [in/out] Starts off as the initial bounds to search. /// Returns the non-background area. LRect *rc ); #if defined(LGI_SDL) LgiFunc LColourSpace PixelFormat2ColourSpace(SDL_PixelFormat *pf); #endif #endif diff --git a/include/lgi/common/LgiInterfaces.h b/include/lgi/common/LgiInterfaces.h --- a/include/lgi/common/LgiInterfaces.h +++ b/include/lgi/common/LgiInterfaces.h @@ -1,591 +1,602 @@ // \file /// \author Matthew Allen #ifndef _LGI_INTERFACES_H_ #define _LGI_INTERFACES_H_ // Includes #include "lgi/common/Mem.h" #include "lgi/common/Array.h" #include "lgi/common/Colour.h" #include "lgi/common/Cancel.h" #include "lgi/common/StringClass.h" #include "lgi/common/LgiUiBase.h" #include "lgi/common/Notifications.h" // Fwd defs class LXmlTag; class LMouseHook; class LFont; class LRect; class LPoint; class LRegion; class LSurface; class LMouse; class LKey; class LWindow; class LVariant; class LCss; class LViewI; class LView; +class LScriptArguments; #ifdef Yield #undef Yield #endif // Classes + class LDomI { public: virtual ~LDomI() {} virtual bool GetValue(const char *Var, LVariant &Value) { return false; } virtual bool SetValue(const char *Var, LVariant &Value) { return false; } - virtual bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { return false; } + virtual bool CallMethod(const char *MethodName, LScriptArguments &Args) { return false; } +}; + +class LVirtualMachineI +{ +public: + virtual ~LVirtualMachineI() {} + + virtual void SetDebuggerEnabled(bool b) {} + virtual void OnException(const char *File, int Line, ssize_t Address, const char *Msg) {} }; /// Stream interface class /// /// Defines the API /// for all the streaming data classes. Allows applications to plug /// different types of date streams into functions that take a LStream. /// Typically this means being able to swap files with sockets or data /// buffers etc. /// class LgiClass LStreamI : virtual public LDomI { public: /// Open a connection /// \returns > zero on success virtual int Open ( /// A string connection parameter const char *Str = 0, /// An integer connection parameter int Int = 0 ) { return false; } /// Returns true is the stream is still open virtual bool IsOpen() { return false; } /// Closes the connection /// \returns > zero on success virtual int Close() { return 0; } /// \brief Gets the size of the stream /// \return The size or -1 on error (e.g. the information is not available) virtual int64 GetSize() { return -1; } /// \brief Sets the size of the stream /// \return The new size or -1 on error (e.g. the size is not set-able) virtual int64 SetSize(int64 Size) { return -1; } /// \brief Gets the current position of the stream /// \return Current position or -1 on error (e.g. the position is not known) virtual int64 GetPos() { return -1; } /// \brief Sets the current position of the stream /// \return The new current position or -1 on error (e.g. the position can't be set) virtual int64 SetPos(int64 Pos) { return -1; } /// \brief Read bytes out of the stream /// \return > 0 on succes, which indicates the number of bytes read virtual ssize_t Read(void *Buffer, ssize_t Size, int Flags = 0) = 0; /// \brief Write bytes to the stream /// \return > 0 on succes, which indicates the number of bytes written virtual ssize_t Write(const void *Buffer, ssize_t Size, int Flags = 0) = 0; /// \brief Creates a dynamically allocated copy of the same type of stream. /// This new stream is not connected to anything. /// \return The new stream or NULL on error. virtual LStreamI *Clone() { return 0; } virtual void ChangeThread() {} /// Utility: Read as LString. LString Read(ssize_t bufLen = -1) { LString s; if (bufLen >= 0) { s.Length(bufLen); } else { auto sz = GetSize(); if (sz > 0) s.Length(sz); else s.Length(256); } LAssert(s.Length() > 0); // Read the data auto rd = Read(s.Get(), s.Length()); if (rd < 0) s.Empty(); else if (rd < (ssize_t) s.Length()) s.Length(rd); // Make it's null terminated. if (s.Get()) s.Get()[s.Length()] = 0; return s; } /// Utility: Write a LString size_t Write(const LString s) { return Write(s.Get(), s.Length()); } }; /// Socket logging types.. enum LSocketLogTypes { /// Do no logging NET_LOG_NONE = 0, /// Log a hex dump of everything NET_LOG_HEX_DUMP = 1, /// Log just the bytes NET_LOG_ALL_BYTES = 2 }; /// Virtual base class for a socket. See the documentation for LSocket for a more /// through treatment of this object's API. class LSocketI : virtual public LStreamI { public: enum SocketMsgType { SocketMsgNone, SocketMsgInfo, SocketMsgSend, SocketMsgReceive, SocketMsgWarning, SocketMsgError, }; virtual ~LSocketI() {} /// Returns the actual socket (as defined by the OS) virtual OsSocket Handle(OsSocket Set = INVALID_SOCKET) { return INVALID_SOCKET; } // Cancel virtual LCancel *GetCancel() { return NULL; } virtual void SetCancel(LCancel *c) { } // Logging and utility virtual class LStreamI *GetLog() { return NULL; } // Host/Port meta data /// Returns the IP at this end of the socket virtual bool GetLocalIp ( /// Ptr to a buffer of at least 16 bytes char *IpAddr ) { return false; } /// Return the port at this end of the connection virtual int GetLocalPort() { return 0; } /// Gets the remote IP virtual bool GetRemoteIp(char *IpAddr) { return false; } /// Return the port at this end of the connection virtual int GetRemotePort() { return 0; } // Timeout /// Gets the current timeout for operations in ms virtual int GetTimeout() { return -1; } /// Sets the current timeout for operations in ms virtual void SetTimeout(int ms) {} /// Sets the continue token // virtual void SetContinue(bool *Token) {} // State /// True if there is data available to read. virtual bool IsReadable(int TimeoutMs = 0) { return false; } /// True if the socket can be written to. virtual bool IsWritable(int TimeoutMs = 0) { return false; } /// True if the socket can be accept. virtual bool CanAccept(int TimeoutMs = 0) { return false; } /// Returns whether the socket is set to blocking or not virtual bool IsBlocking() { return true; } /// Set whether the socket should block or not virtual void IsBlocking(bool block) {} /// Get the send delay setting virtual bool IsDelayed() { return true; } /// Set the send delay setting virtual void IsDelayed(bool Delay) {} // UDP /// Get UPD mode virtual bool GetUdp() { return false; } /// Set UPD mode virtual void SetUdp(bool b) {} /// Read UPD packet virtual int ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip = 0, uint16_t *Port = 0) { return 0; } /// Write UPD packet virtual int WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { return 0; } // Server /// Listens on a given port for an incoming connection. virtual bool Listen(int Port = 0) { return false; } /// Accepts an incoming connection and connects the socket you pass in to the remote host. virtual bool Accept(LSocketI *c) { return false; } // Event call backs /// Called when the connection is dropped virtual void OnDisconnect() {} /// Called when data is read virtual void OnRead(char *Data, ssize_t Len) {} /// Called when data is written virtual void OnWrite(const char *Data, ssize_t Len) {} /// Called when an error occurs virtual void OnError(int ErrorCode, const char *ErrorDescription) {} /// Called when some events happens virtual void OnInformation(const char *Str) {} /// Process an error virtual int Error(void *Param) { return 0; } virtual const char *GetErrorString() { return NULL; } LString LocalIp() { char Ip[32]; return GetLocalIp(Ip) ? Ip : NULL; } }; class LAppI { public: /// The idle function should return false to wait for more /// messages, otherwise it will be called continuously when no /// messages are available. typedef bool (*OnIdleProc)(void *Param); /// Destroys the object virtual ~LAppI() {} virtual bool IsOk() = 0; /// Returns this processes ID virtual OsProcessId GetProcessId() = 0; /// Returns the thread currently running the active message loop virtual OsThreadId GetGuiThreadId() = 0; virtual bool InThread() = 0; /// Resets the arguments virtual void SetAppArgs(OsAppArguments &AppArgs) = 0; /// Returns the arguemnts virtual OsAppArguments *GetAppArgs() = 0; /// Returns the n'th argument as a heap string. Free with DeleteArray(...). virtual const char *GetArgumentAt(int n) = 0; /// Enters the message loop. virtual bool Run ( /// [Optional] Idle callback OnIdleProc IdleCallback = 0, /// [Optional] User param for IdleCallback void *IdleParam = 0 ) = 0; /// Processed queued events and then return virtual bool Yield() = 0; /// Event called to process the command line virtual void OnCommandLine() = 0; /// Event called to process files dropped on the application virtual void OnReceiveFiles(LArray &Files) = 0; /// Event called to process URLs given to the application virtual void OnUrl(const char *Url) = 0; /// Exits the event loop with the code specified virtual void Exit ( /// The application exit code. int Code = 0 ) = 0; /// \brief Parses the command line for a switch /// \return true if the option exists. virtual bool GetOption ( /// The option to look for. const char *Option, /// String to receive the value (if any) of the option LString &Value ) = 0; /// \brief Parses the command line for a switch /// \return true if the option exists. virtual bool GetOption ( /// The option to look for. const char *Option, /// The buffer to receive the value of the command line parameter or NULL if you don't care. char *Dst = 0, /// The buffer size in bytes int DstSize = 0 ) = 0; /// Gets the application conf stored in lgi.conf virtual LString GetConfig(const char *Tag) = 0; /// Sets a single tag in the config. (Not written to disk) virtual void SetConfig(const char *Var, const char *Val) = 0; /// Gets the control with the keyboard focus virtual LViewI *GetFocus() = 0; /// Gets the MIME type of a file virtual LString GetFileMimeType ( /// The file to identify const char *File ) = 0; /// Get a system metric virtual int32 GetMetric ( /// One of #LGI_MET_DECOR_X, #LGI_MET_DECOR_Y LSystemMetric Metric ) = 0; /// Get the mouse hook instance virtual LMouseHook *GetMouseHook() = 0; /// Returns the number of cpu cores or -1 if unknown. virtual int GetCpuCount() { return -1; } /// Gets the font cache virtual class LFontCache *GetFontCache() = 0; }; class LEventSinkI { public: virtual ~LEventSinkI() {} virtual bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0, int64_t TimeoutMs = -1) = 0; }; class LEventTargetI { public: virtual ~LEventTargetI() {} virtual LMessage::Result OnEvent(LMessage *Msg) = 0; }; class LEventsI : public LEventTargetI { public: virtual ~LEventsI() {} // Events virtual void OnMouseClick(LMouse &m) = 0; virtual void OnMouseEnter(LMouse &m) = 0; virtual void OnMouseExit(LMouse &m) = 0; virtual void OnMouseMove(LMouse &m) = 0; virtual bool OnMouseWheel(double Lines) = 0; virtual bool OnKey(LKey &k) = 0; virtual void OnAttach() = 0; virtual void OnCreate() = 0; virtual void OnDestroy() = 0; virtual void OnFocus(bool f) = 0; virtual void OnPulse() = 0; virtual void OnPosChange() = 0; virtual bool OnRequestClose(bool OsShuttingDown) = 0; virtual int OnHitTest(int x, int y) = 0; virtual void OnChildrenChanged(LViewI *Wnd, bool Attaching) = 0; virtual void OnPaint(LSurface *pDC) = 0; virtual int OnCommand(int Cmd, int Event, OsView Wnd) = 0; virtual int OnNotify(LViewI *Ctrl, LNotification Data) = 0; }; class LViewLayoutInfo { public: constexpr static int FILL = -1; struct Range { // 0 if unknown, -1 for "all available" int32 Min, Max; Range() { Min = Max = 0; } }; Range Width; Range Height; }; class LgiClass LViewI : public LEventsI, public LEventSinkI, public virtual LDomI { friend class LView; public: // Handles #if LGI_VIEW_HANDLE virtual OsView Handle() const = 0; #endif virtual int AddDispatch() = 0; virtual OsWindow WindowHandle() { printf("LViewI::WindowHandle()\n"); return NULL; } virtual LView *GetGView() { return NULL; } // Heirarchy virtual bool Attach(LViewI *p) = 0; virtual bool AttachChildren() = 0; virtual bool Detach() = 0; virtual bool IsAttached() = 0; virtual LWindow *GetWindow() = 0; virtual LViewI *GetParent() = 0; virtual void SetParent(LViewI *p) = 0; virtual void Quit(bool DontDelete = false) = 0; virtual bool AddView(LViewI *v, int Where = -1) = 0; virtual bool DelView(LViewI *v) = 0; virtual bool HasView(LViewI *v) = 0; virtual LArray IterateViews() = 0; // Threading virtual bool Lock(const char *file, int line, int TimeOut = -1) = 0; virtual void Unlock() = 0; virtual bool InThread() = 0; // Properties virtual bool Enabled() = 0; virtual void Enabled(bool e) = 0; virtual bool Visible() = 0; virtual void Visible(bool v) = 0; virtual bool Focus() = 0; virtual void Focus(bool f) = 0; virtual class LDragDropSource *DropSource(LDragDropSource *Set = NULL) = 0; virtual class LDragDropTarget *DropTarget(LDragDropTarget *Set = NULL) = 0; virtual bool DropTarget(bool t) = 0; virtual bool Sunken() = 0; virtual void Sunken(bool i) = 0; virtual bool Flat() = 0; virtual void Flat(bool i) = 0; virtual bool Raised() = 0; virtual void Raised(bool i) = 0; virtual bool GetTabStop() = 0; virtual void SetTabStop(bool b) = 0; // Style virtual LCss *GetCss(bool Create = false) = 0; virtual void SetCss(LCss *css) = 0; virtual bool SetColour(LColour &c, bool Fore) = 0; virtual LString CssStyles(const char *Set = NULL) { return LString(); } virtual LString::Array *CssClasses() { return NULL; } virtual LFont *GetFont() = 0; virtual void SetFont(LFont *Fnt, bool OwnIt = false) = 0; // Name and value virtual bool Name(const char *n) = 0; virtual bool NameW(const char16 *n) = 0; virtual const char *Name() = 0; virtual const char16 *NameW() = 0; virtual int64 Value() = 0; virtual void Value(int64 i) = 0; virtual const char *GetClass() { return "LViewI"; } // mainly for debugging // Size and position virtual LRect &GetPos() = 0; virtual LRect &GetClient(bool InClientSpace = true) = 0; virtual bool SetPos(LRect &p, bool Repaint = false) = 0; virtual int X() = 0; virtual int Y() = 0; virtual LPoint GetMinimumSize() = 0; virtual void SetMinimumSize(LPoint Size) = 0; // Id virtual int GetId() = 0; virtual void SetId(int i) = 0; // Events and notification virtual void SendNotify(LNotification note) = 0; virtual LViewI *GetNotify() = 0; virtual void SetNotify(LViewI *n) = 0; // Mouse virtual LCursor GetCursor(int x, int y) = 0; virtual bool Capture(bool c) = 0; virtual bool IsCapturing() = 0; virtual bool GetMouse(LMouse &m, bool ScreenCoords = false) = 0; // Helper #if LGI_VIEW_HANDLE virtual LViewI *FindControl(OsView hnd) = 0; #endif virtual LViewI *FindControl(int Id) = 0; virtual int64 GetCtrlValue(int Id) = 0; virtual void SetCtrlValue(int Id, int64 i) = 0; virtual const char *GetCtrlName(int Id) = 0; virtual void SetCtrlName(int Id, const char *s) = 0; virtual bool GetCtrlEnabled(int Id) = 0; virtual void SetCtrlEnabled(int Id, bool Enabled) = 0; virtual bool GetCtrlVisible(int Id) = 0; virtual void SetCtrlVisible(int Id, bool Visible) = 0; virtual bool Pour(LRegion &r) = 0; template bool GetViewById(int Id, T *&Ptr) { LViewI *Ctrl = FindControl(Id); Ptr = dynamic_cast(Ctrl); #ifdef _DEBUG if (Ctrl != NULL && Ptr == NULL) LgiTrace("%s:%i - Can't cast '%s' to target type.\n", _FL, Ctrl->GetClass()); #endif return Ptr != NULL; } // Points virtual bool PointToScreen(LPoint &p) = 0; virtual bool PointToView(LPoint &p) = 0; virtual bool WindowVirtualOffset(LPoint *Offset) = 0; virtual LViewI *WindowFromPoint(int x, int y, int DebugDepth = 0) = 0; virtual LPoint &GetWindowBorderSize() = 0; virtual bool IsOver(LMouse &m) = 0; // Misc virtual bool Invalidate(LRect *r = 0, bool Repaint = false, bool NonClient = false) = 0; virtual bool Invalidate(LRegion *r, bool Repaint = false, bool NonClient = false) = 0; virtual void SetPulse(int Ms = -1) = 0; virtual bool OnLayout(LViewLayoutInfo &Inf) = 0; protected: virtual bool OnViewMouse(LView *v, LMouse &m) = 0; virtual bool OnViewKey(LView *v, LKey &k) = 0; }; class LMemoryPoolI { public: virtual ~LMemoryPoolI() {} virtual void *Alloc(size_t Size) = 0; virtual void Free(void *Ptr) = 0; virtual void Empty() = 0; }; #endif diff --git a/include/lgi/common/Menu.h b/include/lgi/common/Menu.h --- a/include/lgi/common/Menu.h +++ b/include/lgi/common/Menu.h @@ -1,572 +1,572 @@ /** \file \author Matthew Allen */ #ifndef _LMENU_H_ #define _LMENU_H_ // Os specific declarations #if defined __GTK_H__ typedef Gtk::GtkMenuShell *OsSubMenu; typedef Gtk::GtkMenuItem *OsMenuItem; #elif defined WINNATIVE typedef HMENU OsSubMenu; typedef MENUITEMINFO OsMenuItem; #ifndef COLOR_MENUHILIGHT #define COLOR_MENUHILIGHT 29 #endif #ifndef COLOR_MENUBAR #define COLOR_MENUBAR 30 #endif #elif defined(MAC) && !defined(LGI_SDL) #if LGI_COCOA #ifdef __OBJC__ #include #endif ObjCWrapper(NSMenu, OsSubMenu) ObjCWrapper(NSMenuItem, OsMenuItem) #else typedef MenuRef OsSubMenu; typedef MenuItemIndex OsMenuItem; #endif #elif defined(HAIKU) typedef class BMenu *OsSubMenu; typedef class BMenuItem *OsMenuItem; #else #include "LMenuImpl.h" typedef class MenuClickImpl *OsSubMenu; typedef class MenuItemImpl *OsMenuItem; #endif #include "lgi/common/XmlTree.h" #include "lgi/common/Res.h" #include "lgi/common/ImageList.h" /////////////////////////////////////////////////////////////////////////////////////////////// // Menu wrappers class LgiClass LMenuLoader { friend class LMenuItem; friend class LMenu; friend class LSubMenu; friend class MenuImpl; friend class SubMenuImplPrivate; protected: #ifdef WIN32 OsSubMenu Info; #endif List Items; public: LMenuLoader() { #ifdef WIN32 Info = 0; #endif } bool Load( class LMenuRes *MenuRes, LXmlTag *Tag, ResFileFormat Format, class TagHash *TagList); virtual LMenuItem *AppendItem(const char *Str, int Id, bool Enabled = true, int Where = -1, const char *Shortcut = 0) = 0; virtual LSubMenu *AppendSub(const char *Str, int Where = -1) = 0; virtual LMenuItem *AppendSeparator(int Where = -1) = 0; }; /// Sub menu. class LMenu; class LgiClass LSubMenu : public LBase, public LMenuLoader, public LImageListOwner, public LDom { friend class LMenuItem; friend class LMenu; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; friend class LMouseHookPrivate; // This is not called in the GUI thread static void SysMouseClick(LMouse &m); #if !WINNATIVE && !defined(__GTK_H__) OsSubMenu Info; #endif #if LGI_COCOA LAutoPtr FloatResult; virtual void OnActivate(LMenuItem *item); #endif #if defined(__GTK_H__) GlibWrapper Info; friend void GtkDeactivate(Gtk::GtkMenuShell *widget, LSubMenu *Sub); friend Gtk::gboolean LSubMenuClick(LMouse *m); friend void SubMenuDestroy(LSubMenu *Item); int *_ContextMenuId = NULL; bool InLoop = false; uint64 ActiveTs = 0; bool IsContext(LMenuItem *Item); void OnActivate(bool a); #elif defined(WINNATIVE) HWND TrackHandle = NULL; #else bool OnKey(LKey &k); #endif protected: /// The parent menu item or NULL if the root menu LMenuItem *Parent = NULL; /// The top level window this sub menu belongs to or NULL LMenu *Menu = NULL; /// The window that the menu belongs to or NULL. LViewI *Window = NULL; void OnAttach(bool Attach); void ClearHandle(); public: constexpr static int ItemId_Submenu = -1; constexpr static int ItemId_Separator = -2; LSubMenu(OsSubMenu Hnd); /// Constructor LSubMenu ( /// Name of the menu const char *name = "", /// True if it's popup bool Popup = true ); virtual ~LSubMenu(); /// Returns the OS handle OsSubMenu Handle() { return Info; } /// Detachs the OS handle and returns it OsSubMenu Release() { OsSubMenu Hnd = Info; Info = NULL; return Hnd; } /// Add a new item LMenuItem *AppendItem ( /// The text of the item. /// /// If you put a tab control in the text, anything after the tab is considered /// to be the keyboard shortcut for the menu item. const char *Str, /// Command ID to post to the OnCommand() handler int Id, /// True if the item should be enabled bool Enabled = true, /// The index into the list to insert at, or -1 to insert at the end int Where = -1, /// Shortcut if not embedded in the "Str" parameter. /// /// The shortcut can be a combination of modifiers and keys added together with '+'. /// /// Available modifier names are: /// - Ctrl/Control /// - Alt/Option /// - Shift /// - System/Cmd /// - CtrlCmd (Ctrl on Windows/Linux, Cmd on OSX) /// - AltCmd (Alt on Windows/Linux, Cmd on OSX) /// /// Available key names are: /// - Del/Delete /// - Home/End /// - PageUp/Page Up/Page-Up /// - PageDown/Page Down/Page-Down /// - Backspace /// - Up/Right/Down/Left /// - Esc /// - Space /// - Numbers or letters /// - Some punctuation: ,./\\[]`;\' /// /// Examples: /// - Ctrl+S /// - Shift+Modifier+F /// - Alt+Left const char *Shortcut = NULL ); /// Add a submenu LSubMenu *AppendSub ( /// The text of the item const char *Str, /// The index to insert the item, or -1 to insert on the end int Where = -1 ); /// Add a separator LMenuItem *AppendSeparator(int Where = -1); /// Delete all items void Empty(); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// The index of the item to remove int i ); /// Detachs an item from the sub menu but doesn't delete it bool RemoveItem ( /// Pointer of the item to remove LMenuItem *Item ); /// Returns numbers of items in menu size_t Length(); /// Return a pointer to an item LMenuItem *ItemAt ( /// The index of the item to return int i ); /// Returns a pointer to an item LMenuItem *FindItem ( /// The ID of the item to return int Id ); /// Returns a pointer to an sub menu LSubMenu *FindSubMenu ( /// The ID of the sub menu to return int Id ); enum { BtnLeft = 1, BtnMiddle = 2, BtnRight = 3, }; /// Floats the submenu anywhere on the screen int Float ( /// The parent view LView *Parent, /// The x coord of the top-left corner int x, /// The y coord of the top-left corner int y, /// True if the menu is tracking the left button, else it tracks the right button int Button = BtnRight ); int Float(LView *Parent, LMouse m) { int Btn = 0; if (m.Left()) Btn = BtnLeft; else if (m.Middle()) Btn = BtnMiddle; else if (m.Right()) Btn = BtnRight; m.ToScreen(); return Float(Parent, m.x, m.y, Btn); } /// Returns the parent menu item LMenuItem *GetParent() { return Parent; } /// Returns the menu that this belongs to LMenu *GetMenu() { return Menu; } // Dom impl bool GetVariant(const char *Name, LVariant &Value, const char *Arr = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Arr = NULL); - bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args); + bool CallMethod(const char *MethodName, LScriptArguments &Args); }; /// An item an a menu class LgiClass LMenuItem : public LBase, public LDom { friend class LSubMenu; friend class LMenu; friend class LView; friend class SubMenuImpl; friend class MenuItemImpl; friend class MenuImpl; friend class SubMenuImplPrivate; friend class LMenuPrivate; private: #ifdef WIN32 bool Insert(int Pos); bool Update(); #endif #if defined(__GTK_H__) || defined(LGI_SDL) LString ShortCut; #endif protected: LMenu *Menu = NULL; LSubMenu *Parent = NULL; LSubMenu *Child = NULL; int Position = -1; int _Icon = -1; #ifdef __GTK_H__ GlibWrapper Info; #else OsMenuItem Info; #endif class LMenuItemPrivate *d = NULL; int _Id = 0; int _Flags = 0; #if defined(__GTK_H__) bool InSetCheck = false; LAutoPtr IconImg; bool Replace(Gtk::GtkWidget *newWid); public: void Handle(Gtk::GtkMenuItem *mi); void OnGtkEvent(LString Event); void PaintIcon(Gtk::cairo_t *cr); protected: #else virtual void _Measure(LPoint &Size); virtual void _Paint(LSurface *pDC, int Flags); virtual void _PaintText(LSurface *pDC, int x, int y, int Width); #endif void OnAttach(bool Attach); void ClearHandle(); public: LMenuItem(); LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int Id, int Pos, const char *Shortcut = NULL); virtual ~LMenuItem(); LMenuItem &operator =(const LMenuItem &m) { LAssert(!"This shouldn't be used anywhere") ; return *this; } /// Creates a sub menu off the item LSubMenu *Create(); /// Removes the item from it's place in the menu but doesn't delete it bool Remove(); /// Returns the parent sub menu LSubMenu *GetParent(); /// Returns the parent sub menu LMenu *GetMenu() { return Menu; } /// Scans the text of the item for a keyboard shortcut bool ScanForAccel(); /// Returns the OS handle for the menuitem OsMenuItem Handle() { return Info; } /// Set the id void Id(int i); /// Turn the item into a separator void Separator(bool s); /// Put a check mark on the item void Checked(bool c); /// \brief Set the text of the item /// \sa LSubMenu::AppendItem() bool Name(const char *n) override; /// Enable or disable the item void Enabled(bool e); void Visible(bool v); void Focus(bool f); /// Attach a sub menu to the item void Sub(LSubMenu *s); /// Set the icon for the item. The icon is stored in the LMenu's image list. void Icon(int i); /// Get the id int Id(); /// Get the text of the item const char *Name() override; /// Return whether this item is a separator bool Separator(); /// Return whether this item has a check mark bool Checked(); /// Return whether this item is enabled bool Enabled(); bool Visible(); bool Focus(); /// Return whether this item's submenu LSubMenu *Sub(); /// Return the icon of this this int Icon(); LImageList *GetImageList(); #if LGI_COCOA void OnActivate(LMenuItem *item); #endif }; /// Encapsulates a keyboard shortcut class LgiClass LAccelerator : public LUiEvent { int Vkey = 0; int Chr = 0; int Id = 0; public: LAccelerator(int flags, int vkey, int chr, int id); int GetId() { return Id; } /// See if the accelerator matchs a keyboard event bool Match(LKey &k); }; /** \brief Top level window menu This class contains LMenuItem's and LSubMenu's. A basic menu can be constructed inside a LWindow like this: \code Menu = new LMenu; if (Menu) { Menu->Attach(this); LSubMenu *File = Menu->AppendSub("&File"); if (File) { File->AppendItem("&Open\tCtrl+O", IDM_OPEN, true); File->AppendItem("&Save All\tCtrl+S", IDM_SAVE_ALL, true); File->AppendItem("Save &As", IDM_SAVEAS, true); File->AppendSeparator(); File->AppendItem("&Options", IDM_OPTIONS, true); File->AppendSeparator(); File->AppendItem("E&xit", IDM_EXIT, true); } LSubMenu *Help = Menu->AppendSub("&Help"); if (Help) { Help->AppendItem("&Help", IDM_HELP, true); Help->AppendItem("&About", IDM_ABOUT, true); } } \endcode Or you can load a menu from a resource like this: \code Menu = new LMenu; if (Menu) { Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } \endcode */ class LgiClass LMenu : public LSubMenu { friend class LSubMenu; friend class LMenuItem; friend class LWindow; friend class LMenuPrivate; class LMenuPrivate *d; #if defined WIN32 void OnChange(); #else void OnChange() {} #endif protected: /// List of keyboard shortcuts in the menu items attached List Accel; #ifdef __GTK_H__ Gtk::GtkAccelGroup *AccelGrp; #endif #if LGI_COCOA void OnActivate(LMenuItem *item); #endif #ifdef HAIKU bool PostMessage(BMessage *m); #endif public: /// Constructor LMenu(const char *AppName = NULL); /// Destructor virtual ~LMenu(); /// Returns the font used by the menu items static LFont *GetFont(); /// Returns the top level window that this menu is attached to LViewI *WindowHandle() { return Window; } /// Attach the menu to a window bool Attach(LViewI *p); /// Detact the menu from the window bool Detach(); /// Load the menu from a resource file bool Load ( /// The parent view for any error message boxes LView *p, /// The resource to load. Will probably change to an int sometime. const char *Res, /// Optional list of comma or space separated tags const char *Tags = 0 ); /// \brief See if any of the accelerators match the key event /// \return true if none of the accelerators match bool OnKey ( /// The view that will eventually receive the key event LView *v, /// The keyboard event details LKey &k ); /// This creates copies of the preference and about menu items in the /// application menu. On other platforms it's a NOP. bool SetPrefAndAboutItems(int PrefId, int AboutId); #if defined(WIN32) static int _OnEvent(LMessage *Msg); #elif defined(MAC) int GetIdForCommand(uint32_t Cmd); #endif }; #endif diff --git a/include/lgi/common/Scripting.h b/include/lgi/common/Scripting.h --- a/include/lgi/common/Scripting.h +++ b/include/lgi/common/Scripting.h @@ -1,496 +1,456 @@ /// \file #ifndef _LGI_SCRIPTING_H_ #define _LGI_SCRIPTING_H_ #include "lgi/common/Variant.h" #include "lgi/common/List.h" class LScriptContext; class LScriptEnginePrivate; class LVmCallback; class LVirtualMachine; -class LScriptArguments : public LArray -{ - friend class LScriptEngine; - friend class LVirtualMachine; - - LVirtualMachine *Vm; - LStream *Console; - LVariant _Return; - LVariant *PtrRet; - -public: - static LStream NullConsole; - - LScriptArguments(LVirtualMachine *vm, LVariant *ret = NULL, LStream *console = NULL) - { - Vm = vm; - if (ret) - PtrRet = ret; - else - PtrRet = &_Return; - - if (console) - Console = console; - else - Console = &NullConsole; - } - - LVirtualMachine *GetVm() { return Vm; } - void SetVm(LVirtualMachine *vm) { Vm = vm; } - LVariant *GetReturn() { return PtrRet; } - LStream *GetConsole() { return Console; } - bool Throw(const char *File, int Line, const char *Msg, ...); - - // Accessor shortcuts - const char *StringAt(size_t i) { return IdxCheck(i) ? (*this)[i]->Str() : NULL; } - int32_t Int32At(size_t i, int32_t Default = 0) { return IdxCheck(i) ? (*this)[i]->CastInt32() : Default; } - int64_t Int64At(size_t i, int64_t Default = 0) { return IdxCheck(i) ? (*this)[i]->CastInt64() : Default; } - double DoubleAt(size_t i, double Default = 0) { return IdxCheck(i) ? (*this)[i]->CastDouble() : Default; } -}; - typedef bool (LScriptContext::*ScriptCmd)(LScriptArguments &Args); #define SCOPE_REGISTER 0 #define SCOPE_LOCAL 1 #define SCOPE_GLOBAL 2 #define SCOPE_OBJECT 3 #define SCOPE_RETURN 4 #define SCOPE_MAX 5 /// Execution status enum LExecutionStatus { ScriptNotStarted, ScriptError, ScriptWarning, ScriptSuccess, }; /// Various type of methods enum LFuncType { /// No method type. NullFunc, /// This method is provided by the hosting application. HostFunc, /// This method is defined in the script itself. ScriptFunc, /// This method is defined in an external library. ExternFunc }; struct LFunc { LFuncType Type; LString Method; bool InUse; LFunc(const char *m = 0, LFuncType t = NullFunc) { Type = t; Method = m; InUse = false; // LStackTrace("%p alloc\n", this); } virtual ~LFunc() { // LAssert(!InUse); } virtual LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) = 0; }; struct LHostFunc : public LFunc { LScriptContext *Context; LString Args; ScriptCmd Func; LHostFunc(const LHostFunc &f) { Context = f.Context; Args = f.Args; Method = f.Method; Func = f.Func; } LHostFunc(const char *method, const char *args, ScriptCmd proc) : LFunc(method, HostFunc) { Args = args; Func = proc; } LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; struct LExternFunc : public LFunc { struct ExternType { int Ptr; bool Unsigned; bool Out; int ArrayLen; LVariantType Base; }; LAutoString Lib; ExternType ReturnType; LArray ArgType; LExecutionStatus Call(LScriptContext *Ctx, LScriptArguments &Args) override; }; class LFunctionInfo : public LRefCount { static int _Infos; int32 StartAddr = INVALID_ADDR; LString Name; // The reason why this is a pointer is because during the function compilation the frame // size is actually unknown. If the function calls itself then it won't know what // frame size to insert in the assembly (this is NULL). // In which case it has to insert a post compilation fix-up for the frame size. LAutoPtr FrameSize; // The number and names of the parameters to the function. LArray Params; public: static constexpr int32 INVALID_ADDR = -1; LFunctionInfo(const char *name) { if (name) Name = name; } ~LFunctionInfo() { } char *GetName() { return Name; } LArray &GetParams() { return Params; } bool ValidFrameSize() { return FrameSize.Get() != NULL; } uint16 GetFrameSize() { if (!FrameSize) { LAssert(!"Invalid frame size"); return 0; } return *FrameSize; } template bool SetFrameSize(T size) { if (size >= 0xffff) { LAssert(!"Invalid frame size."); return false; } return FrameSize.Reset(new uint16((uint16)size)); } bool ValidStartAddr() { return StartAddr != INVALID_ADDR; } int32 GetStartAddr() { LAssert(ValidStartAddr()); return StartAddr; } bool SetStartAddr(int32_t addr) { if (addr < 0) { LAssert(!"Invalid start address"); return false; } StartAddr = addr; return true; } LFunctionInfo &operator =(LFunctionInfo &f) { StartAddr = f.StartAddr; FrameSize = f.FrameSize; Name = f.Name; for (unsigned i=0; i { friend class LVirtualMachinePriv; LHashTbl,int> Lut; public: int Scope; int NullIndex; LCustomType *Obj; LVariables(int scope) { Scope = scope; NullIndex = -1; Obj = NULL; } LVariables(LCustomType *obj) { Scope = SCOPE_OBJECT; NullIndex = -1; Obj = obj; } int Var(const char *n, bool create = false) { if (Obj) { return Obj->IndexOf(n); } else { int p = Lut.Find(n); if (p) { return p - 1; } if (create) { int Len = (int)Length(); Lut.Add(n, Len + 1); Length(Len + 1); return Len; } } return -1; } }; /// A block of compile byte code class LCompiledCode { friend class LCompilerPriv; friend class LVirtualMachine; friend class LVirtualMachinePriv; friend class LCompiler; friend class LVmDebuggerWnd; /// The global variables LVariables Globals; /// The byte code of all the instructions LArray ByteCode; /// All the methods defined in the byte code and their arguments. LArray< LAutoRefPtr > Methods; /// All the externs defined in the code. LArray Externs; /// All the user types defined LHashTbl, class LCustomType*> Types; /// The original script details LString FileName; LString Source; /// The system context (all the system functions) LScriptContext *SysContext; /// Any user context (application functions) LScriptContext *UserContext; /// Debug info to map instruction address back to source line numbers LHashTbl, int> Debug; public: LCompiledCode(); LCompiledCode(LCompiledCode ©); ~LCompiledCode(); /// Size of the byte code size_t Length() { return ByteCode.Length(); } /// Assignment operator LCompiledCode &operator =(const LCompiledCode &c); /// Gets a method defined in the code LFunctionInfo *GetMethod(const char *Name, bool Create = false); /// Sets a global variable LVariant *Set(const char *Name, LVariant &v); /// Gets the definition of a struct or custom type LCustomType *GetType(char16 *Name) { return Types.Find(Name); } /// Gets the file name this code was compiled from const char *GetFileName() { return FileName; } /// Gets the source const char *GetSource() { return Source; } /// Gets the source line number associated with an address int ObjectToSourceAddress(size_t ObjAddr); /// Turns an object address into a FileName:LineNumber string. const char *AddrToSourceRef(size_t ObjAddr); /// Sets the file name this code was compiled from void SetSource(const char *file, const char *src) { if (file != FileName.Get()) FileName = file; Source = src; } }; /// New compiler/byte code/VM scripting engine class LScriptEngine { class LScriptEnginePrivate *d; public: LScriptEngine(LViewI *parent, LScriptContext *UserContext, LVmCallback *Callback); ~LScriptEngine(); LStream *GetConsole(); bool SetConsole(LStream *t); LCompiledCode *GetCurrentCode(); bool Compile( LAutoPtr &Obj, LScriptContext *UserContext, const char *Script, const char *FileName = NULL, LDom *Args = NULL); LExecutionStatus Run(LCompiledCode *Obj, LVariant *Ret = NULL, const char *TempPath = NULL); LExecutionStatus RunTemporary(LCompiledCode *Obj, char *Script, LVariant *Ret = NULL); bool EvaluateExpression(LVariant *Result, LDom *VariableSource, const char *Expression); bool CallMethod(LCompiledCode *Obj, const char *Method, LScriptArguments &Args); LScriptContext *GetSystemContext(); }; class LVirtualMachine; class LVmDebugger : public LDom { public: /// Set the VM ownership flag. virtual void OwnVm(bool Own) = 0; /// Makes the debugger the owner of the compiled code virtual void OwnCompiledCode(LAutoPtr Cc) = 0; /// Gets the code owned by the debugger virtual LCompiledCode *GetCode() = 0; /// Set the source and asm virtual void SetSource(const char *Mixed) = 0; /// Show UI and wait for user response virtual void Run() = 0; // Events /// Called to update the debugger UI to the new current execution position virtual void OnAddress(size_t Addr) = 0; /// Called when an error occurs executing the script virtual void OnError(const char *Msg) = 0; /// Called when execution starts or ends virtual void OnRun(bool Running) = 0; }; class LVmCallback : public LDom { public: /// Call a callback by name. virtual bool CallCallback(LVirtualMachine &Vm, LString CallbackName, LScriptArguments &Args) = 0; /// Start a debugger instance to handle the execution in 'Vm' virtual LVmDebugger *AttachVm(LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) = 0; /// Compile a new script virtual bool CompileScript(LAutoPtr &Output, const char *FileName, const char *Source) = 0; }; /// Debugger for vm script class LVmDebuggerWnd : public LWindow, public LVmDebugger { struct LScriptVmDebuggerPriv *d; void UpdateVariables(LList *Lst, LVariant *Arr, ssize_t Len, char Prefix); public: LVmDebuggerWnd(LView *Parent, LVmCallback *Callback, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly); ~LVmDebuggerWnd(); void OwnVm(bool Own); void OnAddress(size_t Addr); void OnError(const char *Msg); void OnRun(bool Running); void SetSource(const char *Mixed); int OnNotify(LViewI *Ctrl, LNotification n); int OnCommand(int Cmd, int Event, OsView Wnd); bool OnRequestClose(bool OsShuttingDown); LMessage::Param OnEvent(LMessage *Msg); void LoadFile(const char *File); LStream *GetLog(); void OwnCompiledCode(LAutoPtr Cc); LCompiledCode *GetCode(); void Run(); }; #endif diff --git a/include/lgi/common/Variant.h b/include/lgi/common/Variant.h --- a/include/lgi/common/Variant.h +++ b/include/lgi/common/Variant.h @@ -1,437 +1,467 @@ /** \file \author Matthew Allen \brief Variant class.\n Copyright (C), Matthew Allen */ #ifndef __LVariant_H__ #define __LVariant_H__ -#include "lgi/common/Dom.h" #undef Bool #include "lgi/common/DateTime.h" #include "lgi/common/Containers.h" #include "lgi/common/HashTable.h" #include "lgi/common/LgiString.h" class LCompiledCode; #if !defined(_MSC_VER) && !defined(LINUX) && (defined(LGI_64BIT) || defined(MAC)) && !defined(HAIKU) #define LVARIANT_SIZET 1 #define LVARIANT_SSIZET 1 #endif /// The different types the varient can be. /// \sa LVariant::TypeToString to convert to string. enum LVariantType { // Main types /// Null type GV_NULL, /// 32-bit integer GV_INT32, /// 64-bit integer GV_INT64, /// true or false boolean. GV_BOOL, /// C++ double GV_DOUBLE, /// Null terminated string value GV_STRING, /// Block of binary data GV_BINARY, /// List of LVariant GV_LIST, /// Pointer to LDom object GV_DOM, /// DOM reference, ie. a variable in a DOM object GV_DOMREF, /// Untyped pointer GV_VOID_PTR, /// LDateTime class. GV_DATETIME, /// Hash table class, containing pointers to LVariants GV_HASHTABLE, // Scripting language operator GV_OPERATOR, // Custom scripting lang type GV_CUSTOM, // Wide string GV_WSTRING, // LSurface ptr GV_LSURFACE, /// Pointer to LView GV_GVIEW, /// Pointer to LMouse GV_LMOUSE, /// Pointer to LKey GV_LKEY, /// Pointer to LStream GV_STREAM, /// The maximum value for the variant type. /// (This is used by the scripting engine to refer to a LVariant itself) GV_MAX, }; /// Language operators enum LOperator { OpNull, OpAssign, OpPlus, OpUnaryPlus, OpMinus, OpUnaryMinus, OpMul, OpDiv, OpMod, OpLessThan, OpLessThanEqual, OpGreaterThan, OpGreaterThanEqual, OpEquals, OpNotEquals, OpPlusEquals, OpMinusEquals, OpMulEquals, OpDivEquals, OpPostInc, OpPostDec, OpPreInc, OpPreDec, OpAnd, OpOr, OpNot, }; class LgiClass LCustomType : public LDom { protected: struct CustomField : public LDom { ssize_t Offset; ssize_t Bytes; ssize_t ArrayLen; LVariantType Type; LString Name; LCustomType *Nested; ssize_t Sizeof(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); }; public: struct Method : public LDom { LString Name; LArray Params; size_t Address; int FrameSize; Method() { Address = -1; FrameSize = -1; } }; protected: // Global vars int Pack; size_t Size; LString Name; // Fields LArray Flds; LHashTbl, int> FldMap; // Methods LArray Methods; LHashTbl, Method*> MethodMap; // Private methods ssize_t PadSize(); public: LCustomType(const char *name, int pack = 1); LCustomType(const char16 *name, int pack = 1); ~LCustomType(); size_t Sizeof(); const char *GetName() { return Name; } ssize_t Members() { return Flds.Length(); } int AddressOf(const char *Field); int IndexOf(const char *Field); bool DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen = 1); bool DefineField(const char *Name, LCustomType *Type, int ArrayLen = 1); Method *DefineMethod(const char *Name, LArray &Params, size_t Address); Method *GetMethod(const char *Name); // Field access. You can't use the LDom interface to get/set member variables because // there is no provision for the 'This' pointer. bool Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex = 0); bool Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex = 0); // Dom access. However the DOM can be used to access information about the type itself. // Which doesn't need a 'This' pointer. bool GetVariant(const char *Name, LVariant &Value, const char *Array = NULL); bool SetVariant(const char *Name, LVariant &Value, const char *Array = NULL); - bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args); + bool CallMethod(const char *MethodName, LScriptArguments &Args); }; /// A class that can be different types class LgiClass LVariant { public: typedef LHashTbl,LVariant*> LHash; /// The type of the variant LVariantType Type; /// The value of the variant union { /// Valid when Type == #GV_INT32 int Int; /// Valid when Type == #GV_BOOL bool Bool; /// Valid when Type == #GV_INT64 int64 Int64; /// Valid when Type == #GV_DOUBLE double Dbl; /// Valid when Type == #GV_STRING char *String; /// Valid when Type == #GV_WSTRING char16 *WString; /// Valid when Type == #GV_DOM LDom *Dom; /// Valid when Type is #GV_VOID_PTR, #GV_GVIEW, #GV_LMOUSE or #GV_LKEY void *Ptr; /// Valid when Type == #GV_BINARY struct _Binary { ssize_t Length; void *Data; } Binary; /// Valid when Type == #GV_LIST List *Lst; /// Valid when Type == #GV_HASHTABLE LHash *Hash; /// Valid when Type == #GV_DATETIME LDateTime *Date; /// Valid when Type == #GV_CUSTOM struct _Custom { LCustomType *Dom; uint8_t *Data; bool operator == (_Custom &c) { return Dom == c.Dom && Data == c.Data; } } Custom; /// Valid when Type == #GV_DOMREF struct _DomRef { /// The pointer to the dom object LDom *Dom; /// The name of the variable to set/get in the dom object char *Name; } DomRef; /// Valid when Type == #GV_OPERATOR LOperator Op; /// Valid when Type == #GV_LSURFACE struct { class LSurface *Ptr; bool Own; LSurface *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Surface; /// Valid when Type == #GV_STREAM struct { class LStreamI *Ptr; bool Own; LStreamI *Release() { auto p = Ptr; Ptr = NULL; Own = false; return p; } } Stream; /// Valid when Type == #GV_GVIEW class LView *View; /// Valid when Type == #GV_LMOUSE class LMouse *Mouse; /// Valid when Type == #GV_LKEY class LKey *Key; } Value; /// Constructor to null LVariant(); /// Constructor for integers LVariant(int32_t i); LVariant(uint32_t i); LVariant(int64_t i); LVariant(uint64_t i); #if LVARIANT_SIZET LVariant(size_t i); #endif #if LVARIANT_SSIZET LVariant(ssize_t i); #endif /// Constructor for double LVariant(double i); /// Constructor for string LVariant(const char *s); /// Constructor for wide string LVariant(const char16 *s); /// Constructor for ptr LVariant(void *p); /// Constructor for DOM ptr LVariant(LDom *p); /// Constructor for DOM variable reference LVariant(LDom *p, char *name); /// Constructor for date LVariant(const LDateTime *d); /// Constructor for variant LVariant(LVariant const &v); /// Constructor for operator LVariant(LOperator Op); /// Destructor ~LVariant(); /// Assign bool value LVariant &operator =(bool i); /// Assign an integer value LVariant &operator =(int32_t i); LVariant &operator =(uint32_t i); LVariant &operator =(int64_t i); LVariant &operator =(uint64_t i); #if LVARIANT_SIZET LVariant &operator =(size_t i); #endif #if LVARIANT_SSIZET LVariant &operator =(ssize_t i); #endif /// Assign double value LVariant &operator =(double i); /// Assign string value (makes a copy) LVariant &operator =(const char *s); /// Assign a wide string value (makes a copy) LVariant &operator =(const char16 *s); /// Assign another variant value LVariant &operator =(LVariant const &i); /// Assign value to a void ptr LVariant &operator =(void *p); /// Assign value to DOM ptr LVariant &operator =(LDom *p); /// Assign value to be a date/time LVariant &operator =(const LDateTime *d); LVariant &operator =(class LView *p); LVariant &operator =(class LMouse *p); LVariant &operator =(class LKey *k); LVariant &operator =(class LStream *s); bool operator ==(LVariant &v); bool operator !=(LVariant &v) { return !(*this == v); } /// Sets the value to a DOM variable reference bool SetDomRef(LDom *obj, char *name); /// Sets the value to a copy of block of binary data bool SetBinary(ssize_t Len, void *Data, bool Own = false); /// Sets the value to a copy of the list List *SetList(List *Lst = NULL); /// Sets the value to a hashtable bool SetHashTable(LHash *Table = NULL, bool Copy = true); /// Set the value to a surface bool SetSurface(class LSurface *Ptr, bool Own); /// Set the value to a stream bool SetStream(class LStreamI *Ptr, bool Own); /// Returns the string if valid (will convert a GV_WSTRING to utf) char *Str(); /// Returns the value as an LString LString LStr(); /// Returns a wide string if valid (will convert a GV_STRING to wide) char16 *WStr(); /// Returns the string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char *ReleaseStr(); /// Returns the wide string, releasing ownership of the memory to caller and /// changing this LVariant to GV_NULL. char16 *ReleaseWStr(); /// Sets the variant to a heap string and takes ownership of it bool OwnStr(char *s); /// Sets the variant to a wide heap string and takes ownership of it bool OwnStr(char16 *s); /// Sets the variant to NULL void Empty(); /// Returns the byte length of the data int64 Length(); /// True if currently a int bool IsInt(); /// True if currently a bool bool IsBool(); /// True if currently a double bool IsDouble(); /// True if currently a string bool IsString(); /// True if currently a binary block bool IsBinary(); /// True if currently null bool IsNull(); /// Changes the variant's type, maintaining the value where possible. If /// no conversion is available then nothing happens. LVariant &Cast(LVariantType NewType); /// Casts the value to int, from whatever source type. The /// LVariant type does not change after calling this. int32 CastInt32() const; /// Casts the value to a 64 bit int, from whatever source type. The /// LVariant type does not change after calling this. int64 CastInt64() const; /// Casts the value to double, from whatever source type. The /// LVariant type does not change after calling this. double CastDouble() const; /// Cast to a string from whatever source type, the LVariant will /// take the type GV_STRING after calling this. This is because /// returning a static string is not thread safe. char *CastString(); /// Casts to a DOM ptr LDom *CastDom() const; /// Casts to a boolean. You probably DON'T want to use this function. The /// behaviour for strings -> bool is such that if the string is value it /// always evaluates to true, and false if it's not a valid string. Commonly /// what you want is to evaluate whether the string is zero or non-zero in /// which cast you should use "CastInt32() != 0" instead. bool CastBool() const; /// Returns the pointer if available. void *CastVoidPtr() const; /// Returns a LView LView *CastView() const { return Type == GV_GVIEW ? Value.View : NULL; } /// List insert bool Add(LVariant *v, int Where = -1); /// Converts the varient type to a string static const char *TypeToString(LVariantType t); /// Converts an operator to a string static const char *OperatorToString(LOperator op); /// Converts the value to a string description include type. LString ToString(); }; +// General collection of arguments and a return value +class LgiClass LScriptArguments : public LArray +{ + friend class LScriptEngine; + friend class LVirtualMachine; + friend class LVirtualMachinePriv; + + LVirtualMachineI *Vm = NULL; + class LStream *Console = NULL; + LVariant *LocalReturn = NULL; // Owned by this instance + LVariant *Return = NULL; + +public: + static LStream NullConsole; + + LScriptArguments(LVirtualMachineI *vm, LVariant *ret = NULL, LStream *console = NULL); + ~LScriptArguments(); + + LVirtualMachineI *GetVm() { return Vm; } + void SetVm(LVirtualMachineI *vm) { Vm = vm; } + LVariant *GetReturn() { return Return; } // Must never be NULL. + LStream *GetConsole() { return Console; } + bool Throw(const char *File, int Line, const char *Msg, ...); + + // Accessor shortcuts + const char *StringAt(size_t i); + int32_t Int32At(size_t i, int32_t Default = 0); + int64_t Int64At(size_t i, int64_t Default = 0); + double DoubleAt(size_t i, double Default = 0); +}; + #endif diff --git a/src/common/Coding/Instructions.h b/src/common/Coding/Instructions.h --- a/src/common/Coding/Instructions.h +++ b/src/common/Coding/Instructions.h @@ -1,2037 +1,2041 @@ /* This file is included in both the LVirtualMachinePriv::Execute and LVirtualMachinePriv::Decompile That way the "parsing" of instructions is the same. During decompile the define VM_DECOMP is active. During execution the define VM_EXECUTE is active. */ #ifdef VM_EXECUTE #define Resolve() &Scope[c.r->Scope][c.r->Index]; c.r++ #define GResolveRef(nm) LVariant *nm = #else #define Resolve() c.r++ #define GResolveRef(nm) // LVarRef * #endif default: { #if VM_DECOMP if (Log) Log->Print("\t%p Unknown instruction %i (0x%x)\n", CurrentScriptAddress - 1, c.u8[-1], c.u8[-1]); #endif OnException(_FL, CurrentScriptAddress, "Unknown instruction"); SetScriptError; break; } case INop: { #if VM_DECOMP if (Log) Log->Print("%p Nop\n", CurrentScriptAddress - 1); #endif break; } case ICast: { #if VM_DECOMP if (Log) Log->Print("%p Cast %s", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); uint8_t Type = *c.u8++; #if VM_DECOMP if (Log) Log->Print(" to %s\n", LVariant::TypeToString((LVariantType)Type)); #endif #if VM_EXECUTE switch (Type) { case GV_INT32: { *Var = Var->CastInt32(); break; } case GV_STRING: { *Var = Var->CastString(); break; } case GV_DOM: { *Var = Var->CastDom(); break; } case GV_DOUBLE: { *Var = Var->CastDouble(); break; } case GV_INT64: { *Var = Var->CastInt32(); break; } case GV_BOOL: { *Var = Var->CastBool(); break; } default: { LString s; s.Printf("%s ICast warning: unknown type %i/%s\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type, LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAssign: { #if VM_DECOMP if (Log) Log->Print("%p Assign %s <- %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE CheckParam(Dst != Src); *Dst = *Src; #endif break; } case IJump: { #if VM_DECOMP if (Log) Log->Print("%p Jump by %i (to 0x%x)\n", CurrentScriptAddress - 1, c.i32[0], CurrentScriptAddress + 4 + c.i32[0]); #endif #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); c.u8 += Jmp; #endif break; } case IJumpZero: { #if VM_DECOMP if (Log) Log->Print("%p JumpZ(%s) by 0x%x\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.i32[1]); #endif GResolveRef(Exp) Resolve(); #ifdef VM_EXECUTE int32 Jmp = * #endif c.i32++; #ifdef VM_EXECUTE CheckParam(Jmp != 0); if (!Exp->CastInt32()) c.u8 += Jmp; #endif break; } // case IUnaryPlus: case IUnaryMinus: { #if VM_DECOMP if (Log) Log->Print("%p UnaryMinus %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(Var) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_DOUBLE: *Var = -Var->CastDouble(); break; case GV_INT64: *Var = -Var->CastInt64(); break; default: *Var = -Var->CastInt32(); break; } #endif break; } case IPlus: case IPlusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Plus %s += %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE if (Dst->Str()) { size_t dlen = strlen(Dst->Str()); char *ss; LVariant SrcTmp; switch (Src->Type) { case GV_NULL: ss = (char*)"(null)"; break; case GV_STRING: ss = Src->Str(); break; default: SrcTmp = *Src; ss = SrcTmp.CastString(); break; } if (ss) { size_t slen = strlen(ss); char *s = new char[slen + dlen + 1]; if (s) { memcpy(s, Dst->Value.String, dlen); memcpy(s + dlen, ss, slen); s[dlen + slen] = 0; DeleteArray(Dst->Value.String); Dst->Value.String = s; } } } else switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() + Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() + Src->CastInt64(); break; default: *Dst = Dst->CastInt32() + Src->CastInt32(); break; } #endif break; } case IMinus: case IMinusEquals: { #if VM_DECOMP if (Log) Log->Print("%p Minus %s -= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() - Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() - Src->CastInt64(); break; default: *Dst = Dst->CastInt32() - Src->CastInt32(); break; } #endif break; } case IMul: case IMulEquals: { #if VM_DECOMP if (Log) Log->Print("%p Mul %s *= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() * Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() * Src->CastInt64(); break; default: *Dst = Dst->CastInt32() * Src->CastInt32(); break; } #endif break; } case IDiv: case IDivEquals: { #if VM_DECOMP if (Log) Log->Print("%p Div %s /= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = Dst->CastDouble() / Src->CastDouble(); break; case GV_INT64: *Dst = Dst->CastInt64() / Src->CastInt64(); break; default: *Dst = Dst->CastInt32() / Src->CastInt32(); break; } #endif break; } case IMod: { #if VM_DECOMP if (Log) Log->Print("%p Mod %s %%= %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE switch (DecidePrecision(Dst->Type, Src->Type)) { case GV_DOUBLE: *Dst = fmod(Dst->CastDouble(), Src->CastDouble()); break; case GV_INT64: *Dst = Dst->CastInt64() % Src->CastInt64(); break; default: *Dst = Dst->CastInt32() % Src->CastInt32(); break; } #endif break; } case IPostInc: case IPreInc: { #if VM_DECOMP if (Log) Log->Print("%p PostInc %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl + 1; break; case GV_INT64: *v = v->Value.Int64 + 1; break; default: *v = v->CastInt32() + 1; break; } #endif break; } case IPostDec: case IPreDec: { #if VM_DECOMP if (Log) Log->Print("%p PostDec %sn", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(v) Resolve(); #ifdef VM_EXECUTE switch (v->Type) { case GV_DOUBLE: *v = v->Value.Dbl - 1; break; case GV_INT64: *v = v->Value.Int64 - 1; break; default: *v = v->CastInt32() - 1; break; } #endif break; } case IEquals: { #if VM_DECOMP if (Log) Log->Print("%p %s == %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) == 0; #endif break; } case INotEquals: { #if VM_DECOMP if (Log) Log->Print( "%p %s != %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) != 0; #endif break; } case ILessThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) < 0; #endif break; } case ILessThanEqual: { #if VM_DECOMP if (Log) Log->Print( "%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) <= 0; #endif break; } case IGreaterThan: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) > 0; #endif break; } case IGreaterThanEqual: { #if VM_DECOMP if (Log) Log->Print("%p %s < %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = CompareVariants(Dst, Src) >= 0; #endif break; } case ICallMethod: { LFunc *Meth = *c.fn++; if (!Meth) { Log->Print( "%s ICallMethod error: No method struct.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(Meth))); SetScriptError; break; } #ifdef VM_DECOMP if (Log) { Log->Print("%p Call: %s = %s(", CurrentScriptAddress - sizeof(Meth) - 1, c.r[0].GetStr(), Meth->Method.Get()); } #endif GResolveRef(Ret) Resolve(); uint16 Args = *c.u16++; #ifdef VM_EXECUTE LScriptArguments Arg(Vm, Ret); #endif for (int i=0; iPrint("%s%s", i?", ":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Arg[i] = Resolve(); CheckParam(Arg[i] != NULL); #else c.r++; #endif } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #if VM_EXECUTE LHostFunc *Hf = dynamic_cast(Meth); if (Hf) { if (!(Hf->Context->*(Hf->Func))(Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } else { // Fixme if (!Meth->Call(NULL, Arg)) { if (Log) Log->Print( "%s ICallMethod error: Method '%s' failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), Meth->Method.Get()); SetScriptError; } } + + Arg.Length(0); // It doesn't own the variants, so don't delete them. #endif break; } case ICallScript: { int32 FuncAddr = *c.i32++; if (FuncAddr < 0 || (uint32_t)FuncAddr >= Code->ByteCode.Length()) { Log->Print( "%s ICallScript error: Script function call invalid addr '%p'.\n", Code->AddrToSourceRef(CurrentScriptAddress - sizeof(FuncAddr)), FuncAddr); SetScriptError; break; } uint16 Frame = *c.u16++; #if VM_DECOMP if (Log) Log->Print("%p CallScript: %s = %p(frame=%i)(", CurrentScriptAddress - 5, c.r[0].GetStr(), FuncAddr, Frame); #endif #ifdef VM_EXECUTE // Set up stack for function call int CurFrameSize = Frames.Last().CurrentFrameSize; StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = Frame; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = *c.r++; if (Sf.ReturnValue.Scope == SCOPE_LOCAL) Sf.ReturnValue.Index -= CurFrameSize; uint16 Args = *c.u16++; // Increase the local stack size size_t LocalsBase = Locals.Length(); size_t LocalsPos = Scope[SCOPE_LOCAL] - Locals.AddressOf(); Locals.SetFixedLength(false); Locals.Length(LocalsBase + Frame); Locals.SetFixedLength(); Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsPos); // Put the arguments of the function call into the local array LArray Arg; #else GResolveRef(Ret) Resolve(); int Args = *c.u16++; #endif for (int i=0; iPrint("%s%s", i?",":"", c.r[0].GetStr()); #endif #if VM_EXECUTE Locals[LocalsBase+i] = *Resolve(); #else c.r++; #endif } #if VM_EXECUTE // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + FuncAddr; Scope[SCOPE_LOCAL] = Locals.AddressOf(LocalsBase); // This can evaluation to NULL when there is NO locals. #endif #if VM_DECOMP if (Log) Log->Print(")\n"); #endif break; } case IRet: { #if VM_DECOMP if (Log) Log->Print("%p Ret %s\n", CurrentScriptAddress - 1, c.r[0].GetStr()); #endif GResolveRef(ReturnValue) Resolve(); #ifdef VM_EXECUTE if (Frames.Length() > 0) { StackFrame Sf = Frames[Frames.Length()-1]; LVarRef &Ret = Sf.ReturnValue; LVariant *RetVar = &Scope[Ret.Scope][Ret.Index]; // LgiTrace("IRet to %i:%i\n", Ret.Scope, Ret.Index); if (Ret.Scope == SCOPE_LOCAL) LAssert(Locals.PtrCheck(RetVar)); *RetVar = *ReturnValue; CheckParam(RetVar->Type == ReturnValue->Type); Frames.Length(Frames.Length()-1); Locals.SetFixedLength(false); if (Locals.Length() >= Sf.CurrentFrameSize) { ssize_t Base = Locals.Length() - Sf.CurrentFrameSize; if (ArgsOutput) { if (Frames.Length() == 0) { for (unsigned i=0; iLength(); i++) { *(*ArgsOutput)[i] = Locals[Base+i]; } } } // LgiTrace("%s:%i Locals %i -> %i\n", _FL, Locals.Length(), Base); Locals.Length(Base); Scope[SCOPE_LOCAL] = &Locals[Sf.PrevFrameStart]; } else { // LgiTrace("%s:%i - Locals %i -> %i\n", _FL, Locals.Length(), 0); Locals.Length(0); Scope[SCOPE_LOCAL] = NULL; } Locals.SetFixedLength(); c.u8 = Base + Sf.ReturnIp; } else { ExitScriptExecution; } #endif break; } case IArrayGet: { #if VM_DECOMP if (Log) Log->Print( "%p ArrayGet %s = %s[%s]\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); LVariant *t = Var->Value.Lst->ItemAt(Idx->CastInt32()); if (t) { if (Var == Dst) { if (Var->Value.Lst->Delete(t)) { *Var = *t; DeleteObj(t); } else CheckParam(!"List delete failed."); } else *Dst = *t; } else Dst->Empty(); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *t = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); if (t) *Dst = *t; else Dst->Empty(); break; } case GV_CUSTOM: { LCustomType *T = Var->Value.Custom.Dom; size_t Sz = T->Sizeof(); int Index = Idx->CastInt32(); Dst->Type = GV_CUSTOM; Dst->Value.Custom.Dom = T; Dst->Value.Custom.Data = Var->Value.Custom.Data + (Sz * Index); break; } case GV_STRING: { auto c = Var->Str(); auto i = Idx->CastInt64(); if (!c || i < 0) break; LUtf8Ptr p(c); uint32_t ch; do { ch = p; if (i-- == 0) { *Dst = ch; break; } p++; } while (ch); break; } default: { LString s; s.Printf("%s IArrayGet warning: Can't array deref variant type %i\n", Code->AddrToSourceRef(CurrentScriptAddress), Var->Type); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IArraySet: { #if VM_DECOMP if (Log) Log->Print( "%p ArraySet %s[%s] = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #endif GResolveRef(Var) Resolve(); GResolveRef(Idx) Resolve(); GResolveRef(Val) Resolve(); #ifdef VM_EXECUTE switch (Var->Type) { case GV_LIST: { CheckParam(Var->Value.Lst); (*Var->Value.Lst).Insert(new LVariant(*Val), Idx->CastInt32()); break; } case GV_HASHTABLE: { CheckParam(Var->Value.Hash); LVariant *Old = (LVariant*)Var->Value.Hash->Find(Idx->CastString()); DeleteObj(Old); Var->Value.Hash->Add(Idx->CastString(), new LVariant(*Val)); break; } default: { LString s; s.Printf("%s IArraySet warning: Can't dereference type '%s'\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Var->Type)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IAnd: { #if VM_DECOMP if (Log) Log->Print("%p %s && %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) && (Src->CastInt32() != 0); #endif break; } case IOr: { #if VM_DECOMP if (Log) Log->Print("%p %s || %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Src) Resolve(); #ifdef VM_EXECUTE *Dst = (Dst->CastInt32() != 0) || (Src->CastInt32() != 0); #endif break; } case INot: { #if VM_DECOMP if (Log) Log->Print("%p %s = !%s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[0].GetStr()); #endif GResolveRef(Dst) Resolve(); #ifdef VM_EXECUTE *Dst = !Dst->CastBool(); #endif break; } case IDomGet: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomGet(%s, %s)\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); #ifdef VM_EXECUTE // Return "NULL" in Dst on error if (Dst != Dom) Dst->Empty(); switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = dom->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); char *sName = Name->Str(); CheckParam(sName); bool Ret = Dom->Value.Date->GetVariant(sName, *Dst, CastArrayIndex(Arr)); if (!Ret) { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (Name->Type == GV_INT32) Fld = Name->Value.Int; else Fld = Type->IndexOf(Name->Str()); int Index = Arr ? Arr->CastInt32() : 0; Type->Get(Fld, *Dst, Dom->Value.Custom.Data, Index); } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Lst->Length(); break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = (int)Dom->Value.Hash->Length(); break; } case GV_BINARY: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); if (p == ObjLength) (*Dst) = Dom->Value.Binary.Length; break; } case GV_INT32: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%i", Dom->Value.Int); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int32 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_INT64: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), LPrintfInt64, Dom->Value.Int64); *Dst = s; break; } case TypeDouble: { *Dst = (double)Dom->Value.Int64; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected int64 member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_DOUBLE: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case TypeString: { char s[32]; sprintf_s(s, sizeof(s), "%g", Dom->Value.Dbl); *Dst = s; break; } case TypeInt: { *Dst = (int64)Dom->Value.Dbl; break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected double member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_STRING: { char *sName = Name->Str(); CheckParam(sName); LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { (*Dst) = (int)strlen(Dom->Str()); break; } case TypeInt: { (*Dst) = Dom->CastInt32(); break; } case TypeDouble: { (*Dst) = Dom->CastDouble(); break; } default: { Dst->Empty(); LString s; s.Printf("%s IDomGet warning: Unexpected string member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } break; } case GV_NULL: { LString s; s.Printf("%s IDomGet warning: Can't deref NULL object.\n", Code->AddrToSourceRef(CurrentScriptAddress)); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } default: { LString s; s.Printf("%s IDomGet warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), _FL, CurrentScriptAddress); if (Log) Log->Write(s, s.Length()); if (LVirtualMachine::BreakOnWarning) OnException(NULL, -1, CurrentScriptAddress, s); Status = ScriptWarning; break; } } #endif break; } case IDomSet: { #if VM_DECOMP if (Log) Log->Print("%p %s->DomSet(%s, %s) = %s\n", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr(), c.r[3].GetStr()); #endif GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); GResolveRef(Arr) Resolve(); GResolveRef(Value) Resolve(); #ifdef VM_EXECUTE char *sName = Name->Str(); if (!sName) { if (Log) Log->Print("%s IDomSet error: No name string.\n", Code->AddrToSourceRef(CurrentScriptAddress)); SetScriptError; break; } switch (Dom->Type) { case GV_DOM: // case GV_GFILE: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom != NULL); bool Ret = dom->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date != NULL); bool Ret = Dom->Value.Date->SetVariant(sName, *Value, CastArrayIndex(Arr)); if (!Ret) { if (Log) Log->Print("%s IDomSet warning: Unexpected %s member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type), sName); Status = ScriptWarning; } break; } case GV_CUSTOM: { LCustomType *Type = Dom->Value.Custom.Dom; if (Type) { int Fld; if (IsDigit(*sName)) Fld = atoi(sName); else Fld = Type->IndexOf(sName); int Index = Arr ? Arr->CastInt32() : 0; if (!Type->Set(Fld, *Value, Dom->Value.Custom.Data, Index) && Log) { Log->Print("%s IDomSet warning: Couldn't set '%s' on custom type.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); } } break; } case GV_STRING: { LDomProperty p = LStringToDomProp(sName); switch (p) { case ObjLength: { char *s; int DLen = Value->CastInt32(); if (DLen && (s = new char[DLen+1])) { size_t SLen = Dom->Str() ? strlen(Dom->Str()) : 0; if (SLen) memcpy(s, Dom->Str(), SLen); memset(s+SLen, ' ', DLen-SLen); s[DLen] = 0; DeleteArray(Dom->Value.String); Dom->Value.String = s; } else Dom->Empty(); break; } case TypeInt: { *Dom = Value->CastInt32(); Dom->Str(); break; } case TypeDouble: { *Dom = Value->CastDouble(); Dom->Str(); break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected string member %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } default: { if (Log) Log->Print("%s IDomSet warning: Unexpected type %s.\n", Code->AddrToSourceRef(CurrentScriptAddress), LVariant::TypeToString(Dom->Type)); Status = ScriptWarning; break; } } #endif break; } case IDomCall: { #if VM_DECOMP if (Log) Log->Print("%p %s = %s->DomCall(%s, ", CurrentScriptAddress - 1, c.r[0].GetStr(), c.r[1].GetStr(), c.r[2].GetStr()); #else LVarRef DstRef = *c.r; #endif GResolveRef(Dst) Resolve(); GResolveRef(Dom) Resolve(); GResolveRef(Name) Resolve(); #ifdef VM_EXECUTE GResolveRef(Args) Resolve(); int ArgCount = Args->CastInt32(); char *sName = Name->Str(); CheckParam(sName) if (Dom->Type == GV_CUSTOM) { #define DEBUG_CUSTOM_METHOD_CALL 1 LCustomType *t = Dom->Value.Custom.Dom; CheckParam(t); LCustomType::Method *m = t->GetMethod(sName); CheckParam(m); CheckParam(m->Params.Length() == ArgCount); // Set up new stack frame... StackFrame &Sf = Frames.New(); Sf.CurrentFrameSize = m->FrameSize; Sf.PrevFrameStart = Locals.Length() ? Scope[1] - &Locals[0] : 0; Sf.ReturnValue = DstRef; // Increase the local stack size AddLocalSize(m->FrameSize + 1); #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("CustomType.Call(%s) Args=%i, Frame=%i, Addr=%i, LocalsBase=%i ", sName, ArgCount, m->FrameSize, m->Address, LocalsBase); #endif size_t i = LocalsBase; Locals[i++] = *Dom; // this pointer... #if DEBUG_CUSTOM_METHOD_CALL LString s = Locals[i-1].ToString(); LgiTrace("This=%s, ", s.Get()); #endif size_t end = i + ArgCount; while (i < end) { Locals[i++] = *Resolve(); #if DEBUG_CUSTOM_METHOD_CALL s = Locals[i-1].ToString(); LgiTrace("[%i]=%s, ", i-1, s.Get()); #endif } // Now adjust the local stack to point to the locals for the function Scope[1] = Locals.Length() ? &Locals[LocalsBase] : NULL; // Set IP to start of function Sf.ReturnIp = CurrentScriptAddress; c.u8 = Base + m->Address; #if DEBUG_CUSTOM_METHOD_CALL LgiTrace("\n"); #endif break; } - LArray Arg; + LScriptArguments Arg(Vm, Dst); Arg.Length(ArgCount); for (int i=0; iType); } else switch (Dom->Type) { case GV_DOM: case GV_STREAM: case GV_LSURFACE: { auto *dom = Dom->CastDom(); CheckParam(dom); - bool Ret = dom->CallMethod(sName, Dst, Arg); + bool Ret = dom->CallMethod(sName, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_DATETIME: { CheckParam(Dom->Value.Date); bool Ret = Dom->Value.Date->CallMethod(sName, Dst, Arg); if (!Ret) { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: %s(...) failed.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; } break; } case GV_LIST: { CheckParam(Dom->Value.Lst); switch (p) { case ObjLength: { *Dst = (int64)Dom->Value.Lst->Length(); break; } case ContainerAdd: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Lst->Insert(v, Index); } break; } case ContainerDelete: { for (unsigned i=0; iCastInt32(); LVariant *Elem = Dom->Value.Lst->ItemAt(n); if (Elem) { Dom->Value.Lst->Delete(Elem); DeleteObj(Elem); } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { int Index = Arg[0]->CastInt32(); *Dst = (bool) (Index >= 0 && Index < (int)Dom->Value.Lst->Length()); } else { *Dst = false; } break; } case ContainerSort: { LVariant *Param = Arg.Length() > 0 ? Arg[0] : NULL; Dom->Value.Lst->Sort(LVariantCmp, (NativeInt)Param); break; } default: { Dst->Empty(); if (Log) Log->Print( "%s IDomCall warning: Unexpected list member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_HASHTABLE: { CheckParam(Dom->Value.Hash); switch (p) { case ObjLength: { *Dst = Dom->Value.Hash->Length(); break; } case ContainerAdd: { if (Arg.Length() == 2 && Arg[0] && Arg[1]) { char *Key = Arg[1]->Str(); if (Key) { LVariant *v = new LVariant; *v = *Arg[0]; Dom->Value.Hash->Add(Key, v); } } break; } case ContainerDelete: { if (Arg.Length() == 1 && Arg[0]) { char *Key = Arg[0]->Str(); if (Key) { LVariant *v = (LVariant*) Dom->Value.Hash->Find(Key); if (v) { Dom->Value.Hash->Delete(Key); delete v; } } } break; } case ContainerHasKey: { if (Arg.Length() > 0 && Arg[0]) { char *Key = Arg[0]->Str(); *Dst = (bool) (Dom->Value.Hash->Find(Key) != NULL); } else { *Dst = false; } break; } default: { Dst->Empty(); if (Log) Log->Print("%s IDomCall warning: Unexpected hashtable member '%s'.\n", Code->AddrToSourceRef(CurrentScriptAddress), sName); Status = ScriptWarning; break; } } break; } case GV_BINARY: { switch (p) { default: break; case ObjLength: *Dst = Dom->Value.Binary.Length; break; } break; } case GV_STRING: { if (Arg.Length() > 0 && !Arg[0]) { Dst->Empty(); break; } switch (p) { case ObjLength: { char *s = Dom->Str(); *Dst = (int) (s ? strlen(s) : 0); break; } case StrJoin: { switch (Arg[0]->Type) { case GV_LIST: { LStringPipe p(256); List *Lst = Arg[0]->Value.Lst; const char *Sep = Dom->CastString(); auto It = Lst->begin(); LVariant *v = *It; if (v) { LVariant Tmp = *v; p.Print("%s", Tmp.CastString()); while ((v = *(++It))) { Tmp = *v; p.Print("%s%s", Sep, Tmp.CastString()); } } Dst->OwnStr(p.NewStr()); break; } default: { *Dst = *Arg[0]; Dst->CastString(); break; } } break; } case StrSplit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); size_t SepLen = strlen(Sep); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = strstr(c, Sep); if (!next) break; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Value.Lst->Insert(v); c = next + SepLen; } if (c && *c) { LVariant *v = new LVariant; v->OwnStr(NewStr(c)); Dst->Value.Lst->Insert(v); } break; } case StrSplitDelimit: { const char *Sep = Arg[0]->Str(); if (!Sep) { Dst->Empty(); break; } LVariant Tmp; if (Dst == Dom) { Tmp = *Dom; Dom = &Tmp; } Dst->SetList(); int MaxSplit = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; const char *c = Dom->CastString(); while (c && *c) { if (MaxSplit > 0 && (int)Dst->Value.Lst->Length() >= MaxSplit) break; const char *next = c; while (*next && !strchr(Sep, *next)) next++; LVariant *v = new LVariant; v->OwnStr(NewStr(c, next - c)); Dst->Add(v); for (c = next; *c && strchr(Sep, *c); c++) ; } if (c && *c) Dst->Add(new LVariant(c)); break; } case StrFind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = Strlen(s); auto sub = Arg[0]->Str(); auto start = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; auto end = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start >= sLen) { *Dst = -1; break; } char *sStart = (char*)s + start; char *pos; if (end >= 0) pos = Strnstr(sStart, sub, end); else pos = Strstr(sStart, sub); if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrRfind: { const char *s = Dom->Str(); if (!s) { *Dst = -1; break; } ssize_t sLen = strlen(s); auto sub = Arg[0]->Str(); auto start_idx = Arg.Length() > 1 ? Arg[1]->CastInt32() : 0; auto end_idx = Arg.Length() > 2 ? Arg[2]->CastInt32() : -1; if (start_idx >= sLen) { *Dst = -1; break; } auto sublen = Strlen(sub); auto cur = s + start_idx; auto end = end_idx >= 0 ? cur + end_idx : NULL; const char *pos = NULL; while (true) { cur = (end) ? Strnstr(cur, sub, end - cur) : Strstr(cur, sub); if (cur) { pos = cur; cur += sublen; } else break; } if (pos) *Dst = (int64) (pos - s); else *Dst = -1; break; } case StrLower: { if (Dst != Dom) *Dst = Dom->CastString(); StrLwr(Dst->Str()); break; } case StrUpper: { if (Dst != Dom) *Dst = Dom->CastString(); StrUpr(Dst->Str()); break; } case StrStrip: { auto s = Dom->Str(); if (s) { const char *Delimit = Arg.Length() > 0 ? Arg[0]->Str() : NULL; if (!Delimit) Delimit = WhiteSpace; auto start = s; auto end = s + Strlen(s); while (start < end && strchr(Delimit, *start)) start++; while (end > start && strchr(Delimit, end[-1])) end--; Dst->OwnStr(NewStr(start, end - start)); } else Dst->Empty(); break; } case StrSub: { auto s = Dom->Str(); if (s) { ssize_t Start = Arg.Length() > 0 ? Arg[0]->CastInt32() : 0; ssize_t End = Arg.Length() > 1 ? Arg[1]->CastInt32() : -1; ssize_t Len = strlen(s); if (End < 0 || End > Len) End = Len; if (Start < 0) Start = 0; if (Start <= End) Dst->OwnStr(NewStr(s + Start, End - Start)); else Dst->Empty(); } else Dst->Empty(); break; } default: { Dst->Empty(); if (Log) Log->Print("%p IDomCall warning: Unexpected string member %s (%s:%i).\n", CurrentScriptAddress, sName, _FL); Status = ScriptWarning; break; } } break; } default: { const char *Type = LVariant::TypeToString(Dom->Type); char t[32]; if (!Type) { sprintf_s(t, sizeof(t), "UnknownType(%i)", Dom->Type); Type = t; } Dst->Empty(); if (Log) { Log->Print("%s IDomCall warning: Unexpected type %s (Src=%s:%i IP=0x%x).\n", Code->AddrToSourceRef(CurrentScriptAddress), Type, _FL, CurrentScriptAddress); } Status = ScriptWarning; break; } } + Arg.Length(0); // It doesn't own the variants, so don't delete them. + #else LVariant *Count = NULL; switch (c.r->Scope) { case SCOPE_GLOBAL: Count = &Code->Globals[c.r->Index]; c.r++; break; default: OnException(_FL, CurrentScriptAddress, "Unsupported scope."); return ScriptError; } int Args = Count->CastInt32(); for (int i=0; iPrint("%s%s", i ? ", " : "", c.r->GetStr()); #endif c.r++; } #if VM_DECOMP if (Log) Log->Print(")\n"); #endif #endif break; } case IBreakPoint: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE OnException(_FL, CurrentScriptAddress-1, "ShowDebugger"); return ScriptWarning; #endif break; } case IDebug: { #if VM_DECOMP if (Log) Log->Print("%p Debugger\n", CurrentScriptAddress-1); #elif VM_EXECUTE #ifdef WINDOWS __debugbreak(); #elif defined MAC __builtin_trap(); #elif defined LINUX Gtk::raise(SIGINT); #else #warning "Not impl." #endif #endif break; } #undef Resolve #undef GResolveRef \ No newline at end of file diff --git a/src/common/Coding/ScriptLibrary.cpp b/src/common/Coding/ScriptLibrary.cpp --- a/src/common/Coding/ScriptLibrary.cpp +++ b/src/common/Coding/ScriptLibrary.cpp @@ -1,1250 +1,1260 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Scripting.h" #include "lgi/common/SubProcess.h" #include "lgi/common/LgiRes.h" #include "lgi/common/FileSelect.h" #include "ScriptingPriv.h" ////////////////////////////////////////////////////////////////////////////////////// char16 sChar[] = L"char"; char16 sInt[] = { 'i','n','t', 0 }; char16 sUInt[] = { 'u','i','n','t', 0 }; char16 sInt32[] = { 'i','n','t','3','2', 0 }; char16 sUInt32[] = { 'u','i','n','t','3','2', 0 }; char16 sInt64[] = { 'i','n','t','6','4', 0 }; char16 sHWND[] = { 'H','W','N','D', 0 }; char16 sDWORD[] = { 'D','W','O','R','D', 0 }; char16 sLPTSTR[] = { 's','L','P','T','S','T','R', 0 }; char16 sLPCTSTR[] = { 's','L','P','C','T','S','T','R', 0 }; char16 sElse[] = { 'e','l','s','e', 0 }; char16 sIf[] = { 'i','f',0 }; char16 sFunction[] = { 'f','u','n','c','t','i','o','n',0 }; char16 sExtern[] = { 'e','x','t','e','r','n',0 }; char16 sFor[] = { 'f','o','r',0 }; char16 sWhile[] = { 'w','h','i','l','e',0 }; char16 sReturn[] = { 'r','e','t','u','r','n',0 }; char16 sInclude[] = { 'i','n','c','l','u','d','e',0 }; char16 sDefine[] = { 'd','e','f','i','n','e',0 }; char16 sStruct[] = { 's','t','r','u','c','t',0 }; char16 sTrue[] = { 't','r','u','e',0 }; char16 sFalse[] = { 'f','a','l','s','e',0 }; char16 sNull[] = { 'n','u','l','l',0 }; char16 sOutParam[] = { '_','o','u','t','_',0}; char16 sHash[] = { '#', 0 }; char16 sPeriod[] = { '.', 0 }; char16 sComma[] = { ',', 0 }; char16 sSemiColon[] = { ';', 0 }; char16 sStartRdBracket[] = { '(', 0 }; char16 sEndRdBracket[] = { ')', 0 }; char16 sStartSqBracket[] = { '[', 0 }; char16 sEndSqBracket[] = { ']', 0 }; char16 sStartCurlyBracket[] = { '{', 0 }; char16 sEndCurlyBracket[] = { '}', 0 }; ////////////////////////////////////////////////////////////////////////////////////// LExecutionStatus LHostFunc::Call(LScriptContext *Ctx, LScriptArguments &Args) { return (Ctx->*(Func))(Args) ? ScriptSuccess : ScriptError; } const char *InstToString(GInstruction i) { #undef _i #define _i(name, opcode, desc) \ case name: return desc; switch (i) { AllInstructions } return "#err"; } -LStream LScriptArguments::NullConsole; - ////////////////////////////////////////////////////////////////////////////////////// int LScriptUtils::atoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atoi(b); } return i; } int64 LScriptUtils::atoi64(char16 *s) { int64 i = 0; if (s) { #ifdef _MSC_VER i = _wtoi64(s); #else char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = strtoll(b, 0, 10); #endif } return i; } double LScriptUtils::atof(char16 *s) { double i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::atof(b); } return i; } int LScriptUtils::htoi(char16 *s) { int i = 0; if (s) { char b[64]; ssize_t Len = StrlenW(s) * sizeof(*s); ssize_t Bytes = LBufConvertCp(b, "utf-8", sizeof(b), (const void*&)s, LGI_WideCharset, Len); b[Bytes/sizeof(*b)] = 0; i = ::htoi(b); } return i; } ////////////////////////////////////////////////////////////////////////////////////// SystemFunctions::SystemFunctions() { Engine = NULL; Log = NULL; #ifdef WINNATIVE Brk = NULL; #endif } SystemFunctions::~SystemFunctions() { } LStream *SystemFunctions::GetLog() { return Log; } bool SystemFunctions::SetLog(LStream *log) { LAssert(Log == NULL); Log = log; return true; } void SystemFunctions::SetEngine(LScriptEngine *Eng) { Engine = Eng; } bool SystemFunctions::Assert(LScriptArguments &Args) { *Args.GetReturn() = true; if (Args.Length() == 0) return true; auto v = Args[0]->CastInt32(); if (!v) { const char *Msg = Args.Length() > 1 ? Args[1]->CastString() : NULL; *Args.GetReturn() = false; Args.Throw(NULL, -1, Msg); } return true; } bool SystemFunctions::DebuggerEnabled(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } Args.GetVm()->SetDebuggerEnabled(Args[0]->CastInt32() != 0); return true; } bool SystemFunctions::Throw(LScriptArguments &Args) { const char *Msg = Args.Length() > 0 ? Args[0]->CastString() : NULL; Args.Throw(NULL, -1, Msg); return true; } bool SystemFunctions::LoadString(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LLoadString(Args[0]->CastInt32()); return true; } bool SystemFunctions::Sprintf(LScriptArguments &Args) { if (Args.Length() < 1) { LAssert(!"Wrong args."); return false; } char *Fmt = Args[0]->Str(); if (!Fmt) return false; #if defined(LINUX) || defined(MAC) // No support for sprintf with generated args... hack a string up // Formatting widths etc not supported. LArray s; int i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { f++; // Skip '%' // char *Fmt = f; while (*f && !IsAlpha(*f)) f++; // Skip formatting.. if (i >= Args.Length()) break; // No more arguments... switch (*f) { case 's': { // String... char *v = Args[i]->CastString(); if (v) s.Add(v, strlen(v)); else s.Add((char*)"(null)", 4); break; } case 'c': { char *Str = Args[i]->Str(); s.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { break; } case 'u': case 'd': case 'i': { // Int... LString v; v.Printf("%i", Args[i]->CastInt32()); s.Add(v.Get(), v.Length()); break; } } i++; } else s.Add(*f); } s.Add(0); // NULL terminate *Args.GetReturn() = s.AddressOf(); #else LArray Params; va_list a; unsigned i = 1; for (char *f = Fmt; *f; f++) { if (f[0] == '%' && f[1] != '%') { char *t = f + 1; while (*t && !IsAlpha(*t)) t++; if (i >= Args.Length()) { LAssert(!"Not enough args."); break; } switch (*t) { case 's': { Params.Add((UNativeInt)Args[i++]->Str()); break; } case 'c': { char *Str = Args[i++]->Str(); Params.Add(Str ? *Str : '?'); break; } case 'f': case 'g': { union tmp { double Dbl; struct { uint32_t High; uint32_t Low; }; } Tmp; Tmp.Dbl = Args[i++]->CastDouble(); Params.Add(Tmp.High); Params.Add(Tmp.Low); break; } default: { Params.Add(Args[i++]->CastInt32()); break; } } f = *t ? t + 1 : t; } } a = (va_list) &Params[0]; #ifndef WIN32 #define _vsnprintf vsnprintf #endif char Buf[1024]; vsprintf_s(Buf, sizeof(Buf), Fmt, a); *Args.GetReturn() = Buf; #endif return true; } bool SystemFunctions::ReadTextFile(LScriptArguments &Args) { if (Args.Length() == 1 && LFileExists(Args[0]->CastString())) { if (Args.GetReturn()->OwnStr(::LReadTextFile(Args[0]->CastString()))) return true; } return false; } bool SystemFunctions::WriteTextFile(LScriptArguments &Args) { if (Args.Length() == 2) { LFile f; if (f.Open(Args[0]->CastString(), O_WRITE)) { f.SetSize(0); LVariant *v = Args[1]; if (v) { switch (v->Type) { default: break; case GV_STRING: { size_t Len = strlen(v->Value.String); *Args.GetReturn() = f.Write(v->Value.String, Len) == Len; return true; break; } case GV_BINARY: { *Args.GetReturn() = f.Write(v->Value.Binary.Data, v->Value.Binary.Length) == v->Value.Binary.Length; return true; break; } } } } } return false; } LView *SystemFunctions::CastLView(LVariant &v) { switch (v.Type) { default: break; case GV_DOM: return dynamic_cast(v.Value.Dom); case GV_GVIEW: return v.Value.View; } return 0; } bool SystemFunctions::SelectFiles(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFiles(Parent, Callback[, FileTypes[, InitialDir[, MultiSelect[, SaveAs]]]]) expects at least 2 arguments."); return false; } - auto Ctx = Args.GetVm()->SaveContext(); + auto Vm = dynamic_cast(Args.GetVm()); + if (!Vm) + return false; + + auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); auto Types = LString(Args.IdxCheck(2) ? Args[2]->CastString() : NULL).SplitDelimit(",;:"); for (auto c: Types) { char *sp = strrchr(c, ' '); if (sp) { *sp++ = 0; s->Type(sp, c); } else { char *dot = strrchr(c, '.'); if (dot) { char Type[256]; sprintf_s(Type, sizeof(Type), "%s files", dot + 1); s->Type(Type, c); } } } s->Type("All Files", LGI_ALL_FILES); s->InitialDir (Args.IdxCheck(3) ? Args[3]->CastString() : 0); s->MultiSelect(Args.IdxCheck(4) ? Args[4]->CastInt32() != 0 : true); bool SaveAs = Args.IdxCheck(5) ? Args[5]->CastInt32() != 0 : false; auto Process = [Callback=LString(Callback),Ctx](LFileSelect *s, bool ok) { if (ok) { LScriptArguments Args(NULL); LVariant *v = new LVariant; if (auto Lst = v->SetList()) { for (unsigned i=0; iLength(); i++) { auto path = (*s)[i]; Lst->Insert(new LVariant(path)); } } Args.Add(v); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); return true; } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "SelectFolder(Parent, Callback[, InitialDir]) expects at least 2 arguments."); return false; } - auto Ctx = Args.GetVm()->SaveContext(); + auto Vm = dynamic_cast(Args.GetVm()); + if (!Vm) + return false; + + auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "SelectFiles(...) requires a valid callback context."); return false; } LFileSelect *s = new LFileSelect; if (!s) return false; s->Parent(CastLView(*Args[0])); auto Callback = Args[1]->Str(); if (Args.IdxCheck(2)) s->InitialDir(Args[2]->CastString()); s->OpenFolder([Ctx, Callback = LString(Callback)](auto s, bool ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(s->Name())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete s; }); return true; } bool SystemFunctions::Sleep(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LSleep(Args[0]->CastInt32()); return true; } bool SystemFunctions::ToString(LScriptArguments &Args) { LStringPipe p; const char *Sep = ", "; for (unsigned i=0; iToString(); p.Print("%s%s", i?Sep:"", s.Get()); } Args.GetReturn()->OwnStr(p.NewStr()); return true; } bool SystemFunctions::Lgi4CC(LScriptArguments &Args) { auto s = Args.StringAt(0); if (!s) return false; auto i = ::Lgi4CC(s); *Args.GetReturn() = i; return true; } bool SystemFunctions::Print(LScriptArguments &Args) { LStream *Out = Log ? Log : (Engine ? Engine->GetConsole() : NULL); for (unsigned n=0; Out && nWrite("NULL", 4); continue; } #if 1 size_t Len = strlen(f); Out->Write(f, Len); #else char *i = f, *o = f; for (; *i; i++) { if (*i == '\\') { i++; switch (*i) { case 'n': *o++ = '\n'; break; case 'r': *o++ = '\r'; break; case 't': *o++ = '\t'; break; case '\\': *o++ = '\\'; break; case '0': *o++ = 0; break; } } else { *o++ = *i; } } *o = 0; Out->Write(f, o - f); #endif } return true; } bool SystemFunctions::FormatSize(LScriptArguments &Args) { if (Args.Length() != 1) return false; char s[64]; LFormatSize(s, sizeof(s), Args[0]->CastInt64()); *Args.GetReturn() = s; return true; } bool SystemFunctions::ClockTick(LScriptArguments &Args) { *Args.GetReturn() = (int64)LCurrentTime(); return true; } bool SystemFunctions::Now(LScriptArguments &Args) { Args.GetReturn()->Empty(); Args.GetReturn()->Type = GV_DATETIME; Args.GetReturn()->Value.Date = new LDateTime; Args.GetReturn()->Value.Date->SetNow(); return true; } bool SystemFunctions::New(LScriptArguments &Args) { if (Args.Length() < 1 || !Args[0]) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->Empty(); char *sType = Args[0]->CastString(); if (!sType) return false; if (IsDigit(*sType)) { // Binary block int Bytes = ::atoi(sType); if (!Bytes) return false; return Args.GetReturn()->SetBinary(Bytes, new char[Bytes], true); } LVariant *Ret = Args.GetReturn(); LDomProperty Type = LStringToDomProp(sType); switch (Type) { case TypeList: { Ret->SetList(); break; } case TypeHashTable: { Ret->SetHashTable(); break; } case TypeSurface: { Ret->Empty(); Ret->Type = GV_LSURFACE; if ((Ret->Value.Surface.Ptr = new LMemDC)) { Ret->Value.Surface.Ptr->IncRef(); Ret->Value.Surface.Own = true; } break; } case TypeFile: { Ret->Empty(); #if 1 Ret->Type = GV_STREAM; Ret->Value.Stream.Ptr = new LFile; if (Ret->Value.Stream.Ptr) Ret->Value.Stream.Own = true; #else Ret->Type = GV_GFILE; if ((Ret->Value.File.Ptr = new LFile)) { Ret->Value.File.Ptr->AddRef(); Ret->Value.File.Own = true; } #endif break; } case TypeDateTime: { Ret->Empty(); Ret->Type = GV_DATETIME; Ret->Value.Date = new LDateTime; break; } default: { Ret->Empty(); LCompiledCode *c = Engine ? Engine->GetCurrentCode() : NULL; if (!c) return false; LAutoWString o(Utf8ToWide(sType)); LCustomType *t = c->GetType(o); if (t) { int ArrayLength = Args.Length() > 1 ? Args[1]->CastInt32() : 1; if (ArrayLength > 0) { Ret->Type = GV_CUSTOM; Ret->Value.Custom.Dom = t; Ret->Value.Custom.Data = new uint8_t[t->Sizeof() * ArrayLength]; } } } } return true; } bool SystemFunctions::Len(LScriptArguments &Args) { size_t i = 0; for (LVariant *v: Args) { switch (v->Type) { case GV_LIST: i += v->Value.Lst->Length(); break; case GV_HASHTABLE: i += v->Value.Hash->Length(); break; case GV_BINARY: i += v->Value.Binary.Length; break; case GV_STRING: i += Strlen(v->Value.String); break; case GV_WSTRING: i += Strlen(v->Value.WString); break; default: i += 1; break; } } *Args.GetReturn() = i; return true; } bool SystemFunctions::Delete(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } LVariant *v = Args[0]; if (v->Type == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } else { v->Empty(); } *Args.GetReturn() = true; return true; } class LFileListEntry : public LDom { bool Folder; LVariant Name; int64 Size; LDateTime Modified; public: LFileListEntry(LDirectory *d) { Folder = d->IsDir(); Name = d->GetName(); Size = d->GetSize(); Modified.Set(d->GetLastWriteTime()); } bool GetVariant(const char *Var, LVariant &Value, const char *Arr = NULL) override { LDomProperty p = LStringToDomProp(Var); switch (p) { case ObjName: Value = Name; break; case ObjLength: Value = Size; break; case FileFolder: Value = Folder; break; case FileModified: Value = &Modified; break; default: return false; } return true; } }; bool SystemFunctions::DeleteFile(LScriptArguments &Args) { if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } char *f = Args[0]->CastString(); if (f) *Args.GetReturn() = FileDev->Delete(Args[0]->CastString()); else *Args.GetReturn() = false; return true; } bool SystemFunctions::CurrentScript(LScriptArguments &Args) { LCompiledCode *Code; if (Engine && (Code = Engine->GetCurrentCode())) { *Args.GetReturn() = Code->GetFileName(); return true; } return false; } bool SystemFunctions::PathExists(LScriptArguments &Args) { if (Args.Length() == 0) { LAssert(!"Wrong args."); return false; } LDirectory d; if (d.First(Args[0]->CastString(), NULL)) { if (d.IsDir()) *Args.GetReturn() = 2; else *Args.GetReturn() = 1; } else { *Args.GetReturn() = 0; } return true; } bool SystemFunctions::PathJoin(LScriptArguments &Args) { char p[MAX_PATH_LEN] = ""; for (unsigned i=0; iCastString(); if (i) LMakePath(p, sizeof(p), p, s); else strcpy_s(p, sizeof(p), s); } if (*p) *Args.GetReturn() = p; else Args.GetReturn()->Empty(); return true; } bool SystemFunctions::PathSep(LScriptArguments &Args) { *Args.GetReturn() = DIR_STR; return true; } bool SystemFunctions::ListFiles(LScriptArguments &Args) { if (Args.Length() < 1) { Args.GetReturn()->Empty(); Args.Throw(_FL, "ListFiles(FolderPath[, FilterPattern]) expects at least one argument."); return false; } Args.GetReturn()->SetList(); auto Folder = Args[0]->CastString(); auto Pattern = Args.Length() > 1 ? Args[1]->CastString() : NULL; LDirectory d; for (auto b=d.First(Folder); b; b=d.Next()) { if (!Pattern || MatchStr(Pattern, d.GetName())) Args.GetReturn()->Value.Lst->Insert(new LVariant(new LFileListEntry(&d))); } return true; } bool SystemFunctions::CreateSurface(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { Args.Throw(_FL, "CreateSurface(x, y[, Bits|ColourSpace]) expects at least two arguments."); return false; } auto x = Args[0]->CastInt32(); auto y = Args[1]->CastInt32(); LColourSpace Cs = CsNone; if (Args.Length() > 2) { LVariant *Type = Args[2]; const char *c; if (Type->IsInt()) { // Bit depth... convert to default Colour Space. Cs = LBitsToColourSpace(Type->CastInt32()); } else if ((c = Type->Str())) { // Parse string colour space def Cs = LStringToColourSpace(Type->Str()); } } if (!Cs) // Catch all error cases and make it the default screen depth. Cs = GdcD->GetColourSpace(); auto r = Args.GetReturn(); if ((r->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { r->Type = GV_LSURFACE; r->Value.Surface.Own = true; r->Value.Surface.Ptr->IncRef(); } return true; } bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "ColourSpaceToString(ColourSpaceInt) expects at least one argument."); return false; } *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); return true; } bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { Args.Throw(_FL, "StringToColourSpace(ColourSpaceStr) expects at least one argument."); return false; } *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); return true; } bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { Args.Throw(_FL, "MessageDlg(Parent, Message[, Title[, Buttons]]) expects at least 2 arguments."); return false; } auto Parent = CastLView(*Args[0]); auto Msg = Args[1]->Str(); auto Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); auto Btns = Args.IdxCheck(3) ? Args[3]->CastInt32() : MB_OK; auto Btn = LgiMsg(Parent, Msg, Title, Btns); *Args.GetReturn() = Btn; return true; } bool SystemFunctions::GetInputDlg(LScriptArguments &Args) { if (Args.Length() < 5) { Args.Throw(_FL, "GetInputDlg(Parent, DefaultInput, Message, Title, IsPassword, Callback) expects 5 arguments."); return false; } - auto Ctx = Args.GetVm()->SaveContext(); + auto Vm = dynamic_cast(Args.GetVm()); + if (!Vm) + return false; + + auto Ctx = Vm->SaveContext(); if (!Ctx) { Args.Throw(_FL, "GetInputDlg requires a valid VM context."); return false; } auto Parent = CastLView(*Args[0]); auto InitVal = Args[1]->Str(); auto Msg = Args[2]->Str(); auto Title = Args[3]->Str(); auto Pass = Args[4]->CastInt32() != 0; auto Callback = Args[5]->Str(); Args.GetReturn()->Empty(); auto Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); Dlg->DoModal([this, Dlg, Ctx, Callback=LString(Callback)](auto d, auto ok) { if (ok) { LScriptArguments Args(NULL); Args.Add(new LVariant(Dlg->GetStr())); Ctx.Call(Callback, Args); Args.DeleteObjects(); } delete Dlg; }); // Don't wait here... return true; } bool SystemFunctions::GetViewById(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); int Id = Args[1]->CastInt32(); if (!Parent || Id <= 0) return false; if (Parent->GetViewById(Id, Args.GetReturn()->Value.View)) { Args.GetReturn()->Type = GV_GVIEW; } return true; } bool SystemFunctions::Execute(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LStringPipe p; char *Exe = Args[0]->CastString(); char *Arguments = Args[1]->CastString(); LSubProcess e(Exe, Arguments); bool Status = e.Start(); if (Status) { e.Communicate(&p); LAutoString o(p.NewStr()); *Args.GetReturn() = o; } else if (Log) { uint32_t ErrCode = e.GetErrorCode(); LString ErrMsg = LErrorCodeToString(ErrCode); if (ErrMsg) Log->Print("Error: Execute(\"%s\",\"%s\") failed with '%s'\n", Exe, Arguments, ErrMsg.Get()); else Log->Print("Error: Execute(\"%s\",\"%s\") failed with '0x%x'\n", Exe, Arguments, ErrCode); } return Status; } bool SystemFunctions::System(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } char *Exe = Args[0]->Str(); char *Arg = Args[1]->Str(); *Args.GetReturn() = LExecute(Exe, Arg); return true; } bool SystemFunctions::OsName(LScriptArguments &Args) { *Args.GetReturn() = LGetOsName(); return true; } bool SystemFunctions::OsVersion(LScriptArguments &Args) { LArray Ver; LGetOs(&Ver); Args.GetReturn()->SetList(); for (int i=0; i<3; i++) Args.GetReturn()->Value.Lst->Insert(new LVariant(Ver[i])); return true; } #define DefFn(Name) \ LHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) LHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), DefFn(Lgi4CC), // Containers/objects DefFn(New), DefFn(Delete), DefFn(Len), // Files DefFn(ReadTextFile), DefFn(WriteTextFile), DefFn(SelectFiles), DefFn(SelectFolder), DefFn(ListFiles), DefFn(DeleteFile), DefFn(CurrentScript), DefFn(PathJoin), DefFn(PathExists), DefFn(PathSep), // Time DefFn(ClockTick), DefFn(Sleep), DefFn(Now), // Images DefFn(CreateSurface), DefFn(ColourSpaceToString), DefFn(StringToColourSpace), // UI DefFn(MessageDlg), DefFn(GetInputDlg), DefFn(GetViewById), // System DefFn(Execute), DefFn(System), DefFn(OsName), DefFn(OsVersion), // End of list marker LHostFunc(0, 0, 0), }; LHostFunc *SystemFunctions::GetCommands() { return SystemLibrary; } 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,2212 +1,2200 @@ #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); } if (Vm && Vm->OpenDebugger(Code)) { if (!Debugger->GetCode()) { LAutoPtr Cp(new LCompiledCode(*Code)); Debugger->OwnCompiledCode(Cp); LStringPipe AsmBuf; Decompile(Code->UserContext, Code, &AsmBuf); LAutoString Asm(AsmBuf.NewStr()); Debugger->SetSource(Asm); } 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) { LAutoString a(p.NewStr()); 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(); if (Type == RunContinue && BreakPts.Length() == 0) { // Unconstrained execution while (c.u8 < e) { #if TIME_INSTRUCTIONS uint8 TimedOpCode = *c.u8; QueryPerformanceCounter(&start); #endif #ifdef BREAK_POINT if (c.u8 - Base == BREAK_POINT) { int asd=0; } #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."); } } 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 && !d->Debugger) { 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 bool OwnVm; LAutoPtr Vm; LVmCallback *Callback; LString Script, Assembly; LArray Blocks; size_t CurrentAddr; LArray LineIsAsm; LAutoPtr Obj; LVariant Return; bool AcceptNotify; // Ui bool RunLoop; LView *Parent; LBox *Main; LBox *Sub; LList *SourceLst; LTabView *Tabs; LDebugView *Text; LList *Locals, *Globals, *Registers, *Stack; LTextLog *Log; LToolBar *Tools; LTableLayout *VarsTbl; LScriptVmDebuggerPriv() { RunLoop = false; OwnVm = false; CurrentAddr = -1; Main = NULL; Tabs = NULL; Log = NULL; Text = NULL; Locals = NULL; Globals = NULL; Registers = NULL; Stack = NULL; Tools = NULL; SourceLst = NULL; Callback = NULL; VarsTbl = NULL; } }; LDebugView::LDebugView(LScriptVmDebuggerPriv *priv) : LTextView3(IDC_TEXT, 0, 0, 100, 100) { d = priv; ErrorLine = -1; SetWrapType(TEXTED_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 >= b.AsmAddr[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, LVirtualMachine *Vm, LCompiledCode *Code, const char *Assembly) { d = new LScriptVmDebuggerPriv; d->Parent = Parent; d->AcceptNotify = false; if (Vm) d->Vm.Reset(new LVirtualMachine(Vm)); d->Callback = Callback; if (Code) d->Script = Code->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 (Code && Code->GetFileName()) { if (_stricmp(p, Code->GetFileName()) == 0) Match = it; } d->SourceLst->Insert(it); } } } if (!Match && Code) { LListItem *it = new LListItem; if (it) { it->SetText(LGetLeaf(Code->GetFileName()), 0); it->SetText(Code->GetFileName(), 1); d->SourceLst->Insert(it); it->Select(true); } } } } d->AcceptNotify = true; } LVmDebuggerWnd::~LVmDebuggerWnd() { LAssert(d->RunLoop == false); } bool LVmDebuggerWnd::OnRequestClose(bool OsShuttingDown) { if (!d->RunLoop) return LWindow::OnRequestClose(OsShuttingDown); d->RunLoop = false; return false; // Wait for Run() to exit in it's own time. } void LVmDebuggerWnd::Run() { // This is to allow objects on the application's stack to // still be valid while the debugger UI is shown. d->RunLoop = true; while (d->RunLoop && Visible()) { LYield(); LSleep(20); } Quit(); } LStream *LVmDebuggerWnd::GetLog() { return d->Log; } void LVmDebuggerWnd::OwnVm(bool Own) { d->OwnVm = Own; } void LVmDebuggerWnd::OwnCompiledCode(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: { d->Vm.Reset(); if (d->RunLoop) d->RunLoop = false; else 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); } -///////////////////////////////////////////////////////////////////////////// -bool LScriptArguments::Throw(const char *File, int Line, const char *Msg, ...) -{ - if (!Vm || !Vm->d) - return false; - - va_list Arg; - va_start(Arg, Msg); - - LString s; - s.Printf(Arg, Msg); - - va_end(Arg); - - Vm->d->OnException(File, Line, -1, s); - return true; -} diff --git a/src/common/Coding/ScriptingPriv.h b/src/common/Coding/ScriptingPriv.h --- a/src/common/Coding/ScriptingPriv.h +++ b/src/common/Coding/ScriptingPriv.h @@ -1,593 +1,597 @@ #ifndef _GSCRIPTING_PRIV_H_ #define _GSCRIPTING_PRIV_H_ #include #include "lgi/common/Scripting.h" #include "lgi/common/RefCount.h" // Instructions #define _i(name, opcode, desc) \ name = opcode, #define AllInstructions \ _i(INop, 0, "Nop") \ _i(IAssign, OpAssign, "OpAssign") \ _i(IPlus, OpPlus, "OpPlus") \ _i(IUnaryPlus, OpUnaryPlus, "OpUnaryPlus") \ _i(IMinus, OpMinus, "OpMinus") \ _i(IUnaryMinus, OpUnaryMinus, "OpUnaryMinus") \ _i(IMul, OpMul, "OpMul") \ _i(IDiv, OpDiv, "OpDiv") \ _i(IMod, OpMod, "OpMod") \ _i(ILessThan, OpLessThan, "OpLessThan") \ _i(ILessThanEqual, OpLessThanEqual, "OpLessThanEqual") \ _i(IGreaterThan, OpGreaterThan, "OpGreaterThan") \ _i(IGreaterThanEqual, OpGreaterThanEqual, "OpGreaterThanEqual") \ _i(IEquals, OpEquals, "OpEquals") \ _i(INotEquals, OpNotEquals, "OpNotEquals") \ _i(IPlusEquals, OpPlusEquals, "OpPlusEquals") \ _i(IMinusEquals, OpMinusEquals, "OpMinusEquals") \ _i(IMulEquals, OpMulEquals, "OpMulEquals") \ _i(IDivEquals, OpDivEquals, "OpDivEquals") \ _i(IPostInc, OpPostInc, "OpPostInc") \ _i(IPostDec, OpPostDec, "OpPostDec") \ _i(IPreInc, OpPreInc, "OpPreInc") \ _i(IPreDec, OpPreDec, "OpPreDec") \ _i(IAnd, OpAnd, "OpAnd") \ _i(IOr, OpOr, "OpOr") \ _i(INot, OpNot, "OpNot") \ \ /** Calls a another part of the script */ \ _i(ICallScript, 64, "CallScript") \ /** Calls a method defined by the script context */ \ _i(ICallMethod, 65, "CallMethod") \ _i(IDomGet, 67, "DomGet") \ _i(IDomSet, 68, "DomSet") \ _i(IPush, 69, "Push") \ _i(IPop, 70, "Pop") \ _i(IJump, 71, "Jump") \ _i(IJumpZero, 72, "JumpZ") \ _i(IArrayGet, 73, "ArrayGet") \ _i(IArraySet, 74, "ArraySet") \ _i(IRet, 75, "Return") \ _i(IDomCall, 76, "DomCall") \ /* Stop in the VM at instruction */ \ _i(IBreakPoint, 77, "BreakPoint") \ _i(ICast, 78, "Cast") \ /* Open the debugger */ \ _i(IDebug, 79, "Debug") \ enum GInstruction { AllInstructions }; enum OperatorType { OpPrefix, OpInfix, OpPostfix, }; extern char16 sChar[]; extern char16 sInt[]; extern char16 sUInt[]; extern char16 sInt32[]; extern char16 sUInt32[]; extern char16 sInt64[]; extern char16 sHWND[]; extern char16 sDWORD[]; extern char16 sLPTSTR[]; extern char16 sLPCTSTR[]; extern char16 sElse[]; extern char16 sIf[]; extern char16 sFunction[]; extern char16 sExtern[]; extern char16 sFor[]; extern char16 sWhile[]; extern char16 sReturn[]; extern char16 sInclude[]; extern char16 sDefine[]; extern char16 sStruct[]; extern char16 sTrue[]; extern char16 sFalse[]; extern char16 sNull[]; extern char16 sOutParam[]; extern char16 sHash[]; extern char16 sPeriod[]; extern char16 sComma[]; extern char16 sSemiColon[]; extern char16 sStartRdBracket[]; extern char16 sEndRdBracket[]; extern char16 sStartSqBracket[]; extern char16 sEndSqBracket[]; extern char16 sStartCurlyBracket[]; extern char16 sEndCurlyBracket[]; extern const char *InstToString(GInstruction i); /* Variable Reference: Can either be a: - stack variable - global variable - register (0 .. MAX_REGISTER - 1) Thus a variable reference encodes 2 pieces of information, first the scope of the reference (as above) and secondly the index into that scope. Scopes are stored as arrays of variables. The scope is one byte, the index is 3 bytes following, totally 4 bytes per ref. */ #define MAX_REGISTER 16 /// 32bit variable reference, used to track where a variable is during compilation. struct LVarRef { /// \sa #SCOPE_REGISTER, #SCOPE_LOCAL or #SCOPE_GLOBAL unsigned Scope : 8; /// Index into scope int Index : 24; bool Valid() { return Index >= 0; } void Empty() { Scope = 0; Index = -1; } bool IsReg() { return Scope == SCOPE_REGISTER && Index >= 0 && Index < MAX_REGISTER; } void SetReg(int i) { Scope = SCOPE_REGISTER; Index = i; } bool operator ==(LVarRef &r) { return r.Scope == Scope && r.Index == Index; } bool operator !=(LVarRef &r) { return r.Scope != Scope || r.Index != Index; } const char *GetStr() { if (Index < 0) { LAssert(!"Invalid reference"); return "NoRef"; } #define GETSTR_BUF_SIZE 16 static char Buf[8][GETSTR_BUF_SIZE]; static int Cur = 0; static char Names[] = {'R', 'L', 'G'}; char *b = Buf[Cur++]; if (Cur >= 8) Cur = 0; LAssert(Scope <= SCOPE_GLOBAL); sprintf_s(b, GETSTR_BUF_SIZE, "%c%i", Names[Scope], Index); return b; } }; union LScriptPtr { uint8_t *u8; uint16 *u16; uint32_t *u32; int8 *i8; int16 *i16; int32 *i32; double *dbl; float *flt; LVarRef *r; LFunc **fn; }; class SystemFunctions; class LCompileTools { protected: OperatorType OpType(LOperator o) { switch (o) { case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return OpPrefix; case OpPostInc: case OpPostDec: return OpPostfix; default: return OpInfix; } } int GetPrecedence(LOperator o) { // Taken from: // http://www.cppreference.com/operator_precedence.html switch (o) { case OpAssign: case OpMinusEquals: case OpPlusEquals: case OpMulEquals: case OpDivEquals: return 16; case OpAnd: return 13; case OpOr: return 14; case OpEquals: case OpNotEquals: return 9; case OpLessThan: case OpLessThanEqual: case OpGreaterThan: case OpGreaterThanEqual: return 8; case OpPlus: case OpMinus: return 6; case OpMul: case OpDiv: case OpMod: return 5; case OpUnaryPlus: case OpUnaryMinus: case OpPreInc: case OpPreDec: case OpNot: return 3; case OpPostInc: case OpPostDec: return 2; case OpNull: return 0; default: LAssert(!"Really?"); break; } return -1; } LOperator IsOp(char16 *s, int PrevIsOp) { if (!s) return OpNull; if (s[0] != 0 && !s[1]) { // One character operator switch (*s) { case '=': return OpAssign; case '*': return OpMul; case '/': return OpDiv; case '<': return OpLessThan; case '>': return OpGreaterThan; case '%': return OpMod; case '!': return OpNot; case '+': { if (PrevIsOp == 0) return OpPlus; return OpUnaryPlus; } case '-': { if (PrevIsOp == 0) return OpMinus; return OpUnaryMinus; } } } else if (s[0] != 0 && s[1] == '=' && !s[2]) { // 2 chars, "something" equals operator switch (*s) { case '!': return OpNotEquals; case '=': return OpEquals; case '<': return OpLessThanEqual; case '>': return OpGreaterThanEqual; case '+': return OpPlusEquals; case '-': return OpMinusEquals; case '*': return OpMulEquals; case '/': return OpDivEquals; } } else if (s[0] == '+' && s[1] == '+' && !s[2]) { if (PrevIsOp == 0) return OpPostInc; return OpPreInc; } else if (s[0] == '-' && s[1] == '-' && !s[2]) { if (PrevIsOp == 0) return OpPostDec; return OpPreDec; } else if (s[0] == '&' && s[1] == '&' && !s[2]) { return OpAnd; } else if (s[0] == '|' && s[1] == '|' && !s[2]) { return OpOr; } return OpNull; } }; /// This class compiles the source down to byte code class LCompiler : public LScriptUtils { class LCompilerPriv *d; public: /// Constructor LCompiler(); ~LCompiler(); /// Compile the source into byte code. bool Compile ( LAutoPtr &Code, LScriptContext *SysContext, LScriptContext *UserContext, const char *FileName, const char *Script, LDom *Args ); }; /// This class is the VM for the byte language -class LVirtualMachine : public LScriptUtils +class LVirtualMachine : + public LVirtualMachineI, + public LScriptUtils { friend class LVmDebuggerWnd; friend class LScriptArguments; class LVirtualMachinePriv *d; public: static bool BreakOnWarning; class Context { friend class LVirtualMachine; LCompiledCode *Code = NULL; LVmCallback *Callback = NULL; ssize_t Addr = -1; public: operator bool() { return Code != NULL && Callback != NULL; } bool Call(LString CallbackName, LScriptArguments &Args) const { if (!Callback) return false; LVirtualMachine Vm(*this); auto Prev = Args.GetVm(); Args.SetVm(&Vm); auto result = Callback->CallCallback(Vm, CallbackName, Args); Args.SetVm(Prev); return result; } }; LVirtualMachine(LVmCallback *callback = NULL); LVirtualMachine(Context ctx); LVirtualMachine(LVirtualMachine *vm); ~LVirtualMachine(); + void OnException(const char *File, int Line, ssize_t Address, const char *Msg); + /// Executes the whole script starting at the top LExecutionStatus Execute ( /// [In] The code to execute LCompiledCode *Code, /// [In] The instruction to start at... [defaults to the start of script) uint32_t StartOffset = 0, /// [Optional] Log file for execution LStream *Log = NULL, /// Start the script execution straight away? bool StartImmediately = true, /// Optional return value LVariant *Return = NULL ); /// Execute just one method and return LExecutionStatus ExecuteFunction ( /// [In] The code to execute LCompiledCode *Code, /// [In] The function to execute LFunctionInfo *Func, /// [In/Out] The function's arguments LScriptArguments &Args, /// [Optional] Log file for execution LStream *Log = NULL, /// [Optional] Copy arguments back to this array LScriptArguments *ArgsOut = NULL ); // Debugging commands LVmDebugger *OpenDebugger(LCompiledCode *Code = NULL, const char *Assembly = NULL); bool StepInstruction(); bool StepLine(); bool StepOut(); bool BreakExecution(); bool Continue(); bool BreakPoint(const char *File, int Line, bool Add); bool BreakPoint(int Addr, bool Add); void SetBreakCpp(bool Brk); void SetDebuggerEnabled(bool b); // Other methods void SetTempPath(const char *Path); LVmCallback *GetCallback(); Context SaveContext(); }; /// Scripting engine system functions class SystemFunctions : public LScriptContext { LScriptEngine *Engine; LStream *Log; #ifdef WINNATIVE HANDLE Brk; #endif LView *CastLView(LVariant &v); public: SystemFunctions(); ~SystemFunctions(); LStream *GetLog() override; bool SetLog(LStream *log) override; void SetEngine(LScriptEngine *Eng); LString GetIncludeFile(const char *FileName) override { return LString(); } LHostFunc *GetCommands() override; // Debug bool Assert(LScriptArguments &Args); bool Throw(LScriptArguments &Args); bool DebuggerEnabled(LScriptArguments &Args); // String bool LoadString(LScriptArguments &Args); /// Formats a string bool Sprintf(LScriptArguments &Args); /// Formats a file size bool FormatSize(LScriptArguments &Args); /// Prints items to the console bool Print(LScriptArguments &Args); /// Converts args to string bool ToString(LScriptArguments &Args); /// Turn a 4 char string into an int bool Lgi4CC(LScriptArguments &Args); // Object creation/deletion bool New(LScriptArguments &Args); bool Delete(LScriptArguments &Args); bool Len(LScriptArguments &Args); // File /// Reads a text file into a variable bool ReadTextFile(LScriptArguments &Args); /// Writes a text file from a variable bool WriteTextFile(LScriptArguments &Args); /// \brief Opens a file open dialog to select files. /// /// Args: LView *Parent, char *Patterns, /// char *InitFolder, bool Multiselect bool SelectFiles(LScriptArguments &Args); /// Open a folder select dialog /// /// Args: LView *Parent, char *InitFolder bool SelectFolder(LScriptArguments &Args); /// Lists file in folder /// /// Args; char *Path, [optional] char *Pattern /// Returns: List of DOM objects with the following fields: /// Name - File/dir name /// Size - Size of entry /// Folder - bool, true if folder /// Modified - LDateTime, modified time bool ListFiles(LScriptArguments &Args); /// Deletes a file bool DeleteFile(LScriptArguments &Args); /// Gets the current script path. bool CurrentScript(LScriptArguments &Args); /// Finds out if a path exists. bool PathExists(LScriptArguments &Args); /// Joins path segments together. bool PathJoin(LScriptArguments &Args); /// Returns the current OS path separator. bool PathSep(LScriptArguments &Args); // Time /// Sleeps a number of milliseconds bool Sleep(LScriptArguments &Args); /// Get the current tick count bool ClockTick(LScriptArguments &Args); /// Get the date time bool Now(LScriptArguments &Args); // Bitmaps /// Creates a memory context bool CreateSurface(LScriptArguments &Args); bool ColourSpaceToString(LScriptArguments &Args); bool StringToColourSpace(LScriptArguments &Args); // User interface /// Standard alert message box bool MessageDlg(LScriptArguments &Args); /// Gets an input string from the user /// String GetInputDlg(Window Parent, String InitialValue, String Question, String Title, bool IsPassword, String Callback. bool GetInputDlg(LScriptArguments &Args); /// Gets a view by id bool GetViewById(LScriptArguments &Args); // System /// Executes a command, waits for it to finish, then returns it's output: /// String Execute(String Application, String CmdLine); bool Execute(LScriptArguments &Args); /// Executes a command and doesn't wait for it to return: /// Bool System(String Application, String CmdLine); bool System(LScriptArguments &Args); /// Gets the operating system name. bool OsName(LScriptArguments &Args); /// Gets the operating system version. bool OsVersion(LScriptArguments &Args); }; #endif diff --git a/src/common/Gdc2/Surface.cpp b/src/common/Gdc2/Surface.cpp --- a/src/common/Gdc2/Surface.cpp +++ b/src/common/Gdc2/Surface.cpp @@ -1,2181 +1,2181 @@ /*hdr ** FILE: GdcPrim.cpp ** AUTHOR: Matthew Allen ** DATE: 1/3/97 ** DESCRIPTION: GDC v2.xx device independent primitives ** ** Copyright (C) 2001, Matthew Allen ** fret@memecode.com */ #include #include #include #include #include #include "lgi/common/Gdc2.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Palette.h" #include "lgi/common/Variant.h" #define LINE_SOLID 0xFFFFFFFF #ifdef MAC #define REGISTER #else #define REGISTER register #endif void LSurface::Init() { OriginX = OriginY = 0; pMem = NULL; pAlphaDC = 0; Flags = 0; Clip.ZOff(-1, -1); pPalette = NULL; pApp = NULL; ColourSpace = CsNone; for (int i=0; iX(), pDC->Y(), pDC->GetColourSpace())) { Blt(0, 0, pDC); if (pDC->Palette()) { LPalette *Pal = new LPalette(pDC->Palette()); if (Pal) { Palette(Pal, true); } } } } LSurface::~LSurface() { #if defined(LINUX) && !defined(LGI_SDL) /* if (Cairo) { Gtk::cairo_destroy(Cairo); Cairo = 0; } */ #endif DrawOnAlpha(false); DeleteObj(pMem); DeleteObj(pAlphaDC); if (pPalette && (Flags & GDC_OWN_PALETTE)) { DeleteObj(pPalette); } if ( (Flags & GDC_OWN_APPLICATOR) && !(Flags & GDC_CACHED_APPLICATOR)) { DeleteObj(pApp); } for (int i=0; iSetSmoothingMode(AntiAlias() ? Gdiplus::SmoothingModeAntiAlias : Gdiplus::SmoothingModeNone); return GdiplusGfx; } Gdiplus::Color LSurface::GdiColour() { COLOUR col = Colour(); switch (GetBits()) { default: LAssert(!"Impl me."); // fall through case 32: return Gdiplus::Color(A32(col), R32(col), G32(col), B32(col)); case 24: return Gdiplus::Color(R24(col), G24(col), B24(col)); } } #endif LString LSurface::GetStr() { LString::Array s; s.SetFixedLength(false); s.New().Printf("Size(%ix%i)", X(), Y()); s.New().Printf("ColourSpace(%s)", LColourSpaceToString(ColourSpace)); s.New().Printf("IsScreen(%i)", IsScreen()); if (pAlphaDC) s.New().Printf("pAlphaDC(%ix%i,%s)", pAlphaDC->X(), pAlphaDC->Y(), LColourSpaceToString(pAlphaDC->GetColourSpace())); if (Clip.Valid()) s.New().Printf("Clip(%s)", Clip.GetStr()); s.New().Printf("Op(%i)", Op()); s.New().Printf("GetRowStep(" LPrintfSizeT ")", GetRowStep()); return LString(" ").Join(s); } LSurface *LSurface::SubImage(LRect r) { if (!pMem || !pMem->Base) return NULL; LRect clip = r; LRect bounds = Bounds(); clip.Intersection(&bounds); if (!clip.Valid()) return NULL; LAutoPtr s(new LSurface); if (!s) return NULL; int BytePx = pMem->GetBits() >> 3; s->pMem = new LBmpMem; s->pMem->Base = pMem->Base + (pMem->Line * clip.y1) + (BytePx * clip.x1); s->pMem->x = clip.X(); s->pMem->y = clip.Y(); s->pMem->Line = pMem->Line; s->pMem->Cs = pMem->Cs; s->pMem->Flags = 0; // Don't own memory. s->Clip = s->Bounds(); s->ColourSpace = pMem->Cs; s->Op(GDC_SET); return s.Release(); } template void SetAlphaPm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->r = Lut[s->r * a]; s->g = Lut[s->g * a]; s->b = Lut[s->b * a]; s->a = Lut[s->a * a]; s++; } } template void SetAlphaNpm(Px *src, int x, uint8_t a) { REG uint8_t *Lut = Div255Lut; REG Px *s = src; REG Px *e = s + x; while (s < e) { s->a = Lut[s->a * a]; s++; } } bool LSurface::SetConstantAlpha(uint8_t Alpha) { bool HasAlpha = LColourSpaceHasAlpha(GetColourSpace()); if (!HasAlpha) return false; if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (pMem->PreMul()) { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaPm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } else { switch (pMem->Cs) { #define SetAlphaCase(px) \ case Cs##px: SetAlphaNpm((L##px*)src, pMem->x, Alpha); break SetAlphaCase(Rgba32); SetAlphaCase(Bgra32); SetAlphaCase(Argb32); SetAlphaCase(Abgr32); #undef SetAlphaCase default: LAssert(!"Unknown CS."); break; } } } return true; } OsBitmap LSurface::GetBitmap() { #if WINNATIVE return hBmp; #else return NULL; #endif } OsPainter LSurface::Handle() { #if WINNATIVE return hDC; #elif defined(__GTK_H__) return NULL; #else return 0; #endif } uchar *LSurface::operator[](int y) { if (pMem && pMem->Base && y >= 0 && y < pMem->y) { return pMem->Base + (pMem->Line * y); } return 0; } void LSurface::Set(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); pApp->Set(); Update(GDC_BITS_CHANGE); } } COLOUR LSurface::Get(int x, int y) { OrgXy(x, y); if (x >= Clip.x1 && y >= Clip.y1 && x <= Clip.x2 && y <= Clip.y2) { pApp->SetPtr(x, y); return pApp->Get(); } return (COLOUR)-1; } void LSurface::HLine(int x1, int x2, int y) { OrgXy(x1, y); OrgX(x2); 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) { pApp->SetPtr(x1, y); if (LineBits == LINE_SOLID) { pApp->Rectangle(x2 - x1 + 1, 1); Update(GDC_BITS_CHANGE); } else { for (; x1 <= x2; x1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncX(); } Update(GDC_BITS_CHANGE); } } } void LSurface::VLine(int x, int y1, int y2) { OrgXy(x, y1); OrgY(y2); 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) { pApp->SetPtr(x, y1); if (LineBits == LINE_SOLID) { pApp->VLine(y2 - y1 + 1); Update(GDC_BITS_CHANGE); } else { for (; y1 <= y2; y1++) { if (LineMask & LineBits) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; pApp->IncY(); } Update(GDC_BITS_CHANGE); } } } void LSurface::Line(int x1, int y1, int x2, int y2) { if (x1 == x2) { VLine(x1, y1, y2); } else if (y1 == y2) { HLine(x1, x2, y1); } else if (Clip.Valid()) { OrgXy(x1, y1); OrgXy(x2, y2); // angled if (y1 > y2) { LSwap(y1, y2); LSwap(x1, x2); } LRect Bound(x1, y1, x2, y2); Bound.Normal(); if (!Bound.Overlap(&Clip)) return; double m = (double) (y2-y1) / (x2-x1); double b = (double) y1 - (m*x1); int xt = (int) (((double)Clip.y1-b)/m); int xb = (int) (((double)Clip.y2-b)/m); if (y1 < Clip.y1) { x1 = xt; y1 = Clip.y1; } if (y2 > Clip.y2) { x2 = xb; y2 = Clip.y2; } int X1, X2; if (x1 < x2) { X1 = x1; X2 = x2; } else { X1 = x2; X2 = x1; } if (X1 < Clip.x1) { if (X2 < Clip.x1) return; if (x1 < Clip.x1) { y1 = (int) (((double) Clip.x1 * m) + b); x1 = Clip.x1; } else { y2 = (int) (((double) Clip.x1 * m) + b); x2 = Clip.x1; } } if (X2 > Clip.x2) { if (X1 > Clip.x2) return; if (x1 > Clip.x2) { y1 = (int) (((double) Clip.x2 * m) + b); x1 = Clip.x2; } else { y2 = (int) (((double) Clip.x2 * m) + b); x2 = Clip.x2; } } int dx = abs(x2-x1); int dy = abs(y2-y1); int EInc, ENoInc, E, Inc = 1; if (dy < dx) { // flat if (x1 > x2) { LSwap(x1, x2); LSwap(y1, y2); } if (y1 > y2) { Inc = -1; } EInc = dy + dy; E = EInc - dx; ENoInc = E - dx; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; x1<=x2; x1++) { pApp->Set(); if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } else { for (; x1<=x2; x1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncX(); E += EInc; } else { pApp->IncPtr(1, Inc); E += ENoInc; } } } } else { if (x1 > x2) { Inc = -1; } // steep EInc = dx + dx; E = EInc - dy; ENoInc = E - dy; pApp->SetPtr(x1, y1); if (LineBits == LINE_SOLID) { for (; y1<=y2; y1++) { pApp->Set(); if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } else { for (; y1<=y2; y1++) { if (LineBits & LineMask) { pApp->Set(); } LineMask >>= 1; if (!LineMask) LineMask = LineReset; if (E < 0) { pApp->IncY(); E += EInc; } else { pApp->IncPtr(Inc, 1); E += ENoInc; } } } } Update(GDC_BITS_CHANGE); } } void LSurface::Circle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0)-1, (Gdiplus::REAL)(radius*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - (2 * radius)); int x = 0; int y = (int) radius; Set(cx, cy + y); Set(cx, cy - y); Set(cx + y, cy); Set(cx - y, cy); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; while (x < y) { Set(cx + x, cy + y); Set(cx - x, cy + y); Set(cx + x, cy - y); Set(cx - x, cy - y); Set(cx + y, cy + x); Set(cx - y, cy + x); Set(cx + y, cy - x); Set(cx - y, cy - x); if (d < 0) { d += (4 * x) + 6; } else { d += (4 * (x - y)) + 10; y--; } x++; } if (x == y) { Set(cx + x, cy + x); Set(cx - x, cy + x); Set(cx + x, cy - x); Set(cx - x, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::FilledCircle(double Cx, double Cy, double radius) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radius), (Gdiplus::REAL)(Cy-radius), (Gdiplus::REAL)(radius*2.0-1.0), (Gdiplus::REAL)(radius*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif int cx = (int)Cx; int cy = (int)Cy; int d = (int) (3 - 2 * radius); int x = 0; int y = (int) radius; // HLine(cx + y, cx - y, cy); if (d < 0) { d += 4 * x + 6; } else { HLine(cx, cx, cy + y); d += 4 * (x - y) + 10; y--; } while (x < y) { HLine(cx + y, cx - y, cy + x); if (x != 0) HLine(cx + y, cx - y, cy - x); if (d < 0) { d += 4 * x + 6; } else { HLine(cx + x, cx - x, cy + y); HLine(cx + x, cx - x, cy - y); d += 4 * (x - y) + 10; y--; } x++; } if (x == y) { HLine(cx + y, cx - y, cy + x); HLine(cx + y, cx - y, cy - x); } Update(GDC_BITS_CHANGE); } void LSurface::Box(LRect *a) { if (a) { LRect b = *a; b.Normal(); if (b.x1 != b.x2 && b.y1 != b.y2) { HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } else { Set(b.x1, b.y1); } } else { LRect b(0, 0, X()-1, Y()-1); HLine(b.x1, b.x2 - 1, b.y1); VLine(b.x2, b.y1, b.y2 - 1); HLine(b.x1 + 1, b.x2, b.y2); VLine(b.x1, b.y1 + 1, b.y2); } } void LSurface::Box(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Box(&a); } void LSurface::Rectangle(LRect *a) { LRect b; if (a) b = a; else b.ZOff(pMem->x-1, pMem->y-1); OrgRgn(b); b.Normal(); b.Bound(&Clip); if (b.Valid()) { pApp->SetPtr(b.x1, b.y1); pApp->Rectangle(b.X(), b.Y()); Update(GDC_BITS_CHANGE); } } void LSurface::Rectangle(int x1, int y1, int x2, int y2) { LRect a(x1, y1, x2, y2); Rectangle(&a); } void LSurface::Ellipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); Gdiplus::Pen pen(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0)-1, (Gdiplus::REAL)(radiusY*2.0)-1); auto status = g->DrawEllipse(&pen, r); Update(GDC_BITS_CHANGE); return; } #endif #define incx() x++, dxt += d2xt, t += dxt #define incy() y--, dyt += d2yt, t += dyt int x = 0, y = (int)radiusY; int a = (int)radiusX; if (a % 2 == 0) a--; int b = (int)radiusY; int xc = (int)Cx; int yc = (int)Cy; long a2 = (long)(a*a), b2 = (long)(b*b); long crit1 = -(a2/4 + (int)a%2 + b2); long crit2 = -(b2/4 + b%2 + a2); long crit3 = -(b2/4 + b%2); long t = -a2*y; /* e(x+1/2,y-1/2) - (a^2+b^2)/4 */ long dxt = 2*b2*x, dyt = -2*a2*y; long d2xt = 2*b2, d2yt = 2*a2; if (a % 2) { // Odd version while (y>=0 && x<=a) { Set(xc+x, yc+y); if (x!=0 || y!=0) Set(xc-x, yc-y); if (x!=0 && y!=0) { Set(xc+x, yc-y); Set(xc-x, yc+y); } if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } else // even version { while (y>=0 && x<=a) { Set(xc+x+1, yc+y); Set(xc+x+1, yc-y); Set(xc-x, yc-y); Set(xc-x, yc+y); if (t + b2*x <= crit1 || /* e(x+1,y-1/2) <= 0 */ t + a2*y <= crit3) /* e(x+1/2,y) <= 0 */ incx(); else if (t - a2*y > crit2) /* e(x+1/2,y-1) > 0 */ incy(); else { incx(); incy(); } } } Update(GDC_BITS_CHANGE); } void LSurface::FilledEllipse(double Cx, double Cy, double radiusX, double radiusY) { #if defined WINNATIVE if (Op() == GDC_SET && GetBits() > 8) { auto g = GetGfx(); auto col = Colour(); Gdiplus::SolidBrush brush(GdiColour()); Gdiplus::RectF r((Gdiplus::REAL)(Cx-radiusX), (Gdiplus::REAL)(Cy-radiusY), (Gdiplus::REAL)(radiusX*2.0-1.0), (Gdiplus::REAL)(radiusY*2.0-1.0)); if (!AntiAlias()) r.Inflate(1.0f, 1.0f); auto status = g->FillEllipse(&brush, r); Update(GDC_BITS_CHANGE); return; } #endif // TODO: fix this primitive for odd widths and heights int cx = (int)Cx; int cy = (int)Cy; /* a = floor(a); b = floor(b); */ long aSq = (long) (radiusX * radiusX); long bSq = (long) (radiusY * radiusY); long two_aSq = aSq+aSq; long two_bSq = bSq+bSq; long x=0, y=(long)radiusY, two_xBsq = 0, two_yAsq = y * two_aSq, error = -y * aSq; if (aSq && bSq && error) { while (two_xBsq <= two_yAsq) if (Op() == GDC_SET) { x++; two_xBsq += two_bSq; error += two_xBsq - bSq; if (error >= 0) { Line((int) (cx+x-1), (int) (cy+y), (int) (cx-x+1), (int) (cy+y)); if (y != 0) Line((int) (cx+x-1), (int) (cy-y), (int) (cx-x+1), (int) (cy-y)); y--; two_yAsq -= two_aSq; error -= two_yAsq; } } x=(long)radiusX; y=0; two_xBsq = x * two_bSq; two_yAsq = 0; error = -x * bSq; while (two_xBsq > two_yAsq) { Line( (int) (cx+x), (int) (cy+y), (int) (cx-x), (int) (cy+y)); if (y != 0) Line((int) (cx+x), (int) (cy-y), (int) (cx-x), (int) (cy-y)); y++; two_yAsq += two_aSq; error += two_yAsq - aSq; if (error >= 0) { x--; two_xBsq -= two_bSq; error -= two_xBsq; } } } Update(GDC_BITS_CHANGE); } struct EDGE { int yMin, yMax, x, dWholeX, dX, dY, frac; }; int nActive, nNextEdge; void LSurface::Polygon(int nPoints, LPoint *aPoints) { LPoint p0, p1; int i, j, gap, x0, x1, y, nEdges; EDGE *ET, **GET, **AET; /******************************************************************** * Add entries to the global edge table. The global edge table has a * bucket for each scan line in the polygon. Each bucket contains all * the edges whose yMin == yScanline. Each bucket contains the yMax, * the x coordinate at yMax, and the denominator of the slope (dX) */ // allocate the tables ET = new EDGE[nPoints]; GET = new EDGE*[nPoints]; AET = new EDGE*[nPoints]; if (ET && GET && AET) { for ( i = 0, nEdges = 0; i < nPoints; i++) { p0 = aPoints[i]; p1 = aPoints[(i+1) % nPoints]; // ignore if this is a horizontal edge if (p0.y == p1.y) continue; // swap points if necessary to ensure p0 contains yMin if (p0.y > p1.y) { p0 = p1; p1 = aPoints[i]; } // create the new edge ET[nEdges].yMin = p0.y; ET[nEdges].yMax = p1.y; ET[nEdges].x = p0.x; ET[nEdges].dX = p1.x - p0.x; ET[nEdges].dY = p1.y - p0.y; ET[nEdges].frac = 0; GET[nEdges] = &ET[nEdges]; nEdges++; } // sort the GET on yMin for (gap = 1; gap < nEdges; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nEdges; i++) for (j = i-gap; j >= 0; j -= gap) { if (GET[j]->yMin <= GET[j+gap]->yMin) break; EDGE *t = GET[j]; GET[j] = GET[j+gap]; GET[j+gap] = t; } // initialize the active edge table, and set y to first entering edge nActive = 0; nNextEdge = 0; y = GET[nNextEdge]->yMin; /* Now process the edges using the scan line algorithm. Active edges will be added to the Active Edge Table (AET), and inactive edges will be deleted. X coordinates will be updated with incremental integer arithmetic using the slope (dY / dX) of the edges. */ while (nNextEdge < nEdges || nActive) { /* Move from the ET bucket y to the AET those edges whose yMin == y (entering edges) */ while (nNextEdge < nEdges && GET[nNextEdge]->yMin == y) AET[nActive++] = GET[nNextEdge++]; /* Remove from the AET those entries for which yMax == y (leaving edges) */ i = 0; while (i < nActive) { if (AET[i]->yMax == y) memmove(&AET[i], &AET[i+1], sizeof(AET[0]) * (--nActive - i)); else i++; } /* Now sort the AET on x. Since the list is usually quite small, the sort is implemented as a simple non-recursive shell sort */ for (gap = 1; gap < nActive; gap = 3*gap + 1); for (gap /= 3; gap > 0; gap /= 3) for (i = gap; i < nActive; i++) for (j = i-gap; j >= 0; j -= gap) { if (AET[j]->x <= AET[j+gap]->x) break; EDGE *t = AET[j]; AET[j] = AET[j+gap]; AET[j+gap] = t; } /* Fill in desired pixels values on scan line y by using pairs of x coordinates from the AET */ for (i = 0; i < nActive; i += 2) { x0 = AET[i]->x; x1 = AET[i+1]->x; /* Left edge adjustment for positive fraction. 0 is interior. */ if (AET[i]->frac > 0) x0++; // Right edge adjustment for negative fraction. 0 is exterior. */ if (AET[i+1]->frac <= 0) x1--; // Draw interior spans if (x1 >= x0) HLine(x0, x1, y); } /* Update all the x coordinates. Edges are scan converted using a modified midpoint algorithm (Bresenham's algorithm reduces to the midpoint algorithm for two dimensional lines) */ for (i = 0; i < nActive; i++) { EDGE *e = AET[i]; // update the fraction by dX e->frac += e->dX; if (e->dX < 0) while ( -(e->frac) >= e->dY) { e->frac += e->dY; e->x--; } else while (e->frac >= e->dY) { e->frac -= e->dY; e->x++; } } y++; } } // Release tables DeleteArray(ET); DeleteArray(GET); DeleteArray(AET); } void LSurface::Blt(int x, int y, LSurface *Src, LRect *a) { OrgXy(x, y); if (!Src || !Src->pMem || !Src->pMem->Base) return; LRect S; if (a) S = *a; else S.ZOff(Src->X()-1, Src->Y()-1); S.Offset(Src->OriginX, Src->OriginY); LRect SClip = S; SClip.Bound(&Src->Clip); if (!SClip.Valid()) return; LRect D = SClip; D.Offset(x-S.x1, y-S.y1); LRect DClip = D; DClip.Bound(&Clip); LRect Re = DClip; Re.Offset(S.x1-x, S.y1-y); SClip.Bound(&Re); if (!DClip.Valid() || !SClip.Valid()) return; LBmpMem Bits, Alpha; Bits.Base = Src->pMem->AddressOf(SClip.x1, SClip.y1); Bits.x = MIN(SClip.X(), DClip.X()); Bits.y = MIN(SClip.Y(), DClip.Y()); Bits.Line = Src->pMem->Line; Bits.Cs = Src->GetColourSpace(); Bits.PreMul(Src->pMem->PreMul()); if (Src->pAlphaDC && !Src->DrawOnAlpha()) { auto ASurface = Src->pAlphaDC->pMem; Alpha = Bits; Alpha.Cs = CsIndex8; Alpha.Line = ASurface->Line; Alpha.Base = ASurface->Base + (SClip.y1 * ASurface->Line) + (SClip.x1); } pApp->SetPtr(DClip.x1, DClip.y1); LPalette *SrcPal = Src->DrawOnAlpha() ? NULL : Src->Palette(); // printf("\t%p::Blt pApp=%p, %s\n", this, pApp, pApp->GetClass()); pApp->Blt(&Bits, SrcPal, Alpha.Base ? &Alpha : NULL); Update(GDC_BITS_CHANGE); if (pApp->GetFlags() & GDC_UPDATED_PALETTE) { Palette(new LPalette(pApp->GetPal())); } } #define sqr(a) ((a)*(a)) #define Distance(a, b) (sqrt(sqr(a.x-b.x)+sqr(a.y-b.y))) class FPt2 { public: double x, y; }; typedef FPt2 BPt; void LSurface::Bezier(int Threshold, LPoint *Pt) { if (Pt) { int OldPts = 3; int NewPts = 0; BPt *BufA = new BPt[1024]; BPt *BufB = new BPt[1024]; BPt *Old = BufA; BPt *New = BufB; Threshold = MAX(Threshold, 1); if (!Old || !New) return; for (int n=0; n Threshold); if (Threshold > 1) { for (int i=0; i= Size) { SetSize(Size+1024); } if (Stack) { Stack[Used].x = x; Stack[Used].y = y; Used++; } } void Pop(int &x, int &y) { if (Stack && Used > 0) { Used--; x = Stack[Used].x; y = Stack[Used].y; } } }; // This should return true if 'Pixel' is in the region being filled. typedef bool (*FillMatchProc)(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits); bool FillMatch_Diff(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { return Seed == Pixel; } bool FillMatch_Near(COLOUR Seed, COLOUR Pixel, COLOUR Border, int Bits) { COLOUR s24 = CBit(24, Seed, Bits); COLOUR p24 = CBit(24, Pixel, Bits); int Dr = R24(s24) - R24(p24); int Dg = G24(s24) - G24(p24); int Db = B24(s24) - B24(p24); return ((unsigned)abs(Dr) < Border) && ((unsigned)abs(Dg) < Border) && ((unsigned)abs(Db) < Border); } void LSurface::FloodFill(int StartX, int StartY, int Mode, COLOUR Border, LRect *FillBounds) { COLOUR Seed = Get(StartX, StartY); if (Seed == 0xffffffff) return; // Doesn't support get pixel PointStack Ps; LRect Bounds; FillMatchProc Proc = 0; int Bits = GetBits(); Bounds.x1 = X(); Bounds.y1 = Y(); Bounds.x2 = 0; Bounds.y2 = 0; Ps.Push(StartX, StartY); switch (Mode) { case GDC_FILL_TO_DIFFERENT: { Proc = FillMatch_Diff; break; } case GDC_FILL_TO_BORDER: { break; } case GDC_FILL_NEAR: { Proc = FillMatch_Near; break; } } if (Proc) { COLOUR Start = Colour(); if (!Proc(Seed, Start, Border, Bits)) { while (Ps.GetSize() > 0) { bool Above = true; bool Below = true; int Ox, Oy; Ps.Pop(Ox, Oy); int x = Ox, y = Oy; // move right loop COLOUR c = Get(x, y); while (x < X() && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x++; c = Get(x, y); } // move left loop x = Ox; Above = !((y > 0) && (Get(x, y - 1) == Seed)); Below = !((y < Y() - 1) && (Get(x, y + 1) == Seed)); x--; c = Get(x, y); while (x >= 0 && Proc(Seed, c, Border, Bits)) { Set(x, y); Bounds.Union(x, y); if (y > 0) { c = Get(x, y - 1); if (Above) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y - 1); Above = false; } } else if (!Proc(Seed, c, Border, Bits)) { Above = true; } } if (y < Y() - 1) { c = Get(x, y + 1); if (Below) { if (Proc(Seed, c, Border, Bits)) { Ps.Push(x, y + 1); Below = false; } } else if (!Proc(Seed, c, Border, Bits)) { Below = true; } } x--; c = Get(x, y); } } } } if (FillBounds) { *FillBounds = Bounds; } Update(GDC_BITS_CHANGE); } void LSurface::Arc(double cx, double cy, double radius, double start, double end) {} void LSurface::FilledArc(double cx, double cy, double radius, double start, double end) {} void LSurface::StretchBlt(LRect *d, LSurface *Src, LRect *s) {} bool LSurface::AntiAlias() { return (Flags & GDC_ANTI_ALIAS) != 0; } bool LSurface::AntiAlias(bool antiAlias) { if (antiAlias) Flags |= GDC_ANTI_ALIAS; else Flags &= ~GDC_ANTI_ALIAS; return true; } bool LSurface::HasAlpha(bool b) { DrawOnAlpha(false); if (b) { if (!pAlphaDC) { pAlphaDC = new LMemDC; } if (pAlphaDC && pMem) { if (!pAlphaDC->Create(pMem->x, pMem->y, CsIndex8)) { DeleteObj(pAlphaDC); } else { ClearFlag(Flags, GDC_DRAW_ON_ALPHA); } } } else { DeleteObj(pAlphaDC); } return (b == HasAlpha()); } bool LSurface::DrawOnAlpha(bool Draw) { bool Prev = DrawOnAlpha(); bool Swap = false; if (Draw) { if (!Prev && pAlphaDC && pMem) { Swap = true; SetFlag(Flags, GDC_DRAW_ON_ALPHA); PrevOp = Op(GDC_SET); } } else { if (Prev && pAlphaDC && pMem) { Swap = true; ClearFlag(Flags, GDC_DRAW_ON_ALPHA); Op(PrevOp); } } if (Swap) { LBmpMem *Temp = pMem; pMem = pAlphaDC->pMem; pAlphaDC->pMem = Temp; #if WINNATIVE OsBitmap hTmp = hBmp; hBmp = pAlphaDC->hBmp; pAlphaDC->hBmp = hTmp; OsPainter hP = hDC; hDC = pAlphaDC->hDC; pAlphaDC->hDC = hP; #endif } return Prev; } LApplicator *LSurface::CreateApplicator(int Op, LColourSpace Cs) { LApplicator *pA = NULL; if (!Cs) { if (pMem) { if (DrawOnAlpha()) { Cs = CsIndex8; } else if (pMem->Cs) { Cs = pMem->Cs; } else { LAssert(!"Memory context has no colour space..."); } } else if (IsScreen()) { Cs = GdcD->GetColourSpace(); } else { LAssert(!"No memory context to read colour space from."); } } pA = LApplicatorFactory::NewApp(Cs, Op); if (pA) { if (DrawOnAlpha()) { pA->SetSurface(pMem); } else { pA->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } pA->SetOp(Op); } else { LApplicatorFactory::NewApp(Cs, Op); const char *CsStr = LColourSpaceToString(Cs); LgiTrace("Error: GDeviceContext::CreateApplicator(%i, %x, %s) failed.\n", Op, Cs, CsStr); LAssert(!"No applicator"); } return pA; } bool LSurface::Applicator(LApplicator *pApplicator) { bool Status = false; if (pApplicator) { if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp) Flags &= ~GDC_OWN_APPLICATOR; } Flags &= ~GDC_CACHED_APPLICATOR; pApp = pApplicator; if (DrawOnAlpha()) { pApp->SetSurface(pMem); } else { pApp->SetSurface(pMem, pPalette, pAlphaDC->pMem); } pApp->SetPtr(0, 0); Status = true; } return Status; } LApplicator *LSurface::Applicator() { return pApp; } LRect LSurface::ClipRgn(LRect *Rgn) { LRect Old = Clip; if (Rgn) { Clip.x1 = MAX(0, Rgn->x1 - OriginX); Clip.y1 = MAX(0, Rgn->y1 - OriginY); Clip.x2 = MIN(X()-1, Rgn->x2 - OriginX); Clip.y2 = MIN(Y()-1, Rgn->y2 - OriginY); } else { Clip.x1 = 0; Clip.y1 = 0; Clip.x2 = X()-1; Clip.y2 = Y()-1; } return Old; } LRect LSurface::ClipRgn() { return Clip; } LColour LSurface::Colour(LSystemColour SysCol) { return Colour(LColour(SysCol)); } LColour LSurface::Colour(LColour c) { LAssert(pApp != NULL); LColour cPrev; uint32_t c32 = c.c32(); LColourSpace Cs = pApp->GetColourSpace(); switch (Cs) { case CsIndex8: { cPrev.Set(pApp->c, 8); if (c.GetColourSpace() == CsIndex8) { pApp->c = c.c8(); } else { LPalette *p = Palette(); if (p) // Colour pApp->c = p->MatchRgb(Rgb32To24(c32)); else // Grey scale pApp->c = c.GetGray(); } break; } case CsRgb15: case CsBgr15: { cPrev.Set(pApp->c, 15); pApp->c = Rgb32To15(c32); break; } case CsRgb16: case CsBgr16: { cPrev.Set(pApp->c, 16); pApp->c = Rgb32To16(c32); break; } case CsRgba32: case CsBgra32: case CsArgb32: case CsAbgr32: case CsRgbx32: case CsBgrx32: case CsXrgb32: case CsXbgr32: case CsRgba64: case CsBgra64: case CsArgb64: case CsAbgr64: { cPrev.Rgb(pApp->p32.r, pApp->p32.g, pApp->p32.b, pApp->p32.a); pApp->p32.r = R32(c32); pApp->p32.g = G32(c32); pApp->p32.b = B32(c32); pApp->p32.a = A32(c32); break; } case CsRgb24: case CsBgr24: case CsRgb48: case CsBgr48: { cPrev.Rgb(pApp->p24.r, pApp->p24.g, pApp->p24.b); pApp->p24.r = R32(c32); pApp->p24.g = G32(c32); pApp->p24.b = B32(c32); break; } default: LAssert(0); break; } #if defined LGI_CARBON { // Update the current colour of the drawing context if present. OsPainter Hnd = Handle(); if (Hnd) { float r = (float)c.r()/255.0; float g = (float)c.g()/255.0; float b = (float)c.b()/255.0; float a = (float)c.a()/255.0; CGContextSetRGBFillColor(Hnd, r, g, b, a); CGContextSetRGBStrokeColor(Hnd, r, g, b, a); } } #endif return cPrev; } COLOUR LSurface::Colour(COLOUR c, int Bits) { LColour n(c, Bits ? Bits : GetBits()); LColour Prev = Colour(n); switch (GetBits()) { case 8: return Prev.c8(); case 24: return Prev.c24(); case 32: return Prev.c32(); } return Prev.c32(); } int LSurface::Op(int NewOp, NativeInt Param) { int PrevOp = (pApp) ? pApp->GetOp() : GDC_SET; if (!pApp || PrevOp != NewOp) { COLOUR cCurrent = (pApp) ? Colour() : 0; if (Flags & GDC_OWN_APPLICATOR) { DeleteObj(pApp); } if (NewOp < GDC_CACHE_SIZE && !DrawOnAlpha()) { pApp = (pAppCache[NewOp]) ? pAppCache[NewOp] : pAppCache[NewOp] = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_OWN_APPLICATOR; Flags |= GDC_CACHED_APPLICATOR; } else { pApp = CreateApplicator(NewOp, GetColourSpace()); Flags &= ~GDC_CACHED_APPLICATOR; Flags |= GDC_OWN_APPLICATOR; } if (pApp) { Colour(cCurrent); if (Param >= 0) pApp->SetVar(GAPP_ALPHA_A, Param); } else { printf("Error: Couldn't create applicator, Op=%i\n", NewOp); LAssert(0); } } return PrevOp; } LPalette *LSurface::Palette() { if (!pPalette && pMem && (pMem->Flags & GDC_ON_SCREEN) && pMem->Cs == CsIndex8) { pPalette = GdcD->GetSystemPalette(); // printf("Setting sys palette: %p\n", pPalette); if (pPalette) { Flags &= ~GDC_OWN_PALETTE; } } return pPalette; } void LSurface::Palette(LPalette *pPal, bool bOwnIt) { if (pPal == pPalette) { LgiTrace("%s:%i - Palette setting itself.\n", _FL); return; } // printf("LSurface::Palette %p %i\n", pPal, bOwnIt); if (pPalette && (Flags & GDC_OWN_PALETTE) != 0) { // printf("\tdel=%p\n", pPalette); delete pPalette; } pPalette = pPal; if (pPal && bOwnIt) { Flags |= GDC_OWN_PALETTE; // printf("\tp=%p own\n", pPalette); } else { Flags &= ~GDC_OWN_PALETTE; // printf("\tp=%p not-own\n", pPalette); } if (pApp) { pApp->SetSurface(pMem, pPalette, (pAlphaDC) ? pAlphaDC->pMem : 0); } } bool LSurface::GetVariant(const char *Name, LVariant &Dst, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceX: // Type: Int32 { Dst = X(); break; } case SurfaceY: // Type: Int32 { Dst = Y(); break; } case SurfaceBits: // Type: Int32 { Dst = GetBits(); break; } case SurfaceColourSpace: // Type: Int32 { Dst = GetColourSpace(); break; } case SurfaceIncludeCursor: // Type: Int32 { Dst = TestFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: { return false; } } return true; } bool LSurface::SetVariant(const char *Name, LVariant &Value, const char *Array) { switch (LStringToDomProp(Name)) { case SurfaceIncludeCursor: { if (Value.CastInt32()) SetFlag(Flags, GDC_CAPTURE_CURSOR); else ClearFlag(Flags, GDC_CAPTURE_CURSOR); break; } default: break; } return false; } -bool LSurface::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) +bool LSurface::CallMethod(const char *Name, LScriptArguments &Args) { return false; } bool LSurface::IsPreMultipliedAlpha() { return pMem ? pMem->PreMul() : false; } template void ConvertToPreMul(Px *src, int x) { REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { s->r = DivLut[s->r * s->a]; s->g = DivLut[s->g * s->a]; s->b = DivLut[s->b * s->a]; s++; } } template void ConvertFromPreMul(Px *src, int x) { // REGISTER uchar *DivLut = Div255Lut; REGISTER Px *s = src; REGISTER Px *e = s + x; while (s < e) { if (s->a > 0 && s->a < 255) { s->r = (int) s->r * 255 / s->a; s->g = (int) s->g * 255 / s->a; s->b = (int) s->b * 255 / s->a; } s++; } } bool LSurface::ConvertPreMulAlpha(bool ToPreMul) { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); if (ToPreMul) { switch (pMem->Cs) { #define ToPreMulCase(px) \ case Cs##px: ConvertToPreMul((L##px*)src, pMem->x); break ToPreMulCase(Rgba32); ToPreMulCase(Bgra32); ToPreMulCase(Argb32); ToPreMulCase(Abgr32); #undef ToPreMulCase default: break; } } else { switch (pMem->Cs) { #define FromPreMulCase(px) \ case Cs##px: ConvertFromPreMul((L##px*)src, pMem->x); break FromPreMulCase(Rgba32); FromPreMulCase(Bgra32); FromPreMulCase(Argb32); FromPreMulCase(Abgr32); #undef FromPreMulCase default: break; } } } pMem->PreMul(ToPreMul); return true; } template void MakeOpaqueRop32(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xff; s++; } } template void MakeOpaqueRop64(Px *in, int len) { REGISTER Px *s = in; REGISTER Px *e = s + len; while (s < e) { s->a = 0xffff; s++; } } bool LSurface::MakeOpaque() { if (!pMem || !pMem->Base) return false; for (int y=0; yy; y++) { uint8_t *src = pMem->Base + (y * pMem->Line); switch (pMem->Cs) { #define OpaqueCase(px, sz) \ case Cs##px: MakeOpaqueRop##sz((L##px*)src, pMem->x); break OpaqueCase(Rgba32, 32); OpaqueCase(Bgra32, 32); OpaqueCase(Argb32, 32); OpaqueCase(Abgr32, 32); OpaqueCase(Rgba64, 64); OpaqueCase(Bgra64, 64); OpaqueCase(Argb64, 64); OpaqueCase(Abgr64, 64); #undef OpaqueCase default: break; } } return true; } diff --git a/src/common/General/FileCommon.cpp b/src/common/General/FileCommon.cpp --- a/src/common/General/FileCommon.cpp +++ b/src/common/General/FileCommon.cpp @@ -1,394 +1,395 @@ // // GFileCommon.cpp // // Created by Matthew Allen on 4/05/14. // Copyright (c) 2014 Memecode. All rights reserved. // #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" ///////////////////////////////////////////////////////////////////////////////// bool LFileSystem::SetCurrentFolder(const char *PathName) { #ifdef WINDOWS bool Status = false; LAutoWString w(Utf8ToWide(PathName)); if (w) Status = ::SetCurrentDirectoryW(w) != 0; return Status; #else return chdir(PathName) == 0; #endif } LString LFileSystem::GetCurrentFolder() { LString Cwd; #ifdef WINDOWS char16 w[DIR_PATH_SIZE+1]; if (::GetCurrentDirectoryW(DIR_PATH_SIZE, w) > 0) Cwd = w; #else char p[MAX_PATH_LEN]; if (getcwd(p, sizeof(p))) Cwd = p; #endif return Cwd; } ///////////////////////////////////////////////////////////////////////////////// bool LFile::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case ObjType: // Type: String Value = "File"; break; case ObjName: // Type: String Value = GetName(); break; case ObjLength: // Type: Int64 Value = GetSize(); break; case FilePos: // Type: Int64 Value = GetPos(); break; default: return false; } return true; } bool LFile::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case ObjLength: SetSize(Value.CastInt64()); break; case FilePos: SetPos(Value.CastInt64()); break; default: return false; } return true; } -bool LFile::CallMethod(const char *Name, LVariant *Dst, LArray &Arg) +bool LFile::CallMethod(const char *Name, LScriptArguments &Args) { + auto Dst = Args.GetReturn(); LDomProperty p = LStringToDomProp(Name); switch (p) { case ObjLength: // Type: ([NewLength]) { - if (Arg.Length() == 1) - *Dst = SetSize(Arg[0]->CastInt64()); + if (Args.Length() == 1) + *Dst = SetSize(Args[0]->CastInt64()); else *Dst = GetSize(); break; } case FilePos: // Type: ([NewPosition]) { - if (Arg.Length() == 1) - *Dst = SetPos(Arg[0]->CastInt64()); + if (Args.Length() == 1) + *Dst = SetPos(Args[0]->CastInt64()); else *Dst = GetPos(); break; } case ObjType: { *Dst = "File"; break; } case FileOpen: // Type: (Path[, Mode]) { - if (Arg.Length() >= 1) + if (Args.Length() >= 1) { int Mode = O_READ; - if (Arg.Length() == 2) + if (Args.Length() == 2) { - char *m = Arg[1]->CastString(); + char *m = Args[1]->CastString(); if (m) { bool Rd = strchr(m, 'r') != NULL; bool Wr = strchr(m, 'w') != NULL; if (Rd && Wr) Mode = O_READWRITE; else if (Wr) Mode = O_WRITE; else Mode = O_READ; } } - *Dst = Open(Arg[0]->CastString(), Mode); + *Dst = Open(Args[0]->CastString(), Mode); } break; } case FileClose: { *Dst = Close(); break; } case FileRead: // Type: ([ReadLength[, ReadType = 0 - string, 1 - integer]]) { int64 RdLen = 0; int RdType = 0; // 0 - string, 1 - int Dst->Empty(); - switch (Arg.Length()) + switch (Args.Length()) { default: case 0: RdLen = GetSize() - GetPos(); break; case 2: - RdType = Arg[1]->CastInt32(); + RdType = Args[1]->CastInt32(); // fall thru case 1: - RdLen = Arg[0]->CastInt64(); + RdLen = Args[0]->CastInt64(); break; } if (RdType) { // Int type switch (RdLen) { case 1: { uint8_t i; if (Read(&i, sizeof(i)) == sizeof(i)) *Dst = i; break; } case 2: { uint16 i; if (Read(&i, sizeof(i)) == sizeof(i)) *Dst = i; break; } case 4: { uint32_t i; if (Read(&i, sizeof(i)) == sizeof(i)) *Dst = (int)i; break; } case 8: { int64 i; if (Read(&i, sizeof(i)) == sizeof(i)) *Dst = i; break; } } } else if (RdLen > 0) { // String type if ((Dst->Value.String = new char[RdLen + 1])) { ssize_t r = Read(Dst->Value.String, (int)RdLen); if (r > 0) { Dst->Type = GV_STRING; Dst->Value.String[r] = 0; } else { DeleteArray(Dst->Value.String); } } } else *Dst = -1; break; } case FileWrite: // Type: (Data[, WriteLength]) { LVariant *v; - if (Arg.Length() < 1 || - Arg.Length() > 2 || - !(v = Arg[0])) + if (Args.Length() < 1 || + Args.Length() > 2 || + !(v = Args[0])) { *Dst = 0; return true; } - switch (Arg.Length()) + switch (Args.Length()) { case 1: { // Auto-size write length to the variable. switch (v->Type) { case GV_INT32: *Dst = Write(&v->Value.Int, sizeof(v->Value.Int)); break; case GV_INT64: *Dst = Write(&v->Value.Int64, sizeof(v->Value.Int64)); break; case GV_STRING: *Dst = Write(&v->Value.String, strlen(v->Value.String)); break; case GV_WSTRING: *Dst = Write(&v->Value.WString, StrlenW(v->Value.WString) * sizeof(char16)); break; default: *Dst = 0; return true; } break; } case 2: { - int64 WrLen = Arg[1]->CastInt64(); + int64 WrLen = Args[1]->CastInt64(); switch (v->Type) { case GV_INT32: { if (WrLen == 1) { uint8_t i = v->Value.Int; *Dst = Write(&i, sizeof(i)); } else if (WrLen == 2) { uint16 i = v->Value.Int; *Dst = Write(&i, sizeof(i)); } else { *Dst = Write(&v->Value.Int, sizeof(v->Value.Int)); } break; } case GV_INT64: { if (WrLen == 1) { uint8_t i = (uint8_t) v->Value.Int64; *Dst = Write(&i, sizeof(i)); } else if (WrLen == 2) { uint16 i = (uint16) v->Value.Int64; *Dst = Write(&i, sizeof(i)); } else if (WrLen == 4) { uint32_t i = (uint32_t)v->Value.Int64; *Dst = Write(&i, sizeof(i)); } else { *Dst = Write(&v->Value.Int64, sizeof(v->Value.Int64)); } break; } case GV_STRING: { size_t Max = strlen(v->Value.String) + 1; *Dst = Write(&v->Value.String, MIN(Max, (size_t)WrLen)); break; } case GV_WSTRING: { size_t Max = (StrlenW(v->Value.WString) + 1) * sizeof(char16); *Dst = Write(&v->Value.WString, MIN(Max, (size_t)WrLen)); break; } default: { *Dst = 0; return true; } } break; } default: { *Dst = 0; return true; } } break; } default: return false; } return true; } const char *LGetLeaf(const char *Path) { if (!Path) return NULL; const char *l = NULL; for (const char *s = Path; *s; s++) { if (*s == '/' || *s == '\\') l = s; } return l ? l + 1 : Path; } char *LGetLeaf(char *Path) { if (!Path) return NULL; char *l = NULL; for (char *s = Path; *s; s++) { if (*s == '/' || *s == '\\') l = s; } return l ? l + 1 : Path; } LString LGetPhysicalDevice(const char *Path) { LString Ph; #ifdef WINDOWS LAutoWString w(Utf8ToWide(Path)); char16 VolPath[256]; if (GetVolumePathNameW(w, VolPath, CountOf(VolPath))) { char16 Name[256] = L"", FsName[256] = L""; DWORD VolumeSerialNumber = 0, MaximumComponentLength = 0, FileSystemFlags = 0; if (GetVolumeInformationW(VolPath, Name, CountOf(Name), &VolumeSerialNumber, &MaximumComponentLength, &FileSystemFlags, FsName, CountOf(FsName))) { if (VolumeSerialNumber) Ph.Printf("/volume/%x", VolumeSerialNumber); else Ph = VolPath; } } #else struct stat s; ZeroObj(s); if (!lstat(Path, &s)) { Ph.Printf("/dev/%i", s.st_dev); // Find dev in '/proc/partitions'? } #endif return Ph; } diff --git a/src/common/Lgi/MenuCommon.cpp b/src/common/Lgi/MenuCommon.cpp --- a/src/common/Lgi/MenuCommon.cpp +++ b/src/common/Lgi/MenuCommon.cpp @@ -1,64 +1,64 @@ // // LMenuCommon.cpp // LgiCarbon // // Created by Matthew Allen on 30/01/2018. // // #include "lgi/common/Lgi.h" #include "lgi/common/Menu.h" bool LSubMenu::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case ObjLength: Value = Length(); break; default: return false; } return true; } bool LSubMenu::SetVariant(const char *Name, LVariant &Value, const char *Array) { /* LDomProperty p = LStringToDomProp(Name); switch (p) { default: return false; } */ return false; } -bool LSubMenu::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) +bool LSubMenu::CallMethod(const char *MethodName, LScriptArguments &Args) { LDomProperty Method = LStringToDomProp(MethodName); switch (Method) { case ::AppendSeparator: { - *ReturnValue = AppendSeparator(); + *Args.GetReturn() = AppendSeparator(); break; } case ::AppendItem: { - char *Str = Args.Length() > 0 ? Args[0]->CastString() : NULL; - int Id = Args.Length() > 1 ? Args[1]->CastInt32() : -1; - bool Enabled = Args.Length() > 2 ? Args[2]->CastBool() : true; - int Where = Args.Length() > 3 ? Args[3]->CastInt32() : -1; - char *ShortCut = Args.Length() > 4 ? Args[4]->CastString() : NULL; + auto Str = Args.StringAt(0); + auto Id = Args.Int32At(1, -1); + auto Enabled = Args.Int32At(2, true); + auto Where = Args.Int32At(3, -1); + auto ShortCut = Args.StringAt(4); - *ReturnValue = AppendItem(Str, Id, Enabled, Where, ShortCut); + *Args.GetReturn() = AppendItem(Str, Id, Enabled, Where, ShortCut); break; } default: return false; } return true; } diff --git a/src/common/Lgi/Variant.cpp b/src/common/Lgi/Variant.cpp --- a/src/common/Lgi/Variant.cpp +++ b/src/common/Lgi/Variant.cpp @@ -1,2345 +1,2405 @@ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" const char *LVariant::TypeToString(LVariantType t) { switch (t) { case GV_NULL: return "NULL"; case GV_INT32: return "int32"; case GV_INT64: return "int64"; case GV_BOOL: return "bool"; case GV_DOUBLE: return "double"; case GV_STRING: return "String"; case GV_BINARY: return "Binary"; case GV_LIST: return "List"; case GV_DOM: return "Dom"; case GV_DOMREF: return "DomReference"; case GV_VOID_PTR: return "VoidPtr"; case GV_DATETIME: return "DateTime"; case GV_HASHTABLE: return "HashTable"; case GV_OPERATOR: return "Operator"; case GV_CUSTOM: return "Custom"; case GV_WSTRING: return "WString"; case GV_GVIEW: return "View"; case GV_STREAM: return "Stream"; case GV_LSURFACE: return "Surface"; case GV_LMOUSE: return "MouseEvent"; case GV_LKEY: return "KeyboardEvent"; default: return "Unknown"; } return NULL; } const char *LVariant::OperatorToString(LOperator op) { switch (op) { case OpNull: return "OpNull"; case OpAssign: return "OpAssign"; case OpPlus: return "OpPlus"; case OpUnaryPlus: return "OpUnaryPlus"; case OpMinus: return "OpMinus"; case OpUnaryMinus: return "OpUnaryMinus"; case OpMul: return "OpMul"; case OpDiv: return "OpDiv"; case OpMod: return "OpMod"; case OpLessThan: return "OpLessThan"; case OpLessThanEqual: return "OpLessThanEqual"; case OpGreaterThan: return "OpGreaterThan"; case OpGreaterThanEqual: return "OpGreaterThanEqual"; case OpEquals: return "OpEquals"; case OpNotEquals: return "OpNotEquals"; case OpPlusEquals: return "OpPlusEquals"; case OpMinusEquals: return "OpMinusEquals"; case OpMulEquals: return "OpMulEquals"; case OpDivEquals: return "OpDivEquals"; case OpPostInc: return "OpPostInc"; case OpPostDec: return "OpPostDec"; case OpPreInc: return "OpPreInc"; case OpPreDec: return "OpPreDec"; case OpAnd: return "OpAnd"; case OpOr: return "OpOr"; case OpNot: return "OpNot"; } return NULL; } LVariant::LVariant() { Type = GV_NULL; ZeroObj(Value); } LVariant::LVariant(LVariant const &v) { Type = GV_NULL; ZeroObj(Value); *this = v; } #if LVARIANT_SIZET LVariant::LVariant(size_t i) { Type = GV_NULL; *this = i; } #endif #if LVARIANT_SSIZET LVariant::LVariant(ssize_t i) { Type = GV_NULL; *this = i; } #endif LVariant::LVariant(int32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(uint32_t i) { Type = GV_INT32; Value.Int = i; } LVariant::LVariant(int64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(uint64_t i) { Type = GV_INT64; Value.Int64 = i; } LVariant::LVariant(double i) { Type = GV_DOUBLE; Value.Dbl = i; } LVariant::LVariant(const char *s) { Value.String = NewStr(s); Type = Value.String ? GV_STRING : GV_NULL; } LVariant::LVariant(const char16 *s) { Value.WString = NewStrW(s); Type = Value.WString ? GV_WSTRING : GV_NULL; } LVariant::LVariant(void *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p) { Type = GV_NULL; *this = p; } LVariant::LVariant(LDom *p, char *name) { Type = GV_NULL; SetDomRef(p, name); } LVariant::LVariant(const LDateTime *d) { Type = GV_NULL; *this = d; } LVariant::LVariant(LOperator Op) { Type = GV_OPERATOR; Value.Op = Op; } LVariant::~LVariant() { Empty(); } bool LVariant::operator ==(LVariant &v) { switch (Type) { default: case GV_NULL: return v.Type == Type; case GV_INT32: return Value.Int == v.CastInt32(); case GV_INT64: return Value.Int64 == v.CastInt64(); case GV_BOOL: return Value.Bool == v.CastBool(); case GV_DOUBLE: return Value.Dbl == v.CastDouble(); case GV_STRING: { char *s = v.Str(); if (Value.String && s) return !strcmp(Value.String, s); break; } case GV_WSTRING: { char16 *w = v.WStr(); if (Value.WString && w) return !StrcmpW(Value.WString, w); break; } case GV_BINARY: { if (v.Type == Type && Value.Binary.Data == v.Value.Binary.Data && Value.Binary.Length == v.Value.Binary.Length) { return true; } break; } case GV_LIST: { if (!Value.Lst || !v.Value.Lst) return false; if (Value.Lst->Length() != v.Value.Lst->Length()) return false; auto ValIt = Value.Lst->begin(); auto VIt = v.Value.Lst->begin(); LVariant *a, *b; while ( (a = *ValIt) && (b = *VIt) ) { if (!(*a == *b)) return false; ValIt++; VIt++; } return true; } case GV_DOMREF: { return Value.DomRef.Dom == v.Value.DomRef.Dom && Value.DomRef.Name != 0 && v.Value.DomRef.Name != 0 && !stricmp(Value.DomRef.Name, v.Value.DomRef.Name); } case GV_DATETIME: { if (Value.Date && v.Value.Date) { return Value.Date->Compare(v.Value.Date) == 0; } break; } case GV_DOM: return Value.Dom == v.Value.Dom; case GV_OPERATOR: return Value.Op == v.Value.Op; case GV_CUSTOM: return Value.Custom == v.Value.Custom; case GV_LSURFACE: return Value.Surface.Ptr == v.Value.Surface.Ptr; case GV_GVIEW: return Value.View == v.Value.View; /* case GV_GFILE: return Value.File.Ptr == v.Value.File.Ptr; */ case GV_STREAM: return Value.Stream.Ptr == v.Value.Stream.Ptr; case GV_LMOUSE: return Value.Mouse == v.Value.Mouse; case GV_LKEY: return Value.Key == v.Value.Key; case GV_VOID_PTR: return Value.Ptr == v.Value.Ptr; case GV_HASHTABLE: { LAssert(0); break; } } return false; } LVariant &LVariant::operator =(const LDateTime *d) { Empty(); if (d) { Type = GV_DATETIME; Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *d; // if (Dirty) *Dirty = true; } } return *this; } LVariant &LVariant::operator =(bool i) { Empty(); Type = GV_BOOL; Value.Bool = i; // if (Dirty) *Dirty = true; return *this; } #if LVARIANT_SIZET LVariant &LVariant::operator =(size_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif #if LVARIANT_SSIZET LVariant &LVariant::operator =(ssize_t i) { Empty(); #if LGI_64BIT Type = GV_INT64; Value.Int64 = i; #else Type = GV_INT32; Value.Int = i; #endif return *this; } #endif LVariant &LVariant::operator =(int32 i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(uint32_t i) { Empty(); Type = GV_INT32; Value.Int = i; return *this; } LVariant &LVariant::operator =(int64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(uint64_t i) { Empty(); Type = GV_INT64; Value.Int64 = i; return *this; } LVariant &LVariant::operator =(double i) { Empty(); Type = GV_DOUBLE; Value.Dbl = i; // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(const char *s) { Empty(); if (s) { Type = GV_STRING; Value.String = NewStr(s); } return *this; } LVariant &LVariant::operator =(const char16 *s) { Empty(); if (s) { Type = GV_WSTRING; Value.WString = NewStrW(s); } // if (Dirty) *Dirty = true; return *this; } LVariant &LVariant::operator =(void *p) { Empty(); if (p) { Type = GV_VOID_PTR; Value.Ptr = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LDom *p) { Empty(); if (p) { Type = GV_DOM; Value.Dom = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LView *p) { Empty(); if (p) { Type = GV_GVIEW; Value.View = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LMouse *p) { Empty(); if (p) { Type = GV_LMOUSE; Value.Mouse = p; // if (Dirty) *Dirty = true; } return *this; } LVariant &LVariant::operator =(LKey *p) { Empty(); if (p) { Type = GV_LKEY; Value.Key = p; } return *this; } LVariant &LVariant::operator =(LStream *s) { Empty(); if (s) { Type = GV_STREAM; Value.Stream.Ptr = s; Value.Stream.Own = false; } return *this; } LVariant &LVariant::operator =(LVariant const &i) { if (&i == this) return *this; Empty(); Type = i.Type; switch (Type) { case GV_NULL: { break; } case GV_INT32: { Value.Int = i.Value.Int; break; } case GV_BOOL: { Value.Bool = i.Value.Bool; break; } case GV_INT64: { Value.Int64 = i.Value.Int64; break; } case GV_DOUBLE: { Value.Dbl = i.Value.Dbl; break; } case GV_STRING: { Value.String = NewStr(((LVariant&)i).Str()); break; } case GV_WSTRING: { Value.WString = NewStrW(i.Value.WString); break; } case GV_BINARY: { SetBinary(i.Value.Binary.Length, i.Value.Binary.Data); break; } case GV_LIST: { SetList(i.Value.Lst); break; } case GV_DOM: { Value.Dom = i.Value.Dom; break; } case GV_VOID_PTR: case GV_GVIEW: case GV_LMOUSE: case GV_LKEY: { Value.Ptr = i.Value.Ptr; break; } case GV_DATETIME: { if (i.Value.Date) { Value.Date = new LDateTime; if (Value.Date) { *Value.Date = *i.Value.Date; } } break; } case GV_HASHTABLE: { if ((Value.Hash = new LHash)) { if (i.Value.Hash) { // const char *k; // for (LVariant *var = i.Value.Hash->First(&k); var; var = i.Value.Hash->Next(&k)) for (auto it : *i.Value.Hash) { Value.Hash->Add(it.key, new LVariant(*it.value)); } } } break; } case GV_CUSTOM: { Value.Custom.Data = i.Value.Custom.Data; Value.Custom.Dom = i.Value.Custom.Dom; break; } case GV_LSURFACE: { Value.Surface = i.Value.Surface; if (Value.Surface.Own && Value.Surface.Ptr) Value.Surface.Ptr->IncRef(); break; } /* case GV_GFILE: { Value.File = i.Value.File; if (Value.File.Own && Value.File.Ptr) Value.File.Ptr->AddRef(); break; } */ case GV_STREAM: { Value.Stream.Ptr = i.Value.Stream.Ptr; Value.Stream.Own = false; break; } default: { printf("%s:%i - Unknown variant type '%i'\n", _FL, Type); LAssert(0); break; } } // if (Dirty) *Dirty = true; return *this; } bool LVariant::SetDomRef(LDom *obj, char *name) { Empty(); Type = GV_DOMREF; Value.DomRef.Dom = obj; Value.DomRef.Name = NewStr(name); return Value.DomRef.Name != 0; } bool LVariant::SetBinary(ssize_t Len, void *Data, bool Own) { bool Status = false; Empty(); Type = GV_BINARY; Value.Binary.Length = Len; if (Own) { Value.Binary.Data = Data; Status = true; } else { if ((Value.Binary.Data = new uchar[Value.Binary.Length])) { if (Data) memcpy(Value.Binary.Data, Data, Value.Binary.Length); else memset(Value.Binary.Data, 0, Value.Binary.Length); Status = true; } } return Status; } List *LVariant::SetList(List *Lst) { Empty(); Type = GV_LIST; if ((Value.Lst = new List) && Lst) { for (auto s: *Lst) { LVariant *New = new LVariant; if (New) { *New = *s; Value.Lst->Insert(New); } } } return Value.Lst; } bool LVariant::SetHashTable(LHash *Table, bool Copy) { Empty(); Type = GV_HASHTABLE; if (Copy && Table) { if ((Value.Hash = new LHash)) { // const char *k; // for (LVariant *p = Table->First(&k); p; p = Table->Next(&k)) for (auto i : *Table) { Value.Hash->Add(i.key, i.value); } } } else { Value.Hash = Table ? Table : new LHash; } return Value.Hash != 0; } bool LVariant::SetSurface(class LSurface *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_LSURFACE; Value.Surface.Ptr = Ptr; if ((Value.Surface.Own = Own)) Value.Surface.Ptr->IncRef(); return true; } bool LVariant::SetStream(class LStreamI *Ptr, bool Own) { Empty(); if (!Ptr) return false; Type = GV_STREAM; Value.Stream.Ptr = Ptr; Value.Stream.Own = Own; return true; } bool LVariant::OwnStr(char *s) { Empty(); if (!s) return false; Type = GV_STRING; Value.String = s; return true; } bool LVariant::OwnStr(char16 *w) { Empty(); if (!w) return false; Type = GV_WSTRING; Value.WString = w; return true; } char *LVariant::ReleaseStr() { char *Ret = Str(); if (Ret) { Value.String = 0; Type = GV_NULL; } return Ret; } LString LVariant::LStr() { return Str(); } char *LVariant::Str() { if (Type == GV_STRING) return Value.String; if (Type == GV_WSTRING) { char *u = WideToUtf8(Value.WString); DeleteArray(Value.WString); Type = GV_STRING; return Value.String = u; } return 0; } char16 *LVariant::ReleaseWStr() { char16 *Ret = WStr(); if (Ret) { Value.WString = 0; Type = GV_NULL; } return Ret; } char16 *LVariant::WStr() { if (Type == GV_WSTRING) return Value.WString; if (Type == GV_STRING) { char16 *w = Utf8ToWide(Value.String); DeleteArray(Value.String); Type = GV_WSTRING; return Value.WString = w; } return 0; } void LVariant::Empty() { switch (Type) { default: break; case GV_CUSTOM: { Value.Custom.Data = 0; Value.Custom.Dom = 0; break; } case GV_DOMREF: { DeleteArray(Value.DomRef.Name); Value.DomRef.Dom = 0; break; } case GV_STRING: { DeleteArray(Value.String); break; } case GV_WSTRING: { DeleteArray(Value.WString); break; } case GV_BINARY: { char *d = (char*) Value.Binary.Data; DeleteArray(d); Value.Binary.Data = 0; break; } case GV_DATETIME: { DeleteObj(Value.Date); break; } case GV_LIST: { if (Value.Lst) { Value.Lst->DeleteObjects(); DeleteObj(Value.Lst); } break; } case GV_HASHTABLE: { if (Value.Hash) { // for (LVariant *v = (LVariant*) Value.Hash->First(); v; v = (LVariant*) Value.Hash->Next()) for (auto i : *Value.Hash) { DeleteObj(i.value); } DeleteObj(Value.Hash); } break; } case GV_LSURFACE: { if (Value.Surface.Own && Value.Surface.Ptr) { Value.Surface.Ptr->DecRef(); Value.Surface.Ptr = NULL; } break; } /* case GV_GFILE: { if (Value.File.Ptr && Value.File.Own) { Value.File.Ptr->DecRef(); Value.File.Ptr = NULL; } break; } */ case GV_STREAM: { if (Value.Stream.Ptr) { if (Value.Stream.Own) delete Value.Stream.Ptr; Value.Stream.Ptr = NULL; } break; } } Type = GV_NULL; ZeroObj(Value); } int64 LVariant::Length() { switch (Type) { case GV_INT32: return sizeof(Value.Int); case GV_INT64: return sizeof(Value.Int64); case GV_BOOL: return sizeof(Value.Bool); case GV_DOUBLE: return sizeof(Value.Dbl); case GV_STRING: return Value.String ? strlen(Value.String) : 0; case GV_BINARY: return Value.Binary.Length; case GV_LIST: { int64 Sz = 0; if (Value.Lst) { for (auto v : *Value.Lst) Sz += v->Length(); } return Sz; } case GV_DOM: { LVariant v; if (Value.Dom) Value.Dom->GetValue("length", v); return v.CastInt32(); } case GV_DOMREF: break; case GV_VOID_PTR: return sizeof(Value.Ptr); case GV_DATETIME: return sizeof(*Value.Date); case GV_HASHTABLE: { int64 Sz = 0; if (Value.Hash) { // for (LVariant *v=Value.Hash->First(); v; v=Value.Hash->Next()) for (auto i : *Value.Hash) Sz += i.value->Length(); } return Sz; } case GV_OPERATOR: return sizeof(Value.Op); case GV_CUSTOM: break; case GV_WSTRING: return Value.WString ? StrlenW(Value.WString) * sizeof(char16) : 0; case GV_LSURFACE: { int64 Sz = 0; if (Value.Surface.Ptr) { LRect r = Value.Surface.Ptr->Bounds(); int Bytes = Value.Surface.Ptr->GetBits() >> 3; Sz = r.X() * r.Y() * Bytes; } return Sz; } case GV_GVIEW: return sizeof(LView); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_STREAM: return Value.Stream.Ptr->GetSize(); default: break; } return 0; } bool LVariant::IsInt() { return Type == GV_INT32 || Type == GV_INT64; } bool LVariant::IsBool() { return Type == GV_BOOL; } bool LVariant::IsDouble() { return Type == GV_DOUBLE; } bool LVariant::IsString() { return Type == GV_STRING; } bool LVariant::IsBinary() { return Type == GV_BINARY; } bool LVariant::IsNull() { return Type == GV_NULL; } #define IsList() (Type == GV_LIST && Value.Lst) LVariant &LVariant::Cast(LVariantType NewType) { if (NewType != Type) { switch (NewType) { default: { // No conversion possible break; } case GV_INT32: { *this = (int)CastInt32(); break; } case GV_INT64: { *this = (int64_t) CastInt64(); break; } case GV_BOOL: { if (Type == GV_DOUBLE) { *this = Value.Dbl != 0.0; } else { *this = CastInt32() != 0; } break; } case GV_DOUBLE: { *this = CastDouble(); break; } case GV_STRING: { *this = CastString(); break; } case GV_DATETIME: { switch (Type) { case GV_STRING: { // String -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set(Value.String); Empty(); Value.Date = Dt; Type = NewType; } break; } case GV_INT64: { // Int64 (system date) -> LDateTime LDateTime *Dt = new LDateTime; if (Dt) { Dt->Set((uint64_t)Value.Int64); Empty(); Value.Date = Dt; Type = NewType; } break; } default: { // No conversion available break; } } break; } } } return *this; } void *LVariant::CastVoidPtr() const { switch (Type) { default: break; case GV_STRING: return Value.String; case GV_BINARY: return Value.Binary.Data; case GV_LIST: return Value.Lst; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_VOID_PTR: return Value.Ptr; case GV_DATETIME: return Value.Date; case GV_HASHTABLE: return Value.Hash; case GV_CUSTOM: return Value.Custom.Data; case GV_WSTRING: return Value.WString; case GV_LSURFACE: return Value.Surface.Ptr; case GV_GVIEW: return Value.View; case GV_LMOUSE: return Value.Mouse; case GV_LKEY: return Value.Key; } return 0; } LDom *LVariant::CastDom() const { switch (Type) { default: break; case GV_DOM: return Value.Dom; case GV_DOMREF: return Value.DomRef.Dom; case GV_STREAM: return dynamic_cast(Value.Stream.Ptr); case GV_LSURFACE: return Value.Surface.Ptr; case GV_CUSTOM: return Value.Custom.Dom; } return NULL; } bool LVariant::CastBool() const { switch (Type) { default: LAssert(0); break; case GV_NULL: return false; case GV_INT32: return Value.Int != 0; case GV_INT64: return Value.Int64 != 0; case GV_BOOL: return Value.Bool; case GV_DOUBLE: return Value.Dbl != 0.0; case GV_BINARY: return Value.Binary.Data != NULL; case GV_LIST: return Value.Lst != NULL; case GV_DOM: return Value.Dom != NULL; case GV_DOMREF: return Value.DomRef.Dom != NULL; case GV_VOID_PTR: return Value.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_DATETIME: return Value.Date != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_OPERATOR: return Value.Op != OpNull; case GV_CUSTOM: return Value.Custom.Dom != 0 && Value.Custom.Data != 0; /* case GV_GFILE: return Value.File.Ptr != NULL; */ case GV_STREAM: return Value.Stream.Ptr != NULL; // As far as I understand this is the behavour in Python, which I'm using for // a reference to what the "correct" thing to do here is. Basically it's treating // the string like a pointer instead of a value. If the pointer is valid the // conversion to bool return true, and false if it's not a valid pointer. This // means things like if (!StrinLVariant) evaluate correctly in the scripting engine // but it means that if you want to evaluate the value of the varient you should // use CastInt32 instead. case GV_STRING: return ValidStr(Value.String); case GV_WSTRING: return ValidStrW(Value.WString); } return false; } double LVariant::CastDouble() const { switch (Type) { default: break; case GV_BOOL: return Value.Bool ? 1.0 : 0.0; case GV_DOUBLE: return Value.Dbl; case GV_INT32: return (double)Value.Int; case GV_INT64: return (double)Value.Int64; case GV_STRING: return Value.String ? atof(Value.String) : 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastDouble(); } } break; } } return 0; } int32 LVariant::CastInt32() const { switch (Type) { default: break; case GV_BOOL: return (int32)Value.Bool; case GV_DOUBLE: return (int32)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return (int32)Value.Int64; case GV_STRING: if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return static_cast(Atoi(Value.String, 16)); return atoi(Value.String); case GV_DOM: return Value.Dom != 0; case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt32(); } } break; } case GV_LIST: return Value.Lst != NULL; case GV_HASHTABLE: return Value.Hash != NULL; case GV_LSURFACE: return Value.Surface.Ptr != NULL; case GV_GVIEW: return Value.View != NULL; case GV_LMOUSE: return Value.Mouse != NULL; case GV_LKEY: return Value.Key != NULL; case GV_STREAM: return Value.Stream.Ptr != NULL; } return 0; } int64 LVariant::CastInt64() const { switch (Type) { default: break; case GV_BOOL: return (int64)Value.Bool; case GV_DOUBLE: return (int64)Value.Dbl; case GV_INT32: return Value.Int; case GV_INT64: return Value.Int64; case GV_STRING: { if (!Value.String) return 0; if (IsAlpha(Value.String[0])) return !Stricmp(Value.String, "true"); else if (Value.String[0] == '0' && tolower(Value.String[1]) == 'x') return Atoi(Value.String, 16); return Atoi(Value.String); } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastInt64(); } } break; } } return 0; } char *LVariant::CastString() { char i[40]; switch (Type) { case GV_LIST: { LStringPipe p(256); List::I it = Value.Lst->begin(); bool First = true; p.Print("{"); for (LVariant *v = *it; v; v = *++it) { if (v->Type == GV_STRING || v->Type == GV_WSTRING) p.Print("%s\"%s\"", First ? "" : ", ", v->CastString()); else p.Print("%s%s", First ? "" : ", ", v->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_HASHTABLE: { LStringPipe p(256); p.Print("{"); bool First = true; // const char *k; // for (LVariant *v = Value.Hash->First(&k); v; v = Value.Hash->Next(&k)) for (auto i : *Value.Hash) { p.Print("%s%s = %s", First ? "" : ", ", i.key, i.value->CastString()); First = false; } p.Print("}"); OwnStr(p.NewStr()); return Str(); break; } case GV_DOMREF: { static LVariant v; if (Value.DomRef.Dom) { if (Value.DomRef.Dom->GetValue(Value.DomRef.Name, v)) { return v.CastString(); } } break; } case GV_INT32: { sprintf_s(i, sizeof(i), "%i", Value.Int); *this = i; return Str(); } case GV_DOUBLE: { sprintf_s(i, sizeof(i), "%f", Value.Dbl); *this = i; return Str(); } case GV_BOOL: { sprintf_s(i, sizeof(i), "%i", Value.Bool); *this = i; return Str(); } case GV_INT64: { sprintf_s(i, sizeof(i), LPrintfInt64, Value.Int64); *this = i; return Str(); } case GV_STRING: case GV_WSTRING: { return Str(); } case GV_DATETIME: { if (Value.Date) { char s[64]; Value.Date->Get(s, sizeof(s)); *this = s; return Str(); } break; } case GV_DOM: { sprintf_s(i, sizeof(i), "dom:%p", Value.Dom); *this = i; break; } default: { break; } } return 0; } ///////////////////////////////////////////////////////////////////////////////// LDom *LDom::ResolveObject(const char *Var, LString &Name, LString &Array) { LDom *Object = this; try { // Tokenize the string LString::Array t; for (auto *s = Var; s && *s; ) { const char *e = s; while (*e && *e != '.') { if (*e == '[') { e++; while (*e && *e != ']') { if (*e == '\"' || *e == '\'') { char d = *e++; while (*e && *e != d) e++; if (*e == d) e++; } else e++; } if (*e == ']') e++; } else e++; } LString part = LString(s, e - s).Strip(); if (part.Length() > 0) t.New() = part; // Store non-empty part s = *e ? e + 1 : e; } // Process elements for (int i=0; iGetVariant(Obj, v, Index)) { if (v.Type == GV_LIST) { int N = atoi(Index); LVariant *Element = v.Value.Lst->ItemAt(N); if (Element && Element->Type == GV_DOM) { Object = Element->Value.Dom; } else { return NULL; } } else if (v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } else { return NULL; } } else { if (Object->GetVariant(Obj, v) && v.Type == GV_DOM) { Object = v.Value.Dom; } else { return NULL; } } } } } catch (...) { LgiTrace("LDom::ResolveObject crashed: '%s'\n", Var); return NULL; } return Object; } struct LDomPropMap { LHashTbl, LDomProperty> ToProp; LHashTbl, const char *> ToString; LDomPropMap() { #undef _ #define _(symbol, txt) Define(txt, symbol); #include "lgi/common/DomFields.h" #undef _ } void Define(const char *s, LDomProperty p) { if (!s) return; #if defined(_DEBUG) // Check for duplicates. auto existing_prop = ToProp.Find(s); LAssert(existing_prop == ObjNone); auto existing_str = ToString.Find(p); LAssert(existing_str == NULL); #endif ToProp.Add(s, p); ToString.Add(p, s); } } DomPropMap; LDomProperty LStringToDomProp(const char *Str) { return DomPropMap.ToProp.Find(Str); } const char *LDomPropToString(LDomProperty Prop) { return DomPropMap.ToString.Find(Prop); } bool LDom::GetValue(const char *Var, LVariant &Value) { if (!Var) return false; if (!_OnAccess(true)) { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); return false; } bool Status = false; LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->GetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get()); } _OnAccess(false); return Status; } bool LDom::SetValue(const char *Var, LVariant &Value) { bool Status = false; if (Var) { // LMutex *Sem = dynamic_cast(this); if (_OnAccess(true)) { LString Name, Arr; LDom *Object = ResolveObject(Var, Name, Arr); if (Object) { if (Name.IsEmpty()) LgiTrace("%s:%i - Warning name parse failed for '%s'\n", _FL, Var); else Status = Object->SetVariant(Name, Value, Arr.IsEmpty() ? NULL : Arr.Get()); } _OnAccess(false); } else { LgiTrace("%s:%i - Locking error\n", _FL); LAssert(0); } } return Status; } bool LVariant::Add(LVariant *v, int Where) { if (!v) { LAssert(!"No value to insert."); return false; } if (Type == GV_NULL) SetList(); if (Type != GV_LIST) { LAssert(!"Not a list variant"); return false; } return Value.Lst->Insert(v, Where); } LString LVariant::ToString() { LString s; switch (Type) { case GV_NULL: s = "NULL"; break; case GV_INT32: s.Printf("(int)%i", Value.Int); break; case GV_INT64: s.Printf("(int64)" LPrintfInt64, Value.Int64); break; case GV_BOOL: s.Printf("(bool)%s", Value.Bool ? "true" : "false"); break; case GV_DOUBLE: s.Printf("(double)%f", Value.Dbl); break; case GV_STRING: s.Printf("(string)\"%s\"", Value.String); break; case GV_BINARY: s.Printf("(binary[%i])%p", Value.Binary.Length, Value.Binary.Data); break; case GV_LIST: s.Printf("(list[%i])%p", Value.Lst?Value.Lst->Length():0, Value.Lst); break; case GV_DOM: s.Printf("(dom)%p", Value.Dom); break; case GV_DOMREF: s.Printf("(dom)%p.%s", Value.DomRef.Dom, Value.DomRef.Name); break; case GV_VOID_PTR: s.Printf("(void*)%p", Value.Ptr); break; case GV_DATETIME: { char dt[64]; Value.Date->Get(dt, sizeof(dt)); s.Printf("(datetime)%s", dt); break; } case GV_HASHTABLE: s.Printf("(hashtbl)%p", Value.Hash); break; case GV_OPERATOR: s.Printf("(operator)%s", OperatorToString(Value.Op)); break; case GV_CUSTOM: s.Printf("(custom.%s)%p", Value.Custom.Dom->GetName(), Value.Custom.Data); break; case GV_WSTRING: s.Printf("(wstring)\"%S\"", Value.WString); break; case GV_LSURFACE: s.Printf("(gsurface)%p", Value.Surface.Ptr); break; case GV_GVIEW: s.Printf("(gview)%p", Value.View); break; case GV_LMOUSE: s.Printf("(gmouse)%p", Value.Mouse); break; case GV_LKEY: s.Printf("(gkey)%p", Value.Key); break; case GV_STREAM: s.Printf("(stream)%p", Value.Stream.Ptr); break; default: s = "(unknown)NULL"; break; } return s; } ///////////////////////////////////////////////////////////////////////////////////////////////////// LCustomType::LCustomType(const char *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::LCustomType(const char16 *name, int pack) : FldMap(0, -1) { Name = name; Pack = 1; Size = 0; } LCustomType::~LCustomType() { Flds.DeleteObjects(); Methods.DeleteObjects(); } size_t LCustomType::Sizeof() { return (size_t)PadSize(); } ssize_t LCustomType::PadSize() { if (Pack > 1) { // Bump size to the pack boundary... int Remain = Size % Pack; if (Remain) return Size + Pack - Remain; } return Size; } int LCustomType::IndexOf(const char *Field) { return FldMap.Find(Field); } int LCustomType::AddressOf(const char *Field) { if (!Field) return -1; for (unsigned i=0; iName, Field)) return (int)i; } return -1; } bool LCustomType::DefineField(const char *Name, LCustomType *Type, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL || Type == NULL) { LAssert(!"Invalid parameter."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = GV_CUSTOM; Def->Bytes = Type->Sizeof(); Def->ArrayLen = ArrayLen; Size += Def->Bytes * ArrayLen; return true; } bool LCustomType::DefineField(const char *Name, LVariantType Type, int Bytes, int ArrayLen) { if (ArrayLen < 1) { LAssert(!"Can't have zero size field."); return false; } if (Name == NULL) { LAssert(!"No field name."); return false; } if (FldMap.Find(Name) >= 0) { LAssert(!"Field already exists."); return false; } FldMap.Add(Name, (int)Flds.Length()); CustomField *Def; Flds.Add(Def = new CustomField); Size = PadSize(); Def->Offset = Size; Def->Name = Name; Def->Type = Type; Def->Bytes = Bytes; Def->ArrayLen = ArrayLen; Size += Bytes * ArrayLen; return true; } LCustomType::Method *LCustomType::GetMethod(const char *Name) { return MethodMap.Find(Name); } LCustomType::Method *LCustomType::DefineMethod(const char *Name, LArray &Params, size_t Address) { Method *m = MethodMap.Find(Name); if (m) { LAssert(!"Method already defined."); return NULL; } Methods.Add(m = new Method); m->Name = Name; m->Params = Params; m->Address = Address; MethodMap.Add(Name, m); return m; } bool LCustomType::CustomField::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String Value = Name; break; case ObjLength: // Type: Int32 Value = Bytes; break; default: return false; } return true; } ssize_t LCustomType::CustomField::Sizeof() { switch (Type) { case GV_INT32: return sizeof(int32); case GV_INT64: return sizeof(int64); case GV_BOOL: return sizeof(bool); case GV_DOUBLE: return sizeof(double); case GV_STRING: return sizeof(char); case GV_DATETIME: return sizeof(LDateTime); case GV_HASHTABLE: return sizeof(LVariant::LHash); case GV_OPERATOR: return sizeof(LOperator); case GV_LMOUSE: return sizeof(LMouse); case GV_LKEY: return sizeof(LKey); case GV_CUSTOM: return Nested->Sizeof(); default: LAssert(!"Unknown type."); break; } return 0; } bool LCustomType::Get(int Index, LVariant &Out, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } uint8_t *Ptr = This + Def->Offset; Out.Empty(); switch (Def->Type) { case GV_STRING: { int Len; for (Len = 0; Ptr[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStr((char*)Ptr, Len)); break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; int Len; for (Len = 0; p[Len] && Len < Def->ArrayLen-1; Len++) ; Out.OwnStr(NewStrW(p, Len)); break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Out.Value.Int = Ptr[ArrayIndex]; Out.Type = GV_INT32; break; } case 2: { Out.Value.Int = ((uint16*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 4: { Out.Value.Int = ((uint32_t*)Ptr)[ArrayIndex]; Out.Type = GV_INT32; break; } case 8: { Out.Value.Int64 = ((uint64*)Ptr)[ArrayIndex]; Out.Type = GV_INT64; break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { Out = *((LVariant*)Ptr); break; } default: { LAssert(!"Impl this type."); return false; } } return true; } bool LCustomType::Set(int Index, LVariant &In, uint8_t *This, int ArrayIndex) { if (Index < 0 || Index >= Flds.Length() || !This) { LAssert(!"Invalid parameter error."); return false; } CustomField *Def = Flds[Index]; uint8_t *Ptr = This + Def->Offset; if (ArrayIndex < 0 || ArrayIndex >= Def->ArrayLen) { LAssert(!"Array out of bounds."); return false; } switch (Def->Type) { case GV_STRING: { char *s = In.Str(); if (!s) { *Ptr = 0; break; } if (Def->Bytes == 1) { // Straight up string copy... if (s) strcpy_s((char*)Ptr, Def->ArrayLen, s); else *Ptr = 0; } else if (Def->Bytes == sizeof(char16)) { // utf8 -> wide conversion... const void *In = Ptr; ssize_t Len = strlen(s); ssize_t Ch = LBufConvertCp(Ptr, LGI_WideCharset, Def->ArrayLen-1, In, "utf-8", Len); if (Ch >= 0) { // Null terminate Ptr[Ch] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_WSTRING: { char16 *p = (char16*)Ptr; char16 *w = In.WStr(); if (!w) { *p = 0; break; } if (Def->Bytes == sizeof(char16)) { // Straight string copy... Strcpy(p, Def->ArrayLen, w); } else { // Conversion to utf-8 const void *In = Ptr; ssize_t Len = StrlenW(w) * sizeof(char16); ssize_t Ch = LBufConvertCp(Ptr, "utf-8", Def->ArrayLen-sizeof(char16), In, LGI_WideCharset, Len); if (Ch >= 0) { // Null terminate p[Ch/sizeof(char16)] = 0; } else { LAssert(!"LBufConvertCp failed."); return false; } } break; } case GV_INT32: case GV_INT64: { switch (Def->Bytes) { case 1: { Ptr[ArrayIndex] = In.CastInt32(); break; } case 2: { ((uint16*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 4: { ((uint32_t*)Ptr)[ArrayIndex] = In.CastInt32(); break; } case 8: { ((uint64*)Ptr)[ArrayIndex] = In.CastInt64(); break; } default: { LAssert(!"Unknown integer size."); return false; } } break; } case GV_MAX: { *((LVariant*)Ptr) = In; break; } default: LAssert(!"Impl this type."); break; } return true; } bool LCustomType::GetVariant(const char *Field, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Field); switch (p) { case ObjName: // Type: String { Value = Name; return true; } case ObjType: // Type: String { Value = "LCustomType"; return true; } case ObjLength: // Type: Int32 { Value = (int)Sizeof(); return true; } case ObjField: // Type: CustomField[] { if (Array) { int Index = atoi(Array); if (Index >= 0 && Index < Flds.Length()) { Value = (LDom*)&Flds[Index]; return true; } } else { Value = (int)Flds.Length(); break; } break; } default: break; } LAssert(0); return false; } bool LCustomType::SetVariant(const char *Name, LVariant &Value, const char *Array) { LAssert(0); return false; } -bool LCustomType::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) +bool LCustomType::CallMethod(const char *MethodName, LScriptArguments &Args) { - if (!MethodName || !ReturnValue) + if (!MethodName) return false; if (!_stricmp(MethodName, "New")) { - ReturnValue->Empty(); - ReturnValue->Type = GV_CUSTOM; - ReturnValue->Value.Custom.Dom = this; - ReturnValue->Value.Custom.Data = new uint8_t[Sizeof()]; + Args.GetReturn()->Empty(); + Args.GetReturn()->Type = GV_CUSTOM; + Args.GetReturn()->Value.Custom.Dom = this; + Args.GetReturn()->Value.Custom.Data = new uint8_t[Sizeof()]; return true; } if (!_stricmp(MethodName, "Delete")) // Type: (Object) { for (unsigned i=0; iType == GV_CUSTOM) { DeleteArray(v->Value.Custom.Data); v->Empty(); } } return true; } LAssert(0); return false; } + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +LStream LScriptArguments::NullConsole; + +LScriptArguments::LScriptArguments(LVirtualMachineI *vm, LVariant *ret, LStream *console) +{ + Vm = vm; + if (ret) + Return = ret; + else + Return = LocalReturn = new LVariant; + + if (console) + Console = console; + else + Console = &NullConsole; +} + +LScriptArguments::~LScriptArguments() +{ + DeleteObjects(); + DeleteObj(LocalReturn); +} + +const char *LScriptArguments::StringAt(size_t i) +{ + return IdxCheck(i) ? (*this)[i]->Str() : NULL; +} + +int32_t LScriptArguments::Int32At(size_t i, int32_t Default) +{ + return IdxCheck(i) ? (*this)[i]->CastInt32() : Default; +} + +int64_t LScriptArguments::Int64At(size_t i, int64_t Default) +{ + return IdxCheck(i) ? (*this)[i]->CastInt64() : Default; +} + +double LScriptArguments::DoubleAt(size_t i, double Default) +{ + return IdxCheck(i) ? (*this)[i]->CastDouble() : Default; +} + +bool LScriptArguments::Throw(const char *File, int Line, const char *Msg, ...) +{ + if (!Vm) + return false; + + va_list Arg; + va_start(Arg, Msg); + + LString s; + s.Printf(Arg, Msg); + + va_end(Arg); + + Vm->OnException(File, Line, -1, s); + return true; +} diff --git a/src/common/Text/XmlTreeUi.cpp b/src/common/Text/XmlTreeUi.cpp --- a/src/common/Text/XmlTreeUi.cpp +++ b/src/common/Text/XmlTreeUi.cpp @@ -1,460 +1,456 @@ /// \file /// \author Matthew Allen #include "lgi/common/Lgi.h" #include "lgi/common/XmlTreeUi.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Button.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Slider.h" #include "lgi/common/Combo.h" #include "lgi/common/Edit.h" #include "lgi/common/Tree.h" #include struct Mapping { int Id = 0; int Hint = GV_NULL; std::function Callback; LString ChildElementName; LXmlTreeUi::EnumMap EnumMap; void LoadTree(LTreeNode *n, LXmlTag *t) { for (auto c: t->Children) { auto i = dynamic_cast(Callback()); if (i) { i->XmlIo(c, false); n->Insert(i); LoadTree(i, c); } } } void SaveTree(LTreeNode *n, LXmlTag *t) { for (LTreeItem *i = n->GetChild(); i; i = i->GetNext()) { LXmlTag *n = new LXmlTag(ChildElementName); if (n) { i->XmlIo(n, true); t->InsertTag(n); SaveTree(i, n); } } } }; class LXmlTreeUiPriv { public: LHashTbl,Mapping*> Maps; ~LXmlTreeUiPriv() { Maps.DeleteObjects(); } }; LXmlTreeUi::LXmlTreeUi() { d = new LXmlTreeUiPriv; } LXmlTreeUi::~LXmlTreeUi() { DeleteObj(d); } void LXmlTreeUi::EmptyAll(LViewI *Ui) { if (Ui) { // for (Mapping *m=d->Maps.First(); m; m=d->Maps.Next()) for (auto m : d->Maps) { if (m.value->Hint == GV_STRING) Ui->SetCtrlName(m.value->Id, 0); } } else LAssert(!"Invalid params"); } void LXmlTreeUi::EnableAll(LViewI *Ui, bool Enable) { if (Ui) { // for (Mapping *m=d->Maps.First(); m; m=d->Maps.Next()) for (auto m : d->Maps) { Ui->SetCtrlEnabled(m.value->Id, Enable); } } else LAssert(!"Invalid params"); } bool LXmlTreeUi::IsMapped(const char *Attr) { return d->Maps.Find(Attr) != NULL; } void LXmlTreeUi::Map(const char *Attr, int UiIdent, int Type) { if (UiIdent > 0 && (Attr != NULL || Type == GV_DOM)) { Mapping *m = new Mapping; if (m) { m->Id = UiIdent; m->Hint = Type; LAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else LAssert(!"Invalid params"); } void LXmlTreeUi::Map(const char *Attr, int UiIdent, const char *ChildElementName, std::function Callback) { if (Attr && UiIdent > 0 && Callback && ChildElementName) { Mapping *m = new Mapping; if (m) { m->Id = UiIdent; m->Callback = Callback; m->Hint = GV_LIST; m->ChildElementName = ChildElementName; LAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else LAssert(!"Invalid params"); } void LXmlTreeUi::Map(const char *Attr, LHashTbl,int> &Map) { if (Attr && Map.Length() > 0) { auto m = new Mapping; if (m) { m->Hint = GV_HASHTABLE; m->EnumMap = Map; LAssert(!d->Maps.Find(Attr)); d->Maps.Add(Attr, m); } } else LAssert(!"Invalid params"); } void LXmlTreeUi::EmptyMaps() { d->Maps.DeleteObjects(); } int GetCtrlType(LViewI *v) { if (v) { if (dynamic_cast(v) || dynamic_cast(v) || dynamic_cast(v)) { return GV_BOOL; } else if (dynamic_cast(v) || dynamic_cast(v) || dynamic_cast(v)) { return GV_INT32; } else if (!Stricmp(v->GetClass(), "LControlTree")) { return GV_DOM; } } return GV_STRING; } int GetDataType(char *str) { if (str) { bool Float = false; char16 w; ssize_t Len = strlen(str); while ((w = LgiUtf8To32((uint8_t*&)str, Len))) { if (strchr("e \t\r\n", w)) { // Doesn't really tell us anything, so ignore it. // The 'e' part is sometimes part of a number or // ignore that too. } else if (!IsDigit(w) || w > 255) { return GV_STRING; } else if (w == '.') { Float = true; } } if (Float) { return GV_DOUBLE; } return GV_INT32; } else LAssert(!"Invalid params"); return GV_NULL; } bool LXmlTreeUi::Convert(LDom *Tag, LViewI *Ui, bool ToUI) { bool Status = false; if (Ui && Tag) { LVariant v; LXmlTag *Xml = dynamic_cast(Tag); if (ToUI) { // Xml -> UI for (auto Map : d->Maps) { Mapping *m = Map.value; switch (m->Hint) { case GV_HASHTABLE: { Tag->GetValue(Map.key, v); if (v.Str()) { for (auto e: m->EnumMap) { auto name = e.key; if (!Stricmp(name, v.Str())) Ui->SetCtrlValue(e.value, true); } } break; } case GV_LIST: { if (!Xml) { LAssert(!"Needs an xml tag."); break; } LXmlTag *t = Xml->GetChildTag(Map.key); if (!t) continue; LList *Lst; if (!Ui->GetViewById(m->Id, Lst)) continue; Lst->Empty(); for (auto c: t->Children) { auto i = dynamic_cast(m->Callback()); if (i) { i->XmlIo(c, false); Lst->Insert(i); } } break; } case GV_CUSTOM: { if (!Xml) { LAssert(!"Needs an xml tag."); break; } LXmlTag *t = Xml->GetChildTag(Map.key); if (!t) continue; LTree *Tree; if (!Ui->GetViewById(m->Id, Tree)) continue; Tree->Empty(); m->LoadTree(Tree, t); break; } case GV_DOM: { LView *ct; if (!Ui->GetViewById(m->Id, ct)) break; - LVariant Ret; - LArray Args; + LScriptArguments Args(NULL); Args[0] = new LVariant(Xml); Args[1] = new LVariant(false); auto Param = LDomPropToString(ControlSerialize); - ct->CallMethod(Param, &Ret, Args); - Args.DeleteObjects(); + ct->CallMethod(Param, Args); break; } default: { if (Tag->GetValue(Map.key, v)) { int Type = m->Hint ? m->Hint : GetDataType(v.Str()); if (Type == GV_BOOL || Type == GV_INT32 || Type == GV_INT64) { Ui->SetCtrlValue(m->Id, v.CastInt32()); } else { Ui->SetCtrlName(m->Id, v.Str()); } Status = true; } else { LEdit *c; if (Ui->GetViewById(m->Id, c)) c->Name(""); } break; } } } } else { // UI -> Xml for (auto Map : d->Maps) { Mapping *m = Map.value; if (m->Hint == GV_HASHTABLE) { for (auto e: m->EnumMap) { if (Ui->GetCtrlValue(e.value)) { Tag->SetValue(Map.key, v = e.key); Status = true; } } } else { LViewI *c = Ui->FindControl(m->Id); if (c) { int Type = m->Hint ? m->Hint : GetCtrlType(c); switch (Type) { case GV_LIST: { if (!Xml) break; LXmlTag *Child = Xml->GetChildTag(Map.key, true); if (!Child) break; LList *Lst = dynamic_cast(c); if (!Lst) break; Child->Empty(true); Child->SetTag(Map.key); List All; Lst->GetAll(All); for (auto i: All) { LXmlTag *n = new LXmlTag(m->ChildElementName); if (n) { i->XmlIo(n, true); Child->InsertTag(n); } } break; } case GV_CUSTOM: // LTree { if (!Xml) break; LXmlTag *Child = Xml->GetChildTag(Map.key, true); if (!Child) break; LTree *Tree = dynamic_cast(c); if (!Tree) break; Child->Empty(true); Child->SetTag(Map.key); m->SaveTree(Tree, Child); break; } case GV_INT32: case GV_BOOL: { Tag->SetValue(Map.key, v = c->Value()); Status = true; break; } case GV_DOM: { LView *ct; if (Ui->GetViewById(m->Id, ct)) { - LVariant Ret; - LArray Args; + LScriptArguments Args(NULL); Args[0] = new LVariant(Xml); Args[1] = new LVariant(true); - ct->CallMethod(LDomPropToString(ControlSerialize), &Ret, Args); - Args.DeleteObjects(); + ct->CallMethod(LDomPropToString(ControlSerialize), Args); } break; } default: { auto Str = c->Name(); if (ValidStr(Str)) v = Str; else v.Empty(); Tag->SetValue(Map.key, v); Status = true; break; } } } else { v.Empty(); Tag->SetValue(Map.key, v); } } } } } else LAssert(!"Invalid params"); return Status; } diff --git a/src/common/Widgets/ControlTree.cpp b/src/common/Widgets/ControlTree.cpp --- a/src/common/Widgets/ControlTree.cpp +++ b/src/common/Widgets/ControlTree.cpp @@ -1,642 +1,642 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/ControlTree.h" #include "lgi/common/Edit.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Button.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #include "lgi/common/FileSelect.h" #define IDC_BROWSE -10 class LControlTreePriv { public: LResources *Factory; LControlTreePriv() { Factory = 0; } }; LControlTree::Item::Item(int ctrlId, char *Txt, const char *opt, LVariantType type, LArray *pEnum) { if (ValidStr(opt)) Opt.Reset(NewStr(opt)); CtrlId = ctrlId; Enum.Reset(pEnum); SetText(Txt); Type = type; Ctrl = 0; Browse = 0; } LControlTree::Item::~Item() { } void LControlTree::Item::SetEnum(LAutoPtr e) { Enum = e; Type = GV_INT32; } void LControlTree::Item::OnVisible(bool v) { if (Ctrl) { if (v) PositionControls(); else { Ctrl->Visible(false); if (Browse) Browse->Visible(false); } } } LControlTree::Item *LControlTree::Item::Find(const char *opt) { if (Opt && !_stricmp(Opt, opt)) { return this; } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { LControlTree::Item *f = ci->Find(opt); if (f) return f; } } return 0; } bool LControlTree::Item::Serialize(LDom *Store, bool Write) { if (Opt) { if (Write) { Save(); Store->SetValue(Opt, Value); } else { Store->GetValue(Opt, Value); } } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { ci->Serialize(Store, Write); } } return true; } void LControlTree::Item::SetValue(LVariant &v) { Value = v; if (LTreeItem::Select()) { DeleteObj(Ctrl); DeleteObj(Browse); Select(true); } else Update(); } LRect &LControlTree::Item::GetRect() { static LRect r; r.ZOff(-1, -1); LRect *p = _GetRect(TreeItemText); if (p) { bool HasBrowse = (Flags & TYPE_FILE) != 0; int x = p->x2 + 5; r.x1 = x; r.x2 = GetTree()->GetPos().X() - (HasBrowse ? 50 : 10); int Cy = Ctrl ? Ctrl->Y() : 16; r.y1 = (p->y1 + (p->Y()/2)) - (Cy / 2); r.y2 = r.y1 + Cy - 1; } return r; } void LControlTree::Item::Save() { if (Ctrl) { switch (Type) { case GV_STRING: { auto v = Ctrl->Name(); if (Stricmp(v, Value.Str())) { Value = v; Tree->SendNotify(LNotifyValueChanged); } break; } case GV_BOOL: { bool b = Ctrl->Value() != 0; if (b != (Value.CastInt32() != 0)) { Value = b; Tree->SendNotify(LNotifyValueChanged); } break; } default: { int Idx = (int)Ctrl->Value(); if (Enum && Enum->Length()) { if (Idx >= 0 && Idx < (int)Enum->Length()) { LControlTree::EnumValue &e = (*Enum)[Idx]; if (e.Value.Type == GV_STRING) { if (Stricmp(Value.Str(), e.Value.Str())) { Value = e.Value; Tree->SendNotify(LNotifyValueChanged); } } else if (Idx != Value.CastInt32()) { Value = Idx; Tree->SendNotify(LNotifyValueChanged); } } else LAssert(0); } else if (Idx != Value.CastInt32()) { Value = Idx; Tree->SendNotify(LNotifyValueChanged); } break; } } } } void LControlTree::Item::Select(bool b) { LTreeItem::Select(b); if ((Ctrl != 0) ^ b) { if (b) { LAssert(Ctrl == 0); int FontY = LSysFont->GetHeight(); int CtrlY = FontY + (FontY >> 1); switch (Type) { default: break; case GV_STRING: if ((Ctrl = new LEdit(CtrlId, 0, 0, 200, CtrlY, 0))) Ctrl->Name(Value.Str()); if (Flags & TYPE_FILE) Browse = new LButton(IDC_BROWSE, 0, 0, -1, CtrlY, "..."); break; case GV_BOOL: if ((Ctrl = new LCheckBox(CtrlId, 0, 0, 14, 16, 0))) Ctrl->Value(Value.CastInt32()); break; case GV_INT32: if (Enum) { LCombo *Cbo; if ((Ctrl = (Cbo = new LCombo(CtrlId, 0, 0, 120, CtrlY, 0)))) { int Idx = -1; for (unsigned i=0; iLength(); i++) { EnumValue &e = (*Enum)[i]; Cbo->Insert(e.Name); if (e.Value == Value) Idx = i; } if (Idx >= 0) Ctrl->Value(Idx); } } else { if ((Ctrl = new LEdit(CtrlId, 0, 0, 60, CtrlY, 0))) Ctrl->Value(Value.CastInt32()); } break; } if (Ctrl) { LColour Ws = LColour(L_WORKSPACE); Ctrl->SetColour(Ws, false); Ctrl->Visible(false); Ctrl->Attach(GetTree()); if (Browse) { Browse->SetColour(Ws, false); Browse->Visible(false); Browse->Attach(GetTree()); } PositionControls(); } } else if (Ctrl) { Save(); DeleteObj(Ctrl); DeleteObj(Browse); } Update(); } } void LControlTree::Item::PositionControls() { if (Ctrl) { LRect r = GetRect(); Ctrl->SetPos(r); Ctrl->Visible(true); if (Browse) { LRect b = Browse->GetPos(); b.Offset(r.x2 + 5 - b.x1, r.y1 - b.y1); Browse->SetPos(b); Browse->Visible(true); } } } void LControlTree::Item::OnPaint(ItemPaintCtx &Ctx) { LTreeItem::OnPaint(Ctx); if (!Ctrl) { LCssTools Tools(GetTree()); auto Ws = LColour(L_WORKSPACE); LSysBold->Colour(Tools.GetFore(), Tools.GetBack(&Ws, 0)); LSysBold->Transparent(true); LRect p = GetRect(); switch (Type) { default: break; case GV_INT32: { char s[32], *Disp = 0; if (Enum) { for (unsigned i=0; iLength(); i++) { EnumValue &e = (*Enum)[i]; #if 0 LString s1 = e.Value.ToString(); LString s2 = Value.ToString(); LgiTrace("EnumMatch %s: %s - %s\n", e.Name, s1.Get(), s2.Get()); #endif if (e.Value == Value) { Disp = e.Name; break; } } if (Disp) { LDisplayString ds(LSysBold, Disp); ds.Draw(Ctx.pDC, p.x1 + 8, p.y1 + 1); } else { LDisplayString ds(LSysFont, LLoadString(L_CONTROLTREE_NO_VALUE, "(no value)")); LSysFont->Colour(LColour(L_LOW), LColour(L_WORKSPACE)); ds.Draw(Ctx.pDC, p.x1 + 8, p.y1 + 1); } } else { sprintf_s(Disp = s, sizeof(s), "%i", Value.CastInt32()); LDisplayString ds(LSysBold, Disp); ds.Draw(Ctx.pDC, p.x1 + 6, p.y1 + 2); } break; } case GV_STRING: { LDisplayString ds(LSysBold, Value.Str()); ds.Draw(Ctx.pDC, p.x1 + 6, p.y1 + 2); break; } case GV_BOOL: { LDisplayString ds(LSysBold, (char*) (Value.CastInt32() ? "true" : "false")); ds.Draw(Ctx.pDC, p.x1 + 1, p.y1 + 1); break; } } } else { PositionControls(); } } /////////////////////////////////////////////////////////////////////// LControlTree::LControlTree() : LTree(-1, 0, 0, 100, 100) { SetObjectName(Res_ControlTree); d = new LControlTreePriv; } LControlTree::~LControlTree() { DeleteObj(d); } LControlTree::Item *LControlTree::Find(const char *opt) { for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) { LControlTree::Item *f = ci->Find(opt); if (f) return f; } } return 0; } -bool LControlTree::CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) +bool LControlTree::CallMethod(const char *MethodName, LScriptArguments &Args) { switch (LStringToDomProp(MethodName)) { case ControlSerialize: { if (Args.Length() != 2) { LAssert(!"Wrong argument count."); return false; } auto *Store = Args[0]->CastDom(); if (!Store) { LAssert(!"Missing store object."); return false; } auto Write = Args[1]->CastInt32() != 0; - *ReturnValue = Serialize(Store, Write); + *Args.GetReturn() = Serialize(Store, Write); return true; } default: break; } return false; } bool LControlTree::Serialize(LDom *Store, bool Write) { bool Error = false; if (!Store) { LAssert(!"Invalid param."); return false; } for (LTreeItem *i = GetChild(); i; i = i->GetNext()) { LControlTree::Item *ci = dynamic_cast(i); if (ci) Error = Error | ci->Serialize(Store, Write); else LAssert(!"Not a control tree item."); } return !Error; } LControlTree::Item *LControlTree::Resolve(bool Create, const char *Path, int CtrlId, LVariantType Type, LArray *Enum) { auto t = LString(Path).SplitDelimit("."); if (t.Length() > 0) { LTreeNode *Cur = this; for (unsigned i=0; iGetChild(); c; c = c->GetNext()) { const char *s = c->GetText(); if (s && _stricmp(t[i], s) == 0) { Match = c; break; } } if (Match) { Cur = Match; } else { if (Create && i == t.Length() - 1) { LControlTree::Item *Ci = new LControlTree::Item(CtrlId, t[i], Path, Type, Enum); if (Ci) { Cur->Insert(Ci); LTreeItem *p = Ci->GetParent(); if (p) p->Expanded(true); return Ci; } } return 0; } } return dynamic_cast(Cur); } return 0; } LTreeItem *LControlTree::Insert(const char *DomPath, int CtrlId, LVariantType Type, LVariant *Value, LArray *Enum) { LControlTree::Item *c = Resolve(true, DomPath, CtrlId, Type, Enum); if (c) { if (Value) c->SetValue(*Value); } return 0; } void LControlTree::ReadTree(LXmlTag *t, LTreeNode *n) { for (auto c: t->Children) { int CtrlId = -1; int StrRef = c->GetAsInt("ref"); LStringRes *Str = d->Factory->StrFromRef(StrRef); LAssert(Str != NULL); if (!Str) continue; CtrlId = Str->Id; char *Type = c->GetAttr("ControlType"); LVariantType iType = GV_NULL; int Flags = 0; if (Type) { if (!_stricmp(Type, "string")) iType = GV_STRING; else if (!_stricmp(Type, "file")) { iType = GV_STRING; Flags |= LControlTree::Item::TYPE_FILE; } else if (!_stricmp(Type, "bool")) iType = GV_BOOL; else if (!_stricmp(Type, "int") || !_stricmp(Type, "enum")) iType = GV_INT32; } const char *Opt = c->GetAttr("ControlTag"); LControlTree::Item *ct = new LControlTree::Item(CtrlId, Str?Str->Str:(char*)"#error", ValidStr(Opt) ? Opt : NULL, iType, 0); if (ct) { ct->Flags = Flags; n->Insert(ct); ReadTree(c, ct); ct->Expanded(true); } } } bool LControlTree::SetVariant(const char *Name, LVariant &Value, const char *Array) { if (!Name) return false; if (!_stricmp(Name, "Tree")) { if (Value.Type != GV_DOM) return false; Empty(); LXmlTag *x = dynamic_cast(Value.Value.Dom); if (!x) LAssert(!"Not the right object."); else if (d->Factory) ReadTree(x, this); else LAssert(!"No factory."); d->Factory = 0; } else if (!_stricmp(Name, "LgiFactory")) { d->Factory = dynamic_cast((ResFactory*)Value.CastVoidPtr()); } else return false; return true; } int LControlTree::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_BROWSE: { Item *i = dynamic_cast(Selection()); if (i) { auto s = new LFileSelect; s->Parent(this); s->Open([i](auto dlg, auto status) { if (status) { LVariant v; i->SetValue(v = dlg->Name()); } delete dlg; }); } return 0; } } return LTree::OnNotify(c, n); } /////////////////////////////////////////////////////////////////////////////// class LControlTree_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LControlTree") == 0) { return new LControlTree; } return 0; } } ControlTree_Factory; diff --git a/src/win/Lgi/View.cpp b/src/win/Lgi/View.cpp --- a/src/win/Lgi/View.cpp +++ b/src/win/Lgi/View.cpp @@ -1,2193 +1,2195 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Win32 LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Base64.h" #include "lgi/common/Com.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/DropFiles.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/Css.h" #include "lgi/common/Edit.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Menu.h" #include "lgi/common/Thread.h" #include "ViewPriv.h" #define DEBUG_MOUSE_CLICKS 0 #define DEBUG_OVER 0 #define OLD_WM_CHAR_MODE 1 //////////////////////////////////////////////////////////////////////////////////////////////////// bool In_SetWindowPos = false; HWND LViewPrivate::hPrevCapture = 0; LViewPrivate::LViewPrivate(LView *view) : View(view) { WndStyle = WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN; IsThemed = LResources::DefaultColours; } LViewPrivate::~LViewPrivate() { while (EventTargets.Length()) delete EventTargets[0]; if (hTheme) { CloseThemeData(hTheme); hTheme = NULL; } if (FontOwnType == GV_FontOwned) { DeleteObj(Font); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // Helper Stuff #include "zmouse.h" int MouseRollMsg = 0; #ifdef __GNUC__ #define MSH_WHEELMODULE_CLASS "MouseZ" #define MSH_WHEELMODULE_TITLE "Magellan MSWHEEL" #define MSH_SCROLL_LINES "MSH_SCROLL_LINES_MSG" #endif int _lgi_mouse_wheel_lines() { UINT nScrollLines; if (SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, (PVOID) &nScrollLines, 0)) return nScrollLines; return 3; } #define SetKeyFlag(v, k, f) if (GetKeyState(k)&0xFF00) { v |= f; } int _lgi_get_key_flags() { int Flags = 0; if (LGetOs() == LGI_OS_WIN9X) { SetKeyFlag(Flags, VK_MENU, LGI_EF_ALT); SetKeyFlag(Flags, VK_SHIFT, LGI_EF_SHIFT); SetKeyFlag(Flags, VK_CONTROL, LGI_EF_CTRL); } else // is NT/2K/XP { SetKeyFlag(Flags, VK_LMENU, LGI_EF_LALT); SetKeyFlag(Flags, VK_RMENU, LGI_EF_RALT); SetKeyFlag(Flags, VK_LSHIFT, LGI_EF_LSHIFT); SetKeyFlag(Flags, VK_RSHIFT, LGI_EF_RSHIFT); SetKeyFlag(Flags, VK_LCONTROL, LGI_EF_LCTRL); SetKeyFlag(Flags, VK_RCONTROL, LGI_EF_RCTRL); } if (GetKeyState(VK_CAPITAL)) SetFlag(Flags, LGI_EF_CAPS_LOCK); return Flags; } //////////////////////////////////////////////////////////////////////////////////////////////////// int GetInputACP() { char16 Str[16]; LCID Lcid = (NativeInt)GetKeyboardLayout(GetCurrentThreadId()) & 0xffff; GetLocaleInfo(Lcid, LOCALE_IDEFAULTANSICODEPAGE, Str, sizeof(Str)); return _wtoi(Str); } LKey::LKey(int v, uint32_t flags) { const char *Cp = 0; vkey = v; Data = flags; c16 = 0; #if OLD_WM_CHAR_MODE c16 = vkey; #else typedef int (WINAPI *p_ToUnicode)(UINT, UINT, PBYTE, LPWSTR, int, UINT); static bool First = true; static p_ToUnicode ToUnicode = 0; if (First) { ToUnicode = (p_ToUnicode) GetProcAddress(LoadLibrary("User32.dll"), "ToUnicode"); First = false; } if (ToUnicode) { BYTE state[256]; GetKeyboardState(state); char16 w[4]; int r = ToUnicode(vkey, flags & 0x7f, state, w, CountOf(w), 0); if (r == 1) { c16 = w[0]; } } #endif } //////////////////////////////////////////////////////////////////////////////////////////////////// template bool CastHwnd(T *&Ptr, HWND hWnd) { #if _MSC_VER >= _MSC_VER_VS2005 LONG_PTR user = GetWindowLongPtr(hWnd, GWLP_USERDATA); #else LONG user = GetWindowLong(hWnd, GWL_USERDATA); #endif LONG magic = GetWindowLong(hWnd, GWL_LGI_MAGIC); if (magic != LGI_GViewMagic) { TCHAR ClsName[256] = {0}; int Ch = GetClassName(hWnd, ClsName, CountOf(ClsName)); LString Cls = ClsName; // LgiTrace("%s:%i - Error: hWnd=%p/%s, GWL_LGI_MAGIC=%i\n", _FL, hWnd, Cls.Get(), magic); return false; } Ptr = dynamic_cast((LViewI*)user); return Ptr != NULL; } bool SetLgiMagic(HWND hWnd) { SetLastError(0); LONG res = SetWindowLong(hWnd, GWL_LGI_MAGIC, LGI_GViewMagic); bool Status = res != 0; if (!Status) { DWORD err = GetLastError(); Status = err == 0; } LONG v = GetWindowLong(hWnd, GWL_LGI_MAGIC); // LgiTrace("set LGI_GViewMagic for %p, %i, %i\n", hWnd, Status, v); return Status; } LRESULT CALLBACK LWindowsClass::Redir(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (m == WM_NCCREATE) { LPCREATESTRUCT Info = (LPCREATESTRUCT) b; LViewI *ViewI = (LViewI*) Info->lpCreateParams; if (ViewI) { LView *View = ViewI->GetGView(); if (View) View->_View = hWnd; #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)ViewI); #else SetWindowLong(hWnd, GWL_USERDATA, (LONG)ViewI); #endif SetLgiMagic(hWnd); } } LViewI *Wnd = (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif if (Wnd) { LMessage Msg(m, a, b); Msg.hWnd = hWnd; return Wnd->OnEvent(&Msg); } return DefWindowProcW(hWnd, m, a, b); } LRESULT CALLBACK LWindowsClass::SubClassRedir(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (m == WM_NCCREATE) { LPCREATESTRUCT Info = (LPCREATESTRUCT) b; LViewI *ViewI = 0; if (Info->lpCreateParams) { if (ViewI = (LViewI*) Info->lpCreateParams) { LView *View = ViewI->GetGView(); if (View) View->_View = hWnd; } } #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) ViewI); #else SetWindowLong(hWnd, GWL_USERDATA, (LONG) ViewI); #endif SetLgiMagic(hWnd); } LViewI *Wnd = (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif if (Wnd) { LMessage Msg(m, a, b); Msg.hWnd = hWnd; LMessage::Result Status = Wnd->OnEvent(&Msg); return Status; } return DefWindowProcW(hWnd, m, a, b); } LWindowsClass::LWindowsClass(const char *name) { Name(name); ZeroObj(Class); Class.lpfnWndProc = (WNDPROC) Redir; Class.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; Class.cbWndExtra = GWL_EXTRA_BYTES; Class.cbSize = sizeof(Class); ParentProc = 0; } LWindowsClass::~LWindowsClass() { UnregisterClassW(NameW(), LProcessInst()); Class.lpszClassName = NULL; } LWindowsClass *LWindowsClass::Create(const char *ClassName) { if (!LAppInst) return NULL; LApp::ClassContainer *Classes = LAppInst->GetClasses(); if (!Classes) return NULL; LWindowsClass *c = Classes->Find(ClassName); if (!c) { c = new LWindowsClass(ClassName); if (c) Classes->Add(ClassName, c); } return c; } bool LWindowsClass::IsSystem(const char *Cls) { if (!_stricmp(Cls, WC_BUTTONA) || !_stricmp(Cls, WC_COMBOBOXA) || !_stricmp(Cls, WC_STATICA)|| !_stricmp(Cls, WC_LISTBOXA)|| !_stricmp(Cls, WC_SCROLLBARA)|| !_stricmp(Cls, WC_HEADERA)|| !_stricmp(Cls, WC_LISTVIEWA)|| !_stricmp(Cls, WC_TREEVIEWA)|| !_stricmp(Cls, WC_COMBOBOXEXA)|| !_stricmp(Cls, WC_TABCONTROLA)|| !_stricmp(Cls, WC_IPADDRESSA)|| !_stricmp(Cls, WC_EDITA)) { return true; } return false; } bool LWindowsClass::Register() { bool Status = false; if (IsSystem(Name())) { ZeroObj(Class); Class.cbSize = sizeof(Class); Status = GetClassInfoExW(LProcessInst(), NameW(), &Class) != 0; LAssert(Status); } else // if (!Class.lpszClassName) { Class.hInstance = LProcessInst(); if (!Class.lpszClassName) Class.lpszClassName = NameW(); Status = RegisterClassExW(&Class) != 0; if (!Status) { auto err = GetLastError(); if (err == 1410) Status = true; else LAssert(Status); } } return Status; } bool LWindowsClass::SubClass(char *Parent) { bool Status = false; if (!Class.lpszClassName) { HBRUSH hBr = Class.hbrBackground; LAutoWString p(Utf8ToWide(Parent)); if (p) { if (GetClassInfoExW(LProcessInst(), p, &Class)) { ParentProc = Class.lpfnWndProc; if (hBr) { Class.hbrBackground = hBr; } Class.cbWndExtra = max(Class.cbWndExtra, GWL_EXTRA_BYTES); Class.hInstance = LProcessInst(); Class.lpfnWndProc = (WNDPROC) SubClassRedir; Class.lpszClassName = NameW(); Status = RegisterClassExW(&Class) != 0; LAssert(Status); } } } else Status = true; return Status; } LRESULT CALLBACK LWindowsClass::CallParent(HWND hWnd, UINT m, WPARAM a, LPARAM b) { if (!ParentProc) return 0; if (IsWindowUnicode(hWnd)) { return CallWindowProcW(ParentProc, hWnd, m, a, b); } else { return CallWindowProcA(ParentProc, hWnd, m, a, b); } } ////////////////////////////////////////////////////////////////////////////// LViewI *LWindowFromHandle(HWND hWnd) { if (hWnd) { SetLastError(0); int32 m = GetWindowLong(hWnd, GWL_LGI_MAGIC); #if 0 //def _DEBUG DWORD err = GetLastError(); if (err == 1413) { TCHAR name[256]; if (GetClassName(hWnd, name, sizeof(name))) { WNDCLASSEX cls; ZeroObj(cls); cls.cbSize = sizeof(WNDCLASSEX); if (GetClassInfoEx(LAppInst->GetInstance(), name, &cls)) { if (cls.cbWndExtra >= 8) { LAssert(!"Really?"); } } } } #endif if (m == LGI_GViewMagic) { return (LViewI*) #if _MSC_VER >= _MSC_VER_VS2005 GetWindowLongPtr(hWnd, GWLP_USERDATA); #else GetWindowLong(hWnd, GWL_USERDATA); #endif } } return 0; } ////////////////////////////////////////////////////////////////////////////// const char *LView::GetClass() { return "LView"; } void LView::_Delete() { if (_View && d->DropTarget) { RevokeDragDrop(_View); } #ifdef _DEBUG // Sanity check.. // LArray HasView; for (auto c: Children) { auto par = c->GetParent(); bool ok = ((LViewI*)par) == this || par == NULL; if (!ok) LAssert(!"heirachy error"); } #endif // Delete myself out of my parent's list if (d->Parent) { d->Parent->OnChildrenChanged(this, false); d->Parent->DelView(this); d->Parent = 0; d->ParentI = 0; } // Delete all children LViewI *c; while (c = Children[0]) { // If it has no parent, remove the pointer from the child list, // Because the child isn't going to do it... if (c->GetParent() == 0) Children.Delete(c); // Delete the child view DeleteObj(c); } // Delete the OS representation of myself if (_View && IsWindow(_View)) { WndFlags |= GWF_DESTRUCTOR; BOOL Status = DestroyWindow(_View); LAssert(Status != 0); } // NULL my handles and flags _View = 0; WndFlags = 0; // Remove static references to myself if (_Over == this) _Over = 0; if (_Capturing == this) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, this, GetClass()); #endif _Capturing = 0; } LWindow *Wnd = GetWindow(); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); // this should only exist in an ex-LWindow, due to the way // C++ deletes objects it needs to be here. DeleteObj(_Lock); } void LView::Quit(bool DontDelete) { if (_View) { if (!DontDelete) { WndFlags |= GWF_QUIT_WND; } DestroyWindow(_View); } } uint32_t LView::GetDlgCode() { return d->WndDlgCode; } void LView::SetDlgCode(uint32_t i) { d->WndDlgCode = i; } uint32_t LView::GetStyle() { return d->WndStyle; } void LView::SetStyle(uint32_t i) { d->WndStyle = i; } uint32_t LView::GetExStyle() { return d->WndExStyle; } void LView::SetExStyle(uint32_t i) { d->WndExStyle = i; } const char *LView::GetClassW32() { return d->WndClass; } void LView::SetClassW32(const char *c) { d->WndClass = c; } LWindowsClass *LView::CreateClassW32(const char *Class, HICON Icon, int AddStyles) { if (Class) { SetClassW32(Class); } if (GetClassW32()) { LWindowsClass *c = LWindowsClass::Create(GetClassW32()); if (c) { if (Icon) { c->Class.hIcon = Icon; } if (AddStyles) { c->Class.style |= AddStyles; } c->Register(); return c; } } return 0; } bool LView::IsAttached() { return _View && IsWindow(_View); } bool LView::Attach(LViewI *p) { bool Status = false; SetParent(p); LView *Parent = d->GetParent(); + auto IsWnd = dynamic_cast(this); if (Parent && !_Window) _Window = Parent->_Window; const char *ClsName = GetClassW32(); if (!ClsName) ClsName = GetClass(); if (ClsName) { // Real window with HWND bool Enab = Enabled(); // Check the class is created bool IsSystemClass = LWindowsClass::IsSystem(ClsName); LWindowsClass *Cls = LWindowsClass::Create(ClsName); if (Cls) { auto r = Cls->Register(); if (!r) { LAssert(0); } } else if (!IsSystemClass) return false; - LAssert(!Parent || Parent->Handle() != 0); + if (!IsWnd) + LAssert(!Parent || Parent->Handle() != 0); DWORD Style = GetStyle(); DWORD ExStyle = GetExStyle() & ~WS_EX_CONTROLPARENT; if (!TestFlag(WndFlags, GWF_SYS_BORDER)) ExStyle &= ~(WS_EX_CLIENTEDGE | WS_EX_WINDOWEDGE); auto Text = LBase::NameW(); LAutoWString WCls(Utf8ToWide(ClsName)); bool hasCaption = (GetStyle() & WS_CAPTION) != 0; int Shadow = WINDOWS_SHADOW_AMOUNT; /* LgiTrace("%p/%s::Attach %s\n", this, Name(), Pos.GetStr()); LRect r = Pos; r.x2 += Shadow; r.y2 += Shadow; LgiTrace(" %s\n", r.GetStr()); */ _View = CreateWindowExW(ExStyle, WCls, Text, Style, Pos.x1, Pos.y1, Pos.X(), Pos.Y(), Parent ? Parent->Handle() : 0, NULL, LProcessInst(), (LViewI*) this); #ifdef _DEBUG if (!_View) { DWORD e = GetLastError(); LgiTrace("%s:%i - CreateWindowExW failed with 0x%x\n", _FL, e); LAssert(!"CreateWindowEx failed"); } #endif if (_View) { Status = (_View != NULL); if (d->Font) SendMessage(_View, WM_SETFONT, (WPARAM) d->Font->Handle(), 0); if (d->DropTarget) RegisterDragDrop(_View, d->DropTarget); if (TestFlag(WndFlags, GWF_FOCUS)) SetFocus(_View); if (d->WantsPulse > 0) { SetPulse(d->WantsPulse); d->WantsPulse = -1; } } OnAttach(); } else { // Virtual window (no HWND) Status = true; } if (Status && d->Parent) { auto isWnd = dynamic_cast(this); if (!isWnd) { if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } } return Status; } bool LView::Detach() { bool Status = false; if (_Window) { LWindow *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } if (d->Parent) { d->Parent->DelView(this); d->Parent->OnChildrenChanged(this, false); d->Parent = 0; d->ParentI = 0; Status = true; WndFlags &= ~GWF_FOCUS; if (_Capturing == this) { if (_View) ReleaseCapture(); #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, this, GetClass()); #endif _Capturing = 0; } if (_View) { WndFlags &= ~GWF_QUIT_WND; BOOL Status = DestroyWindow(_View); DWORD Err = GetLastError(); LAssert(Status != 0); } } return Status; } LRect &LView::GetClient(bool InClientSpace) { static LRect Client; if (_View) { RECT rc; GetClientRect(_View, &rc); Client = rc; } else { Client.Set(0, 0, Pos.X()-1, Pos.Y()-1); if (dynamic_cast(this) || dynamic_cast(this)) { Client.x1 += GetSystemMetrics(SM_CXFRAME); Client.x2 -= GetSystemMetrics(SM_CXFRAME); Client.y1 += GetSystemMetrics(SM_CYFRAME) + GetSystemMetrics(SM_CYCAPTION); Client.y2 -= GetSystemMetrics(SM_CYFRAME); } else if (Sunken() || Raised()) { Client.Inset(_BorderSize, _BorderSize); } } if (InClientSpace) Client.Offset(-Client.x1, -Client.y1); return Client; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } #ifndef GCL_HCURSOR #define GCL_HCURSOR -12 #endif bool LgiToWindowsCursor(OsView Hnd, LCursor Cursor) { char16 *Set = 0; switch (Cursor) { case LCUR_UpArrow: Set = IDC_UPARROW; break; case LCUR_Cross: Set = IDC_CROSS; break; case LCUR_Wait: Set = IDC_WAIT; break; case LCUR_Ibeam: Set = IDC_IBEAM; break; case LCUR_SizeVer: Set = IDC_SIZENS; break; case LCUR_SizeHor: Set = IDC_SIZEWE; break; case LCUR_SizeBDiag: Set = IDC_SIZENESW; break; case LCUR_SizeFDiag: Set = IDC_SIZENWSE; break; case LCUR_SizeAll: Set = IDC_SIZEALL; break; case LCUR_PointingHand: { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && Ver[0] >= 5) { #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif Set = IDC_HAND; } // else not supported break; } case LCUR_Forbidden: Set = IDC_NO; break; // Not impl case LCUR_SplitV: break; case LCUR_SplitH: break; case LCUR_Blank: break; } HCURSOR cur = LoadCursor(0, Set ? Set : IDC_ARROW); SetCursor(cur); if (Hnd) SetWindowLongPtr(Hnd, GCL_HCURSOR, (LONG_PTR)cur); return true; } bool LView::PointToScreen(LPoint &p) { POINT pt = {p.x, p.y}; LViewI *t = this; while ( t && t->GetParent() && !t->Handle()) { pt.x += t->GetPos().x1; pt.y += t->GetPos().y1; t = t->GetParent(); } ClientToScreen(t->Handle(), &pt); p.x = pt.x; p.y = pt.y; return true; } bool LView::PointToView(LPoint &p) { POINT pt = {p.x, p.y}; LViewI *t = this; while ( t && t->GetParent() && !t->Handle()) { pt.x -= t->GetPos().x1; pt.y -= t->GetPos().y1; t = t->GetParent(); } ScreenToClient(t->Handle(), &pt); p.x = pt.x; p.y = pt.y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { // position POINT p; GetCursorPos(&p); if (!ScreenCoords) { ScreenToClient(_View, &p); } m.x = p.x; m.y = p.y; m.Target = this; // buttons m.Flags = ((GetAsyncKeyState(VK_LBUTTON)&0x8000) ? LGI_EF_LEFT : 0) | ((GetAsyncKeyState(VK_MBUTTON)&0x8000) ? LGI_EF_MIDDLE : 0) | ((GetAsyncKeyState(VK_RBUTTON)&0x8000) ? LGI_EF_RIGHT : 0) | ((GetAsyncKeyState(VK_CONTROL)&0x8000) ? LGI_EF_CTRL : 0) | ((GetAsyncKeyState(VK_MENU) &0x8000) ? LGI_EF_ALT : 0) | ((GetAsyncKeyState(VK_LWIN) &0x8000) ? LGI_EF_SYSTEM : 0) | ((GetAsyncKeyState(VK_RWIN) &0x8000) ? LGI_EF_SYSTEM : 0) | ((GetAsyncKeyState(VK_SHIFT) &0x8000) ? LGI_EF_SHIFT : 0); if (m.Flags & (LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT)) { m.Flags |= LGI_EF_DOWN; } return true; } bool LView::SetPos(LRect &p, bool Repaint) { bool Status = true; LRect OldPos = Pos; if (Pos != p) { Pos = p; if (_View) { HWND hOld = GetFocus(); bool WasVis = IsWindowVisible(_View) != 0; int Shadow = WINDOWS_SHADOW_AMOUNT; In_SetWindowPos = true; Status = SetWindowPos( _View, NULL, Pos.x1, Pos.y1, Pos.X() + Shadow, Pos.Y() + Shadow, // ((Repaint) ? 0 : SWP_NOREDRAW) | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER) != 0; In_SetWindowPos = false; } else if (GetParent()) { OnPosChange(); } if (Repaint) { Invalidate(); } } return Status; } bool LView::Invalidate(LRect *r, bool Repaint, bool Frame) { if (_View) { bool Status = false; if (Frame) { RedrawWindow( _View, NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN | ((Repaint) ? RDW_UPDATENOW : 0)); } else { if (r) { Status = InvalidateRect(_View, &((RECT)*r), false) != 0; } else { RECT c = GetClient(); Status = InvalidateRect(_View, &c, false) != 0; } } if (Repaint) { UpdateWindow(_View); } return Status; } else { LRect Up; LViewI *p = this; if (r) { Up = *r; } else { Up.Set(0, 0, Pos.X()-1, Pos.Y()-1); } if (dynamic_cast(this)) return true; while (p && !p->Handle()) { LViewI *Par = p->GetParent(); LView *VPar = Par?Par->GetGView():0; LRect w = p->GetPos(); LRect c = p->GetClient(false); if (Frame && p == this) Up.Offset(w.x1, w.y1); else Up.Offset(w.x1 + c.x1, w.y1 + c.y1); p = Par; } if (p && p->Handle()) { return p->Invalidate(&Up, Repaint); } } return false; } void CALLBACK LView::TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, uint32_t dwTime) { LView *View = (LView*) idEvent; if (View) { View->OnPulse(); } } void LView::SetPulse(int Length) { if (_View) { if (Length > 0) { d->TimerId = SetTimer(_View, (UINT_PTR) this, Length, (TIMERPROC) TimerProc); } else { KillTimer(_View, d->TimerId); d->TimerId = 0; } } else { d->WantsPulse = Length; } } static int ConsumeTabKey = 0; bool SysOnKey(LView *w, LMessage *m) { if (m->a == VK_TAB && (m->m == WM_KEYDOWN || m->m == WM_SYSKEYDOWN) ) { if (!TestFlag(w->d->WndDlgCode, DLGC_WANTTAB) && !TestFlag(w->d->WndDlgCode, DLGC_WANTALLKEYS)) { // push the focus to the next control bool Shifted = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; LViewI *Wnd = GetNextTabStop(w, Shifted); if (Wnd) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\\n", _FL, Wnd->Handle()); } ConsumeTabKey = 2; ::SetFocus(Wnd->Handle()); return true; } } } return false; } #ifdef _MSC_VER #include "vsstyle.h" void LView::DrawThemeBorder(LSurface *pDC, LRect &r) { if (!d->hTheme) d->hTheme = OpenThemeData(_View, VSCLASS_EDIT); if (d->hTheme) { RECT rc = r; int StateId; if (!Enabled()) StateId = EPSN_DISABLED; else if (GetFocus() == _View) StateId = EPSN_FOCUSED; else StateId = EPSN_NORMAL; // LgiTrace("ThemeDraw %s: %i\n", GetClass(), StateId); RECT clip[4]; clip[0] = LRect(r.x1, r.y1, r.x1 + 1, r.y2); // left clip[1] = LRect(r.x1 + 2, r.y1, r.x2 - 2, r.y1 + 1); // top clip[2] = LRect(r.x2 - 1, r.y1, r.x2, r.y2); // right clip[3] = LRect(r.x1 + 2, r.y2 - 1, r.x2 - 2, r.y2); // bottom LColour cols[4] = { LColour(255, 0, 0), LColour(0, 255, 0), LColour(0, 0, 255), LColour(255, 255, 0) }; for (int i=0; iColour(cols[i]); pDC->Rectangle(&tmp); #else DrawThemeBackground(d->hTheme, pDC->Handle(), EP_EDITBORDER_NOSCROLL, StateId, &rc, &clip[i]); #endif } pDC->Colour(L_MED); pDC->Set(r.x1, r.y1); pDC->Set(r.x2, r.y1); pDC->Set(r.x1, r.y2); pDC->Set(r.x2, r.y2); r.Inset(2, 2); } else { LWideBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); d->IsThemed = false; } } #else void LView::DrawThemeBorder(LSurface *pDC, LRect &r) { LWideBorder(pDC, r, DefaultSunkenEdge); } #endif bool IsKeyChar(LKey &k, int vk) { if (k.Ctrl() || k.Alt() || k.System()) return false; switch (vk) { case VK_BACK: case VK_TAB: case VK_RETURN: case VK_SPACE: case 0xba: // ; case 0xbb: // = case 0xbc: // , case 0xbd: // - case 0xbe: // . case 0xbf: // / case 0xc0: // ` case 0xdb: // [ case 0xdc: // | case 0xdd: // ] case 0xde: // ' return true; } if (vk >= VK_NUMPAD0 && vk <= VK_DIVIDE) return true; if (vk >= '0' && vk <= '9') return true; if (vk >= 'A' && vk <= 'Z') return true; return false; } #define KEY_FLAGS (~(MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) LMessage::Result LView::OnEvent(LMessage *Msg) { int Status = 0; if (Msg->Msg() == MouseRollMsg) { HWND hFocus = GetFocus(); if (_View) { int Flags = ((GetKeyState(VK_SHIFT)&0xF000) ? VK_SHIFT : 0) | ((GetKeyState(VK_CONTROL)&0xF000) ? VK_CONTROL : 0); PostMessage(hFocus, WM_MOUSEWHEEL, MAKELONG(Flags, (short)Msg->a), Msg->b); } return 0; } for (auto target: d->EventTargets) { if (target->Msgs.Length() == 0 || target->Msgs.Find(Msg->Msg())) target->OnEvent(Msg); } if (_View) { switch (Msg->m) { #if 1 case WM_CTLCOLORBTN: case WM_CTLCOLOREDIT: case WM_CTLCOLORSTATIC: { HDC hdc = (HDC)Msg->A(); HWND hwnd = (HWND)Msg->B(); LViewI *v = FindControl(hwnd); LView *gv = v ? v->GetGView() : NULL; if (gv) { int Depth = dynamic_cast(gv) ? 1 : 10; LColour Fore = gv->StyleColour(LCss::PropColor, LColour(), Depth); LColour Back = gv->StyleColour(LCss::PropBackgroundColor, LColour(), Depth); if (Fore.IsValid()) { COLORREF c = RGB(Fore.r(), Fore.g(), Fore.b()); SetTextColor(hdc, c); } if (Back.IsValid()) { COLORREF c = RGB(Back.r(), Back.g(), Back.b()); SetBkColor(hdc, c); SetDCBrushColor(hdc, c); } if (Fore.IsValid() || Back.IsValid()) { #if !defined(DC_BRUSH) #define DC_BRUSH 18 #endif return (LRESULT) GetStockObject(DC_BRUSH); } } goto ReturnDefaultProc; return 0; } #endif case 5700: { // I forget what this is for... break; } case WM_ERASEBKGND: { return 1; } case WM_GETFONT: { LFont *f = GetFont(); if (!f || f == LSysFont) return (LMessage::Result) LSysFont->Handle(); return (LMessage::Result) f->Handle(); break; } case WM_MENUCHAR: case WM_MEASUREITEM: { return LMenu::_OnEvent(Msg); break; } case WM_DRAWITEM: { DRAWITEMSTRUCT *di = (DRAWITEMSTRUCT*)Msg->B(); if (di) { if (di->CtlType == ODT_MENU) { return LMenu::_OnEvent(Msg); } /* else if (di->CtlType == ODT_BUTTON) { LView *b; if (CastHwnd(b, di->hwndItem) && b->GetCss()) { LScreenDC dc(di->hDC, di->hwndItem); switch (di->itemAction) { case ODA_DRAWENTIRE: { LRect c = di->rcItem; LMemDC m(c.X(), c.Y(), GdcD->GetColourSpace()); HDC hdc = m.StartDC(); m.Colour(LColour(255, 0, 255)); m.Line(0, 0, m.X()-1, m.Y()-1); LONG s = GetWindowLong(_View, GWL_STYLE); SetWindowLong(_View, GWL_STYLE, (s & ~BS_TYPEMASK) | BS_PUSHBUTTON); SendMessage(_View, WM_PRINT, (WPARAM)hdc, PRF_ERASEBKGND|PRF_CLIENT); SetWindowLong(_View, GWL_STYLE, (s & ~BS_TYPEMASK) | BS_OWNERDRAW); m.EndDC(); dc.Blt(0, 0, &m); break; } case ODA_FOCUS: { break; } case ODA_SELECT: { break; } } return true; } } */ } if (!(WndFlags & GWF_DIALOG)) goto ReturnDefaultProc; break; } case WM_ENABLE: { Invalidate(&Pos); break; } case WM_HSCROLL: case WM_VSCROLL: { LViewI *Wnd = FindControl((HWND) Msg->b); if (Wnd) { Wnd->OnEvent(Msg); } break; } case WM_GETDLGCODE: { // we handle all tab control stuff return DLGC_WANTALLKEYS; // d->WndDlgCode | DLGC_WANTTAB; } case WM_MOUSEWHEEL: { // short fwKeys = LOWORD(Msg->a); // key flags short zDelta = (short) HIWORD(Msg->a); // wheel rotation int nScrollLines = - _lgi_mouse_wheel_lines(); double Lines = ((double)zDelta * (double)nScrollLines) / WHEEL_DELTA; if (ABS(Lines) < 1.0) Lines *= 1.0 / ABS(Lines); // LgiTrace("Lines = %g, zDelta = %i, nScrollLines = %i\n", Lines, zDelta, nScrollLines); // Try giving the event to the current window... if (!OnMouseWheel(Lines)) { // Find the window under the cursor... and try giving it the mouse wheel event short xPos = (short) LOWORD(Msg->b); // horizontal position of pointer short yPos = (short) HIWORD(Msg->b); // vertical position of pointer POINT Point = {xPos, yPos}; HWND hUnder = ::WindowFromPoint(Point); HWND hParent = ::GetParent(hUnder); if (hUnder && hUnder != _View && // Don't want to send ourselves a message... hParent != _View) // WM_MOUSEWHEEL will propagate back up to us and cause an infinite loop { // Do a post event in case the window is deleting... at least it won't crash. PostMessage(hUnder, Msg->m, Msg->a, Msg->b); } } return 0; } case M_CHANGE: { LWindow *w = GetWindow(); LAutoPtr note((LNotification*)Msg->B()); LViewI *Ctrl = w ? w->FindControl((int)Msg->a) : 0; if (Ctrl) { LAssert(note.Get() != NULL); return OnNotify(Ctrl, note ? *note : LNotifyNull); } else { LgiTrace("Ctrl %i not found.\n", Msg->a); } break; } case M_COMMAND: { // LViewI *Ci = FindControl((HWND) Msg->b); // LView *Ctrl = Ci ? Ci->GetGView() : 0; LView *Ctrl; if (Msg->b && CastHwnd(Ctrl, (HWND)Msg->b)) { short Code = HIWORD(Msg->a); switch (Code) { case CBN_CLOSEUP: { PostMessage(_View, WM_COMMAND, MAKELONG(Ctrl->GetId(), CBN_EDITCHANGE), Msg->b); break; } case CBN_EDITCHANGE: // COMBO { Ctrl->SysOnNotify(Msg->Msg(), Code); OnNotify(Ctrl, LNotifyValueChanged); break; } /* case BN_CLICKED: // BUTTON case EN_CHANGE: // EDIT */ default: { Ctrl->SysOnNotify(Msg->Msg(), Code); break; } } } break; } case WM_NCDESTROY: { #if _MSC_VER >= _MSC_VER_VS2005 SetWindowLongPtr(_View, GWLP_USERDATA, 0); #else SetWindowLong(_View, GWL_USERDATA, 0); #endif _View = NULL; if (WndFlags & GWF_QUIT_WND) { delete this; } break; } case WM_CLOSE: { if (OnRequestClose(false)) { Quit(); } break; } case WM_DESTROY: { OnDestroy(); break; } case WM_CREATE: { SetId(d->CtrlId); LWindow *w = GetWindow(); if (w && w->GetFocus() == this) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p) (%s)\\n", __FILE__, __LINE__, Handle(), GetClass()); } SetFocus(_View); } } if (TestFlag(GViewFlags, GWF_DROP_TARGET)) { DropTarget(true); } OnCreate(); break; } case WM_SETFOCUS: { LWindow *w = GetWindow(); if (w) { w->SetFocus(this, LWindow::GainFocus); } else { // This can happen in popup sub-trees of views. Where the focus // is tracked separately from the main LWindow. OnFocus(true); Invalidate((LRect*)NULL, false, true); } break; } case WM_KILLFOCUS: { LWindow *w = GetWindow(); if (w) { w->SetFocus(this, LWindow::LoseFocus); } else { // This can happen when the LWindow is being destroyed Invalidate((LRect*)NULL, false, true); OnFocus(false); } break; } case WM_WINDOWPOSCHANGED: { if (!IsIconic(_View)) { WINDOWPOS *Info = (LPWINDOWPOS) Msg->b; if (Info) { if (Info->x == -32000 && Info->y == -32000) { #if 0 LgiTrace("WM_WINDOWPOSCHANGED %i,%i,%i,%i (icon=%i)\\n", Info->x, Info->y, Info->cx, Info->cy, IsIconic(Handle())); #endif } else { int Shadow = WINDOWS_SHADOW_AMOUNT; LRect r; r.ZOff(Info->cx-1, Info->cy-1); r.Offset(Info->x, Info->y); if (r.Valid() && r != Pos) { Pos = r; /* LgiTrace("%p/%s::PosChange %s\n", this, Name(), Pos.GetStr()); Pos.x2 -= Shadow; Pos.y2 -= Shadow; LgiTrace(" %s\n", Pos.GetStr()); */ } } } OnPosChange(); } if (!(WndFlags & GWF_DIALOG)) { goto ReturnDefaultProc; } break; } case WM_CAPTURECHANGED: { LViewI *Wnd; if (Msg->B() && CastHwnd(Wnd, (HWND)Msg->B())) { if (Wnd != _Capturing) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> %p/%s\n", _FL, _Capturing, _Capturing?_Capturing->GetClass():0, Wnd, Wnd?Wnd->GetClass() : 0); #endif _Capturing = Wnd; } } else if (_Capturing) { #if DEBUG_CAPTURE LgiTrace("%s:%i - _Capturing %p/%s -> NULL\n", _FL, _Capturing, _Capturing?_Capturing->GetClass():0); #endif _Capturing = NULL; } break; } case M_MOUSEENTER: { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = 0; LViewI *MouseOver = WindowFromPoint(Ms.x, Ms.y); if (MouseOver && _Over != MouseOver && !(MouseOver == this || MouseOver->Handle() == 0)) { if (_Capturing) { if (MouseOver == _Capturing) { Ms = lgi_adjust_click(Ms, _Capturing); _Capturing->OnMouseEnter(Ms); } } else { if (_Over) { LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseExit(m); #if DEBUG_OVER LgiTrace("Enter.LoseOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif } _Over = MouseOver; if (_Over) { #if DEBUG_OVER LgiTrace("Enter.GetOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseEnter(m); } } } break; } case M_MOUSEEXIT: { if (_Over) { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = 0; bool Mine = false; if (_Over->Handle()) { Mine = _Over == this; } else { for (LViewI *o = _Capturing ? _Capturing : _Over; o; o = o->GetParent()) { if (o == this) { Mine = true; break; } } } if (Mine) { if (_Capturing) { LMouse m = lgi_adjust_click(Ms, _Capturing); _Capturing->OnMouseExit(m); } else { #if DEBUG_OVER LgiTrace("Exit.LoseOver=%p '%-20s'\n", _Over, _Over->Name()); #endif _Over->OnMouseExit(Ms); _Over = 0; } } } break; } case WM_MOUSEMOVE: { LMouse Ms; Ms.Target = this; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags(); Ms.IsMove(true); if (TestFlag(Msg->a, MK_LBUTTON)) SetFlag(Ms.Flags, LGI_EF_LEFT); if (TestFlag(Msg->a, MK_RBUTTON)) SetFlag(Ms.Flags, LGI_EF_RIGHT); if (TestFlag(Msg->a, MK_MBUTTON)) SetFlag(Ms.Flags, LGI_EF_MIDDLE); SetKeyFlag(Ms.Flags, VK_MENU, MK_ALT); Ms.Down((Msg->a & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) != 0); LViewI *MouseOver = WindowFromPoint(Ms.x, Ms.y); if (_Over != MouseOver) { if (_Over) { #if DEBUG_OVER LgiTrace("Move.LoseOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseExit(m); } _Over = MouseOver; if (_Over) { LMouse m = lgi_adjust_click(Ms, _Over); _Over->OnMouseEnter(m); #if DEBUG_OVER LgiTrace("Move.GetOver=%p/%s '%-20s'\n", _Over, _Over->GetClass(), _Over->Name()); #endif } } // int CurX = Ms.x, CurY = Ms.y; LCursor Cursor = (_Over ? _Over : this)->GetCursor(Ms.x, Ms.y); LgiToWindowsCursor(_View, Cursor); #if 0 LgiTrace("WM_MOUSEMOVE %i,%i target=%p/%s, over=%p/%s, cap=%p/%s\n", Ms.x, Ms.y, Ms.Target, Ms.Target?Ms.Target->GetClass():0, _Over, _Over?_Over->GetClass():0, _Capturing, _Capturing?_Capturing->GetClass():0); #endif if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else return 0; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) { Ms.Target->OnMouseMove(Ms); } break; } case WM_NCHITTEST: { POINT Pt = { LOWORD(Msg->b), HIWORD(Msg->b) }; ScreenToClient(_View, &Pt); int Hit = OnHitTest(Pt.x, Pt.y); if (Hit >= 0) { // LgiTrace("%I64i Hit=%i\n", LCurrentTime(), Hit); return Hit; } if (!(WndFlags & GWF_DIALOG)) { goto ReturnDefaultProc; } break; } case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: case WM_LBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_LEFT; Ms.Down(Msg->m != WM_LBUTTONUP); Ms.Double(Msg->m == WM_LBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; #if DEBUG_MOUSE_CLICKS LString Msg; Msg.Printf("%s.Click", Ms.Target->GetClass()); Ms.Trace(Msg); #endif LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_RBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_RIGHT; Ms.Down(Msg->m != WM_RBUTTONUP); Ms.Double(Msg->m == WM_RBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; #if DEBUG_MOUSE_CLICKS LString Msg; Msg.Printf("%s.Click", Ms.Target->GetClass()); Ms.Trace(Msg); #endif LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_MBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONUP: { LMouse Ms; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_MIDDLE; Ms.Down(Msg->m != WM_MBUTTONUP); Ms.Double(Msg->m == WM_MBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_XBUTTONDBLCLK: case WM_XBUTTONDOWN: case WM_XBUTTONUP: { LMouse Ms; int Clicked = (Msg->a >> 16) & 0xffff; Ms.x = (short) (Msg->b&0xFFFF); Ms.y = (short) (Msg->b>>16); Ms.Flags = _lgi_get_key_flags() | LGI_EF_MIDDLE; Ms.Button1(TestFlag(Clicked, XBUTTON1)); Ms.Button2(TestFlag(Clicked, XBUTTON2)); Ms.Down(Msg->m != WM_XBUTTONUP); Ms.Double(Msg->m == WM_XBUTTONDBLCLK); if (_Capturing) Ms = lgi_adjust_click(Ms, _Capturing, true); else if (_Over) Ms = lgi_adjust_click(Ms, _Over); else Ms.Target = this; LWindow *Wnd = GetWindow(); if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(Ms.Target), Ms)) Ms.Target->OnMouseClick(Ms); break; } case WM_SYSKEYUP: case WM_SYSKEYDOWN: case WM_KEYDOWN: case WM_KEYUP: { static char AltCode[32]; bool IsDialog = TestFlag(WndFlags, GWF_DIALOG); bool IsDown = Msg->m == WM_KEYDOWN || Msg->m == WM_SYSKEYDOWN; int KeyFlags = _lgi_get_key_flags(); HWND hwnd = _View; if (SysOnKey(this, Msg)) { // LgiTrace("SysOnKey true, Msg=0x%x %x,%x\n", Msg->m, Msg->a, Msg->b); return 0; } else { // Key LKey Key((int)Msg->a, (int)Msg->b); Key.Flags = KeyFlags; Key.Down(IsDown); Key.IsChar = false; if (Key.Ctrl()) { Key.c16 = (char16)Msg->a; } if (Key.c16 == VK_TAB && ConsumeTabKey) { ConsumeTabKey--; } else { LWindow *Wnd = GetWindow(); if (Wnd) { if (Key.Alt() || Key.Ctrl() || (Key.c16 < 'A' || Key.c16 > 'Z')) { Wnd->HandleViewKey(this, Key); } } else { OnKey(Key); } } if (Msg->m == WM_SYSKEYUP || Msg->m == WM_SYSKEYDOWN) { if (Key.vkey >= VK_F1 && Key.vkey <= VK_F12 && Key.Alt() == false) { // So in LgiIde if you press F10 (debug next) you get a hang // sometimes in DefWindowProc. Until I figure out what's going // on this code exits before calling DefWindowProc without // breaking other WM_SYSKEY* functionality (esp Alt+F4). return 0; } } } if (!IsDialog) { // required for Alt-Key function (eg Alt-F4 closes window) goto ReturnDefaultProc; } break; } #if OLD_WM_CHAR_MODE case WM_CHAR: { LKey Key((int)Msg->a, (int)Msg->b); Key.Flags = _lgi_get_key_flags(); Key.Down(true); Key.IsChar = true; bool Shift = Key.Shift(); bool Caps = TestFlag(Key.Flags, LGI_EF_CAPS_LOCK); if (!(Shift ^ Caps)) { Key.c16 = ToLower(Key.c16); } else { Key.c16 = ToUpper(Key.c16); } if (Key.c16 == LK_TAB && ConsumeTabKey) { ConsumeTabKey--; } else { LWindow *Wnd = GetWindow(); if (Wnd) { Wnd->HandleViewKey(this, Key); } else { OnKey(Key); } } break; } #endif case M_SET_WND_STYLE: { SetWindowLong(Handle(), GWL_STYLE, (LONG)Msg->b); SetWindowPos( Handle(), 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOZORDER | SWP_NOSIZE | SWP_FRAMECHANGED); break; } case WM_PAINT: { _Paint(); break; } case WM_NCPAINT: { if (GetWindow() != this && !TestFlag(WndFlags, GWF_SYS_BORDER)) { HDC hDC = GetWindowDC(_View); LScreenDC Dc(hDC, _View, true); LRect p(0, 0, Dc.X()-1, Dc.Y()-1); OnNcPaint(&Dc, p); } goto ReturnDefaultProc; break; } case WM_NCCALCSIZE: { LMessage::Param Status = 0; int Edge = (Sunken() || Raised()) ? _BorderSize : 0; RECT *rc = NULL; if (Msg->a) { NCCALCSIZE_PARAMS *p = (NCCALCSIZE_PARAMS*) Msg->b; rc = p->rgrc; } else { rc = (RECT*)Msg->b; } if (!(WndFlags & GWF_DIALOG)) { Status = DefWindowProcW(_View, Msg->m, Msg->a, Msg->b); } if (Edge && rc && !TestFlag(WndFlags, GWF_SYS_BORDER)) { rc->left += Edge; rc->top += Edge; rc->right -= Edge; rc->bottom -= Edge; return 0; } return Status; } case WM_NOTIFY: { NMHDR *Hdr = (NMHDR*)Msg->B(); if (Hdr) { LView *Wnd; if (CastHwnd(Wnd, Hdr->hwndFrom)) Wnd->SysOnNotify(Msg->Msg(), Hdr->code); } break; } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } default: { if (!(WndFlags & GWF_DIALOG)) goto ReturnDefaultProc; break; } } } return 0; ReturnDefaultProc: #ifdef _DEBUG uint64 start = LCurrentTime(); #endif LRESULT r = DefWindowProcW(_View, Msg->m, Msg->a, Msg->b); #ifdef _DEBUG uint64 now = LCurrentTime(); if (now - start > 1000) { LgiTrace("DefWindowProc(0x%.4x, %i, %i) took %ims\n", Msg->m, Msg->a, Msg->b, (int)(now - start)); } #endif return r; } LViewI *LView::FindControl(OsView hCtrl) { if (_View == hCtrl) { return this; } for (List::I i = Children.begin(); i.In(); i++) { LViewI *Ctrl = (*i)->FindControl(hCtrl); if (Ctrl) return Ctrl; } return 0; }