diff --git a/Ide/Code/LgiIde.cpp b/Ide/Code/LgiIde.cpp --- a/Ide/Code/LgiIde.cpp +++ b/Ide/Code/LgiIde.cpp @@ -1,4658 +1,4668 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Mdi.h" #include "lgi/common/Token.h" #include "lgi/common/XmlTree.h" #include "lgi/common/Panel.h" #include "lgi/common/Button.h" #include "lgi/common/TabView.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/Edit.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Combo.h" #include "lgi/common/CheckBox.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Box.h" #include "lgi/common/SubProcess.h" #include "lgi/common/About.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #include "lgi/common/FileSelect.h" #include "lgi/common/SubProcess.h" #include "LgiIde.h" #include "FtpThread.h" #include "FindSymbol.h" #include "Debugger.h" #include "ProjectNode.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #define USE_HAIKU_PULSE_HACK 0 #define OPT_ENTIRE_SOLUTION "SearchSolution" #define OPT_SPLIT_PX "SplitPos" #define OPT_OUTPUT_PX "OutputPx" #define OPT_FIX_RENAMED "FixRenamed" #define OPT_RENAMED_SYM "RenamedSym" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public LDialog { AppWnd *App; LList *Lst; public: FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDC_FIND_PROJECT_FILE)) { MoveSameScreen(App); LViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; RegisterHook(this, LKeyEvents, 0); } } bool OnViewKey(LView *v, LKey &k) { switch (k.vkey) { case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { return Lst->OnKey(k); break; } case LK_RETURN: { if (k.Down()) { LListItem *i = Lst->GetSelected(); if (i) { const char *Ref = i->GetText(0); App->GotoReference(Ref, 1, false); } EndModal(1); return true; } break; } case LK_ESCAPE: { if (k.Down()) { EndModal(0); return true; } break; } } return false; } void Search(const char *s) { IdeProject *p = App->RootProject(); if (!p || !s) return; LArray Matches, Nodes; List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s); Lst->Empty(); for (auto m: Matches) { LListItem *li = new LListItem; LString Fn = m->GetFileName(); #ifdef WINDOWS Fn = Fn.Replace("/","\\"); #else Fn = Fn.Replace("\\","/"); #endif m->GetProject()->CheckExists(Fn); li->SetText(Fn); Lst->Insert(li); } Lst->ResizeColumnsToContent(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_FILES: if (n.Type == LNotifyItemDoubleClick) { LListItem *i = Lst->GetSelected(); if (i) { App->GotoReference(i->GetText(0), 1, false); EndModal(1); } } break; case IDC_TEXT: if (n.Type != LNotifyReturnKey) Search(c->Name()); break; case IDCANCEL: EndModal(0); break; } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// char AppName[] = "LgiIde"; char *dirchar(char *s, bool rev = false) { if (rev) { char *last = 0; while (s && *s) { if (*s == '/' || *s == '\\') last = s; s++; } return last; } else { while (s && *s) { if (*s == '/' || *s == '\\') return s; s++; } } return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class AppDependency : public LTreeItem { char *File; bool Loaded; LTreeItem *Fake; public: AppDependency(const char *file) { File = NewStr(file); char *d = strrchr(File, DIR_CHAR); Loaded = false; Insert(Fake = new LTreeItem); if (LFileExists(File)) { SetText(d?d+1:File); } else { char s[256]; sprintf(s, "%s (missing)", d?d+1:File); SetText(s); } } ~AppDependency() { DeleteArray(File); } char *GetFile() { return File; } void Copy(LStringPipe &p, int Depth = 0) { { char s[1024]; ZeroObj(s); memset(s, ' ', Depth * 4); sprintf(s+(Depth*4), "[%c] %s\n", Expanded() ? '-' : '+', GetText(0)); p.Push(s); } if (Loaded) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { ((AppDependency*)i)->Copy(p, Depth+1); } } } char *Find(const char *Paths, char *e) { LToken Path(Paths, LGI_PATH_SEPARATOR); for (int p=0; pSunken(true); Root = new AppDependency(File); if (Root) { t->Insert(Root); Root->Expanded(true); } Children.Insert(t); Children.Insert(new LButton(IDC_COPY, 10, t->LView::GetPos().y2 + 10, 60, 20, "Copy")); Children.Insert(new LButton(IDOK, 80, t->LView::GetPos().y2 + 10, 60, 20, "Ok")); } DoModal(NULL); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_COPY: { if (Root) { LStringPipe p; Root->Copy(p); char *s = p.NewStr(); if (s) { LClipBoard c(this); c.Text(s); DeleteArray(s); } break; } break; } case IDOK: { EndModal(0); break; } } return 0; } }; ////////////////////////////////////////////////////////////////////////////////////////// class DebugTextLog : public LTextLog { public: DebugTextLog(int id) : LTextLog(id) { } void PourText(size_t Start, ssize_t Len) override { auto Ts = LCurrentTime(); LTextView3::PourText(Start, Len); auto Dur = LCurrentTime() - Ts; if (Dur > 1500) { // Yo homes, too much text bro... Name(NULL); } else { for (auto l: Line) { char16 *t = Text + l->Start; if (l->Len > 5 && !StrnicmpW(t, L"(gdb)", 5)) { l->c.Rgb(0, 160, 0); } else if (l->Len > 1 && t[0] == '[') { l->c.Rgb(192, 192, 192); } } } } }; WatchItem::WatchItem(IdeOutput *out, const char *Init) { Out = out; Expanded(false); if (Init) SetText(Init); Insert(PlaceHolder = new LTreeItem); } WatchItem::~WatchItem() { } bool WatchItem::SetValue(LVariant &v) { char *Str = v.CastString(); if (ValidStr(Str)) SetText(Str, 2); else LTreeItem::SetText(NULL, 2); return true; } bool WatchItem::SetText(const char *s, int i) { if (ValidStr(s)) { LTreeItem::SetText(s, i); if (i == 0 && Tree && Tree->GetWindow()) { LViewI *Tabs = Tree->GetWindow()->FindControl(IDC_DEBUG_TAB); if (Tabs) Tabs->SendNotify(LNotifyValueChanged); } return true; } if (i == 0) delete this; return false; } void WatchItem::OnExpand(bool b) { if (b && PlaceHolder) { // Do something } } class BuildLog : public LTextLog { public: BuildLog(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { List::I it = LTextView3::Line.begin(); for (LTextLine *ln = *it; ln; ln = *++it) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; char16 *Err = Strnistr(t, L"error", ln->Len); char16 *Undef = Strnistr(t, L"undefined reference", ln->Len); char16 *Warn = Strnistr(t, L"warning", ln->Len); if ( (Err && strchr(":[", Err[5])) || (Undef != NULL) ) ln->c.Rgb(222, 0, 0); else if (Warn && strchr(":[", Warn[7])) ln->c.Rgb(255, 128, 0); else ln->c = LColour(L_TEXT); } } } }; class IdeOutput : public LTabView { public: AppWnd *App; LTabPage *Build; LTabPage *Output; LTabPage *Debug; LTabPage *Find; LTabPage *Ftp; LList *FtpLog; LTextLog *Txt[AppWnd::Channels::ChannelMax]; LArray Buf[AppWnd::Channels::ChannelMax]; LFont Small; LFont Fixed; LTabView *DebugTab; LBox *DebugBox; LBox *DebugLog; LList *Locals, *CallStack, *Threads; LTree *Watch; LTextLog *ObjectDump, *MemoryDump, *Registers; LTableLayout *MemTable; LEdit *DebugEdit; LTextLog *DebuggerLog; IdeOutput(AppWnd *app) { ZeroObj(Txt); App = app; Build = Output = Debug = Find = Ftp = 0; FtpLog = 0; DebugBox = NULL; Locals = NULL; Watch = NULL; DebugLog = NULL; DebugEdit = NULL; DebuggerLog = NULL; CallStack = NULL; ObjectDump = NULL; MemoryDump = NULL; MemTable = NULL; Threads = NULL; Registers = NULL; Small = *LSysFont; Small.PointSize(Small.PointSize()-1); Small.Create(); LAssert(Small.Handle()); LFontType Type; if (Type.GetSystemFont("Fixed")) { Type.SetPointSize(LSysFont->PointSize()-1); Fixed.Create(&Type); } else { Fixed.PointSize(LSysFont->PointSize()-1); Fixed.Face("Courier"); Fixed.Create(); } GetCss(true)->MinHeight("60px"); Build = Append("Build"); Output = Append("Output"); Find = Append("Find"); Ftp = Append("Ftp"); Debug = Append("Debug"); SetFont(&Small); Build->SetFont(&Small); Output->SetFont(&Small); Find->SetFont(&Small); Ftp->SetFont(&Small); Debug->SetFont(&Small); if (Build) Build->Append(Txt[AppWnd::BuildTab] = new BuildLog(IDC_BUILD_LOG)); if (Output) Output->Append(Txt[AppWnd::OutputTab] = new LTextLog(IDC_OUTPUT_LOG)); if (Find) Find->Append(Txt[AppWnd::FindTab] = new LTextLog(IDC_FIND_LOG)); if (Ftp) Ftp->Append(FtpLog = new LList(104, 0, 0, 100, 100)); if (Debug) { Debug->Append(DebugBox = new LBox); if (DebugBox) { DebugBox->SetVertical(false); if ((DebugTab = new LTabView(IDC_DEBUG_TAB))) { DebugTab->GetCss(true)->Padding("0px"); DebugTab->SetFont(&Small); DebugBox->AddView(DebugTab); LTabPage *Page; if ((Page = DebugTab->Append("Locals"))) { Page->SetFont(&Small); if ((Locals = new LList(IDC_LOCALS_LIST, 0, 0, 100, 100, "Locals List"))) { Locals->SetFont(&Small); Locals->AddColumn("", 30); Locals->AddColumn("Type", 50); Locals->AddColumn("Name", 50); Locals->AddColumn("Value", 1000); Locals->SetPourLargest(true); Page->Append(Locals); } } if ((Page = DebugTab->Append("Object"))) { Page->SetFont(&Small); if ((ObjectDump = new LTextLog(IDC_OBJECT_DUMP))) { ObjectDump->SetFont(&Fixed); ObjectDump->SetPourLargest(true); Page->Append(ObjectDump); } } if ((Page = DebugTab->Append("Watch"))) { Page->SetFont(&Small); if ((Watch = new LTree(IDC_WATCH_LIST, 0, 0, 100, 100, "Watch List"))) { Watch->SetFont(&Small); Watch->ShowColumnHeader(true); Watch->AddColumn("Watch", 80); Watch->AddColumn("Type", 100); Watch->AddColumn("Value", 600); Watch->SetPourLargest(true); Page->Append(Watch); LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { for (auto c: w->Children) { if (c->IsTag("watch")) { Watch->Insert(new WatchItem(this, c->GetContent())); } } App->GetOptions()->Unlock(); } } } if ((Page = DebugTab->Append("Memory"))) { Page->SetFont(&Small); if ((MemTable = new LTableLayout(IDC_MEMORY_TABLE))) { LCombo *cbo; LCheckBox *chk; LTextLabel *txt; LEdit *ed; MemTable->SetFont(&Small); int x = 0, y = 0; auto *c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Address:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ADDR, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(cbo = new LCombo(IDC_MEM_SIZE, 0, 0, 60, 20)); cbo->SetFont(&Small); cbo->Insert("1 byte"); cbo->Insert("2 bytes"); cbo->Insert("4 bytes"); cbo->Insert("8 bytes"); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(txt = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Page width:")); txt->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->PaddingRight(LCss::Len("1em")); c->Add(ed = new LEdit(IDC_MEM_ROW_LEN, 0, 0, 60, 20)); ed->SetFont(&Small); } c = MemTable->GetCell(x++, y); if (c) { c->VerticalAlign(LCss::VerticalMiddle); c->Add(chk = new LCheckBox(IDC_MEM_HEX, 0, 0, -1, -1, "Show Hex")); chk->SetFont(&Small); chk->Value(true); } int cols = x; x = 0; c = MemTable->GetCell(x++, ++y, true, cols); if ((MemoryDump = new LTextLog(IDC_MEMORY_DUMP))) { MemoryDump->SetFont(&Fixed); MemoryDump->SetPourLargest(true); c->Add(MemoryDump); } Page->Append(MemTable); } } if ((Page = DebugTab->Append("Threads"))) { Page->SetFont(&Small); if ((Threads = new LList(IDC_THREADS, 0, 0, 100, 100, "Threads"))) { Threads->SetFont(&Small); Threads->AddColumn("", 20); Threads->AddColumn("Thread", 1000); Threads->SetPourLargest(true); Threads->MultiSelect(false); Page->Append(Threads); } } if ((Page = DebugTab->Append("Call Stack"))) { Page->SetFont(&Small); if ((CallStack = new LList(IDC_CALL_STACK, 0, 0, 100, 100, "Call Stack"))) { CallStack->SetFont(&Small); CallStack->AddColumn("", 20); CallStack->AddColumn("Call Stack", 1000); CallStack->SetPourLargest(true); CallStack->MultiSelect(false); Page->Append(CallStack); } } if ((Page = DebugTab->Append("Registers"))) { Page->SetFont(&Small); if ((Registers = new LTextLog(IDC_REGISTERS))) { Registers->SetFont(&Small); Registers->SetPourLargest(true); Page->Append(Registers); } } } if ((DebugLog = new LBox)) { DebugLog->SetVertical(true); DebugBox->AddView(DebugLog); DebugLog->AddView(DebuggerLog = new DebugTextLog(IDC_DEBUGGER_LOG)); DebuggerLog->SetFont(&Small); DebugLog->AddView(DebugEdit = new LEdit(IDC_DEBUG_EDIT, 0, 0, 60, 20)); DebugEdit->GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)(LSysFont->GetHeight() + 8))); } } } if (FtpLog) { FtpLog->SetPourLargest(true); FtpLog->Sunken(true); FtpLog->AddColumn("Entry", 1000); FtpLog->ShowColumnHeader(false); } for (int n=0; nSetTabSize(8); Txt[n]->Sunken(true); } } ~IdeOutput() { } const char *GetClass() { return "IdeOutput"; } void Save() { if (Watch) { LXmlTag *w = App->GetOptions()->LockTag("watches", _FL); if (!w) { App->GetOptions()->CreateTag("watches"); w = App->GetOptions()->LockTag("watches", _FL); } if (w) { w->EmptyChildren(); for (LTreeItem *ti = Watch->GetChild(); ti; ti = ti->GetNext()) { LXmlTag *t = new LXmlTag("watch"); if (t) { t->SetContent(ti->GetText(0)); w->InsertTag(t); } } App->GetOptions()->Unlock(); } } } void OnCreate() { SetPulse(1000); AttachChildren(); } void RemoveAnsi(LArray &a) { char *s = a.AddressOf(); char *e = s + a.Length(); while (s < e) { if (*s == 0x7) { a.DeleteAt(s - a.AddressOf(), true); s--; } else if ( *s == 0x1b && s[1] >= 0x40 && s[1] <= 0x5f ) { // ANSI seq char *end; if (s[1] == '[' && s[2] == '0' && s[3] == ';') end = s + 4; else { end = s + 2; while (end < e && !IsAlpha(*end)) { end++; } if (*end) end++; } auto len = end - s; memmove(s, end, e - end); a.Length(a.Length() - len); s--; } s++; } } void OnPulse() { int Changed = -1; for (int Channel = 0; Channel w; if (!LIsUtf8(Utf, (ssize_t)Size)) { LgiTrace("Ch %i not utf len=" LPrintfInt64 "\n", Channel, Size); // Clear out the invalid UTF? uint8_t *u = (uint8_t*) Utf, *e = u + Size; ssize_t len = Size; LArray out; while (u < e) { int32 u32 = LgiUtf8To32(u, len); if (u32) { out.Add(u32); } else { out.Add(0xFFFD); u++; } } out.Add(0); w.Reset(out.Release()); } else { RemoveAnsi(Buf[Channel]); w.Reset(Utf8ToWide(Utf, (ssize_t)Size)); } // auto OldText = Txt[Channel]->NameW(); ssize_t OldLen = Txt[Channel]->Length(); auto Cur = Txt[Channel]->GetCaret(); Txt[Channel]->Insert(OldLen, w, StrlenW(w)); if (Cur > OldLen - 1) Txt[Channel]->SetCaret(OldLen + StrlenW(w), false); else printf("Caret move: %i, %i = %i\n", (int)Cur, (int)OldLen, Cur > OldLen - 1); Changed = Channel; Buf[Channel].Length(0); Txt[Channel]->Invalidate(); } } /* if (Changed >= 0) Value(Changed); */ } }; int DocSorter(IdeDoc *a, IdeDoc *b, NativeInt d) { auto A = a->GetFileName(); auto B = b->GetFileName(); if (A && B) { auto Af = strrchr(A, DIR_CHAR); auto Bf = strrchr(B, DIR_CHAR); return stricmp(Af?Af+1:A, Bf?Bf+1:B); } return 0; } struct FileLoc { LAutoString File; int Line; void Set(const char *f, int l) { File.Reset(NewStr(f)); Line = l; } }; class AppWndPrivate { public: AppWnd *App; LMdiParent *Mdi; LOptionsFile Options; LBox *HBox, *VBox; List Docs; List Projects; LImageList *Icons; LTree *Tree; IdeOutput *Output; bool Debugging; bool Running; bool Building; bool FixBuildWait = false; int RebuildWait = 0; LSubMenu *WindowsMenu; LSubMenu *CreateMakefileMenu; LAutoPtr FindSym; LArray SystemIncludePaths; LArray BreakPoints; // Debugging LDebugContext *DbgContext; // Cursor history tracking int HistoryLoc; LArray CursorHistory; bool InHistorySeek; void SeekHistory(int Direction) { if (CursorHistory.Length()) { int Loc = HistoryLoc + Direction; if (Loc >= 0 && Loc < CursorHistory.Length()) { HistoryLoc = Loc; FileLoc &Loc = CursorHistory[HistoryLoc]; App->GotoReference(Loc.File, Loc.Line, false, false); App->DumpHistory(); } } } // Find in files LAutoPtr FindParameters; LAutoPtr Finder; int AppHnd; // Mru LString::Array RecentFiles; LSubMenu *RecentFilesMenu = NULL; LString::Array RecentProjects; LSubMenu *RecentProjectsMenu = NULL; // Object AppWndPrivate(AppWnd *a) : Options(LOptionsFile::DesktopMode, AppName), AppHnd(LEventSinkMap::Dispatch.AddSink(a)) { FindSym.Reset(new FindSymbolSystem(AppHnd)); HistoryLoc = 0; InHistorySeek = false; WindowsMenu = 0; App = a; HBox = VBox = NULL; Tree = 0; Mdi = NULL; DbgContext = NULL; Output = 0; Debugging = false; Running = false; Building = false; Icons = LLoadImageList("icons.png", 16, 16); Options.SerializeFile(false); App->SerializeState(&Options, "WndPos", true); SerializeStringList("RecentFiles", &RecentFiles, false); SerializeStringList("RecentProjects", &RecentProjects, false); } ~AppWndPrivate() { FindSym.Reset(); Finder.Reset(); if (Output) Output->Save(); App->SerializeState(&Options, "WndPos", false); SerializeStringList("RecentFiles", &RecentFiles, true); SerializeStringList("RecentProjects", &RecentProjects, true); Options.SerializeFile(true); while (Docs.Length()) { auto len = Docs.Length(); delete Docs[0]; LAssert(Docs.Length() != len); // doc must delete itself... } auto root = App->RootProject(); if (root) { // printf("Deleting proj %s\n", root->GetFileName()); delete root; } LAssert(!Projects.Length()); DeleteObj(Icons); } bool FindSource(LAutoString &Full, char *File, char *Context) { if (!LIsRelativePath(File)) { Full.Reset(NewStr(File)); } char *ContextPath = 0; if (Context && !Full) { char *Dir = strrchr(Context, DIR_CHAR); for (auto p: Projects) { ContextPath = p->FindFullPath(Dir?Dir+1:Context); if (ContextPath) break; } if (ContextPath) { LTrimDir(ContextPath); char p[300]; LMakePath(p, sizeof(p), ContextPath, File); if (LFileExists(p)) { Full.Reset(NewStr(p)); } } else { LgiTrace("%s:%i - Context '%s' not found in project.\n", _FL, Context); } } if (!Full) { List::I Projs = Projects.begin(); for (IdeProject *p=*Projs; p; p=*++Projs) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; LMakePath(Path, sizeof(Path), Base, File); if (LFileExists(Path)) { Full.Reset(NewStr(Path)); break; } } } } if (!Full) { char *Dir = dirchar(File, true); for (auto p: Projects) { if (Full.Reset(p->FindFullPath(Dir?Dir+1:File))) break; } if (!Full) { if (LFileExists(File)) { Full.Reset(NewStr(File)); } } } return ValidStr(Full); } void ViewMsg(char *File, int Line, char *Context) { LAutoString Full; if (FindSource(Full, File, Context)) { App->GotoReference(Full, Line, false); } } #if 1 #define LOG_SEEK_MSG(...) printf(__VA_ARGS__) #else #define LOG_SEEK_MSG(...) #endif void GetContext(const char16 *Txt, ssize_t &i, char16 *&Context) { static char16 NMsg[] = L"In file included "; static char16 FromMsg[] = L"from "; auto NMsgLen = StrlenW(NMsg); if (Txt[i] != '\n') return; if (StrncmpW(Txt + i + 1, NMsg, NMsgLen)) return; i += NMsgLen + 1; while (Txt[i]) { // Skip whitespace while (Txt[i] && strchr(" \t\r\n", Txt[i])) i++; // Check for 'from' if (StrncmpW(FromMsg, Txt + i, 5)) break; i += 5; auto Start = Txt + i; // Skip to end of doc or line const char16 *Colon = 0; while (Txt[i] && Txt[i] != '\n') { if (Txt[i] == ':' && Txt[i+1] != '\n') { Colon = Txt + i; } i++; } if (Colon) { DeleteArray(Context); Context = NewStrW(Start, Colon-Start); } } } template bool IsTimeStamp(T *s, ssize_t i) { while (i > 0 && s[i-1] != '\n') i--; auto start = i; while (s[i] && (IsDigit(s[i]) || strchr(" :-", s[i]))) i++; LString txt(s + start, i - start); auto parts = txt.SplitDelimit(" :-"); return parts.Length() == 6 && parts[0].Length() == 4; } template bool IsContext(T *s, ssize_t i) { auto key = L"In file included"; auto end = i; while (i > 0 && s[i-1] != '\n') i--; if (Strnistr(s + i, key, end - i)) return true; return false; } #define PossibleLineSep(ch) \ ( (ch) == ':' || (ch) == '(' ) void SeekMsg(int Direction) { LString Comp; IdeProject *p = App->RootProject(); if (p) p ->GetSettings()->GetStr(ProjCompiler); // bool IsIAR = Comp.Equals("IAR"); if (!Output) return; int64 Current = Output->Value(); LTextView3 *o = Current < CountOf(Output->Txt) ? Output->Txt[Current] : 0; if (!o) return; auto Txt = o->NameW(); if (!Txt) return; ssize_t Cur = o->GetCaret(); char16 *Context = NULL; // Scan forward to the end of file for the next filename/line number separator. ssize_t i; for (i=Cur; Txt[i]; i++) { GetContext(Txt, i, Context); if ( PossibleLineSep(Txt[i]) && isdigit(Txt[i+1]) && !IsTimeStamp(Txt, i) && !IsContext(Txt, i) ) { break; } } // If not found then scan from the start of the file for the next filename/line number separator. if (!PossibleLineSep(Txt[i])) { for (i=0; i 0 && !strchr("\n>", Txt[Line-1])) { Line--; } // Store the filename LString File(Txt+Line, i-Line); if (!File) return; #if DIR_CHAR == '\\' File = File.Replace("/", "\\"); #else File = File.Replace("\\", "/"); #endif // Scan over the line number.. auto NumIndex = ++i; while (isdigit(Txt[NumIndex])) NumIndex++; // Store the line number LString NumStr(Txt + i, NumIndex - i); if (!NumStr) return; // Convert it to an integer auto LineNumber = (int)NumStr.Int(); o->SetCaret(Line, false); o->SetCaret(NumIndex + 1, true); LString Context8 = Context; ViewMsg(File, LineNumber, Context8); } void UpdateMenus() { static const char *None = "(none)"; if (!App->GetMenu()) return; // This happens in GTK during window destruction if (RecentFilesMenu) { RecentFilesMenu->Empty(); if (RecentFiles.Length() == 0) RecentFilesMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentFiles.begin(); (f = *It); f=*(++It)) { for (; f; f=*(++It)) { if (LIsUtf8(f)) RecentFilesMenu->AppendItem(f, IDM_RECENT_FILE+n++, true); else RecentFiles.Delete(It); } } } } if (RecentProjectsMenu) { RecentProjectsMenu->Empty(); if (RecentProjects.Length() == 0) RecentProjectsMenu->AppendItem(None, 0, false); else { int n=0; char *f; for (auto It = RecentProjects.begin(); (f = *It); f=*(++It)) { if (LIsUtf8(f)) RecentProjectsMenu->AppendItem(f, IDM_RECENT_PROJECT+n++, true); else RecentProjects.Delete(It); } } } if (WindowsMenu) { WindowsMenu->Empty(); Docs.Sort(DocSorter); int n=0; for (auto d: Docs) { const char *File = d->GetFileName(); if (!File) File = "(untitled)"; char *Dir = strrchr((char*)File, DIR_CHAR); WindowsMenu->AppendItem(Dir?Dir+1:File, IDM_WINDOWS+n++, true); } if (!Docs.Length()) { WindowsMenu->AppendItem(None, 0, false); } } } void Dump(LString::Array &a) { for (auto i: a) printf(" %s\n", i.Get()); } void OnFile(const char *File, bool IsProject = false) { if (!File) return; auto *Recent = IsProject ? &RecentProjects : &RecentFiles; for (auto &f: *Recent) { if (f && LFileCompare(f, File) == 0) { f = File; UpdateMenus(); return; } } Recent->AddAt(0, File); if (Recent->Length() > 10) Recent->Length(10); UpdateMenus(); } void RemoveRecent(const char *File) { if (File) { LString::Array *Recent[3] = { &RecentProjects, &RecentFiles, 0 }; for (int i=0; Recent[i]; i++) { auto &a = *Recent[i]; for (size_t n=0; nIsFile(File)) { return Doc; } } // LgiTrace("%s:%i - '%s' not found in %i docs.\n", _FL, File, Docs.Length()); return 0; } IdeProject *IsProjectOpen(const char *File) { if (File) { for (auto p: Projects) { if (p->GetFileName() && stricmp(p->GetFileName(), File) == 0) { return p; } } } return 0; } void SerializeStringList(const char *Opt, LString::Array *Lst, bool Write) { LVariant v; LString Sep = OptFileSeparator; if (Write) { if (Lst->Length() > 0) { auto s = Sep.Join(*Lst); Options.SetValue(Opt, v = s.Get()); // printf("Saving '%s' to %s\n", s.Get(), Opt); } else Options.DeleteValue(Opt); } else if (Options.GetValue(Opt, v)) { auto files = LString(v.Str()).Split(Sep); Lst->Length(0); for (auto f: files) if (f.Length() > 0) Lst->Add(f); // printf("Reading '%s' to %s, file.len=%i %s\n", v.Str(), Opt, (int)files.Length(), v.Str()); } // else printf("%s:%i - No option '%s' to read.\n", _FL, Opt); } }; AppWnd::AppWnd() { #ifdef __GTK_H__ LgiGetResObj(true, AppName); #endif LRect r(0, 0, 1000, 760); SetPos(r); MoveToCenter(); d = new AppWndPrivate(this); Name(AppName); SetQuitOnClose(true); #if WINNATIVE SetIcon((char*)MAKEINTRESOURCE(IDI_APP)); #else SetIcon("icon64.png"); #endif if (!Attach(0)) { LgiTrace("%s:%i - Attach failed.\n", _FL); return; } if ((Menu = new LMenu)) { Menu->Attach(this); bool Loaded = Menu->Load(this, "IDM_MENU"); LAssert(Loaded); if (Loaded) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); d->RecentFilesMenu = Menu->FindSubMenu(IDM_RECENT_FILES); d->RecentProjectsMenu = Menu->FindSubMenu(IDM_RECENT_PROJECTS); d->WindowsMenu = Menu->FindSubMenu(IDM_WINDOW_LST); d->CreateMakefileMenu = Menu->FindSubMenu(IDM_CREATE_MAKEFILE); if (d->CreateMakefileMenu) { d->CreateMakefileMenu->Empty(); for (int i=0; PlatformNames[i]; i++) { d->CreateMakefileMenu->AppendItem(PlatformNames[i], IDM_MAKEFILE_BASE + i); } } else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); if (Debug) Debug->Checked(true); else LgiTrace("%s:%i - FindSubMenu failed.\n", _FL); d->UpdateMenus(); } } LToolBar *Tools = NULL; if (GdcD->Y() > 1200) Tools = LgiLoadToolbar(this, "cmds-32px.png", 32, 32); else Tools = LgiLoadToolbar(this, "cmds-16px.png", 16, 16); if (Tools) { Tools->AppendButton("New", IDM_NEW, TBT_PUSH, true, CMD_NEW); Tools->AppendButton("Open", IDM_OPEN, TBT_PUSH, true, CMD_OPEN); Tools->AppendButton("Save", IDM_SAVE_ALL, TBT_PUSH, true, CMD_SAVE_ALL); Tools->AppendSeparator(); Tools->AppendButton("Cut", IDM_CUT, TBT_PUSH, true, CMD_CUT); Tools->AppendButton("Copy", IDM_COPY, TBT_PUSH, true, CMD_COPY); Tools->AppendButton("Paste", IDM_PASTE, TBT_PUSH, true, CMD_PASTE); Tools->AppendSeparator(); Tools->AppendButton("Compile", IDM_COMPILE, TBT_PUSH, true, CMD_COMPILE); Tools->AppendButton("Build", IDM_BUILD, TBT_PUSH, true, CMD_BUILD); Tools->AppendButton("Stop", IDM_STOP_BUILD, TBT_PUSH, true, CMD_STOP_BUILD); // Tools->AppendButton("Execute", IDM_EXECUTE, TBT_PUSH, true, CMD_EXECUTE); Tools->AppendSeparator(); Tools->AppendButton("Debug", IDM_START_DEBUG, TBT_PUSH, true, CMD_DEBUG); Tools->AppendButton("Pause", IDM_PAUSE_DEBUG, TBT_PUSH, true, CMD_PAUSE); Tools->AppendButton("Restart", IDM_RESTART_DEBUGGING, TBT_PUSH, true, CMD_RESTART); Tools->AppendButton("Kill", IDM_STOP_DEBUG, TBT_PUSH, true, CMD_KILL); Tools->AppendButton("Step Into", IDM_STEP_INTO, TBT_PUSH, true, CMD_STEP_INTO); Tools->AppendButton("Step Over", IDM_STEP_OVER, TBT_PUSH, true, CMD_STEP_OVER); Tools->AppendButton("Step Out", IDM_STEP_OUT, TBT_PUSH, true, CMD_STEP_OUT); Tools->AppendButton("Run To", IDM_RUN_TO, TBT_PUSH, true, CMD_RUN_TO); Tools->AppendSeparator(); Tools->AppendButton("Find In Files", IDM_FIND_IN_FILES, TBT_PUSH, true, CMD_FIND_IN_FILES); Tools->GetCss(true)->Padding("4px"); Tools->Attach(this); } else LgiTrace("%s:%i - No tools obj?", _FL); LVariant v = 270, OutPx = 250; d->Options.GetValue(OPT_SPLIT_PX, v); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); AddView(d->VBox = new LBox); d->VBox->SetVertical(true); d->HBox = new LBox; d->VBox->AddView(d->HBox); d->VBox->AddView(d->Output = new IdeOutput(this)); d->HBox->AddView(d->Tree = new IdeTree); if (d->Tree) { d->Tree->SetImageList(d->Icons, false); d->Tree->Sunken(false); } d->HBox->AddView(d->Mdi = new LMdiParent); if (d->Mdi) { d->Mdi->HasButton(true); } d->HBox->Value(MAX(v.CastInt32(), 20)); LRect c = GetClient(); if (c.Y() > OutPx.CastInt32()) { auto Px = OutPx.CastInt32(); LCss::Len y(LCss::LenPx, (float)MAX(Px, 120)); d->Output->GetCss(true)->Height(y); } AttachChildren(); OnPosChange(); UpdateState(); Visible(true); DropTarget(true); SetPulse(1000); #ifdef LINUX LFinishXWindowsStartup(this); #endif #if USE_HAIKU_PULSE_HACK if (d->Output) d->Output->SetPulse(1000); #endif OnCommand(IDM_NEW, 0, NULL); } AppWnd::~AppWnd() { LAssert(IsClean()); WaitThread(); if (d->HBox) { LVariant v = d->HBox->Value(); d->Options.SetValue(OPT_SPLIT_PX, v); } if (d->Output) { LVariant v = d->Output->Y(); d->Options.SetValue(OPT_OUTPUT_PX, v); } ShutdownFtpThread(); LAppInst->AppWnd = NULL; DeleteObj(d); } void AppWnd::OnPulse() { IdeDoc *Top = TopDoc(); if (Top) Top->OnPulse(); if (d->FixBuildWait) { d->FixBuildWait = false; if (OnFixBuildErrors() > 0) d->RebuildWait = 3; } else if (d->RebuildWait > 0) { if (--d->RebuildWait == 0) Build(); } } LDebugContext *AppWnd::GetDebugContext() { return d->DbgContext; } struct DumpBinThread : public LThread { LStream *Out; LString InFile; bool IsLib; public: DumpBinThread(LStream *out, LString file) : LThread("DumpBin.Thread") { Out = out; InFile = file; DeleteOnExit = true; auto Ext = LGetExtension(InFile); IsLib = Ext && !stricmp(Ext, "lib"); Run(); } bool DumpBin(LString Args, LStream *Str) { char Buf[256]; ssize_t Rd; const char *Prog = "c:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\dumpbin.exe"; LSubProcess s(Prog, Args); if (!s.Start(true, false)) { Out->Print("%s:%i - '%s' doesn't exist.\n", _FL, Prog); return false; } while ((Rd = s.Read(Buf, sizeof(Buf))) > 0) Str->Write(Buf, Rd); return true; } LString::Array Dependencies(const char *Executable, int Depth = 0) { LString Args; LStringPipe p; Args.Printf("/dependents \"%s\"", Executable); DumpBin(Args, &p); char Spaces[256]; int Len = Depth * 2; memset(Spaces, ' ', Len); Spaces[Len] = 0; LString::Array Files; auto Parts = p.NewGStr().Replace("\r", "").Split("\n\n"); if (Parts.Length() > 0) { Files = Parts[4].Strip().Split("\n"); auto Path = LGetPath(); for (size_t i=0; i= 0) { auto p = Ln.Strip().Split(Key); if (p.Length() == 2) { Arch = p[1].Strip("()"); Machine = p[0].Int(16); } } } if (Machine == 0x14c) Arch += " 32bit"; else if (Machine == 0x200) Arch += " 64bit Itanium"; else if (Machine == 0x8664) Arch += " 64bit"; return Arch; } LString GetExports() { LString Args; LStringPipe p; /* if (IsLib) Args.Printf("/symbols \"%s\"", InFile.Get()); else */ Args.Printf("/exports \"%s\"", InFile.Get()); DumpBin(Args, &p); LString Exp; auto Raw = p.NewGStr().Replace("\r", ""); auto Sect = Raw.Split("\n\n"); #if 0 // Debug output Exp = Raw; #else if (IsLib) { for (auto &s : Sect) { if (s.Find("COFF/PE Dumper") >= 0) continue; auto ln = s.Split("\n"); if (ln.Length() == 1) continue; for (auto &l: ln) l = l.LStrip(); Exp = LString("\n").Join(ln); break; } } else { bool Ord = false; for (auto &s : Sect) { if (s.Strip().Find("ordinal") == 0) Ord = true; else if (Ord) { Exp = s; break; } else Ord = false; } } #endif return Exp; } int Main() { if (!IsLib) { auto Deps = Dependencies(InFile); if (Deps.Length()) Out->Print("Dependencies:\n\t%s\n\n", LString("\n\t").Join(Deps).Get()); } auto Arch = GetArch(); if (Arch) Out->Print("Arch: %s\n\n", Arch.Get()); auto Exp = GetExports(); if (Arch) Out->Print("Exports:\n%s\n\n", Exp.Get()); return 0; } }; void AppWnd::OnReceiveFiles(LArray &Files) { for (int i=0; iSetFileName(Docs, false); new DumpBinThread(Doc, Files[i]); } } else { OpenFile(f); } } Raise(); if (LAppInst->GetOption("createMakeFiles")) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatformCurrent, false); } } } void AppWnd::OnDebugState(bool Debugging, bool Running) { // Make sure this event is processed in the GUI thread. #if DEBUG_SESSION_LOGGING LgiTrace("AppWnd::OnDebugState(%i,%i) InThread=%i\n", Debugging, Running, InThread()); #endif PostEvent(M_DEBUG_ON_STATE, Debugging, Running); } bool IsVarChar(LString &s, ssize_t pos) { if (pos < 0) return false; if (pos >= s.Length()) return false; char i = s[pos]; return IsAlpha(i) || IsDigit(i) || i == '_'; } bool ReplaceWholeWord(LString &Ln, LString Word, LString NewWord) { ssize_t Pos = 0; bool Status = false; while (Pos >= 0) { Pos = Ln.Find(Word, Pos); if (Pos < 0) return Status; ssize_t End = Pos + Word.Length(); if (!IsVarChar(Ln, Pos-1) && !IsVarChar(Ln, End)) { LString NewLn = Ln(0,Pos) + NewWord + Ln(End,-1); Ln = NewLn; Status = true; } Pos++; } return Status; } struct LFileInfo { LString Path; LString::Array Lines; bool Dirty; LFileInfo() { Dirty = false; } bool Save() { LFile f; if (!f.Open(Path, O_WRITE)) return false; LString NewFile = LString("\n").Join(Lines); f.SetSize(0); f.Write(NewFile); f.Close(); return true; } }; int AppWnd::OnFixBuildErrors() { LHashTbl, LString> Map; LVariant v; if (GetOptions()->GetValue(OPT_RENAMED_SYM, v)) { auto Lines = LString(v.Str()).Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(); if (p.Length() == 2) Map.Add(p[0], p[1]); } } LString Raw = d->Output->Txt[AppWnd::BuildTab]->Name(); LString::Array Lines = Raw.Split("\n"); auto *Log = d->Output->Txt[AppWnd::OutputTab]; Log->Name(NULL); Log->Print("Parsing errors...\n"); int Replacements = 0; LArray Files; LHashTbl,bool> FixHistory; for (int Idx=0; Idx= 0) { #ifdef WINDOWS LString::Array p = Ln.SplitDelimit(">()"); #else LString::Array p = Ln(0, ErrPos).Strip().SplitDelimit(":"); #endif if (p.Length() <= 2) { Log->Print("Error: Only %i parts? '%s'\n", (int)p.Length(), Ln.Get()); } else { #ifdef WINDOWS int Base = p[0].IsNumeric() ? 1 : 0; LString Fn = p[Base]; if (Fn.Find("Program Files") >= 0) { Log->Print("Is prog file\n"); continue; } auto LineNo = p[Base+1].Int(); bool FileNotFound = Ln.Find("Cannot open include file:") > 0; #else LString Fn = p[0]; auto LineNo = p[1].Int(); bool FileNotFound = false; // fixme #endif LAutoString Full; if (!d->FindSource(Full, Fn, NULL)) { Log->Print("Error: Can't find Fn='%s' Line=%i\n", Fn.Get(), (int)LineNo); continue; } LFileInfo *Fi = NULL; for (auto &i: Files) { if (i.Path.Equals(Full)) { Fi = &i; break; } } if (!Fi) { LFile f(Full, O_READ); if (f.IsOpen()) { Fi = &Files.New(); Fi->Path = Full.Get(); auto OldFile = f.Read(); Fi->Lines = OldFile.SplitDelimit("\n", -1, false); } else { Log->Print("Error: Can't open '%s'\n", Full.Get()); } } if (Fi) { LString Loc; Loc.Printf("%s:%i", Full.Get(), (int)LineNo); if (FixHistory.Find(Loc)) { // Log->Print("Already fixed %s\n", Loc.Get()); } else if (LineNo <= Fi->Lines.Length()) { FixHistory.Add(Loc, true); if (FileNotFound) { auto n = p.Last().SplitDelimit("\'"); auto wrongName = n[1]; LFile f(Full, O_READ); auto Lines = f.Read().SplitDelimit("\n", -1, false); f.Close(); if (LineNo <= Lines.Length()) { auto &errLine = Lines[LineNo-1]; auto Pos = errLine.Find(wrongName); /* if (Pos < 0) { for (int i=0; iPrint("[%i]=%s\n", i, Lines[i].Get()); } */ if (Pos > 0) { // Find where it went... LString newPath; for (auto p: d->Projects) { const char *SubStr[] = { ".", "lgi/common" }; LArray IncPaths; if (p->BuildIncludePaths(IncPaths, true, false, PlatformCurrent)) { for (auto &inc: IncPaths) { for (int sub=0; !newPath && subPrint("Already changed '%s'.\n", wrongName.Get()); } else { LString backup = LString(Full.Get()) + ".orig"; if (LFileExists(backup)) FileDev->Delete(backup); LError Err; if (FileDev->Move(Full, backup, &Err)) { errLine = newLine; LString newLines = LString("\n").Join(Lines); LFile out(Full, O_WRITE); out.Write(newLines); Log->Print("Fixed '%s'->'%s' on ln %i in %s\n", wrongName.Get(), newPath.Get(), (int)LineNo, Full.Get()); Replacements++; } else Log->Print("Error: moving '%s' to backup (%s).\n", Full.Get(), Err.GetMsg().Get()); } } else Log->Print("Error: Missing header '%s'.\n", wrongName.Get()); } else { Log->Print("Error: '%s' not found in line %i of '%s' -> '%s'\n", wrongName.Get(), (int)LineNo, Fn.Get(), Full.Get()); // return; } } else Log->Print("Error: Line %i is beyond file lines: %i\n", (int)LineNo, (int)Lines.Length()); } else { auto OldReplacements = Replacements; for (auto i: Map) { for (int Offset = 0; (LineNo + Offset >= 1) && Offset >= -1; Offset--) { LString &s = Fi->Lines[LineNo+Offset-1]; if (ReplaceWholeWord(s, i.key, i.value)) { Log->Print("Renamed '%s' -> '%s' at %s:%i\n", i.key, i.value.Get(), Full.Get(), LineNo+Offset); Fi->Dirty = true; Replacements++; Offset = -2; } } } if (OldReplacements == Replacements && Ln.Find("syntax error: id") > 0) { Log->Print("Unhandled: %s\n", Ln.Get()); } } } else { Log->Print("Error: Invalid line %i\n", (int)LineNo); } } else { Log->Print("Error: Fi is NULL\n"); } } } } for (auto &Fi : Files) { if (Fi.Dirty) Fi.Save(); } Log->Print("%i replacements made.\n", Replacements); if (Replacements > 0) d->Output->Value(AppWnd::OutputTab); return Replacements; } void AppWnd::OnBuildStateChanged(bool NewState) { LVariant v; if (!NewState && GetOptions()->GetValue(OPT_FIX_RENAMED, v) && v.CastInt32()) { d->FixBuildWait = true; } } void AppWnd::UpdateState(int Debugging, int Building) { // printf("UpdateState %i %i\n", Debugging, Building); if (Debugging >= 0) d->Debugging = Debugging; if (Building >= 0) { if (d->Building != (Building != 0)) OnBuildStateChanged(Building); d->Building = Building; } SetCtrlEnabled(IDM_COMPILE, !d->Building); SetCtrlEnabled(IDM_BUILD, !d->Building); SetCtrlEnabled(IDM_STOP_BUILD, d->Building); // SetCtrlEnabled(IDM_RUN, !d->Building); // SetCtrlEnabled(IDM_TOGGLE_BREAKPOINT, !d->Building); SetCtrlEnabled(IDM_START_DEBUG, !d->Debugging && !d->Building); SetCtrlEnabled(IDM_PAUSE_DEBUG, d->Debugging); SetCtrlEnabled(IDM_RESTART_DEBUGGING, d->Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, d->Debugging); SetCtrlEnabled(IDM_STEP_INTO, d->Debugging); SetCtrlEnabled(IDM_STEP_OVER, d->Debugging); SetCtrlEnabled(IDM_STEP_OUT, d->Debugging); SetCtrlEnabled(IDM_RUN_TO, d->Debugging); } void AppWnd::AppendOutput(char *Txt, AppWnd::Channels Channel) { if (!d->Output) { LgiTrace("%s:%i - No output panel.\n", _FL); return; } if (Channel < 0 || Channel >= CountOf(d->Output->Txt)) { LgiTrace("%s:%i - Channel range: %i, %i.\n", _FL, Channel, CountOf(d->Output->Txt)); return; } if (!d->Output->Txt[Channel]) { LgiTrace("%s:%i - No log for channel %i.\n", _FL, Channel); return; } if (Txt) { d->Output->Buf[Channel].Add(Txt, strlen(Txt)); } else { auto Ctrl = d->Output->Txt[Channel]; Ctrl->UnSelectAll(); Ctrl->Name(""); } } bool AppWnd::IsClean() { for (auto Doc: d->Docs) { if (!Doc->GetClean()) return false; } for (auto Proj: d->Projects) { if (!Proj->GetClean()) return false; } return true; } struct SaveState { AppWndPrivate *d = NULL; LArray Docs; LArray Projects; std::function Callback; bool Status = true; bool CloseDirty = false; void Iterate() { if (Docs.Length()) { auto doc = Docs[0]; Docs.DeleteAt(0); // printf("Saving doc...\n"); doc->SetClean([this, doc](bool ok) { // printf("SetClean cb ok=%i\n", ok); if (ok) d->OnFile(doc->GetFileName()); else { if (CloseDirty) delete doc; Status = false; } // printf("SetClean cb iter\n", ok); Iterate(); }); } else if (Projects.Length()) { auto proj = Projects[0]; Projects.DeleteAt(0); // printf("Saving proj...\n"); proj->SetClean([this, proj](bool ok) { if (ok) d->OnFile(proj->GetFileName(), true); else { if (CloseDirty) delete proj; Status = false; } Iterate(); }); } else { // printf("Doing callback...\n"); if (Callback) Callback(Status); // printf("Deleting...\n"); delete this; } } }; void AppWnd::SaveAll(std::function Callback, bool CloseDirty) { auto ss = new SaveState; ss->d = d; ss->Callback = Callback; ss->CloseDirty = CloseDirty; for (auto Doc: d->Docs) { if (!Doc->GetClean()) ss->Docs.Add(Doc); } for (auto Proj: d->Projects) { if (!Proj->GetClean()) ss->Projects.Add(Proj); } ss->Iterate(); } void AppWnd::CloseAll() { SaveAll([&](auto status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } while (d->Docs[0]) delete d->Docs[0]; IdeProject *p = RootProject(); if (p) DeleteObj(p); while (d->Projects[0]) delete d->Projects[0]; DeleteObj(d->DbgContext); }); } bool AppWnd::OnRequestClose(bool IsOsQuit) { if (!IsClean()) { SaveAll([](bool status) { LCloseApp(); }, true); return false; } else { return LWindow::OnRequestClose(IsOsQuit); } } bool AppWnd::OnBreakPoint(LDebugger::BreakPoint &b, bool Add) { List::I it = d->Docs.begin(); for (IdeDoc *doc = *it; doc; doc = *++it) { auto fn = doc->GetFileName(); bool Match = !Stricmp(fn, b.File.Get()); if (Match) doc->AddBreakPoint(b.Line, Add); } if (d->DbgContext) d->DbgContext->OnBreakPoint(b, Add); return true; } bool AppWnd::LoadBreakPoints(IdeDoc *doc) { if (!doc) return false; auto fn = doc->GetFileName(); for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(fn, b.File)) { doc->AddBreakPoint(b.Line, true); } } return true; } bool AppWnd::LoadBreakPoints(LDebugger *db) { if (!db) return false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &bp = d->BreakPoints[i]; db->SetBreakPoint(&bp); } return true; } bool AppWnd::ToggleBreakpoint(const char *File, ssize_t Line) { bool DeleteBp = false; for (int i=0; iBreakPoints.Length(); i++) { LDebugger::BreakPoint &b = d->BreakPoints[i]; if (!_stricmp(File, b.File) && b.Line == Line) { OnBreakPoint(b, false); d->BreakPoints.DeleteAt(i); DeleteBp = true; break; } } if (!DeleteBp) { LDebugger::BreakPoint &b = d->BreakPoints.New(); b.File = File; b.Line = Line; OnBreakPoint(b, true); } return true; } void AppWnd::DumpHistory() { #if 0 LgiTrace("History %i of %i\n", d->HistoryLoc, d->CursorHistory.Length()); for (int i=0; iCursorHistory.Length(); i++) { FileLoc &p = d->CursorHistory[i]; LgiTrace(" [%i] = %s, %i %s\n", i, p.File.Get(), p.Line, d->HistoryLoc == i ? "<-----":""); } #endif } /* void CheckHistory(LArray &CursorHistory) { if (CursorHistory.Length() > 0) { FileLoc *loc = &CursorHistory[0]; for (unsigned i=CursorHistory.Length(); iInHistorySeek) { if (d->CursorHistory.Length() > 0) { FileLoc &Last = d->CursorHistory.Last(); if (_stricmp(File, Last.File) == 0 && abs(Last.Line - Line) <= 1) { // Previous or next line... just update line number Last.Line = Line; DumpHistory(); return; } // Add new entry d->HistoryLoc++; FileLoc &loc = d->CursorHistory[d->HistoryLoc]; #ifdef WIN64 if ((NativeInt)loc.File.Get() == 0xcdcdcdcdcdcdcdcd) LAssert(0); // wtf? else #endif loc.Set(File, Line); } else { // Add new entry d->CursorHistory[0].Set(File, Line); } // Destroy any history after the current... d->CursorHistory.Length(d->HistoryLoc+1); DumpHistory(); } } void AppWnd::OnFile(char *File, bool IsProject) { d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { IdeDoc *Doc = new IdeDoc(this, Src, 0); if (Doc) { d->Docs.Insert(Doc); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); Doc->Raise(); if (FileName) d->OnFile(FileName); } return Doc; } IdeDoc *AppWnd::GetCurrentDoc() { if (d->Mdi) return dynamic_cast(d->Mdi->GetTop()); return NULL; } IdeDoc *AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory) { if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) { Doc->SetLine(Line, CurIp); Doc->Focus(true); } if (!WithHistory) d->InHistorySeek = false; return Doc; } IdeDoc *AppWnd::FindOpenFile(char *FileName) { List::I it = d->Docs.begin(); for (IdeDoc *i=*it; i; i=*++it) { auto f = i->GetFileName(); if (f) { IdeProject *p = i->GetProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) { char Path[MAX_PATH_LEN]; if (*f == '.') LMakePath(Path, sizeof(Path), Base, f); else strcpy_s(Path, sizeof(Path), f); if (stricmp(Path, FileName) == 0) return i; } } else { if (stricmp(f, FileName) == 0) return i; } } } return 0; } IdeDoc *AppWnd::OpenFile(const char *FileName, NodeSource *Src) { static bool DoingProjectFind = false; IdeDoc *Doc = 0; const char *File = Src ? Src->GetFileName() : FileName; if (!Src && !ValidStr(File)) { LgiTrace("%s:%i - No source or file?\n", _FL); return NULL; } LString FullPath; if (LIsRelativePath(File)) { IdeProject *Proj = Src && Src->GetProject() ? Src->GetProject() : RootProject(); if (Proj) { List Projs; Projs.Insert(Proj); Proj->CollectAllSubProjects(Projs); for (auto p: Projs) { auto ProjPath = p->GetBasePath(); char s[MAX_PATH_LEN]; LMakePath(s, sizeof(s), ProjPath, File); LString Path = s; if (p->CheckExists(Path)) { FullPath = Path; File = FullPath; break; } } } } // Sniff type... bool probablyLgiProj = false; if (!Stricmp(LGetExtension(File), "xml")) { LFile f(File, O_READ); if (f) { char buf[256]; auto rd = f.Read(buf, sizeof(buf)); if (rd > 0) probablyLgiProj = Strnistr(buf, "IsFileOpen(File); if (!Doc) { if (Src) { Doc = NewDocWnd(File, Src); } else if (!DoingProjectFind) { DoingProjectFind = true; List::I Proj = d->Projects.begin(); for (IdeProject *p=*Proj; p && !Doc; p=*++Proj) { p->InProject(LIsRelativePath(File), File, true, &Doc); } DoingProjectFind = false; d->OnFile(File); } } if (!Doc && LFileExists(File)) { Doc = new IdeDoc(this, 0, File); if (Doc) { Doc->OpenFile(File); LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); d->Docs.Insert(Doc); d->OnFile(File); } } if (Doc) { Doc->SetEditorParams(4, 4, true, false); if (!Doc->IsAttached()) { Doc->Attach(d->Mdi); } Doc->Focus(true); Doc->Raise(); } return Doc; } IdeProject *AppWnd::RootProject() { for (auto p: d->Projects) if (!p->GetParentProject()) return p; return NULL; } IdeProject *AppWnd::OpenProject(const char *FileName, IdeProject *ParentProj, bool Create, bool Dep) { if (!FileName) { LgiTrace("%s:%i - Error: No filename.\n", _FL); return NULL; } if (d->IsProjectOpen(FileName)) { LgiTrace("%s:%i - Warning: Project already open.\n", _FL); return NULL; } IdeProject *p = new IdeProject(this); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } LString::Array Inc; p->BuildIncludePaths(Inc, false, false, PlatformCurrent); d->FindSym->SetIncludePaths(Inc); p->SetParentProject(ParentProj); ProjectStatus Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!Dep) { auto d = strrchr(FileName, DIR_CHAR); if (d++) { char n[256]; sprintf(n, "%s [%s]", AppName, d); Name(n); } } } else { LgiTrace("%s:%i - Failed to open '%s'\n", _FL, FileName); DeleteObj(p); if (Status == OpenError) d->RemoveRecent(FileName); } if (!GetTree()->Selection()) { GetTree()->Select(GetTree()->GetChild()); } GetTree()->Focus(true); return p; } LMessage::Result AppWnd::OnEvent(LMessage *m) { switch (m->Msg()) { case M_MAKEFILES_CREATED: { IdeProject *p = (IdeProject*)m->A(); if (p) p->OnMakefileCreated(); break; } case M_LAST_MAKEFILE_CREATED: { if (LAppInst->GetOption("exit")) LCloseApp(); break; } case M_START_BUILD: { IdeProject *p = RootProject(); if (p) p->Build(true, GetBuildMode()); else printf("%s:%i - No root project.\n", _FL); break; } case M_BUILD_DONE: { UpdateState(-1, false); IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case M_BUILD_ERR: { char *Msg = (char*)m->B(); if (Msg) { d->Output->Txt[AppWnd::BuildTab]->Print("Build Error: %s\n", Msg); DeleteArray(Msg); } break; } case M_APPEND_TEXT: { LAutoString Text((char*) m->A()); Channels Ch = (Channels) m->B(); AppendOutput(Text, Ch); break; } case M_SELECT_TAB: { if (!d->Output) break; d->Output->Value(m->A()); break; } case M_DEBUG_ON_STATE: { bool Debugging = m->A(); bool Running = m->B(); if (d->Running != Running) { bool RunToNotRun = d->Running && !Running; d->Running = Running; if (RunToNotRun && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(LNotifyValueChanged); } } if (d->Debugging != Debugging) { d->Debugging = Debugging; if (!Debugging) { IdeDoc::ClearCurrentIp(); IdeDoc *c = GetCurrentDoc(); if (c) c->UpdateControl(); // Shutdown the debug context and free the memory DeleteObj(d->DbgContext); } } SetCtrlEnabled(IDM_START_DEBUG, !Debugging || !Running); SetCtrlEnabled(IDM_PAUSE_DEBUG, Debugging && Running); SetCtrlEnabled(IDM_RESTART_DEBUGGING, Debugging); SetCtrlEnabled(IDM_STOP_DEBUG, Debugging); SetCtrlEnabled(IDM_STEP_INTO, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OVER, Debugging && !Running); SetCtrlEnabled(IDM_STEP_OUT, Debugging && !Running); SetCtrlEnabled(IDM_RUN_TO, Debugging && !Running); break; } default: { if (d->DbgContext) d->DbgContext->OnEvent(m); break; } } return LWindow::OnEvent(m); } bool AppWnd::OnNode(const char *Path, ProjectNode *Node, FindSymbolSystem::SymAction Action) { // This takes care of adding/removing files from the symbol search engine. if (!Path || !Node) return false; if (d->FindSym) d->FindSym->OnFile(Path, Action, Node->GetPlatforms()); return true; } LOptionsFile *AppWnd::GetOptions() { return &d->Options; } class Options : public LDialog { AppWnd *App; LFontType Font; public: Options(AppWnd *a) { SetParent(App = a); if (LoadFromResource(IDD_OPTIONS)) { SetCtrlEnabled(IDC_FONT, false); MoveToCenter(); if (!Font.Serialize(App->GetOptions(), OPT_EditorFont, false)) { Font.GetSystemFont("Fixed"); } char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } LVariant v; if (App->GetOptions()->GetValue(OPT_Jobs, v)) SetCtrlValue(IDC_JOBS, v.CastInt32()); else SetCtrlValue(IDC_JOBS, 2); } } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDOK: { LVariant v; Font.Serialize(App->GetOptions(), OPT_EditorFont, true); App->GetOptions()->SetValue(OPT_Jobs, v = GetCtrlValue(IDC_JOBS)); } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { Font.DoUI(this, [&](auto ui) { char s[256]; if (Font.GetDescription(s, sizeof(s))) { SetCtrlName(IDC_FONT, s); } }); break; } } return 0; } }; void AppWnd::UpdateMemoryDump() { if (d->DbgContext) { const char *sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; int64 RowLen = GetCtrlValue(IDC_MEM_ROW_LEN); bool InHex = GetCtrlValue(IDC_MEM_HEX) != 0; d->DbgContext->FormatMemoryDump(iWord, (int)RowLen, InHex); } } int AppWnd::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_PROJECT_TREE: { if (n.Type == LNotifyDeleteKey) { ProjectNode *n = dynamic_cast(d->Tree->Selection()); if (n) n->Delete(); } break; } case IDC_DEBUG_EDIT: { if (n.Type == LNotifyReturnKey && d->DbgContext) { const char *Cmd = Ctrl->Name(); if (Cmd) { d->DbgContext->OnUserCommand(Cmd); Ctrl->Name(NULL); } } break; } case IDC_MEM_ADDR: { if (n.Type == LNotifyReturnKey) { if (d->DbgContext) { const char *s = Ctrl->Name(); if (s) { auto sWord = GetCtrlName(IDC_MEM_SIZE); int iWord = sWord ? atoi(sWord) : 1; d->DbgContext->OnMemoryDump(s, iWord, (int)GetCtrlValue(IDC_MEM_ROW_LEN), GetCtrlValue(IDC_MEM_HEX) != 0); } else if (d->DbgContext->MemoryDump) { d->DbgContext->MemoryDump->Print("No address specified."); } else { LAssert(!"No MemoryDump."); } } else LAssert(!"No debug context."); } break; } case IDC_MEM_ROW_LEN: { if (n.Type == LNotifyReturnKey) UpdateMemoryDump(); break; } case IDC_MEM_HEX: case IDC_MEM_SIZE: { UpdateMemoryDump(); break; } case IDC_DEBUG_TAB: { if (d->DbgContext && n.Type == LNotifyValueChanged) { switch (Ctrl->Value()) { case AppWnd::LocalsTab: { d->DbgContext->UpdateLocals(); break; } case AppWnd::WatchTab: { d->DbgContext->UpdateWatches(); break; } case AppWnd::RegistersTab: { d->DbgContext->UpdateRegisters(); break; } case AppWnd::CallStackTab: { d->DbgContext->UpdateCallStack(); break; } case AppWnd::ThreadsTab: { d->DbgContext->UpdateThreads(); break; } default: break; } } break; } case IDC_LOCALS_LIST: { if (d->Output->Locals && n.Type == LNotifyItemDoubleClick && d->DbgContext) { LListItem *it = d->Output->Locals->GetSelected(); if (it) { const char *Var = it->GetText(2); const char *Val = it->GetText(3); if (Var) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::ObjectTab); d->DbgContext->DumpObject(Var, Val); } } } break; } case IDC_CALL_STACK: { if (n.Type == LNotifyValueChanged) { if (d->Output->DebugTab) d->Output->DebugTab->Value(AppWnd::CallStackTab); } else if (n.Type == LNotifyItemSelect) { // This takes the user to a given call stack reference if (d->Output->CallStack && d->DbgContext) { LListItem *item = d->Output->CallStack->GetSelected(); if (item) { LAutoString File; int Line; if (d->DbgContext->ParseFrameReference(item->GetText(1), File, Line)) { LAutoString Full; if (d->FindSource(Full, File, NULL)) { GotoReference(Full, Line, false); const char *sFrame = item->GetText(0); if (sFrame && IsDigit(*sFrame)) d->DbgContext->SetFrame(atoi(sFrame)); } } } } } break; } case IDC_WATCH_LIST: { WatchItem *Edit = NULL; switch (n.Type) { case LNotifyDeleteKey: { LArray Sel; for (LTreeItem *c = d->Output->Watch->GetChild(); c; c = c->GetNext()) { if (c->Select()) Sel.Add(c); } Sel.DeleteObjects(); break; } case LNotifyItemClick: { Edit = dynamic_cast(d->Output->Watch->Selection()); break; } case LNotifyContainerClick: { // Create new watch. Edit = new WatchItem(d->Output); if (Edit) d->Output->Watch->Insert(Edit); break; } default: break; } if (Edit) Edit->EditLabel(0); break; } case IDC_THREADS: { if (n.Type == LNotifyItemSelect) { // This takes the user to a given thread if (d->Output->Threads && d->DbgContext) { LListItem *item = d->Output->Threads->GetSelected(); if (item) { LString sId = item->GetText(0); int ThreadId = (int)sId.Int(); if (ThreadId > 0) { d->DbgContext->SelectThread(ThreadId); } } } } break; } } return 0; } bool AppWnd::Build() { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL); return; } IdeDoc *Top; IdeProject *p = RootProject(); if (p) { UpdateState(-1, true); p->Build(false, GetBuildMode()); } else if ((Top = TopDoc())) { Top->Build(); } }); return false; } class RenameDlg : public LDialog { AppWnd *App; public: static RenameDlg *Inst; RenameDlg(AppWnd *a) { Inst = this; SetParent(App = a); MoveSameScreen(a); if (LoadFromResource(IDC_RENAME)) { LVariant v; if (App->GetOptions()->GetValue(OPT_FIX_RENAMED, v)) SetCtrlValue(IDC_FIX_RENAMED, v.CastInt32()); if (App->GetOptions()->GetValue(OPT_RENAMED_SYM, v)) SetCtrlName(IDC_SYM, v.Str()); SetAlwaysOnTop(true); DoModeless(); } } ~RenameDlg() { Inst = NULL; } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_APPLY: { LVariant v; App->GetOptions()->SetValue(OPT_RENAMED_SYM, v = GetCtrlName(IDC_SYM)); App->GetOptions()->SetValue(OPT_FIX_RENAMED, v = GetCtrlValue(IDC_FIX_RENAMED)); App->GetOptions()->SerializeFile(true); break; } case IDC_CLOSE: { EndModeless(); break; } } return 0; } }; void AppWnd::SeekHistory(int Offset) { d->SeekHistory(Offset); } RenameDlg *RenameDlg::Inst = NULL; bool AppWnd::ShowInProject(const char *Fn) { if (!Fn) return false; for (auto p: d->Projects) { ProjectNode *Node = NULL; if (p->FindFullPath(Fn, &Node)) { for (LTreeItem *i = Node->GetParent(); i; i = i->GetParent()) { i->Expanded(true); } Node->Select(true); Node->ScrollTo(); return true; } } return false; } int AppWnd::OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_EXIT: { LCloseApp(); break; } case IDM_OPTIONS: { auto dlg = new Options(this); dlg->DoModal(NULL); break; } case IDM_HELP: { LExecute(APP_URL); break; } case IDM_ABOUT: { LAbout a(this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); break; } case IDM_NEW: { IdeDoc *Doc; d->Docs.Insert(Doc = new IdeDoc(this, 0, 0)); if (Doc && d->Mdi) { LRect p = d->Mdi->NewPos(); Doc->LView::SetPos(p); Doc->Attach(d->Mdi); Doc->Focus(true); } break; } case IDM_OPEN: { LFileSelect *s = new LFileSelect; s->Parent(this); // printf("File open dlg from thread=%u\n", GetCurrentThreadId()); s->Open([&](auto s, auto ok) { // printf("open handler start... ok=%i thread=%u\n", ok, GetCurrentThreadId()); if (ok) OpenFile(s->Name()); // printf("open handler deleting...\n"); delete s; // printf("open handler deleted...\n"); }); break; } case IDM_SAVE_ALL: { SaveAll(NULL); break; } case IDM_SAVE: { IdeDoc *Top = TopDoc(); if (Top) Top->SetClean(NULL); break; } case IDM_SAVEAS: { IdeDoc *Top = TopDoc(); if (Top) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Save([&](auto s, auto ok) { Top->SetFileName(s->Name(), true); d->OnFile(s->Name()); delete s; }); } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top) { if (Top->OnRequestClose(false)) { Top->Quit(); } } DeleteObj(d->DbgContext); break; } case IDM_CLOSE_ALL: { CloseAll(); Name(AppName); break; } // // Editor // case IDM_UNDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Undo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REDO: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->Redo(); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFind(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_FIND_NEXT: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoFindNext(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_REPLACE: { LTextView3 *Doc = FocusEdit(); if (Doc) { Doc->DoReplace(NULL); } else LgiTrace("%s:%i - No focus doc.\n", _FL); break; } case IDM_GOTO: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->DoGoto(NULL); else { LInput *Inp = new LInput(this, NULL, LLoadString(L_TEXTCTRL_GOTO_LINE, "Goto [file:]line:"), "Goto"); Inp->DoModal([&](auto dlg, auto code) { LString s = Inp->GetStr(); LString::Array p = s.SplitDelimit(":,"); if (p.Length() == 2) { LString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true); } else LgiMsg(this, "Error: Needs a file name as well.", AppName); delete Inp; }); } break; } case IDM_CUT: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_CUT); break; } case IDM_COPY: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_COPY); break; } case IDM_PASTE: { LTextView3 *Doc = FocusEdit(); if (Doc) Doc->PostEvent(M_PASTE); break; } case IDM_FIND_IN_FILES: { if (!d->Finder) { d->Finder.Reset(new FindInFilesThread(d->AppHnd)); } if (d->Finder) { if (!d->FindParameters && d->FindParameters.Reset(new FindParams)) { LVariant var; if (GetOptions()->GetValue(OPT_ENTIRE_SOLUTION, var)) d->FindParameters->Type = var.CastInt32() ? FifSearchSolution : FifSearchDirectory; } FindInFiles Dlg(this, d->FindParameters); LViewI *Focus = GetFocus(); if (Focus) { LTextView3 *Edit = dynamic_cast(Focus); if (Edit && Edit->HasSelection()) { LAutoString a(Edit->GetSelection()); Dlg.Params->Text = a; } } IdeProject *p = RootProject(); if (p) { LAutoString Base = p->GetBasePath(); if (Base) Dlg.Params->Dir = Base; } Dlg.DoModal([&](auto dlg, auto code) { if (p && Dlg.Params->Type == FifSearchSolution) { Dlg.Params->ProjectFiles.Length(0); List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); for (unsigned i=0; iGetFullPath(); if (s) Dlg.Params->ProjectFiles.Add(s); } } LVariant var = d->FindParameters->Type == FifSearchSolution; GetOptions()->SetValue(OPT_ENTIRE_SOLUTION, var); d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) new FindParams(d->FindParameters)); }); } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { d->FindSym->OpenSearchDlg(this, [&](auto r) { if (r.File) GotoReference(r.File, r.Line, false); }); } break; } case IDM_GOTO_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchSymbol(); } break; } case IDM_FIND_PROJECT_FILE: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->SearchFile(); } else { auto d = new FindInProject(this); d->DoModal([](auto dlg, auto ctrlId){ delete dlg; }); } break; } case IDM_FIND_REFERENCES: { LViewI *f = LAppInst->GetFocus(); LDocView *doc = dynamic_cast(f); if (!doc) break; ssize_t c = doc->GetCaret(); if (c < 0) break; LString Txt = doc->Name(); char *s = Txt.Get() + c; char *e = s; while ( s > Txt.Get() && IsSymbolChar(s[-1])) s--; while (*e && IsSymbolChar(*e)) e++; if (e <= s) break; LString Word(s, e - s); if (!d->Finder) d->Finder.Reset(new FindInFilesThread(d->AppHnd)); if (!d->Finder) break; IdeProject *p = RootProject(); if (!p) break; List Projects; Projects.Insert(p); p->GetChildProjects(Projects); LArray Nodes; for (auto p: Projects) p->GetAllNodes(Nodes); LAutoPtr Params(new FindParams); Params->Type = FifSearchSolution; Params->MatchWord = true; Params->Text = Word; for (unsigned i = 0; i < Nodes.Length(); i++) { Params->ProjectFiles.New() = Nodes[i]->GetFullPath(); } d->Finder->Stop(); d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) Params.Release()); break; } case IDM_PREV_LOCATION: { d->SeekHistory(-1); break; } case IDM_NEXT_LOCATION: { d->SeekHistory(1); break; } // // Project // case IDM_NEW_PROJECT: { CloseAll(); IdeProject *p; d->Projects.Insert(p = new IdeProject(this)); if (p) { p->CreateProject(); } break; } case IDM_NEW_PROJECT_TEMPLATE: { NewProjectFromTemplate(this); break; } case IDM_OPEN_PROJECT: { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Projects", "*.xml"); s->Open([&](auto s, auto ok) { if (ok) { CloseAll(); OpenProject(s->Name(), NULL, Cmd == IDM_NEW_PROJECT); if (d->Tree) { d->Tree->Focus(true); } } delete s; }); break; } case IDM_IMPORT_DSP: { IdeProject *p = RootProject(); if (p) { LFileSelect *s = new LFileSelect; s->Parent(this); s->Type("Developer Studio Project", "*.dsp"); s->Open([&](auto s, auto ok) { if (ok) p->ImportDsp(s->Name()); delete s; }); } break; } case IDM_RUN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(); }); break; } case IDM_VALGRIND: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Execute(ExeValgrind); }); break; } case IDM_FIX_MISSING_FILES: { IdeProject *p = RootProject(); if (p) p->FixMissingFiles(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_FIND_DUPE_SYM: { IdeProject *p = RootProject(); if (p) p->FindDuplicateSymbols(); else LgiMsg(this, "No project loaded.", AppName); break; } case IDM_RENAME_SYM: { if (!RenameDlg::Inst) new RenameDlg(this); break; } case IDM_START_DEBUG: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (!p) { LgiMsg(this, "No project loaded.", "Error"); return; } LString ErrMsg; if (d->DbgContext) { d->DbgContext->OnCommand(IDM_CONTINUE); } else if ((d->DbgContext = p->Execute(ExeDebug, &ErrMsg))) { d->DbgContext->DebuggerLog = d->Output->DebuggerLog; d->DbgContext->Watch = d->Output->Watch; d->DbgContext->Locals = d->Output->Locals; d->DbgContext->CallStack = d->Output->CallStack; d->DbgContext->Threads = d->Output->Threads; d->DbgContext->ObjectDump = d->Output->ObjectDump; d->DbgContext->Registers = d->Output->Registers; d->DbgContext->MemoryDump = d->Output->MemoryDump; d->DbgContext->OnCommand(IDM_START_DEBUG); d->Output->Value(AppWnd::DebugTab); d->Output->DebugEdit->Focus(true); } else if (ErrMsg) { LgiMsg(this, "Error: %s", AppName, MB_OK, ErrMsg.Get()); } }); break; } case IDM_TOGGLE_BREAKPOINT: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) ToggleBreakpoint(Cur->GetFileName(), Cur->GetLine()); break; } case IDM_ATTACH_TO_PROCESS: case IDM_PAUSE_DEBUG: case IDM_RESTART_DEBUGGING: case IDM_RUN_TO: case IDM_STEP_INTO: case IDM_STEP_OVER: case IDM_STEP_OUT: { if (d->DbgContext) d->DbgContext->OnCommand(Cmd); break; } case IDM_STOP_DEBUG: { if (d->DbgContext && d->DbgContext->OnCommand(Cmd)) { DeleteObj(d->DbgContext); } break; } case IDM_BUILD: { Build(); break; } case IDM_STOP_BUILD: { IdeProject *p = RootProject(); if (p) p->StopBuild(); break; } case IDM_CLEAN: { SaveAll([&](bool status) { if (!status) { LgiTrace("%s:%i - status=%i\n", _FL, status); return; } IdeProject *p = RootProject(); if (p) p->Clean(true, GetBuildMode()); }); break; } case IDM_NEXT_MSG: { d->SeekMsg(1); break; } case IDM_PREV_MSG: { d->SeekMsg(-1); break; } case IDM_DEBUG_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(true); Release->Checked(false); } break; } case IDM_RELEASE_MODE: { LMenuItem *Debug = GetMenu()->FindItem(IDM_DEBUG_MODE); LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Debug && Release) { Debug->Checked(false); Release->Checked(true); } break; } // // Other // case IDM_LOOKUP_SYMBOLS: { IdeDoc *Cur = GetCurrentDoc(); if (Cur) { // LookupSymbols(Cur->Read()); } break; } case IDM_DEPENDS: { IdeProject *p = RootProject(); if (p) { LString Exe = p->GetExecutable(GetCurrentPlatform()); if (LFileExists(Exe)) { Depends Dlg(this, Exe); } else { LgiMsg(this, "Couldn't find '%s'\n", AppName, MB_OK, Exe ? Exe.Get() : ""); } } break; } case IDM_SP_TO_TAB: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(true); break; } case IDM_TAB_TO_SP: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->ConvertWhiteSpace(false); break; } case IDM_ESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(true); break; } case IDM_DESCAPE: { IdeDoc *Doc = FocusDoc(); if (Doc) Doc->EscapeSelection(false); break; } case IDM_SPLIT: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->SplitSelection(i->GetStr()); delete i; }); break; } case IDM_JOIN: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; LInput *i = new LInput(this, "", "Separator:", AppName); i->DoModal([&](auto dlg, auto ok) { if (ok) Doc->JoinSelection(i->GetStr()); delete i; }); break; } case IDM_EOL_LF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(false); break; } case IDM_EOL_CRLF: { IdeDoc *Doc = FocusDoc(); if (!Doc) break; Doc->SetCrLf(true); break; } case IDM_LOAD_MEMDUMP: { NewMemDumpViewer(this); break; } case IDM_SYS_CHAR_SUPPORT: { new SysCharSupport(this); break; } default: { int index = Cmd - IDM_RECENT_FILE; auto r = d->RecentFiles.IdxCheck(index)? d->RecentFiles[index] : NULL; if (r) { + auto idx = Cmd - IDM_RECENT_FILE; + if (idx > 0) + { + d->RecentFiles.DeleteAt(idx, true); + d->RecentFiles.AddAt(0, r); + } + IdeDoc *f = d->IsFileOpen(r); if (f) - { f->Raise(); - } else - { OpenFile(r); - } } index = Cmd - IDM_RECENT_PROJECT; auto p = d->RecentProjects.IdxCheck(index) ? d->RecentProjects[index] : NULL; if (p) { + auto idx = Cmd - IDM_RECENT_PROJECT; + if (idx > 0) + { + d->RecentProjects.DeleteAt(idx, true); + d->RecentProjects.AddAt(0, p); + } + CloseAll(); OpenProject(p, NULL, false); if (d->Tree) { d->Tree->Focus(true); } } IdeDoc *Doc = d->Docs[Cmd - IDM_WINDOWS]; if (Doc) { Doc->Raise(); } IdePlatform PlatIdx = (IdePlatform) (Cmd - IDM_MAKEFILE_BASE); const char *Platform = PlatIdx >= 0 && PlatIdx < PlatformMax ? PlatformNames[Cmd - IDM_MAKEFILE_BASE] : NULL; if (Platform) { IdeProject *p = RootProject(); if (p) { p->CreateMakefile(PlatIdx, false); } } break; } } return 0; } LTree *AppWnd::GetTree() { return d->Tree; } IdeDoc *AppWnd::TopDoc() { return d->Mdi ? dynamic_cast(d->Mdi->GetTop()) : NULL; } LTextView3 *AppWnd::FocusEdit() { return dynamic_cast(GetWindow()->GetFocus()); } IdeDoc *AppWnd::FocusDoc() { IdeDoc *Doc = TopDoc(); if (Doc) { if (Doc->HasFocus()) { return Doc; } else { LViewI *f = GetFocus(); LgiTrace("%s:%i - Edit doesn't have focus, f=%p %s doc.edit=%s\n", _FL, f, f ? f->GetClass() : 0, Doc->Name()); } } return 0; } void AppWnd::OnProjectDestroy(IdeProject *Proj) { if (d) { auto locked = Lock(_FL); // printf("OnProjectDestroy(%s) %i\n", Proj->GetFileName(), locked); d->Projects.Delete(Proj); if (locked) Unlock(); } else LAssert(!"No priv"); } void AppWnd::OnProjectChange() { LArray Views; if (d->Mdi->GetChildren(Views)) { for (unsigned i=0; i(Views[i]); if (Doc) Doc->OnProjectChange(); } } } void AppWnd::OnDocDestroy(IdeDoc *Doc) { if (d) { auto locked = Lock(_FL); d->Docs.Delete(Doc); d->UpdateMenus(); if (locked) Unlock(); } else { LAssert(!"OnDocDestroy no priv...\n"); } } BuildConfig AppWnd::GetBuildMode() { LMenuItem *Release = GetMenu()->FindItem(IDM_RELEASE_MODE); if (Release && Release->Checked()) { return BuildRelease; } return BuildDebug; } LList *AppWnd::GetFtpLog() { return d->Output->FtpLog; } LStream *AppWnd::GetBuildLog() { return d->Output->Txt[AppWnd::BuildTab]; } LStream *AppWnd::GetDebugLog() { return d->Output->Txt[AppWnd::DebugTab]; } void AppWnd::FindSymbol(int ResultsSinkHnd, const char *Sym, bool AllPlatforms) { d->FindSym->Search(ResultsSinkHnd, Sym, AllPlatforms); } bool AppWnd::GetSystemIncludePaths(::LArray &Paths) { if (d->SystemIncludePaths.Length() == 0) { #if !defined(WINNATIVE) // echo | gcc -v -x c++ -E - LSubProcess sp1("echo"); LSubProcess sp2("gcc", "-v -x c++ -E -"); sp1.Connect(&sp2); sp1.Start(true, false); char Buf[256]; ssize_t r; LStringPipe p; while ((r = sp1.Read(Buf, sizeof(Buf))) > 0) { p.Write(Buf, r); } bool InIncludeList = false; while (p.Pop(Buf, sizeof(Buf))) { if (stristr(Buf, "#include")) { InIncludeList = true; } else if (stristr(Buf, "End of search")) { InIncludeList = false; } else if (InIncludeList) { LAutoString a(TrimStr(Buf)); d->SystemIncludePaths.New() = a; } } #else char p[MAX_PATH_LEN]; LGetSystemPath(LSP_USER_DOCUMENTS, p, sizeof(p)); LMakePath(p, sizeof(p), p, "Visual Studio 2008\\Settings\\CurrentSettings.xml"); if (LFileExists(p)) { LFile f; if (f.Open(p, O_READ)) { LXmlTree t; LXmlTag r; if (t.Read(&r, &f)) { LXmlTag *Opts = r.GetChildTag("ToolsOptions"); if (Opts) { LXmlTag *Projects = NULL; char *Name; for (auto c: Opts->Children) { if (c->IsTag("ToolsOptionsCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "Projects")) { Projects = c; break; } } LXmlTag *VCDirectories = NULL; if (Projects) for (auto c: Projects->Children) { if (c->IsTag("ToolsOptionsSubCategory") && (Name = c->GetAttr("Name")) && !stricmp(Name, "VCDirectories")) { VCDirectories = c; break; } } if (VCDirectories) for (auto prop: VCDirectories->Children) { if (prop->IsTag("PropertyValue") && (Name = prop->GetAttr("Name")) && !stricmp(Name, "IncludeDirectories")) { char *Bar = strchr(prop->GetContent(), '|'); LToken t(Bar ? Bar + 1 : prop->GetContent(), ";"); for (int i=0; iSystemIncludePaths.New().Reset(NewStr(s)); } } } } } } } #endif } for (int i=0; iSystemIncludePaths.Length(); i++) { Paths.Add(NewStr(d->SystemIncludePaths[i])); } return true; } /* class SocketTest : public LWindow, public LThread { LTextLog *Log; public: SocketTest() : LThread("SocketTest") { Log = new LTextLog(100); SetPos(LRect(200, 200, 900, 800)); Attach(0); Visible(true); Log->Attach(this); Run(); } int Main() { LSocket s; s.SetTimeout(15000); Log->Print("Starting...\n"); auto r = s.Open("192.168.1.30", 7000); Log->Print("Open =%i\n", r); return 0; } }; */ class TestView : public LView { public: TestView() { LRect r(10, 10, 110, 210); SetPos(r); Sunken(true); printf("_BorderSize=%i\n", _BorderSize); } void OnPaint(LSurface *pdc) { auto c = GetClient(); pdc->Colour(LColour::Red); pdc->Line(c.x1, c.y1, c.x2, c.y2); pdc->Ellipse(c.x1+(c.X()/2)+10, c.y1+(c.Y()/2), c.X()/2, c.Y()/2); } }; #include "lgi/common/Tree.h" #include "lgi/common/List.h" class Test : public LWindow { public: Test() { LRect r(100, 100, 800, 700); SetPos(r); Name("Test"); SetQuitOnClose(true); if (Attach(0)) { // AddView(new TestView); // auto t = new LTree(10, 10, 10, 100, 200); auto t = new LTextLabel(10, 10, 10, 100, 35, "Text"); AddView(t); AttachChildren(); Visible(true); } } }; int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); LApp a(AppArgs, "LgiIde"); if (a.IsOk()) { a.AppWnd = new AppWnd; // a.AppWnd->_Dump(); a.Run(); } return 0; } diff --git a/Ide/LgiIdeProj.xml b/Ide/LgiIdeProj.xml --- a/Ide/LgiIdeProj.xml +++ b/Ide/LgiIdeProj.xml @@ -1,280 +1,281 @@ .\buildHaiku.py ./buildHaiku.py ./MacCocoa/LgiIde.xcodeproj Makefile.haiku ./lgiide ./lgiide LgiIde.exe LgiIde.exe WINDOWS WINNATIVE WINDOWS WINNATIVE POSIX POSIX LIBPNG_VERSION=\"1.2\" LIBPNG_VERSION=\"1.2\" MingW /home/matthew/Code/Lgi/trunk/Ide/Code/IdeProjectSettings.cpp ./Code ./Resources ../include ./Code ./Resources ../include ..\include\lgi\win /local/include ..\include\lgi\win /local/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/linux ../include/lgi/linux/Gtk ../../../../codelib/openssl/include ../include/lgi/haiku ../include/lgi/haiku ../include/lgi/mac/cocoa ../include/lgi/mac/cocoa imm32 imm32 magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc magic pthread `pkg-config --libs gtk+-3.0` -static-libgcc be be ..\Debug ..\Release + Executable lgiide lgiide LgiIde.exe LgiIde.exe lgiide lgiide C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk C:\Data\CodeLib\Gtk\include\gtk-2.0\gtk C:\Data\CodeLib\Gtk\include\gtk-2.0\gdk `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gtk+-3.0` ./Code/icon-haiku 4 4 0 1 SOME_TEST=testing /home/matthew/code/scribe/trunk_os/Linux/ScribeProj.xml diff --git a/Ide/Makefile.haiku b/Ide/Makefile.haiku --- a/Ide/Makefile.haiku +++ b/Ide/Makefile.haiku @@ -1,144 +1,145 @@ #!/usr/bin/make # .SILENT : CC = gcc CPP = g++ Target = lgiide ifndef Build Build = Debug endif BuildDir = $(Build) Flags = -fPIC -w -fno-inline -fpermissive ifeq ($(Build),Debug) Flags += -g -std=c++14 Tag = d Defs = -D_DEBUG -DHAIKU -D_REENTRANT -D_GNU_SOURCE -DPOSIX Libs = \ -L../Debug \ -lbe \ -lgnu \ -llgi$(Tag) \ -L../$(BuildDir) Inc = \ -I./Resources \ -I./Code \ -I../include/lgi/haiku \ -I../include else Flags += -s -Os -std=c++14 Defs = -DHAIKU -D_REENTRANT -D_GNU_SOURCE -DPOSIX Libs = \ -L../Release \ -lbe \ -lgnu \ -llgi$(Tag) \ -L../$(BuildDir) Inc = \ -I./Resources \ -I./Code \ -I../include/lgi/haiku \ -I../include endif # Dependencies Sources = Code/DebugContext.cpp \ Code/Debugger.cpp \ Code/AddFtpFile.cpp \ Code/WebFldDlg.cpp \ Code/DocEdit.cpp \ Code/DocEditStyling.cpp \ + Code/History.cpp \ Code/IdeDoc.cpp \ Code/FtpThread.cpp \ Code/IdeProject.cpp \ Code/IdeProjectSettings.cpp \ Code/levenshtein.c \ Code/MissingFiles.cpp \ Code/NewProjectFromTemplate.cpp \ Code/ProjectNode.cpp \ Code/FindInFiles.cpp \ Code/FindSymbol.cpp \ Code/History.cpp \ Code/JavascriptParser.cpp \ Code/PythonParser.cpp \ Code/SimpleCppParser.cpp \ Code/SysCharSupport.cpp \ Code/IdeCommon.cpp \ Code/LgiIde.cpp \ Code/LgiUtils.cpp \ Code/MemDumpViewer.cpp \ Code/SpaceTabConv.cpp \ ../src/common/Lgi/SubProcess.cpp \ ../src/common/Lgi/About.cpp \ ../src/common/Net/Ftp.cpp \ ../src/common/Coding/LexCpp.cpp \ ../src/common/Coding/ParseCpp.cpp \ ../src/common/Text/Html.cpp \ ../src/common/Text/HtmlCommon.cpp \ ../src/common/Text/HtmlParser.cpp \ ../src/common/Text/DocView.cpp \ ../src/common/Text/Homoglyphs/Homoglyphs.cpp \ ../src/common/Text/Homoglyphs/HomoglyphsTable.cpp \ ../src/common/Net/Http.cpp \ ../src/common/Lgi/LgiMain.cpp \ ../src/common/Lgi/Mdi.cpp \ ../src/common/Net/OpenSSLSocket.cpp \ ../src/common/Gdc2/Filters/Png.cpp \ ../src/common/Text/TextConvert.cpp SourceLst := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(Sources))) Objects := $(addprefix $(BuildDir)/,$(SourceLst)) #all: # echo "../Debug/$(Objects:.o=.d)" # Target # addattr -f ./Code/icon-haiku -t "'VICN'" "BEOS:ICON" $(Target) # Executable target $(Target) : ../$(BuildDir)/liblgi$(Tag).so $(Objects) @echo Linking $(Target) [$(Build)]... $(CPP) -Wl,-export-dynamic,-R. -o \ $(Target) $(Objects) $(Libs) @echo Done. .PHONY: ../$(BuildDir)/liblgi$(Tag).so ../$(BuildDir)/liblgi$(Tag).so : export Build=$(Build); \ $(MAKE) -C .. -f Makefile.haiku .SECONDEXPANSION: $(Objects): $(BuildDir)/%.o: $$(wildcard %.c*) mkdir -p $(@D) @echo $( + + + - - - - - - - + + + + + + + Makefile.linux Makefile.win64 Makefile.macosx gcc 0 ./include ./private/common ./include ./private/common ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux ./include/lgi/linux ./include/lgi/linux/Gtk ./private/linux include/lgi/win include/lgi/win /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` /usr/include/libappindicator3-0.1 `pkg-config --cflags gtk+-3.0` `pkg-config --cflags gstreamer-1.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` magic appindicator3 crypt -static-libgcc `pkg-config --libs gtk+-3.0` lgi-gtk3 lgi-gtk3 DynamicLibrary diff --git a/Lgi_vs2019.vcxproj b/Lgi_vs2019.vcxproj --- a/Lgi_vs2019.vcxproj +++ b/Lgi_vs2019.vcxproj @@ -1,471 +1,471 @@  Debug x64 ReleaseNoOptimize x64 Release x64 Lgi {95DF9CA4-6D37-4A85-A648-80C2712E0DA1} 10.0 DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode DynamicLibrary v142 false Unicode <_ProjectFileVersion>12.0.30501.0 .\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include LgiHaiku19x64 .\Lib\ $(Platform)$(Configuration)19\ true $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include LgiHaiku19x64d .\Lib\ $(Platform)$(Configuration)19\ false $(VC_IncludePath);$(WindowsSDK_IncludePath);..\..\..\CodeLib\libiconv-1.14\include LgiHaiku19x64nop NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 _DEBUG;%(PreprocessorDefinitions) true true X64 .\Debug/Lgi.tlb Disabled include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;LGI_LIBRARY;_DEBUG;WINDOWS;LGI_RES;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level3 true ProgramDatabase Default _DEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) NotSet $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows false $(OutDir)$(TargetName).lib MachineX64 NDEBUG;%(PreprocessorDefinitions) true true X64 .\Release/Lgi.tlb MinSpace OnlyExplicitInline include;include\lgi\win;private\common;private\win;..\..\..\CodeLib\libiconv\include;..\..\scribe\libs\build-x64\libiconv-1.17\include;%(AdditionalIncludeDirectories) WIN64;NDEBUG;WINDOWS;LGI_RES;LGI_LIBRARY;%(PreprocessorDefinitions) true MultiThreadedDLL true true $(IntDir)$(TargetName).pch $(IntDir) $(IntDir) $(IntDir) Level2 true ProgramDatabase Default NDEBUG;%(PreprocessorDefinitions) 0x0c09 ComCtl32.lib;Ws2_32.lib;UxTheme.lib;imm32.lib;%(AdditionalDependencies) $(OutDir)$(TargetFileName) true true $(OutDir)$(TargetName).pdb Windows true false $(OutDir)$(TargetName).lib MachineX64 false true true false false false true true true true true true false false false true true true true true true - + \ No newline at end of file diff --git a/Lgi_vs2019.vcxproj.filters b/Lgi_vs2019.vcxproj.filters --- a/Lgi_vs2019.vcxproj.filters +++ b/Lgi_vs2019.vcxproj.filters @@ -1,782 +1,782 @@  {afe8cb77-9ad1-4536-bbdd-3c127e7ed08c} cpp;c;cxx;rc;def;r;odl;idl;hpj;bat {66a64573-871b-4499-ae26-c19e9e2a514a} {3fc23ef0-f144-4f1f-a9b4-18d3392bb63d} {c6cd6d73-d33c-4413-ade1-9dad78e2dc9c} {c8684fc7-2e3c-4f15-8284-9d44b044f6c6} {87b1c801-b9ce-4f6c-97ab-a8f89aee9594} {c06c25f2-2c07-4900-a517-4a6a324069e9} {2c01a737-36cf-4197-bfa1-20395060263f} {1e4cd802-8b94-4328-930e-37bfbfbedff5} {01075698-dde2-4ed0-808f-7dd54414b597} {4a6845a8-e6ec-47d5-8f6c-aa0cfdbc68df} {f567a76b-edd5-434d-b0d9-d51481f34845} {3dee0237-b01c-44e8-a730-08dc661df82f} {bbaaace6-0ac7-4e76-90ae-9e5d5a0cb899} {71e7b970-a070-40a7-a99f-88c215e14e44} {6e115ea1-09fb-492b-82b6-428afe17fed9} {719ef36f-419f-46f9-aef9-2f8158e4d106} {fb221556-3700-4dd8-ba9a-10b5d66bfe54} {8e9f0321-56ae-4485-a338-e87d714c7f50} {c6050f41-574b-4a92-9ce5-860be2719ecf} {a07cd969-801e-4ec9-9394-e34912a3271d} {e067fae0-ef98-4e7a-8434-6f36590ba0e6} {0a3e2151-b13a-407a-8cd9-ccb20f15cacf} {c72248a4-c2f0-43b9-950d-89d09bfddbb3} {dad60202-c763-4f32-9dfb-fe7def4637ee} {532dfa4a-58d3-4133-9ed6-a9fbf7f1556e} {5409aca4-2a55-4b2f-a719-d3db4e3cd7e4} {e3a3aadd-47ef-4723-9bcc-7db1f75a18b0} {c4327acf-78c3-4ef1-b8bc-3aac9ea52b41} {b3c906b8-595e-4641-8eec-8ad03ab13f6f} {baf0a65b-4a9c-4b11-850d-a957c19a22bf} {6e349f5b-36a8-4821-9574-4040a3784204} {ddfdebae-8bcf-4224-8938-2716aba03822} {a126c55a-edee-489f-a852-25cbd1b433b2} {ab5fd4a0-3862-42fd-b4ff-d5d8b0443f53} {048d5e0a-220f-4911-999d-96965eb53691} {258aef64-8cd0-4838-8131-147196656a99} {814a5d81-3fd5-461b-a4a3-cda593ea404b} {5fe450b8-5fa9-440e-9fb0-03860b3548d0} h;hpp;hxx;hm;inl Source Files\Core\Resources Source Files\Core\Resources Source Files\Core\Skin Source Files\General\Hash Source Files\General\Hash Source Files\Graphics Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Graphics\Applicators Source Files\Dialogs Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Dialogs Source Files\Dialogs Source Files\Text Source Files\Widgets\Native Windows Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Graphics\Font Source Files\General\Clipboard Source Files\Graphics Source Files\Graphics\Colour Reduction Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Core\Memory Subsystem Source Files\Text Source Files\Text Source Files\Core\DateTime Source Files\Widgets\Native Windows Source Files\Graphics\Font Source Files\Dialogs Source Files\Core\Drag and Drop Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\General Source Files\Core\File Source Files\Core\File Source Files\Graphics\Filters Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics\Font Source Files\Graphics Source Files\Graphics\Gdi Leak Source Files\General Source Files\Graphics Source Files\Dialogs Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Libraries Source Files\General Source Files\General Source Files\General Source Files\Widgets\Xp Source Files\Dialogs Source Files\Text Source Files\Core\Memory Subsystem Source Files\Graphics\Surfaces Source Files\Core\Memory Subsystem Source Files\Core\Menus Source Files\Core\Menus Source Files\General Source Files\General\Mru Source Files\Core\Mutex Source Files\Network Source Files\Network Source Files\General Source Files\Core\File Source Files\Widgets\Xp Source Files\General Source Files\Graphics Source Files\Widgets\Xp Source Files\Graphics\Surfaces Source Files\Graphics Source Files\Widgets\Native Windows Source Files\Dialogs Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\General Source Files\Graphics Source Files\Graphics\Surfaces Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Native Windows Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Memory Subsystem Source Files\Text Source Files\Widgets\Xp Source Files\Core\Process Source Files\Graphics\Surfaces Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Core\Threads Source Files\Core\Threads Source Files\Core\Threads Source Files\Text Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Widgets\Xp Source Files\Graphics\Font Source Files\Text Source Files\Text Source Files\Network Source Files\Text Source Files\Core\Variant Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Interface Source Files\Text Source Files\Widgets\Xp Source Files\Core\Resources Source Files\Core\Resources Source Files\General\Hash Source Files\General\Hash Source Files\Graphics Source Files\Graphics\Gdi Leak Header Files Header Files Header Files Header Files Header Files Header Files Source Files\Graphics\Font Source Files\Interface Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files - - Header Files - Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files + + Header Files + Header Files \ No newline at end of file diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp --- a/Lvc/Src/Main.cpp +++ b/Lvc/Src/Main.cpp @@ -1,1666 +1,1695 @@ #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 "../Resources/resdefs.h" #ifdef WINDOWS #include "../Resources/resource.h" #endif ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; #define DEFAULT_BUILD_FIX_MSG "Build fix." #define OPT_Hosts "Hosts" #define OPT_Host "Host" 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([&](auto dlg, auto status) { if (status) SetCtrlName(EditId, s->Name()); delete dlg; }); } 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.NewGStr().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; } 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::Array a = s.Replace("\r").Split("\n"); LArray index; LString Diff, oldName, newName; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(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, NULL, 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) { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } } } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; - LBox *FoldersBox; + LBox *FoldersBox = NULL; bool CallMethod(const char *MethodName, LVariant *ReturnValue, LArray &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *ReturnValue = (AppPriv*)this; return true; } return false; } public: App() { - FoldersBox = NULL; - 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); LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (f) { 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([&](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 = [&](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); Tree->Insert(new VcFolder(this, u.ToString())); } }; if (!Fld) { auto s = new LFileSelect; s->Parent(this); s->OpenFolder([&](auto dlg, auto status) { if (status) Load(dlg->Name()); delete dlg; }); } 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)); 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); - break; + // Fall through } case IDC_FILTER_FOLDERS: { - LString n = c->Name(); + 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); - break; + // Fall through } case IDC_FILTER_COMMITS: { - LString n = c->Name(); + 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); - break; + // Fall through } case IDC_FILTER_FILES: { - LString n = c->Name(); + 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 : NULL); SetCtrlName(IDC_USER, cur ? cur->User : NULL); SetCtrlName(IDC_PASS, cur ? cur->Pass : 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(); } LAssert(VcCommit::Instances == 0); return 0; } diff --git a/Lvc/Src/PatchViewer.cpp b/Lvc/Src/PatchViewer.cpp --- a/Lvc/Src/PatchViewer.cpp +++ b/Lvc/Src/PatchViewer.cpp @@ -1,600 +1,604 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Box.h" #include "lgi/common/TextLog.h" #include "lgi/common/TableLayout.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/FileSelect.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/XmlTreeUi.h" enum Ctrls { IDC_STATIC = -1, IDC_BOX1 = 100, IDC_BOX2, IDC_IN, IDC_OUT, IDC_TBL, IDC_PATCH_FILE, IDC_BROWSE_PATCH, IDC_BASE_DIR, IDC_BROWSE_BASE, IDC_APPLY, IDC_SAVE_PATCH, }; template ssize_t lineLen(T *start) { auto s = start; for (; *s && *s != '\n'; s++); return s - start; } class PatchView : public LTextLog { public: PatchView(int id) : LTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto l: Line) { auto s = Text + l->Start; if (*s == '+' && s[1] != '+') { l->c = LColour::Green; } else if (*s == '-' && s[1] != '-') { l->c = LColour::Red; } else if (*s == '@' && s[1] == '@') { l->c.Rgb(128, 128, 128); l->Back.Rgb(222, 222, 222); } } } }; class FileView : public LTextLog { public: LArray Fore, Back; FileView(int id) : LTextLog(id) { } bool Open(const char *Name, const char *Cs = NULL) { Fore.Length(0); Back.Length(0); return LTextView3::Open(Name, Cs); } void Reset(const char16 *t) { NameW(t); Fore.Length(0); Back.Length(0); } void Update() { PourStyle(0, Size); Invalidate(); } void PourStyle(size_t Start, ssize_t Length) { for (size_t i=0; iStart; if (Fore.IdxCheck(i)) { auto &c = Fore[i]; if (c.IsValid()) l->c = c; } if (Back.IdxCheck(i)) { auto &c = Back[i]; if (c.IsValid()) l->Back = c; } } } }; class PatchViewer : public LWindow, public LXmlTreeUi { LBox *box1 = NULL; LBox *box2 = NULL; FileView *in = NULL; LTextLog *out = NULL; LTableLayout *tbl = NULL; LOptionsFile *Opts = NULL; public: PatchViewer(LViewI *Parent, LOptionsFile *opts) : Opts(opts) { LRect r(0, 0, 1600, 1000); SetPos(r); Name("Patcher"); MoveToCenter(); Map("PatchFile", IDC_PATCH_FILE); Map("PatchBaseDir", IDC_BASE_DIR); if (!Attach(0)) return; AddView(box1 = new LBox(IDC_BOX1, true)); box1->AddView(tbl = new LTableLayout(IDC_TBL)); tbl->GetCss(true)->Height("6em"); int y = 0; auto c = tbl->GetCell(0, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Patch file:")); c->VerticalAlign(LCss::VerticalMiddle); c->PaddingLeft("0.5em"); c->PaddingTop("0.5em"); c = tbl->GetCell(1, y); c->Add(new LEdit(IDC_PATCH_FILE, 0, 0, 60, 20)); c->PaddingTop("0.5em"); c = tbl->GetCell(2, y++); c->Add(new LButton(IDC_BROWSE_PATCH, 0, 0, -1, -1, "...")); c->PaddingRight("0.5em"); c->PaddingTop("0.5em"); c = tbl->GetCell(0, y); c->Add(new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Base folder:")); c->VerticalAlign(LCss::VerticalMiddle); c->PaddingLeft("0.5em"); c = tbl->GetCell(1, y); c->Add(new LEdit(IDC_BASE_DIR, 0, 0, 60, 20)); c = tbl->GetCell(2, y++); c->Add(new LButton(IDC_BROWSE_BASE, 0, 0, -1, -1, "...")); c->PaddingRight("0.5em"); c = tbl->GetCell(0, y); c = tbl->GetCell(1, y); c->Add(new LButton(IDC_SAVE_PATCH, 0, 0, -1, -1, "Save Patch")); c->TextAlign(LCss::AlignRight); c = tbl->GetCell(2, y); c->Add(new LButton(IDC_APPLY, 0, 0, -1, -1, "Apply")); c->PaddingRight("0.5em"); box1->AddView(box2 = new LBox(IDC_BOX2)); box2->AddView(in = new FileView(IDC_IN)); box2->AddView(out = new PatchView(IDC_OUT)); Convert(Opts, this, true); AttachChildren(); Visible(true); in->Name("...select a patch blob..."); auto p = GetCtrlName(IDC_PATCH_FILE); if (p) Open(p); } ~PatchViewer() { Convert(Opts, this, false); } bool Open(const char *Patch) { if (!out->LTextView3::Open(Patch)) return false; SetCtrlName(IDC_PATCH_FILE, Patch); return true; } template LArray GetLines(T *txt) { LArray lines; lines.Add(txt); for (auto t = txt; *t; t++) { if (*t == '\n' && *t) lines.Add(t + 1); } return lines; } struct FilePatch { LString File; LArray Hunks; }; bool Apply() { auto patch = out->NameW(); auto lines = GetLines(out->NameW()); LArray Patches; auto error = [](const char *msg) -> bool { LgiTrace("Apply error: %s\n", msg); return false; }; auto scanLines = [&lines](const char16 *key, size_t from) -> ssize_t { auto len = Strlen(key); for (size_t i = from; i LString { auto ptr = lines[idx]; LString s(ptr, lineLen(ptr)); return s; }; ssize_t aFile = -1, bFile = -1; for (size_t i=0; i 0 && bFile > 0 && aFile == bFile - 1) { FilePatch &fp = Patches.New(); auto s = lineAt(aFile); fp.File = s(6,-1); LgiTrace("File: %s\n", fp.File.Get()); // Collect blobs LRange *cur = NULL; size_t n = bFile + 1; for (; n < lines.Length(); n++) { auto ln = lines[n]; if (*ln == ' ') continue; bool start = ln[0] == '@' && ln[1] == '@'; bool end = (ln[0] == '-' && ln[1] == '-'); bool finish = Strnicmp(ln, L"diff ", 5) == 0; if ((start || end || finish) && cur) { cur->Len = n - cur->Start; cur = NULL; } if (start && !cur) { cur = &fp.Hunks.New(); cur->Start = n; } if (finish) break; } for (auto &b: fp.Hunks) LgiTrace(" Blob: %i:%i\n", (int)b.Start, (int)b.Len); i = n; aFile = bFile = -1; } } // Iterate all the patches and apply them... for (auto &fp: Patches) { LFile::Path path(GetCtrlName(IDC_BASE_DIR), fp.File); if (!path.Exists()) { LgiTrace("%s:%i - File to patch '%s' doesn't exist.\n", _FL, path.GetFull().Get()); return false; } LFile f(path, O_READ); auto utf = f.Read(); size_t crlf = 0, lf = 0; for (const char *s = utf; *s; s++) { if (s[0] == '\r' && s[1] == '\n') { crlf++; s++; } else if (s[0] == '\n') { lf++; } } auto eol = crlf > lf ? L"\r\n" : L"\n"; auto eolLen = Strlen(eol); LAutoWString wide(Utf8ToWide(utf)); LArray docLines; for (char16 *w = wide; *w; ) { for (auto e = w; true; e++) { if (!Strncmp(e, eol, eolLen)) { docLines.New().Reset(NewStrW(w, e - w)); w = e + eolLen; break; } if (!*e) { docLines.New().Reset(NewStrW(w, e - w)); w = e; break; } } } /* for (size_t i=0; i (ssize_t)docLines.Length()) return error("docLine out of range."); auto docTxt = docLines[docLine].Get(); auto docTxtLen = lineLen(docTxt); auto inputLineLen = lineLen(inputTxt); if (*inputTxt == '+') { // Process insert: add 'inputLine' at docLine LAutoWString insert(NewStrW(inputTxt + 1, inputLineLen - 1)); docLines.AddAt(docLine, insert); } else { // Check content... if (docTxtLen != inputLineLen - 1) return error("doc and input line lengths don't match."); auto cmp = Strncmp(docTxt, inputTxt + 1/*prefix char*/, docTxtLen); if (cmp) return error("doc and input line content don't match."); if (*inputTxt == '-') { // Process delete... docLines.DeleteAt(docLine--, true); } } docLine++; inputTxt = lines[hunk.Start + ++inputLine]; } } } return true; } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BROWSE_PATCH: { LFileSelect s; s.Parent(this); - if (s.OpenFolder()) + s.InitialDir(GetCtrlName(IDC_PATCH_FILE)); + if (s.Open()) { + SetCtrlName(IDC_PATCH_FILE, s.Name()); Open(s.Name()); } break; } case IDC_BROWSE_BASE: { LFileSelect s; s.Parent(this); if (s.OpenFolder()) { SetCtrlName(IDC_BASE_DIR, s.Name()); } break; } case IDC_SAVE_PATCH: { auto patchFile = GetCtrlName(IDC_PATCH_FILE); out->Save(patchFile); break; } case IDC_OUT: { if (n.Type != LNotifyCursorChanged) break; auto car = out->GetCaret(); - LgiTrace("CursorChanged: %i\n", (int)car); auto txt = out->NameW(); + if (!txt) + break; + ssize_t caratLine = -1; LArray lines; lines.Add(txt); for (auto t = txt; *t; t++) { if (caratLine < 0 && (t - txt) >= car) caratLine = lines.Length() - 1; if (*t == '\n' && *t) lines.Add(t + 1); } if (caratLine < 0) { LgiTrace("%s:%i - No line num for carat pos.\n", _FL); break; } if (caratLine >= (ssize_t)lines.Length()) { LgiTrace("%s:%i - carat line greater than lines.len.\n", _FL); break; } LRange blob(-1, 0); for (ssize_t s = caratLine; s >= 0; s--) { auto ln = lines[s]; if (ln[0] == '@' && ln[1] == '@') { blob.Start = s; break; } } for (ssize_t e = caratLine; e < (ssize_t)lines.Length(); e++) { auto ln = lines[e]; if ( (ln[0] == '@' && ln[1] == '@') || (ln[0] == '-' && ln[1] == '-') ) { blob.Len = e - blob.Start; break; } else if (e == lines.Length() - 1) { blob.Len = e - blob.Start + 1; break; } } if (!blob.Valid()) { LgiTrace("%s:%i - invalid patch blob size.\n", _FL); break; } auto filesIdx = blob.Start; while (filesIdx >= 0) { auto t = lines[filesIdx]; if (Strnicmp(t, L"--- a/", 6) == 0) break; filesIdx--; } if (filesIdx == 0) { LgiTrace("%s:%i - couldn't find file names lines.\n", _FL); break; } auto aFile = lines[filesIdx]; auto bFile = lines[filesIdx + 1]; LFile::Path p(GetCtrlName(IDC_BASE_DIR), LString(aFile, lineLen(aFile))(5,-1)); if (!p.Exists()) { LgiTrace("%s:%i - file '%s' doesn't exist.\n", _FL, p.GetFull().Get()); break; } if (!in->Open(p)) { LgiTrace("%s:%i - failed to open file '%s'.\n", _FL, p.GetFull().Get()); break; } auto sText = lines[blob.Start]; auto eText = lines[blob.End()]; auto ln = LString(sText, lineLen(sText)); auto hdr = ln.SplitDelimit(); auto before = hdr[1].SplitDelimit(","); auto after = hdr[2].SplitDelimit(","); auto beforeLn = -before[0].Int() - 1; auto beforeEnd = beforeLn + before[1].Int(); size_t blobIdx = blob.Start + 1; for (ssize_t i=beforeLn; iBack[i].Rgb(222, 222, 222); auto patchLn = lines[blobIdx++]; while (*patchLn == '+') patchLn = lines[blobIdx++]; if (*patchLn != '+') { auto inTxt = in->TextAtLine(i); auto inLen = lineLen(inTxt); if (Strnicmp(inTxt, patchLn + 1, inLen)) { in->Fore[i] = LColour::Red; } else { if (*patchLn == '-') in->Fore[i] = LColour::Green; } } } in->Update(); in->SetLine((int)beforeLn); break; } case IDC_APPLY: { Apply(); break; } } return LWindow::OnNotify(Ctrl, n); } }; void OpenPatchViewer(LViewI *Parent, LOptionsFile *Opts) { new PatchViewer(Parent, Opts); } diff --git a/Lvc/Src/VcFolder.cpp b/Lvc/Src/VcFolder.cpp --- a/Lvc/Src/VcFolder.cpp +++ b/Lvc/Src/VcFolder.cpp @@ -1,4480 +1,4475 @@ #include "Lvc.h" #include "../Resources/resdefs.h" #include "lgi/common/Combo.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Json.h" #include "lgi/common/ProgressDlg.h" #ifndef CALL_MEMBER_FN #define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember)) #endif #define MAX_AUTO_RESIZE_ITEMS 2000 #define PROFILE_FN 0 #if PROFILE_FN #define PROF(s) Prof.Add(s) #else #define PROF(s) #endif class TmpFile : public LFile { int Status; LString Hint; public: TmpFile(const char *hint = NULL) { Status = 0; if (hint) Hint = hint; else Hint = "_lvc"; } LFile &Create() { LFile::Path p(LSP_TEMP); p += Hint; do { char s[256]; sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand()); p += s; } while (p.Exists()); Status = LFile::Open(p.GetFull(), O_READWRITE); return *this; } }; bool TerminalAt(LString Path) { #if defined(MAC) return LExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path); #elif defined(WINDOWS) TCHAR w[MAX_PATH_LEN]; auto r = GetWindowsDirectory(w, CountOf(w)); if (r > 0) { LFile::Path p = LString(w); p += "system32\\cmd.exe"; FileDev->SetCurrentFolder(Path); return LExecute(p); } #elif defined(LINUX) LExecute("gnome-terminal", NULL, Path); #endif return false; } int Ver2Int(LString v) { auto p = v.Split("."); int i = 0; for (auto s : p) { auto Int = s.Int(); if (Int < 256) { i <<= 8; i |= (uint8_t)Int; } else { LAssert(0); return 0; } } return i; } int ToolVersion[VcMax] = {0}; #define DEBUG_READER_THREAD 0 #if DEBUG_READER_THREAD #define LOG_READER(...) printf(__VA_ARGS__) #else #define LOG_READER(...) #endif ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread") { Vcs = vcs; Process = p; Out = out; Result = -1; FilterCount = 0; // We don't start this thread immediately... because the number of threads is scaled to the system // resources, particularly CPU cores. } ReaderThread::~ReaderThread() { Out = NULL; while (!IsExited()) LSleep(1); } const char *HgFilter = "We\'re removing Mercurial support"; const char *CvsKill = "No such file or directory"; int ReaderThread::OnLine(char *s, ssize_t len) { switch (Vcs) { case VcHg: { if (strnistr(s, HgFilter, len)) FilterCount = 4; if (FilterCount > 0) { FilterCount--; return 0; } else if (LString(s, len).Strip().Equals("remote:")) { return 0; } break; } case VcCvs: { if (strnistr(s, CvsKill, len)) return -1; break; } default: break; } return 1; } bool ReaderThread::OnData(char *Buf, ssize_t &r) { LOG_READER("OnData %i\n", (int)r); #if 1 char *Start = Buf; for (char *c = Buf; c < Buf + r;) { bool nl = *c == '\n'; c++; if (nl) { int Result = OnLine(Start, c - Start); if (Result < 0) { // Kill process and exit thread. Process->Kill(); return false; } if (Result == 0) { ssize_t LineLen = c - Start; ssize_t NextLine = c - Buf; ssize_t Remain = r - NextLine; if (Remain > 0) memmove(Start, Buf + NextLine, Remain); r -= LineLen; c = Start; } else Start = c; } } #endif Out->Write(Buf, r); return true; } int ReaderThread::Main() { bool b = Process->Start(true, false); if (!b) { LString s("Process->Start failed.\n"); Out->Write(s.Get(), s.Length(), ErrSubProcessFailed); return ErrSubProcessFailed; } char Buf[1024]; ssize_t r; LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle()); while (Process->IsRunning()) { if (Out) { LOG_READER("%s:%i - starting read.\n", _FL); r = Process->Read(Buf, sizeof(Buf)); LOG_READER("%s:%i - read=%i.\n", _FL, (int)r); if (r > 0) { if (!OnData(Buf, r)) return -1; } } else { Process->Kill(); return -1; break; } } LOG_READER("%s:%i - process loop done.\n", _FL); if (Out) { while ((r = Process->Read(Buf, sizeof(Buf))) > 0) OnData(Buf, r); } LOG_READER("%s:%i - loop done.\n", _FL); Result = (int) Process->GetExitValue(); #if _DEBUG if (Result) printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result); #endif return Result; } ///////////////////////////////////////////////////////////////////////////////////////////// int VcFolder::CmdMaxThreads = 0; int VcFolder::CmdActiveThreads = 0; void VcFolder::Init(AppPriv *priv) { if (!CmdMaxThreads) CmdMaxThreads = LAppInst->GetCpuCount(); d = priv; IsCommit = false; IsLogging = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; IsBranches = StatusNone; IsIdent = StatusNone; Unpushed = Unpulled = -1; Type = VcNone; CmdErrors = 0; CurrentCommitIdx = -1; Expanded(false); Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); LAssert(d != NULL); } VcFolder::VcFolder(AppPriv *priv, const char *uri) { Init(priv); Uri.Set(uri); GetType(); } VcFolder::VcFolder(AppPriv *priv, LXmlTag *t) { Init(priv); Serialize(t, false); } VcFolder::~VcFolder() { Log.DeleteObjects(); } VersionCtrl VcFolder::GetType() { if (Type == VcNone) Type = d->DetectVcs(this); return Type; } const char *VcFolder::LocalPath() { if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty()) { LAssert(!"Shouldn't call this if not a file path."); return NULL; } auto c = Uri.sPath.Get(); #ifdef WINDOWS if (*c == '/') c++; #endif return c; } const char *VcFolder::GetText(int Col) { switch (Col) { case 0: { if (Uri.IsFile()) Cache = LocalPath(); else Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get()); if (Cmds.Length()) Cache += " (...)"; return Cache; } case 1: { CountCache.Printf("%i/%i", Unpulled, Unpushed); CountCache = CountCache.Replace("-1", "--"); return CountCache; } } return NULL; } bool VcFolder::Serialize(LXmlTag *t, bool Write) { if (Write) t->SetContent(Uri.ToString()); else { LString s = t->GetContent(); bool isUri = s.Find("://") >= 0; if (isUri) Uri.Set(s); else Uri.SetFile(s); } return true; } LXmlTag *VcFolder::Save() { LXmlTag *t = new LXmlTag(OPT_Folder); if (t) Serialize(t, true); return t; } const char *VcFolder::GetVcName() { const char *Def = NULL; switch (GetType()) { case VcGit: Def = "git"; break; case VcSvn: Def = "svn"; break; case VcHg: Def = "hg"; break; case VcCvs: Def = "cvs"; break; default: break; } if (!VcCmd) { LString Opt; Opt.Printf("%s-path", Def); LVariant v; if (d->Opts.GetValue(Opt, v)) VcCmd = v.Str(); } if (!VcCmd) VcCmd = Def; return VcCmd; } bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback) { Result Ret; Ret.Code = -1; const char *Exe = GetVcName(); if (!Exe || CmdErrors > 2) return false; if (Uri.IsFile()) { new ProcessCallback(Exe, Args, LocalPath(), Logging == LogNone ? d->Log : NULL, GetTree()->GetWindow(), Callback); } else { LAssert(!"Impl me."); return false; } return true; } bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging) { const char *Exe = GetVcName(); if (!Exe) return false; if (CmdErrors > 2) return false; if (Uri.IsFile()) { if (d->Log && Logging != LogSilo) d->Log->Print("%s %s\n", Exe, Args); LAutoPtr Process(new LSubProcess(Exe, Args)); if (!Process) return false; Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath()); #ifdef MAC // Mac GUI apps don't share the terminal path, so this overrides that and make it work auto Path = LGetPath(); if (Path.Length()) { LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path); Process->SetEnvironment("PATH", Tmp); } #endif LString::Array Ctx; Ctx.SetFixedLength(false); Ctx.Add(LocalPath()); Ctx.Add(Exe); Ctx.Add(Args); LAutoPtr c(new Cmd(Ctx, Logging, d->Log)); if (!c) return false; c->PostOp = Parser; c->Params.Reset(Params); c->Rd.Reset(new ReaderThread(GetType(), Process, c)); Cmds.Add(c.Release()); } else { auto c = d->GetConnection(Uri.ToString()); if (!c) return false; if (!c->Command(this, Exe, Args, Parser, Params)) return false; } Update(); return true; } int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data) { VcCommit *A = dynamic_cast(a); VcCommit *B = dynamic_cast(b); if ((A != NULL) ^ (B != NULL)) { // This handles keeping the "working folder" list item at the top return (A != NULL) - (B != NULL); } // Sort the by date from most recent to least return -A->GetTs().Compare(&B->GetTs()); } void VcFolder::AddGitName(LString Hash, LString Name) { LString Existing = GitNames.Find(Hash); if (Existing) GitNames.Add(Hash, Existing + "," + Name); else GitNames.Add(Hash, Name); } LString VcFolder::GetGitNames(LString Hash) { LString Short = Hash(0, 11); return GitNames.Find(Short); } bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { LString::Array a = s.SplitDelimit("\r\n"); for (auto &l: a) { LString::Array c = l.SplitDelimit(" \t"); if (c[0].Equals("*")) { CurrentBranch = c[1]; AddGitName(c[2], CurrentBranch); Branches.Add(CurrentBranch, new VcBranch(CurrentBranch, c[2])); } else { AddGitName(c[1], c[0]); Branches.Add(c[0], new VcBranch(c[0], c[1])); } } break; } case VcHg: { auto a = s.SplitDelimit("\r\n"); Branches.DeleteObjects(); for (auto b: a) { if (!CurrentBranch) CurrentBranch = b; Branches.Add(b, new VcBranch(b)); } if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } default: { break; } } IsBranches = Result ? StatusError : StatusNone; OnBranchesChange(); return false; } void VcFolder::OnBranchesChange() { auto *w = d->Tree->GetWindow(); if (!w || !LTreeItem::Select()) return; if (Branches.Length()) { // Set the colours up LString Default; for (auto b: Branches) { if (!stricmp(b.key, "default") || !stricmp(b.key, "trunk")) Default = b.key; /* else printf("Other=%s\n", b.key); */ } int Idx = 1; for (auto b: Branches) { if (!b.value->Colour.IsValid()) { if (Default && !stricmp(b.key, Default)) b.value->Colour = GetPaletteColour(0); else b.value->Colour = GetPaletteColour(Idx++); } } } DropDownBtn *dd; if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd)) { LString::Array a; for (auto b: Branches) a.Add(b.key); dd->SetList(IDC_BRANCH, a); } LViewI *b; if (Branches.Length() > 0 && w->GetViewById(IDC_BRANCH, b)) { if (CurrentBranch) { b->Name(CurrentBranch); } else { auto it = Branches.begin(); if (it != Branches.end()) b->Name((*it).key); } } } void VcFolder::DefaultFields() { if (Fields.Length() == 0) { switch (GetType()) { case VcHg: { Fields.Add(LGraph); Fields.Add(LIndex); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } case VcGit: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LBranch); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } default: { Fields.Add(LGraph); Fields.Add(LRevision); Fields.Add(LAuthor); Fields.Add(LTimeStamp); Fields.Add(LMessageTxt); break; } } } } void VcFolder::UpdateColumns() { d->Commits->EmptyColumns(); for (auto c: Fields) { switch (c) { case LGraph: d->Commits->AddColumn("---", 60); break; case LIndex: d->Commits->AddColumn("Index", 60); break; case LBranch: d->Commits->AddColumn("Branch", 60); break; case LRevision: d->Commits->AddColumn("Revision", 60); break; case LAuthor: d->Commits->AddColumn("Author", 240); break; case LTimeStamp: d->Commits->AddColumn("Date", 130); break; case LMessageTxt: d->Commits->AddColumn("Message", 400); break; default: LAssert(0); break; } } } void VcFolder::FilterCurrentFiles() { LArray All; d->Files->GetAll(All); // Update the display property for (auto i: All) { auto fn = i->GetText(COL_FILENAME); bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get()); i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); // LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis); } d->Files->Sort(0); d->Files->UpdateAllItems(); d->Files->ResizeColumnsToContent(); } void VcFolder::Select(bool b) { #if PROFILE_FN LProfile Prof("Select"); #endif if (!b) { auto *w = d->Tree->GetWindow(); w->SetCtrlName(IDC_BRANCH, NULL); } PROF("Parent.Select"); LTreeItem::Select(b); if (b) { if (Uri.IsFile() && !LDirExists(LocalPath())) return; PROF("DefaultFields"); DefaultFields(); PROF("Type Change"); if (GetType() != d->PrevType) { d->PrevType = GetType(); UpdateColumns(); } PROF("UpdateCommitList"); if ((Log.Length() == 0 || CommitListDirty) && !IsLogging) { switch (GetType()) { case VcGit: { LVariant Limit; d->Opts.GetValue("git-limit", Limit); LString cmd = "rev-list --all --header --timestamp --author-date-order", s; if (Limit.CastInt32() > 0) { s.Printf(" -n %i", Limit.CastInt32()); cmd += s; } IsLogging = StartCmd(cmd, &VcFolder::ParseRevList); break; } case VcSvn: { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); if (CommitListDirty) { IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log")); break; } LString s; if (Limit.CastInt32() > 0) s.Printf("log --limit %i", Limit.CastInt32()); else s = "log"; IsLogging = StartCmd(s, &VcFolder::ParseLog); break; } case VcHg: { IsLogging = StartCmd("log", &VcFolder::ParseLog); StartCmd("resolve -l", &VcFolder::ParseResolveList); break; } case VcPending: { break; } default: { IsLogging = StartCmd("log", &VcFolder::ParseLog); break; } } CommitListDirty = false; } PROF("GetBranches"); if (GetBranches()) OnBranchesChange(); if (d->CurFolder != this) { PROF("RemoveAll"); d->CurFolder = this; d->Commits->RemoveAll(); } PROF("Uncommit"); if (!Uncommit) Uncommit.Reset(new UncommitedItem(d)); d->Commits->Insert(Uncommit, 0); PROF("Log Loop"); int64 CurRev = Atoi(CurrentCommit.Get()); List Ls; for (auto l: Log) { if (CurrentCommit && l->GetRev()) { switch (GetType()) { case VcSvn: { int64 LogRev = Atoi(l->GetRev()); if (CurRev >= 0 && CurRev >= LogRev) { CurRev = -1; l->SetCurrent(true); } else { l->SetCurrent(false); } break; } default: l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev())); break; } } bool Add = !d->CommitFilter; if (d->CommitFilter) { const char *s = l->GetRev(); if (s && strstr(s, d->CommitFilter) != NULL) Add = true; s = l->GetAuthor(); if (s && stristr(s, d->CommitFilter) != NULL) Add = true; s = l->GetMsg(); if (s && stristr(s, d->CommitFilter) != NULL) Add = true; } LList *CurOwner = l->GetList(); if (Add ^ (CurOwner != NULL)) { if (Add) Ls.Insert(l); else d->Commits->Remove(l); } } PROF("Ls Ins"); d->Commits->Insert(Ls); if (d->Resort >= 0) { PROF("Resort"); d->Commits->Sort(LstCmp, d->Resort); d->Resort = -1; } PROF("ColSizing"); if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS) { int i = 0; if (GetType() == VcHg && d->Commits->GetColumns() >= 7) { d->Commits->ColumnAt(i++)->Width(60); // LGraph d->Commits->ColumnAt(i++)->Width(40); // LIndex d->Commits->ColumnAt(i++)->Width(100); // LRevision d->Commits->ColumnAt(i++)->Width(60); // LBranch d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } else if (d->Commits->GetColumns() >= 5) { d->Commits->ColumnAt(i++)->Width(40); // LGraph d->Commits->ColumnAt(i++)->Width(270); // LRevision d->Commits->ColumnAt(i++)->Width(240); // LAuthor d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp d->Commits->ColumnAt(i++)->Width(400); // LMessage } } else d->Commits->ResizeColumnsToContent(); PROF("UpdateAll"); d->Commits->UpdateAllItems(); PROF("GetCur"); GetCurrentRevision(); } } int CommitRevCmp(VcCommit **a, VcCommit **b) { int64 arev = Atoi((*a)->GetRev()); int64 brev = Atoi((*b)->GetRev()); int64 diff = (int64)brev - arev; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitIndexCmp(VcCommit **a, VcCommit **b) { auto ai = (*a)->GetIndex(); auto bi = (*b)->GetIndex(); auto diff = (int64)bi - ai; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } int CommitDateCmp(VcCommit **a, VcCommit **b) { uint64 ats, bts; (*a)->GetTs().Get(ats); (*b)->GetTs().Get(bts); int64 diff = (int64)bts - ats; if (diff < 0) return -1; return (diff > 0) ? 1 : 0; } void VcFolder::GetCurrentRevision(ParseParams *Params) { if (CurrentCommit || IsIdent != StatusNone) return; switch (GetType()) { case VcGit: if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcSvn: if (StartCmd("info", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcHg: if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params)) IsIdent = StatusActive; break; case VcCvs: break; default: break; } } bool VcFolder::GetBranches(ParseParams *Params) { if (Branches.Length() > 0 || IsBranches != StatusNone) return true; switch (GetType()) { case VcGit: if (StartCmd("-P branch -a -v", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; break; case VcSvn: Branches.Add("trunk", new VcBranch("trunk")); OnBranchesChange(); break; case VcHg: if (StartCmd("branch", &VcFolder::ParseBranches, Params)) IsBranches = StatusActive; break; case VcCvs: break; default: break; } return false; } bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params) { Log.DeleteObjects(); int Errors = 0; switch (GetType()) { case VcGit: { LString::Array Commits; Commits.SetFixedLength(false); // Split on the NULL chars... char *c = s.Get(); char *e = c + s.Length(); while (c < e) { char *nul = c; while (nul < e && *nul) nul++; if (nul <= c) break; Commits.New().Set(c, nul-c); if (nul >= e) break; c = nul + 1; } for (auto Commit: Commits) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->GitParse(Commit, true)) { Log.Add(Rev.Release()); } else { LAssert(!"Parse failed."); LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get()); Errors++; } } LinkParents(); break; } default: LAssert(!"Impl me."); break; } IsLogging = false; return Errors == 0; } LString VcFolder::GetFilePart(const char *uri) { LUri u(uri); LString File = u.IsFile() ? u.DecodeStr(u.LocalPath()) : u.sPath(Uri.sPath.Length(), -1).LStrip("/"); return File; } void VcFolder::LogFile(const char *uri) { LString Args; switch (GetType()) { case VcSvn: case VcHg: + case VcGit: { LString File = GetFilePart(uri); ParseParams *Params = new ParseParams(uri); Args.Printf("log \"%s\"", File.Get()); IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal); break; } - /* - case VcGit: - break; - case VcCvs: - break; - */ default: LAssert(!"Impl me."); break; } } VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree) { VcLeaf *r = NULL; if (OpenTree) DoExpand(); for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params) { LHashTbl, VcCommit*> Map; for (auto pc: Log) Map.Add(pc->GetRev(), pc); int Skipped = 0, Errors = 0; VcLeaf *File = Params ? FindLeaf(Params->Str, true) : NULL; LArray *Out = File ? &File->Log : &Log; if (File) { for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent())) Leaf->OnExpand(true); File->Select(true); File->ScrollTo(); } switch (GetType()) { case VcGit: { LString::Array c; c.SetFixedLength(false); char *prev = s.Get(); for (char *i = s.Get(); *i; ) { if (!strnicmp(i, "commit ", 7)) { if (i > prev) { c.New().Set(prev, i - prev); // LgiTrace("commit=%i\n", (int)(i - prev)); } prev = i; } while (*i) { if (*i++ == '\n') break; } } for (unsigned i=0; i Rev(new VcCommit(d, this)); if (Rev->GitParse(c[i], false)) { if (!Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else { LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get()); Errors++; } } Out->Sort(CommitDateCmp); break; } case VcSvn: { LString::Array c = s.Split("------------------------------------------------------------------------"); for (unsigned i=0; i Rev(new VcCommit(d, this)); LString Raw = c[i].Strip(); if (Rev->SvnParse(Raw)) { if (File || !Map.Find(Rev->GetRev())) Out->Add(Rev.Release()); else Skipped++; } else if (Raw) { OnCmdError(Raw, "ParseLog Failed"); Errors++; } } Out->Sort(CommitRevCmp); break; } case VcHg: { LString::Array c = s.Split("\n\n"); LHashTbl, VcCommit*> Idx; for (auto &Commit: c) { LAutoPtr Rev(new VcCommit(d, this)); if (Rev->HgParse(Commit)) { auto Existing = File ? NULL : Map.Find(Rev->GetRev()); if (!Existing) Out->Add(Existing = Rev.Release()); if (Existing->GetIndex() >= 0) Idx.Add(Existing->GetIndex(), Existing); } } if (!File) { // Patch all the trivial parents... for (auto c: Log) { if (c->GetParents()->Length() > 0) continue; auto CIdx = c->GetIndex(); if (CIdx <= 0) continue; auto Par = Idx.Find(CIdx - 1); if (Par) c->GetParents()->Add(Par->GetRev()); } } Out->Sort(CommitIndexCmp); if (!File) LinkParents(); d->Resort = 1; break; } case VcCvs: { if (Result) { OnCmdError(s, "Cvs command failed."); break; } LHashTbl, VcCommit*> Map; LString::Array c = s.Split("============================================================================="); for (auto &Commit: c) { if (Commit.Strip().Length()) { LString Head, File; LString::Array Versions = Commit.Split("----------------------------"); LString::Array Lines = Versions[0].SplitDelimit("\r\n"); for (auto &Line: Lines) { LString::Array p = Line.Split(":", 1); if (p.Length() == 2) { // LgiTrace("Line: %s\n", Line->Get()); LString Var = p[0].Strip().Lower(); LString Val = p[1].Strip(); if (Var.Equals("branch")) { if (Val.Length()) Branches.Add(Val, new VcBranch(Val)); } else if (Var.Equals("head")) { Head = Val; } else if (Var.Equals("rcs file")) { LString::Array f = Val.SplitDelimit(","); File = f.First(); } } } // LgiTrace("%s\n", Commit->Get()); for (unsigned i=1; i= 3) { LString Ver = Lines[0].Split(" ").Last(); LString::Array a = Lines[1].SplitDelimit(";"); LString Date = a[0].Split(":", 1).Last().Strip(); LString Author = a[1].Split(":", 1).Last().Strip(); LString Id = a[2].Split(":", 1).Last().Strip(); LString Msg = Lines[2]; LDateTime Dt; if (Dt.Parse(Date)) { uint64 Ts; if (Dt.Get(Ts)) { VcCommit *Cc = Map.Find(Ts); if (!Cc) { Map.Add(Ts, Cc = new VcCommit(d, this)); Out->Add(Cc); Cc->CvsParse(Dt, Author, Msg); } Cc->Files.Add(File.Get()); } else LAssert(!"NO ts for date."); } else LAssert(!"Date parsing failed."); } } } } break; } default: LAssert(!"Impl me."); break; } if (File) File->ShowLog(); // LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors); IsLogging = false; return !Result; } void VcFolder::LinkParents() { #if PROFILE_FN LProfile Prof("LinkParents"); #endif LHashTbl,VcCommit*> Map; // Index all the commits int i = 0; for (auto c:Log) { c->Idx = i++; c->NodeIdx = -1; Map.Add(c->GetRev(), c); } // Create all the edges... PROF("Create edges."); for (auto c:Log) { auto *Par = c->GetParents(); for (auto &pRev : *Par) { auto *p = Map.Find(pRev); if (p) new VcEdge(p, c); #if 0 else return; #endif } } // Map the edges to positions PROF("Map edges."); typedef LArray EdgeArr; LArray Active; for (auto c:Log) { for (unsigned i=0; c->NodeIdx<0 && iParent == c) { c->NodeIdx = i; break; } } } // Add starting edges to active set for (auto e:c->Edges) { if (e->Child == c) { if (c->NodeIdx < 0) c->NodeIdx = (int)Active.Length(); e->Idx = c->NodeIdx; c->Pos.Add(e, e->Idx); Active[e->Idx].Add(e); } } // Now for all active edges... assign positions for (unsigned i=0; iLength(); n++) { LAssert(Active.PtrCheck(Edges)); VcEdge *e = (*Edges)[n]; if (c == e->Child || c == e->Parent) { LAssert(c->NodeIdx >= 0); c->Pos.Add(e, c->NodeIdx); } else { // May need to untangle edges with different parents here bool Diff = false; for (auto edge: *Edges) { if (edge != e && edge->Child != c && edge->Parent != e->Parent) { Diff = true; break; } } if (Diff) { int NewIndex = -1; // Look through existing indexes for a parent match for (unsigned ii=0; iiParent? bool Match = true; for (auto ee: Active[ii]) { if (ee->Parent != e->Parent) { Match = false; break; } } if (Match) NewIndex = ii; } if (NewIndex < 0) // Create new index for this parent NewIndex = (int)Active.Length(); Edges->Delete(e); auto &NewEdges = Active[NewIndex]; NewEdges.Add(e); Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to e->Idx = NewIndex; c->Pos.Add(e, NewIndex); n--; } else { LAssert(e->Idx == i); c->Pos.Add(e, i); } } } } // Process terminating edges for (auto e: c->Edges) { if (e->Parent == c) { if (e->Idx < 0) { // This happens with out of order commits..? continue; } int i = e->Idx; if (c->NodeIdx < 0) c->NodeIdx = i; if (Active[i].HasItem(e)) Active[i].Delete(e); else LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL); } } // Collapse any empty active columns for (unsigned i=0; iIdx > 0); edge->Idx--; c->Pos.Add(edge, edge->Idx); } } i--; } } } // Find all the "heads", i.e. a commit without any children PROF("Find heads."); LCombo *Heads; if (d->Wnd()->GetViewById(IDC_HEADS, Heads)) { Heads->Empty(); for (auto c:Log) { bool Has = false; for (auto e:c->Edges) { if (e->Parent == c) { Has = true; break; } } if (!Has) Heads->Insert(c->GetRev()); } Heads->SendNotify(LNotifyTableLayoutRefresh); } } VcFile *AppPriv::FindFile(const char *Path) { if (!Path) return NULL; LArray files; if (Files->GetAll(files)) { LString p = Path; p = p.Replace(DIR_STR, "/"); for (auto f : files) { auto Fn = f->GetFileName(); if (p.Equals(Fn)) return f; } } return NULL; } VcFile *VcFolder::FindFile(const char *Path) { return d->FindFile(Path); } void VcFolder::OnCmdError(LString Output, const char *Msg) { if (!CmdErrors) { if (Output.Length()) d->Log->Write(Output, Output.Length()); auto vc_name = GetVcName(); if (vc_name) { LString::Array a = GetProgramsInPath(GetVcName()); d->Log->Print("'%s' executables in the path:\n", GetVcName()); for (auto Bin : a) d->Log->Print(" %s\n", Bin.Get()); } else if (Msg) { d->Log->Print("%s\n", Msg); } } CmdErrors++; d->Tabs->Value(1); GetCss(true)->Color(LColour::Red); } void VcFolder::ClearError() { GetCss(true)->Color(LCss::ColorInherit); } bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: { auto p = s.Strip().SplitDelimit(); CurrentCommit = p[0].Strip(" \t\r\n+"); if (p.Length() > 1) CurrentCommitIdx = p[1].Int(); else CurrentCommitIdx = -1; if (Params && Params->Str.Equals("CountToTip")) CountToTip(); break; } case VcSvn: { if (s.Find("client is too old") >= 0) { OnCmdError(s, "Client too old"); break; } LString::Array c = s.Split("\n"); for (unsigned i=0; iIsWorking = true; ParseStatus(Result, s, Params); break; } case VcCvs: { bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); if (Untracked) { auto Lines = s.SplitDelimit("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(" \t", 1); if (p.Length() > 1) { auto f = new VcFile(d, this, NULL, true); f->SetText(p[0], COL_STATE); f->SetText(p[1], COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } } } // else fall thru } default: { ParseDiffs(s, NULL, true); break; } } IsWorkingFld = false; FilterCurrentFiles(); d->Files->ResizeColumnsToContent(); if (GetType() == VcSvn) { Unpushed = d->Files->Length() > 0 ? 1 : 0; Update(); } return false; } void VcFolder::DiffRange(const char *FromRev, const char *ToRev) { if (!FromRev || !ToRev) return; switch (GetType()) { case VcSvn: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("diff -r%s:%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcGit: { ParseParams *p = new ParseParams; p->IsWorking = false; p->Str = LString(FromRev) + ":" + ToRev; LString a; a.Printf("-P diff %s..%s", FromRev, ToRev); StartCmd(a, &VcFolder::ParseDiff, p); break; } case VcCvs: case VcHg: default: LAssert(!"Impl me."); break; } } bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params) { if (Params) ParseDiffs(s, Params->Str, Params->IsWorking); else ParseDiffs(s, NULL, true); return false; } void VcFolder::Diff(VcFile *file) { auto Fn = file->GetFileName(); if (!Fn || !Stricmp(Fn, ".") || !Stricmp(Fn, "..")) return; const char *Prefix = ""; switch (GetType()) { case VcGit: Prefix = "-P "; // fall through case VcHg: { LString a; auto rev = file->GetRevision(); if (rev) a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn); else a.Printf("%sdiff \"%s\"", Prefix, Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcSvn: { LString a; if (file->GetRevision()) a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn); else a.Printf("diff \"%s\"", Fn); StartCmd(a, &VcFolder::ParseDiff); break; } case VcCvs: break; default: LAssert(!"Impl me."); break; } } bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking) { LAssert(IsWorking || Rev.Get() != NULL); switch (GetType()) { case VcGit: { LString::Array a = s.Split("\n"); LString Diff; VcFile *f = NULL; for (unsigned i=0; iSetDiff(Diff); Diff.Empty(); auto Bits = a[i].SplitDelimit(); LString Fn, State = "M"; if (Bits[1].Equals("--cc")) { Fn = Bits.Last(); State = "C"; } else Fn = Bits.Last()(2,-1); // LgiTrace("%s\n", a[i].Get()); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(State, COL_STATE); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } else if (!_strnicmp(Ln, "new file", 8)) { if (f) f->SetText("A", COL_STATE); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { if (Diff) Diff += "\n"; Diff += a[i]; } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcHg: { LString Sep("\n"); LString::Array a = s.Split(Sep); LString::Array Diffs; VcFile *f = NULL; List Files; LProgressDlg Prog(GetTree(), 1000); Prog.SetDescription("Reading diff lines..."); Prog.SetRange(a.Length()); // Prog.SetYieldTime(300); for (unsigned i=0; iSetDiff(Sep.Join(Diffs)); Diffs.Empty(); auto MainParts = a[i].Split(" -r "); auto FileParts = MainParts.Last().Split(" ",1); LString Fn = FileParts.Last(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); // f->SetText(Status, COL_STATE); Files.Insert(f); } else if (!_strnicmp(Ln, "index", 5) || !_strnicmp(Ln, "commit", 6) || !_strnicmp(Ln, "Author:", 7) || !_strnicmp(Ln, "Date:", 5) || !_strnicmp(Ln, "+++", 3) || !_strnicmp(Ln, "---", 3)) { // Ignore } else { Diffs.Add(a[i]); } Prog.Value(i); if (Prog.IsCancelled()) break; } if (f && Diffs.Length()) { f->SetDiff(Sep.Join(Diffs)); Diffs.Empty(); } d->Files->Insert(Files); break; } case VcSvn: { LString::Array a = s.Replace("\r").Split("\n"); LString Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(Diff); f->Select(false); } Diff.Empty(); InDiff = false; InPreamble = false; LString Fn = a[i].Split(":", 1).Last().Strip(); f = FindFile(Fn); if (!f) f = new VcFile(d, this, Rev, IsWorking); f->SetText(Fn.Replace("\\","/"), COL_FILENAME); 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) { if (!strncmp(Ln, "--- ", 4) || !strncmp(Ln, "+++ ", 4)) { } else { if (Diff) Diff += "\n"; Diff += a[i]; } } } if (f && Diff) { f->SetDiff(Diff); Diff.Empty(); } break; } case VcCvs: { break; } default: { LAssert(!"Impl me."); break; } } FilterCurrentFiles(); return true; } bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params) { d->ClearFiles(); ParseDiffs(s, Params->Str, false); IsFilesCmd = false; FilterCurrentFiles(); return false; } void VcFolder::OnSshCmd(SshParams *p) { if (!p || !p->f) { LAssert(!"Param error."); return; } LString s = p->Output; int Result = p->ExitCode; if (Result == ErrSubProcessFailed) { CmdErrors++; } else if (p->Parser) { bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params); if (Reselect) { if (LTreeItem::Select()) Select(true); } } } void VcFolder::OnPulse() { bool Reselect = false, CmdsChanged = false; static bool Processing = false; if (!Processing) { Processing = true; // Lock out processing, if it puts up a dialog or something... // bad things happen if we try and re-process something. // printf("Cmds.Len=%i\n", (int)Cmds.Length()); for (unsigned i=0; iRd->GetState()=%i\n", c->Rd->GetState()); if (c->Rd->GetState() == LThread::THREAD_INIT) { if (CmdActiveThreads < CmdMaxThreads) { c->Rd->Run(); CmdActiveThreads++; // LgiTrace("CmdActiveThreads++ = %i\n", CmdActiveThreads); } // else printf("Too many active threads."); } else if (c->Rd->IsExited()) { CmdActiveThreads--; // LgiTrace("CmdActiveThreads-- = %i\n", CmdActiveThreads); LString s = c->GetBuf(); int Result = c->Rd->ExitCode(); if (Result == ErrSubProcessFailed) { if (!CmdErrors) d->Log->Print("Error: Can't run '%s'\n", GetVcName()); CmdErrors++; } else if (c->PostOp) { if (s.Length() == 18 && s.Equals("GSUBPROCESS_ERROR\n")) { OnCmdError(s, "Sub process failed."); } else { Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params); } } Cmds.DeleteAt(i--, true); delete c; CmdsChanged = true; } // else printf("Not exited.\n"); } Processing = false; } if (Reselect) { if (LTreeItem::Select()) Select(true); } if (CmdsChanged) { Update(); } if (CmdErrors) { d->Tabs->Value(1); CmdErrors = false; } } void VcFolder::OnRemove() { LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL); if (t) { Uncommit.Reset(); if (LTreeItem::Select()) { d->Files->Empty(); d->Commits->RemoveAll(); } bool Found = false; auto u = Uri.ToString(); for (auto c: t->Children) { if (c->IsTag(OPT_Folder) && c->GetContent()) { auto Content = c->GetContent(); if (!_stricmp(Content, u)) { c->RemoveTag(); delete c; Found = true; break; } } } LAssert(Found); d->Opts.Unlock(); } } void VcFolder::Empty() { Type = VcNone; IsCommit = false; IsLogging = false; IsUpdate = false; IsFilesCmd = false; IsWorkingFld = false; CommitListDirty = false; IsUpdatingCounts = false; IsBranches = StatusNone; IsIdent = StatusNone; Unpushed = Unpulled = -1; CmdErrors = 0; CurrentCommitIdx = -1; CurrentCommit.Empty(); RepoUrl.Empty(); VcCmd.Empty(); Uncommit.Reset(); Log.DeleteObjects(); d->Commits->Empty(); d->Files->Empty(); if (!Uri.IsFile()) GetCss(true)->Color(LColour::Blue); } void VcFolder::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile()); s.AppendItem( #ifdef WINDOWS "Command Prompt At", #else "Terminal At", #endif IDM_TERMINAL, Uri.IsFile()); s.AppendItem("Clean", IDM_CLEAN); s.AppendSeparator(); s.AppendItem("Pull", IDM_PULL); s.AppendItem("Status", IDM_STATUS); s.AppendItem("Push", IDM_PUSH); s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit); s.AppendSeparator(); s.AppendItem("Remove", IDM_REMOVE); if (!Uri.IsFile()) { s.AppendSeparator(); s.AppendItem("Edit Location", IDM_EDIT); } int Cmd = s.Float(GetTree(), m); switch (Cmd) { case IDM_BROWSE_FOLDER: { LBrowseToFile(LocalPath()); break; } case IDM_TERMINAL: { TerminalAt(LocalPath()); break; } case IDM_CLEAN: { Clean(); break; } case IDM_PULL: { Pull(); break; } case IDM_STATUS: { FolderStatus(); break; } case IDM_PUSH: { Push(); break; } case IDM_UPDATE_SUBS: { UpdateSubs(); break; } case IDM_REMOVE: { OnRemove(); delete this; break; } case IDM_EDIT: { auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location"); Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId) { if (ctrlId) { Uri.Set(Dlg->GetStr()); Empty(); Select(true); } delete dlg; }); break; } default: break; } } } void VcFolder::OnUpdate(const char *Rev) { if (!Rev) return; if (!IsUpdate) { LString Args; NewRev = Rev; switch (GetType()) { case VcGit: Args.Printf("checkout %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcSvn: Args.Printf("up -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; case VcHg: Args.Printf("update -r %s", Rev); IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal); break; default: { LAssert(!"Impl me."); break; } } } } /////////////////////////////////////////////////////////////////////////////////////// int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData) { VcLeaf *A = dynamic_cast(a); VcLeaf *B = dynamic_cast(b); if (!A || !B) return 0; return A->Compare(B); } struct SshFindEntry { LString Flags, Name, User, Group; uint64_t Size; LDateTime Modified, Access; SshFindEntry &operator =(const LString &s) { auto p = s.SplitDelimit("/"); if (p.Length() == 7) { Flags = p[0]; Group = p[1]; User = p[2]; Access.Set((uint64_t) p[3].Int()); Modified.Set((uint64_t) p[4].Int()); Size = p[5].Int(); Name = p[6]; } return *this; } bool IsDir() { return Flags(0) == 'd'; } bool IsHidden() { return Name(0) == '.'; } const char *GetName() { return Name; } static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); } }; bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params) { if (!Params || !s) return false; auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this); LUri u(Params->Str); auto Lines = s.SplitDelimit("\r\n"); LArray Entries; for (size_t i=1; iStr, Dir.GetName(), true); } } else if (!Dir.IsHidden()) { char *Ext = LGetExtension(Dir.GetName()); if (!Ext) continue; if (!stricmp(Ext, "c") || !stricmp(Ext, "cpp") || !stricmp(Ext, "h")) { LUri Path = u; Path += Dir.GetName(); new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false); } } } return false; } void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri) { LUri u(ReadUri); if (u.IsFile()) { // Read child items LDirectory Dir; for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next()) { auto name = Dir.GetName(); if (Dir.IsHidden()) continue; LUri Path = u; Path += name; new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir()); } } else { auto c = d->GetConnection(ReadUri); if (!c) return; LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/"); LString Args; Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : "."); auto *Params = new ParseParams(ReadUri); Params->Leaf = dynamic_cast(Parent); c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params); return; } Parent->Sort(FolderCompare); } void VcFolder::OnVcsType(LString errorMsg) { if (!d) { LAssert(!"No priv instance"); return; } auto c = d->GetConnection(Uri.ToString(), false); if (c) { auto NewType = c->Types.Find(Uri.sPath); if (NewType && NewType != Type) { if (NewType == VcError) { OnCmdError(NULL, errorMsg); } else { Type = NewType; ClearError(); Update(); if (LTreeItem::Select()) Select(true); } } } } void VcFolder::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); ReadDir(this, Uri.ToString()); } } void VcFolder::OnExpand(bool b) { if (b) DoExpand(); } void VcFolder::ListCommit(VcCommit *c) { if (!IsFilesCmd) { LString Args; switch (GetType()) { case VcGit: Args.Printf("-P show %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcSvn: Args.Printf("log --verbose --diff -r %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; case VcCvs: { d->ClearFiles(); for (unsigned i=0; iFiles.Length(); i++) { VcFile *f = new VcFile(d, this, c->GetRev(), false); if (f) { f->SetText(c->Files[i], COL_FILENAME); d->Files->Insert(f); } } FilterCurrentFiles(); break; } case VcHg: { Args.Printf("diff --change %s", c->GetRev()); IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev())); break; } default: LAssert(!"Impl me."); break; } if (IsFilesCmd) d->ClearFiles(); } } LString ConvertUPlus(LString s) { LArray c; LUtf8Ptr p(s); int32 ch; while ((ch = p)) { if (ch == '{') { auto n = p.GetPtr(); if (n[1] == 'U' && n[2] == '+') { // Convert unicode code point p += 3; ch = (int32)htoi(p.GetPtr()); c.Add(ch); while ((ch = p) != '}') p++; } else c.Add(ch); } else c.Add(ch); p++; } c.Add(0); #ifdef LINUX return LString((char16*)c.AddressOf()); #else return LString(c.AddressOf()); #endif } bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params) { bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0; bool IsWorking = Params ? Params->IsWorking : false; List Ins; switch (GetType()) { case VcCvs: { LHashTbl,VcFile*> Map; for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) Map.Add(f->GetText(COL_FILENAME), f); } #if 0 LFile Tmp("C:\\tmp\\output.txt", O_WRITE); Tmp.Write(s); Tmp.Close(); #endif LString::Array a = s.Split("==================================================================="); for (auto i : a) { LString::Array Lines = i.SplitDelimit("\r\n"); if (Lines.Length() == 0) continue; LString f = Lines[0].Strip(); if (f.Find("File:") == 0) { LString::Array Parts = f.SplitDelimit("\t"); LString File = Parts[0].Split(": ").Last().Strip(); LString Status = Parts[1].Split(": ").Last(); LString WorkingRev; for (auto l : Lines) { LString::Array p = l.Strip().Split(":", 1); if (p.Length() > 1 && p[0].Strip().Equals("Working revision")) { WorkingRev = p[1].Strip(); } } VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, WorkingRev, IsWorking))) Ins.Insert(f); } if (f) { f->SetText(Status, COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } else if (f(0) == '?' && ShowUntracked) { LString File = f(2, -1); VcFile *f = Map.Find(File); if (!f) { if ((f = new VcFile(d, this, NULL, IsWorking))) Ins.Insert(f); } if (f) { f->SetText("?", COL_STATE); f->SetText(File, COL_FILENAME); f->Update(); } } } for (auto i: *d->Files) { VcFile *f = dynamic_cast(i); if (f) { if (f->GetStatus() == VcFile::SUnknown) f->SetStatus(VcFile::SUntracked); } } break; } case VcGit: { LString::Array Lines = s.SplitDelimit("\r\n"); int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1; for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("usage: git") >= 0) { // It's probably complaining about the --porcelain=2 parameter OnCmdError(s, "Args error"); } else if (Type != '?') { VcFile *f = NULL; if (Fmt == 2) { LString::Array p = Ln.SplitDelimit(" ", 8); if (p.Length() < 7) d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get()); else { f = new VcFile(d, this, p[6], IsWorking); f->SetText(p[1].Strip("."), COL_STATE); f->SetText(p.Last(), COL_FILENAME); } } else if (Fmt == 1) { LString::Array p = Ln.SplitDelimit(" "); f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(p.Last(), COL_FILENAME); } if (f) Ins.Insert(f); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } case VcHg: case VcSvn: { if (s.Find("failed to import") >= 0) { OnCmdError(s, "Tool error."); return false; } LString::Array Lines = s.SplitDelimit("\r\n"); for (auto Ln : Lines) { char Type = Ln(0); if (Ln.Lower().Find("error:") >= 0) { } else if (Ln.Find("client is too old") >= 0) { OnCmdError(s, "Client too old."); return false; } else if (Strchr(" \t", Type) || Ln.Find("Summary of conflicts") >= 0) { // Ignore } else if (Type != '?') { LString::Array p = Ln.SplitDelimit(" ", 1); if (p.Length() == 2) { LString File; if (GetType() == VcSvn) File = ConvertUPlus(p.Last()); else File = p.Last(); if (GetType() == VcSvn && File.Find("+ ") == 0) { File = File(5, -1); } VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText(p[0], COL_STATE); f->SetText(File.Replace("\\","/"), COL_FILENAME); f->GetStatus(); Ins.Insert(f); } else LAssert(!"What happen?"); } else if (ShowUntracked) { VcFile *f = new VcFile(d, this, NULL, IsWorking); f->SetText("?", COL_STATE); f->SetText(Ln(2,-1), COL_FILENAME); Ins.Insert(f); } } break; } default: { LAssert(!"Impl me."); break; } } if ((Unpushed = Ins.Length() > 0)) { if (CmdErrors == 0) GetCss(true)->Color(LColour(255, 128, 0)); } else if (Unpulled == 0) { GetCss(true)->Color(LCss::ColorInherit); } Update(); if (LTreeItem::Select()) { d->Files->Insert(Ins); FilterCurrentFiles(); } else { Ins.DeleteObjects(); } if (Params && Params->Leaf) Params->Leaf->AfterBrowse(); return false; // Don't refresh list } // Clone/checkout any sub-repositries. bool VcFolder::UpdateSubs() { LString Arg; switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: Arg = "submodule update --init"; break; } return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal); } bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params) { switch (GetType()) { default: case VcSvn: case VcHg: case VcCvs: return false; case VcGit: break; } return false; } void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify) { LUri Uri(uri); if (Uri.IsFile() && Uri.sPath) { LFile::Path p(Uri.sPath(1,-1)); if (!p.IsFolder()) { LAssert(!"Needs to be a folder."); return; } } if (LTreeItem::Select()) d->ClearFiles(); LString Arg; switch (GetType()) { case VcSvn: case VcHg: Arg = "status"; break; case VcCvs: Arg = "status -l"; break; case VcGit: if (!ToolVersion[VcGit]) LAssert(!"Where is the version?"); // What version did =2 become available? It's definitely not in v2.5.4 // Not in v2.7.4 either... if (ToolVersion[VcGit] >= Ver2Int("2.8.0")) Arg = "-P status --porcelain=2"; else Arg = "-P status --porcelain"; break; default: return; } ParseParams *p = new ParseParams; if (uri && Notify) { p->AltInitPath = uri; p->Leaf = Notify; } else { p->IsWorking = true; } StartCmd(Arg, &VcFolder::ParseStatus, p); switch (GetType()) { case VcHg: CountToTip(); break; default: break; } } void VcFolder::CountToTip() { // if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk")) { // LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx); if (!CurrentBranch) GetBranches(new ParseParams("CountToTip")); else if (CurrentCommitIdx < 0) GetCurrentRevision(new ParseParams("CountToTip")); else { LString Arg; Arg.Printf("id -n -r %s", CurrentBranch.Get()); StartCmd(Arg, &VcFolder::ParseCountToTip); } } } bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: if (CurrentCommitIdx >= 0) { auto p = s.Strip(); auto idx = p.Int(); if (idx >= CurrentCommitIdx) { Unpulled = (int) (idx - CurrentCommitIdx); Update(); } } break; default: break; } return false; } void VcFolder::ListWorkingFolder() { if (IsWorkingFld) return; d->ClearFiles(); bool Untracked = d->IsMenuChecked(IDM_UNTRACKED); LString Arg; switch (GetType()) { case VcCvs: if (Untracked) Arg = "-qn update"; else Arg = "-q diff --brief"; break; case VcSvn: Arg = "status"; break; case VcGit: Arg = "-P diff --diff-filter=ACDMRTU"; break; case VcHg: Arg = "status -mard"; break; default: return; } IsWorkingFld = StartCmd(Arg, &VcFolder::ParseWorking); } void VcFolder::GitAdd() { if (!PostAdd) return; LString Args; if (PostAdd->Files.Length() == 0) { LString m(PostAdd->Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -m \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal); PostAdd.Reset(); } else { LString Last = PostAdd->Files.Last(); Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get()); PostAdd->Files.PopLast(); StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal); } } bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params) { GitAdd(); return false; } bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params) { if (LTreeItem::Select()) Select(true); CommitListDirty = Result == 0; CurrentCommit.Empty(); IsCommit = false; if (Result) { switch (GetType()) { case VcGit: { if (s.Find("Please tell me who you are") >= 0) { auto i = new LInput(GetTree(), "", "Git user name:", AppName); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.name \"%s\"", i->GetStr().Get()); StartCmd(Args); auto inp = new LInput(GetTree(), "", "Git user email:", AppName); i->DoModal([this, inp](auto dlg, auto ctrlId) { if (ctrlId) { LString Args; Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get()); StartCmd(Args); } delete dlg; }); } delete dlg; }); } break; } default: break; } return false; } if (Result == 0 && LTreeItem::Select()) { d->ClearFiles(); auto *w = d->Diff ? d->Diff->GetWindow() : NULL; if (w) w->SetCtrlName(IDC_MSG, NULL); } switch (GetType()) { case VcGit: { Unpushed++; CommitListDirty = true; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); break; } case VcSvn: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } case VcHg: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); if (Params && Params->Str.Find("Push") >= 0) Push(); else GetCss(true)->Color(LColour::Green); } break; } case VcCvs: { CurrentCommit.Empty(); CommitListDirty = true; GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (!Result) { Unpushed = 0; Update(); GetCss(true)->Color(LColour::Green); } break; } default: { LAssert(!"Impl me."); break; } } return true; } void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush) { LArray Add; bool Partial = false; for (auto fp: *d->Files) { VcFile *f = dynamic_cast(fp); if (f) { int c = f->Checked(); if (c > 0) Add.Add(f); else Partial = true; } } if (CurrentBranch && Branch && !CurrentBranch.Equals(Branch)) { int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO); if (Response != IDYES) return; LJson j; j.Set("Command", "commit"); j.Set("Msg", Msg); j.Set("AndPush", (int64_t)AndPush); StartBranch(Branch, j.GetJson()); return; } if (!IsCommit) { LString Args; ParseParams *Param = AndPush ? new ParseParams("Push") : NULL; switch (GetType()) { case VcGit: { if (Add.Length() == 0) { break; } else if (Partial) { if (PostAdd.Reset(new GitCommit)) { PostAdd->Files.SetFixedLength(false); for (auto f : Add) PostAdd->Files.Add(f->GetFileName()); PostAdd->Msg = Msg; PostAdd->Branch = Branch; PostAdd->Param = Param; GitAdd(); } } else { LString m(Msg); m = m.Replace("\"", "\\\""); Args.Printf("commit -am \"%s\"", m.Get()); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); } break; } case VcSvn: { LString::Array a; a.New().Printf("commit -m \"%s\"", Msg); for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcHg: { LString::Array a; LString CommitMsg = Msg; TmpFile Tmp; if (CommitMsg.Find("\n") >= 0) { Tmp.Create().Write(Msg); a.New().Printf("commit -l \"%s\"", Tmp.GetName()); } else { a.New().Printf("commit -m \"%s\"", Msg); } if (Partial) { for (auto pf: Add) { LString s = pf->GetFileName(); if (s.Find(" ") >= 0) a.New().Printf("\"%s\"", s.Get()); else a.New() = s; } } Args = LString(" ").Join(a); IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal); if (d->Tabs && IsCommit) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } break; } case VcCvs: { LString a; a.Printf("commit -m \"%s\"", Msg); IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal); break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } } } bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { if (Result == 0 && Params && Params->Str) { LJson j(Params->Str); auto cmd = j.Get("Command"); if (cmd.Equals("commit")) { auto Msg = j.Get("Msg"); auto AndPush = j.Get("AndPush").Int(); if (Msg) { Commit(Msg, NULL, AndPush > 0); } } } break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } return true; } void VcFolder::StartBranch(const char *BranchName, const char *OnCreated) { if (!BranchName) return; switch (GetType()) { case VcHg: { LString a; a.Printf("branch \"%s\"", BranchName); StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL); break; } default: { OnCmdError(NULL, "No commit impl for type."); break; } } } void VcFolder::Push(bool NewBranchOk) { LString Args; bool Working = false; switch (GetType()) { case VcHg: { auto args = NewBranchOk ? "push --new-branch" : "push"; Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal); break; } case VcGit: { Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal); break; } case VcSvn: { // Nothing to do here.. the commit pushed the data already break; } default: { OnCmdError(NULL, "No push impl for type."); break; } } if (d->Tabs && Working) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params) { bool Status = false; if (Result) { if (GetType() == VcHg) { if (s.Find("push creates new remote branches") > 0) { if (LgiMsg(GetTree(), "Push will create a new remote branch. Is that ok?", AppName, MB_YESNO) == IDYES) { Push(true); return false; } } } OnCmdError(s, "Push failed."); } else { switch (GetType()) { case VcGit: break; case VcSvn: break; default: break; } Unpushed = 0; GetCss(true)->Color(LColour::Green); Update(); Status = true; } GetTree()->SendNotify((LNotifyType)LvcCommandEnd); return Status; // no reselect } void VcFolder::Pull(int AndUpdate, LoggingType Logging) { bool Status = false; if (AndUpdate < 0) AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0; switch (GetType()) { case VcNone: return; case VcHg: Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging); break; case VcGit: Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging); break; case VcSvn: Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging); break; default: OnCmdError(NULL, "No pull impl for type."); break; } if (d->Tabs && Status) { d->Tabs->Value(1); GetTree()->SendNotify((LNotifyType)LvcCommandStart); } } bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params) { GetTree()->SendNotify((LNotifyType)LvcCommandEnd); if (Result) { OnCmdError(s, "Pull failed."); return false; } else ClearError(); switch (GetType()) { case VcGit: { // Git does a merge by default, so the current commit changes... CurrentCommit.Empty(); break; } case VcHg: { CurrentCommit.Empty(); auto Lines = s.SplitDelimit("\n"); bool HasUpdates = false; for (auto Ln: Lines) { if (Ln.Find("files updated") < 0) continue; auto Parts = Ln.Split(","); for (auto p: Parts) { auto n = p.Strip().Split(" ", 1); if (n.Length() == 2) { if (n[0].Int() > 0) HasUpdates = true; } } } if (HasUpdates) GetCss(true)->Color(LColour::Green); else GetCss(true)->Color(LCss::ColorInherit); break; } case VcSvn: { // Svn also does a merge by default and can update our current position... CurrentCommit.Empty(); LString::Array a = s.SplitDelimit("\r\n"); for (auto &Ln: a) { if (Ln.Find("At revision") >= 0) { LString::Array p = Ln.SplitDelimit(" ."); CurrentCommit = p.Last(); break; } else if (Ln.Find("svn cleanup") >= 0) { OnCmdError(s, "Needs cleanup"); break; } } if (Params && Params->Str.Equals("log")) { LVariant Limit; d->Opts.GetValue("svn-limit", Limit); LString Args; if (Limit.CastInt32() > 0) Args.Printf("log --limit %i", Limit.CastInt32()); else Args = "log"; IsLogging = StartCmd(Args, &VcFolder::ParseLog); return false; } break; } default: break; } CommitListDirty = true; return true; // Yes - reselect and update } void VcFolder::MergeToLocal(LString Rev) { switch (GetType()) { case VcGit: { LString Args; Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } case VcHg: { LString Args; Args.Printf("merge -r %s", Rev.Get()); StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal); break; } default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: case VcHg: if (Result == 0) CommitListDirty = true; else OnCmdError(s, "Merge failed."); break; default: LAssert(!"Impl me."); break; } return true; } void VcFolder::Refresh() { CommitListDirty = true; CurrentCommit.Empty(); GitNames.Empty(); Branches.DeleteObjects(); if (Uncommit && Uncommit->LListItem::Select()) Uncommit->Select(true); Select(true); } void VcFolder::Clean() { switch (GetType()) { case VcSvn: StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal); break; default: LgiMsg(GetTree(), "Not implemented.", AppName); break; } } bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcSvn: if (Result == 0) GetCss(true)->Color(LCss::ColorInherit); break; default: LAssert(!"Impl me."); break; } return false; } LColour VcFolder::BranchColour(const char *Name) { if (!Name) return GetPaletteColour(0); auto b = Branches.Find(Name); if (!b) // Must be a new one? { int i = 1; for (auto b: Branches) { auto &v = b.value; if (!v->Colour.IsValid()) { if (v->Default) v->Colour = GetPaletteColour(0); else v->Colour = GetPaletteColour(i++); } } Branches.Add(Name, b = new VcBranch(Name)); b->Colour = GetPaletteColour((int)Branches.Length()); } return b ? b->Colour : GetPaletteColour(0); } void VcFolder::CurrentRev(std::function Callback) { LString Cmd; Cmd.Printf("id -i"); RunCmd(Cmd, LogNormal, [Callback](auto r) { if (r.Code == 0) Callback(r.Out.Strip()); }); } bool VcFolder::RenameBranch(LString NewName, LArray &Revs) { switch (GetType()) { case VcHg: { // Update to the ancestor of the commits LHashTbl,int> Refs(0, -1); for (auto c: Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) < 0) Refs.Add(p, 0); if (Refs.Find(c->GetRev()) >= 0) Refs.Add(c->GetRev(), 1); } LString::Array Ans; for (auto i:Refs) { if (i.value == 0) Ans.Add(i.key); } LArray Ancestors = d->GetRevs(Ans); if (Ans.Length() != 1) { // We should only have one ancestor LString s, m; s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length()); for (auto i: Ancestors) { m.Printf("\t%s\n", i->GetRev()); s += m; } LgiMsg(GetTree(), s, AppName, MB_OK); break; } LArray Top; for (auto c:Revs) { for (auto p:*c->GetParents()) if (Refs.Find(p) == 0) Top.Add(c); } if (Top.Length() != 1) { d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL); return false; } // Create the new branch... auto First = Ancestors.First(); LString Cmd; Cmd.Printf("update -r " LPrintfInt64, First->GetIndex()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } Cmd.Printf("branch \"%s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } // Commit it to get a revision point to rebase to Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode) { // Rebase the old tree to this point Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get()); RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r) { if (r.Code) { d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL); return; } CommitListDirty = true; d->Log->Print("Finished rename.\n", _FL); }); }); }); }); }); break; } default: { LgiMsg(GetTree(), "Not impl for this VCS.", AppName); break; } } return true; } void VcFolder::GetVersion() { auto t = GetType(); switch (t) { case VcGit: case VcSvn: case VcHg: case VcCvs: StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal); break; case VcPending: break; default: OnCmdError(NULL, "No version control found."); break; } } bool VcFolder::ParseVersion(int Result, LString s, ParseParams *Params) { if (Result) return false; auto p = s.SplitDelimit(); switch (GetType()) { case VcGit: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Git version: %s\n", p[2].Get()); } else LAssert(0); break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[GetType()] = Ver2Int(p[2]); printf("Svn version: %s\n", p[2].Get()); } else LAssert(0); break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[GetType()] = Ver2Int(Ver); printf("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { #ifdef _DEBUG for (auto i : p) printf("i='%s'\n", i.Get()); #endif if (p.Length() > 1) { auto Ver = p[2]; ToolVersion[GetType()] = Ver2Int(Ver); printf("Cvs version: %s\n", Ver.Get()); } break; } default: break; } return false; } bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcCvs: { if (Result) { d->Tabs->Value(1); OnCmdError(s, "Add file failed"); } else ClearError(); break; } default: break; } return false; } bool VcFolder::AddFile(const char *Path, bool AsBinary) { if (!Path) return false; switch (GetType()) { case VcCvs: { auto p = LString(Path).RSplit(DIR_STR, 1); ParseParams *params = NULL; if (p.Length() >= 2) { if ((params = new ParseParams)) params->AltInitPath = p[0]; } LString a; a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path); return StartCmd(a, &VcFolder::ParseAddFile, params); break; } default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params) { if (GetType() == VcSvn) { if (s.Find("Skipped ") >= 0) Result = 1; // Stupid svn... *sigh* } if (Result) { OnCmdError(s, "Error reverting changes."); } ListWorkingFolder(); return false; } bool VcFolder::Revert(LString::Array &Uris, const char *Revision) { if (Uris.Length() == 0) return false; switch (GetType()) { case VcGit: { LStringPipe p; p.Print("checkout"); for (auto u: Uris) { auto Path = GetFilePart(u); p.Print(" \"%s\"", Path.Get()); } auto a = p.NewGStr(); return StartCmd(a, &VcFolder::ParseRevert); break; } case VcHg: case VcSvn: { LStringPipe p; if (Revision) p.Print("up -r %s", Revision); else p.Print("revert"); for (auto u: Uris) { auto Path = GetFilePart(u); p.Print(" \"%s\"", Path.Get()); } auto a = p.NewGStr(); return StartCmd(a, &VcFolder::ParseRevert); break; } default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseResolveList(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcHg: { auto lines = s.Replace("\r").Split("\n"); for (auto &ln: lines) { auto p = ln.Split(" ", 1); if (p.Length() == 2) { if (p[0].Equals("U")) { auto f = new VcFile(d, this, NULL, true); f->SetText(p[0], COL_STATE); f->SetText(p[1], COL_FILENAME); f->GetStatus(); d->Files->Insert(f); } } } break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { break; } case VcHg: { d->Log->Print("Resolve: %s\n", s.Get()); break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::Resolve(const char *Path, LvcResolve Type) { if (!Path) return false; switch (GetType()) { case VcGit: { LString a; a.Printf("add \"%s\"", Path); return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); } case VcHg: { LString a; auto local = GetFilePart(Path); switch (Type) { case ResolveMark: a.Printf("resolve -m \"%s\"", local.Get()); break; case ResolveUnmark: a.Printf("resolve -u \"%s\"", local.Get()); break; case ResolveLocal: a.Printf("resolve -t internal:local \"%s\"", local.Get()); break; case ResolveIncoming: a.Printf("resolve -t internal:other \"%s\"", local.Get()); break; default: break; } if (a) return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path)); break; } case VcSvn: case VcCvs: default: { LAssert(!"Impl me."); break; } } return false; } bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params) { new BlameUi(d, GetType(), s); return false; } bool VcFolder::Blame(const char *Path) { if (!Path) return false; LUri u(Path); switch (GetType()) { case VcGit: { LString a; a.Printf("-P blame \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcHg: { LString a; a.Printf("annotate -un \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } case VcSvn: { LString a; a.Printf("blame \"%s\"", u.sPath.Get()); return StartCmd(a, &VcFolder::ParseBlame); break; } default: { LAssert(!"Impl me."); break; } } return true; } bool VcFolder::SaveFileAs(const char *Path, const char *Revision) { if (!Path || !Revision) return false; return true; } bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params) { return false; } bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params) { switch (GetType()) { case VcGit: { Unpushed = (int) s.Strip().Split("\n").Length(); break; } case VcSvn: { int64 ServerRev = 0; bool HasUpdate = false; LString::Array c = s.Split("\n"); for (unsigned i=0; i 1 && a[0].Equals("Status")) ServerRev = a.Last().Int(); else if (a[0].Equals("*")) HasUpdate = true; } if (ServerRev > 0 && HasUpdate) { int64 CurRev = CurrentCommit.Int(); Unpulled = (int) (ServerRev - CurRev); } else Unpulled = 0; Update(); break; } default: { LAssert(!"Impl me."); break; } } IsUpdatingCounts = false; Update(); return false; // No re-select } void VcFolder::SetEol(const char *Path, int Type) { if (!Path) return; switch (Type) { case IDM_EOL_LF: { ConvertEol(Path, false); break; } case IDM_EOL_CRLF: { ConvertEol(Path, true); break; } case IDM_EOL_AUTO: { #ifdef WINDOWS ConvertEol(Path, true); #else ConvertEol(Path, false); #endif break; } } } void VcFolder::UncommitedItem::Select(bool b) { LListItem::Select(b); if (b) { LTreeItem *i = d->Tree->Selection(); VcFolder *f = dynamic_cast(i); if (f) f->ListWorkingFolder(); if (d->Msg) { d->Msg->Name(NULL); auto *w = d->Msg->GetWindow(); if (w) { w->SetCtrlEnabled(IDC_COMMIT, true); w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true); } } } } void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx) { LFont *f = GetList()->GetFont(); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.Back); LDisplayString ds(f, "(working folder)"); ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx); } ////////////////////////////////////////////////////////////////////////////////////////// VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder) { Parent = parent; d = Parent->GetPriv(); LAssert(uri.Find("://") >= 0); // Is URI Uri.Set(uri); LAssert(Uri); Leaf = leaf; Folder = folder; Tmp = NULL; Item->Insert(this); if (Folder) { Insert(Tmp = new LTreeItem); Tmp->SetText("Loading..."); } } VcLeaf::~VcLeaf() { Log.DeleteObjects(); } LString VcLeaf::Full() { LUri u = Uri; u += Leaf; return u.ToString(); } void VcLeaf::OnBrowse() { auto full = Full(); LList *Files = d->Files; Files->Empty(); LDirectory Dir; for (int b = Dir.First(full); b; b = Dir.Next()) { if (Dir.IsDir()) continue; VcFile *f = new VcFile(d, Parent, NULL, true); if (f) { f->SetUri(LString("file://") + full); f->SetText(Dir.GetName(), COL_FILENAME); Files->Insert(f); } } Files->ResizeColumnsToContent(); if (Folder) Parent->FolderStatus(full, this); } void VcLeaf::AfterBrowse() { } VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree) { if (!Stricmp(Path, Full().Get())) return this; if (OpenTree) DoExpand(); VcLeaf *r = NULL; for (auto n = GetChild(); !r && n; n = n->GetNext()) { auto l = dynamic_cast(n); if (l) r = l->FindLeaf(Path, OpenTree); } return r; } void VcLeaf::DoExpand() { if (Tmp) { Tmp->Remove(); DeleteObj(Tmp); Parent->ReadDir(this, Full()); } } void VcLeaf::OnExpand(bool b) { if (b) DoExpand(); } const char *VcLeaf::GetText(int Col) { if (Col == 0) return Leaf; return NULL; } int VcLeaf::GetImage(int Flags) { return Folder ? IcoFolder : IcoFile; } int VcLeaf::Compare(VcLeaf *b) { // Sort folders to the top... if (Folder ^ b->Folder) return (int)b->Folder - (int)Folder; // Then alphabetical return Stricmp(Leaf.Get(), b->Leaf.Get()); } bool VcLeaf::Select() { return LTreeItem::Select(); } void VcLeaf::Select(bool b) { LTreeItem::Select(b); if (b) { d->Commits->RemoveAll(); OnBrowse(); ShowLog(); } } void VcLeaf::ShowLog() { if (Log.Length()) { d->Commits->RemoveAll(); Parent->DefaultFields(); Parent->UpdateColumns(); for (auto i: Log) d->Commits->Insert(i); } } void VcLeaf::OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Log", IDM_LOG); s.AppendItem("Blame", IDM_BLAME, !Folder); s.AppendSeparator(); s.AppendItem("Browse To", IDM_BROWSE_FOLDER); s.AppendItem("Terminal At", IDM_TERMINAL); int Cmd = s.Float(GetTree(), m - _ScrollPos()); switch (Cmd) { case IDM_LOG: { Parent->LogFile(Full()); break; } case IDM_BLAME: { Parent->Blame(Full()); break; } case IDM_BROWSE_FOLDER: { LBrowseToFile(Full()); break; } case IDM_TERMINAL: { TerminalAt(Full()); break; } } } } ///////////////////////////////////////////////////////////////////////////////////////// ProcessCallback::ProcessCallback(LString exe, LString args, LString localPath, LTextLog *log, LView *view, std::function callback) : Log(log), View(view), Callback(callback), LThread("ProcessCallback.Thread"), LSubProcess(exe, args) { SetInitFolder(localPath); if (Log) Log->Print("%s %s\n", exe.Get(), args.Get()); Run(); } int ProcessCallback::Main() { if (!Start()) { Ret.Out.Printf("Process failed with %i", GetErrorCode()); Callback(Ret); } else { while (IsRunning()) { auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } } auto Rd = Read(); if (Rd.Length()) { Ret.Out += Rd; if (Log) Log->Write(Rd.Get(), Rd.Length()); } Ret.Code = GetExitValue(); } View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this); return 0; } void ProcessCallback::OnComplete() // Called in the GUI thread... { Callback(Ret); } diff --git a/ResourceEditor/Code/LgiRes_Dialog.cpp b/ResourceEditor/Code/LgiRes_Dialog.cpp --- a/ResourceEditor/Code/LgiRes_Dialog.cpp +++ b/ResourceEditor/Code/LgiRes_Dialog.cpp @@ -1,4379 +1,4371 @@ /* ** FILE: LgiRes_Dialog.cpp ** AUTHOR: Matthew Allen ** DATE: 5/8/1999 ** DESCRIPTION: Dialog Resource Editor ** ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ // Old control offset 3,17 //////////////////////////////////////////////////////////////////// #include #include "LgiResEdit.h" #include "LgiRes_Dialog.h" #include "lgi/common/Button.h" #include "lgi/common/Variant.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Menu.h" #include "lgi/common/StatusBar.h" #include "lgi/common/ToolBar.h" #include "resdefs.h" //////////////////////////////////////////////////////////////////// #define IDC_UP 101 #define IDC_DOWN 102 #define DEBUG_OVERLAY 0 // Name mapping table class LgiObjectName { public: int Type; const char *ObjectName; char *ResourceName; bool ToolbarBtn; } NameMap[] = { // ID Lgi's name Resource editor name {UI_DIALOG, "LDialog", Res_Dialog, false}, {UI_TABLE, "LTableLayout", Res_Table, true}, {UI_TEXT, "LText", Res_StaticText, true}, {UI_EDITBOX, "LEdit", Res_EditBox, true}, {UI_CHECKBOX, "LCheckBox", Res_CheckBox, true}, {UI_BUTTON, "LButton", Res_Button, true}, {UI_GROUP, "LRadioGroup", Res_Group, true}, {UI_RADIO, "LRadioButton", Res_RadioBox, true}, {UI_TABS, "LTabView", Res_TabView, true}, {UI_TAB, "LTabPage", Res_Tab, false}, {UI_LIST, "LList", Res_ListView, true}, {UI_COLUMN, "LListColumn", Res_Column, false}, {UI_COMBO, "LCombo", Res_ComboBox, true}, {UI_TREE, "LTree", Res_TreeView, true}, {UI_BITMAP, "LBitmap", Res_Bitmap, true}, {UI_PROGRESS, "LProgressView", Res_Progress, true}, {UI_SCROLL_BAR, "LScrollBar", Res_ScrollBar, true}, {UI_CUSTOM, "LCustom", Res_Custom, true}, {UI_CONTROL_TREE, "LControlTree", Res_ControlTree, true}, // If you add a new control here update ResDialog::CreateCtrl(int Tool) as well {0, 0, 0, 0} }; class CtrlItem : public LListItem { friend class TabOrder; ResDialogCtrl *Ctrl; public: CtrlItem(ResDialogCtrl *ctrl) { Ctrl = ctrl; } const char *GetText(int Col) { switch (Col) { case 0: { if (Ctrl && Ctrl->GetStr()) return Ctrl->GetStr()->GetDefine(); break; } case 1: { if (Ctrl && Ctrl->GetStr()) return Ctrl->GetStr()->Get(); break; } } return NULL; } }; class TabOrder : public LDialog { ResDialogCtrl *Top; LList *Lst; LButton *Ok; LButton *Cancel; LButton *Up; LButton *Down; public: TabOrder(LView *Parent, ResDialogCtrl *top) { Top = top; SetParent(Parent); Children.Insert(Lst = new LList(IDC_LIST, 10, 10, 350, 300)); Children.Insert(Ok = new LButton(IDOK, Lst->GetPos().x2 + 10, 10, 60, 20, "Ok")); Children.Insert(Cancel = new LButton(IDCANCEL, Lst->GetPos().x2 + 10, Ok->GetPos().y2 + 5, 60, 20, "Cancel")); Children.Insert(Up = new LButton(IDC_UP, Lst->GetPos().x2 + 10, Cancel->GetPos().y2 + 15, 60, 20, "Up")); Children.Insert(Down = new LButton(IDC_DOWN, Lst->GetPos().x2 + 10, Up->GetPos().y2 + 5, 60, 20, "Down")); LRect r(0, 0, Ok->GetPos().x2 + 17, Lst->GetPos().y2 + 40); SetPos(r); MoveToCenter(); Lst->AddColumn("#define", 150); Lst->AddColumn("Text", 150); Lst->MultiSelect(false); if (Top) { for (auto c: Top->View()->IterateViews()) { ResDialogCtrl *Ctrl = dynamic_cast(c); if (Ctrl->GetType() != UI_TEXT) { Lst->Insert(new CtrlItem(Ctrl)); } } char s[256]; sprintf(s, "Set Tab Order: %s", Top->GetStr()->GetDefine()); Name(s); } } int OnNotify(LViewI *Ctrl, LNotification n) { int MoveDir = 1; switch (Ctrl->GetId()) { case IDC_UP: { MoveDir = -1; // fall through } case IDC_DOWN: { if (Lst) { CtrlItem *Sel = dynamic_cast(Lst->GetSelected()); if (Sel) { int Index = Lst->IndexOf(Sel); CtrlItem *Up = dynamic_cast(Lst->ItemAt(Index+MoveDir)); if (Up) { Lst->Remove(Sel); Lst->Insert(Sel, Index+MoveDir); Sel->Select(true); Sel->ScrollTo(); } } } break; } case IDOK: { if (Lst) { int i=0; List All; Lst->GetAll(All); for (auto n: All) { Top->View()->DelView(n->Ctrl->View()); Top->View()->AddView(n->Ctrl->View(), i++); } } // fall through } case IDCANCEL: { EndModal(0); break; } } return 0; } }; //////////////////////////////////////////////////////////////////// void DrawGoobers(LSurface *pDC, LRect &r, LRect *Goobers, LColour c, int OverIdx) { int Mx = (r.x2 + r.x1) / 2 - (GOOBER_SIZE / 2); int My = (r.y2 + r.y1) / 2 - (GOOBER_SIZE / 2); pDC->Colour(c); Goobers[0].x1 = r.x1 - GOOBER_BORDER; Goobers[0].y1 = r.y1 - GOOBER_BORDER; Goobers[0].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[0].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[1].x1 = Mx; Goobers[1].y1 = r.y1 - GOOBER_BORDER; Goobers[1].x2 = Mx + GOOBER_SIZE; Goobers[1].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[2].y1 = r.y1 - GOOBER_BORDER; Goobers[2].x2 = r.x2 + GOOBER_BORDER; Goobers[2].y2 = r.y1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[3].y1 = My; Goobers[3].x2 = r.x2 + GOOBER_BORDER; Goobers[3].y2 = My + GOOBER_SIZE; Goobers[4].x1 = r.x2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[4].x2 = r.x2 + GOOBER_BORDER; Goobers[4].y2 = r.y2 + GOOBER_BORDER; Goobers[5].x1 = Mx; Goobers[5].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[5].x2 = Mx + GOOBER_SIZE; Goobers[5].y2 = r.y2 + GOOBER_BORDER; Goobers[6].x1 = r.x1 - GOOBER_BORDER; Goobers[6].y1 = r.y2 + (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[6].y2 = r.y2 + GOOBER_BORDER; Goobers[7].x1 = r.x1 - GOOBER_BORDER; Goobers[7].y1 = My; Goobers[7].x2 = r.x1 - (GOOBER_BORDER - GOOBER_SIZE); Goobers[7].y2 = My + GOOBER_SIZE; for (int i=0; i<8; i++) { if (OverIdx == i) pDC->Rectangle(Goobers+i); else pDC->Box(Goobers+i); } } //////////////////////////////////////////////////////////////////// int ResDialogCtrl::TabDepth = 0; ResDialogCtrl::ResDialogCtrl(ResDialog *dlg, char *CtrlTypeName, LXmlTag *load) : ResObject(CtrlTypeName) { Dlg = dlg; - _Str = NULL; - DragCtrl = -1; - AcceptChildren = false; - Movable = true; - MoveCtrl = false; - Vis = true; Client.ZOff(-1, -1); - SelectMode = SelNone; SelectStart.ZOff(-1, -1); - OverGoober = -1; if (load) { // Base a string off the xml int r = load->GetAsInt("ref"); if (Dlg) { SetStr(Dlg->Symbols->FindRef(r)); LAssert(GetStr()); if (!GetStr()) // oh well we should have one anyway... fix things up so to speak. SetStr(Dlg->Symbols->CreateStr()); } LAssert(GetStr()); } else if (Dlg->CreateSymbols) { // We create a symbol for this resource SetStr((Dlg && Dlg->Symbols) ? Dlg->Symbols->CreateStr() : NULL); if (GetStr()) { char Def[256]; sprintf(Def, "IDC_%i", GetStr()->GetRef()); GetStr()->SetDefine(Def); } } else { // Someone else is going to create the symbol SetStr(NULL); } } ResDialogCtrl::~ResDialogCtrl() { if (ResDialog::Symbols) { auto s = GetStr(); SetStr(NULL); ResDialog::Symbols->DeleteStr(s); } if (Dlg) { Dlg->App()->OnObjDelete(this); Dlg->OnDeselect(this); } } char *ResDialogCtrl::GetRefText() { static char Buf[64]; if (GetStr()) { sprintf(Buf, "Ref=%i", GetStr()->GetRef()); } else { Buf[0] = 0; } return Buf; } void ResDialogCtrl::ListChildren(List &l, bool Deep) { for (LViewI *w: View()->IterateViews()) { ResDialogCtrl *c = dynamic_cast(w); LAssert(c); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l); } } } } LRect ResDialogCtrl::GetMinSize() { LRect m(0, 0, GRID_X-1, GRID_Y-1); if (IsContainer()) { LRect cli = View()->GetClient(false); for (LViewI *c: View()->IterateViews()) { LRect cpos = c->GetPos(); cpos.Offset(cli.x1, cli.y1); m.Union(&cpos); } } return m; } bool ResDialogCtrl::SetPos(LRect &p, bool Repaint) { LRect m = GetMinSize(); if (m.X() > p.X()) p.x2 = p.x1 + m.X() - 1; if (m.Y() > p.Y()) p.y2 = p.y1 + m.Y() - 1; if (p != View()->GetPos()) { /* if (ParentCtrl) { if (p.x1 < ParentCtrl->Client.x1) { p.Offset(ParentCtrl->Client.x1 - p.x1, 0); } if (p.y1 < ParentCtrl->Client.y1) { p.Offset(0, ParentCtrl->Client.y1 - p.y1); } } */ // set our size bool Status = View()->SetPos(p, Repaint); // tell everyone else about the change OnFieldChange(); LRect r(0, 0, p.X()-1, p.Y()-1); r.Inset(-GOOBER_BORDER, -GOOBER_BORDER); View()->Invalidate(&r, false, true); // check our parents are big enough to show us... ResDialogCtrl *Par = ParentCtrl(); if (Par) { LRect t = Par->View()->GetPos(); Par->ResDialogCtrl::SetPos(t, true); } return Status; } return true; } bool ResDialogCtrl::SetStr(ResString *s) { if (_Str == s) { if (_Str) LAssert(_Str->Refs.HasItem(this)); return true; } if (_Str) { if (!_Str->Refs.HasItem(this)) { LAssert(!"Refs incorrect."); return false; } _Str->Refs.Delete(this); } _Str = s; if (_Str) { if (_Str->Refs.HasItem(this)) { LAssert(!"Refs already has us."); return false; } _Str->Refs.Add(this); } else { // LgiTrace("%s:%i - %p::SetStr(NULL)\n", _FL, this); } return true; } void ResDialogCtrl::TabString(char *Str) { if (!Str) return; memset(Str, '\t', TabDepth); Str[TabDepth] = 0; } LRect ResDialogCtrl::AbsPos() { LViewI *w = View(); LRect r = w->GetPos(); r.Offset(-r.x1, -r.y1); for (; w && w != Dlg; w = w->GetParent()) { LRect pos = w->GetPos(); if (w->GetParent()) { // LView *Ctrl = w->GetParent()->GetGView(); LRect client = w->GetParent()->GetClient(false); r.Offset(pos.x1 + client.x1, pos.y1 + client.y1); } else { r.Offset(pos.x1, pos.y1); } } return r; } void ResDialogCtrl::StrFromRef(int Ref) { // get the string object SetStr(Dlg->App()->GetStrFromRef(Ref)); if (!GetStr()) { LgiTrace("%s:%i - String with ref '%i' missing.\n", _FL, Ref); LAssert(0); if (SetStr(Dlg->App()->GetDialogSymbols()->CreateStr())) { GetStr()->SetRef(Ref); } else return; } // if this assert fails then the Ref doesn't exist // and the string can't be found // if this assert fails then the Str is already // associated with a control, and thus we would // duplicate the pointer to the string if we let // it go by // LAssert(Str->UpdateWnd == 0); // set the string's control to us GetStr()->UpdateWnd = View(); // make the strings refid unique GetStr()->UnDuplicate(); View()->Name(GetStr()->Get()); } bool ResDialogCtrl::GetFields(FieldTree &Fields) { if (GetStr()) { GetStr()->GetFields(Fields); } int Id = 101; Fields.Insert(this, DATA_STR, Id++, VAL_Pos, "Pos"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Visible, "Visible"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Enabled, "Enabled"); Fields.Insert(this, DATA_STR, Id++, VAL_Class, "Class", -1); Fields.Insert(this, DATA_STR, Id++, VAL_Style, "Style", -1, true); return true; } bool ResDialogCtrl::Serialize(FieldTree &Fields) { if ((Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::UiToObj) && GetStr()) { GetStr()->Serialize(Fields); } LRect r = View()->GetPos(), Old = View()->GetPos(); bool e = true; if (Fields.GetMode() == FieldTree::ObjToUi || Fields.GetMode() == FieldTree::ObjToStore) { r = View()->GetPos(); e = View()->Enabled(); } Fields.Serialize(this, VAL_Pos, r); Fields.Serialize(this, VAL_Visible, Vis, true); Fields.Serialize(this, VAL_Enabled, e, true); Fields.Serialize(this, VAL_Class, CssClass); Fields.Serialize(this, VAL_Style, CssStyle); if (Fields.GetMode() == FieldTree::UiToObj || Fields.GetMode() == FieldTree::StoreToObj) { View()->Enabled(e); SetPos(r); r.Union(&Old); r.Inset(-GOOBER_BORDER, -GOOBER_BORDER); if (View()->GetParent()) { View()->GetParent()->Invalidate(&r); } } if (Dlg && Dlg->Item) Dlg->Item->Update(); return true; } void ResDialogCtrl::CopyText() { if (GetStr()) GetStr()->CopyText(); } void ResDialogCtrl::PasteText() { if (GetStr()) { GetStr()->PasteText(); View()->Invalidate(); } } bool ResDialogCtrl::AttachCtrl(ResDialogCtrl *Ctrl, LRect *r) { bool Status = false; if (Ctrl) { Ctrl->View()->Visible(true); View()->AddView(Ctrl->View()); Ctrl->View()->SetParent(View()); if (r) { if (!dynamic_cast(Ctrl->View()->GetParent())) { Dlg->SnapRect(r, this); } Ctrl->SetPos(*r); } if (Dlg->Resource::IsSelected()) { Dlg->OnSelect(Ctrl); } Status = true; } return Status; } void ResDialogCtrl::OnPaint(LSurface *pDC) { if (DragCtrl >= 0) { LRect r = DragRgn; r.Normal(); pDC->Colour(L_FOCUS_SEL_BACK); pDC->Box(&r); } } LMouse ResDialogCtrl::MapToDialog(LMouse m) { // Convert co-ords from out own local space to be relative to 'Dlg' // the parent dialog. LMouse Ms = m; LViewI *Parent; for (LViewI *i = View(); i && i != (LViewI*)Dlg; i = Parent) { Parent = i->GetParent(); LRect Pos = i->GetPos(), Cli = i->GetClient(false); #if DEBUG_OVERLAY LgiTrace("%s %i,%i + %i,%i + %i,%i = %i,%i\n", i->GetClass(), Ms.x, Ms.y, Pos.x1, Pos.y1, Cli.x1, Cli.y1, Ms.x + Pos.x1 + Cli.x1, Ms.y + Pos.y1 + Cli.y1); #endif Ms.x += Pos.x1 + Cli.x1; Ms.y += Pos.y1 + Cli.y1; } return Ms; } void ResDialogCtrl::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { // LgiTrace("Click down=%i %i,%i\n", m.Down(), m.x, m.y); if (Dlg) { #if DEBUG_OVERLAY LPoint Prev(0, 0); auto &DebugOverlay = Dlg->DebugOverlay; if (!DebugOverlay) { DebugOverlay.Reset(new LMemDC(Dlg->X(), Dlg->Y(), System32BitColourSpace)); DebugOverlay->Colour(0, 32); DebugOverlay->Rectangle(); DebugOverlay->Colour(LColour(64, 192, 64)); } #endif bool Processed = false; LRect c = View()->GetClient(); bool ClickedThis = c.Overlap(m.x, m.y); // Convert co-ords from out own local space to be relative to 'Dlg' // the parent dialog. LMouse Ms = MapToDialog(m); #if DEBUG_OVERLAY if (DebugOverlay) { DebugOverlay->Line(Prev.x, Prev.y, Ms.x, Ms.y); DebugOverlay->Circle(Ms.x, Ms.y, 5); Prev.x = Ms.x; Prev.y = Ms.y; } #endif Dlg->OnMouseClick(Ms); if (ClickedThis && !Dlg->IsDraging()) { DragCtrl = Dlg->CurrentTool(); if ((DragCtrl > 0 && AcceptChildren) || ((DragCtrl == 0) && !Movable)) { LPoint p(m.x, m.y); Dlg->SnapPoint(&p, ParentCtrl()); DragStart.x = DragRgn.x1 = DragRgn.x2 = p.x; DragStart.y = DragRgn.y1 = DragRgn.y2 = p.y; View()->Capture(true); Processed = true; } else { DragCtrl = -1; if (Movable) { DragRgn.x1 = m.x; DragRgn.y1 = m.y; MoveCtrl = true; Processed = true; View()->Capture(true); } } SelectMode = (m.Shift()) ? SelAdd : SelSet; SelectStart = View()->GetPos(); } } } else if (m.IsContextMenu()) { LSubMenu RClick; bool PasteData = false; bool PasteTranslations = false; CtrlButton *Btn = dynamic_cast(this); { LClipBoard c(Dlg); char *Clip = c.Text(); if (Clip) { PasteTranslations = strstr(Clip, TranslationStrMagic); char *p = Clip; while (*p && strchr(" \r\n\t", *p)) p++; PasteData = *p == '<'; } } RClick.AppendItem("Cu&t", IDM_CUT, Dlg->Selection.Length()>0); RClick.AppendItem("&Copy Control", IDM_COPY, Dlg->Selection.Length()>0); RClick.AppendItem("&Paste", IDM_PASTE, PasteData); RClick.AppendSeparator(); RClick.AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick.AppendItem("Paste Text", IDM_PASTE_TEXT, PasteTranslations); RClick.AppendSeparator(); RClick.AppendItem("&Delete", IDM_DELETE, Dlg->Selection.Length()>0); if (Btn) { RClick.AppendSeparator(); RClick.AppendItem("Set 'Ok'", IDM_SET_OK); RClick.AppendItem("Set 'Cancel'", IDM_SET_CANCEL); } if (Dlg->GetMouse(m, true)) { int Cmd = 0; switch (Cmd = RClick.Float(Dlg, m.x, m.y)) { case IDM_DELETE: { Dlg->Delete(); break; } case IDM_CUT: { Dlg->Copy(true); break; } case IDM_COPY: { Dlg->Copy(); break; } case IDM_PASTE: { Dlg->Paste(); break; } case IDM_COPY_TEXT: { ResDialogCtrl *Ctrl = Dlg->Selection[0]; if (Ctrl) Ctrl->CopyText(); break; } case IDM_PASTE_TEXT: { PasteText(); break; } case IDM_SET_OK: { ResString *s = Btn->GetStr(); if (s) { s->Set("Ok"); s->SetDefine("IDOK"); } break; } case IDM_SET_CANCEL: { ResString *s = Btn->GetStr(); if (s) { s->Set("Cancel"); s->SetDefine("IDCANCEL"); } break; } } } } } else { if (DragCtrl >= 0) { bool Exit = false; if (Dlg && (DragRgn.X() > 1 || DragRgn.Y() > 1)) { if (DragCtrl == 0) { Dlg->SelectRect(this, &DragRgn, SelectMode != SelAdd); } else { ResDialogCtrl *Ctrl = Dlg->CreateCtrl(DragCtrl, 0); if (Ctrl) { AttachCtrl(Ctrl, &DragRgn); } } Exit = true; } DragCtrl = -1; View()->Invalidate(); View()->Capture(false); if (Exit) { return; } } if (MoveCtrl) { View()->Capture(false); MoveCtrl = false; } if (SelectMode > SelNone) { LRect r = View()->GetPos(); if (SelectStart == r) { Dlg->OnSelect(this, SelectMode != SelAdd); } SelectMode = SelNone; } } } void ResDialogCtrl::OnMouseMove(LMouse &m) { // Drag a rubber band... if (DragCtrl >= 0) { LRect Old = DragRgn; DragRgn.x1 = DragStart.x; DragRgn.y1 = DragStart.y; DragRgn.x2 = m.x; DragRgn.y2 = m.y; DragRgn.Normal(); Dlg->SnapRect(&DragRgn, this); Old.Union(&DragRgn); Old.Inset(-1, -1); View()->Invalidate(&Old); } if (Dlg) { LMouse Ms = MapToDialog(m); Dlg->OnMouseMove(Ms); } // Move some ctrl(s) if (MoveCtrl && !m.Shift()) { int Dx = m.x - DragRgn.x1; int Dy = m.y - DragRgn.y1; if (Dx != 0 || Dy != 0) { // LgiTrace("Move %i,%i + %i,%i\n", m.x, m.y, Dx, Dy); if (!Dlg->IsSelected(this)) { Dlg->OnSelect(this); } LRect Old = View()->GetPos(); LRect New = Old; New.Offset( m.x - DragRgn.x1, m.y - DragRgn.y1); LPoint p(New.x1, New.y1); Dlg->SnapPoint(&p, ParentCtrl()); New.Set(p.x, p.y, p.x + New.X() - 1, p.y + New.Y() - 1); if (New != Old) { Dlg->MoveSelection(New.x1 - Old.x1, New.y1 - Old.y1); } } } } char *ReadInt(char *s, int &Value) { char *c = strchr(s, ','); if (c) { *c = 0; Value = atoi(s); return c+1; } Value = atoi(s); return 0; } void ResDialogCtrl::ReadPos(char *Str) { if (Str) { char *s = NewStr(Str); if (s) { LRect r = View()->GetPos(); char *p = ReadInt(s, r.x1); if (p) p = ReadInt(p, r.y1); if (p) p = ReadInt(p, r.x2); if (p) p = ReadInt(p, r.y2); DeleteArray(s); } } } //////////////////////////////////////////////////////////////////// CtrlDlg::CtrlDlg(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Dialog, load) { Movable = false; AcceptChildren = true; GetStr()->UpdateWnd = View(); View()->Name("CtrlDlg"); } IMPL_DIALOG_CTRL(CtrlDlg) LRect &CtrlDlg::GetClient(bool InClientSpace) { static LRect r; Client.Set(0, 0, View()->X()-1, View()->Y()-1); Client.Inset(2, 2); Client.y1 += LAppInst->GetMetric(LGI_MET_DECOR_CAPTION); if (Client.y1 > Client.y2) Client.y1 = Client.y2; if (InClientSpace) r = Client.ZeroTranslate(); else r = Client; return r; } void CtrlDlg::OnNcPaint(LSurface *pDC, LRect &r) { // Draw the border LWideBorder(pDC, r, DefaultRaisedEdge); // Draw the title bar int TitleY = LAppInst->GetMetric(LGI_MET_DECOR_CAPTION); LRect t = r; t.y2 = t.y1 + TitleY - 1; pDC->Colour(L_ACTIVE_TITLE); pDC->Rectangle(&t); if (GetStr()) { LDisplayString ds(LSysFont, GetStr()->Get()); LSysFont->Fore(L_ACTIVE_TITLE_TEXT); LSysFont->Transparent(true); ds.Draw(pDC, t.x1 + 10, t.y1 + ((t.Y()-ds.Y())/2)); } r.y1 = t.y2 + 1; } void CtrlDlg::OnPaint(LSurface *pDC) { Client = GetClient(); // Draw the grid pDC->Colour(L_MED); pDC->Rectangle(&Client); pDC->Colour(Rgb24(0x80, 0x80, 0x80), 24); for (int y=Client.y1; ySet(x, y); } ResDialogCtrl::OnPaint(pDC); } ///////////////////////////////////////////////////////////////////// // Text box CtrlText::CtrlText(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_StaticText, load) { if (GetStr() && !load) { GetStr()->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlText) void CtrlText::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); char *Text = GetStr()->Get(); LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); if (Text) { LRect Client = GetClient(); int y = 0; char *Start = Text; for (char *s = Text; 1; s++) { if ((*s == '\\' && *(s+1) == 'n') || (*s == 0)) { LDisplayString ds(LSysFont, Start, s - Start); ds.Draw(pDC, 0, y, &Client); y += 15; Start = s + 2; if (*s == '\\') s++; } if (*s == 0) break; } } } // Editbox CtrlEditbox::CtrlEditbox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_EditBox, load) { Password = false; MultiLine = false; } IMPL_DIALOG_CTRL(CtrlEditbox) void CtrlEditbox::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Enabled() ? L_WORKSPACE : L_MED); pDC->Rectangle(&r); char *Text = GetStr()->Get(); LSysFont->Fore(Enabled() ? L_TEXT : L_LOW); LSysFont->Transparent(true); if (Text) { if (Password) { char *t = NewStr(Text); if (t) { for (char *p = t; *p; p++) *p = '*'; LDisplayString ds(LSysFont, t); ds.Draw(pDC, 4, 4); DeleteArray(t); } } else { LDisplayString ds(LSysFont, Text); ds.Draw(pDC, 4, 4); } } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Password "Pw" #define VAL_MultiLine "MultiLine" bool CtrlEditbox::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_BOOL, 300, VAL_Password, "Password"); Fields.Insert(this, DATA_BOOL, 301, VAL_MultiLine, "MultiLine"); } return Status; } bool CtrlEditbox::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Password, Password, false); Fields.Serialize(this, VAL_MultiLine, MultiLine, false); } return Status; } // Check box CtrlCheckbox::CtrlCheckbox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_CheckBox, load) { } IMPL_DIALOG_CTRL(CtrlCheckbox) void CtrlCheckbox::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r(0, 0, 12, 12); // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(L_WORKSPACE); pDC->Rectangle(&r); LPoint Pt[6] = { LPoint(3, 4), LPoint(3, 7), LPoint(5, 10), LPoint(10, 5), LPoint(10, 2), LPoint(5, 7)}; pDC->Colour(0); pDC->Polygon(6, Pt); pDC->Set(3, 5); char *Text = GetStr()->Get(); if (Text) { LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Button CtrlButton::CtrlButton(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Button, load) { IsToggle = false; } IMPL_DIALOG_CTRL(CtrlButton) bool CtrlButton::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); int Id = 160; Fields.Insert(this, DATA_FILENAME, Id++, VAL_Image, "Image"); Fields.Insert(this, DATA_BOOL, Id++, VAL_Toggle, "Toggle"); return Status; } bool CtrlButton::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Image, Image); Fields.Serialize(this, VAL_Toggle, IsToggle); } return Status; } void CtrlButton::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r = Client; char *Text = GetStr()->Get(); // Draw the ctrl LWideBorder(pDC, r, DefaultRaisedEdge); LSysFont->Fore(L_TEXT); if (ValidStr(Text)) { LSysFont->Back(L_MED); LSysFont->Transparent(false); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x1 + ((r.X()-ds.X())/2), r.y1 + ((r.Y()-ds.Y())/2), &r); } else { pDC->Colour(L_MED); pDC->Rectangle(&r); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Group CtrlGroup::CtrlGroup(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Group, load) { AcceptChildren = true; if (GetStr() && !load) { GetStr()->SetDefine("IDC_STATIC"); } } IMPL_DIALOG_CTRL(CtrlGroup) void CtrlGroup::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r = Client; // Draw the ctrl r.y1 += 5; LWideBorder(pDC, r, EdgeXpChisel); r.y1 -= 5; LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); char *Text = GetStr()->Get(); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x1 + 8, r.y1 - 2); // Draw children //LWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //Radio button uint32_t RadioBmp[] = { 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x80808080, 0x80808080, 0x80808080, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0x8080C0C0, 0x80808080, 0x00000000, 0x00000000, 0x00000000, 0x80808080, 0xC0C08080, 0xC0C0C0C0, 0x80C0C0C0, 0x00008080, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFF0000, 0xC0C0C0FF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x00808080, 0xFFFF0000, 0xFFFFFFFF, 0x00FFFFFF, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xDFDFFFFF, 0xFFFFFFDF, 0x80C0C0C0, 0x00008080, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFFFFFFF, 0xFFFFDFDF, 0xC0C0C0FF, 0x80C0C0C0, 0xDFDF8080, 0xDFDFDFDF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xDFDFDFDF, 0xFFFFDFDF, 0xC0C0C0FF, 0xC0C0C0C0, 0xFFFFC0C0, 0xFFFFFFFF, 0xDFDFDFDF, 0xDFDFDFDF, 0xDFDFDFDF, 0xFFFFFFFF, 0xC0C0FFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0C0C0C0, 0xC0C0C0C0, 0xC0C0C0C0}; CtrlRadio::CtrlRadio(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_RadioBox, load) { Bmp = new LMemDC; if (Bmp && Bmp->Create(12, 12, GdcD->GetColourSpace())) { int Len = ((Bmp->X()*24)+31)/32*4; for (int y=0; yY(); y++) { for (int x=0; xX(); x++) { uchar *s = ((uchar*) RadioBmp) + (y * Len) + (x * 3); Bmp->Colour(Rgb24(s[0], s[1], s[2]), 24); Bmp->Set(x, y); } } } } CtrlRadio::~CtrlRadio() { DeleteObj(Bmp); } IMPL_DIALOG_CTRL(CtrlRadio) void CtrlRadio::OnPaint(LSurface *pDC) { Client.ZOff(X()-1, Y()-1); LRect r(0, 0, 12, 12); // Draw the ctrl if (Bmp) pDC->Blt(r.x1, r.y1, Bmp); char *Text = GetStr()->Get(); if (Text) { LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); LDisplayString ds(LSysFont, Text); ds.Draw(pDC, r.x2 + 10, r.y1-2); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Tab CtrlTab::CtrlTab(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Tab, load) { GetStr()->UpdateWnd = this; } IMPL_DIALOG_CTRL(CtrlTab) void CtrlTab::OnPaint(LSurface *pDC) { } void CtrlTab::ListChildren(List &l, bool Deep) { ResDialogCtrl *Ctrl = dynamic_cast(GetParent()); CtrlTabs *Par = dynamic_cast(Ctrl); LAssert(Par); auto MyIndex = Par->Tabs.IndexOf(this); LAssert(MyIndex >= 0); List *CList = (Par->Current == MyIndex) ? &Par->Children : &Children; for (auto w: *CList) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); if (Deep) { c->ListChildren(l, Deep); } } } } // Tab control CtrlTabs::CtrlTabs(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_TabView, load) { AcceptChildren = true; Current = 0; if (!load) { for (int i=0; i<3; i++) { CtrlTab *t = new CtrlTab(dlg, load); if (t) { char Text[256]; sprintf(Text, "Tab %i", i+1); if (t->GetStr()) { t->GetStr()->Set(Text); t->SetParent(this); Tabs.Insert(t); } else { DeleteObj(t); } } } } } CtrlTabs::~CtrlTabs() { Empty(); } void CtrlTabs::OnMouseMove(LMouse &m) { ResDialogCtrl::OnMouseMove(m); } void CtrlTabs::ShowMe(ResDialogCtrl *Child) { CtrlTab *t = dynamic_cast(Child); if (t) { auto Idx = Tabs.IndexOf(t); if (Idx >= 0) { ToTab(); Current = Idx; FromTab(); } } } void CtrlTabs::EnumCtrls(List &Ctrls) { List::I it = Tabs.begin(); for (CtrlTab *t = *it; t; t = *++it) { t->EnumCtrls(Ctrls); } ResDialogCtrl::EnumCtrls(Ctrls); } LRect CtrlTabs::GetMinSize() { List l; ListChildren(l, false); LRect r(0, 0, GRID_X-1, GRID_Y-1); /* // don't resize smaller than the tabs for (CtrlTab *t = Tabs.First(); t; t = Tabs.Next()) { r.x2 = max(r.x2, t->r.x2 + 10); r.y2 = max(r.y2, t->r.y2 + 10); } */ // don't resize smaller than any of the children // on any of the tabs LRect cli = GetClient(false); for (auto c: l) { LRect cpos = c->View()->GetPos(); cpos.Offset(cli.x1, cli.y1); r.Union(&cpos); } return r; } void CtrlTabs::ListChildren(List &l, bool Deep) { int n=0; for (auto t: Tabs) { l.Add(t); auto It = (Current == n ? (LViewI*)this : (LViewI*)t)->IterateViews(); for (LViewI *w: It) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l, Deep); } } n++; } } void CtrlTabs::Empty() { ToTab(); Tabs.DeleteObjects(); } void CtrlTabs::OnPaint(LSurface *pDC) { // Draw the ctrl Title.ZOff(X()-1, 17); Client.ZOff(X()-1, Y()-1); Client.y1 = Title.y2; LRect r = Client; LWideBorder(pDC, r, DefaultRaisedEdge); // Draw the tabs int i = 0; int x = 2; for (auto Tab: Tabs) { char *Str = Tab->GetStr() ? Tab->GetStr()->Get() : 0; LDisplayString ds(LSysFont, Str); int Width = 12 + ds.X(); LRect t(x, Title.y1 + 2, x + Width - 1, Title.y2 - 1); if (Current == i) { t.Inset(-2, -2); if (Tab->IterateViews().Length() > 0) FromTab(); } if (Tab->View()->GetPos() != t) Tab->View()->SetPos(t); pDC->Colour(L_LIGHT); pDC->Line(t.x1, t.y1+2, t.x1, t.y2); pDC->Set(t.x1+1, t.y1+1); pDC->Line(t.x1+2, t.y1, t.x2-2, t.y1); pDC->Colour(L_MED); pDC->Line(t.x1+1, t.y1+2, t.x1+1, t.y2); pDC->Line(t.x1+2, t.y1+1, t.x2-2, t.y1+1); pDC->Colour(L_LOW); pDC->Line(t.x2-1, t.y1, t.x2-1, t.y2); pDC->Colour(0, 24); pDC->Line(t.x2, t.y1+2, t.x2, t.y2); t.Inset(2, 2); t.y2 += 2; LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); ds.Draw(pDC, t.x1 + ((t.X()-ds.X())/2), t.y1 + ((t.Y()-ds.Y())/2), &t); x += Width + ((Current == i) ? 2 : 1); i++; } // Draw children //LWindow::OnPaint(pDC); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } void CtrlTabs::ToTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // move all our children into the tab losing focus auto CurIt = Cur->IterateViews(); auto ThisIt = IterateViews(); for (auto v: CurIt) { Cur->DelView(v); } for (auto v: ThisIt) { DelView(v); Cur->AddView(v); } } } void CtrlTabs::FromTab() { CtrlTab *Cur = Tabs.ItemAt(Current); if (Cur) { // load all our children from the new tab auto CurIt = Cur->IterateViews(); auto ThisIt = IterateViews(); for (auto v: ThisIt) DelView(v); for (auto v: CurIt) { Cur->DelView(v); AddView(v); } } } void CtrlTabs::OnMouseClick(LMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { // select current tab int i = 0; for (auto Tab: Tabs) { if (Tab->View()->GetPos().Overlap(m.x, m.y) /* && i != Current*/) { ToTab(); Current = i; FromTab(); Dlg->OnSelect(Tab); Invalidate(); break; } i++; } } if (m.IsContextMenu() && Title.Overlap(m.x, m.y)) { auto RClick = new LSubMenu; if (RClick) { bool HasTab = Tabs.ItemAt(Current); RClick->AppendItem("New tab", IDM_NEW, true); RClick->AppendItem("Delete tab", IDM_DELETE, HasTab); RClick->AppendItem("Rename tab", IDM_RENAME, HasTab); RClick->AppendItem("Move tab left", IDM_MOVE_LEFT, HasTab); RClick->AppendItem("Move tab right", IDM_MOVE_RIGHT, HasTab); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, Dlg->Selection.Length()==1); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, true); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_NEW: { CtrlTab *t = new CtrlTab(Dlg, 0); if (t) { char Text[256]; sprintf(Text, "Tab " LPrintfSizeT, Tabs.Length()+1); t->GetStr()->Set(Text); t->SetParent(this); Tabs.Insert(t); } break; } case IDM_DELETE: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { ToTab(); Tabs.Delete(t); DeleteObj(t); FromTab(); } break; } case IDM_RENAME: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { if (!t->GetStr()) t->SetStr(Dlg->CreateSymbol()); auto Input = new LInput(this, t->GetStr()->Get(), "Enter tab name:", "Rename"); Input->SetParent(Dlg); Input->DoModal([t, Input](auto dlg, auto id) { if (id) t->GetStr()->Set(Input->GetStr()); delete dlg; }); } break; } case IDM_MOVE_LEFT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current > 0) { Tabs.Delete(t); Tabs.Insert(t, --Current); } break; } case IDM_MOVE_RIGHT: { CtrlTab *t = Tabs.ItemAt(Current); if (t && Current < Tabs.Length()-1) { Tabs.Delete(t); Tabs.Insert(t, ++Current); } break; } case IDM_COPY_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->CopyText(); } break; } case IDM_PASTE_TEXT: { CtrlTab *t = Tabs.ItemAt(Current); if (t) { t->PasteText(); } break; } } Invalidate(); } } return; } } ResDialogCtrl::OnMouseClick(m); } // List column ListCol::ListCol(ResDialog *dlg, LXmlTag *load, char *s, int Width) : ResDialogCtrl(dlg, Res_Column, load) { if (s && GetStr()) { GetStr()->Set(s); } LRect r(0, 0, Width-1, 18); ResDialogCtrl::SetPos(r); } IMPL_DIALOG_CTRL(ListCol) void ListCol::OnPaint(LSurface *pDC) { } // List control CtrlList::CtrlList(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ListView, load) { DragCol = -1; } CtrlList::~CtrlList() { Empty(); } void CtrlList::ListChildren(List &l, bool Deep) { if (Deep) { for (auto w: Cols) { ResDialogCtrl *c = dynamic_cast(w); if (c) { l.Insert(c); c->ListChildren(l); } } } } void CtrlList::Empty() { for (auto c: Cols) { DeleteObj(c); } Cols.Empty(); } void CtrlList::OnMouseClick(LMouse &m) { if (m.Down()) { if (Title.Overlap(m.x, m.y)) { int x=0; ListCol *c = 0; ssize_t DragOver = -1; for (auto Col: Cols) { if (m.x >= Col->r().x1 && m.x <= Col->r().x2) { if (m.x > Col->r().x2 - 6) { DragOver = Cols.IndexOf(Col); } if (m.x < Col->r().x1 + 6) { DragOver = Cols.IndexOf(Col) - 1; } c = Col; break; } x += Col->r().X(); } if (m.Left()) { if (DragOver >= 0) { DragCol = DragOver; Capture(true); } } else if (m.IsContextMenu()) { auto RClick = new LSubMenu; if (RClick) { bool HasCol = c != 0; RClick->AppendItem("New column", IDM_NEW, true); RClick->AppendItem("Delete column", IDM_DELETE, HasCol); RClick->AppendItem("Rename column", IDM_RENAME, HasCol); RClick->AppendItem("Move column left", IDM_MOVE_LEFT, HasCol); RClick->AppendItem("Move column right", IDM_MOVE_RIGHT, HasCol); RClick->AppendSeparator(); RClick->AppendItem("Copy Text", IDM_COPY_TEXT, HasCol); RClick->AppendItem("Paste Text", IDM_PASTE_TEXT, HasCol); if (GetMouse(m, true)) { switch (RClick->Float(this, m.x, m.y, false)) { case IDM_COPY_TEXT: { if (c && c->GetStr()) { c->GetStr()->CopyText(); } break; } case IDM_PASTE_TEXT: { if (c && c->GetStr()) { c->GetStr()->PasteText(); } break; } case IDM_NEW: { ListCol *c = dynamic_cast(Dlg->CreateCtrl(UI_COLUMN,0)); if (c) { char Text[256]; sprintf(Text, "Col " LPrintfSizeT, Cols.Length()+1); c->GetStr()->Set(Text); Cols.Insert(c); } break; } case IDM_DELETE: { Cols.Delete(c); DeleteObj(c); break; } case IDM_RENAME: { if (c) { auto Input = new LInput(this, c->GetStr()->Get(), "Enter column name:", "Rename"); Input->SetParent(Dlg); Input->DoModal([Input, c](auto dlg, auto id) { if (id) c->GetStr()->Set(Input->GetStr()); delete dlg; }); } break; } case IDM_MOVE_LEFT: { auto Current = Cols.IndexOf(c); if (c && Current > 0) { Cols.Delete(c); Cols.Insert(c, --Current); } break; } case IDM_MOVE_RIGHT: { auto Current = Cols.IndexOf(c); if (c && Current < Cols.Length()-1) { Cols.Delete(c); Cols.Insert(c, ++Current); } break; } } Invalidate(); } DeleteObj(RClick); return; } } return; } } else { if (DragCol >= 0) { Capture(false); DragCol = -1; } } ResDialogCtrl::OnMouseClick(m); } void CtrlList::OnMouseMove(LMouse &m) { if (DragCol >= 0) { int i=0, x=0;; for (auto Col: Cols) { if (i == DragCol) { int Dx = (m.x - x - Title.x1); LRect r = Col->GetPos(); r.x2 = r.x1 + Dx; Col->ResDialogCtrl::SetPos(r); break; } x += Col->r().X(); i++; } Invalidate(); } ResDialogCtrl::OnMouseMove(m); } void CtrlList::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); Title = r; Title.y2 = Title.y1 + 15; Client = r; Client.y1 = Title.y2 + 1; pDC->Colour(L_WORKSPACE); pDC->Rectangle(&Client); int x = Title.x1; for (auto c: Cols) { int Width = c->r().X(); c->r().Set(x, Title.y1, x + Width - 1, Title.y2); LRect r = c->r(); r.x2 = MIN(r.x2, Title.x2); x = r.x2 + 1; if (r.Valid()) { LWideBorder(pDC, r, DefaultRaisedEdge); LSysFont->Fore(L_TEXT); LSysFont->Back(L_MED); LSysFont->Transparent(false); const char *Str = c->GetStr()->Get(); if (!Str) Str = ""; LDisplayString ds(LSysFont, Str); ds.Draw(pDC, r.x1 + 2, r.y1 + ((r.Y()-ds.Y())/2) - 1, &r); } } LRect Client(x, Title.y1, Title.x2, Title.y2); if (Client.Valid()) { LWideBorder(pDC, Client, DefaultRaisedEdge); pDC->Colour(L_MED);; pDC->Rectangle(&Client); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } // Combo box CtrlComboBox::CtrlComboBox(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ComboBox, load) { } IMPL_DIALOG_CTRL(CtrlComboBox) void CtrlComboBox::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl LWideBorder(pDC, r, DefaultSunkenEdge); // Allocate space LRect e = r; e.x2 -= 15; LRect d = r; d.x1 = e.x2 + 1; // Draw edit pDC->Colour(L_WORKSPACE); pDC->Rectangle(&e); // Draw drap down LWideBorder(pDC, d, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&d); pDC->Colour(0, 24); int Size = 4; int cx = (d.X()/2) + d.x1 - 4; int cy = (d.Y()/2) + d.y1 - 3; for (int i=1; i<=Size; i++) { pDC->Line(cx+i, cy+i, cx+(Size*2)-i, cy+i); } // Text char *Text = GetStr()->Get(); LSysFont->Fore(L_TEXT); LSysFont->Transparent(true); if (Text) { LDisplayString ds(LSysFont, Text); ds.Draw(pDC, 4, 4); } // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlScrollBar::CtrlScrollBar(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_ScrollBar, load) { } IMPL_DIALOG_CTRL(CtrlScrollBar) void CtrlScrollBar::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; // Draw the ctrl bool Vertical = r.Y() > r.X(); int ButSize = Vertical ? r.X() : r.Y(); LRect a, b, c; if (Vertical) { a.Set(r.x1, r.y1, r.x2, r.y1 + ButSize); c.Set(r.x1, r.y2 - ButSize, r.x2, r.y2); b.Set(r.x1, a.y2 + 1, r.x2, c.y1 - 1); } else { a.Set(r.x1, r.y1, r.x1 + ButSize, r.y2); c.Set(r.x2 - ButSize, r.y1, r.x2, r.y2); b.Set(a.x2 + 1, r.y1, c.x1 - 1, r.y2); } // Buttons LWideBorder(pDC, a, DefaultRaisedEdge); LWideBorder(pDC, c, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&a); pDC->Rectangle(&c); // Arrows pDC->Colour(0); int x = a.x1 + (a.X()/2) - 2; int y = a.y1 + (a.Y()/2); int i; for (i=0; i<5; i++) { pDC->Line(x+i, y-i, x+i, y+i); } x = c.x1 + (c.X()/2) - 2; y = c.y1 + (c.Y()/2); for (i=0; i<5; i++) { pDC->Line(x+i, y-(4-i), x+i, y+(4-i)); } // Slider pDC->Colour(0xd0d0d0, 24); pDC->Rectangle(&b); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlTree::CtrlTree(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_TreeView, load) { } IMPL_DIALOG_CTRL(CtrlTree) void CtrlTree::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); LSysFont->Colour(L_TEXT, L_WORKSPACE); LSysFont->Transparent(true); LDisplayString ds(LSysFont, "Tree"); ds.Draw(pDC, r.x1 + 3, r.y1 + 3, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlBitmap::CtrlBitmap(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Bitmap, load) { } IMPL_DIALOG_CTRL(CtrlBitmap) void CtrlBitmap::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); pDC->Colour(Rgb24(255, 255, 255), 24); pDC->Rectangle(&r); pDC->Colour(0, 24); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlProgress::CtrlProgress(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Progress, load) { } IMPL_DIALOG_CTRL(CtrlProgress) void CtrlProgress::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); COLOUR Flat = Rgb24(0x7f, 0x7f, 0x7f); COLOUR White = Rgb24(0xff, 0xff, 0xff); int Pos = 60; int x = Pos * r.X() / 100; pDC->Colour(Flat, 24); pDC->Rectangle(r.x1, r.y1, r.x1 + x, r.y2); pDC->Colour(White, 24); pDC->Rectangle(r.x1 + x + 1, r.y1, r.x2, r.y2); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } //////////////////////////////////////////////////////////////////// CtrlCustom::CtrlCustom(ResDialog *dlg, LXmlTag *load) : ResDialogCtrl(dlg, Res_Custom, load) { Control = 0; } IMPL_DIALOG_CTRL(CtrlCustom) CtrlCustom::~CtrlCustom() { DeleteArray(Control); } void CtrlCustom::OnPaint(LSurface *pDC) { LRect r(0, 0, X()-1, Y()-1); Client = r; LWideBorder(pDC, r, DefaultSunkenEdge); COLOUR White = Rgb24(0xff, 0xff, 0xff); pDC->Colour(White, 24); pDC->Rectangle(&r); char s[256] = "Custom: "; if (Control) { strcat(s, Control); } LSysFont->Colour(L_TEXT, L_WORKSPACE); LDisplayString ds(LSysFont, s); ds.Draw(pDC, r.x1+2, r.y1+1, &r); // Draw any rubber band ResDialogCtrl::OnPaint(pDC); } #define VAL_Control "Ctrl" bool CtrlCustom::GetFields(FieldTree &Fields) { bool Status = ResDialogCtrl::GetFields(Fields); if (Status) { Fields.Insert(this, DATA_STR, 320, VAL_Control, "Control", 1); } return Status; } bool CtrlCustom::Serialize(FieldTree &Fields) { bool Status = ResDialogCtrl::Serialize(Fields); if (Status) { Fields.Serialize(this, VAL_Control, Control); } return Status; } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// ResStringGroup *ResDialog::Symbols = 0; int ResDialog::SymbolRefs = 0; bool ResDialog::CreateSymbols = true; void ResDialog::AddLanguage(const char *Id) { if (Symbols) { Symbols->AppendLanguage(Id); } } void ResDialogCtrl::EnumCtrls(List &Ctrls) { Ctrls.Insert(this); for (LViewI *c: View()->IterateViews()) { ResDialogCtrl *dc = dynamic_cast(c); LAssert(dc); dc->EnumCtrls(Ctrls); } } void ResDialog::EnumCtrls(List &Ctrls) { for (auto ci: Children) { ResDialogCtrl *c = dynamic_cast(ci); if (c) c->EnumCtrls(Ctrls); } } int GooberCaps[] = { RESIZE_X1 | RESIZE_Y1, RESIZE_Y1, RESIZE_X2 | RESIZE_Y1, RESIZE_X2, RESIZE_X2 | RESIZE_Y2, RESIZE_Y2, RESIZE_X1 | RESIZE_Y2, RESIZE_X1}; ResDialog::ResDialog(AppWnd *w, int type) : Resource(w, type) { // Maintain a string group just for our dialog // defines if (!Symbols && w) { // First check to see of the symbols have been loaded from a file // and we don't have a pointer to it yet Symbols = App()->GetDialogSymbols(); if (!Symbols) { // else we need to create the symbols object Symbols = new ResStringGroup(w); if (Symbols) { Symbols->Name(StrDialogSymbols); w->InsertObject(TYPE_STRING, Symbols); } } if (Symbols) { Symbols->SystemObject(true); } } SymbolRefs++; // Init dialog resource Ui = 0; DlgPos.ZOff(500-1, 400-1); DragGoober = -1; DragX = 0; DragY = 0; DragCtrl = 0; } ResDialog::~ResDialog() { // Decrement and delete the shared string group SymbolRefs--; if (SymbolRefs < 1) { App()->DelObject(Symbols); Symbols = 0; } // Delete our Ui DeleteObj(Ui); } void ResDialog::OnShowLanguages() { // Current language changed. OnSelect(Selection[0]); Invalidate(); OnLanguageChange(); } void ResDialog::OnChildrenChanged(LViewI *Wnd, bool Attaching) { printf("ResDialog::OnChildrenChanged %p, %i\n", Wnd, Attaching); } const char *ResDialog::Name() { LViewI *v = Children[0]; ResDialogCtrl *Ctrl = dynamic_cast(v); if (!Ctrl) { static char msg[256]; sprintf_s(msg, sizeof(msg), "#no_ctrl=%p,children=%i", v, (int)Children.Length()); return msg; } if (!Ctrl->GetStr()) return "#no_str"; if (!Ctrl->GetStr()->GetDefine()) return "#no_defined"; return Ctrl->GetStr()->GetDefine(); } bool ResDialog::Name(const char *n) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl && Ctrl->GetStr()) { Ctrl->GetStr()->SetDefine((n)?n:""); return Ctrl->GetStr()->GetDefine() != 0; } return false; } char *ResDialog::StringFromRef(int Ref) { if (Symbols) { ResString *Str = Symbols->FindRef(Ref); if (Str) { return Str->Get(); } } return 0; } bool ResDialog::Res_GetProperties(ResObject *Obj, LDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::ObjToStore); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } LDom *ResDialog::Res_GetDom(ResObject *Obj) { return dynamic_cast(Obj); } bool ResDialog::Res_SetProperties(ResObject *Obj, LDom *Props) { ResDialogCtrl *Ctrl = dynamic_cast(Obj); if (Ctrl && Props) { int Next = -1000; FieldTree t(Next, false); t.SetStore(Props); t.SetMode(FieldTree::StoreToObj); Ctrl->GetFields(t); Ctrl->Serialize(t); return true; } return false; } ResObject *ResDialog::CreateObject(LXmlTag *Tag, ResObject *Parent) { return dynamic_cast(CreateCtrl(Tag)); } void ResDialog::Res_SetPos(ResObject *Obj, int x1, int y1, int x2, int y2) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { LRect r(x1, y1, x2, y2); Ctrl->SetPos(r); } } } void ResDialog::Res_SetPos(ResObject *Obj, char *s) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Ctrl->ReadPos(s); } } } LRect ResDialog::Res_GetPos(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); LAssert(Ctrl); if (Ctrl) { return Ctrl->View()->GetPos(); } } return LRect(0, 0, 0, 0); } int ResDialog::Res_GetStrRef(ResObject *Obj) { if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { return Ctrl->GetStr()->GetRef(); } } return -1; } bool ResDialog::Res_SetStrRef(ResObject *Obj, int Ref, ResReadCtx *Ctx) { ResDialogCtrl *Ctrl = 0; if (!Obj || !Symbols) return false; Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (!Ctrl) return false; Ctrl->StrFromRef(Ref); LAssert(Ctrl && Ctrl->GetStr()); return Ctrl->GetStr() != 0; } void ResDialog::Res_Attach(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); ResDialogCtrl *Par = dynamic_cast((ResDialogCtrl*)Parent); if (Ctrl && Par) { Par->AttachCtrl(Ctrl); } } } bool ResDialog::Res_GetChildren(ResObject *Obj, List *l, bool Deep) { bool Status = false; if (Obj) { ResDialogCtrl *Ctrl = dynamic_cast((ResDialogCtrl*)Obj); if (Ctrl) { Status = true; List Child; Ctrl->ListChildren(Child, Deep); for (auto o: Child) { l->Insert(o); } } } return Status; } void ResDialog::Res_Append(ResObject *Obj, ResObject *Parent) { if (Obj && Parent) { CtrlTabs *Tabs = dynamic_cast(Obj); CtrlTab *Tab = dynamic_cast(Parent); if (Tabs && Tab) { Tab->SetParent(Tabs); Tabs->Tabs.Insert(Tab); if (Tabs->Tabs.Length() == 1) { Tabs->FromTab(); } return; } CtrlList *Lst = dynamic_cast(Obj); ListCol *Col = dynamic_cast(Parent); if (Lst && Col) { Lst->Cols.Insert(Col); return; } } } bool ResDialog::Res_GetItems(ResObject *Obj, List *l) { if (Obj && l) { CtrlTabs *Tabs = dynamic_cast(Obj); if (Tabs) { for (auto Tab: Tabs->Tabs) { l->Insert(Tab); } return true; } CtrlList *Lst = dynamic_cast(Obj); if (Lst) { for (auto Col: Lst->Cols) { l->Insert(Col); } return true; } } return false; } void ResDialog::Create(LXmlTag *load, SerialiseContext *Ctx) { CtrlDlg *Dlg = new CtrlDlg(this, load); if (Dlg) { LRect r = DlgPos; r.Offset(GOOBER_BORDER, GOOBER_BORDER); Children.Insert(Dlg); Dlg->SetParent(this); if (load) { if (Ctx) Read(load, *Ctx); else LAssert(0); } else { Dlg->ResDialogCtrl::SetPos(r); if (Dlg->GetStr()) Dlg->GetStr()->Set("Dialog"); } } } void ResDialog::Delete() { // Deselect the dialog ctrl OnDeselect(dynamic_cast(Children[0])); // Delete selected controls ResDialogCtrl *c; while ((c = Selection[0])) { c->View()->Detach(); DeleteObj(c); } // Repaint LView::Invalidate(); } bool IsChild(ResDialogCtrl *Parent, ResDialogCtrl *Child) { if (Parent && Child) { for ( ResDialogCtrl *c = Child->ParentCtrl(); c; c = c->ParentCtrl()) { if (c == Parent) { return true; } } } return false; } void GetTopCtrls(List &Top, List &Selection) { // all children will automatically be cut as well for (auto c: Selection) { // is c a child of an item already in Top? bool Ignore = false; for (auto p: Top) { if (IsChild(p, c)) { Ignore = true; break; } } if (!Ignore) { // is not a child Top.Insert(c); } } } void ResDialog::Copy(bool Delete) { bool Status = false; // Deselect the dialog... can't cut that OnDeselect(dynamic_cast(Children[0])); // Get top level list List Top; GetTopCtrls(Top, Selection); // ok we have a top level list of ctrls // write them to the file List All; LXmlTag *Root = new LXmlTag("Resources"); if (Root) { if (Delete) { // remove selection from UI AppWindow->OnObjSelect(0); } // write the string resources first for (auto c: Top) { All.Insert(c); c->ListChildren(All, true); } // write the strings out at the top of the block // so that we can reference them from the objects // below. SerialiseContext Ctx; for (auto c: All) { // Write the string out LXmlTag *t = new LXmlTag; if (t && c->GetStr()->Write(t, Ctx)) { Root->InsertTag(t); } else { DeleteObj(t); } } // write the objects themselves for (auto c: Top) { LXmlTag *t = new LXmlTag; if (t && Res_Write(c, t)) { Root->InsertTag(t); } else { DeleteObj(t); } } // Read the file in and copy to the clipboard LStringPipe Xml; LXmlTree Tree; if (Tree.Write(Root, &Xml)) { char *s = Xml.NewStr(); { LClipBoard Clip(Ui); char16 *w = Utf8ToWide(s); Clip.TextW(w); Status = Clip.Text(s, false); DeleteObj(w); } DeleteArray(s); } DeleteObj(Root); if (Delete && Status) { // delete them ResDialogCtrl *c; while ( Selection.Length() && (c = Selection[0])) { c->View()->Detach(); Selection.Delete(c); DeleteObj(c); } } // Repaint LView::Invalidate(); } } class StringId { public: ResString *Str; int OldRef; int NewRef; }; void RemapAllRefs(LXmlTag *t, List &Strs) { char *RefStr; if ((RefStr = t->GetAttr("Ref"))) { int r = atoi(RefStr); for (auto i: Strs) { if (i->OldRef == r) { // this string ref is to be remapped char Buf[32]; sprintf(Buf, "%i", i->NewRef); t->SetAttr("Ref", Buf); // delete the string ref map // it's not needed anymore Strs.Delete(i); DeleteObj(i); // leave the loop RefStr = 0; break; } } if (RefStr) { // if this assert failes then no map for this // string was found. every incomming string needs // to be remapped LAssert(0); } } for (auto c: t->Children) { RemapAllRefs(c, Strs); } } void ResDialog::Paste() { // Get the clipboard data char *Mem = 0; char *Data = 0; { LClipBoard Clip(Ui); char16 *w = Clip.TextW(); if (w) Data = Mem = WideToUtf8(w); else Data = Clip.Text(); } if (Data) { ResDialogCtrl *Container = 0; // Find a container to plonk the controls in ResDialogCtrl *c = Selection[0]; if (c && c->IsContainer()) { Container = c; } if (!Container) { // Otherwise just use the dialog as the container Container = dynamic_cast(Children[0]); } if (Container) { // remap list List Strings; int NextRef = 0; // Deselect everything OnSelect(NULL); // Parse the data List NewStrs; LXmlTree Tree; LStringPipe p; p.Push(Data); // Create the new controls, strings first // that way we can setup the remapping properly to avoid // string ref duplicates LXmlTag Root; if (Tree.Read(&Root, &p, 0)) { for (auto t: Root.Children) { if (t->IsTag("string")) { // string tag LAssert(Symbols); ResString *Str = Symbols->CreateStr(); SerialiseContext Ctx; if (Str && Str->Read(t, Ctx)) { // setup remap object, so that we can make all the strings // unique StringId *Id = new StringId; LAssert(Id); Id->Str = Str; Id->OldRef = Str->GetRef(); NextRef = Str->SetRef(Id->NewRef = AppWindow->GetUniqueStrRef(NextRef + 1)); Strings.Insert(Id); // insert out new string NewStrs.Insert(Str); } else { break; } } else { // object tag // all strings should have been processed by the time // we get in here. We remap the incomming string refs to // unique values so that we don't get conflicts later. RemapAllRefs(t, Strings); } } // load all the objects now List NewCtrls; for (auto t: Root.Children) { if (!t->IsTag("string")) { // object (not string) CreateSymbols = false; ResDialogCtrl *Ctrl = dynamic_cast(CreateCtrl(t)); CreateSymbols = true; ResReadCtx Ctx; if (Ctrl && Res_Read(Ctrl, t, Ctx)) { NewCtrls.Insert(Ctrl); } else { break; } } } // calculate the new control set's dimensions so we // can paste them in at the top of the container auto It = NewCtrls.begin(); ResDialogCtrl *c = *It; if (c) { LRect All = c->View()->GetPos(); while ((c = *(++It))) { All.Union(&c->View()->GetPos()); } // now paste in the controls for (auto c: NewCtrls) { LRect *Preference = Container->GetPasteArea(); LRect p = c->View()->GetPos(); p.Offset(-All.x1, -All.y1); p.Offset(Preference ? Preference->x1 : Container->Client.x1 + GRID_X, Preference ? Preference->y1 : Container->Client.y1 + GRID_Y); c->SetPos(p); Container->AttachCtrl(c, &c->View()->GetPos()); OnSelect(c, false); } // reset parent size to fit LRect cp = Container->View()->GetPos(); Container->SetPos(cp, true); } // Deduplicate all these new strings for (auto s: NewStrs) { s->UnDuplicate(); } } // Repaint LView::Invalidate(); } } DeleteArray(Mem); } void ResDialog::SnapPoint(LPoint *p, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (p && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; LView *Parent = dynamic_cast(Ctrl); if (From) { for (LViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } } p->x += Ox; p->y += Oy; p->x = ((p->x + (GRID_X / 2)) / GRID_X * GRID_X); p->y = ((p->y + (GRID_X / 2)) / GRID_X * GRID_X); p->x -= Ox; p->y -= Oy; } } void ResDialog::SnapRect(LRect *r, ResDialogCtrl *From) { ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (r && Ctrl) { int Ox = 0; // -Ctrl->Client.x1; int Oy = 0; // -Ctrl->Client.y1; LView *Parent = dynamic_cast(Ctrl); for (LViewI *w = From->View(); w && w != Parent; w = w->GetParent()) { Ox += w->GetPos().x1; Oy += w->GetPos().y1; } r->Normal(); r->Offset(Ox, Oy); r->x1 = ((r->x1 + (GRID_X / 2)) / GRID_X * GRID_X); r->y1 = ((r->y1 + (GRID_X / 2)) / GRID_X * GRID_X); r->x2 = ((r->x2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->y2 = ((r->y2 + (GRID_X / 2)) / GRID_X * GRID_X) - 1; r->Offset(-Ox, -Oy); } } bool ResDialog::IsDraging() { return DragGoober >= 0; } int ResDialog::CurrentTool() { return (Ui) ? Ui->CurrentTool() : 0; } void ResDialog::MoveSelection(int Dx, int Dy) { auto It = Selection.begin(); ResDialogCtrl *w = *It; if (!w) return; ResDialogCtrl *Parent = w->ParentCtrl(); if (!Parent) return; // find dimensions of group LRect All = w->View()->GetPos(); for (; w; w = *(++It)) All.Union(&w->View()->GetPos()); // limit the move to the top-left corner of the parent's client LRect ParentClient = Parent->Client; if (dynamic_cast(Parent)) ParentClient.ZOff(-ParentClient.x1, -ParentClient.y1); if (All.x1 + Dx < ParentClient.x1) Dx = ParentClient.x1 - All.x1; if (All.y1 + Dy < ParentClient.y1) Dy = ParentClient.y1 - All.y1; // move the ctrls LRegion Update; for (auto w: Selection) { LRect Old = w->View()->GetPos(); LRect New = Old; New.Offset(Dx, Dy); // optionally limit the move to the containers bounds LRect *b = w->ParentCtrl()->GetChildArea(w); if (b) { if (New.x1 < b->x1) New.Offset(b->x1 - New.x1, 0); else if (New.x2 > b->x2) New.Offset(b->x2 - New.x2, 0); if (New.y1 < b->y1) New.Offset(0, b->y1 - New.y1); else if (New.y2 > b->y2) New.Offset(0, b->y2 - New.y2); } LRect Up = w->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); w->SetPos(New, false); Up = w->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } void ResDialog::SelectCtrl(ResDialogCtrl *c) { Selection.Empty(); if (c) { bool IsMine = false; for (LViewI *p = c->View(); p; p = p->GetParent()) { if ((LViewI*)this == p) { IsMine = true; break; } } if (IsMine) { Selection.Insert(c); // int TabIdx = -1; ResDialogCtrl *Prev = 0; for (LViewI *p = c->View()->GetParent(); p; p = p->GetParent()) { ResDialogCtrl *c = dynamic_cast(p); if (c) { c->ShowMe(Prev); Prev = c; } else break; } } else LgiTrace("%s:%i - Ctrl doesn't belong to me.\n", __FILE__, __LINE__); } else LgiTrace("Selecting '0'\n"); Invalidate(); if (AppWindow) AppWindow->OnObjSelect(c); } void ResDialog::SelectNone() { Selection.Empty(); Invalidate(); if (AppWindow) { AppWindow->OnObjSelect(0); } } void ResDialog::SelectRect(ResDialogCtrl *Parent, LRect *r, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Parent && r) { for (LViewI *c: Parent->View()->IterateViews()) { if (c->GetPos().Overlap(r)) { ResDialogCtrl *Ctrl = dynamic_cast(c); if (Ctrl) { if (Selection.IndexOf(Ctrl) >= 0) { Selection.Delete(Ctrl); } else { Selection.Insert(Ctrl); } } } } } Invalidate(); if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection[0] : 0); } } bool ResDialog::IsSelected(ResDialogCtrl *Ctrl) { return Selection.IndexOf(Ctrl) >= 0; } void ResDialog::OnSelect(ResDialogCtrl *Wnd, bool ClearPrev) { if (ClearPrev) { Selection.Empty(); } if (Wnd) { ResDialogCtrl *Ctrl = dynamic_cast(Wnd); if (Ctrl) { if (!Selection.HasItem(Ctrl)) { Selection.Insert(Ctrl); } if (Ui) { Ui->SelectTool(UI_CURSOR); } Invalidate(); } if (AppWindow) { AppWindow->OnObjSelect((Selection.Length() == 1) ? Selection[0] : 0); } } } void ResDialog::OnDeselect(ResDialogCtrl *Wnd) { if (Selection.HasItem(Wnd)) { Selection.Delete(Wnd); AppWindow->OnObjSelect(0); } } ResDialogCtrl *ResDialog::CreateCtrl(LXmlTag *t) { if (t) { for (LgiObjectName *o = NameMap; o->Type; o++) { if (t->IsTag(o->ResourceName)) { return CreateCtrl(o->Type, t); } } } return 0; } ResDialogCtrl *ResDialog::CreateCtrl(int Tool, LXmlTag *load) { ResDialogCtrl *Ctrl = 0; switch (Tool) { case UI_DIALOG: { Ctrl = new CtrlDlg(this, load); break; } case UI_TABLE: { Ctrl = new CtrlTable(this, load); break; } case UI_TEXT: { Ctrl = new CtrlText(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Some text"); } break; } case UI_EDITBOX: { Ctrl = new CtrlEditbox(this, load); break; } case UI_CHECKBOX: { Ctrl = new CtrlCheckbox(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Checkbox"); } break; } case UI_BUTTON: { Ctrl = new CtrlButton(this, load); if (Ctrl && Ctrl->GetStr() && !load) { static int i = 1; char Text[256]; sprintf(Text, "Button %i", i++); Ctrl->GetStr()->Set(Text); } break; } case UI_GROUP: { Ctrl = new CtrlGroup(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Text"); } break; } case UI_RADIO: { Ctrl = new CtrlRadio(this, load); if (Ctrl && Ctrl->GetStr() && !load) { Ctrl->GetStr()->Set("Radio"); } break; } case UI_TAB: { Ctrl = new CtrlTab(this, load); break; } case UI_TABS: { Ctrl = new CtrlTabs(this, load); break; } case UI_LIST: { Ctrl = new CtrlList(this, load); break; } case UI_COLUMN: { Ctrl = new ListCol(this, load); break; } case UI_COMBO: { Ctrl = new CtrlComboBox(this, load); break; } case UI_TREE: { Ctrl = new CtrlTree(this, load); break; } case UI_BITMAP: { Ctrl = new CtrlBitmap(this, load); break; } case UI_SCROLL_BAR: { Ctrl = new CtrlScrollBar(this, load); break; } case UI_PROGRESS: { Ctrl = new CtrlProgress(this, load); break; } case UI_CUSTOM: { Ctrl = new CtrlCustom(this, load); break; } case UI_CONTROL_TREE: { Ctrl = new CtrlControlTree(this, load); break; } default: { LAssert(!"No control factory handler."); break; } } if (Ctrl && Ctrl->GetStr()) { Ctrl->GetStr()->UpdateWnd = Ctrl->View(); } return Ctrl; } LView *ResDialog::CreateUI() { return Ui = new ResDialogUi(this); } void ResDialog::DrawSelection(LSurface *pDC) { if (Selection.Length() == 0) return; // Draw selection for (auto Ctrl: Selection) { LRect r = Ctrl->AbsPos(); LColour s(255, 0, 0); LColour c = GetParent()->Focus() ? s : s.Mix(LColour(L_MED), 0.4); DrawGoobers(pDC, r, Ctrl->Goobers, c, Ctrl->OverGoober); } } #ifdef WINDOWS #define USE_MEM_DC 1 #else #define USE_MEM_DC 0 #endif void ResDialog::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if USE_MEM_DC LDoubleBuffer DblBuf(pDC); #endif LView::_Paint(pDC, Offset, Update); if (GetParent()) { #ifndef WIN32 LRect p = GetPos(); if (Offset) p.Offset(Offset); pDC->SetClient(&p); #endif DrawSelection(pDC); #ifndef WIN32 pDC->SetClient(NULL); #endif if (DebugOverlay) { pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, DebugOverlay); } } } void ResDialog::OnPaint(LSurface *pDC) { pDC->Colour(L_WORKSPACE); pDC->Rectangle(); ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (!Ctrl) return; } void ResDialog::OnLanguageChange() { if (Ui && Ui->StatusInfo) { LLanguage *l = Symbols->GetLanguage(App()->GetCurLang()->Id); if (l) { char Str[256]; sprintf(Str, "Current Language: %s (Id: %s)", l->Name, l->Id); Ui->StatusInfo->Name(Str); } } } bool ResDialog::OnKey(LKey &k) { if (k.Ctrl()) { switch (k.c16) { case LK_UP: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx > 0) { LLanguage *l = Symbols->GetLanguage(Idx - 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } case LK_DOWN: { if (k.Down()) { int Idx = Symbols->GetLangIdx(App()->GetCurLang()->Id); if (Idx < Symbols->GetLanguages() - 1) { LLanguage *l = Symbols->GetLanguage(Idx + 1); if (l) { App()->SetCurLang(l); } } Invalidate(); OnLanguageChange(); } return true; break; } } } return false; } void ResDialog::OnMouseClick(LMouse &m) { if (m.Down()) { if (GetParent()) GetParent()->Focus(true); if (m.Left()) { DragGoober = -1; for (auto c: Selection) { for (int i=0; i<8; i++) { if (c->Goobers[i].Overlap(m.x, m.y)) { DragGoober = i; DragCtrl = c; // LgiTrace("IN goober[%i]=%s %i,%i\n", i, c->Goobers[i].GetStr(), m.x, m.y); break; } else { // LgiTrace("goober[%i]=%s %i,%i\n", i, c->Goobers[i].GetStr(), m.x, m.y); } } } if (DragGoober >= 0) { DragRgn = DragCtrl->View()->GetPos(); DragX = DragY = 0; int Cap = GooberCaps[DragGoober]; // Lock the dialog to the top left corner if (dynamic_cast(DragCtrl)) { Cap &= ~(RESIZE_X1|RESIZE_Y1); } if (!Cap) { // Can't move the object anyway so abort the drag DragGoober = -1; } else { // Allow movement along the appropriate edge if (Cap & RESIZE_X1) { DragX = &DragRgn.x1; } if (Cap & RESIZE_Y1) { DragY = &DragRgn.y1; } if (Cap & RESIZE_X2) { DragX = &DragRgn.x2; } if (Cap & RESIZE_Y2) { DragY = &DragRgn.y2; } // Remember the offset to the mouse from the egdes if (DragX) DragOx = m.x - *DragX; if (DragY) DragOy = m.y - *DragY; Capture(true); } } } } else { if (DragGoober >= 0) { Capture(false); DragGoober = -1; DragX = 0; DragY = 0; } } } void ResDialog::OnMouseMove(LMouse &m) { // This code hilights the goober when the mouse is over it. for (auto c: Selection) { int Old = c->OverGoober; c->OverGoober = -1; for (int i=0; i<8; i++) { if (c->Goobers[i].Overlap(m.x, m.y)) { c->OverGoober = i; break; } } if (c->OverGoober != Old) Invalidate(); } if (DragGoober >= 0) { if (DragX) *DragX = m.x - DragOx; if (DragY) *DragY = m.y - DragOy; // DragRgn in real coords LRect Old = DragCtrl->View()->GetPos(); LRect New = DragRgn; auto It = IterateViews(); if (DragCtrl->View() != It[0]) SnapRect(&New, DragCtrl->ParentCtrl()); // New now in snapped coords // If that means the dragging control changes then if (New != Old) { LRegion Update; // change everyone else by the same amount for (auto c: Selection) { LRect OldPos = c->View()->GetPos(); LRect NewPos = OldPos; NewPos.x1 += New.x1 - Old.x1; NewPos.y1 += New.y1 - Old.y1; NewPos.x2 += New.x2 - Old.x2; NewPos.y2 += New.y2 - Old.y2; LRect Up = c->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); c->SetPos(NewPos); Up = c->AbsPos(); Up.Inset(-GOOBER_BORDER, -GOOBER_BORDER); Update.Union(&Up); } Invalidate(&Update); } } } bool ResDialog::Test(ErrorCollection *e) { return true; } bool ResDialog::Read(LXmlTag *t, SerialiseContext &Ctx) { bool Status = false; // turn symbol creation off so that ctrls find their // symbol via Id matching instead of creating their own CreateSymbols = false; ResDialogCtrl *Dlg = dynamic_cast(Children[0]); if (Dlg) { // Load the resource ResReadCtx Ctx; Status = Res_Read(Dlg, t, Ctx); Item->Update(); } CreateSymbols = true; return Status; } void ResDialog::CleanSymbols() { // removed unreferenced dialog strings if (Symbols) { Symbols->RemoveUnReferenced(); } // re-ID duplicate entries ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl) { // list all the entries List l; Ctrl->ListChildren(l); l.Insert(Ctrl); // insert the dialog too // remove duplicate string entries for (auto c: l) { LAssert(c->GetStr()); c->GetStr()->UnDuplicate(); } } // sort list (cause I need to read the file myself) if (Symbols) { Symbols->Sort(); } } bool ResDialog::Write(LXmlTag *t, SerialiseContext &Ctx) { bool Status = false; ResDialogCtrl *Ctrl = dynamic_cast(Children[0]); if (Ctrl) { // duplicates symbols should have been removed before the // strings were written out // so we don't have to do it here. // write the resources out if (Ctrl) { Status = Res_Write(Ctrl, t); } } else LgiTrace("%s:%i - Not a ResDialogCtrl.\n", _FL); return Status; } void ResDialog::OnRightClick(LSubMenu *RClick) { if (RClick) { if (Enabled()) { RClick->AppendSeparator(); if (Type() > 0) { RClick->AppendItem("Dump to C++", IDM_DUMP, true); auto Export = RClick->AppendSub("Export to..."); if (Export) { Export->AppendItem("Lgi File", IDM_EXPORT, true); Export->AppendItem("Win32 Resource Script", IDM_EXPORT_WIN32, false); } } } } } const char *TypeOfRes(ResDialogCtrl *Ctrl) { // the default return "LWindow"; } const char *TextOfCtrl(ResDialogCtrl *Ctrl) { static char Buf[256]; switch (Ctrl->GetType()) { // Has text case UI_TEXT: case UI_EDITBOX: case UI_CHECKBOX: case UI_BUTTON: case UI_GROUP: case UI_RADIO: case UI_COMBO: { char *s = Ctrl->GetStr()->Get(); sprintf(Buf, ", \"%s\"", s?s:""); return Buf; } // Not processed case UI_COLUMN: case UI_TAB: { LAssert(0); break; } // No text... case UI_BITMAP: case UI_PROGRESS: case UI_TABS: case UI_LIST: case UI_TREE: case UI_SCROLL_BAR: { break; } } return ""; } void OutputCtrl(LStringPipe &Def, LStringPipe &Var, LStringPipe &Inst, ResDialogCtrl *Ctrl, ResDialogCtrl *Parent, int &Index) { char Str[256]; const char *Type = "LView"; for (LgiObjectName *on=NameMap; on->Type; on++) { if (Ctrl->GetType() == on->Type) { Type = on->ObjectName; break; } } if (stricmp(Type, "LDialog")) { if (ValidStr(Ctrl->GetStr()->GetDefine()) && stricmp(Ctrl->GetStr()->GetDefine(), "IDOK") && stricmp(Ctrl->GetStr()->GetDefine(), "IDCANCEL") && stricmp(Ctrl->GetStr()->GetDefine(), "IDC_STATIC")) { char Tab[8]; auto Tabs = (32 - strlen(Ctrl->GetStr()->GetDefine()) - 1) / 4; memset(Tab, '\t', Tabs); Tab[Tabs] = 0; sprintf(Str, "#define %s%s%i\n", Ctrl->GetStr()->GetDefine(), Tab, 1000 + Index); Def.Push(Str); } sprintf(Str, "\t%s *Ctrl%i;\n", Type, Index); Var.Push(Str); sprintf(Str, "\tChildren.Insert(Ctrl%i = new %s(%s, %i, %i, %i, %i%s));\n", Index, Type, Ctrl->GetStr()->GetDefine(), Ctrl->View()->GetPos().x1 - 3, Ctrl->View()->GetPos().y1 - 17, Ctrl->View()->X(), Ctrl->View()->Y(), TextOfCtrl(Ctrl)); Inst.Push(Str); CtrlList *List = dynamic_cast(Ctrl); if (List) { // output columns for (auto c: List->Cols) { sprintf(Str, "\tCtrl%i->AddColumn(\"%s\", %i);\n", Index, c->GetStr()->Get(), c->X()); Inst.Push(Str); } } Index++; } for (auto c: Ctrl->View()->IterateViews()) OutputCtrl(Def, Var, Inst, dynamic_cast(c), Ctrl, Index); } void ResDialog::OnCommand(int Cmd) { switch (Cmd) { case IDM_DUMP: { LStringPipe Def, Var, Inst; LStringPipe Buf; char Str[256]; ResDialogCtrl *Dlg = dynamic_cast(Children[0]); if (Dlg) { // List controls int i=0; OutputCtrl(Def, Var, Inst, Dlg, 0, i); // #define's char *Defs = Def.NewStr(); if (Defs) { Buf.Push(Defs); Def.Empty(); } // Class def Buf.Push( "\nclass Dlg : public LDialog\n" "{\n"); // Variables char *Vars = Var.NewStr(); if (Vars) { Buf.Push(Vars); Var.Empty(); } // Member functions Buf.Push( "\n" "public:\n" "\tDlg(LView *Parent);\n" "\t~Dlg();\n" "\n" "\tint OnNotify(LViewI *Ctrl, int Flags);\n" "};\n" "\n"); // Class impl Buf.Push( "Dlg::Dlg(LView *Parent)\n" "{\n" "\tSetParent(Parent);\n"); sprintf(Str, "\tName(\"%s\");\n" "\tGRegion r(0, 0, %i, %i);\n" "\tSetPos(r);\n", Dlg->GetStr()->Get(), Dlg->View()->X(), Dlg->View()->Y()); Buf.Push(Str); Buf.Push("\tMoveToCenter();\n\n"); // Ctrl instancing char *NewCtrls = Inst.NewStr(); if (NewCtrls) { Buf.Push(NewCtrls); Inst.Empty(); } Buf.Push( "}\n" "\n"); // Destructor Buf.Push( "Dlg::~Dlg()\n" "{\n" "}\n" "\n"); // ::OnNotify Buf.Push( "int Dlg::OnNotify(LViewI *Ctrl, int Flags)\n" "{\n" "\tswitch (Ctrl->GetId())\n" "\t{\n" "\t\tcase IDOK:\n" "\t\t{\n" "\t\t\t// Do something here\n" "\t\t\t// fall thru\n" "\t\t}\n" "\t\tcase IDCANCEL:\n" "\t\t{\n" "\t\t\tEndModal(Ctrl->GetId());\n" "\t\t\tbreak;\n" "\t\t}\n" "\t}\n" "\n" "\treturn 0;\n" "}\n"); // Output to clipboard char *Text = Buf.NewStr(); if (Text) { LClipBoard Clip(Ui); Clip.Text(Text); DeleteArray(Text); } } break; } case IDM_EXPORT: { auto Select = new LFileSelect(AppWindow); Select->Type("Text", "*.txt"); Select->Save([&](auto dlg, auto status) { if (status) { LFile F; if (F.Open(Select->Name(), O_WRITE)) { F.SetSize(0); // Serialize(F, true); } else { LgiMsg(AppWindow, "Couldn't open file for writing."); } } delete dlg; }); break; } case IDM_EXPORT_WIN32: { break; } } } int ResDialog::OnCommand(int Cmd, int Event, OsView hWnd) { switch (Cmd) { /* case IDM_SET_LANG: { Symbols->SetCurrent(); OnSelect(Selection.First()); Invalidate(); OnLanguageChange(); break; } */ case IDM_TAB_ORDER: { ResDialogCtrl *Top = 0; if (Selection.Length() == 1 && Selection[0]->IsContainer()) { Top = Selection[0]; } if (!Top) { Top = dynamic_cast(Children[0]); } if (Top) { auto Dlg = new TabOrder(this, Top); Dlg->DoModal([](auto dlg, auto id) { delete dlg; }); } break; } } return 0; } ResString *ResDialog::CreateSymbol() { return (Symbols) ? Symbols->CreateStr() : 0; } //////////////////////////////////////////////////////////////////// ResDialogUi::ResDialogUi(ResDialog *Res) { Dialog = Res; Tools = 0; Status = 0; StatusInfo = 0; Name("ResDialogUi"); if (Res) { Res->OnSelect(Res->Selection[0]); ShortCutView *scv = Res->App()->GetShortCutView(); if (scv) scv->OnDialogChange(Res); } } ResDialogUi::~ResDialogUi() { if (Dialog) { ShortCutView *scv = Dialog->App()->GetShortCutView(); if (scv) scv->OnDialogChange(NULL); Dialog->Ui = 0; } } void ResDialogUi::OnPaint(LSurface *pDC) { LRegion Client(0, 0, X()-1, Y()-1); for (auto w: Children) { LRect r = w->GetPos(); Client.Subtract(&r); } pDC->Colour(L_MED); for (LRect *r = Client.First(); r; r = Client.Next()) { pDC->Rectangle(r); } } void ResDialogUi::PourAll() { LRegion Client(GetClient()); LRegion Update; for (auto v: Children) { LRect OldPos = v->GetPos(); Update.Union(&OldPos); if (v->Pour(Client)) { if (!v->Visible()) { v->Visible(true); } if (OldPos != v->GetPos()) { // position has changed update... v->Invalidate(); } Client.Subtract(&v->GetPos()); Update.Subtract(&v->GetPos()); } else { // make the view not visible v->Visible(false); } } for (int i=0; iSetBitmap(FileName, 16, 16)) { Tools->Attach(this); Tools->AppendButton("Cursor", 0, TBT_RADIO); for (LgiObjectName *o=NameMap; o->Type; o++) { if (o->ToolbarBtn) { Tools->AppendButton(o->ObjectName, o->Type, TBT_RADIO); } } Tools->AppendSeparator(); // Tools->AppendButton("Change language", IDM_SET_LANG, TBT_PUSH); Tools->AppendButton("Tab Order", IDM_TAB_ORDER, TBT_PUSH, true, 17); } else { DeleteObj(Tools); } } Status = new LStatusBar; if (Status) { Status->Attach(this); StatusInfo = Status->AppendPane("", 2); } ResFrame *Frame = new ResFrame(Dialog); if (Frame) { Frame->Attach(this); } PourAll(); } int ResDialogUi::CurrentTool() { if (Tools) { auto It = Tools->IterateViews(); for (size_t i=0; i(It[i]); if (But && But->Value()) return But->GetId(); } } return -1; } void ResDialogUi::SelectTool(int i) { if (Tools) { auto It = Tools->IterateViews(); LViewI *w = It[i]; if (w) { LToolButton *But = dynamic_cast(w); if (But) But->Value(true); } } } LMessage::Result ResDialogUi::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COMMAND: { Dialog->OnCommand(Msg->A()&0xffff, (int)(Msg->A()>>16), (OsView) Msg->B()); break; } case M_DESCRIBE: { char *Text = (char*) Msg->B(); if (Text) { StatusInfo->Name(Text); } break; } } return LView::OnEvent(Msg); } diff --git a/ResourceEditor/Code/LgiRes_Dialog.h b/ResourceEditor/Code/LgiRes_Dialog.h --- a/ResourceEditor/Code/LgiRes_Dialog.h +++ b/ResourceEditor/Code/LgiRes_Dialog.h @@ -1,522 +1,522 @@ /* ** FILE: LgiRes_Dialog.h ** AUTHOR: Matthew Allen ** DATE: 5/8/1999 ** DESCRIPTION: Dialog Resource Editor ** ** ** Copyright (C) 1999, Matthew Allen ** fret@memecode.com */ #ifndef __LGIRES_DIALOG_H #define __LGIRES_DIALOG_H #include "lgi/common/Res.h" #include "LgiRes_String.h" class ResDialog; class ResDialogUi; #define UI_CURSOR 0 #define UI_TABLE 1 #define UI_TEXT 2 #define UI_EDITBOX 3 #define UI_CHECKBOX 4 #define UI_BUTTON 5 #define UI_GROUP 6 #define UI_RADIO 7 #define UI_TABS 8 #define UI_LIST 9 #define UI_COMBO 10 #define UI_TREE 11 #define UI_BITMAP 12 #define UI_PROGRESS 13 #define UI_SCROLL_BAR 14 #define UI_CUSTOM 15 #define UI_TAB 16 #define UI_COLUMN 17 #define UI_CONTROL_TREE 18 #define UI_DIALOG 100 #define GOOBER_SIZE 5 #define GOOBER_BORDER 7 #define GRID_X 7 #define GRID_Y 7 //////////////////////////////////////////////////////////////// #define DECL_DIALOG_CTRL(id) \ int GetType() { return id; } \ LView *View() { return this; } \ void OnMouseClick(LMouse &m); \ void OnMouseMove(LMouse &m); \ void OnPaint(LSurface *pDC); #define IMPL_DIALOG_CTRL(cls) \ void cls::OnMouseClick(LMouse &m) { ResDialogCtrl::OnMouseClick(m); } \ void cls::OnMouseMove(LMouse &m) { ResDialogCtrl::OnMouseMove(m); } enum DlgSelectMode { SelNone, SelSet, SelAdd, }; class ResDialogCtrl : public FieldSource, public ResObject { friend class ResDialog; - ResString *_Str; + ResString *_Str = NULL; protected: static int TabDepth; void TabString(char *Str); void ReadPos(char *Str); char *GetRefText(); LRect Goobers[8]; - int OverGoober; + int OverGoober = -1; ResDialog *Dlg; LRect Title; LRect Client; - int DragCtrl; + int DragCtrl = -1; LRect DragRgn; LPoint DragStart; - bool MoveCtrl; - DlgSelectMode SelectMode; + bool MoveCtrl = false; + DlgSelectMode SelectMode = SelNone; LRect SelectStart; - bool AcceptChildren; - bool Movable; - bool Vis; + bool AcceptChildren = false; + bool Movable = true; + bool Vis = true; LAutoString CssClass; LAutoString CssStyle; LMouse MapToDialog(LMouse m); public: ResDialogCtrl(ResDialog *dlg, char *CtrlTypeName, LXmlTag *load); ~ResDialogCtrl(); const char *GetClass() { return "ResDialogCtrl"; } virtual LView *View() = 0; ResDialogCtrl *ParentCtrl() { return dynamic_cast(View()->GetParent()); } ResDialog *GetDlg() { return Dlg; } ResString *GetStr() { return _Str; } bool SetStr(ResString *s); bool IsContainer() { return AcceptChildren; } void OnPaint(LSurface *pDC); void OnMouseClick(LMouse &m); void OnMouseMove(LMouse &m); bool SetPos(LRect &p, bool Repaint = false); void StrFromRef(int Id); LRect AbsPos(); bool GetFields(FieldTree &Fields); // Copy/Paste translations only void CopyText(); void PasteText(); virtual int GetType() = 0; virtual LRect GetMinSize(); virtual bool Serialize(FieldTree &Fields); virtual void ListChildren(List &l, bool Deep = true); virtual bool AttachCtrl(ResDialogCtrl *Ctrl, LRect *r = 0); virtual LRect *GetChildArea(ResDialogCtrl *Ctrl) { return 0; } virtual LRect *GetPasteArea() { return 0; } virtual void EnumCtrls(List &Ctrls); virtual void ShowMe(ResDialogCtrl *Child) {} }; #define RESIZE_X1 0x0001 #define RESIZE_Y1 0x0002 #define RESIZE_X2 0x0004 #define RESIZE_Y2 0x0008 class ResDialog : public Resource, public LLayout, public ResFactory { friend class ResDialogCtrl; friend class ResDialogUi; friend class CtrlDlg; friend class CtrlTabs; friend class CtNode; protected: static int SymbolRefs; static ResStringGroup *Symbols; static bool CreateSymbols; ResDialogUi *Ui; List Selection; LRect DlgPos; int DragGoober; int *DragX, *DragY; int DragOx, DragOy; LRect DragRgn; ResDialogCtrl *DragCtrl; void DrawSelection(LSurface *pDC); public: LAutoPtr DebugOverlay; ResDialog(AppWnd *w, int type = TYPE_DIALOG); ~ResDialog(); void Create(LXmlTag *load, SerialiseContext *Ctx) override; const char *GetClass() override { return "ResDialog"; } LView *Wnd() override { return dynamic_cast(this); } static void AddLanguage(LLanguageId Id); ResDialog *IsDialog() override { return this; } void EnumCtrls(List &Ctrls); void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; // GObj overrides const char *Name() override; bool Name(const char *n) override; // Factory char *StringFromRef(int Ref) override; ResObject *CreateObject(LXmlTag *Tag, ResObject *Parent) override; int Res_GetStrRef(ResObject *Obj) override; bool Res_SetStrRef(ResObject *Obj, int Id, ResReadCtx *Ctx) override; void Res_SetPos(ResObject *Obj, int x1, int y1, int x2, int y2) override; void Res_SetPos(ResObject *Obj, char *s) override; LRect Res_GetPos(ResObject *Obj) override; void Res_Attach(ResObject *Obj, ResObject *Parent) override; bool Res_GetChildren(ResObject *Obj, List *l, bool Deep) override; void Res_Append(ResObject *Obj, ResObject *Parent) override; bool Res_GetItems(ResObject *Obj, List *l) override; bool Res_GetProperties(ResObject *Obj, LDom *Props) override; bool Res_SetProperties(ResObject *Obj, LDom *Props) override; LDom *Res_GetDom(ResObject *Obj) override; // Implementation int CurrentTool(); ResDialogCtrl *CreateCtrl(LXmlTag *Tag); ResDialogCtrl *CreateCtrl(int Tool, LXmlTag *load); bool IsSelected(ResDialogCtrl *Ctrl); bool IsDraging(); void SnapPoint(LPoint *p, ResDialogCtrl *From); void SnapRect(LRect *r, ResDialogCtrl *From); void MoveSelection(int Dx, int Dy); void SelectRect(ResDialogCtrl *Parent, LRect *r, bool ClearPrev = true); void SelectNone(); void SelectCtrl(ResDialogCtrl *c); void CleanSymbols(); ResString *CreateSymbol(); void OnShowLanguages() override; // Copy/Paste whole controls void Delete() override; void Copy(bool Delete = false) override; void Paste() override; // Methods LView *CreateUI() override; void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnKey(LKey &k) override; void OnSelect(ResDialogCtrl *Wnd, bool ClearPrev = true); void OnDeselect(ResDialogCtrl *Wnd); void OnRightClick(LSubMenu *RClick) override; void OnCommand(int Cmd) override; int OnCommand(int Cmd, int Event, OsView hWnd) override; void OnLanguageChange(); void _Paint(LSurface *pDC = NULL, LPoint *Offset = NULL, LRect *Update = NULL) override; void OnPaint(LSurface *pDC) override; bool Test(ErrorCollection *e) override; bool Read(LXmlTag *Tag, SerialiseContext &Ctx) override; bool Write(LXmlTag *Tag, SerialiseContext &Ctx) override; }; class ResDialogUi : public LLayout { friend class ResDialog; LToolBar *Tools; ResDialog *Dialog; LStatusBar *Status; LStatusPane *StatusInfo; public: ResDialogUi(ResDialog *Res); ~ResDialogUi(); const char *GetClass() { return "ResDialogUi"; } void OnPaint(LSurface *pDC); void PourAll(); void OnPosChange(); void OnCreate(); LMessage::Result OnEvent(LMessage *Msg); int CurrentTool(); void SelectTool(int i); }; /////////////////////////////////////////////////////////////////////// class CtrlDlg : public ResDialogCtrl, public LView { public: CtrlDlg(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_DIALOG) const char *GetClass() { return "CtrlDlg"; } LRect &GetClient(bool InClientSpace = true); // void _Paint(LSurface *pDC = NULL, LPoint *Offset = NULL, LRect *Update = NULL); void OnNcPaint(LSurface *pDC, LRect &r); }; class CtrlTable : public ResDialogCtrl, public LDom, public LView { class CtrlTablePrivate *d; public: CtrlTable(ResDialog *dlg, LXmlTag *load); ~CtrlTable(); DECL_DIALOG_CTRL(UI_TABLE) const char *GetClass() { return "CtrlTable"; } bool GetFields(FieldTree &Fields); bool Serialize(FieldTree &Fields); void SetAttachCell(class ResTableCell *c); bool AttachCtrl(ResDialogCtrl *Ctrl, LRect *r = 0); void OnChildrenChanged(LViewI *Wnd, bool Attaching); LRect *GetPasteArea(); LRect *GetChildArea(ResDialogCtrl *Ctrl); void Layout(); void UnMerge(class ResTableCell *Cell); void Fix(); void InsertCol(int x); void InsertRow(int y); void EnumCtrls(List &Ctrls); void OnPosChange(); bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0); bool SetVariant(const char *Name, LVariant &Value, const char *Array = 0); }; class CtrlText : public ResDialogCtrl, public LView { public: CtrlText(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_TEXT) const char *GetClass() { return "CtrlText"; } }; class CtrlEditbox : public ResDialogCtrl, public LView { bool Password; bool MultiLine; public: CtrlEditbox(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_EDITBOX) const char *GetClass() { return "CtrlEditbox"; } bool GetFields(FieldTree &Fields); bool Serialize(FieldTree &Fields); }; class CtrlCheckbox : public ResDialogCtrl, public LView { public: CtrlCheckbox(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_CHECKBOX) const char *GetClass() { return "CtrlCheckbox"; } }; class CtrlButton : public ResDialogCtrl, public LView { LString Image; bool IsToggle; public: CtrlButton(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_BUTTON) const char *GetClass() { return "CtrlButton"; } bool GetFields(FieldTree &Fields); bool Serialize(FieldTree &Fields); }; class CtrlGroup : public ResDialogCtrl, public LView { public: CtrlGroup(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_GROUP) const char *GetClass() { return "CtrlGroup"; } }; class CtrlRadio : public ResDialogCtrl, public LView { LSurface *Bmp; public: CtrlRadio(ResDialog *dlg, LXmlTag *load); ~CtrlRadio(); DECL_DIALOG_CTRL(UI_RADIO) const char *GetClass() { return "CtrlRadio"; } }; class CtrlTab : public ResDialogCtrl, public LView { public: CtrlTab(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_TAB) const char *GetClass() { return "CtrlTab"; } void ListChildren(List &l, bool Deep); }; class CtrlTabs : public ResDialogCtrl, public LView { friend class CtrlTab; ssize_t Current; public: List Tabs; CtrlTabs(ResDialog *dlg, LXmlTag *load); ~CtrlTabs(); DECL_DIALOG_CTRL(UI_TABS) const char *GetClass() { return "CtrlTabs"; } void ListChildren(List &l, bool Deep); void Empty(); void ToTab(); void FromTab(); LRect GetMinSize(); void EnumCtrls(List &Ctrls); void ShowMe(ResDialogCtrl *Child); }; class ListCol : public ResDialogCtrl, public LView { public: LRect &r() { return GetPos(); } DECL_DIALOG_CTRL(UI_COLUMN) const char *GetClass() { return "ListCol"; } ListCol(ResDialog *dlg, LXmlTag *load, char *Str = 0, int Width = 50); }; class CtrlList : public ResDialogCtrl, public LView { ssize_t DragCol; public: List Cols; CtrlList(ResDialog *dlg, LXmlTag *load); ~CtrlList(); DECL_DIALOG_CTRL(UI_LIST) const char *GetClass() { return "CtrlList"; } void ListChildren(List &l, bool Deep); void Empty(); }; class CtrlComboBox : public ResDialogCtrl, public LView { public: CtrlComboBox(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_COMBO) const char *GetClass() { return "CtrlComboBox"; } }; class CtrlScrollBar : public ResDialogCtrl, public LView { public: CtrlScrollBar(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_SCROLL_BAR) const char *GetClass() { return "CtrlScrollBar"; } }; class CtrlTree : public ResDialogCtrl, public LView { public: CtrlTree(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_TREE) const char *GetClass() { return "CtrlTree"; } }; class CtrlBitmap : public ResDialogCtrl, public LView { public: CtrlBitmap(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_BITMAP) const char *GetClass() { return "CtrlBitmap"; } }; class CtrlProgress : public ResDialogCtrl, public LView { public: CtrlProgress(ResDialog *dlg, LXmlTag *load); DECL_DIALOG_CTRL(UI_PROGRESS) const char *GetClass() { return "CtrlProgress"; } }; class CtrlCustom : public ResDialogCtrl, public LView { char *Control; public: CtrlCustom(ResDialog *dlg, LXmlTag *load); ~CtrlCustom(); DECL_DIALOG_CTRL(UI_CUSTOM) const char *GetClass() { return "CtrlCustom"; } bool GetFields(FieldTree &Fields); bool Serialize(FieldTree &Fields); }; class CtrlControlTree : public ResDialogCtrl, public LTree, public LDom { class CtrlControlTreePriv *d; public: CtrlControlTree(ResDialog *dlg, LXmlTag *load); ~CtrlControlTree(); DECL_DIALOG_CTRL(UI_CONTROL_TREE) const char *GetClass() { return "CtrlControlTree"; } bool GetFields(FieldTree &Fields); bool Serialize(FieldTree &Fields); bool GetVariant(const char *Name, LVariant &Value, const char *Array = 0); bool SetVariant(const char *Name, LVariant &Value, const char *Array = 0); }; //////////////////////////////////////////////////////////////// #endif diff --git a/ResourceEditor/MacCocoa/LgiRes.xcodeproj/project.pbxproj b/ResourceEditor/MacCocoa/LgiRes.xcodeproj/project.pbxproj --- a/ResourceEditor/MacCocoa/LgiRes.xcodeproj/project.pbxproj +++ b/ResourceEditor/MacCocoa/LgiRes.xcodeproj/project.pbxproj @@ -1,670 +1,657 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 48; objects = { /* Begin PBXBuildFile section */ 343FB8952386621600797ABC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 343FB8942386621600797ABC /* Assets.xcassets */; }; 343FB8AB238662C500797ABC /* LgiMain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343FB8AA238662C500797ABC /* LgiMain.cpp */; }; 343FB8C62386638C00797ABC /* LgiCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 343FB8A92386627A00797ABC /* LgiCocoa.framework */; }; 343FB8C82386639C00797ABC /* LgiCocoa.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 343FB8A92386627A00797ABC /* LgiCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 343FB8DC2386642A00797ABC /* OptionsFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 343FB8DB2386642A00797ABC /* OptionsFile.cpp */; }; + 347A9B582970B8C200D57A0B /* libpng16.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 347A9B552970B8BD00D57A0B /* libpng16.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34CC002823AAD2A100CCAAEA /* Search.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001B23AAD2A100CCAAEA /* Search.cpp */; }; 34CC002923AAD2A100CCAAEA /* LgiRes_ControlTree.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */; }; 34CC002A23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */; }; 34CC002B23AAD2A100CCAAEA /* LgiRes_Menu.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */; }; 34CC002C23AAD2A100CCAAEA /* LgiRes_Css.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */; }; 34CC002D23AAD2A100CCAAEA /* LgiResApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */; }; 34CC002E23AAD2A100CCAAEA /* ShowLanguages.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */; }; 34CC002F23AAD2A100CCAAEA /* LgiRes_Dialog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */; }; 34CC003023AAD2A100CCAAEA /* LgiRes_String.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */; }; 34CC003223AAD2C100CCAAEA /* StatusBar.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */; }; 34CC003423AAD2E700CCAAEA /* Mru.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003323AAD2E700CCAAEA /* Mru.cpp */; }; 34CC003723AAD2FE00CCAAEA /* DocApp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */; }; 34CC003823AAD2FE00CCAAEA /* About.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC003623AAD2FE00CCAAEA /* About.cpp */; }; 34CC003C23AAD33500CCAAEA /* mac-icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 34CC003A23AAD33500CCAAEA /* mac-icon.icns */; }; 34CC003D23AAD33500CCAAEA /* lgires.lr8 in Resources */ = {isa = PBXBuildFile; fileRef = 34CC003B23AAD33500CCAAEA /* lgires.lr8 */; }; 34CC003F23AAD34600CCAAEA /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 34CC003E23AAD34600CCAAEA /* Cocoa.framework */; }; 34CC004223AAD54200CCAAEA /* Png.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC004123AAD54200CCAAEA /* Png.cpp */; }; - 34CC006123AAD5A200CCAAEA /* libpng15.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 34CC005423AAD57F00CCAAEA /* libpng15.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 34CC006623AAD5DB00CCAAEA /* _StringIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */; }; 34CC006723AAD5DB00CCAAEA /* _DialogIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */; }; 34CC006823AAD5DB00CCAAEA /* _MenuIcons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */; }; 34CC006923AAD5DB00CCAAEA /* _Icons.gif in Resources */ = {isa = PBXBuildFile; fileRef = 34CC006523AAD5DB00CCAAEA /* _Icons.gif */; }; 34CC006C23AAD5FC00CCAAEA /* Gif.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */; }; 34CC006D23AAD5FC00CCAAEA /* Lzw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 343FB8A82386627A00797ABC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; proxyType = 2; remoteGlobalIDString = 3477C2681CBF020F0028B84B; remoteInfo = LgiCocoa; }; 343FB8C32386638800797ABC /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; proxyType = 1; remoteGlobalIDString = 3477C2671CBF020F0028B84B; remoteInfo = LgiCocoa; }; + 347A9B542970B8BD00D57A0B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 9D1B005B4C804977BD5DC316; + remoteInfo = libpng16; + }; + 347A9B562970B8BD00D57A0B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 5BBB106F0CF040E99654EA52; + remoteInfo = libpng16_static; + }; 34CC005123AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = E5FD78A808A24711B59ED1F2; remoteInfo = example; }; - 34CC005323AAD57F00CCAAEA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 51A0D964221847638E674CCE; - remoteInfo = libpng15; - }; - 34CC005523AAD57F00CCAAEA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 9D2D3C67E1D144698BBC9AC4; - remoteInfo = libpng15_static; - }; 34CC005723AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 787EECA18CBC4D4FB4E389EA; remoteInfo = minigzip; }; 34CC005923AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = 480BA9D67D004C9FB7A41391; remoteInfo = pngtest; }; 34CC005B23AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = C246F8AB721C4E3383C137C4; remoteInfo = zlib; }; 34CC005D23AAD57F00CCAAEA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; proxyType = 2; remoteGlobalIDString = FE658531E3604B19A40B327E; remoteInfo = zlib_static; }; - 34CC005F23AAD58F00CCAAEA /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = A5BD8566EC9F44589879D4FC; - remoteInfo = libpng15; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 343FB8C72386639100797ABC /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 34CC006123AAD5A200CCAAEA /* libpng15.dylib in CopyFiles */, + 347A9B582970B8C200D57A0B /* libpng16.dylib in CopyFiles */, 343FB8C82386639C00797ABC /* LgiCocoa.framework in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 343FB88E2386621400797ABC /* LgiRes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LgiRes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 343FB8942386621600797ABC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 343FB8992386621600797ABC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 343FB89C2386621600797ABC /* LgiRes.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LgiRes.entitlements; sourceTree = ""; }; 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LgiCocoa.xcodeproj; path = ../../src/mac/cocoa/LgiCocoa.xcodeproj; sourceTree = ""; }; 343FB8AA238662C500797ABC /* LgiMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiMain.cpp; path = ../../src/common/Lgi/LgiMain.cpp; sourceTree = ""; }; 343FB8DB2386642A00797ABC /* OptionsFile.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = OptionsFile.cpp; path = ../../src/common/Lgi/OptionsFile.cpp; sourceTree = ""; }; 34CC001B23AAD2A100CCAAEA /* Search.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Search.cpp; path = ../Code/Search.cpp; sourceTree = ""; }; 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_ControlTree.cpp; path = ../Code/LgiRes_ControlTree.cpp; sourceTree = ""; }; 34CC001D23AAD2A100CCAAEA /* LgiRes_Dialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_Dialog.h; path = ../Code/LgiRes_Dialog.h; sourceTree = ""; }; 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_TableLayout.cpp; path = ../Code/LgiRes_TableLayout.cpp; sourceTree = ""; }; 34CC001F23AAD2A100CCAAEA /* LgiResEdit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiResEdit.h; path = ../Code/LgiResEdit.h; sourceTree = ""; }; 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Menu.cpp; path = ../Code/LgiRes_Menu.cpp; sourceTree = ""; }; 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Css.cpp; path = ../Code/LgiRes_Css.cpp; sourceTree = ""; }; 34CC002223AAD2A100CCAAEA /* LgiRes_String.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_String.h; path = ../Code/LgiRes_String.h; sourceTree = ""; }; 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiResApp.cpp; path = ../Code/LgiResApp.cpp; sourceTree = ""; }; 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ShowLanguages.cpp; path = ../Code/ShowLanguages.cpp; sourceTree = ""; }; 34CC002523AAD2A100CCAAEA /* LgiRes_Menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LgiRes_Menu.h; path = ../Code/LgiRes_Menu.h; sourceTree = ""; }; 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_Dialog.cpp; path = ../Code/LgiRes_Dialog.cpp; sourceTree = ""; }; 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LgiRes_String.cpp; path = ../Code/LgiRes_String.cpp; sourceTree = ""; }; 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StatusBar.cpp; path = ../../src/common/Widgets/StatusBar.cpp; sourceTree = ""; }; 34CC003323AAD2E700CCAAEA /* Mru.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Mru.cpp; path = ../../src/common/Lgi/Mru.cpp; sourceTree = ""; }; 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = DocApp.cpp; path = ../../src/common/Lgi/DocApp.cpp; sourceTree = ""; }; 34CC003623AAD2FE00CCAAEA /* About.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = About.cpp; path = ../../src/common/Lgi/About.cpp; sourceTree = ""; }; 34CC003A23AAD33500CCAAEA /* mac-icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = "mac-icon.icns"; path = "../Resources/mac-icon.icns"; sourceTree = ""; }; 34CC003B23AAD33500CCAAEA /* lgires.lr8 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = lgires.lr8; path = ../Resources/lgires.lr8; sourceTree = ""; }; 34CC003E23AAD34600CCAAEA /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 34CC004123AAD54200CCAAEA /* Png.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Png.cpp; path = ../../src/common/Gdc2/Filters/Png.cpp; sourceTree = ""; }; 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libpng.xcodeproj; path = ../../../../../CodeLib/libpng/build/libpng.xcodeproj; sourceTree = ""; }; 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _StringIcons.gif; path = ../Code/_StringIcons.gif; sourceTree = ""; }; 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _DialogIcons.gif; path = ../Code/_DialogIcons.gif; sourceTree = ""; }; 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _MenuIcons.gif; path = ../Code/_MenuIcons.gif; sourceTree = ""; }; 34CC006523AAD5DB00CCAAEA /* _Icons.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; name = _Icons.gif; path = ../Code/_Icons.gif; sourceTree = ""; }; 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Gif.cpp; path = ../../src/common/Gdc2/Filters/Gif.cpp; sourceTree = ""; }; 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Lzw.cpp; path = ../../src/common/Gdc2/Filters/Lzw.cpp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 343FB88B2386621400797ABC /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 34CC003F23AAD34600CCAAEA /* Cocoa.framework in Frameworks */, 343FB8C62386638C00797ABC /* LgiCocoa.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 343FB8852386621400797ABC = { isa = PBXGroup; children = ( 343FB8A22386625100797ABC /* Cocoa */, 343FB8B9238662DB00797ABC /* Code */, 343FB8C52386638C00797ABC /* Frameworks */, 343FB8A32386626700797ABC /* Lgi */, 343FB88F2386621400797ABC /* Products */, 34CC003923AAD32200CCAAEA /* Resources */, ); sourceTree = ""; }; 343FB88F2386621400797ABC /* Products */ = { isa = PBXGroup; children = ( 343FB88E2386621400797ABC /* LgiRes.app */, ); name = Products; sourceTree = ""; }; 343FB8A22386625100797ABC /* Cocoa */ = { isa = PBXGroup; children = ( 343FB8942386621600797ABC /* Assets.xcassets */, 343FB8992386621600797ABC /* Info.plist */, 343FB89C2386621600797ABC /* LgiRes.entitlements */, ); name = Cocoa; sourceTree = ""; }; 343FB8A32386626700797ABC /* Lgi */ = { isa = PBXGroup; children = ( 34CC006A23AAD5FC00CCAAEA /* Gif.cpp */, 34CC006B23AAD5FC00CCAAEA /* Lzw.cpp */, 34CC003623AAD2FE00CCAAEA /* About.cpp */, 34CC003523AAD2FD00CCAAEA /* DocApp.cpp */, 34CC003323AAD2E700CCAAEA /* Mru.cpp */, 343FB8DB2386642A00797ABC /* OptionsFile.cpp */, 34CC003123AAD2C100CCAAEA /* StatusBar.cpp */, 343FB8AA238662C500797ABC /* LgiMain.cpp */, 34CC004123AAD54200CCAAEA /* Png.cpp */, 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */, 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */, ); name = Lgi; sourceTree = ""; }; 343FB8A52386627A00797ABC /* Products */ = { isa = PBXGroup; children = ( 343FB8A92386627A00797ABC /* LgiCocoa.framework */, ); name = Products; sourceTree = ""; }; 343FB8B9238662DB00797ABC /* Code */ = { isa = PBXGroup; children = ( 34CC001C23AAD2A100CCAAEA /* LgiRes_ControlTree.cpp */, 34CC002123AAD2A100CCAAEA /* LgiRes_Css.cpp */, 34CC002623AAD2A100CCAAEA /* LgiRes_Dialog.cpp */, 34CC001D23AAD2A100CCAAEA /* LgiRes_Dialog.h */, 34CC002023AAD2A100CCAAEA /* LgiRes_Menu.cpp */, 34CC002523AAD2A100CCAAEA /* LgiRes_Menu.h */, 34CC002723AAD2A100CCAAEA /* LgiRes_String.cpp */, 34CC002223AAD2A100CCAAEA /* LgiRes_String.h */, 34CC001E23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp */, 34CC002323AAD2A100CCAAEA /* LgiResApp.cpp */, 34CC001F23AAD2A100CCAAEA /* LgiResEdit.h */, 34CC001B23AAD2A100CCAAEA /* Search.cpp */, 34CC002423AAD2A100CCAAEA /* ShowLanguages.cpp */, ); name = Code; sourceTree = ""; }; 343FB8C52386638C00797ABC /* Frameworks */ = { isa = PBXGroup; children = ( 34CC003E23AAD34600CCAAEA /* Cocoa.framework */, ); name = Frameworks; sourceTree = ""; }; 34CC003923AAD32200CCAAEA /* Resources */ = { isa = PBXGroup; children = ( 34CC006323AAD5DB00CCAAEA /* _DialogIcons.gif */, 34CC006523AAD5DB00CCAAEA /* _Icons.gif */, 34CC006423AAD5DB00CCAAEA /* _MenuIcons.gif */, 34CC006223AAD5DB00CCAAEA /* _StringIcons.gif */, 34CC003B23AAD33500CCAAEA /* lgires.lr8 */, 34CC003A23AAD33500CCAAEA /* mac-icon.icns */, ); name = Resources; sourceTree = ""; }; 34CC004423AAD57F00CCAAEA /* Products */ = { isa = PBXGroup; children = ( 34CC005223AAD57F00CCAAEA /* example */, - 34CC005423AAD57F00CCAAEA /* libpng15.dylib */, - 34CC005623AAD57F00CCAAEA /* libpng15_static.a */, + 347A9B552970B8BD00D57A0B /* libpng16.dylib */, + 347A9B572970B8BD00D57A0B /* libpng16_static.a */, 34CC005823AAD57F00CCAAEA /* minigzip */, 34CC005A23AAD57F00CCAAEA /* pngtest */, 34CC005C23AAD57F00CCAAEA /* libz_local.dylib */, 34CC005E23AAD57F00CCAAEA /* libzlib_static.a */, ); name = Products; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 343FB88D2386621400797ABC /* LgiRes */ = { isa = PBXNativeTarget; buildConfigurationList = 343FB89F2386621600797ABC /* Build configuration list for PBXNativeTarget "LgiRes" */; buildPhases = ( 343FB88A2386621400797ABC /* Sources */, 343FB88B2386621400797ABC /* Frameworks */, 343FB88C2386621400797ABC /* Resources */, 343FB8C72386639100797ABC /* CopyFiles */, ); buildRules = ( ); dependencies = ( - 34CC006023AAD58F00CCAAEA /* PBXTargetDependency */, 343FB8C42386638800797ABC /* PBXTargetDependency */, ); name = LgiRes; productName = LgiRes; productReference = 343FB88E2386621400797ABC /* LgiRes.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 343FB8862386621400797ABC /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1010; ORGANIZATIONNAME = Memecode; TargetAttributes = { 343FB88D2386621400797ABC = { CreatedOnToolsVersion = 10.1; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 343FB8892386621400797ABC /* Build configuration list for PBXProject "LgiRes" */; compatibilityVersion = "Xcode 8.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 343FB8852386621400797ABC; productRefGroup = 343FB88F2386621400797ABC /* Products */; projectDirPath = ""; projectReferences = ( { ProductGroup = 343FB8A52386627A00797ABC /* Products */; ProjectRef = 343FB8A42386627A00797ABC /* LgiCocoa.xcodeproj */; }, { ProductGroup = 34CC004423AAD57F00CCAAEA /* Products */; ProjectRef = 34CC004323AAD57F00CCAAEA /* libpng.xcodeproj */; }, ); projectRoot = ""; targets = ( 343FB88D2386621400797ABC /* LgiRes */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ 343FB8A92386627A00797ABC /* LgiCocoa.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; path = LgiCocoa.framework; remoteRef = 343FB8A82386627A00797ABC /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 347A9B552970B8BD00D57A0B /* libpng16.dylib */ = { + isa = PBXReferenceProxy; + fileType = "compiled.mach-o.dylib"; + name = libpng16.dylib; + path = libpng16.15.29.0.dylib; + remoteRef = 347A9B542970B8BD00D57A0B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 347A9B572970B8BD00D57A0B /* libpng16_static.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libpng16_static.a; + remoteRef = 347A9B562970B8BD00D57A0B /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 34CC005223AAD57F00CCAAEA /* example */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = example; remoteRef = 34CC005123AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 34CC005423AAD57F00CCAAEA /* libpng15.dylib */ = { - isa = PBXReferenceProxy; - fileType = "compiled.mach-o.dylib"; - name = libpng15.dylib; - path = libpng15.15.4.0.dylib; - remoteRef = 34CC005323AAD57F00CCAAEA /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 34CC005623AAD57F00CCAAEA /* libpng15_static.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libpng15_static.a; - remoteRef = 34CC005523AAD57F00CCAAEA /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; 34CC005823AAD57F00CCAAEA /* minigzip */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = minigzip; remoteRef = 34CC005723AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005A23AAD57F00CCAAEA /* pngtest */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.executable"; path = pngtest; remoteRef = 34CC005923AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005C23AAD57F00CCAAEA /* libz_local.dylib */ = { isa = PBXReferenceProxy; fileType = "compiled.mach-o.dylib"; name = libz_local.dylib; path = libz_local.1.2.5.dylib; remoteRef = 34CC005B23AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; 34CC005E23AAD57F00CCAAEA /* libzlib_static.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libzlib_static.a; remoteRef = 34CC005D23AAD57F00CCAAEA /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ 343FB88C2386621400797ABC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 343FB8952386621600797ABC /* Assets.xcassets in Resources */, 34CC003C23AAD33500CCAAEA /* mac-icon.icns in Resources */, 34CC006623AAD5DB00CCAAEA /* _StringIcons.gif in Resources */, 34CC006723AAD5DB00CCAAEA /* _DialogIcons.gif in Resources */, 34CC006823AAD5DB00CCAAEA /* _MenuIcons.gif in Resources */, 34CC006923AAD5DB00CCAAEA /* _Icons.gif in Resources */, 34CC003D23AAD33500CCAAEA /* lgires.lr8 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 343FB88A2386621400797ABC /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 34CC002B23AAD2A100CCAAEA /* LgiRes_Menu.cpp in Sources */, 34CC002C23AAD2A100CCAAEA /* LgiRes_Css.cpp in Sources */, 343FB8DC2386642A00797ABC /* OptionsFile.cpp in Sources */, 34CC002F23AAD2A100CCAAEA /* LgiRes_Dialog.cpp in Sources */, 34CC003723AAD2FE00CCAAEA /* DocApp.cpp in Sources */, 34CC003823AAD2FE00CCAAEA /* About.cpp in Sources */, 34CC002E23AAD2A100CCAAEA /* ShowLanguages.cpp in Sources */, 34CC002923AAD2A100CCAAEA /* LgiRes_ControlTree.cpp in Sources */, 34CC003023AAD2A100CCAAEA /* LgiRes_String.cpp in Sources */, 34CC006D23AAD5FC00CCAAEA /* Lzw.cpp in Sources */, 34CC002823AAD2A100CCAAEA /* Search.cpp in Sources */, 34CC006C23AAD5FC00CCAAEA /* Gif.cpp in Sources */, 34CC002D23AAD2A100CCAAEA /* LgiResApp.cpp in Sources */, 34CC003423AAD2E700CCAAEA /* Mru.cpp in Sources */, 34CC002A23AAD2A100CCAAEA /* LgiRes_TableLayout.cpp in Sources */, 34CC004223AAD54200CCAAEA /* Png.cpp in Sources */, 343FB8AB238662C500797ABC /* LgiMain.cpp in Sources */, 34CC003223AAD2C100CCAAEA /* StatusBar.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 343FB8C42386638800797ABC /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = LgiCocoa; targetProxy = 343FB8C32386638800797ABC /* PBXContainerItemProxy */; }; - 34CC006023AAD58F00CCAAEA /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = libpng15; - targetProxy = 34CC005F23AAD58F00CCAAEA /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 343FB89D2386621600797ABC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; }; name = Debug; }; 343FB89E2386621600797ABC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; }; name = Release; }; 343FB8A02386621600797ABC /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 2AV9WN2LD8; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, ../Code, ../Resources, ../../../../../CodeLib/libpng/build, ../../../../../CodeLib/libpng, ); INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = ( "-DMAC", "-DLGI_COCOA", "-D_DEBUG", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.LgiRes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; 343FB8A12386621600797ABC /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 2AV9WN2LD8; HEADER_SEARCH_PATHS = ( ../../include, ../../include/lgi/mac/cocoa, ../Code, ../Resources, ../../../../../CodeLib/libpng/build, ../../../../../CodeLib/libpng, ); INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.10; OTHER_CFLAGS = ( "-DMAC", "-DLGI_COCOA", ); PRODUCT_BUNDLE_IDENTIFIER = com.memecode.LgiRes; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 343FB8892386621400797ABC /* Build configuration list for PBXProject "LgiRes" */ = { isa = XCConfigurationList; buildConfigurations = ( 343FB89D2386621600797ABC /* Debug */, 343FB89E2386621600797ABC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 343FB89F2386621600797ABC /* Build configuration list for PBXNativeTarget "LgiRes" */ = { isa = XCConfigurationList; buildConfigurations = ( 343FB8A02386621600797ABC /* Debug */, 343FB8A12386621600797ABC /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 343FB8862386621400797ABC /* Project object */; } diff --git a/include/lgi/common/Css.h b/include/lgi/common/Css.h --- a/include/lgi/common/Css.h +++ b/include/lgi/common/Css.h @@ -1,1296 +1,1298 @@ /// \file /// \author Matthew Allen #pragma once /// I've using the American spelling for 'color' as opposed to the English 'colour' /// because the CSS spec is written with 'color' as the spelling. #include "LgiInc.h" #include "LgiOsDefs.h" #include "lgi/common/Mem.h" #include "lgi/common/Gdc2.h" #include "lgi/common/AutoPtr.h" #include "lgi/common/LgiString.h" #include "lgi/common/HashTable.h" #ifndef LINUX #pragma pack(push, 1) #endif /// Css property container class LgiClass LCss { bool ReadOnly; public: enum ParsingStyle { ParseStrict, ParseRelaxed, }; enum PropTypes { TypeEnum = 1, TypeLen, TypeGRect, TypeColor, TypeBackground, TypeImage, TypeBorder, TypeStrings, }; /// These are all the types of CSS properties catagorised by their data type. /// The enum value of the prop is encoded in the bottom 8 bits, the data type of /// the property is encoding in the top remaining top bits. enum PropType { PropNull = 0, // Enum based props PropDisplay = TypeEnum << 8,// DisplayType PropFloat, // FloatType PropPosition, // PositionType PropOverflow, // OverflowType PropVisibility, // VisibilityType PropFontStyle, // FontStyleType PropFontVariant, // FontVariantType PropFontWeight, // FontWeightType PropBackgroundRepeat, // RepeatType PropBackgroundAttachment, // AttachmentType PropTextDecoration, // TextDecorType PropWordWrap, // WordWraptype PropListStyleType, // ListStyleTypes PropLetterSpacing, PropFont, PropListStyle, PropBorderCollapse, // BorderCollapseType PropBorderTopStyle, PropBorderRightStyle, PropBorderBottomStyle, PropBorderLeftStyle, // Length based props PropZIndex = TypeLen << 8, PropWidth, PropMinWidth, PropMaxWidth, PropHeight, PropMinHeight, PropMaxHeight, PropTop, PropRight, PropBottom, PropLeft, PropMargin, PropMarginTop, PropMarginRight, PropMarginBottom, PropMarginLeft, PropPadding, PropPaddingTop, PropPaddingRight, PropPaddingBottom, PropPaddingLeft, PropLineHeight, PropVerticalAlign, PropFontSize, PropBackgroundX, PropBackgroundY, PropBackgroundPos, PropTextAlign, PropBorderSpacing, PropBorderLeftWidth, PropBorderTopWidth, PropBorderRightWidth, PropBorderBottomWidth, PropBorderRadius, Prop_CellPadding, // not real CSS, but used by LHtml2 to store 'cellpadding' // LRect based props PropClip = TypeGRect<<8, PropXSubRect, // Background meta style PropBackground = TypeBackground << 8, // ColorDef based PropColor = TypeColor << 8, PropBackgroundColor, PropNoPaintColor, PropBorderTopColor, PropBorderRightColor, PropBorderBottomColor, PropBorderLeftColor, // ImageDef based PropBackgroundImage = TypeImage << 8, // BorderDef based PropBorder = TypeBorder << 8, PropBorderStyle, PropBorderColor, PropBorderTop, PropBorderRight, PropBorderBottom, PropBorderLeft, // StringsDef based PropFontFamily = TypeStrings << 8, }; PropTypes GetType(PropType p) { return (PropTypes) ((int)p >> 8); } enum BorderCollapseType { CollapseInherit, CollapseCollapse, CollapseSeparate, }; enum WordWrapType { WrapNormal, WrapBreakWord, WrapNone, // Not in the CSS standard but useful for apps anyway. }; enum DisplayType { DispInherit, DispBlock, DispInline, DispInlineBlock, DispListItem, DispNone, }; enum PositionType { PosInherit, PosStatic, PosRelative, PosAbsolute, PosFixed, }; enum LengthType { // Standard length types LenInherit, LenAuto, LenPx, LenPt, LenEm, LenEx, LenCm, LenPercent, LenNormal, + LenMinContent, + LenMaxContent, // Horizontal alignment types AlignLeft, AlignRight, AlignCenter, AlignJustify, // Vertical alignment types VerticalBaseline, VerticalSub, VerticalSuper, VerticalTop, VerticalTextTop, VerticalMiddle, VerticalBottom, VerticalTextBottom, // Absolute font sizes SizeXXSmall, SizeXSmall, SizeSmall, SizeMedium, SizeLarge, SizeXLarge, SizeXXLarge, // Relitive font sizes, SizeSmaller, SizeLarger, }; enum FloatType { FloatInherit, FloatLeft, FloatRight, FloatNone, }; enum OverflowType { OverflowInherit, OverflowVisible, OverflowHidden, OverflowScroll, OverflowAuto, }; enum VisibilityType { VisibilityInherit, VisibilityVisible, VisibilityHidden, VisibilityCollapse }; enum ListStyleTypes { ListInherit, ListNone, ListDisc, ListCircle, ListSquare, ListDecimal, ListDecimalLeadingZero, ListLowerRoman, ListUpperRoman, ListLowerGreek, ListUpperGreek, ListLowerAlpha, ListUpperAlpha, ListArmenian, ListGeorgian, }; enum FontStyleType { FontStyleInherit, FontStyleNormal, FontStyleItalic, FontStyleOblique, }; enum FontVariantType { FontVariantInherit, FontVariantNormal, FontVariantSmallCaps, }; enum FontWeightType { FontWeightInherit, FontWeightNormal, FontWeightBold, FontWeightBolder, FontWeightLighter, FontWeight100, FontWeight200, FontWeight300, FontWeight400, FontWeight500, FontWeight600, FontWeight700, FontWeight800, FontWeight900, }; enum TextDecorType { TextDecorInherit, TextDecorNone, TextDecorUnderline, TextDecorOverline, TextDecorLineThrough, TextDecorSquiggle, // for spelling errors }; enum ColorType { ColorInherit, ColorTransparent, ColorRgb, ColorLinearGradient, ColorRadialGradient, }; enum RepeatType { RepeatInherit, RepeatBoth, RepeatX, RepeatY, RepeatNone, }; enum AttachmentType { AttachmentInherit, AttachmentScroll, AttachmentFixed, }; static float FontSizeTable[7]; // SizeXSmall etc #define _FloatAbs(a) \ (((a) < 0.0f) ? -(a) : (a)) #define FloatIsEqual(a, b) \ (_FloatAbs(a - b) < 0.0001) struct LgiClass Len { LengthType Type; float Value; Len(const char *init = NULL, ParsingStyle ParseType = ParseRelaxed) { Type = LenInherit; Value = 0.0; if (init) Parse(init, PropNull, ParseType); } Len(LengthType t, float v = 0.0) { Type = t; Value = v; } Len(LengthType t, int v) { Type = t; Value = (float)v; } Len(LengthType t, int64_t v) { Type = t; Value = (float)v; } Len &operator =(const Len &l) { Type = l.Type; Value = l.Value; return *this; } bool Parse(const char *&s, PropType Prop = PropNull, ParsingStyle Type = ParseStrict); bool IsValid() const { return Type != LenInherit; } bool IsDynamic() const { return Type == LenPercent || Type == LenInherit || Type == LenAuto || Type == SizeSmaller || Type == SizeLarger; } bool operator ==(const Len &l) const { return Type == l.Type && FloatIsEqual(Value, l.Value); } bool operator !=(const Len &l) const { return !(*this == l); } bool ToString(LStream &p) const; /// Convert the length to pixels int ToPx ( /// The size of the parent box if known, or -1 if unknown. int Box = 0, /// Any relevant font LFont *Font = 0, /// The DPI of the relevant device if known, or -1 if unknown int Dpi = -1 ); Len operator *(const Len &l) const; }; struct LgiClass ColorStop { float Pos; uint32_t Rgb32; bool operator ==(const ColorStop &c) { return FloatIsEqual(Pos, c.Pos) && Rgb32 == c.Rgb32; } bool operator !=(const ColorStop &c) { return !(*this == c); } }; struct LgiClass ColorDef { ColorType Type; uint32_t Rgb32; LArray Stops; ColorDef(ColorType ct = ColorInherit, uint32_t rgb32 = 0) { Type = ct; Rgb32 = rgb32; } ColorDef(const LColour &col) { Type = ColorRgb; Rgb32 = col.c32(); } ColorDef(const char *init) { Type = ColorInherit; Parse(init); } ColorDef(COLOUR col) { Type = ColorRgb; Rgb32 = Rgb24To32(col); } operator LColour() { LColour c; if (Type == ColorRgb) c.Set(Rgb32, 32); return c; } bool IsValid() { return Type != ColorInherit; } bool Parse(const char *&s); ColorDef &operator =(const ColorDef &c) { Type = c.Type; Rgb32 = c.Rgb32; Stops = c.Stops; return *this; } bool operator ==(const ColorDef &c) { if (Type != c.Type) return false; if (Type == ColorRgb) return Rgb32 == c.Rgb32; if (Stops.Length() != c.Stops.Length()) return false; for (uint32_t i=0; i { public: StringsDef(const char *init = 0) { if (ValidStr(init)) *this = init; else LAssert(init == 0); } StringsDef(const StringsDef &c) { *this = c; } ~StringsDef() { Empty(); } StringsDef &operator =(const char *s) { Parse(s); return *this; } void Empty() { DeleteArrays(); } StringsDef &operator =(const StringsDef &s) { Empty(); for (unsigned i=0; i Start) { LAutoString n(NewStr(Start, s-Start)); if (ValidStr(n)) { if (_stricmp(n, "inherit")) Add(n.Release()); } else LAssert(!"Not a valid string."); } if (s) s++; } else { const char *Start = s; while (*s && !strchr(Delimiters, *s)) s++; if (s > Start) { LAutoString n(NewStr(Start, s-Start)); if (ValidStr(n)) { if (_stricmp(n, "inherit")) Add(n.Release()); } else LAssert(!"Not a valid string."); } } } return true; } }; class Store; /// This class parses and stores a selector. The job of matching selectors and /// hashing them is still the responsibility of the calling library. If an application /// needs some code to do that it can optionally use LCss::Store to do that. class LgiClass Selector { public: enum PartType { SelNull, SelType, SelUniversal, SelAttrib, SelClass, SelMedia, SelID, SelPseudo, SelFontFace, SelPage, SelList, SelImport, SelKeyFrames, SelIgnored, CombDesc, CombChild, CombAdjacent, }; enum MediaType { MediaNull = 0x0, MediaPrint = 0x1, MediaScreen = 0x2, }; struct Part { PartType Type; LAutoString Value; LAutoString Param; int Media; Part() { Type = SelNull; Media = MediaNull; } bool IsSel() { return Type == SelType || Type == SelUniversal || Type == SelAttrib || Type == SelClass || Type == SelMedia || Type == SelID || Type == SelPseudo; } Part &operator =(const Part &s) { Type = s.Type; Value.Reset(NewStr(s.Value)); Param.Reset(NewStr(s.Param)); return *this; } }; LArray Parts; LArray Combs; char *Style; int SourceIndex; LAutoString Raw; LAutoPtr Children; Selector() { Style = NULL; SourceIndex = 0; } bool TokString(LAutoString &a, const char *&s); const char *PartTypeToString(PartType p); LAutoString Print(); bool Parse(const char *&s); size_t GetSimpleIndex() { return Combs.Length() ? Combs[Combs.Length()-1] + 1 : 0; } bool IsAtMedia(); bool ToString(LStream &p); uint32_t GetSpecificity(); Selector &operator =(const Selector &s); }; /// This hash table stores arrays of selectors by name. typedef LArray SelArray; typedef LHashTbl,SelArray*> SelMap; class SelectorMap : public SelMap { public: ~SelectorMap() { Empty(); } void Empty() { // for (SelArray *s = First(); s; s = Next()) for (auto s : *this) { s.value->DeleteObjects(); delete s.value; } SelMap::Empty(); } SelArray *Get(const char *s) { SelArray *a = Find(s); if (!a) Add(s, a = new SelArray); return a; } }; template struct ElementCallback { public: /// Returns the element name virtual const char *GetElement(T *obj) = 0; /// Returns the document unque element ID virtual const char *GetAttr(T *obj, const char *Attr) = 0; /// Returns the class virtual bool GetClasses(LString::Array &Classes, T *obj) = 0; /// Returns the parent object virtual T *GetParent(T *obj) = 0; /// Returns an array of child objects virtual LArray GetChildren(T *obj) = 0; }; /// This class parses and stores the CSS selectors and styles. class LgiClass Store { protected: /// This code matches a simple part of a selector, i.e. no combinatorial operators involved. template bool MatchSimpleSelector ( /// The full selector. LCss::Selector *Sel, /// The start index of the simple selector parts. Stop at the first comb operator or the end of the parts. ssize_t PartIdx, /// Our context callback to get properties of the object ElementCallback *Context, /// The object to match T *Obj ) { const char *Element = Context->GetElement(Obj); for (size_t n = PartIdx; nParts.Length(); n++) { LCss::Selector::Part &p = Sel->Parts[n]; switch (p.Type) { case LCss::Selector::SelType: { const char *Tag = Context->GetElement(Obj); if (!Tag || _stricmp(Tag, p.Value)) return false; break; } case LCss::Selector::SelUniversal: { // Match everything break; } case LCss::Selector::SelAttrib: { if (!p.Value) return false; char *e = strchr(p.Value, '='); if (!e) return false; LAutoString Var(NewStr(p.Value, e - p.Value)); const char *TagVal = Context->GetAttr(Obj, Var); if (!TagVal) return false; LAutoString Val(NewStr(e + 1)); if (_stricmp(Val, TagVal)) return false; break; } case LCss::Selector::SelClass: { // Check the class matches LString::Array Class; if (!Context->GetClasses(Class, Obj)) return false; if (Class.Length() == 0) return false; bool Match = false; for (unsigned i=0; iGetAttr(Obj, "id"); if (!Id || _stricmp(Id, p.Value)) return false; break; } case LCss::Selector::SelPseudo: { const char *Href = NULL; if ( ( Element != NULL && !_stricmp(Element, "a") && p.Value && !_stricmp(p.Value, "link") && (Href = Context->GetAttr(Obj, "href")) != NULL ) || ( p.Value && *p.Value == '-' ) ) break; return false; break; } default: { // Comb operator, so return the current match value return true; } } } return true; } /// This code matches a all the parts of a selector. template bool MatchFullSelector(LCss::Selector *Sel, ElementCallback *Context, T *Obj) { bool Complex = Sel->Combs.Length() > 0; ssize_t CombIdx = Complex ? (ssize_t)Sel->Combs.Length() - 1 : 0; ssize_t StartIdx = (Complex) ? Sel->Combs[CombIdx] + 1 : 0; bool Match = MatchSimpleSelector(Sel, StartIdx, Context, Obj); if (!Match) return false; if (Complex) { T *CurrentParent = Context->GetParent(Obj); for (; CombIdx >= 0; CombIdx--) { if (CombIdx >= (int)Sel->Combs.Length()) break; StartIdx = Sel->Combs[CombIdx]; LAssert(StartIdx > 0); if (StartIdx >= (ssize_t)Sel->Parts.Length()) break; LCss::Selector::Part &p = Sel->Parts[StartIdx]; switch (p.Type) { case LCss::Selector::CombChild: { // LAssert(!"Not impl."); return false; break; } case LCss::Selector::CombAdjacent: { // LAssert(!"Not impl."); return false; break; } case LCss::Selector::CombDesc: { // Does the parent match the previous simple selector ssize_t PrevIdx = StartIdx - 1; while (PrevIdx > 0 && Sel->Parts[PrevIdx-1].IsSel()) { PrevIdx--; } bool ParMatch = false; for (; !ParMatch && CurrentParent; CurrentParent = Context->GetParent(CurrentParent)) { ParMatch = MatchSimpleSelector(Sel, PrevIdx, Context, CurrentParent); } if (!ParMatch) return false; break; } default: { LAssert(!"This must be a comb."); return false; break; } } } } return Match; } // This stores the unparsed style strings. More than one selector // may reference this memory. LArray Styles; // Sort the styles into less specific to more specific order void SortStyles(LCss::SelArray &Styles); public: SelectorMap TypeMap, ClassMap, IdMap; SelArray Other; LString Error; ~Store() { Empty(); } /// Empty the data store void Empty() { TypeMap.Empty(); ClassMap.Empty(); IdMap.Empty(); Other.DeleteObjects(); Error.Empty(); Styles.DeleteArrays(); } /// Parse general CSS into selectors. bool Parse(const char *&s, int Depth = 0); /// Converts store back into string form bool ToString(LStream &p); /// Use to finding matching selectors for an element. template bool Match(LCss::SelArray &Styles, ElementCallback *Context, T *Obj) { SelArray *s; if (!Context || !Obj) return false; // An array of potential selector matches... LArray Maps; // Check element type const char *Type = Context->GetElement(Obj); if (Type && (s = TypeMap.Find(Type))) Maps.Add(s); // Check the ID const char *Id = Context->GetAttr(Obj, "id"); if (Id && (s = IdMap.Find(Id))) Maps.Add(s); // Check all the classes LString::Array Classes; if (Context->GetClasses(Classes, Obj)) { for (unsigned i=0; iLength(); i++) { LCss::Selector *Sel = (*s)[i]; if (!Styles.HasItem(Sel) && MatchFullSelector(Sel, Context, Obj)) { // Output the matching selector Styles.Add(Sel); } } } // Sort the selectors into less specific -> more specific order. SortStyles(Styles); return true; } /// For debugging: dumps a description of the store to a stream bool Dump(LStream &out); }; ////////////////////////////////////////////////////////////////////////////////////////////////////////// LCss(); LCss(const LCss &c); void Init(); virtual ~LCss(); #define Accessor(PropName, Type, Default, BaseProp) \ Type PropName() { Type *Member = (Type*)Props.Find(Prop##PropName); \ if (Member) return *Member; \ else if ((Member = (Type*)Props.Find(BaseProp))) return *Member; \ return Default; } \ void PropName(Type t) { LAssert(!ReadOnly); \ Type *Member = (Type*)Props.Find(Prop##PropName); \ if (Member) *Member = t; \ else { Props.Add(Prop##PropName, Member = new Type); \ *Member = t; } \ OnChange(Prop##PropName); } Accessor(Display, DisplayType, DispInherit, PropNull); Accessor(Float, FloatType, FloatInherit, PropNull); Accessor(Position, PositionType, PosInherit, PropNull); Accessor(ZIndex, Len, Len(), PropNull); Accessor(TextAlign, Len, Len(), PropNull); Accessor(VerticalAlign, Len, Len(), PropNull); Accessor(Width, Len, Len(), PropNull); Accessor(MinWidth, Len, Len(), PropNull); Accessor(MaxWidth, Len, Len(), PropNull); Accessor(Height, Len, Len(), PropNull); Accessor(MinHeight, Len, Len(), PropNull); Accessor(MaxHeight, Len, Len(), PropNull); Accessor(LineHeight, Len, Len(), PropNull); Accessor(Top, Len, Len(), PropNull); Accessor(Right, Len, Len(), PropNull); Accessor(Bottom, Len, Len(), PropNull); Accessor(Left, Len, Len(), PropNull); Accessor(Margin, Len, Len(), PropNull); Accessor(MarginTop, Len, Len(), PropNull); Accessor(MarginRight, Len, Len(), PropNull); Accessor(MarginBottom, Len, Len(), PropNull); Accessor(MarginLeft, Len, Len(), PropNull); Accessor(Padding, Len, Len(), PropNull); Accessor(PaddingTop, Len, Len(), PropPadding); Accessor(PaddingRight, Len, Len(), PropPadding); Accessor(PaddingBottom, Len, Len(), PropPadding); Accessor(PaddingLeft, Len, Len(), PropPadding); Accessor(Border, BorderDef, BorderDef(), PropNull); Accessor(BorderTop, BorderDef, BorderDef(), PropBorder); Accessor(BorderRight, BorderDef, BorderDef(), PropBorder); Accessor(BorderBottom, BorderDef, BorderDef(), PropBorder); Accessor(BorderLeft, BorderDef, BorderDef(), PropBorder); Accessor(BorderSpacing, Len, Len(), PropNull); // 'cellspacing' Accessor(BorderRadius, Len, Len(), PropNull); Accessor(BorderCollapse, BorderCollapseType, CollapseInherit, PropBorderCollapse); Accessor(_CellPadding, Len, Len(), PropNull); // 'cellpadding' (not CSS) Accessor(Overflow, OverflowType, OverflowInherit, PropNull); Accessor(Clip, LRect, LRect(0, 0, -1, -1), PropNull); Accessor(XSubRect, LRect, LRect(0, 0, -1, -1), PropNull); Accessor(Visibility, VisibilityType, VisibilityInherit, PropNull); Accessor(ListStyleType, ListStyleTypes, ListInherit, PropNull); Accessor(FontFamily, StringsDef, StringsDef(), PropNull); Accessor(FontSize, Len, Len(), PropNull); Accessor(FontStyle, FontStyleType, FontStyleInherit, PropNull); Accessor(FontVariant, FontVariantType, FontVariantInherit, PropNull); Accessor(FontWeight, FontWeightType, FontWeightInherit, PropNull); Accessor(TextDecoration, TextDecorType, TextDecorInherit, PropNull); Accessor(WordWrap, WordWrapType, WrapNormal, PropNull); Accessor(Color, ColorDef, ColorDef(), PropNull); Accessor(NoPaintColor, ColorDef, ColorDef(), PropNull); Accessor(BackgroundColor, ColorDef, ColorDef(), PropNull); Accessor(BackgroundImage, ImageDef, ImageDef(), PropNull); Accessor(BackgroundRepeat, RepeatType, RepeatInherit, PropNull); Accessor(BackgroundAttachment, AttachmentType, AttachmentInherit, PropNull); Accessor(BackgroundX, Len, Len(), PropNull); Accessor(BackgroundY, Len, Len(), PropNull); Accessor(BackgroundPos, Len, Len(), PropNull); bool GetReadOnly() { return ReadOnly; } void SetReadOnly(bool b) { ReadOnly = b; }; void Empty(); void DeleteProp(PropType p); size_t Length() { return Props.Length(); } virtual void OnChange(PropType Prop); bool CopyStyle(const LCss &c); bool operator ==(LCss &c); bool operator !=(LCss &c) { return !(*this == c); } LCss &operator =(const LCss &c) { Empty(); CopyStyle(c); return *this; } LCss &operator +=(const LCss &c) { CopyStyle(c); return *this; } LCss &operator -=(const LCss &c); void *PropAddress(PropType p) { return Props.Find(p); } LAutoString ToString(); const char *ToString(DisplayType dt); bool HasFontStyle(); void FontBold(bool b) { FontWeight(b ? FontWeightBold : FontWeightNormal); } // Parsing virtual bool Parse(const char *&Defs, ParsingStyle Type = ParseStrict); bool ParseDisplayType(const char *&s); void ParsePositionType(const char *&s); bool ParseFontStyle(PropType p, const char *&s); bool ParseFontVariant(PropType p, const char *&s); bool ParseFontWeight(PropType p, const char *&s); bool ParseBackgroundRepeat(const char *&s); template T *GetOrCreate(T *&ptr, PropType PropId) { ptr = (T*)Props.Find(PropId); if (!ptr) Props.Add(PropId, ptr = new T); return ptr; } // Inheritance calculation typedef LArray PropArray; typedef LHashTbl, PropArray*> PropMap; /// Copies valid properties from the node 'c' into the property collection 'Contrib'. /// Usually called for each node up the parent chain until the function returns false; bool InheritCollect(LCss &c, PropMap &Contrib); /// After calling InheritCollect on all the parent nodes, this method works out the final /// value of each property. e.g. multiplying percentages together etc. bool InheritResolve(PropMap &Map); /* Code sample: LCss::PropMap Map; Map.Add(PropFontFamily, new LCss::PropArray); Map.Add(PropFontSize, new LCss::PropArray); Map.Add(PropFontStyle, new LCss::PropArray); for (LTag *t = Parent; t; t = t->Parent) { if (!c.InheritCollect(*t, Map)) break; } LCss c; // Container for final values c.InheritResolve(Map); Map.DeleteObjects(); */ protected: inline void DeleteProp(PropType p, void *Ptr); LHashTbl, void*> Props; static LHashTbl, PropType> Lut; static LHashTbl, PropType> ParentProp; static const char *PropName(PropType p); virtual bool OnUnhandledColor(ColorDef *def, const char *&s) { return false; } template void StoreProp(PropType id, T *obj, bool own) { T *e = (T*)Props.Find(id); if (e) { *e = *obj; if (own) delete obj; } else if (own) { Props.Add(id, obj); } else { Props.Add(id, new T(*obj)); } } }; #ifndef LINUX #pragma pack(pop) #endif diff --git a/include/lgi/common/DateTime.h b/include/lgi/common/DateTime.h --- a/include/lgi/common/DateTime.h +++ b/include/lgi/common/DateTime.h @@ -1,414 +1,419 @@ /// \file /// \author Matthew Allen /** * \defgroup Time Time and date handling * \ingroup Lgi */ #ifndef __DATE_TIME_H #define __DATE_TIME_H #include #include "lgi/common/StringClass.h" #define GDTF_DEFAULT 0 /// Format the date as DD/MM/YYYY /// \ingroup Time #define GDTF_DAY_MONTH_YEAR 0x001 /// Format the date as MM/DD/YYYY /// \ingroup Time #define GDTF_MONTH_DAY_YEAR 0x002 /// Format the date as YYYY/MM/DD /// \ingroup Time #define GDTF_YEAR_MONTH_DAY 0x004 /// The bit mask for the date /// \ingroup Time #define GDTF_DATE_MASK 0x00f /// Format the time as HH:MM and an am/pm marker /// \ingroup Time #define GDTF_12HOUR 0x010 /// Format the time as 24 hour time /// \ingroup Time #define GDTF_24HOUR 0x020 /// The bit mask for the time /// \ingroup Time #define GDTF_TIME_MASK 0x0f0 /// Format the date with a leading zero /// \ingroup Time #define GDTF_DAY_LEADINGZ 0x100 /// Format the month with a leading zero /// \ingroup Time #define GDTF_MONTH_LEADINGZ 0x200 /// A date/time class /// /// This class interacts with system times represented as 64bit ints. The various OS support different /// formats for that 64bit int values. On windows the system times are in 100-nanosecond intervals since /// January 1, 1601 (UTC), as per the FILETIME structure, on Posix systems (Linux/Mac) the 64bit values /// are in milliseconds since January 1, 1970 UTC. This is just unix time * 1000. If you are serializing /// these 64bit values you should take that into account, they are NOT cross platform. The LDirectory class /// uses the same 64bit values as accepted here for the file's last modified timestamp etc. To convert the /// 64bit values to seconds, divide by LDateTime::Second64Bit, useful for calculating the time in seconds /// between 2 LDateTime objects. /// /// \ingroup Time class LgiClass LDateTime // This class can't have a virtual table, because it's used in // LArray's which initialize with all zero bytes. { /// 1 - DaysInMonth int16 _Day; /// #### int16 _Year; /// Milliseconds: 0-999 int16 _Thousands; /// 1-12 int16 _Month; /// 0-59 int16 _Seconds; /// 0-59 int16 _Minutes; /// 0-23 (24hr) int16 _Hours; /// The current timezone of this object, defaults to the system timezone int16 _Tz; // in minutes (+10 == 600 etc) /// Combination of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 _Format; /// The default formatting of datetimes static uint16 DefaultFormat; /// The default date separator character static char DefaultSeparator; public: LDateTime(const char *Init = NULL); LDateTime(uint64 Ts); LDateTime(const LDateTime &dt) { *this = dt; } ~LDateTime(); /// Resolution of a second when using 64 bit int's /// \sa LDateTime::Get(int64), LDateTime::Set(int64) #ifdef WIN32 static constexpr int64_t Second64Bit = 10000000; #else static constexpr int64_t Second64Bit = 1000; #endif static constexpr int64_t MinuteLength = 60; // seconds static constexpr int64_t HourLength = MinuteLength * 60; // seconds static constexpr int64_t DayLength = HourLength * 24; // seconds static constexpr const char *WeekdaysShort[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static constexpr const char *WeekdaysLong[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; static constexpr const char *MonthsShort[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; static constexpr const char *MonthsLong[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; // On Posix systems this allows representation of times before 1/1/1970. // The Lgi epoch is considered to be 1/1/1800 instead and this offset converts // from one to the other. static constexpr int64_t Offset1800 = 5364662400; // Seconds from 1/1/1800 to 1/1/1970 /// Returns true if all the components are in a valid range bool IsValid() const; /// Sets the date to an NULL state void Empty(); /// Returns the day int Day() const { return _Day; } /// Sets the day void Day(int d) { _Day = d; } /// Returns the month int Month() const { return _Month; } /// Sets the month void Month(int m) { _Month = m; } /// Sets the month by it's name void Month(char *m); /// Returns the year int Year() const { return _Year; } /// Sets the year void Year(int y) { _Year = y; } /// Returns the millisecond part of the time int Thousands() const { return _Thousands; } /// Sets the millisecond part of the time void Thousands(int t) { _Thousands = t; } /// Returns the seconds part of the time int Seconds() const { return _Seconds; } /// Sets the seconds part of the time void Seconds(int s) { _Seconds = s; } /// Returns the minutes part of the time int Minutes() const { return _Minutes; } /// Sets the minutes part of the time void Minutes(int m) { _Minutes = m; } /// Returns the hours part of the time int Hours() const { return _Hours; } /// Sets the hours part of the time void Hours(int h) { _Hours = h; } /// Returns the timezone of this current date time object in minutes (+10 = 600) int GetTimeZone() const { return _Tz; } /// Returns the timezone in hours double GetTimeZoneHours() const { return (double)_Tz / 60.0; } /// Sets the timezone of this current object.in minutes (+10 = 600) void SetTimeZone ( /// The new timezone int Tz, /// True if you want to convert the date and time to the new zone, /// False if you want to leave the date/time as it is. bool ConvertTime ); /// Set this object to UTC timezone, changing the other members as /// needed LDateTime &ToUtc(bool AssumeLocal = false) { if (AssumeLocal) _Tz = SystemTimeZone(); SetTimeZone(0, true); return *this; } /// \returns the UTC version of this object. LDateTime Utc() const { LDateTime dt = *this; return dt.ToUtc(); } /// Changes the timezone to the local zone, changing other members /// as needed. LDateTime &ToLocal(bool AssumeUtc = false) { if (AssumeUtc) _Tz = 0; SetTimeZone(SystemTimeZone(), true); return *this; } /// \returns the local time version of this object. LDateTime Local() const { LDateTime dt = *this; return dt.ToLocal(); } /// Gets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string. /// \returns a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 GetFormat() const { return _Format; } /// Sets the current formatting of the date, the format only effects /// the representation of the date when converted to/from a string void SetFormat ( /// a bit mask of (#GDTF_DAY_MONTH_YEAR or #GDTF_MONTH_DAY_YEAR or #GDTF_YEAR_MONTH_DAY) and (#GDTF_12HOUR or #GDTF_24HOUR) uint16 f ) { _Format = f; } /// \returns zero based index of weekday, or -1 if not found. static int IsWeekDay(const char *s); /// \returns zero based index of month, or -1 if not found. static int IsMonth(const char *s); /// The default format for the date when formatted as a string static uint16 GetDefaultFormat(); /// Sets the default format for the date when formatted as a string static void SetDefaultFormat(uint16 f) { DefaultFormat = f; } /// Gets the data and time as a LString LString Get() const; /// Gets the date and time as a string /// \sa LDateTime::GetFormat() void Get(char *Str, size_t SLen) const; /// Gets the data and time as a 64 bit int (os specific) bool Get(uint64 &s) const; /// Gets just the date as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetDate(char *Str, size_t SLen) const; /// Gets just the date as a LString /// \sa LDateTime::GetFormat() LString GetDate() const; /// Gets just the time as a string /// \sa LDateTime::GetFormat() /// \returns The number of characters written to 'Str' int GetTime(char *Str, size_t SLen) const; /// Gets just the time as a LString /// \sa LDateTime::GetFormat() LString GetTime() const; /// Returns the 64bit timestamp. uint64 Ts() const; + /// Get the timestamp in a format compatible with the current operating system APIs. + /// \returns the timestamp or zero on error. + uint64_t OsTime() const; + /// Get the current time... static LDateTime Now(); /// Sets the date and time to the system clock - void SetNow(); + LDateTime &SetNow(); /// Parses a date time from a string /// \sa LDateTime::GetFormat() bool Set(const char *Str); /// Sets the date and time from a 64 bit int (os specific) bool Set(uint64 s); bool SetUnix(uint64 s); // Assume unix timestamp (seconds since 1/1/1970) /// Sets the time from a time_t bool Set(time_t tt); /// Parses the date from a string /// \sa LDateTime::GetFormat() bool SetDate(const char *Str); /// Parses the time from a string /// \sa LDateTime::GetFormat() bool SetTime(const char *Str); /// Parses the date time from a free form string bool Parse(LString s); /// Describes the perios between this and 'to' in the form: /// ##d ##h ##m ##s /// Order of the dates isn't important. LString DescribePeriod(LDateTime to); + static LString DescribePeriod(double seconds); /// \returns true if 'd' is on the same day as this object bool IsSameDay(LDateTime &d) const; /// \returns true if 'd' is on the same month as this object bool IsSameMonth(LDateTime &d) const; /// \returns true if 'd' is on the same year as this object bool IsSameYear(LDateTime &d) const; /// \returns whether a year is a leap year or not bool IsLeapYear ( /// Pass a specific year here, or ignore to return if the current Date/Time is in a leap year. int Year = -1 ) const; /// Returns the day of the week as an index, 0=sun, 1=mon, 2=teus etc int DayOfWeek() const; /// \returns the number of days in the current month int DaysInMonth() const; /// Adds a number of seconds to the current date/time void AddSeconds(int64 Seconds); /// Adds a number of minutes to the current date/time void AddMinutes(int64 Minutes); /// Adds a number of hours to the current date/time void AddHours(int64 Hours); /// Adds a number of days to the current date/time bool AddDays(int64 Days); /// Adds a number of months to the current date/time void AddMonths(int64 Months); /// The system timezone including daylight savings offset in minutes, +10 would return 600 static int SystemTimeZone(bool ForceUpdate = false); /// Any daylight savings offset applied to TimeZone(), in minutes. e.g. to retreive the /// timezone uneffected by DST use TimeZone() - TimeZoneOffset(). static int SystemTimeZoneOffset(); /// Daylight savings info record struct LgiClass GDstInfo { /// Timestamp where the DST timezone changes to 'Offset' uint64 UtcTimeStamp; /// The new offset in minutes (e.g. 600 = +10 hours) int Offset; LDateTime GetLocal(); }; /// Retreives daylight savings start and end events for a given period. One event will be emitted /// for the current DST/TZ setting at the datetime specified by 'Start', followed by any changes that occur /// between that and the 'End' datetime. To retreive just the DST info for start, use NULL for end. static bool GetDaylightSavingsInfo ( /// [Out] The array to receive DST info. At minimum one record will be returned /// matching the TZ in place for the start datetime. LArray &Out, /// [In] The start date that you want DST info for. LDateTime &Start, /// [Optional In] The end of the period you want DST info for. LDateTime *End = 0 ); /// Using the DST info this will convert 'dt' from UTC to local static bool DstToLocal(LArray &Dst, LDateTime &dt); /// Decodes an email date into the current instance bool Decode(const char *In); /// Returns a month index from a month name static int MonthFromName(const char *Name); // File int Sizeof(); bool Serialize(class LFile &f, bool Write); bool Serialize(class LDom *Props, char *Name, bool Write); // operators bool operator <(const LDateTime &dt) const; bool operator <=(const LDateTime &dt) const; bool operator >(const LDateTime &dt) const; bool operator >=(const LDateTime &dt) const; bool operator ==(const LDateTime &dt) const; bool operator !=(const LDateTime &dt) const; int Compare(const LDateTime *d) const; LDateTime operator -(const LDateTime &dt); LDateTime operator +(const LDateTime &dt); int DiffMonths(const LDateTime &dt); operator uint64() { uint64 ts = 0; Get(ts); return ts; } LDateTime &operator =(uint64 ts) { Set(ts); return *this; } LDateTime &operator =(struct tm *t); LDateTime &operator =(const LDateTime &t); LDateTime &operator =(LDateTime const *t) { if (t) *this = *t; return *this; } /// LDom interface. /// /// Even though we don't inherit from a LDom class this class supports the same /// interface for ease of use. Currently there are cases where LDateTime is used /// in LArray's which don't implement calling a constructor (they init with all /// zeros). bool GetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool SetVariant(const char *Name, class LVariant &Value, char *Array = NULL); bool CallMethod(const char *Name, class LVariant *ReturnValue, LArray &Args); }; /// Time zone information struct GTimeZone { public: /// The offset from UTC float Offset; /// The name of the zone const char *Text; }; /// A list of all known timezones. extern GTimeZone GTimeZones[]; #ifdef _DEBUG LgiFunc bool LDateTime_Test(); #endif #endif 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,734 +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; // 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/LgiDefs.h b/include/lgi/common/LgiDefs.h --- a/include/lgi/common/LgiDefs.h +++ b/include/lgi/common/LgiDefs.h @@ -1,591 +1,591 @@ /** \file \author Matthew Allen \date 24/9/1999 \brief Defines and types Copyright (C) 1999-2004, Matthew Allen */ #ifndef _LGIDEFS_H_ #define _LGIDEFS_H_ #ifdef HAIKU #include #endif #include "LgiInc.h" #if defined(WIN32) && defined(__GNUC__) #define PLATFORM_MINGW #endif #include // Unsafe typedefs, for backward compatibility typedef unsigned char uchar; typedef unsigned short ushort; typedef unsigned int uint; typedef unsigned long ulong; // Length safe typedesf, use these in new code #ifdef _MSC_VER /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed __int64 int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned __int64 uint64; #ifndef _WCHAR_T_DEFINED #include #endif #pragma warning(error:4263) #ifdef _WIN64 typedef signed __int64 ssize_t; #else typedef signed int ssize_t; #endif #elif defined(LINUX) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef int64_t int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef uint64_t uint64; #elif !defined(HAIKU) /// 64-bit signed int type (size safe, guaranteed to be 64 bits) typedef signed long long int64; /// 64-bit unsigned int type (size safe, guaranteed to be 64 bits) typedef unsigned long long uint64; #endif #ifndef __cplusplus #include #if defined(_MSC_VER) && _MSC_VER<1800 typedef unsigned char bool; #define true 1 #define false 0 #else #include #endif #endif /// \brief Wide unicode char /// /// This is 16 bits on Win32 and Mac, but 32 bits on unix platforms. There are a number /// of wide character string function available for manipulating wide char strings. /// /// Firstly to convert to and from utf-8 there is: ///
    ///
  • Utf8ToWide() ///
  • WideToUtf8() ///
/// /// Wide versions of standard library functions are available: ///
    ///
  • StrchrW() ///
  • StrrchrW() ///
  • StrnchrW() ///
  • StrstrW() ///
  • StristrW() ///
  • StrnstrW() ///
  • StrnistrW() ///
  • StrcmpW() ///
  • StricmpW() ///
  • StrncmpW() ///
  • StrnicmpW() ///
  • StrcpyW() ///
  • StrncpyW() ///
  • StrlenW() ///
  • StrcatW() ///
  • HtoiW() ///
  • NewStrW() ///
  • TrimStrW() ///
  • ValidStrW() ///
  • MatchStrW() ///
#include typedef wchar_t char16; #if !WINNATIVE #ifdef UNICODE typedef char16 TCHAR; #ifndef _T #define _T(arg) L##arg #endif #else typedef char TCHAR; #ifndef _T #define _T(arg) arg #endif #endif #endif #if defined(_MSC_VER) #if _MSC_VER >= 1400 #ifdef _WIN64 typedef __int64 NativeInt; typedef unsigned __int64 UNativeInt; #else typedef _W64 int NativeInt; typedef _W64 unsigned int UNativeInt; #endif #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #else #if __LP64__ typedef int64 NativeInt; typedef uint64 UNativeInt; #else typedef int NativeInt; typedef unsigned int UNativeInt; #endif #endif /// Generic pointer to any base type. Used when addressing continuous data of /// different types. typedef union { int8_t *s8; uint8_t *u8; int16_t *s16; uint16_t *u16; int32_t *s32; uint32_t *u32; int64 *s64; uint64 *u64; NativeInt *ni; UNativeInt *uni; char *c; char16 *w; float *f; double *d; #ifdef __cplusplus bool *b; #else unsigned char *b; #endif void **vp; int i; } LPointer; // Basic macros #define limit(i,l,u) (((i)<(l)) ? (l) : (((i)>(u)) ? (u) : (i))) #define makelong(a, b) ((a)<<16 | (b&0xFFFF)) #define loword(a) (a&0xFFFF) #define hiword(a) (a>>16) #undef ABS #ifdef __cplusplus template inline T ABS(T v) { if (v < 0) return -v; return v; } #else #define ABS(v) ((v) < 0 ? -(v) : (v)) #endif /// Returns true if 'c' is an ascii character #define IsAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) /// Returns true if 'c' is a digit (number) #define IsDigit(c) ((c) >= '0' && (c) <= '9') /// Returns true if 'c' is a hexadecimal digit #define IsHexDigit(c) ( \ ((c) >= '0' && (c) <= '9') || \ ((c) >= 'a' && (c) <= 'f') || \ ((c) >= 'A' && (c) <= 'F') \ ) // Byte swapping #define LgiSwap16(a) ( (((a) & 0xff00) >> 8) | \ (((a) & 0x00ff) << 8) ) #define LgiSwap32(a) ( (((a) & 0xff000000) >> 24) | \ (((a) & 0x00ff0000) >> 8) | \ (((a) & 0x0000ff00) << 8) | \ (((a) & 0x000000ff) << 24) ) #ifdef __GNUC__ #define LgiSwap64(a) ( (((a) & 0xff00000000000000LLU) >> 56) | \ (((a) & 0x00ff000000000000LLU) >> 40) | \ (((a) & 0x0000ff0000000000LLU) >> 24) | \ (((a) & 0x000000ff00000000LLU) >> 8) | \ (((a) & 0x00000000ff000000LLU) << 8) | \ (((a) & 0x0000000000ff0000LLU) << 24) | \ (((a) & 0x000000000000ff00LLU) << 40) | \ (((a) & 0x00000000000000ffLLU) << 56) ) #else #define LgiSwap64(a) ( (((a) & 0xff00000000000000) >> 56) | \ (((a) & 0x00ff000000000000) >> 40) | \ (((a) & 0x0000ff0000000000) >> 24) | \ (((a) & 0x000000ff00000000) >> 8) | \ (((a) & 0x00000000ff000000) << 8) | \ (((a) & 0x0000000000ff0000) << 24) | \ (((a) & 0x000000000000ff00) << 40) | \ (((a) & 0x00000000000000ff) << 56) ) #endif // Asserts LgiFunc void _lgi_assert(bool b, const char *test, const char *file, int line); #ifdef _DEBUG #define LAssert(b) _lgi_assert(b, #b, __FILE__, __LINE__) #else -#define LAssert(b) while (0) +#define LAssert(b) ((void)0) #endif // Good ol NULLy #ifndef NULL #define NULL 0 #endif // Slashes and quotes #define IsSlash(c) (((c)=='/')||((c)=='\\')) #define IsQuote(c) (((c)=='\"')||((c)=='\'')) // Some objectish ones #define ZeroObj(obj) memset(&obj, 0, sizeof(obj)) #ifndef CountOf #define CountOf(array) (sizeof(array)/sizeof(array[0])) #endif #ifdef __cplusplus template void LSwap(T &a, T &b) { T tmp = a; a = b; b = tmp; } #endif #ifndef MEMORY_DEBUG #define DeleteObj(obj) if (obj) { delete obj; obj = 0; } #define DeleteArray(obj) if (obj) { delete [] obj; obj = 0; } #endif // Flags #define SetFlag(i, f) (i) |= (f) #define ClearFlag(i, f) (i) &= ~(f) #define TestFlag(i, f) (((i) & (f)) != 0) // Defines /// Enum of all the operating systems we might be running on. enum LgiOs { /// \brief Unknown OS /// \sa LGetOs LGI_OS_UNKNOWN = 0, /// \brief Windows 95, 98[se] or ME. (Not supported) /// \sa LGetOs LGI_OS_WIN9X, /// \brief 32bit NT, 2k, XP, Vista, 7, 8 or later. (XP and later supported) /// \sa LGetOs LGI_OS_WIN32, /// \brief 64bit NT, 2k, XP, Vista, 7, 8 or later. (XP and later supported) /// \sa LGetOs LGI_OS_WIN64, /// \brief BeOS/Haiku. (Somewhat supported) /// \sa LGetOs LGI_OS_HAIKU, /// \brief Linux. (Kernels v2.4 and up supported) /// \sa LGetOs LGI_OS_LINUX, /// \brief There was an Atheos port at one point. (Not supported) /// \sa LGetOs LGI_OS_MAC_OS_X, /// One higher than the maximum OS define LGI_OS_MAX, }; // Edge types enum LEdge { EdgeNone, EdgeXpSunken, EdgeXpRaised, EdgeXpChisel, EdgeXpFlat, EdgeWin7FocusSunken, EdgeWin7Sunken, }; #define DefaultSunkenEdge EdgeWin7Sunken #define DefaultRaisedEdge EdgeXpRaised // Cursors enum LCursor { /// Blank/invisible cursor LCUR_Blank, /// Normal arrow LCUR_Normal, /// Upwards arrow LCUR_UpArrow, /// Downwards arrow LCUR_DownArrow, /// Left arrow LCUR_LeftArrow, /// Right arrow LCUR_RightArrow, /// Crosshair LCUR_Cross, /// Hourglass/watch LCUR_Wait, /// Ibeam/text entry LCUR_Ibeam, /// Vertical resize (|) LCUR_SizeVer, /// Horizontal resize (-) LCUR_SizeHor, /// Diagonal resize (/) LCUR_SizeBDiag, /// Diagonal resize (\) LCUR_SizeFDiag, /// All directions resize LCUR_SizeAll, /// Vertical splitting LCUR_SplitV, /// Horziontal splitting LCUR_SplitH, /// A pointing hand LCUR_PointingHand, /// A slashed circle LCUR_Forbidden, /// Copy Drop LCUR_DropCopy, /// Copy Move LCUR_DropMove, }; // General Event Flags #define LGI_EF_LCTRL (1 << 0) #define LGI_EF_RCTRL (1 << 1) #define LGI_EF_CTRL (LGI_EF_LCTRL | LGI_EF_RCTRL) #define LGI_EF_LALT (1 << 2) #define LGI_EF_RALT (1 << 3) #define LGI_EF_ALT (LGI_EF_LALT | LGI_EF_RALT) #define LGI_EF_LSHIFT (1 << 4) #define LGI_EF_RSHIFT (1 << 5) #define LGI_EF_SHIFT (LGI_EF_LSHIFT | LGI_EF_RSHIFT) #define LGI_EF_DOWN (1 << 6) #define LGI_EF_DOUBLE (1 << 7) #define LGI_EF_CAPS_LOCK (1 << 8) #define LGI_EF_IS_CHAR (1 << 9) #define LGI_EF_IS_NOT_CHAR (1 << 10) #define LGI_EF_SYSTEM (1 << 11) // Windows key/Apple key etc // Mouse Event Flags #define LGI_EF_LEFT (1 << 16) #define LGI_EF_MIDDLE (1 << 17) #define LGI_EF_RIGHT (1 << 18) #define LGI_EF_MOVE (1 << 19) #define LGI_EF_XBTN1 (1 << 20) #define LGI_EF_XBTN2 (1 << 21) // Emit compiler warnings #define __STR2__(x) #x #define __STR1__(x) __STR2__(x) #define __LOC__ __FILE__ "(" __STR1__(__LINE__) ") : Warning: " // To use just do #pragma message(__LOC__"My warning message") // Simple definition of breakable unicode characters #define LGI_BreakableChar(c) ( \ (c) == '\n' || \ (c) == ' ' || \ (c) == '\t' || \ ( (c) >= 0x3040 && (c) <= 0x30FF ) || \ ( (c) >= 0x3300 && (c) <= 0x9FAF ) \ ) // Os metrics enum LSystemMetric { /// Get the standard window horizontal border size /// \sa LApp::GetMetric() LGI_MET_DECOR_X = 1, /// Get the standard window vertical border size including caption bar. /// \sa LApp::GetMetric() LGI_MET_DECOR_Y, /// Get the standard window caption bar height only. /// \sa LApp::GetMetric() LGI_MET_DECOR_CAPTION, /// Get the height of a single line menu bar /// \sa LApp::GetMetric() LGI_MET_MENU, /// This is non-zero if the system is theme aware LGI_MET_THEME_AWARE }; /// \brief Types of system paths available for querying /// \sa LgiGetSystemPath enum LSystemPath { LSP_ROOT, /// The location of the operating system folder /// [Win32] = e.g. C:\Windows /// [Mac] = /System /// [Linux] /boot LSP_OS, /// The system library folder /// [Win32] = e.g. C:\Windows\System32 /// [Mac] = /Library /// [Linux] = /usr/lib LSP_OS_LIB, /// A folder for storing temporary files. These files are usually /// deleted automatically later by the system. /// [Win32] = ~\Local Settings\Temp /// [Mac] = ~/Library/Caches/TemporaryItems /// [Linux] = /tmp LSP_TEMP, /// System wide application data /// [Win32] = ~\..\All Users\Application Data /// [Mac] = /System/Library /// [Linux] = /usr LSP_COMMON_APP_DATA, /// User specific application data /// [Win32] = ~\Application Data /// [Mac] = ~/Library /// [Linux] = /usr /// [Haiku] = ~/config (???) LSP_USER_APP_DATA, /// Machine + user specific application data (probably should not use) /// [Win32] = ~\Local Settings\Application Data /// [Mac] = ~/Library /// [Linux] = /usr/local LSP_LOCAL_APP_DATA, /// Desktop dir /// i.e. ~/Desktop LSP_DESKTOP, /// Home dir /// i.e. ~ LSP_HOME, /// Application install folder: /// [Win] c:\Program Files /// [Mac] /Applications /// [Linux] /usr/bin LSP_USER_APPS, /// The running application's path. /// [Mac] This doesn't include the "Contents/MacOS" part of the path inside the bundle. LSP_EXE, /// The system trash folder. LSP_TRASH, /// The app's install folder. /// [Win32] = $ExePath (sans '\Release' or '\Debug') /// [Mac/Linux] = $ExePath /// Where $ExePath is the folder the application is running from LSP_APP_INSTALL, /// The app's root folder (Where config and data should be stored) /// LOptionsFile uses LSP_APP_ROOT as the default location. /// [Win32] = ~\Application Data\Roaming\$AppName /// [Mac] = ~/Library/$AppName /// [Linux] = ~/.$AppName /// Where $AppName = LApp::GetName. /// If the given folder doesn't exist it will be created. LSP_APP_ROOT, /// This is the user's documents folder /// [Win32] ~\My Documents /// [Mac] ~\Documents LSP_USER_DOCUMENTS, /// This is the user's music folder /// [Win32] ~\My Music /// [Mac] ~\Music LSP_USER_MUSIC, /// This is the user's video folder /// [Win32] ~\My Videos /// [Mac] ~\Movies LSP_USER_VIDEO, /// This is the user's download folder /// ~\Downloads LSP_USER_DOWNLOADS, /// This is the user's links folder /// [Win32] = ~\Links /// [Mac] = ??? /// [Linux] = ??? LSP_USER_LINKS, /// User's pictures/photos folder LSP_USER_PICTURES, /// [Win32] = C:\Users\%HOME%\Pictures /// [Mac] = ??? /// [Linux] = ~\Pictures LSP_MOUNT_POINT, }; // Deprecated method defines #ifdef __GNUC__ #define DEPRECATED(func) func __attribute__ ((deprecated)) #elif defined(_MSC_VER) #define DEPRECATED(func) __declspec(deprecated) func #else #pragma message("WARNING: You need to implement DEPRECATED for this compiler") #define DEPRECATED(func) func #endif // #ifdef _DEBUG #define DeclDebugArgs , const char *_file, int _line #define PassDebugArgs , __FILE__, __LINE__ #else #define DeclDebugArgs #define PassDebugArgs #endif #define _FL __FILE__, __LINE__ #define CALL_MEMBER_FN(obj, memFn) ((obj).*(memFn)) #include "lgi/common/AutoPtr.h" #endif diff --git a/include/lgi/common/ListItemCheckBox.h b/include/lgi/common/ListItemCheckBox.h --- a/include/lgi/common/ListItemCheckBox.h +++ b/include/lgi/common/ListItemCheckBox.h @@ -1,83 +1,81 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A checkbox for a list item #ifndef __LList_ITEM_CHECKBOX_H #define __LList_ITEM_CHECKBOX_H #include "lgi/common/List.h" /// A checkbox suitable for a LListItem. class LListItemCheckBox : public LListItemColumn { public: /// Constructor LListItemCheckBox ( /// The hosting list item. LListItem *host, /// The column to draw in. int column, // The initial value. bool value = false ) : LListItemColumn(host, column) { Value(value); } void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *Col) { LSurface *pDC = Ctx.pDC; LRect c(0, 0, 10, 10); c.Offset(Ctx.x1 + ((Ctx.X()-c.X())/2), Ctx.y1 + ((Ctx.Y()-c.Y())/2)); // Box pDC->Colour(LColour(L_TEXT)); pDC->Box(&c); c.Inset(1, 1); pDC->Colour(LColour(L_WORKSPACE)); pDC->Rectangle(&c); // Value if (Value()) { pDC->Colour(LColour(L_TEXT)); pDC->Line(c.x1+1, c.y1+1, c.x2-1, c.y2-1); pDC->Line(c.x1+1, c.y1+2, c.x2-2, c.y2-1); pDC->Line(c.x1+2, c.y1+1, c.x2-1, c.y2-2); pDC->Line(c.x1+1, c.y2-1, c.x2-1, c.y1+1); pDC->Line(c.x1+1, c.y2-2, c.x2-2, c.y1+1); pDC->Line(c.x1+2, c.y2-1, c.x2-1, c.y1+2); } } void OnMouseClick(LMouse &m) { if (m.Down() && m.Left()) { LList *l = GetList(); List Sel; if (l->GetSelection(Sel) && Sel.Length()) { bool v = !Value(); Sel.Delete(GetItem()); for (auto i: Sel) { LListItemColumn *c = GetItemCol(i, GetColumn()); if (c) - { c->Value(v); - } } Value(v); - GetItem()->Update(); + GetItem()->OnColumnNotify(GetColumn(), v); } } } }; #endif diff --git a/include/lgi/common/Mail.h b/include/lgi/common/Mail.h --- a/include/lgi/common/Mail.h +++ b/include/lgi/common/Mail.h @@ -1,847 +1,835 @@ /** \file \author Matthew Allen */ #ifndef __MAIL_H #define __MAIL_H #include #include "lgi/common/Net.h" #include "lgi/common/Base64.h" #include "lgi/common/Progress.h" #include "lgi/common/Variant.h" #include "lgi/common/OAuth2.h" #include "lgi/common/Store3Defs.h" #ifndef GPL_COMPATIBLE #define GPL_COMPATIBLE 0 #endif // Defines #define MAX_LINE_SIZE 1024 #define MAX_NAME_SIZE 64 #define EMAIL_LINE_SIZE 76 // #define IsDigit(c) ((c) >= '0' AND (c) <= '9') // Mail logging defines #define MAIL_SEND_COLOUR Rgb24(0, 0, 0xff) #define MAIL_RECEIVE_COLOUR Rgb24(0, 0x8f, 0) #define MAIL_ERROR_COLOUR Rgb24(0xff, 0, 0) #define MAIL_WARNING_COLOUR Rgb24(0xff, 0x7f, 0) #define MAIL_INFO_COLOUR Rgb24(0, 0, 0) // Helper functions extern void TokeniseStrList(char *Str, List &Output, const char *Delim); extern char ConvHexToBin(char c); #define ConvBinToHex(i) (((i)<10)?'0'+(i):'A'+(i)-10) extern void DecodeAddrName(const char *Start, std::function cb, const char *DefaultDomain); extern void DecodeAddrName(const char *Start, LAutoString &Name, LAutoString &Addr, const char *DefaultDomain); extern void DecodeAddrName(const char *Start, LString &Name, LString &Addr, const char *DefaultDomain); extern int MaxLineLen(char *Text); extern char *EncodeImapString(const char *s); extern char *DecodeImapString(const char *s); extern bool UnBase64Str(LString &s); extern bool Base64Str(LString &s); extern const char *sTextPlain; extern const char *sTextHtml; extern const char *sTextXml; extern const char *sApplicationInternetExplorer; extern const char sMultipartMixed[]; extern const char sMultipartEncrypted[]; extern const char sMultipartSigned[]; extern const char sMultipartAlternative[]; extern const char sMultipartRelated[]; extern const char sAppOctetStream[]; // Classes class MailProtocol; struct MailProtocolError { int Code; LString ErrMsg; MailProtocolError() { Code = 0; } }; class MailProtocolProgress { public: uint64 Start; ssize_t Value; ssize_t Range; MailProtocolProgress() { Empty(); } void Empty() { Start = 0; Value = 0; Range = 0; } void StartTransfer(ssize_t Size) { Start = LCurrentTime(); Value = 0; Range = Size; } }; class LogEntry { LColour c; public: LArray Txt; LogEntry(LColour col); LColour GetColour() { return c; } bool Add(const char *t, ssize_t len = -1); }; /// Attachment descriptor class FileDescriptor : public LBase { protected: // Global int64 Size; char *MimeType; char *ContentId; // Read from file LFile File; LStreamI *Embeded; bool OwnEmbeded; int64 Offset; LMutex *Lock; // Write to memory uchar *Data; LAutoPtr DataStream; public: FileDescriptor(LStreamI *embed, int64 Offset, int64 Size, char *Name); FileDescriptor(char *name); FileDescriptor(char *data, int64 len); FileDescriptor(); ~FileDescriptor(); void SetLock(LMutex *l); LMutex *GetLock(); void SetOwnEmbeded(bool i); // Access functions LStreamI *GotoObject(); // Get data to read uchar *GetData(); // Get data from write int Sizeof(); char *GetMimeType() { return MimeType; } void SetMimeType(char *s) { DeleteArray(MimeType); MimeType = NewStr(s); } char *GetContentId() { return ContentId; } void SetContentId(char *s) { DeleteArray(ContentId); ContentId = NewStr(s); } // Decode MIME data to memory bool Decode(char *ContentType, char *ContentTransferEncoding, char *MimeData, int MimeDataLength); }; /// Address descriptor class AddressDescriptor { public: uint8_t Status = false; EmailAddressType CC = MAIL_ADDR_TO; LString sName; LString sAddr; AddressDescriptor(const AddressDescriptor *Copy = NULL); virtual ~AddressDescriptor(); void _Delete(); LString Print(); }; /// Base class for mail protocol implementations class MailProtocol { protected: char Buffer[4<<10]; LMutex SocketLock; LAutoPtr Socket; LOAuth2::Params OAuth2; LDom *SettingStore; bool Error(const char *file, int line, const char *msg, ...); bool Read(); bool Write(const char *Buf = NULL, bool Log = false); virtual void OnUserMessage(char *Str) {} public: // Logging LStreamI *Logger; void Log(const char *Str, LSocketI::SocketMsgType type); // Task Progress MailProtocolProgress *Items; MailProtocolProgress *Transfer; // Settings int ErrMsgId; /// \sa #L_ERROR_ESMTP_NO_AUTHS, #L_ERROR_ESMTP_UNSUPPORTED_AUTHS LString ErrMsgFmt; /// The format for the printf LString ErrMsgParam; /// The arguments for the printf LString ProgramName; LString ExtraOutgoingHeaders; List CharsetPrefs; // Object MailProtocol(); virtual ~MailProtocol(); // Methods void SetOAuthParams(LOAuth2::Params &p) { OAuth2 = p; } void SetSettingStore(LDom *store) { SettingStore = store; } /// Thread safe hard close (quit now) bool CloseSocket() { LMutex::Auto l(&SocketLock, _FL); if (Socket != NULL) return Socket->Close() != 0; return false; } void SetError(int ResourceId, const char *Fmt, const char *Param = NULL) { ErrMsgId = ResourceId; ErrMsgFmt = Fmt; ErrMsgParam = Param; } }; ///////////////////////////////////////////////////////////////////// // Mail IO parent classes /// Enable STARTTLS support (requires an SSL capable socket) #define MAIL_USE_STARTTLS 0x01 /// Use authentication #define MAIL_USE_AUTH 0x02 /// Force the use of PLAIN type authentication #define MAIL_USE_PLAIN 0x04 /// Force the use of LOGIN type authentication #define MAIL_USE_LOGIN 0x08 /// Force the use of NTLM type authentication #define MAIL_USE_NTLM 0x10 /// Secure auth #define MAIL_SECURE_AUTH 0x20 /// Use SSL #define MAIL_SSL 0x40 /// OAUTH2 #define MAIL_USE_OAUTH2 0x80 /// CRAM-MD5 #define MAIL_USE_CRAM_MD5 0x100 /// Mail sending protocol class MailSink : public MailProtocol { public: /// Connection setup/shutdown virtual bool Open ( /// The transport layer to use LSocketI *S, /// The host to connect to const char *RemoteHost, /// The local domain const char *LocalDomain, /// The sink username (or NULL) const char *UserName, /// The sink password (or NULL) const char *Password, /// The port to connect with or 0 for default. int Port, /// Options: Use any of #MAIL_SSL, #MAIL_USE_STARTTLS, #MAIL_SECURE_AUTH, #MAIL_USE_PLAIN, #MAIL_USE_LOGIN etc or'd together. int Flags ) = 0; /// Close the connection virtual bool Close() = 0; // Commands available while connected /// Write the email's contents into the LStringPipe returned from /// SendStart and then call SendEnd to finish the transaction virtual LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0) = 0; /// Finishes the mail send virtual bool SendEnd(LStringPipe *Sink) = 0; }; struct ImapMailFlags { union { struct { uint8_t ImapAnswered : 1; uint8_t ImapDeleted : 1; uint8_t ImapDraft : 1; uint8_t ImapFlagged : 1; uint8_t ImapRecent : 1; uint8_t ImapSeen : 1; uint8_t ImapExpunged :1; }; uint16 All = 0; }; ImapMailFlags(char *init = 0) { if (init) Set(init); } LString Get() { char s[256] = ""; int ch = 0; if (ImapAnswered) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\answered "); if (ImapDeleted) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\deleted "); if (ImapDraft) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\draft "); if (ImapFlagged) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\flagged "); if (ImapRecent) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\recent "); if (ImapSeen) ch += sprintf_s(s+ch, sizeof(s)-ch, "\\seen "); if (ch == 0) return NULL; LAssert(ch < sizeof(s)); s[--ch] = 0; return s; } void Set(const char *s) { All = 0; if (!s) s = ""; while (*s) { if (*s == '/' || *s == '\\') { while (*s == '/' || *s == '\\') s++; const char *e = s; while (*e && isalpha(*e)) e++; if (!_strnicmp(s, "answered", e-s)) ImapAnswered = true; else if (!_strnicmp(s, "deleted", e-s)) ImapDeleted = true; else if (!_strnicmp(s, "draft", e-s)) ImapDraft = true; else if (!_strnicmp(s, "flagged", e-s)) ImapFlagged = true; else if (!_strnicmp(s, "recent", e-s)) ImapRecent = true; else if (!_strnicmp(s, "seen", e-s)) ImapSeen = true; s = e; } else s++; } } ImapMailFlags &operator =(const ImapMailFlags &f) { All = f.All; return *this; } bool operator ==(ImapMailFlags &f) const { return All == f.All; } bool operator !=(ImapMailFlags &f) const { return All != f.All; } }; /// A bulk mail handling class class MailTransaction { public: /// The index of the mail in the folder int Index; /// \sa #MAIL_POSTED_TO_GUI, #MAIL_EXPLICIT int Flags; // bool Delete; bool Status; bool Oversize; /// The mail protocol handler writes the email to this stream LStreamI *Stream; /// Flags used on the IMAP protocolf ImapMailFlags Imap; /// The user app can use this for whatever void *UserData; MailTransaction(); ~MailTransaction(); }; /// Return code from MailSrcCallback enum MailSrcStatus { /// Download the whole email DownloadAll, /// Download just the top part DownloadTop, /// Skip this email DownloadNone, /// About the whole receive DownloadAbort }; /// The callback function used by MailSource::Receive typedef MailSrcStatus (*MailSrcCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The size of the email about to be downloaded uint64 Size, /// If DownloadTop is returned, you can set the number of lines to retreive here int *LinesToDownload, /// The data cookie passed into MailSource::Receive void *Data ); /// The callback function used by MailSource::Receive typedef bool (*MailReceivedCallback) ( /// The currently executing transaction MailTransaction *Trans, /// The data cookie passed into MailSource::Receive void *Data ); /// Collection of callbacks called during mail receive. You should zero this /// entire object before using it. Because if someone adds new callbacks after /// you write the calling code you wouldn't want to leave some callbacks un- /// initialized. A NULL callback is ignored. struct MailCallbacks { /// The callback data void *CallbackData; /// Called before receiving mail MailSrcCallback OnSrc; /// Called after mail received MailReceivedCallback OnReceive; }; - -/* -/// Enable STARTTLS support (requires an SSL capable socket) -#define MAIL_SOURCE_STARTTLS 0x01 -/// Use authentication -#define MAIL_SOURCE_AUTH 0x02 -/// Force the use of PLAIN type authentication -#define MAIL_SOURCE_USE_PLAIN 0x04 -/// Force the use of LOGIN type authentication -#define MAIL_SOURCE_USE_LOGIN 0x08 -*/ - /// A generic mail source object class MailSource : public MailProtocol { public: /// Opens a connection to the server virtual bool Open ( /// The transport socket LSocketI *S, /// The hostname or IP of the server const char *RemoteHost, /// The port on the host to connect to int Port, /// The username for authentication const char *User, /// The password for authentication const char *Password, /// [Optional] Persistant storage of settings LDom *SettingStore, /// [Optional] Flags: #MAIL_SOURCE_STARTTLS, #MAIL_SOURCE_AUTH, #MAIL_SOURCE_USE_PLAIN, #MAIL_SOURCE_USE_LOGIN int Flags = 0) = 0; /// Closes the connection virtual bool Close() = 0; /// Returns the number of messages available on the server virtual ssize_t GetMessages() = 0; /// Receives a list of messages from the server. virtual bool Receive ( /// An array of messages to receive. The MailTransaction objects contains the index of the message to receive /// and various status values returned after the operation. LArray &Trans, /// An optional set of callback functions. MailCallbacks *Callbacks = 0 ) = 0; /// Deletes a message on the server virtual bool Delete(int Message) = 0; /// Gets the size of the message on the server virtual int Sizeof(int Message) = 0; /// Gets the size of all the messages on the server virtual bool GetSizes(LArray &Sizes) { return false; } /// Gets the unique identifier of the message virtual bool GetUid(int Message, char *Id, int IdLen) = 0; /// Gets the unique identifiers of a list of messages virtual bool GetUidList(LString::Array &Id) = 0; /// Gets the headers associated with a given message virtual LString GetHeaders(int Message) = 0; /// Sets the proxy server. e.g. HTTP mail. virtual void SetProxy(char *Server, int Port) {} }; ///////////////////////////////////////////////////////////////////// // Mail IO implementations /// SMTP implementation class MailSmtp : public MailSink { protected: bool ReadReply(const char *Str, LStringPipe *Pipe = 0, MailProtocolError *Err = 0); bool WriteText(const char *Str); public: MailSmtp(); ~MailSmtp(); bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); bool SendToFrom(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); LStringPipe *SendData(MailProtocolError *Err = 0); LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(LStringPipe *Sink); // bool Send(MailMessage *Msg, bool Mime = false); }; class MailSendFolder : public MailSink { class MailPostFolderPrivate *d; public: MailSendFolder(char *Path); ~MailSendFolder(); bool Open(LSocketI *S, const char *RemoteHost, const char *LocalDomain, const char *UserName, const char *Password, int Port = 0, int Flags = 0); bool Close(); LStringPipe *SendStart(List &To, AddressDescriptor *From, MailProtocolError *Err = 0); bool SendEnd(LStringPipe *Sink); }; class MailPop3 : public MailSource { protected: bool ReadReply(); LString ReadMultiLineReply(); int GetInt(); bool MailIsEnd(char *Ptr, ssize_t Len); bool ListCmd(const char *Cmd, LHashTbl, bool> &Results); const char *End; const char *Marker; int Messages; public: MailPop3(); ~MailPop3(); // Connection bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override; bool Close() override; // Commands available while connected ssize_t GetMessages() override; bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override; bool Delete(int Message) override; int Sizeof(int Message) override; bool GetSizes(LArray &Sizes) override; bool GetUid(int Message, char *Id, int IdLen) override; bool GetUidList(LString::Array &Id) override; LString GetHeaders(int Message) override; }; class MailReceiveFolder : public MailSource { protected: class MailReceiveFolderPrivate *d; public: MailReceiveFolder(char *Path); ~MailReceiveFolder(); // Connection bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override; bool Close() override; // Commands available while connected ssize_t GetMessages() override; bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override; bool Delete(int Message) override; int Sizeof(int Message) override; bool GetUid(int Message, char *Id, int IdLen) override; bool GetUidList(LString::Array &Id) override; LString GetHeaders(int Message) override; }; class MailPhp : public MailSource { protected: class MailPhpPrivate *d; bool Get(LSocketI *S, char *Uri, LStream &Out, bool ChopDot); public: MailPhp(); ~MailPhp(); // Connection bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override; bool Close() override; // Commands available while connected ssize_t GetMessages() override; bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override; bool Delete(int Message) override; int Sizeof(int Message) override; bool GetSizes(LArray &Sizes) override; bool GetUid(int Message, char *Id, int IdLen) override; bool GetUidList(LString::Array &Id) override; LString GetHeaders(int Message) override; void SetProxy(char *Server, int Port) override; }; class MailImapFolder { friend class MailIMap; friend struct ImapThreadPrivate; char Sep; char *Path; public: bool NoSelect; bool NoInferiors; bool Marked; int Exists; int Recent; int Deleted; // int UnseenIndex; MailImapFolder(); virtual ~MailImapFolder(); char *GetPath(); void SetPath(const char *s); char *GetName(); void SetName(const char *s); char GetSep() { return Sep; } void operator =(LHashTbl,int> &v); }; class MailIMap : public MailSource { protected: class MailIMapPrivate *d; char Buf[2048]; List Uid; LStringPipe ReadBuf; List Dialog; void ClearDialog(); void ClearUid(); bool FillUidList(); bool WriteBuf(bool ObsurePass = false, const char *Buffer = 0, bool Continuation = false); bool ReadResponse(int Cmd = -1, bool Plus = false); bool Read(LStreamI *Out = 0, int Timeout = -1); bool ReadLine(); bool IsResponse(const char *Buf, int Cmd, bool &Ok); void CommandFinished(); public: typedef LHashTbl,LString> StrMap; struct StrRange { ssize_t Start, End; void Set(ssize_t s, ssize_t e) { Start = s; End = e; } ssize_t Len() { return End - Start; } }; // Typedefs struct Untagged { LString Cmd; LString Param; int Id; }; /// This callback is used to notify the application using this object of IMAP fetch responses. /// \returns true if the application wants to continue reading and has taken ownership of the strings in "Parts". typedef bool (*FetchCallback) ( /// The IMAP object class MailIMap *Imap, /// The message sequence number uint32_t Msg, /// The fetch parts (which the callee needs to own if returning true) StrMap &Parts, /// The user data passed to the Fetch function void *UserData ); // Object MailIMap(); ~MailIMap(); // Mutex bool Lock(const char *file, int line); bool LockWithTimeout(int Timeout, const char *file, int line); void Unlock(); // General char GetFolderSep(); char *EncodePath(const char *Path); char *GetCurrentPath(); bool GetExpungeOnExit(); void SetExpungeOnExit(bool b); bool ServerOption(char *Opt); bool IsOnline(); const char *GetWebLoginUri(); void SetParentWindow(LViewI *wnd); void SetCancel(LCancel *Cancel); ssize_t ParseImapResponse(char *Buffer, ssize_t BufferLen, LArray &Ranges, int Names); // Connection bool Open(LSocketI *S, const char *RemoteHost, int Port, const char *User, const char *Password, LDom *SettingStore, int Flags = 0) override; bool Close() override; // Non-threadsafe soft close (normal operation) bool GetCapabilities(LArray &s); // Commands available while connected bool Receive(LArray &Trans, MailCallbacks *Callbacks = 0) override; ssize_t GetMessages() override; bool Delete(int Message) override; bool Delete(bool ByUid, const char *Seq); int Sizeof(int Message) override; bool GetSizes(LArray &Sizes) override; bool GetUid(int Message, char *Id, int IdLen) override; bool GetUidList(LString::Array &Id) override; LString GetHeaders(int Message) override; char *SequenceToString(LArray *Seq); // Imap specific commands /// This method wraps the imap FETCH command /// \returns the number of records accepted by the callback fn int Fetch ( /// True if 'Seq' is a UID, otherwise it's a sequence bool ByUid, /// The sequence number or UID const char *Seq, /// The parts to retrieve const char *Parts, /// Data is returned to the caller via this callback function FetchCallback Callback, /// A user defined param to pass back to the 'Callback' function. void *UserData, /// [Optional] The raw data received will be written to this stream if provided, else NULL. LStreamI *RawCopy = NULL, /// [Optional] The rough size of the fetch... used to pre-allocate a buffer to receive data. int64 SizeHint = -1, /// [Optional] An error object LError *Error = NULL ); /// Appends a message to the specified folder bool Append ( /// The folder to write to const char *Folder, /// [Optional] Flags for the message ImapMailFlags *Flags, /// The rfc822 body of the message const char *Msg, /// [Out] The UID of the message appended (if known, can be empty if not known) LString &NewUid ); bool GetFolders(LArray &Folders); bool SelectFolder(const char *Path, StrMap *Values = 0); char *GetSelectedFolder(); int GetMessages(const char *Path); bool CreateFolder(MailImapFolder *f); bool DeleteFolder(const char *Path); bool RenameFolder(const char *From, const char *To); bool SetFolderFlags(MailImapFolder *f); /// Expunges (final delete) any deleted messages the current folder. bool ExpungeFolder(); // Uid methods bool CopyByUid(LArray &InUids, const char *DestFolder); bool SetFlagsByUid(LArray &Uids, const char *Flags); /// Idle processing... /// \returns true if something happened bool StartIdle(); // bool OnIdle(int Timeout, LArray &Resp); bool OnIdle(int Timeout, LString::Array &Resp); bool FinishIdle(); bool Poll(int *Recent = 0, LArray *New = 0); bool Status(char *Path, int *Recent); bool Search(bool Uids, LArray &SeqNumbers, const char *Filter); // Utility static bool Http(LSocketI *S, LAutoString *OutHeaders, LAutoString *OutBody, int *StatusCode, const char *InMethod, const char *InUri, const char *InHeaders, const char *InBody); }; #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,571 +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); }; /// 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 Key; - int Id; + int Vkey = 0; + int Chr = 0; + int Id = 0; public: - LAccelerator(int flags, int key, int id); + 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/Message.h b/include/lgi/common/Message.h --- a/include/lgi/common/Message.h +++ b/include/lgi/common/Message.h @@ -1,313 +1,314 @@ #ifndef _GMESSAGE_H_ #define _GMESSAGE_H_ #ifdef HAIKU #include #endif enum LgiMessages { #if defined(__GTK_H__) /// Base point for system messages. M_SYSTEM = 900, /// Message that indicates the user is trying to close a top level window. M_CLOSE, /// Implemented to handle paint requests in the GUI thread. M_X11_REPARENT, /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEENTER, /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, M_MENU, M_COMMAND, M_DRAG_DROP, M_TRAY_NOTIFY, M_CUT, M_COPY, M_PASTE, M_GTHREADWORK_COMPELTE, /// Implemented to handle timer events in the GUI thread. M_PULSE, M_SET_VISIBLE, M_CAPTURE_PULSE, /// Sent from a worker thread when calling LTextLabel::Name M_TEXT_UPDATE_NAME, #elif defined(WINNATIVE) // [WM_APP:WM_APP+200] is reserved for LGI itself. // [WM_APP+200:0xBFFF] is reserved for LGI applications. M_CUT = WM_CUT, M_COPY = WM_COPY, M_PASTE = WM_PASTE, M_COMMAND = WM_COMMAND, M_CLOSE = WM_CLOSE, // wParam = bool Inside; // is the mouse inside the client area? // lParam = MAKELONG(x, y); // mouse location M_MOUSEENTER = WM_APP, M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, // wParam = void // lParam = (MSG*) Msg; M_TRANSLATE_ACCELERATOR, // None M_TRAY_NOTIFY, // lParam = Style M_SET_WND_STYLE, // lParam = LScrollBar *Obj M_SCROLL_BAR_CHANGED, // lParam = HWND of window under mouse // This is only sent for non-LGI window in our process // because we'd get WM_MOUSEMOVE's for our own windows M_HANDLEMOUSEMOVE, // Calls SetWindowPlacement... M_SET_WINDOW_PLACEMENT, // Set the visibility of the window M_SET_VISIBLE, /// Sent from a worker thread when calling LTextLabel::Name M_TEXT_UPDATE_NAME, /// Send when a window is losing it's mouse capture. Usually // because something else has requested it. M_LOSING_CAPTURE, // A: code M_DIALOG_END_MODAL, #elif defined(LGI_SDL) || defined(HAIKU) /// Minimum value for application defined message ID's M_MOUSEENTER = 900, M_MOUSEEXIT, M_COMMAND, M_CUT, M_COPY, M_PASTE, M_PULSE, M_SET_VISIBLE, M_MOUSE_CAPTURE_POLL, M_TEXT_UPDATE_NAME, M_ASSERT_DLG, M_CLOSE, #if defined(HAIKU) M_HANDLE_IN_THREAD, // A = (LMessage::InThreadCb*)Cb; M_LWINDOW_DELETE, M_LMENUITEM_ENABLE, M_LSUBMENU_APPENDITEM, #endif #elif defined(MAC) /// Base point for system messages. M_SYSTEM = 0, /// Message that indicates the user is trying to close a top level window. M_CLOSE = (M_SYSTEM+92), /// \brief Mouse enter event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEENTER = (M_SYSTEM+900), /// \brief Mouse exit event /// /// a = bool Inside; // is the mouse inside the client area?\n /// b = MAKELONG(x, y); // mouse location M_MOUSEEXIT, // return (bool) M_WANT_DIALOG_PROC, M_MENU, M_COMMAND, M_DRAG_DROP, M_TRAY_NOTIFY, M_CUT, M_COPY, M_PASTE, M_PULSE, M_MOUSE_TRACK_UP, M_GTHREADWORK_COMPELTE, M_TEXT_UPDATE_NAME, M_SET_VISIBLE, M_SETPOS, // A=(LRect*)Rectangle, B=(LView*)this M_ASSERT_DLG, M_QUIT, M_ABOUT, M_PERFERENCES, M_HIDE, M_DESTROY, - #else - - #endif M_DESCRIBE, M_CHANGE, M_DELETE, M_TABLE_LAYOUT, M_URL, M_LOG_TEXT, M_ASSERT_UI, // A=(LString*)Msg - M_INVALIDATE, // A=(LRect*)Rectangle, B=(LView*)this - M_RESIZE_TO_CONTENT, // LItemContainer - M_SCROLL_TO, // LTreeItem->LTree + M_INVALIDATE, // A=(LRect*)Rectangle, B=(LView*)this + M_RESIZE_TO_CONTENT, // A=(int)Border (used by LItemContainer) + M_SCROLL_TO, // Sent from LTreeItem to LTree M_SET_SCROLL, // LScrollBar - M_JOBS_LOADED, // LHtml - M_THREAD_COMPLETED, // A=(LThread*)Thread + M_JOBS_LOADED, // LHtml + M_THREAD_COMPLETED, // A=(LThread*)Thread + M_SET_CTRL_NAME, // A=(int)CtrlId, B=(LString*)Name + M_SET_CTRL_ENABLE, // A=(int)CtrlId, B=(bool)Enabled + M_SET_CTRL_VISIBLE, // A=(int)CtrlId, B=(bool)Visible #ifdef WINDOWS M_USER = WM_APP + 200 #else M_USER = 1000 #endif }; class LgiClass LMessage #ifdef HAIKU : public BMessage #endif { public: #if defined(WINNATIVE) typedef LPARAM Param; typedef LRESULT Result; #elif defined(LGI_SDL) typedef void *Param; typedef NativeInt Result; #else typedef NativeInt Param; typedef NativeInt Result; #endif #if !defined(LGI_SDL) && !defined(HAIKU) int m; #endif #if defined(LGI_SDL) SDL_Event Event; struct EventParams { Param a, b; EventParams(Param A, Param B) { a = A; b = B; } }; #elif defined(WINNATIVE) HWND hWnd; WPARAM a; LPARAM b; #elif !defined(HAIKU) Param a; Param b; #endif #if LGI_COCOA #if __OBJC__ NSEvent *event; #else void *event; #endif #endif #if defined(HAIKU) - static constexpr const char *PropA = "lgiA"; - static constexpr const char *PropB = "lgiB"; - static constexpr const char *PropView = "lgiView"; - static constexpr const char *PropCallback = "lgiCallback"; + static constexpr char *PropA = "lgiA"; + static constexpr char *PropB = "lgiB"; + static constexpr char *PropView = "lgiView"; + static constexpr char *PropCallback = "lgiCallback"; + static constexpr char *PropNames[2] = {"lgi_a", "lgi_b"}; typedef std::function InThreadCb; #endif LMessage() { #if defined(LGI_SDL) Set(0, 0, 0); #else #if defined(WINNATIVE) hWnd = 0; #endif #if !defined(LGI_SDL) && !defined(HAIKU) m = 0; a = 0; b = 0; #endif #endif #if LGI_COCOA event = NULL; #endif } LMessage ( int M, #if defined(WINNATIVE) WPARAM A = 0, LPARAM B = 0 #else Param A = 0, Param B = 0 #endif ) { #if defined(WINNATIVE) hWnd = 0; #endif #if LGI_COCOA event = NULL; #endif Set(M, A, B); } #if defined(LGI_SDL) || defined(HAIKU) int Msg(); Param A(); Param B(); #else int Msg() { return m; } Param A() { return a; } Param B() { return b; } #endif void Set(int m, Param a, Param b); bool Send(class LViewI *View); }; #ifdef LINUX extern LMessage CreateMsg(int m, int a = 0, int b = 0); #else #define CreateMsg(m, a, b) LMessage(m, a, b) #endif #endif diff --git a/include/lgi/common/Mru.h b/include/lgi/common/Mru.h --- a/include/lgi/common/Mru.h +++ b/include/lgi/common/Mru.h @@ -1,70 +1,70 @@ #ifndef __GMRU_H #define __GMRU_H #include #ifdef HAS_PROPERTIES #include "GProperties.h" #endif #include "lgi/common/FileSelect.h" // Message defines #define IDM_OPEN 15000 #define IDM_SAVEAS 15001 // Classes class LgiClass LMru { private: class LMruPrivate *d; void _Update(); protected: virtual bool _OpenFile(const char *File, bool ReadOnly); virtual bool _SaveFile(const char *File); - virtual char *_GetCurFile(); + virtual const char *_GetCurFile(); virtual void GetFileTypes(LFileSelect *Dlg, bool Write); virtual LFileType *GetSelectedType(); void DoFileDlg(LFileSelect &Select, bool Open, std::function OnSelect); /// This method converts the storage reference (which can contain user/pass credentials) /// between the display, raw and stored forms. Display and Raw are used at runtime and /// the stored form is written to disk. /// /// The implementor should fill in any NULL strings by converting from the supplied forms. /// The caller must supply either the Raw or Stored form. virtual bool SerializeEntry ( /// The displayable version of the reference (this should have any passwords blanked out) LString *Display, /// The form passed to the client software to open/save. (passwords NOT blanked) LString *Raw, /// The form safe to write to disk, if a password is present it must be encrypted. LString *Stored ); public: LMru(); virtual ~LMru(); // Impl bool Set(LSubMenu *parent, int size = -1); const char *AddFile(const char *FileName, bool Update = true); void RemoveFile(const char *FileName, bool Update = true); LMessage::Result OnEvent(LMessage *Msg); void OnCommand(int Cmd, std::function OnStatus); // Serialization bool Serialize(LDom *Store, const char *Prefix, bool Write); #ifdef HAS_PROPERTIES bool Serialize(ObjProperties *Store, char *Prefix, bool Write); #endif // Events virtual bool OpenFile(const char *FileName, bool ReadOnly) = 0; virtual bool SaveFile(const char *FileName) = 0; }; #endif diff --git a/include/lgi/common/ProgressDlg.h b/include/lgi/common/ProgressDlg.h --- a/include/lgi/common/ProgressDlg.h +++ b/include/lgi/common/ProgressDlg.h @@ -1,118 +1,119 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A progress window #ifndef __LProgressDlg_H #define __LProgressDlg_H #include "lgi/common/TextLabel.h" #include "lgi/common/TableLayout.h" #include "lgi/common/ProgressView.h" #include "lgi/common/Button.h" /// Progress window pane, tracks one task. class LProgressDlg; class LgiClass LProgressPane : public Progress, public LLayout { friend class LProgressDlg; Progress *Ref = NULL; LProgressDlg *Dlg = NULL; protected: LTableLayout *t = NULL; LTextLabel *Desc = NULL, *ValText = NULL, *Rate = NULL, *Remaining = NULL; LProgressView *Bar; LButton *But = NULL; bool UiDirty = false; public: LProgressPane(LProgressDlg *dlg); ~LProgressPane(); void SetDescription(const char *d) override; bool SetRange(const LRange &r) override; int64 Value() override { return Progress::Value(); } void Value(int64 v) override; LFont *GetFont() override; void UpdateUI(); + void SetStartTs(); LProgressPane &operator++(int); LProgressPane &operator--(int); void OnCreate() override; int OnNotify(LViewI *Ctrl, LNotification n) override; void OnPaint(LSurface *pDC) override; void OnPosChange() override; }; /// Progress dialog class LgiClass LProgressDlg : public LDialog, public Progress { friend class LProgressPane; protected: uint64 Ts, Timeout; LArray Panes; bool CanCancel; void Resize(); void TimeCheck(); public: /// Constructor LProgressDlg ( /// The parent window LView *parent = NULL, /// Specify a timeout to become visible (in ms) uint64 timeout = 0 ); ~LProgressDlg(); /// Adds a pane. By default there is one pane. LProgressPane *Push(); /// Pops off a pane void Pop(LProgressPane *p = 0); /// Gets the pane at index 'i' LProgressPane *ItemAt(int i); /// Set ability to cancel void SetCanCancel(bool cc); /// Returns the description of the first pane LString GetDescription() override; /// Sets the description of the first pane void SetDescription(const char *d) override; /// Returns the upper and lower limits of the first pane LRange GetRange() override; /// Sets the upper and lower limits of the first pane bool SetRange(const LRange &r) override; /// Returns the scaling factor of the first pane double GetScale() override; /// Sets the scaling factor of the first pane void SetScale(double s) override; /// Returns the current value of the first pane int64 Value() override; /// Sets the current value of the first pane void Value(int64 v) override; /// Returns the type description of the first pane LString GetType() override; /// Sets the type description of the first pane void SetType(const char *t) override; /// Returns whether the user has cancelled the operation bool IsCancelled() override; LProgressDlg &operator++(int); LProgressDlg &operator--(int); int OnNotify(LViewI *Ctrl, LNotification n) override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; void OnCreate() override; void OnPosChange() override; bool OnRequestClose(bool OsClose) override; void OnPulse() override; }; #endif diff --git a/include/lgi/common/RefCount.h b/include/lgi/common/RefCount.h --- a/include/lgi/common/RefCount.h +++ b/include/lgi/common/RefCount.h @@ -1,155 +1,160 @@ #ifndef _REF_COUNT_H_ #define _REF_COUNT_H_ #include "lgi/common/LgiUiBase.h" class LRefCount { #if defined(_WIN32) - LONG _Count; + LONG _Count; #else - int _Count; + int _Count; #endif - bool _DebugTrace; + bool _DebugTrace; public: #ifdef _DEBUG int _GetCount() { return _Count; } #endif LRefCount(bool trace = false) { _Count = 0; _DebugTrace = trace; if (_DebugTrace) LgiTrace("%s:%i - LRefCount.Construct=%i\n", _FL, _Count); } void SetDebug(bool b) { _DebugTrace = b; } virtual ~LRefCount() { if (_DebugTrace) LgiTrace("%s:%i - ~LRefCount=%i\n", _FL, _Count); LAssert(_Count == 0); } - virtual void AddRef() + // Can't be 'AddRef' because it would conflict with + // LDragDropSource::AddRef/IEnumFORMATETC::AddRef + virtual void IncRef() { #if defined(_WIN32) InterlockedIncrement(&_Count); #elif defined(__GNUC__) __sync_fetch_and_add(&_Count, 1); #else #error "Impl me." _Count++; #endif + if (_DebugTrace) LgiTrace("%s:%i - LRefCount.AddRef=%i\n", _FL, _Count); } virtual bool DecRef() { if (_DebugTrace) LgiTrace("%s:%i - LRefCount.DecRef=%i\n", _FL, _Count); - LAssert(_Count > 0) - ; + + LAssert(_Count > 0); + #if defined(_WIN32) if (InterlockedDecrement(&_Count) == 0) #elif defined(__GNUC__) if (__sync_sub_and_fetch(&_Count, 1) == 0) #else #error "Impl me." if (--_Count == 0) #endif { delete this; return true; } + return false; } }; template class LAutoRefPtr { T *Ptr; bool Debug; public: - LAutoRefPtr(T *ptr = 0, bool debug = false) + LAutoRefPtr(T *ptr = NULL, bool debug = false) { Ptr = ptr; Debug = debug; if (Ptr) - Ptr->AddRef(); + Ptr->IncRef(); } ~LAutoRefPtr() { Empty(); } void Swap(LAutoRefPtr &p) { LSwap(Ptr, p.Ptr); } operator T*() { return Ptr; } T *operator->() const { LAssert(Ptr != NULL); return Ptr; } void Empty() { if (Ptr) { #ifdef _DEBUG if (Debug) printf("LAutoRefPtr.Empty %p %i->%i\n", Ptr, Ptr->_GetCount(), Ptr->_GetCount()-1); #endif Ptr->DecRef(); Ptr = NULL; } } LAutoRefPtr &operator =(T *p) { Empty(); Ptr = p; if (Ptr) { #ifdef _DEBUG if (Debug) printf("LAutoRefPtr.Assign %p %i->%i\n", Ptr, Ptr->_GetCount(), Ptr->_GetCount()+1); #endif - Ptr->AddRef(); + Ptr->IncRef(); } return *this; } LAutoRefPtr &operator =(const LAutoRefPtr &refptr) { Empty(); Ptr = refptr.Ptr; Debug = refptr.Debug; if (Ptr) { #ifdef _DEBUG if (Debug) printf("LAutoRefPtr.ObjAssign %p %i->%i\n", Ptr, Ptr->_GetCount(), Ptr->_GetCount()+1); #endif - Ptr->AddRef(); + Ptr->IncRef(); } return *this; } }; #endif \ No newline at end of file diff --git a/include/lgi/common/Store3.h b/include/lgi/common/Store3.h --- a/include/lgi/common/Store3.h +++ b/include/lgi/common/Store3.h @@ -1,708 +1,726 @@ /// \file /// \author Matthew Allen, fret@memecode.com #ifndef _MAIL_STORE_H_ #define _MAIL_STORE_H_ #include #include "Mail.h" #include "Store3Defs.h" #undef GetObject /* Handling of attachments in the Store3 API ----------------------------------------- Given the mail object ptr: LDataI *m; Query that the mail for it's root mime segment: LDataI *Seg = dynamic_cast(m->GetObj(FIELD_MIME_SEG)); Query a seg for it's children: GDataIt Children = Seg->GetList(FIELD_MIME_SEG); LDataI *FirstChild = Children->First(); Access segment's charset and mimetype: char *Charset = Seg->GetStr(FIELD_CHARSET); char *MimeType = Seg->GetStr(FIELD_MIME_TYPE); Get/Set the segment for it's content: GAutoStreamI Data = Seg->GetStream(_FL); // get Seg->SetStream(new MyStream(Data)); // set Delete an mime segment: Seg->Delete(); Add a new segment somewhere in the tree, including reparenting it to another segments or mail object, even if the target parent it not attached yet: NewSeg->Save(ParentSeg); */ #include "LgiInterfaces.h" #include "lgi/common/Mime.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/Variant.h" class LDataI; class LDataFolderI; class LDataStoreI; class LDataPropI; typedef LAutoPtr LAutoStreamI; void ParseIdList(char *In, List &Out); extern const char *Store3ItemTypeToMime(Store3ItemTypes type); /// A storage event /// a = StoreId /// b = (void*)UserParam /// \sa LDataEventsI::Post #define M_STORAGE_EVENT (M_USER+0x500) /// The storage class has this property (positive properties are owned by the app #define FIELD_IS_ONLINE -100 #define FIELD_PROFILE_IMAP_LISTING -101 #define FIELD_PROFILE_IMAP_SELECT -102 #define LDATA_INT32_PROP(name, id) \ int32 Get##name() { return GetObject() ? (int32)GetObject()->GetInt(id) : OnError(_FL); } \ bool Set##name(int32 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_INT64_PROP(name, id) \ int64 Get##name() { return GetObject() ? GetObject()->GetInt(id) : OnError(_FL); } \ bool Set##name(int64 val) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_ENUM_PROP(name, id, type) \ type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \ bool Set##name(type val) { return GetObject() ? GetObject()->SetInt(id, (int)val) >= Store3Delayed : OnError(_FL); } #define LDATA_STR_PROP(name, id) \ const char *Get##name() { auto o = GetObject(); return o ? o->GetStr(id) : (const char*)OnError(_FL); } \ bool Set##name(const char *val) { return GetObject() ? GetObject()->SetStr(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_DATE_PROP(name, id) \ const LDateTime *Get##name() { return (GetObject() ? GetObject()->GetDate(id) : (LDateTime*)OnError(_FL)); } \ bool Set##name(const LDateTime *val) { return GetObject() ? GetObject()->SetDate(id, val) >= Store3Delayed : OnError(_FL); } #define LDATA_INT_TYPE_PROP(type, name, id, defaultVal) \ type Get##name() { return (type) (GetObject() ? GetObject()->GetInt(id) : OnError(_FL)); } \ bool Set##name(type val = defaultVal) { return GetObject() ? GetObject()->SetInt(id, val) >= Store3Delayed : OnError(_FL); } /// This class is an interface to a collection of objects (NOT thread-safe). typedef std::function LIteratorProgressFn; template class LDataIterator { public: virtual ~LDataIterator() {} /// \returns an empty object of the right type. virtual T Create(LDataStoreI *Store) = 0; /// \returns the first object (NOT thread-safe) virtual T First() = 0; /// \returns the first object (NOT thread-safe) virtual T Next() = 0; /// \returns the number of items in the collection virtual size_t Length() = 0; /// \returns the 'nth' item in the collection virtual T operator [](size_t idx) = 0; /// \returns the index of the given item in the collection virtual ssize_t IndexOf(T n, bool NoAssert = false) = 0; /// Deletes an item /// \returns true on success virtual bool Delete(T ptr) = 0; /// Inserts an item at 'idx' or the end if not supplied. /// \returns true on success virtual bool Insert(T ptr, ssize_t idx = -1, bool NoAssert = false) = 0; /// Clears list, but doesn't delete objects. /// \returns true on success virtual bool Empty() = 0; /// Deletes all the objects from memory /// \returns true on success virtual bool DeleteObjects() = 0; /// Gets the current loading/loaded state. virtual Store3State GetState() = 0; /// Sets the progress function virtual void SetProgressFn(LIteratorProgressFn cb) = 0; }; typedef LDataIterator *GDataIt; #define EmptyVirtual(t) LAssert(0); return t #define Store3CopyDecl bool CopyProps(LDataPropI &p) override #define Store3CopyImpl(Cls) bool Cls::CopyProps(LDataPropI &p) /// A generic interface for getting / setting properties. class LDataPropI : virtual public LDom { LDataPropI &operator =(LDataPropI &p) = delete; public: virtual ~LDataPropI() {} /// Copy all the values from 'p' over to this object virtual bool CopyProps(LDataPropI &p) { return false; } /// Gets a string property virtual const char *GetStr(int id) { EmptyVirtual(NULL); } /// Sets a string property, it will make a copy of the string, so you /// still retain ownership of the string you're passing in. virtual Store3Status SetStr(int id, const char *str) { EmptyVirtual(Store3Error); } /// Gets an integer property. virtual int64 GetInt(int id) { EmptyVirtual(false); } /// Sets an integer property. virtual Store3Status SetInt(int id, int64 i) { EmptyVirtual(Store3Error); } /// Gets a date property virtual const LDateTime *GetDate(int id) { EmptyVirtual(NULL); } /// Sets a date property virtual Store3Status SetDate(int id, const LDateTime *i) { EmptyVirtual(Store3Error); } /// Gets a variant virtual const LVariant *GetVar(int id) { EmptyVirtual(NULL); } /// Sets a variant property virtual Store3Status SetVar(int id, LVariant *i) { EmptyVirtual(Store3Error); } /// Gets a sub object pointer virtual LDataPropI *GetObj(int id) { EmptyVirtual(NULL); } /// Sets a sub object pointer virtual Store3Status SetObj(int id, LDataPropI *i) { EmptyVirtual(Store3Error); } /// Gets an iterator interface to a list of sub-objects. virtual GDataIt GetList(int id) { EmptyVirtual(NULL); } /// Set the mime segments virtual Store3Status SetRfc822(LStreamI *Rfc822Msg) { LAssert(!"Pretty sure you should be implementing this") ; return Store3Error; } }; #pragma warning(default:4263) class LDataUserI { friend class LDataI; LDataI *Object; public: LString SetterRef; LDataUserI(); virtual ~LDataUserI(); LDataI *GetObject(); - virtual bool SetObject(LDataI *o, const char *File, int Line); + virtual bool SetObject + ( + /// The client side object to link with this object. + LDataI *o, + /// In the special case that 'Object' is being deleted, and is an + /// orphaned objects, SetObject must not attempt to delete 'Object' + /// a second time. This flag allows for that case. + bool InDestuctor, + /// The file name of the caller + const char *File, + /// The line number of the caller + int Line + ); }; /// This class is an interface between the UI and the backend for things /// like email, contacts, calendar events, groups and filters class LDataI : virtual public LDataPropI { friend class LDataUserI; LDataI &operator =(LDataI &p) = delete; public: LDataUserI *UserData; LDataI() { UserData = NULL; } virtual ~LDataI() { if (UserData) UserData->Object = NULL; } /// Returns the type of object /// \sa MAGIC_MAIL and it's like virtual uint32_t Type() = 0; /// \return true if the object has been written to disk. By default the object /// starts life in memory only. virtual bool IsOnDisk() = 0; /// \return true if the object is owned by some other object... virtual bool IsOrphan() = 0; /// \returns size of object on disk virtual uint64 Size() = 0; /// Saves the object to disk. If this function fails the object /// is deleted, so if it returns false, stop using the ptr you /// have to it. /// \returns true if successful. virtual Store3Status Save(LDataI *Parent = 0) = 0; /// Delete the on disk representation of the object. This will cause LDataEventsI::OnDelete /// to be called after which this object will be freed from heap memory automatically. So /// Once you call this method assume the object pointed at is gone. virtual Store3Status Delete(bool ToTrash = true) = 0; /// Gets the storage that this object belongs to. virtual LDataStoreI *GetStore() = 0; /// \returns a stream to access the data stored at this node. The caller /// is responsible to free the stream when finished with it. /// For Type == MAGIC_ATTACHMENT: the decoded body of the MIME segment. /// For Type == MAGIC_MAIL: is an RFC822 encoded version of the email. /// For other objects the stream is not defined. virtual LAutoStreamI GetStream(const char *file, int line) = 0; /// Sets the stream, which is used during the next call to LDataI::Save, which /// also deletes the object when it's used. The caller loses ownership of the /// object passed into this function. virtual bool SetStream(LAutoStreamI stream) { return false; } /// Parses the headers of the object and updates all the metadata fields virtual bool ParseHeaders() { return false; } }; /// An interface to a folder structure class LDataFolderI : virtual public LDataI { LDataFolderI &operator =(LDataFolderI &p) = delete; public: virtual ~LDataFolderI() {} /// \returns an iterator for the sub-folders. virtual LDataIterator &SubFolders() = 0; /// \returns an iterator for the child objects virtual LDataIterator &Children() = 0; /// \returns an iterator for the fields this folder defines virtual LDataIterator &Fields() = 0; /// Deletes all child objects from disk and memory. /// \return true on success; virtual Store3Status DeleteAllChildren() { return Store3Error; } /// Frees all the memory used by children objects without deleting from disk virtual Store3Status FreeChildren() { return Store3Error; } /// Called when the user selects the folder in the UI virtual void OnSelect(bool s) {} /// Called when the user selects a relevant context menu command virtual void OnCommand(const char *Name) {} }; #pragma warning(error:4263) /// Event callback interface. Calls to these methods may be in a worker /// thread, so make appropriate locking or pass the event off to the GUI /// thread via a message. class LDataEventsI { public: virtual ~LDataEventsI() {} /// This allows the caller to pass source:line info for debugging /// It should be called prior to one of the following functions and /// expires immediately after the function call. virtual void SetContext(const char *file, int line) {} /// Posts something to the GUI thread /// \sa M_STORAGE_EVENT virtual void Post(LDataStoreI *store, void *Param) {} /// \returns the system path virtual bool GetSystemPath(int Folder, LVariant &Path) { return false; } /// \returns the options object virtual LOptionsFile *GetOptions(bool Create = false) { return 0; } /// A new item is available virtual void OnNew(LDataFolderI *parent, LArray &new_items, int pos, bool is_new) = 0; /// When an item is deleted virtual bool OnDelete(LDataFolderI *parent, LArray &items) = 0; /// When an item is moved to a new folder virtual bool OnMove(LDataFolderI *new_parent, LDataFolderI *old_parent, LArray &items) = 0; /// When an item changes virtual bool OnChange(LArray &items, int FieldHint) = 0; /// Notifcation of property change virtual void OnPropChange(LDataStoreI *Store, int Prop, LVariantType Type) {} /// Get the logging stream virtual LStreamI *GetLogger(LDataStoreI *store) { return 0; } /// Search for a object by type and name virtual bool Match(LDataStoreI *store, LDataPropI *Addr, int ObjectType, LArray &Matches) { return 0; } }; /// The virtual mail storage interface from which all mail stores inherit from. /// /// The data store should implement LDataPropI::GetInt and handle these properties: /// - FIELD_STATUS, acceptable returns value are: /// * 0 - mail store is in error. /// * 1 - mail store is ready to use / ok. /// * 2 - mail store requires upgrading to use. /// These are codified in the enum LDataStoreI::DataStoreStatus /// - FIELD_READONLY, return values are: /// * false - mail store is read/write /// * true - mail store is read only /// - FIELD_VERSION, existing return values are: /// * 3 - a 'mail3' Scribe sqlite database. /// * 10 - the Scribe IMAP implementation. /// If you are create a new mail store use, values above 10 for your implementation /// and optionally register them with Memecode for inclusion here. /// - FIELD_IS_ONLINE, optionally returned if mail store is online or not. /// - FIELD_ACCOUNT_ID, optionally return if the mail store is associated with an account. /// /// The data store may optionally implement LDataPropI::SetInt to handle this property: /// - FIELD_IS_ONLINE, acceptable values are: /// * false - take the mail store offline. /// * true - go online. /// This is currently only implemented on the IMAP mail store. class LDataStoreI : virtual public LDataPropI { public: static LHashTbl,LDataStoreI*> Map; int Id; class LDsTransaction { protected: LDataStoreI *Store; public: LDsTransaction(LDataStoreI *s) { Store = s; } virtual ~LDsTransaction() {} }; typedef LAutoPtr StoreTrans; LDataStoreI() { LAssert(LAppInst->InThread()); while (Map.Find(Id = LRand(1000))) ; Map.Add(Id, this); } virtual ~LDataStoreI() { LAssert(LAppInst->InThread()); if (!Map.Delete(Id)) LAssert(!"Delete failed."); } /// \returns size of object on disk virtual uint64 Size() = 0; /// Create a new data object that isn't written to disk yet virtual LDataI *Create(int Type) = 0; /// Get the root folder object virtual LDataFolderI *GetRoot(bool create = false) = 0; /// Move objects into a different folder. /// /// Success: /// 'Items' are owned by 'NewFolder', and not any previous folder. /// Any LDataEventsI interface owned by the data store has it's 'OnMove' method called. /// /// Failure: /// 'Item' is owned by it's previous folder. /// /// \return true on success. virtual Store3Status Move ( /// The folder to move the object to LDataFolderI *NewFolder, /// The object to move LArray &Items ) = 0; /// Deletes items, which results in either the items being moved to the local trash folder /// or the items being completely deleted if there is no local trash. The items should all /// be in the same folder and of the same type. /// /// Success: /// 'Items' are either owned by the local trash and not any previous folder, and /// LDataEventsI::OnMove is called. /// -or- /// 'Items' are completely deleted and removed from it's parent and /// LDataEventsI::OnDelete is called, after which the objects are freed. /// /// Failure: /// 'Items' are owned by it's previous folder. /// /// \return true on success. virtual Store3Status Delete ( /// The object to delete LArray &Items, /// Send to the trash or not... bool ToTrash ) = 0; /// Changes items, which results in either the items properties being adjusted. /// /// Success: /// The items properties are changed, and the LDataEventsI::OnChange callback /// is made. /// /// Failure: /// Items are not changed. No callback is made. /// /// \return true on success. virtual Store3Status Change ( /// The object to change LArray &Items, /// The property to change... int PropId, /// The value to assign /// (GV_INT32/64 -> SetInt, GV_DATETIME -> SetDateTime, GV_STRING -> SetStr) LVariant &Value, /// Optional operator for action LOperator Operator ) = 0; /// Compact the mail store - virtual bool Compact + virtual void Compact ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields - LDataPropI *Props + LDataPropI *Props, + /// The callback to get status, could be called by a worker thread... + std::function OnStatus ) = 0; /// Upgrades the mail store to the current version for this build. You should call this in response /// to getting Store3UpgradeRequired back from this->GetInt(FIELD_STATUS). - virtual bool Upgrade + virtual void Upgrade ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields - LDataPropI *Props - ) { return false; } + LDataPropI *Props, + /// The callback to get status, could be called by a worker thread... + std::function OnStatus + ) { if (OnStatus) OnStatus(false); } /// Tries to repair the database. - virtual bool Repair + virtual void Repair ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields - LDataPropI *Props - ) { return false; } + LDataPropI *Props, + /// The callback to get status, could be called by a worker thread... + std::function OnStatus + ) { if (OnStatus) OnStatus(false); } /// Set the sub-format virtual bool SetFormat ( /// The parent window of the UI LViewI *Parent, /// The store should pass information up to the UI via setting various parameters from Store3UiFields LDataPropI *Props ) { return false; } /// Called when event posted virtual void OnEvent(void *Param) = 0; /// Called when the application is not receiving messages. /// \returns false to wait for more messages. virtual bool OnIdle() = 0; /// Gets the events interface virtual LDataEventsI *GetEvents() = 0; /// Start a scoped transaction virtual StoreTrans StartTransaction() { return StoreTrans(0); } }; /// Open a mail3 folder /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenMail3 ( /// The file to open const char *Mail3Folder, /// Event interface, LDataEventsI *Callback, /// true if you want to create a new mail3 file. bool Create = false ); /// Open am imap store /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenImap ( /// The host name of the IMAP server char *Host, /// The port to connect to, or <= 0 means use default int Port, /// The user name of the account to connect to char *User, /// [Optional] The password of the user char *Pass, /// Various flags that control the type of connection made: /// \sa #MAIL_SSL, #MAIL_SECURE_AUTH int ConnectFlags, /// Callback interface for various events... LDataEventsI *Callback, /// This allows the IMAP client to request SSL support from the /// parent applications. LCapabilityClient *caps, /// Pointers to the progress info bars, or NULL if not needed. MailProtocolProgress *prog[2], /// The logging stream. LStream *Log, /// The identifier for the account int AccoundId, /// An interface into the persistant storage area. LAutoPtr store ); #ifdef WIN32 /// Open a MAPI store /// \return a valid ptr or NULL on failure extern LDataStoreI *OpenMapiStore ( /// The MAPI profile name const char *Profile, /// The username to login as const char *Username, /// Their password const char *Password, /// The account ID uint64 AccountId, /// Event interface, LDataEventsI *Callback ); #endif ////////////////////////////////////////////////////////////////////////////// // Common implementation of interfaces template class DNullIterator : public LDataIterator { public: T First() { return 0; } T Next() { return 0; } int Length() { return 0; } T operator [](int idx) { return 0; } bool Delete(T ptr) { return 0; } bool Insert(T ptr, int idx = -1, bool NoAssert = false) { return 0; } bool DeleteObjects() { return 0; } bool Empty() { return false; } int IndexOf(T n, bool NoAssert = false) { return -1; } }; template class DIterator : public LDataIterator { int Cur; public: LArray a; Store3State State; LIteratorProgressFn Prog; DIterator() { Cur = -1; State = Store3Unloaded; } Store3State GetState() { return State; } void SetProgressFn(LIteratorProgressFn cb) { Prog = cb; } void Swap(DIterator &di) { LSwap(Cur, di.Cur); LSwap(State, di.State); a.Swap(di.a); } TPub *Create(LDataStoreI *Store) { LAssert(State == Store3Loaded); return new TPriv(dynamic_cast(Store)); } TPub *First() { LAssert(State == Store3Loaded); Cur = 0; return (int)a.Length() > Cur ? a[Cur] : 0; } TPub *Next() { LAssert(State == Store3Loaded); Cur++; return (int)a.Length() > Cur ? a[Cur] : 0; } size_t Length() { return a.Length(); } TPub *operator [](size_t idx) { LAssert(State == Store3Loaded); return a[idx]; } bool Delete(TPub *pub_ptr) { LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return false; } ssize_t i = a.IndexOf(priv_ptr); if (i < 0) return false; a.DeleteAt(i, true); return true; } bool Insert(TPub *pub_ptr, ssize_t idx = -1, bool NoAssert = false) { if (!NoAssert) LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return false; } return a.AddAt(idx < 0 ? a.Length() : idx, priv_ptr); } bool Empty() { LAssert(State == Store3Loaded); a.Length(0); return true; } bool DeleteObjects() { a.DeleteObjects(); return true; } ssize_t IndexOf(TPub *pub_ptr, bool NoAssert = false) { if (!NoAssert) LAssert(State == Store3Loaded); TPriv *priv_ptr = dynamic_cast(pub_ptr); if (!priv_ptr) { LAssert(!"Not the right type of object."); return -1; } return a.IndexOf(priv_ptr); } }; #endif diff --git a/include/lgi/common/Tree.h b/include/lgi/common/Tree.h --- a/include/lgi/common/Tree.h +++ b/include/lgi/common/Tree.h @@ -1,307 +1,307 @@ /// \file /// \author Matthew Allen (fret@memecode.com) /// \brief A tree/heirarchy control #ifndef __GTREE2_H #define __GTREE2_H #include "lgi/common/ItemContainer.h" #include enum LTreeItemRect { TreeItemPos, TreeItemThumb, TreeItemText, TreeItemIcon }; class LTreeItem; class LgiClass LTreeNode { protected: class LTree *Tree; LTreeItem *Parent; List Items; virtual LTreeItem *Item() { return 0; } virtual LRect *Pos() { return 0; } virtual void _ClearDs(int Col); void _Visible(bool v); void SetLayoutDirty(); public: LTreeNode(); virtual ~LTreeNode(); /// Inserts a tree item as a child at 'Pos' LTreeItem *Insert(LTreeItem *Obj = NULL, ssize_t Pos = -1); /// Removes this node from it's parent, for permanent separation. void Remove(); /// Detachs the item from the tree so it can be re-inserted else where. void Detach(); /// Gets the node after this one at the same level. LTreeItem *GetNext(); /// Gets the node before this one at the same level. LTreeItem *GetPrev(); /// Gets the first child node. LTreeItem *GetChild(); /// Gets the parent of this node. LTreeItem *GetParent() { return Parent; } /// Gets the owning tree. May be NULL if not attached to a tree. LTree *GetTree() { return Tree; } /// Returns true if this is the root node. bool IsRoot(); /// Returns the index of this node in the list of item owned by it's parent. ssize_t IndexOf(); /// \returns number of child. size_t Length(); /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); List::I begin() { return Items.begin(); } List::I end() { return Items.end(); } /// Sorts the child items template bool Sort(int (*Compare)(LTreeItem*, LTreeItem*, T user_param), T user_param = 0) { if (!Compare) return false; Items.Sort(Compare, user_param); SetLayoutDirty(); return true; } /// Calls a f(n) for each int ForEach(std::function Fn); virtual bool Expanded() { return false; } virtual void Expanded(bool b) {} virtual void OnVisible(bool v) {} }; /// The item class for a tree. This defines a node in the heirarchy. class LgiClass LTreeItem : public LItem, public LTreeNode { friend class LTree; friend class LTreeNode; protected: class LTreeItemPrivate *d; // Private methods void _RePour(); void _Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible); void _Remove(); void _MouseClick(LMouse &m); void _SetTreePtr(LTree *t); LTreeItem *_HitTest(int x, int y, bool Debug = false); LRect *_GetRect(LTreeItemRect Which); LPoint _ScrollPos(); LTreeItem *Item() override { return this; } LRect *Pos() override; virtual void _PourText(LPoint &Size); virtual void _PaintText(LItem::ItemPaintCtx &Ctx); void _ClearDs(int Col) override; virtual void OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c); int GetColumnSize(int Col); protected: - LArray Str; - int Sys_Image; + LString::Array Str; + int Sys_Image = -1; public: LTreeItem(); virtual ~LTreeItem(); LItemContainer *GetContainer() override; /// \brief Get the text for the node /// /// You can either return a string stored internally to your /// object by implementing this function in your item class /// or use the SetText function to store the string in this /// class. const char *GetText(int i = 0) override; /// \brief Sets the text for the node. /// /// This will allocate and store the string in this class. bool SetText(const char *s, int i=0) override; /// Returns the icon index into the parent tree's LImageList. int GetImage(int Flags = 0) override; /// Sets the icon index into the parent tree's LImageList. void SetImage(int i) override; /// Tells the item to update itself on the screen when the /// LTreeItem::GetText data has changed. void Update() override; /// Returns true if the tree item is currently selected. bool Select() override; /// Selects or deselects the tree item. void Select(bool b) override; /// Returns true if the node has children and is open. bool Expanded() override; /// Opens or closes the node to show or hide the children. void Expanded(bool b) override; /// Scrolls the tree view so this node is visible. void ScrollTo() override; /// Gets the bounding box of the item. LRect *GetPos(int Col = -1) override; /// True if the node is the drop target bool IsDropTarget(); /// Called when the node expands/contracts to show or hide it's children. virtual void OnExpand(bool b); /// Paints the item void OnPaint(ItemPaintCtx &Ctx) override; void OnPaint(LSurface *pDC) override { LAssert(0); } }; /// A tree control. class LgiClass LTree : public LItemContainer, public ResObject, public LTreeNode { friend class LTreeItem; friend class LTreeNode; class LTreePrivate *d; // Private methods void _Pour(); void _OnSelect(LTreeItem *Item); void _Update(LRect *r = 0, bool Now = false); void _UpdateBelow(int y, bool Now = false); void _UpdateScrollBars(); List *GetSelLst(); protected: // Options bool Lines; bool Buttons; bool LinesAtRoot; bool EditLabels; LRect rItems; LPoint _ScrollPos(); LTreeItem *GetAdjacent(LTreeItem *From, bool Down); void OnDragEnter(); void OnDragExit(); void ClearDs(int Col) override; public: LTree(int id, int x = 0, int y = 0, int cx = 100, int cy = 100, const char *name = NULL); ~LTree(); const char *GetClass() override { return "LTree"; } /// Called when an item is clicked virtual void OnItemClick(LTreeItem *Item, LMouse &m); /// Called when an item is dragged from it's position virtual void OnItemBeginDrag(LTreeItem *Item, LMouse &m); /// Called when an item is expanded/contracted to show or hide it's children virtual void OnItemExpand(LTreeItem *Item, bool Expand); /// Called when an item is selected virtual void OnItemSelect(LTreeItem *Item); // Implementation void OnMouseClick(LMouse &m) override; void OnMouseMove(LMouse &m) override; bool OnMouseWheel(double Lines) override; void OnPaint(LSurface *pDC) override; void OnFocus(bool b) override; void OnPosChange() override; bool OnKey(LKey &k) override; int OnNotify(LViewI *Ctrl, LNotification n) override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPulse() override; int GetContentSize(int ColumnIdx) override; LCursor GetCursor(int x, int y) override; bool Lock(const char *file, int line, int TimeOut = -1) override; void Unlock() override; /// Add a item to the tree LTreeItem *Insert(LTreeItem *Obj = 0, ssize_t Pos = -1); /// Remove and delete an item bool Delete(LTreeItem *Obj); /// Remove but don't delete an item bool Remove(LTreeItem *Obj); /// Gets the item at an index LTreeItem *ItemAt(size_t Pos) { return Items[Pos]; } /// \returns if the object is in the tree bool HasItem(LTreeItem *obj, bool recurse = true); /// Select the item 'Obj' bool Select(LTreeItem *Obj); /// Returns the first selected item LTreeItem *Selection(); /// Gets the whole selection and puts it in 'n' template bool GetSelection(LArray &n) { n.Empty(); auto s = GetSelLst(); for (auto i : *s) { T *ptr = dynamic_cast(i); if (ptr) n.Add(ptr); } return n.Length() > 0; } /// Gets an array of all items template bool GetAll(LArray &n) { n.Empty(); return ForAllItems([&n](LTreeItem *item) { T *t = dynamic_cast(item); if (t) n.Add(t); }); } /// Call a function for every item bool ForAllItems(std::function Callback); /// Returns the item at an x,y location LTreeItem *ItemAtPoint(int x, int y, bool Debug = false); /// Temporarily selects one of the items as the drop target during a /// drag and drop operation. Call SelectDropTarget(0) when done. void SelectDropTarget(LTreeItem *Item); /// Delete all items (frees the items) void Empty(); /// Remove reference to items (doesn't free the items) void RemoveAll(); /// Call 'Update' on all tree items void UpdateAllItems() override; // Visual style enum ThumbStyle { TreePlus, TreeTriangle }; void SetVisualStyle(ThumbStyle Btns, bool JoiningLines); }; #endif diff --git a/include/lgi/common/Window.h b/include/lgi/common/Window.h --- a/include/lgi/common/Window.h +++ b/include/lgi/common/Window.h @@ -1,314 +1,315 @@ #ifndef _LWINDOW_H_ #define _LWINDOW_H_ #include "lgi/common/View.h" /// The available states for a top level window enum LWindowZoom { /// Minimized LZoomMin, /// Restored/Normal LZoomNormal, /// Maximized LZoomMax }; enum LWindowHookType { LNoEvents = 0, /// \sa LWindow::RegisterHook() LMouseEvents = 1, /// \sa LWindow::RegisterHook() LKeyEvents = 2, /// \sa LWindow::RegisterHook() LKeyAndMouseEvents = LMouseEvents | LKeyEvents, }; /// A top level window. class LgiClass LWindow : public LView, // This needs to be second otherwise is causes v-table problems. #ifndef LGI_SDL virtual #endif public LDragDropTarget { friend class BViewRedir; friend class LApp; friend class LView; friend class LButton; friend class LDialog; friend class LWindowPrivate; friend struct LDialogPriv; bool _QuitOnClose; protected: class LWindowPrivate *d; #if WINNATIVE LRect OldPos; LWindow *_Dialog; #elif defined(HAIKU) LWindowZoom _PrevZoom = LZoomNormal; #else OsWindow Wnd; void SetDeleteOnClose(bool i); #endif #if defined __GTK_H__ friend class LMenu; friend void lgi_widget_size_allocate(Gtk::GtkWidget *widget, Gtk::GtkAllocation *allocation); Gtk::GtkWidget *_Root, *_VBox, *_MenuBar; void OnGtkDelete(); Gtk::gboolean OnGtkEvent(Gtk::GtkWidget *widget, Gtk::GdkEvent *event); #elif defined(LGI_CARBON) friend pascal OSStatus LgiWindowProc(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); void _Delete(); bool _RequestClose(bool os); #elif defined(__OBJC__) public: // This returns the root level content NSView NSView *Handle(); protected: #endif /// The default button LViewI *_Default; /// The menu on the window LMenu *Menu; void SetChildDialog(LDialog *Dlg); void SetDragHandlers(bool On); /// Haiku: This shuts down the window's thread cleanly. int WaitThread(); public: #ifdef _DEBUG LMemDC DebugDC; #endif #ifdef __GTK_H__ LWindow(Gtk::GtkWidget *w = NULL); #elif LGI_CARBON LWindow(WindowRef wr = NULL); #elif LGI_COCOA LWindow(OsWindow wnd = NULL); #else LWindow(); #endif ~LWindow(); const char *GetClass() override { return "LWindow"; } /// Lays out the child views into the client area. virtual void PourAll(); /// Returns the current menu object LMenu *GetMenu() { return Menu; } /// Set the menu object. void SetMenu(LMenu *m) { Menu = m; } /// Set the window's icon bool SetIcon(const char *FileName); /// Gets the "quit on close" setting. bool GetQuitOnClose() { return _QuitOnClose; } /// \brief Sets the "quit on close" setting. /// /// When this is switched on the application will quit the main message /// loop when this LWindow is closed. This is really useful for your /// main application window. Otherwise the UI will disappear but the /// application is still running. void SetQuitOnClose(bool i) { _QuitOnClose = i; } bool GetSnapToEdge(); void SetSnapToEdge(bool b); bool GetAlwaysOnTop(); void SetAlwaysOnTop(bool b); /// Gets the current zoom setting LWindowZoom GetZoom(); /// Sets the current zoom void SetZoom(LWindowZoom i); /// Raises the window to the top of the stack. void Raise(); /// Moves a top level window on screen. void MoveOnScreen(); /// Moves a top level to the center of the screen void MoveToCenter(); /// Moves a top level window to where the mouse is void MoveToMouse(); /// Moves the window to somewhere on the same screen as 'wnd' bool MoveSameScreen(LViewI *wnd); // Focus setting LViewI *GetFocus(); enum FocusType { GainFocus, LoseFocus, ViewDelete }; void SetFocus(LViewI *ctrl, FocusType type); /// Registers a watcher to receive OnView... messages before they /// are passed through to the intended recipient. bool RegisterHook ( /// The target view. LView *Target, /// Combination of: /// #LMouseEvents - Where Target->OnViewMouse(...) is called for each click. /// and /// #LKeyEvents - Where Target->OnViewKey(...) is called for each key. /// OR'd together. LWindowHookType EventType, /// Not implemented int Priority = 0 ); /// Unregisters a hook target bool UnregisterHook(LView *Target); /// Gets the default view LViewI *GetDefault(); /// Sets the default view void SetDefault(LViewI *v); /// Saves/loads the window's state, e.g. position, minimized/maximized etc bool SerializeState ( /// The data store for reading/writing LDom *Store, /// The field name to use for storing settings under const char *FieldName, /// TRUE if loading the settings into the window, FALSE if saving to the store. bool Load ); /// Builds a map of keyboard short cuts. typedef LHashTbl,LViewI*> ShortcutMap; void BuildShortcuts(ShortcutMap &Map, LViewI *v = NULL); ////////////////////// Events /////////////////////////////// /// Called when the window zoom state changes. virtual void OnZoom(LWindowZoom Action) {} /// Called when the tray icon is clicked. (if present) virtual void OnTrayClick(LMouse &m); /// Called when the tray icon menu is about to be displayed. virtual void OnTrayMenu(LSubMenu &m) {} /// Called when the tray icon menu item has been selected. virtual void OnTrayMenuResult(int MenuId) {} /// Called when files are dropped on the window. virtual void OnReceiveFiles(LArray &Files) {} /// Called when a URL is sent to the window virtual void OnUrl(const char *Url) {}; ///////////////// Implementation //////////////////////////// void OnPosChange() override; LMessage::Result OnEvent(LMessage *Msg) override; void OnPaint(LSurface *pDC) override; bool HandleViewMouse(LView *v, LMouse &m); bool HandleViewKey(LView *v, LKey &k); /// Return true to accept application quit bool OnRequestClose(bool OsShuttingDown) override; bool Obscured(); bool Visible() override; void Visible(bool i) override; bool IsActive(); bool SetActive(); LRect &GetPos() override; void SetDecor(bool Visible); LPoint GetDpi(); LPointF GetDpiScale(); void ScaleSizeToDpi(); // D'n'd int WillAccept(LDragFormats &Formats, LPoint Pt, int KeyState) override; int OnDrop(LArray &Data, LPoint Pt, int KeyState) override; #if !WINNATIVE bool Attach(LViewI *p) override; // Props #if defined(HAIKU) OsWindow WindowHandle() override; #else OsWindow WindowHandle() override { return Wnd; } #endif bool Name(const char *n) override; const char *Name() override; bool SetPos(LRect &p, bool Repaint = false) override; LRect &GetClient(bool InClientSpace = true) override; // Events void OnChildrenChanged(LViewI *Wnd, bool Attaching) override; void OnCreate() override; virtual void OnFrontSwitch(bool b); #else OsWindow WindowHandle() override { return _View; } #endif #if defined(LGI_SDL) virtual bool PushWindow(LWindow *v); virtual LWindow *PopWindow(); #elif defined __GTK_H__ void OnGtkRealize(); bool IsAttached(); void Quit(bool DontDelete = false); LRect *GetDecorSize(); bool TranslateMouse(LMouse &m); LViewI *WindowFromPoint(int x, int y, bool Debug = false); void _SetDynamic(bool b); void _OnViewDelete(); + void SetParent(LViewI *p) override; #elif defined(MAC) bool PostEvent(int Cmd, LMessage::Param a = 0, LMessage::Param b = 0) override; void Quit(bool DontDelete = false) override; int OnCommand(int Cmd, int Event, OsView Wnd) override; LViewI *WindowFromPoint(int x, int y, int DebugDebug = 0) override; #if defined(LGI_CARBON) OSErr HandlerCallback(DragTrackingMessage *tracking, DragRef theDrag); #endif #endif }; #endif diff --git a/include/lgi/mac/cocoa/LgiOsDefs.h b/include/lgi/mac/cocoa/LgiOsDefs.h --- a/include/lgi/mac/cocoa/LgiOsDefs.h +++ b/include/lgi/mac/cocoa/LgiOsDefs.h @@ -1,303 +1,308 @@ // // FILE: LgiOsDefs.h (Mac) // AUTHOR: Matthew Allen // DATE: 1/1/2013 // DESCRIPTION: Lgi cocoa header // // Copyright (C) 2013, Matthew Allen // fret@memecode.com // #pragma once #include #include #include #include #ifdef __OBJC__ #import +#import #endif #include #include #include "lgi/common/LgiInc.h" #include "lgi/common/LgiDefs.h" #include "lgi/common/AutoPtr.h" ////////////////////////////////////////////////////////////////// #include "ObjCWrapper.h" ObjCWrapper(NSWindow, OsWindow) ObjCWrapper(NSView, OsView) typedef pthread_t OsThread; typedef uint64_t OsThreadId; typedef pthread_mutex_t OsSemaphore; typedef uint16_t OsChar; typedef int OsProcessId; typedef int OsProcess; typedef CGContextRef OsPainter; typedef CGContextRef OsBitmap; typedef CTFontRef OsFont; // #include "lgi/common/LgiUiBase.h" #include "lgi/common/Message.h" class OsAppArguments { struct OsAppArgumentsPriv *d; public: int Args; const char **Arg; OsAppArguments(int args = 0, const char **arg = 0); ~OsAppArguments(); void Set(const char *CmdLine); OsAppArguments &operator =(OsAppArguments &a); }; ////////////////////////////////////////////////////////////////// // Defines #define _stricmp strcasecmp #define _strnicmp strncasecmp // Text system #define USE_CORETEXT 1 // System defines #define POSIX 1 #define LGI_COCOA 1 #define LGI_64BIT 1 #define LGI_VIEW_HANDLE 0 // GViews DON'T have individual OsView handles #define LGI_VIEW_HASH 1 // LView DO have a hash table for validity // Process typedef int OsProcess; typedef int OsProcessId; #define LGetCurrentProcess() getpid() // Threads #define LGetCurrentThread() pthread_self() LgiFunc OsThreadId GetCurrentThreadId(); // Sockets #define ValidSocket(s) ((s)>=0) #define INVALID_SOCKET -1 typedef int OsSocket; // Sleep the current thread LgiFunc void LSleep(uint32_t i); #define LGI_GViewMagic 0x14412662 #define LGI_FileDropFormat "public.file-url" #define LGI_StreamDropFormat "com.apple.pasteboard.promised-file-url" #define LGI_LgiDropFormat "lgi " #define LGI_WideCharset "utf-32" #define LPrintfInt64 "%lli" #define LPrintfHex64 "%llx" #define LPrintfSizeT "%zu" #define LPrintfSSizeT "%zi" #define atoi64 atoll #define sprintf_s snprintf #define vsprintf_s vsnprintf #define stricmp strcasecmp #define LGI_IllegalFileNameChars "/" // FIXME: what other characters should be in here? #define LGI_EXECUTABLE_EXT "" // Empty // Window flags #define GWF_VISIBLE 0x00000001 #define GWF_DISABLED 0x00000002 #define GWF_FOCUS 0x00000004 #define GWF_OVER 0x00000008 #define GWF_DROP_TARGET 0x00000010 #define GWF_SUNKEN 0x00000020 #define GWF_FLAT 0x00000040 #define GWF_RAISED 0x00000080 #define GWF_BORDER 0x00000100 #define GWF_DIALOG 0x00000200 #define GWF_DESTRUCTOR 0x00000400 #define GWF_QUIT_WND 0x00000800 // Menu flags #define ODS_SELECTED 0x1 #define ODS_DISABLED 0x2 #define ODS_CHECKED 0x4 /// Edge type: Sunken #define SUNKEN 1 /// Edge type: Raised #define RAISED 2 /// Edge type: Chiseled #define CHISEL 3 /// Edge type: Flat #define FLAT 4 /// The directory separator character on Linux as a char #define DIR_CHAR '/' /// The directory separator character on Linux as a string #define DIR_STR "/" /// The standard end of line string for Linux #define EOL_SEQUENCE "\n" /// Tests a char for being a slash #define IsSlash(c) (((c)=='/')||((c)=='\\')) /// Tests a char for being a quote #define IsQuote(c) (((c)=='\"')||((c)=='\'')) /// The path list separator character for Linux #define LGI_PATH_SEPARATOR ":" /// The pattern that matches all files in Linux #define LGI_ALL_FILES "*" /// The stardard extension for dynamically linked code #define LGI_LIBRARY_EXT "dylib" enum LDialogIds { /// Standard ID for an "Ok" button. /// \sa LgiMsg IDOK = 1, /// Standard ID for a "Cancel" button. /// \sa LgiMsg IDCANCEL, /// Standard ID for a "Yes" button. /// \sa LgiMsg IDYES, /// Standard ID for a "No" button. /// \sa LgiMsg IDNO, /// Standard message box with an Ok button. /// \sa LgiMsg MB_OK, /// Standard message box with Ok and Cancel buttons. /// \sa LgiMsg MB_OKCANCEL, /// Standard message box with Yes and No buttons. /// \sa LgiMsg MB_YESNO, /// Standard message box with Yes, No and Cancel buttons. /// \sa LgiMsg MB_YESNOCANCEL, }; #define MB_SYSTEMMODAL 0x1000 /// The CTRL key is pressed /// \sa LKey #define LGI_VKEY_CTRL 0x001 /// The ALT key is pressed /// \sa LKey #define LGI_VKEY_ALT 0x002 /// The SHIFT key is pressed /// \sa LKey #define LGI_VKEY_SHIFT 0x004 /// The left mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_LEFT 0x008 /// The middle mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_MIDDLE 0x010 /// The right mouse button is pressed /// \sa LMouse #define LGI_VMOUSE_RIGHT 0x020 /// The ctrl key is pressed /// \sa LMouse #define LGI_VMOUSE_CTRL 0x040 /// The alt key is pressed /// \sa LMouse #define LGI_VMOUSE_ALT 0x080 /// The shift key is pressed /// \sa LMouse #define LGI_VMOUSE_SHIFT 0x100 /// The mouse event is a down click /// \sa LMouse #define LGI_VMOUSE_DOWN 0x200 /// The mouse event is a double click /// \sa LMouse #define LGI_VMOUSE_DOUBLE 0x400 +#ifndef __OBJC__ +#define NSDeleteFunctionKey 0xF728 +#endif + #define MacKeyDef() \ _(A, 0) _(B, 11) _(C, 8) _(D, 2) _(E, 14) _(F, 3) \ _(G, 5) _(H, 4) _(I, 34) _(J, 38) _(K, 40) _(L, 37) \ _(M, 46) _(N, 45) _(O, 31) _(P, 35) _(Q, 12) _(R, 15) \ _(S, 1) _(T, 17) _(U, 32) _(V, 9) _(W, 13) _(X, 7) _(Y, 16) _(Z, 6) \ _(KEY1, 18) _(KEY2, 19) _(KEY3, 20) _(KEY4, 21) _(KEY5, 23) \ _(KEY6, 22) _(KEY7, 26) _(KEY8, 28) _(KEY9, 25) _(KEY0, 29) \ _(MINUS, 27) \ _(EQUALS, 24) \ _(CLOSEBRACKET, 30) \ _(OPENBRACKET, 33) \ _(ENTER, 36) \ _(SINGLEQUOTE, 39) \ _(BACKSLASH, 42) \ _(COLON, 41) \ _(LESSTHAN, 43) _(GREATERTHAN, 47) \ _(FORWARDSLASH, 44) \ _(TAB, 48) \ _(SPACE, 49) \ _(TILDE, 50) \ _(BACKSPACE, 51) \ _(ESCAPE, 53) \ _(CMDRIGHT, 54)_(CMDLEFT, 55) \ _(SHIFTLEFT, 56) _(SHIFTRIGHT, 60) \ _(CAPSLOCK, 57) \ _(ALTLEFT, 58) _(ALTRIGHT, 61) \ _(CTRLLEFT, 59) _(CTRLRIGHT, 62) \ _(KEYPADPERIOD, 65) \ _(KEYPADASTERISK, 67) \ _(KEYPADPLUS, 69) \ _(KEYPADENTER, 76) \ _(KEYPADNUMLOCK, 71) \ _(KEYPADFORWARDSLASH, 75) \ _(KEYPADMINUS, 78) _(KEYPADEQUALS, 81) \ _(KEYPAD0, 82) _(KEYPAD1, 83) _(KEYPAD2, 84) _(KEYPAD3, 85) _(KEYPAD4, 86) \ _(KEYPAD5, 87) _(KEYPAD6, 88) _(KEYPAD7, 89) _(KEYPAD8, 91) _(KEYPAD9, 92) \ _(F1, 122) _(F2, 120) _(F3, 99) _(F4, 118) _(F5, 96) _(F6, 97) \ _(F7, 98) _(F8, 100) _(F9, 101) _(F10, 109) _(F11, 103) _(F12, 111) \ _(PRINTSCREEN, 105) \ _(CONTEXTMENU, 110) \ _(INSERT, 114) \ _(HOME, 115) \ _(PAGEUP, 116) \ - _(DELETE, 117) \ + _(DELETE, 117) _(DELETEFORWARD, NSDeleteFunctionKey) \ _(END, 119) \ _(PAGEDOWN, 121) \ _(LEFT, 123) _(RIGHT, 124) _(DOWN, 125) _(UP, 126) #ifdef __OBJC__ #define _KEY(sym,val) sym #else #define _KEY(sym,val) val #endif // Keys enum LVirtualKeys { #define _(k, v) LK_ ##k = v, MacKeyDef() #undef _ LK_RETURN = LK_ENTER, LK_APPS = 0x1000, }; #undef _KEY LgiFunc const char *LVirtualKeyToString(LVirtualKeys c); ///////////////////////////////////////////////////////////////////////////////////// // Externs LgiFunc int GetMouseWheelLines(); LgiFunc int WinPointToHeight(int Pt); LgiFunc int WinHeightToPoint(int Ht); LgiFunc int stricmp(const char *a, const char *b); LgiFunc char *strlwr(char *a); LgiFunc char *strupr(char *a); LgiFunc char *p2c(unsigned char *s); /// Convert a string d'n'd format to an OS dependant integer. LgiFunc int FormatToInt(char *s); /// Convert a Os dependant integer d'n'd format to a string. LgiFunc char *FormatToStr(int f); 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,1189 +1,1189 @@ #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 GHostFunc::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::WaitForReturn(LScriptArguments &Args) { while (Args.GetReturn()->Type != GV_NULL) { // This should only ever loop on Haiku... LYield(); LSleep(10); } return true; } bool SystemFunctions::SelectFiles(LScriptArguments &Args) { LFileSelect *s = new LFileSelect; Args.GetReturn()->Empty(); if (Args.Length() > 0) s->Parent(CastLView(*Args[0])); auto t = LString(Args.Length() > 1 ? Args[1]->CastString() : NULL).SplitDelimit(",;:"); for (auto c: t) { 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.Length() > 2 ? Args[2]->CastString() : 0); s->MultiSelect(Args.Length() > 3 ? Args[3]->CastInt32() != 0 : true); bool SaveAs = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; auto Process = [&Args](LFileSelect *s, bool ok) { if (ok) { Args.GetReturn()->SetList(); auto Lst = Args.GetReturn()->Value.Lst; for (unsigned i=0; iLength(); i++) Lst->Insert(new LVariant((*s)[i])); } else *Args.GetReturn() = false; delete s; }; if (SaveAs) s->Save(Process); else s->Open(Process); return WaitForReturn(Args); } bool SystemFunctions::SelectFolder(LScriptArguments &Args) { LFileSelect *s = new LFileSelect; if (Args.Length() > 0) s->Parent(CastLView(*Args[0])); s->InitialDir(Args.Length() > 1 ? Args[1]->CastString() : 0); Args.GetReturn()->Empty(); s->OpenFolder([&Args](LFileSelect *s, bool ok) { if (ok) *Args.GetReturn() = s->Name(); else *Args.GetReturn() = false; delete s; }); return WaitForReturn(Args); } 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::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->AddRef(); + 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) { LAssert(!"Wrong args."); return false; } Args.GetReturn()->SetList(); LDirectory d; char *Pattern = Args.Length() > 1 ? Args[1]->CastString() : 0; char *Folder = Args[0]->CastString(); for (int 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) { LAssert(!"Wrong args."); return false; } int x = Args[0]->CastInt32(); int 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(); if ((Args.GetReturn()->Value.Surface.Ptr = new LMemDC(x, y, Cs))) { Args.GetReturn()->Type = GV_LSURFACE; Args.GetReturn()->Value.Surface.Own = true; - Args.GetReturn()->Value.Surface.Ptr->AddRef(); + Args.GetReturn()->Value.Surface.Ptr->IncRef(); } return true; } bool SystemFunctions::ColourSpaceToString(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = LColourSpaceToString((LColourSpace)Args[0]->CastInt32()); return true; } bool SystemFunctions::StringToColourSpace(LScriptArguments &Args) { Args.GetReturn()->Empty(); if (Args.Length() != 1) { LAssert(!"Wrong args."); return false; } *Args.GetReturn() = (int)LStringToColourSpace(Args[0]->Str()); return true; } bool SystemFunctions::MessageDlg(LScriptArguments &Args) { if (Args.Length() < 2) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); auto *Msg = Args[1]->Str(); auto *Title = Args.IdxCheck(2) ? Args[2]->Str() : LAppInst->Name(); uint32_t 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() < 4) { LAssert(!"Wrong args."); return false; } LViewI *Parent = CastLView(*Args[0]); char *InitVal = Args[1]->Str(); char *Msg = Args[2]->Str(); char *Title = Args[3]->Str(); bool Pass = Args.Length() > 4 ? Args[4]->CastInt32() != 0 : false; LInput *Dlg = new LInput(Parent, InitVal, Msg, Title, Pass); Dlg->DoModal([Dlg, &Args](auto d, auto code) { *Args.GetReturn() = code ? Dlg->GetStr() : -1; delete Dlg; }); return WaitForReturn(Args); } 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) \ GHostFunc(#Name, 0, (ScriptCmd)&SystemFunctions::Name) GHostFunc SystemLibrary[] = { // Debug DefFn(Assert), DefFn(Throw), DefFn(DebuggerEnabled), // String handling DefFn(LoadString), DefFn(FormatSize), DefFn(Sprintf), DefFn(Print), DefFn(ToString), // 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 GHostFunc(0, 0, 0), }; GHostFunc *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,2169 +1,2169 @@ #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 }; LStream *Log; LCompiledCode *Code; LExecutionStatus Status; GPtr c; LVariant Reg[MAX_REGISTER]; LArray Locals; LVariant *Scope[SCOPE_MAX]; LArray Frames; RunType StepType; LVmDebuggerCallback *DbgCallback; bool DebuggerEnabled; LVmDebugger *Debugger; LVirtualMachine *Vm; LScriptArguments *ArgsOutput; bool BreakCpp; LArray BreakPts; LString TempPath; LVirtualMachinePriv(LVirtualMachine *vm, LVmDebuggerCallback *Callback) { Vm = vm; DebuggerEnabled = true; BreakCpp = false; ArgsOutput = NULL; Log = NULL; Code = NULL; Debugger = NULL; DbgCallback = 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 (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); GPtr 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 // LHashTbl, int> &Debug = Code->Debug; 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 == 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; } } if (Debugger && Status != ScriptError) Debugger->OnAddress(CurrentScriptAddress); return Status; } }; bool LVirtualMachine::BreakOnWarning = false; LVirtualMachine::LVirtualMachine(LVmDebuggerCallback *callback) { d = new LVirtualMachinePriv(this, callback); - d->AddRef(); + d->IncRef(); } LVirtualMachine::LVirtualMachine(LVirtualMachine *vm) { d = vm->d; - d->AddRef(); + d->IncRef(); } LVirtualMachine::~LVirtualMachine() { if (d->Vm == this) d->Vm = NULL; d->DecRef(); } 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; Args.Vm = this; LExecutionStatus r = d->Run(LVirtualMachinePriv::RunContinue); Args.Vm = NULL; 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->DbgCallback) return NULL; d->Debugger = d->DbgCallback->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; } 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; LVmDebuggerCallback *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, LVmDebuggerCallback *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/Gdc2/Font/DisplayString.cpp b/src/common/Gdc2/Font/DisplayString.cpp --- a/src/common/Gdc2/Font/DisplayString.cpp +++ b/src/common/Gdc2/Font/DisplayString.cpp @@ -1,2275 +1,2275 @@ ////////////////////////////////////////////////////////////////////// // Includes #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Variant.h" #include "lgi/common/FontSelect.h" #include "lgi/common/GdiLeak.h" #include "lgi/common/DisplayString.h" #include "lgi/common/PixelRops.h" #include "lgi/common/UnicodeString.h" #ifdef FontChange #undef FontChange #endif #ifdef LGI_SDL #include "ftsynth.h" #endif #if WINNATIVE static OsChar GDisplayStringDots[] = {'.', '.', '.', 0}; #endif //345678123456781234567812345678 // 2nd #define DEBUG_CHAR_AT 0 #define DEBUG_BOUNDS 0 #if defined(__GTK_H__) || (defined(MAC) && !defined(LGI_SDL)) #define DISPLAY_STRING_FRACTIONAL_NATIVE 1 #else #define DISPLAY_STRING_FRACTIONAL_NATIVE 0 #endif #if defined(__GTK_H__) struct Block : public LRect { /// This points to somewhere in Ds->Str OsChar *Str = NULL; /// Bytes in this block int Bytes = 0; /// Utf-8 characters in this block int Chars = 0; /// Alternative font to get characters from (NULL if using the display string's font) LFont *Fnt = NULL; /// Layout for this block. Shouldn't ever be NULL. But shouldn't crash otherwise. Gtk::PangoLayout *Hnd = NULL; ~Block() { if (Hnd) g_object_unref(Hnd); } }; struct GDisplayStringPriv { LDisplayString *Ds; LArray Blocks; bool Debug; int LastTabOffset; GDisplayStringPriv(LDisplayString *str) : Ds(str) { #if 0 Debug = Stristr(Ds->Str, "(Jumping).wma") != 0; #else Debug = false; #endif LastTabOffset = -1; } ~GDisplayStringPriv() { } void Create(Gtk::GtkPrintContext *PrintCtx) { auto *Fs = LFontSystem::Inst(); auto *Fnt = Ds->Font; auto Tbl = Fnt->GetGlyphMap(); int Chars = 0; LUtf8Ptr p(Ds->Str); auto *Start = p.GetPtr(); if (Tbl) { int32 w; Block *b = NULL; auto DisplayCtx = LFontSystem::Inst()->GetContext(); while ((w = (int32)p)) { LFont *f; if (w >= 0x80 && !_HasUnicodeGlyph(Tbl, w)) f = Fs->GetGlyph(w, Ds->Font); else f = Ds->Font; if (!b || (f != NULL && f != Fnt)) { // Finish old block if (b) { b->Bytes = p.GetPtr() - Start; b->Chars = Chars; Chars = 0; } Start = p.GetPtr(); if (f) { // Start new block... b = &Blocks.New(); b->Str = (char*)Start; b->Bytes = -1; // unknown at this point if (f != Ds->Font) // External font b->Fnt = f; // Create a pango layout if (PrintCtx) b->Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b->Hnd = Gtk::pango_layout_new(DisplayCtx); } // else no font supports glyph Fnt = f; } // else no change in font p++; Chars++; } if (b) { // Finish the last block b->Bytes = p.GetPtr() - Start; b->Chars = Chars; } if (Debug) { // Print the block array for (size_t i=0; iStrWords) { p++; Chars++; } auto &b = Blocks.New(); b.Str = (char*)Start; b.Bytes = p.GetPtr() - Start; b.Chars = Chars; if (PrintCtx) b.Hnd = Gtk::gtk_print_context_create_pango_layout(PrintCtx); else b.Hnd = Gtk::pango_layout_new(LFontSystem::Inst()->GetContext()); } /* This could get stuck in an infinite loop. Leaving out for the moment. for (auto &b: Blocks) { if (b.Hnd == NULL && b.Fnt == NULL) { Blocks.Length(0); goto Start; } } */ } void UpdateTabs(int Offset, int Size, bool Debug = false) { if (Ds->Font && Ds->Font->TabSize()) { int Len = 16; LastTabOffset = Offset; Gtk::PangoTabArray *t = Gtk::pango_tab_array_new(Len, true); if (t) { for (int i=0; i bool StringConvert(Out *&out, ssize_t &OutWords, const In *in, ssize_t InLen) { if (!in) { out = 0; OutWords = 0; return false; } auto InSz = sizeof(In); auto OutSz = sizeof(Out); // Work out input size ssize_t InWords; if (InLen >= 0) InWords = InLen; else for (InWords = 0; in[InWords]; InWords++) ; if (InSz == OutSz) { // No conversion optimization out = (Out*)Strdup(in, InWords); OutWords = out ? InWords : 0; return out != 0; } else { // Convert the string to new word size static const char *Cp[] = { NULL, "utf-8", "utf-16", NULL, "utf-32"}; LAssert(OutSz <= 4 && InSz <= 4 && Cp[OutSz] && Cp[InSz]); out = (Out*) LNewConvertCp(Cp[OutSz], in, Cp[InSz], InWords*sizeof(In)); OutWords = Strlen(out); return out != NULL; } return false; } ////////////////////////////////////////////////////////////////////// #define SubtractPtr(a, b) ( ((a)-(b)) / sizeof(*a) ) #define VisibleTabChar 0x2192 #define IsTabChar(c) (c == '\t') // || (c == VisibleTabChar && VisibleTab)) #if USE_CORETEXT #include void LDisplayString::CreateAttrStr() { if (!Wide) return; if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } wchar_t *w = Wide; CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, (const uint8_t*)w, StrlenW(w) * sizeof(*w), kCFStringEncodingUTF32LE, false); if (string) { CFDictionaryRef attributes = Font->GetAttributes(); if (attributes) AttrStr = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes); // else LAssert(0); CFRelease(string); } } #endif LDisplayString::LDisplayString(LFont *f, const char *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str) { LAssert(StrWords >= 0); if (StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); } #elif defined MAC && !defined(LGI_SDL) Hnd = 0; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else ATSUCreateTextLayout(&Hnd); #endif } #endif } LDisplayString::LDisplayString(LFont *f, const char16 *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #elif defined MAC && !defined(LGI_SDL) Hnd = NULL; #if USE_CORETEXT AttrStr = NULL; #endif if (Font && Str && StrWords > 0) { #if USE_CORETEXT CreateAttrStr(); #else OSStatus e = ATSUCreateTextLayout(&Hnd); if (e) printf("%s:%i - ATSUCreateTextLayout failed with %i.\n", _FL, (int)e); #endif } #endif } #ifdef _MSC_VER LDisplayString::LDisplayString(LFont *f, const uint32_t *s, ssize_t l, LSurface *pdc) { pDC = pdc; Font = f; x = y = 0; xf = 0; yf = 0; DrawOffsetF = 0; LaidOut = 0; AppendDots = 0; VisibleTab = 0; #if LGI_DSP_STR_CACHE StringConvert(Wide, WideWords, s, l); #endif StringConvert(Str, StrWords, s, l); #if defined __GTK_H__ d = new GDisplayStringPriv(this); if (Font && Str && StrWords > 0) d->Create(pDC ? pDC->GetPrintContext() : NULL); #endif } #endif LDisplayString::~LDisplayString() { #if defined(LGI_SDL) Img.Reset(); #elif defined __GTK_H__ DeleteObj(d); #elif defined MAC #if USE_CORETEXT if (Hnd) { CFRelease(Hnd); Hnd = NULL; } if (AttrStr) { CFRelease(AttrStr); AttrStr = NULL; } #else if (Hnd) ATSUDisposeTextLayout(Hnd); #endif #endif DeleteArray(Str); #if LGI_DSP_STR_CACHE DeleteArray(Wide); #endif } void LDisplayString::DrawWhiteSpace(LSurface *pDC, char Ch, LRect &r) { if (Ch == '\t') { r.Inset(3, 3); if (r.Y()/2 == 0) r.y2++; int Cy = (r.Y() >> 1); pDC->Line(r.x1, r.y1+Cy, r.x2, r.y1+Cy); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y1); pDC->Line(r.x2, r.y1+Cy, r.x2-Cy, r.y2); } else // Space { int x = r.x1 + (r.X()>>1) - 1; int Cy = r.y1 + (int)ceil(Font->Ascent()) - 2; pDC->Rectangle(x, Cy, x+1, Cy+1); } } void LDisplayString::Layout(bool Debug) { if (LaidOut || !Font) return; LaidOut = 1; #if defined(LGI_SDL) FT_Face Fnt = Font->Handle(); FT_Error error; if (!Fnt || !Str) return; // Create an array of glyph indexes LArray Glyphs; for (OsChar *s = Str; *s; s++) { FT_UInt index = FT_Get_Char_Index(Fnt, *s); if (index) Glyphs.Add(index); } // Measure the string... LPoint Sz; int FontHt = Font->GetHeight(); int AscentF = (int) (Font->Ascent() * FScale); int LoadMode = FT_LOAD_FORCE_AUTOHINT; for (unsigned i=0; iglyph->metrics.horiBearingY; Sz.x += Fnt->glyph->metrics.horiAdvance; Sz.y = MAX(Sz.y, PyF + Fnt->glyph->metrics.height); } } // Create the memory context to draw into xf = Sz.x; x = ((Sz.x + FScale - 1) >> FShift) + 1; yf = FontHt << FShift; y = FontHt; // ((Sz.y + FScale - 1) >> FShift) + 1; if (Img.Reset(new LMemDC(x, y, CsIndex8))) { // Clear the context to black Img->Colour(0); Img->Rectangle(); bool IsItalic = Font->Italic(); int CurX = 0; int FBaseline = Fnt->size->metrics.ascender; for (unsigned i=0; iglyph); error = FT_Render_Glyph(Fnt->glyph, FT_RENDER_MODE_NORMAL); if (error == 0) { FT_Bitmap &bmp = Fnt->glyph->bitmap; if (bmp.buffer) { // Copy rendered glyph into our image memory int Px = (CurX + (FScale >> 1)) >> FShift; int PyF = AscentF - Fnt->glyph->metrics.horiBearingY; int Py = PyF >> FShift; int Skip = 0; if (Fnt->glyph->format == FT_GLYPH_FORMAT_BITMAP) { Px += Fnt->glyph->bitmap_left; Skip = Fnt->glyph->bitmap_left < 0 ? -Fnt->glyph->bitmap_left : 0; Py = (AscentF >> FShift) - Fnt->glyph->bitmap_top; } LAssert(Px + bmp.width <= Img->X()); for (int y=0; y= 0); out += Px+Skip; memcpy(out, in+Skip, bmp.width-Skip); } /* else { LAssert(!"No scanline?"); break; } */ } } if (i < Glyphs.Length() - 1) { FT_Vector kerning; FT_Get_Kerning(Fnt, Glyphs[i], Glyphs[i+1], FT_KERNING_DEFAULT, &kerning); CurX += Fnt->glyph->metrics.horiAdvance + kerning.x; } else { CurX += Fnt->glyph->metrics.horiAdvance; } } } } } else LgiTrace("::Layout Create MemDC failed\n"); #elif defined(__GTK_H__) y = Font->GetHeight(); yf = y * PANGO_SCALE; if (!d->Blocks.Length() || !Font->Handle()) return; LUtf8Ptr Utf(Str); int32 Wide; while (*Utf.GetPtr()) { Wide = Utf; if (!Wide) { LgiTrace("%s:%i - Not utf8\n", _FL); return; } Utf++; } LFontSystem *FSys = LFontSystem::Inst(); Gtk::pango_context_set_font_description(FSys->GetContext(), Font->Handle()); int TabSizePx = Font->TabSize(); int TabSizeF = TabSizePx * FScale; int TabOffsetF = DrawOffsetF % TabSizeF; int OffsetF = TabOffsetF ? TabSizeF - TabOffsetF : 0; d->UpdateTabs(OffsetF / FScale, Font->TabSize()); if (Font->Underline()) { Gtk::PangoAttrList *attrs = Gtk::pango_attr_list_new(); Gtk::PangoAttribute *attr = Gtk::pango_attr_underline_new(Gtk::PANGO_UNDERLINE_SINGLE); Gtk::pango_attr_list_insert(attrs, attr); for (auto &b: d->Blocks) Gtk::pango_layout_set_attributes(b.Hnd, attrs); Gtk::pango_attr_list_unref(attrs); } int Fx = 0; for (auto &b: d->Blocks) { int bx = 0, by = 0; if (b.Hnd) { if (!LIsUtf8(b.Str, b.Bytes)) { LgiTrace("Invalid UTF8: '%.*S'\n", (int)b.Bytes, b.Str); } else { Gtk::pango_layout_set_text(b.Hnd, b.Str, b.Bytes); } Gtk::pango_layout_get_size(b.Hnd, &bx, &by); } else if (b.Fnt) { b.Fnt->_Measure(bx, by, b.Str, b.Bytes); bx <<= FShift; by <<= FShift; } b.ZOff(bx-1, by-1); b.Offset(Fx, 0); xf += bx; yf = MAX(yf, by); } x = (xf + PANGO_SCALE - 1) / PANGO_SCALE; #if 1 y = Font->GetHeight(); #else y = (yf + PANGO_SCALE - 1) / PANGO_SCALE; #endif if (y > Font->GetHeight()) { printf("%s:%i - Height error: %i > %i\n", _FL, y, Font->GetHeight()); } #elif defined MAC && !defined(LGI_SDL) #if USE_CORETEXT int height = Font->GetHeight(); y = height; if (AttrStr) { LAssert(!Hnd); Hnd = CTLineCreateWithAttributedString(AttrStr); if (Hnd) { CGFloat ascent = 0.0; CGFloat descent = 0.0; CGFloat leading = 0.0; double width = CTLineGetTypographicBounds(Hnd, &ascent, &descent, &leading); x = ceil(width); xf = width * FScale; yf = height * FScale; } } #else if (!Hnd || !Str) return; OSStatus e = ATSUSetTextPointerLocation(Hnd, Str, 0, len, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetTextPointerLocation failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } e = ATSUSetRunStyle(Hnd, Font->Handle(), 0, len); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUSetRunStyle failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } ATSUTextMeasurement fTextBefore; ATSUTextMeasurement fTextAfter; if (pDC) { OsPainter dc = pDC->Handle(); ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } ATSUTab Tabs[32]; for (int i=0; iTabSize()) << 16; Tabs[i].tabType = kATSULeftTab; } e = ATSUSetTabArray(Hnd, Tabs, CountOf(Tabs)); if (e) printf("%s:%i - ATSUSetTabArray failed (e=%i)\n", _FL, (int)e); e = ATSUGetUnjustifiedBounds( Hnd, kATSUFromTextBeginning, kATSUToTextEnd, &fTextBefore, &fTextAfter, &fAscent, &fDescent); if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUGetUnjustifiedBounds failed with errorcode %i (%s)\n", _FL, (int)e, a); DeleteArray(a); return; } xf = fTextAfter - fTextBefore; yf = fAscent + fDescent; x = (xf + 0xffff) >> 16; y = (yf + 0xffff) >> 16; ATSUSetTransientFontMatching(Hnd, true); #endif #elif defined(HAIKU) if (!Font) { LgiTrace("%s:%i - Missing pointer: %p\n", _FL, Font); return; } BFont *fnt = Font->Handle(); if (!fnt) { LgiTrace("%s:%i - Missing handle. %p/%p\n", _FL, fnt); return; } int tabSize = Font->TabSize() ? Font->TabSize() : 32; font_height height = {0}; fnt->GetHeight(&height); yf = y = height.ascent + height.descent + height.leading; if (!Str) return; LUtf8Ptr start(Str); int isTab = -1; for (LUtf8Ptr p(Str); true; p++) { int32_t ch = p; if (isTab < 0) { isTab = IsTabChar(ch); } else if (!ch || IsTabChar(ch) ^ isTab) { auto &l = Info.New(); - l.Str = (OsChar*)start.GetPtr(); // FIXME: get rid of this cast! + l.Str = start.GetPtr(); l.Len = p.GetPtr() - start.GetPtr(); - const OsChar *strArr[] = { l.Str }; + const char *strArr[] = { l.Str }; const int32 strLen[] = { l.Len }; float width[1] = { 0 }; fnt->GetStringWidths(strArr, strLen, 1, width); if (isTab) { // Handle tab(s) for (int t=0; tHandle()) Font->Create(); y = Font->GetHeight(); LFontSystem *Sys = LFontSystem::Inst(); if (Sys && Str) { LFont *f = Font; bool GlyphSub = Font->SubGlyphs(); Info[i].Str = Str; int TabSize = Font->TabSize() ? Font->TabSize() : 32; bool WasTab = IsTabChar(*Str); f = GlyphSub ? Sys->GetGlyph(*Str, Font) : Font; if (f && f != Font) { f->Size(Font->Size()); f->SetWeight(Font->GetWeight()); if (!f->Handle()) f->Create(); } bool Debug = WasTab; uint32_t u32; for (LUnicodeString u(Str, StrWords); true; u++) { u32 = *u; LFont *n = GlyphSub ? Sys->GetGlyph(u32, Font) : Font; bool Change = n != f || // The font changed (IsTabChar(u32) ^ WasTab) || // Entering/leaving a run of tabs !u32 || // Hit a NULL character (u.Get() - Info[i].Str) >= 1000; // This is to stop very long segments not rendering if (Change) { // End last segment if (n && n != Font) { n->Size(Font->Size()); n->SetWeight(Font->GetWeight()); if (!n->Handle()) n->Create(); } Info[i].Len = (int) (u.Get() - Info[i].Str); if (Info[i].Len) { if (WasTab) { // Handle tab(s) for (int t=0; tGetHeight() > Font->GetHeight())) { Info[i].SizeDelta = -1; f->PointSize(Font->PointSize() + Info[i].SizeDelta); f->Create(); } #endif if (!f) { // no font, so ? out the chars... as they aren't available anyway // printf("Font Cache Miss, Len=%i\n\t", Info[i].Len); m = Font; for (int n=0; n_Measure(sx, sy, Info[i].Str, Info[i].Len); x += Info[i].X = sx > 0xffff ? 0xffff : sx; } auto Ch = Info[i].First(); Info[i].FontId = !f || Font == f ? 0 : Sys->Lut[Ch]; i++; } f = n; // Start next segment WasTab = IsTabChar(u32); Info[i].Str = u.Get(); } if (!u32) break; } if (Info.Length() > 0 && Info.Last().Len == 0) { Info.Length(Info.Length()-1); } } xf = x; yf = y; #endif } int LDisplayString::GetDrawOffset() { return DrawOffsetF >> FShift; } void LDisplayString::SetDrawOffset(int Px) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Px << FShift; } bool LDisplayString::ShowVisibleTab() { return VisibleTab; } void LDisplayString::ShowVisibleTab(bool i) { VisibleTab = i; } bool LDisplayString::IsTruncated() { return AppendDots; } void LDisplayString::TruncateWithDots(int Width) { Layout(); #if defined __GTK_H__ int Fx = 0; int Fwid = Width << FShift; for (auto &b: d->Blocks) { if (Fwid < Fx + b.X()) { if (b.Hnd) { Gtk::pango_layout_set_ellipsize(b.Hnd, Gtk::PANGO_ELLIPSIZE_END); Gtk::pango_layout_set_width(b.Hnd, Fwid - Fx); Gtk::pango_layout_set_single_paragraph_mode(b.Hnd, true); } else if (b.Fnt) { } break; } Fx += b.X(); } #elif WINNATIVE if (Width < X() + 8) { ssize_t c = CharAt(Width); if (c >= 0 && c < StrWords) { if (c > 0) c--; // fudge room for dots if (c > 0) c--; AppendDots = 1; if (Info.Length()) { int Width = 0; int Pos = 0; for (int i=0; i= Pos && c < Pos + Info[i].Len) { Info[i].Len = (int) (c - Pos); Info[i].Str[Info[i].Len] = 0; LFont *f = Font; if (Info[i].FontId) { LFontSystem *Sys = LFontSystem::Inst(); f = Sys->Font[Info[i].FontId]; f->PointSize(Font->PointSize() + Info[i].SizeDelta); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { int Sx, Sy; f->_Measure(Sx, Sy, Info[i].Str, Info[i].Len); Info[i].X = Sx; Width += Info[i].X; } Info.Length(i + 1); break; } Pos += Info[i].Len; Width += Info[i].X; } int DotsX, DotsY; Font->_Measure(DotsX, DotsY, GDisplayStringDots, 3); x = Width + DotsX; } } } #elif defined(LGI_SDL) #elif defined(MAC) #if USE_CORETEXT if (Hnd) { /* CFAttributedStringRef truncationString = CFAttributedStringCreate(NULL, CFSTR("\u2026"), Font->GetAttributes()); if (truncationString) { CTLineRef truncationToken = CTLineCreateWithAttributedString(truncationString); CFRelease(truncationString); if (truncationToken) { CTLineRef TruncatedLine = CTLineCreateTruncatedLine(Hnd, Width, kCTLineTruncationEnd, truncationToken); CFRelease(truncationToken); if (TruncatedLine) { CFRelease(Hnd); Hnd = TruncatedLine; } } } */ } #endif #endif } ssize_t LDisplayString::CharAt(int Px, LPxToIndexType Type) { Layout(); if (Px < 0) { return 0; } else if (Px >= (int)x) { #if defined __GTK_H__ if (Str) { LUtf8Str u(Str); return u.GetChars(); } return 0; #else #if LGI_DSP_STR_CACHE return WideWords; #else return StrWords; #endif #endif } int Status = -1; #if defined(__GTK_H__) int Fx = 0; int Fpos = Px << FShift; Status = 0; for (auto &b: d->Blocks) { int Index = 0, Trailing = 0; int Foffset = Fpos - Fx; if (b.Hnd && Gtk::pango_layout_xy_to_index(b.Hnd, Foffset, 0, &Index, &Trailing)) { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Foffset=%g index=%i trailing=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, (double)Foffset/FScale, Index, Trailing); LUtf8Str u(Str); while ((OsChar*)u.GetPtr() < Str + Index + Trailing) { u++; Status++; } return Status; } else { if (d->Debug) printf("CharAt(%g) x=%g Status=%i Chars=%i\n", (double)Fpos/FScale, (double)b.X()/FScale, Status, b.Chars); Status += b.Chars; } Fx += b.X(); } #elif defined(LGI_SDL) LAssert(!"Impl me"); #elif defined(MAC) if (Hnd && Str) { #if USE_CORETEXT CGPoint pos = { (CGFloat)Px, 1.0f }; CFIndex utf16 = CTLineGetStringIndexForPosition(Hnd, pos); // 'utf16' is in UTF-16, and the API needs to return a UTF-32 index... // So convert between the 2 here... LAssert(Str != NULL); int utf32 = 0; for (int i=0; Str[i] && iTabSize() ? Font->TabSize() : 32; int Cx = 0; int Char = 0; #if DEBUG_CHAR_AT printf("CharAt(%i) Str='%s'\n", Px, Str); #endif for (int i=0; i= Cx && Px < Cx + Info[i].X) { // The position is in this block of characters if (IsTabChar(Info[i].Str[0])) { // Search through tab block for (int t=0; t= Cx && Px < Cx + TabX) { Status = Char; #if DEBUG_CHAR_AT printf("\tIn tab block %i\n", i); #endif break; } Cx += TabX; Char++; } } else { // Find the pos in this block LFont *f = Font; #if defined(WIN32) if (Info[i].FontId) { f = Sys->Font[Info[i].FontId]; f->Colour(Font->Fore(), Font->Back()); f->Size(Font->Size()); if (!f->Handle()) { f->Create(); } } #endif int Fit = f->_CharAt(Px - Cx, Info[i].Str, Info[i].Len, Type); #if DEBUG_CHAR_AT printf("\tNon tab block %i, Fit=%i, Px-Cx=%i-%i=%i, Str='%.5s'\n", i, Fit, Px, Cx, Px-Cx, Info[i].Str); #endif if (Fit >= 0) Status = Char + Fit; else Status = -1; break; } } else { // Not in this block, skip the whole lot Cx += Info[i].X; Char += Info[i].Len; } } if (Status < 0) { Status = Char; } } #endif return Status; } ssize_t LDisplayString::Length() { return StrWords; } int LDisplayString::X() { Layout(); return x; } int LDisplayString::Y() { Layout(); return y; } LPoint LDisplayString::Size() { Layout(); return LPoint(x, y); } #if defined LGI_SDL template bool CompositeText8Alpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = Div255[a * fore_px.r]; map[a].g = Div255[a * fore_px.g]; map[a].b = Div255[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = (oma * back_px.r) + (a * fore_px.r) / 255; map[a].g = (oma * back_px.g) + (a * fore_px.g) / 255; map[a].b = (oma * back_px.b) + (a * fore_px.b) / 255; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *d = ((OutPx*) (*Out)[py + y]) + Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)d >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, o; OutPx *s; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *d = map[a]; break; default: // Blend o = 255 - a; s = map + a; #define NonPreMulOver32NoAlpha(c) d->c = ((s->c * a) + (Div255[d->c * 255] * o)) / 255 NonPreMulOver32NoAlpha(r); NonPreMulOver32NoAlpha(g); NonPreMulOver32NoAlpha(b); break; } d++; } } else { while (i < e) { // Copy rop *d++ = map[*i++]; } } LAssert((uint8_t*)d <= EndOfBuffer); } return true; } template bool CompositeText8NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { LRgba32 map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... // Create colour map of the foreground/background colours uint8_t *DivLut = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = DivLut[a * fore_px.r]; map[a].g = DivLut[a * fore_px.g]; map[a].b = DivLut[a * fore_px.b]; map[a].a = a; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = DivLut[(oma * back_px.r) + (a * fore_px.r)]; map[a].g = DivLut[(oma * back_px.g) + (a * fore_px.g)]; map[a].b = DivLut[(oma * back_px.b) + (a * fore_px.b)]; map[a].a = 255; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (int y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = (OutPx*) (*Out)[py + y]; if (!dst) continue; dst += Clip.DstClip.x1; if ((uint8_t*)dst < StartOfBuffer) continue; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LRgba32 *src; LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a, oma; while (i < e) { // Alpha blend map and output pixel a = *i++; src = map + a; switch (a) { case 0: break; case 255: // Copy dst->r = src->r; dst->g = src->g; dst->b = src->b; break; default: // Blend OverPm32toPm24(src, dst); break; } dst++; } } else { while (i < e) { // Copy rop src = map + *i++; dst->r = src->r; dst->g = src->g; dst->b = src->b; dst++; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } template bool CompositeText5NoAlpha(LSurface *Out, LSurface *In, LFont *Font, int px, int py, LBlitRegions &Clip) { OutPx map[256]; if (!Out || !In || !Font) return false; // FIXME, do blt clipping here... #define MASK_5BIT 0x1f #define MASK_6BIT 0x3f // Create colour map of the foreground/background colours uint8_t *Div255 = Div255Lut; LColour fore = Font->Fore(); LRgb24 fore_px; fore_px.r = fore.r(); fore_px.g = fore.g(); fore_px.b = fore.b(); if (Font->Transparent()) { for (int a=0; a<256; a++) { map[a].r = (int)Div255[a * fore_px.r] >> 3; map[a].g = (int)Div255[a * fore_px.g] >> 2; map[a].b = (int)Div255[a * fore_px.b] >> 3; } } else { LColour back = Font->Back(); LRgb24 back_px; back_px.r = back.r(); back_px.g = back.g(); back_px.b = back.b(); for (int a=0; a<256; a++) { int oma = 255 - a; map[a].r = Div255[(oma * back_px.r) + (a * fore_px.r)] >> 3; map[a].g = Div255[(oma * back_px.g) + (a * fore_px.g)] >> 2; map[a].b = Div255[(oma * back_px.b) + (a * fore_px.b)] >> 3; } } uint8_t *StartOfBuffer = (*Out)[0]; uint8_t *EndOfBuffer = StartOfBuffer + (Out->GetRowStep() * Out->Y()); for (unsigned y=Clip.SrcClip.y1; y<=Clip.SrcClip.y2; y++) { OutPx *dst = ((OutPx*) (*Out)[py + y]); if (!dst) continue; dst += Clip.DstClip.x1; uint8_t *i = (*In)[y]; if (!i) return false; uint8_t *e = i + Clip.DstClip.X(); LAssert((uint8_t*)dst >= StartOfBuffer); if (Font->Transparent()) { uint8_t a; OutPx *src; while (i < e) { // Alpha blend map and output pixel a = *i++; switch (a) { case 0: break; case 255: // Copy *dst = map[a]; break; default: { // Blend #if 0 uint8_t oma = 255 - a; src = map + a; LRgb24 d = { G5bitTo8bit(dst->r), G6bitTo8bit(dst->g), G5bitTo8bit(dst->b)}; LRgb24 s = { G5bitTo8bit(src->r), G6bitTo8bit(src->g), G5bitTo8bit(src->b)}; dst->r = Div255[(oma * d.r) + (a * s.r)] >> 3; dst->g = Div255[(oma * d.g) + (a * s.g)] >> 2; dst->b = Div255[(oma * d.b) + (a * s.b)] >> 3; #else uint8_t a5 = a >> 3; uint8_t a6 = a >> 2; uint8_t oma5 = MASK_5BIT - a5; uint8_t oma6 = MASK_6BIT - a6; src = map + a; dst->r = ((oma5 * (uint8_t)dst->r) + (a5 * (uint8_t)src->r)) / MASK_5BIT; dst->g = ((oma6 * (uint8_t)dst->g) + (a6 * (uint8_t)src->g)) / MASK_6BIT; dst->b = ((oma5 * (uint8_t)dst->b) + (a5 * (uint8_t)src->b)) / MASK_5BIT; #endif break; } } dst++; } } else { while (i < e) { // Copy rop *dst++ = map[*i++]; } } LAssert((uint8_t*)dst <= EndOfBuffer); } return true; } #endif void LDisplayString::Draw(LSurface *pDC, int px, int py, LRect *r, bool Debug) { Layout(); #if DISPLAY_STRING_FRACTIONAL_NATIVE // GTK / Mac use fractional pixels, so call the fractional version: LRect rc; if (r) { rc = *r; rc.x1 <<= FShift; rc.y1 <<= FShift; #if defined(MAC) && !defined(__GTK_H__) rc.x2 <<= FShift; rc.y2 <<= FShift; #else rc.x2 = (rc.x2 + 1) << FShift; rc.y2 = (rc.y2 + 1) << FShift; #endif } FDraw(pDC, px << FShift, py << FShift, r ? &rc : NULL, Debug); #elif defined(HAIKU) if (!Font || !pDC) { LgiTrace("%s:%i - No ptr: %p/%p.\n", _FL, Font, pDC); return; } BFont *fnt = Font->Handle(); BView *view = pDC->Handle(); if (!fnt || !view) { LgiTrace("%s:%i - No handle: %p/%p(%s).\n", _FL, fnt, view, pDC->GetClass()); return; } if (!Info.Length()) { LgiTrace("%s:%i - No layout.\n", _FL); return; } font_height height = {0}; fnt->GetHeight(&height); if (!Font->Transparent()) { view->SetHighColor(Font->Back()); if (r) view->FillRect(*r); else view->FillRect(BRect(px, py, px+x, py+y)); } view->SetFont(fnt); view->SetHighColor(Font->Fore()); int cx = px; for (auto &i: Info) { view->DrawString(i.Str, i.Len, BPoint(cx, py + height.ascent)); cx += i.X; } #elif defined(LGI_SDL) if (Img && pDC && pDC->Y() > 0 && (*pDC)[0]) { int Ox = 0, Oy = 0; pDC->GetOrigin(Ox, Oy); LBlitRegions Clip(pDC, px-Ox, py-Oy, Img, r); LColourSpace DstCs = pDC->GetColourSpace(); switch (DstCs) { #define DspStrCase(px_fmt, comp) \ case Cs##px_fmt: \ CompositeText##comp(pDC, Img, Font, px-Ox, py-Oy, Clip); \ break; DspStrCase(Rgb16, 5NoAlpha) DspStrCase(Bgr16, 5NoAlpha) DspStrCase(Rgb24, 8NoAlpha) DspStrCase(Bgr24, 8NoAlpha) DspStrCase(Rgbx32, 8NoAlpha) DspStrCase(Bgrx32, 8NoAlpha) DspStrCase(Xrgb32, 8NoAlpha) DspStrCase(Xbgr32, 8NoAlpha) DspStrCase(Rgba32, 8Alpha) DspStrCase(Bgra32, 8Alpha) DspStrCase(Argb32, 8Alpha) DspStrCase(Abgr32, 8Alpha) default: LgiTrace("%s:%i - LDisplayString::Draw Unsupported colour space.\n", _FL); // LAssert(!"Unsupported colour space."); break; #undef DspStrCase } } else LgiTrace("::Draw argument error.\n"); #elif defined WINNATIVE if (Info.Length() && pDC && Font) { LFontSystem *Sys = LFontSystem::Inst(); COLOUR Old = pDC->Colour(); int TabSize = Font->TabSize() ? Font->TabSize() : 32; int Ox = px; LColour cFore = Font->Fore(); LColour cBack = Font->Back(); LColour cWhitespace; if (VisibleTab) { cWhitespace = Font->WhitespaceColour(); LAssert(cWhitespace.IsValid()); } for (int i=0; iFont[Info[i].FontId]; f->Colour(cFore, cBack); auto Sz = Font->Size(); Sz.Value += Info[i].SizeDelta; f->Size(Sz); f->Transparent(Font->Transparent()); f->Underline(Font->Underline()); if (!f->Handle()) { f->Create(); } } else { f = Font; } if (f) { LRect b; if (r) { b.x1 = i ? px : r->x1; b.y1 = r->y1; b.x2 = i < Info.Length() - 1 ? px + Info[i].X - 1 : r->x2; b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Info[i].X - 1; b.y2 = py + Y() - 1; } if (b.Valid()) { if (IsTabChar(*Info[i].Str)) { // Invisible tab... draw blank space if (!Font->Transparent()) { pDC->Colour(cBack); pDC->Rectangle(&b); } if (VisibleTab) { int X = px; for (int n=0; nColour(cWhitespace); DrawWhiteSpace(pDC, '\t', r); X += Dx; } } } else { // Draw the character(s) LColour Fg = f->Fore(); LAssert(Fg.IsValid()); f->_Draw(pDC, px, py, Info[i].Str, Info[i].Len, &b, Fg); if (VisibleTab) { OsChar *start = Info[i].Str; OsChar *s = start; OsChar *e = s + Info[i].Len; int Sp = -1; while (s < e) { if (*s == ' ') { int Sx, Sy; if (Sp < 0) f->_Measure(Sp, Sy, s, 1); f->_Measure(Sx, Sy, start, (int)(s - start)); LRect r(0, 0, Sp-1, Sy-1); r.Offset(px + Sx, py); pDC->Colour(cWhitespace); DrawWhiteSpace(pDC, ' ', r); } s++; } } } } } // Inc my position px += Info[i].X; } if (AppendDots) { int Sx, Sy; Font->_Measure(Sx, Sy, GDisplayStringDots, 3); LRect b; if (r) { b.x1 = px; b.y1 = r->y1; b.x2 = min(px + Sx - 1, r->x2); b.y2 = r->y2; } else { b.x1 = px; b.y1 = py; b.x2 = px + Sx - 1; b.y2 = py + Y() - 1; } LColour Fg = Font->Fore(); Font->_Draw(pDC, px, py, GDisplayStringDots, 3, &b, Fg); } pDC->Colour(Old); } else if (r && Font && !Font->Transparent()) { pDC->Colour(Font->Back()); pDC->Rectangle(r); } #endif } int LDisplayString::GetDrawOffsetF() { return DrawOffsetF; } void LDisplayString::SetDrawOffsetF(int Fpx) { if (LaidOut) LAssert(!"No point setting TabOrigin after string is laid out.\n"); DrawOffsetF = Fpx; } int LDisplayString::FX() { Layout(); return xf; } int LDisplayString::FY() { Layout(); return yf; } LPoint LDisplayString::FSize() { Layout(); return LPoint(xf, yf); } void LDisplayString::FDraw(LSurface *pDC, int fx, int fy, LRect *frc, bool Debug) { Layout(Debug); #if !DISPLAY_STRING_FRACTIONAL_NATIVE // Windows doesn't use fractional pixels, so call the integer version: LRect rc; if (frc) { rc = *frc; rc.x1 >>= FShift; rc.y1 >>= FShift; rc.x2 >>= FShift; rc.y2 >>= FShift; } Draw(pDC, fx >> FShift, fy >> FShift, frc ? &rc : NULL, Debug); #elif defined __GTK_H__ Gtk::cairo_t *cr = pDC->Handle(); if (!cr) { LAssert(!"Can't get cairo."); return; } pango_context_set_font_description(LFontSystem::Inst()->GetContext(), Font->Handle()); cairo_save(cr); LColour b = Font->Back(); double Dx = ((double)fx / FScale); double Dy = ((double)fy / FScale); double rx, ry, rw, rh; if (!Font->Transparent()) { // Background fill cairo_set_source_rgb(cr, (double)b.r()/255.0, (double)b.g()/255.0, (double)b.b()/255.0); cairo_new_path(cr); if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; } else { rx = Dx; ry = Dy; rw = x; rh = y; } cairo_rectangle(cr, rx, ry, rw, rh); cairo_fill(cr); if (frc) { cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } } else if (frc) { rx = ((double)frc->x1 / FScale); ry = ((double)frc->y1 / FScale); rw = (double)frc->X() / FScale; rh = (double)frc->Y() / FScale; cairo_rectangle(cr, rx, ry, rw, rh); cairo_clip(cr); } cairo_translate(cr, Dx, Dy); LColour f = Font->Fore(); for (auto &b: d->Blocks) { double Bx = ((double)b.X()) / FScale; #if DEBUG_BOUNDS double By = Font->GetHeight(); cairo_set_source_rgb(cr, 1.0, 0.0, 0.0); cairo_rectangle(cr, 0, 0, Bx, By); cairo_rectangle(cr, 1, 1, Bx - 2.0, By - 2.0); cairo_set_fill_rule(cr, Gtk::CAIRO_FILL_RULE_EVEN_ODD); cairo_fill(cr); #endif if (b.Hnd) { cairo_set_source_rgb( cr, (double)f.r()/255.0, (double)f.g()/255.0, (double)f.b()/255.0); pango_cairo_show_layout(cr, b.Hnd); if (VisibleTab && Str) { LUtf8Str Ptr(Str); auto Ws = Font->WhitespaceColour(); pDC->Colour(Ws); for (int32 u, Idx = 0 ; (u = Ptr); Idx++) { if (IsTabChar(u) || u == ' ') { Gtk::PangoRectangle pos; Gtk::pango_layout_index_to_pos(b.Hnd, Idx, &pos); LRect r(0, 0, pos.width / FScale, pos.height / FScale); r.Offset(pos.x / FScale, pos.y / FScale); DrawWhiteSpace(pDC, u, r); } Ptr++; } } } else if (b.Fnt) { b.Fnt->Transparent(Font->Transparent()); b.Fnt->Back(Font->Back()); b.Fnt->_Draw(pDC, 0, 0, b.Str, b.Bytes, NULL, f); } else LAssert(0); cairo_translate(cr, Bx, 0); } cairo_restore(cr); #elif defined MAC && !defined(LGI_SDL) int Ox = 0, Oy = 0; int px = fx >> FShift; int py = fy >> FShift; LRect rc; if (frc) rc.Set( frc->x1 >> FShift, frc->y1 >> FShift, frc->x2 >> FShift, frc->y2 >> FShift); if (pDC && !pDC->IsScreen()) pDC->GetOrigin(Ox, Oy); if (pDC && !Font->Transparent()) { LColour Old = pDC->Colour(Font->Back()); if (frc) { pDC->Rectangle(&rc); } else { LRect a(px, py, px + x - 1, py + y - 1); pDC->Rectangle(&a); } pDC->Colour(Old); } if (Hnd && pDC && StrWords > 0) { OsPainter dc = pDC->Handle(); #if USE_CORETEXT int y = (pDC->Y() - py + Oy); CGContextSaveGState(dc); pDC->Colour(Font->Fore()); if (pDC->IsScreen()) { if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } } CGFloat Tx = (CGFloat)fx / FScale - Ox; CGFloat Ty = (CGFloat)y - Font->Ascent(); CGContextSetTextPosition(dc, Tx, Ty); CTLineDraw(Hnd, dc); CGContextRestoreGState(dc); #else ATSUAttributeTag Tags[1] = {kATSUCGContextTag}; ByteCount Sizes[1] = {sizeof(CGContextRef)}; ATSUAttributeValuePtr Values[1] = {&dc}; e = ATSUSetLayoutControls(Hnd, 1, Tags, Sizes, Values); if (e) { printf("%s:%i - ATSUSetLayoutControls failed (e=%i)\n", _FL, (int)e); } else { // Set style attr ATSURGBAlphaColor c; LColour Fore = Font->Fore(); c.red = (double) Fore.r() / 255.0; c.green = (double) Fore.g() / 255.0; c.blue = (double) Fore.b() / 255.0; c.alpha = 1.0; ATSUAttributeTag Tags[] = {kATSURGBAlphaColorTag}; ATSUAttributeValuePtr Values[] = {&c}; ByteCount Lengths[] = {sizeof(c)}; e = ATSUSetAttributes( Font->Handle(), CountOf(Tags), Tags, Lengths, Values); if (e) { printf("%s:%i - Error setting font attr (e=%i)\n", _FL, (int)e); } else { int y = (pDC->Y() - py + Oy); if (pDC->IsScreen()) { CGContextSaveGState(dc); if (frc) { CGRect rect = rc; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } CGContextTranslateCTM(dc, 0, pDC->Y()-1); CGContextScaleCTM(dc, 1.0, -1.0); e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, fx - Long2Fix(Ox), Long2Fix(y) - fAscent); CGContextRestoreGState(dc); } else { if (frc) { CGContextSaveGState(dc); CGRect rect = rc; rect.origin.x -= Ox; rect.origin.y = pDC->Y() - rect.origin.y + Oy - rect.size.height; rect.size.width += 1.0; rect.size.height += 1.0; CGContextClipToRect(dc, rect); } e = ATSUDrawText(Hnd, kATSUFromTextBeginning, kATSUToTextEnd, Long2Fix(px - Ox), Long2Fix(y) - fAscent); if (frc) CGContextRestoreGState(dc); } if (e) { char *a = 0; StringConvert(a, NULL, Str, len); printf("%s:%i - ATSUDrawText failed with %i, len=%i, str=%.20s\n", _FL, (int)e, len, a); DeleteArray(a); } } } #endif } #endif } diff --git a/src/common/General/Containers.cpp b/src/common/General/Containers.cpp --- a/src/common/General/Containers.cpp +++ b/src/common/General/Containers.cpp @@ -1,840 +1,835 @@ /** * \file * \author Matthew Allen * \date 27/7/1995 * \brief Container classes.\n Copyright (C) 1995-2003, Matthew Allen */ #ifdef LINUX #define _ISOC99_SOURCE 1 #include #endif #include #include #include #include #include "lgi/common/Mem.h" #include "lgi/common/Containers.h" #include "lgi/common/LgiString.h" #include "lgi/common/LgiCommon.h" ///////////////////////////////////////////////////////////////////////////////////////////////// int ACmp(LString *a, LString *b) { return stricmp(*a, *b); } int LCmp(char *a, char *b, NativeInt d) { return stricmp(a, b); } void UnitTest_CreateList(LArray &a, List &l, int sz = 100) { a.Empty(); for (int i=0; i &a, List &l) { if (a.Length() != l.Length()) return UnitTest_Err("Wrong size."); int n = 0; for (auto s : l) { LString t = s; if (t.Equals(a[n])) ;//printf("%i: %s\n", n, s); else return UnitTest_Err("Wrong value."); n++; } return true; } #define CHECK(val) if (!(val)) return false bool UnitTest_ListClass() { LArray a; List l; // Create test.. UnitTest_CreateList(a, l, 100); CHECK(UnitTest_Check(a, l)); // Delete tests.. l.Delete(a[3].Get()); a.DeleteAt(3, true); CHECK(UnitTest_Check(a, l)); while (a.Length() > 60) { a.DeleteAt(60, true); l.DeleteAt(60); } CHECK(UnitTest_Check(a, l)); // Sort test.. a.Sort([](auto a, auto b) { return stricmp(*a, *b); }); l.Sort(LCmp); CHECK(UnitTest_Check(a, l)); return true; } //////////////////////////////////////////////////////////////////////////////////////// LMemQueue::LMemQueue(size_t prealloc) { PreAlloc = prealloc; } LMemQueue::~LMemQueue() { Empty(); } LMemQueue &LMemQueue::operator=(LMemQueue &p) { Empty(); PreAlloc = p.PreAlloc; for (auto b: p.Mem) { int Alloc = sizeof(Block) + b->Size; Alloc = LGI_ALLOC_ALIGN(Alloc); Block *n = (Block*) malloc(Alloc); if (n) { memcpy(n, b, sizeof(Block) + b->Size); Mem.Insert(n); } else break; } return *this; } void LMemQueue::Empty() { for (auto b: Mem) free(b); Mem.Empty(); } int64 LMemQueue::GetSize() { int64 Size = 0; for (auto b: Mem) Size += b->Used - b->Next; return Size; } int64 LMemQueue::Peek(uchar *Ptr, int Size) { int64 Status = 0; if (Ptr && Size <= GetSize()) { for (auto b: Mem) { if (Size <= 0) break; int Copy = MIN(Size, b->Size - b->Next); if (Copy > 0) { memcpy(Ptr, b->Ptr() + b->Next, Copy); Ptr += Copy; Size -= Copy; Status += Copy; } } } return Status; } void *LMemQueue::New(int AddBytes) { int64 Len = GetSize(); uchar *Data = Len > 0 ? new uchar[Len+AddBytes] : 0; if (Data) { ssize_t Rd = Read(Data, Len); if (Rd >= 0 && AddBytes) { memset(Data+Len, 0, AddBytes); } } return Data; } ssize_t LMemQueue::Read(void *Ptr, ssize_t Size, int Flags) { int Status = 0; if (Ptr && Size > 0) { for (auto b: Mem) { if (Size <= 0) break; int Copy = (int) MIN(Size, b->Used - b->Next); if (Copy > 0) { memcpy(Ptr, b->Ptr() + b->Next, Copy); ((uchar*&)Ptr) += Copy; Size -= Copy; b->Next += Copy; Status += Copy; } } while (Mem.Length() > 0) { auto it = Mem.begin(); auto b = *it; if (b->Next < b->Used) break; Mem.Delete(b); free(b); } } return Status; } ssize_t LMemQueue::Write(const void *Ptr, ssize_t Size, int Flags) { ssize_t Status = 0; if (Ptr && Size > 0) { if (PreAlloc > 0) { auto it = Mem.rbegin(); Block *Last = *it; if (Last) { int Len = (int) MIN(Size, Last->Size - Last->Used); if (Len > 0) { memcpy(Last->Ptr() + Last->Used, Ptr, Len); Last->Used += Len; Size -= Len; ((uchar*&)Ptr) += Len; Status += Len; } } } if (Size > 0) { ssize_t Bytes = MAX(PreAlloc, Size); ssize_t Alloc = sizeof(Block) + Bytes; Alloc = LGI_ALLOC_ALIGN(Alloc); Block *b = (Block*) malloc(Alloc); if (b) { void *p = b->Ptr(); memcpy(p, Ptr, Size); b->Size = (int)Bytes; b->Used = (int)Size; b->Next = 0; Mem.Insert(b); Status += Size; } } } return Status; } ssize_t LMemQueue::Find(LString str, bool caseSensitive) { if (!str.Get()) return -1; if (!caseSensitive) str = str.Lower(); ssize_t pos = 0; char *s = str.Get(); ssize_t si = 0; for (auto b: Mem) { auto p = b->Ptr() + b->Next; auto e = b->Ptr() + b->Used; while (p < e) { if ( (caseSensitive && (s[si] == *p)) || (!caseSensitive && (s[si] == ToLower(*p))) ) { si++; if (si == str.Length()) return pos - si + 1; } else si = 0; p++; pos++; } } return -1; } void LMemQueue::Iterate(std::function callback, bool reverse) { if (!callback) { LAssert(!"No callback."); return; } if (reverse) { for (auto it = Mem.rbegin(); it >= Mem.begin(); --it) { auto p = (*it)->Ptr(); if (!callback(p, (*it)->Used)) break; } } else { for (auto b: Mem) { auto p = b->Ptr(); if (!callback(p, b->Used)) break; } } } LMemQueue::Buffer LMemQueue::GetBuffer() { Buffer b(this); if (Mem.Length()) { // Check if last block has space left: auto it = Mem.rbegin(); b.blk = *it; if (b.blk->Used < b.blk->Size) { b.ptr = b.blk->Ptr() + b.blk->Used; b.len = b.blk->Size - b.blk->Used; #ifdef _DEBUG memset(b.ptr, 0xcd, b.len); #endif return b; } } // Otherwise allocate a block auto sz = PreAlloc > 0 ? PreAlloc : 1024/* some reasonable default? */; auto alloc = sizeof(Block) + sz; alloc = LGI_ALLOC_ALIGN(alloc); b.blk = (Block*) malloc(alloc); if (b.blk) { b.blk->Next = 0; b.blk->Used = 0; b.blk->Size = (int)sz; b.ptr = b.blk->Ptr(); b.len = sz; #ifdef _DEBUG memset(b.ptr, 0xcd, b.len); #endif Mem.Insert(b.blk); } return b; } bool LMemQueue::Buffer::Commit(size_t bytes) { if (!blk || !ptr) { LAssert(!"Invalid param."); return false; } if (blk->Used + bytes > blk->Size) { LAssert(!"Wrote too much data?"); blk->Used = blk->Size; return false; } blk->Used += (int)bytes; return true; } ////////////////////////////////////////////////////////////////////////// LString LStringPipe::NewGStr() { LString s; int64 Sz = GetSize(); if (Sz > 0) { if (s.Length(Sz)) { ssize_t Rd = Read(s.Get(), s.Length()); if (Rd > 0 && Rd <= Sz) { s.Get()[Rd] = 0; } } else s.Empty(); } return s; } ssize_t LStringPipe::LineChars() { ssize_t Len = -1; for (auto m: Mem) { uint8_t *p = m->Ptr(); for (int i = m->Next; i < m->Used; i++) { Len++; if (p[i] == '\n') return Len; } } return Len; } ssize_t LStringPipe::SaveToBuffer(char *Start, ssize_t BufSize, ssize_t Chars) { char *Str = Start; char *End = Str + BufSize; // Not including NULL auto it = Mem.begin(); Block *m = *it; while (m) { for ( char *MPtr = (char*)m->Ptr(); m->Next < m->Used; m->Next++) { if (Str < End) *Str++ = MPtr[m->Next]; if (MPtr[m->Next] == '\n') { m->Next++; goto EndPop; } } if (m->Next >= m->Used) { Mem.Delete(it); free(m); it = Mem.begin(); m = *it; } } EndPop: *Str = 0; return Str - Start; } ssize_t LStringPipe::Pop(LArray &Buf) { ssize_t Chars = LineChars(); if (Chars < 0) return 0; Chars++; // For the '\n' if ((ssize_t)Buf.Length() < Chars + 1) if (!Buf.Length(Chars + 1)) return -1; SaveToBuffer(Buf.AddressOf(), Chars, Chars); return Chars; } LString LStringPipe::Pop() { LString s; ssize_t Chars = LineChars(); if (Chars < 0) return s; - if (Chars == 0) - { - int asd=0; - } - s.Length(Chars); SaveToBuffer(s.Get(), Chars, Chars); return s; } ssize_t LStringPipe::Pop(char *Str, ssize_t BufSize) { if (!Str) return 0; ssize_t Chars = LineChars(); if (Chars < 0) return 0; return SaveToBuffer(Str, BufSize-1 /* for the NULL */, Chars); } ssize_t LStringPipe::Push(const char *Str, ssize_t Chars) { if (!Str) return 0; if (Chars < 0) Chars = strlen(Str); return Write((void*)Str, Chars); } ssize_t LStringPipe::Push(const char16 *Str, ssize_t Chars) { if (!Str) return 0; if (Chars < 0) Chars = StrlenW(Str); return Write((void*)Str, Chars * sizeof(char16)); } #ifdef _DEBUG bool LStringPipe::UnitTest() { char Buf[16]; memset(Buf, 0x1, sizeof(Buf)); LStringPipe p(8); const char s[] = "1234567890abc\n" "abcdefghijklmn\n"; p.Write(s, sizeof(s)-1); ssize_t rd = p.Pop(Buf, 10); int cmp = memcmp(Buf, "123456789\x00\x01\x01\x01\x01\x01\x01", 16); if (cmp) return false; rd = p.Pop(Buf, 10); cmp = memcmp(Buf, "abcdefghi\x00\x01\x01\x01\x01\x01\x01", 16); if (cmp) return false; p.Empty(); p.Write(s, sizeof(s)-1); LString r; r = p.Pop(); if (!r.Equals("1234567890abc")) return false; r = p.Pop(); if (!r.Equals("abcdefghijklmn")) return false; return true; } #endif ////////////////////////////////////////////////////////////////////////////////////////////////// GMemFile::GMemFile(int BlkSize) { CurPos = 0; Blocks = 0; BlockSize = BlkSize < 16 ? 16 : BlkSize; ZeroObj(Local); } GMemFile::~GMemFile() { Empty(); } GMemFile::Block *GMemFile::Get(int Index) { if (Blocks == 0 || Index < 0) return NULL; if (Index < GMEMFILE_BLOCKS) return Local[Index]; if (Index - GMEMFILE_BLOCKS >= Extra.Length()) { LAssert(!"Index beyond last block"); return NULL; } return Extra[Index - GMEMFILE_BLOCKS]; } GMemFile::Block *GMemFile::Create() { int Alloc = sizeof(Block) + BlockSize - 1; Alloc = LGI_ALLOC_ALIGN(Alloc); Block *b = (Block*) malloc(Alloc); if (!b) return NULL; b->Used = 0; b->Offset = Blocks * BlockSize; if (Blocks < GMEMFILE_BLOCKS) Local[Blocks] = b; else Extra.Add(b); Blocks++; return b; } void GMemFile::Empty() { CurPos = 0; for (int i=0; iUsed; } bool GMemFile::FreeBlock(Block *b) { if (!b) return false; ssize_t Idx = b->Offset / BlockSize; if (Idx < GMEMFILE_BLOCKS) { // Local block if (Local[Idx] != b || Idx < Blocks-1) { LAssert(!"Block index error."); return false; } free(b); Local[Idx] = NULL; Blocks--; return true; } // Extra block ssize_t Off = Idx - GMEMFILE_BLOCKS; if (Off != Extra.Length() - 1) { LAssert(!"Block index error."); return false; } free(b); Extra.DeleteAt(Off, true); Blocks--; return true; } int64 GMemFile::SetSize(int64 Size) { if (Size <= 0) { Empty(); } else { int64 CurSize = GetSize(); if (Size > CurSize) { // Increase size... int64 Diff = Size - CurSize; Block *b = GetLast(); if (b->Used < BlockSize) { // Add size to last incomplete block ssize_t Remaining = BlockSize - b->Used; ssize_t Add = MIN(Diff, Remaining); b->Used += Add; Diff -= Add; } while (Diff > 0) { // Add new blocks to cover the size... ssize_t Add = MIN(BlockSize, Diff); b = Create(); b->Used = Add; Diff -= Add; } } else { // Decrease size... uint64 Diff = CurSize - Size; while (Diff > 0 && Blocks > 0) { Block *b = GetLast(); if (!b) break; ssize_t Sub = MIN(b->Used, Diff); b->Used -= Sub; Diff -= Sub; if (b->Used == 0) FreeBlock(b); } } } return GetSize(); } int64 GMemFile::GetPos() { return CurPos; } int64 GMemFile::SetPos(int64 Pos) { if (Pos <= 0) return CurPos = 0; // Off the start of the structure ssize_t BlockIndex = Pos / BlockSize; if (BlockIndex >= Blocks) return CurPos = GetSize(); // Off the end of the structure if (BlockIndex >= 0 && BlockIndex < Blocks - 1) return CurPos = Pos; // Inside a full block Block *Last = GetLast(); uint64 Offset = Pos - Last->Offset; if (Offset >= Last->Used) return CurPos = Last->Offset + Last->Used; // End of last block return CurPos = Pos; // Inside the last block } ssize_t GMemFile::Read(void *Ptr, ssize_t Size, int Flags) { if (!Ptr || Size < 1) return 0; uint8_t *p = (uint8_t*) Ptr; uint8_t *end = p + Size; while (p < end) { int Cur = CurBlock(); if (Cur >= Blocks) break; Block *b = Get(Cur); // Where are we in the current block? ssize_t BlkOffset = CurPos - b->Offset; LAssert(b && BlkOffset >= 0 && BlkOffset <= (ssize_t)b->Used); ssize_t Remaining = b->Used - BlkOffset; if (Remaining > 0) { ssize_t Common = MIN(Remaining, end - p); memcpy(p, b->Data + BlkOffset, Common); CurPos += Common; p += Common; } else break; LAssert(p <= end); if (p >= end) // End of read buffer reached? break; // Exit loop } return p - (uint8_t*) Ptr; } ssize_t GMemFile::Write(const void *Ptr, ssize_t Size, int Flags) { if (!Ptr || Size < 1) return 0; uint8_t *p = (uint8_t*) Ptr; ssize_t len = Size; Block *b = GetLast(); if (b && b->Used < BlockSize) { // Any more space in the last block? ssize_t Remaining = BlockSize - b->Used; ssize_t Common = MIN(Remaining, Size); if (Common > 0) { memcpy(b->Data + b->Used, p, Common); p += Common; len -= Common; b->Used += Common; } } // Store remaining data into new blocks while (len > 0) { b = Create(); if (!b) break; ssize_t Common = MIN(BlockSize, len); memcpy(b->Data, p, Common); b->Used = Common; p += Common; len -= Common; } return Size - len; } diff --git a/src/common/General/DateTime.cpp b/src/common/General/DateTime.cpp --- a/src/common/General/DateTime.cpp +++ b/src/common/General/DateTime.cpp @@ -1,2157 +1,2212 @@ /* ** FILE: LDateTime.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Scribe Date Time Object ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _DEFAULT_SOURCE #include #include #include #include #include #if defined(MAC) #include #endif #ifdef WINDOWS #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DateTime.h" #include "lgi/common/DocView.h" #if !defined(WINDOWS) constexpr const char *LDateTime::WeekdaysShort[]; constexpr const char *LDateTime::WeekdaysLong[]; constexpr const char *LDateTime::MonthsShort[]; constexpr const char *LDateTime::MonthsLong[]; #define MIN_YEAR 1800 #endif ////////////////////////////////////////////////////////////////////////////// uint16 LDateTime::DefaultFormat = GDTF_DEFAULT; char LDateTime::DefaultSeparator = '/'; uint16 LDateTime::GetDefaultFormat() { if (DefaultFormat == GDTF_DEFAULT) { #ifdef WIN32 TCHAR s[80] = _T("1"); GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE, s, CountOf(s)); switch (_tstoi(s)) { case 0: DefaultFormat = GDTF_MONTH_DAY_YEAR; break; default: case 1: DefaultFormat = GDTF_DAY_MONTH_YEAR; break; case 2: DefaultFormat = GDTF_YEAR_MONTH_DAY; break; } GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ITIME, s, sizeof(s)); if (_tstoi(s) == 1) { DefaultFormat |= GDTF_24HOUR; } else { DefaultFormat |= GDTF_12HOUR; } if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, s, sizeof(s))) DefaultSeparator = (char)s[0]; if (GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SSHORTDATE, s, sizeof(s))) { char Sep[] = { DefaultSeparator, '/', '\\', '-', '.', 0 }; LString Str = s; auto t = Str.SplitDelimit(Sep); for (int i=0; i= low && (v) <= high) bool LDateTime::IsValid() const { return InRange(_Day, 1, 31) && InRange(_Year, 1600, 2100) && InRange(_Thousands, 0, 999) && InRange(_Month, 1, 12) && InRange(_Seconds, 0, 59) && InRange(_Minutes, 0, 59) && InRange(_Hours, 0, 23) && InRange(_Tz, -720, 720); } void LDateTime::SetTimeZone(int NewTz, bool ConvertTime) { if (ConvertTime && NewTz != _Tz) { // printf("SetTimeZone: %i\n", NewTz - _Tz); AddMinutes(NewTz - _Tz); } _Tz = NewTz; } int LDateTime::SystemTimeZone(bool ForceUpdate) { if (ForceUpdate || CurTz == NO_ZONE) { CurTz = 0; CurTzOff = 0; #ifdef MAC #ifdef LGI_COCOA NSTimeZone *timeZone = [NSTimeZone localTimeZone]; if (timeZone) { NSDate *Now = [NSDate date]; CurTz = (int) [timeZone secondsFromGMTForDate:Now] / 60; CurTzOff = [timeZone daylightSavingTimeOffsetForDate:Now] / 60; CurTz -= CurTzOff; } #elif defined LGI_CARBON CFTimeZoneRef tz = CFTimeZoneCopySystem(); CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); Boolean dst = CFTimeZoneIsDaylightSavingTime(tz, now); if (dst) { CFAbsoluteTime next = CFTimeZoneGetNextDaylightSavingTimeTransition(tz, now); CurTz = CFTimeZoneGetSecondsFromGMT(tz, next + 100) / 60; } else { CurTz = CFTimeZoneGetSecondsFromGMT(tz, now) / 60; } CurTzOff = CFTimeZoneGetDaylightSavingTimeOffset(tz, now) / 60; CFRelease(tz); #endif #elif defined(WIN32) timeb tbTime; ftime(&tbTime); CurTz = -tbTime.timezone; TIME_ZONE_INFORMATION Tzi; if (GetTimeZoneInformation(&Tzi) == TIME_ZONE_ID_DAYLIGHT) CurTzOff = -Tzi.DaylightBias; #elif defined(LINUX) || defined(HAIKU) int six_months = (365 * 24 * 60 * 60) / 2; time_t now = 0, then = 0; time (&now); then = now - six_months; tm now_tz, then_tz; tm *t = localtime_r(&now, &now_tz); if (t) { localtime_r(&then, &then_tz); CurTz = now_tz.tm_gmtoff / 60; if (now_tz.tm_isdst) { CurTzOff = (now_tz.tm_gmtoff - then_tz.tm_gmtoff) / 60; CurTz = then_tz.tm_gmtoff / 60; } else // This is not DST so there is no offset right? CurTzOff = 0; // (then_tz.tm_gmtoff - now_tz.tm_gmtoff) / 60; } else return NO_ZONE; #else #error "Impl me." #endif } return CurTz + CurTzOff; } int LDateTime::SystemTimeZoneOffset() { if (CurTz == NO_ZONE) SystemTimeZone(); return CurTzOff; } #if defined WIN32 LDateTime ConvertSysTime(SYSTEMTIME &st, int year) { LDateTime n; if (st.wYear) { n.Year(st.wYear); n.Month(st.wMonth); n.Day(st.wDay); } else { n.Year(year); n.Month(st.wMonth); // Find the 'nth' matching weekday, starting from the first day in the month n.Day(1); LDateTime c = n; for (int i=0; iCompare(b); } #elif defined(LINUX) static bool ParseValue(char *s, LAutoString &var, LAutoString &val) { if (!s) return false; char *e = strchr(s, '='); if (!e) return false; *e++ = 0; var.Reset(NewStr(s)); val.Reset(NewStr(e)); *e = '='; return var != 0 && val != 0; } #endif /* Testing code... LDateTime Start, End; LArray Info; Start.Set("1/1/2010"); End.Set("31/12/2014"); LDateTime::GetDaylightSavingsInfo(Info, Start, &End); LStringPipe p; for (int i=0; i,int> { MonthHash() { for (int i=0; i &Info, LDateTime &Start, LDateTime *End) { bool Status = false; #if defined(WIN32) TIME_ZONE_INFORMATION Tzi; auto r = GetTimeZoneInformation(&Tzi); if (r > TIME_ZONE_ID_UNKNOWN) { Info.Length(0); // Find the dates for the previous year from Start. This allows // us to cover the start of the current year. LDateTime s = ConvertSysTime(Tzi.StandardDate, Start.Year() - 1); LDateTime d = ConvertSysTime(Tzi.DaylightDate, Start.Year() - 1); // Create initial Info entry, as the last change in the previous year auto *i = &Info.New(); if (s < d) { // Year is: Daylight->Standard->Daylight LDateTime tmp = d; i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } else { // Year is: Standard->Daylight->Standard LDateTime tmp = s; i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts();; } for (auto y=Start.Year(); y<=(End?End->Year():Start.Year()); y++) { if (s < d) { // Cur year, first event: end of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.StandardDate, y); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); // Cur year, second event: start of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.DaylightDate, y); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } else { // Cur year, first event: start of DST i = &Info.New(); auto tmp = ConvertSysTime(Tzi.DaylightDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.DaylightBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); // Cur year, second event: end of DST i = &Info.New(); tmp = ConvertSysTime(Tzi.StandardDate, Start.Year()); i->Offset = -(Tzi.Bias + Tzi.StandardBias); tmp.AddMinutes(-i->Offset); i->UtcTimeStamp = tmp.Ts(); } } Status = true; } #elif defined(MAC) LDateTime Before = Start; Before.AddMonths(-6); NSTimeZone *tz = [NSTimeZone systemTimeZone]; NSDate *startDate = [[NSDate alloc] initWithTimeIntervalSince1970:(Before.Ts() / Second64Bit) - Offset1800]; for (int n=0; n<6; n++) { NSDate *next = [tz nextDaylightSavingTimeTransitionAfterDate:startDate]; auto &i = Info.New(); i.UtcTimeStamp = ([next timeIntervalSince1970] + Offset1800) * Second64Bit; i.Offset = (int)([tz secondsFromGMTForDate:[next dateByAddingTimeInterval:60]]/60); #if DEBUG_DST_INFO { LDateTime dt; dt.Set(i.UtcTimeStamp); LgiTrace("%s:%i - Ts=%s Off=%i\n", _FL, dt.Get().Get(), i.Offset); } #endif [startDate release]; startDate = next; } #elif defined(LINUX) if (!Zdump.Length()) { FILE *f = popen("zdump -v /etc/localtime", "r"); if (f) { char s[256]; size_t r; LStringPipe p(1024); while ((r = fread(s, 1, sizeof(s), f)) > 0) { p.Write(s, (int)r); } fclose(f); LString ps = p.NewGStr(); Zdump = ps.Split("\n"); } } MonthHash Lut; LDateTime Prev; int PrevOff = 0; for (auto Line: Zdump) { auto l = Line.SplitDelimit(" \t"); if (l.Length() >= 16 && l[0].Equals("/etc/localtime")) { // /etc/localtime Sat Oct 3 15:59:59 2037 UTC = Sun Oct 4 01:59:59 2037 EST isdst=0 gmtoff=36000 // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #if DEBUG_DST_INFO printf("DST: %s\n", Line); #endif LDateTime Utc; Utc.Year(l[5].Int()); auto Tm = l[4].SplitDelimit(":"); if (Tm.Length() != 3) { #if DEBUG_DST_INFO printf("%s:%i - Tm '%s' has wrong parts: %s\n", _FL, l[4], Line); #endif continue; } Utc.Hours(Tm[0].Int()); Utc.Minutes(Tm[1].Int()); Utc.Seconds(Tm[2].Int()); if (Utc.Minutes() < 0) { #if DEBUG_DST_INFO printf("%s:%i - Mins is zero: %s\n", _FL, l[4]); #endif continue; } int m = Lut.Find(l[2]); if (!m) { #if DEBUG_DST_INFO printf("%s:%i - Unknown month '%s'\n", _FL, l[2]); #endif continue; } Utc.Day(l[3].Int()); Utc.Month(m); LAutoString Var, Val; if (!ParseValue(l[14], Var, Val) || stricmp(Var, "isdst")) { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } if (!ParseValue(l[15], Var, Val) || stricmp(Var, "gmtoff")) { #if DEBUG_DST_INFO printf("%s:%i - Unknown value for isdst\n", _FL); #endif continue; } int Off = atoi(Val) / 60; if (Utc.Ts() == 0) continue; if (Prev.Year() && Prev < Start && Start < Utc) { // Emit initial entry for 'start' auto &inf = Info.New(); inf.UtcTimeStamp = Prev; inf.Offset = PrevOff; #if DEBUG_DST_INFO printf("Info: Start=%s %i\n", Prev.Get().Get(), inf.Offset); #endif } if (Utc > Start) { // Emit furthur entries for DST events between start and end. auto &inf = Info.New(); inf.UtcTimeStamp = Utc; inf.Offset = Off; #if DEBUG_DST_INFO printf("Info: Next=%s %i\n", Utc.Get().Get(), inf.Offset); #endif if (End && Utc > *End) { // printf("Utc after end: %s > %s\n", Utc.Get().Get(), End->Get().Get()); break; } } Prev = Utc; PrevOff = Off; } } Status = Info.Length() > 1; #else LAssert(!"Not implemented."); #endif return Status; } bool LDateTime::DstToLocal(LArray &Dst, LDateTime &dt) { if (dt.GetTimeZone()) { LAssert(!"Should be a UTC date."); return true; } // LgiTrace("DstToLocal: %s\n", dt.Get().Get()); LAssert(Dst.Length() > 1); // Needs to have at least 2 entries...? for (size_t i=0; i %s\n", (int)i, start.Get().Get(), end.Get().Get()); if (dt >= start && dt < end) { dt.SetTimeZone(a.Offset, true); return true; } } auto Last = Dst.Last(); LDateTime d; d.Set(Last.UtcTimeStamp); if (dt >= d && dt.Year() == d.Year()) { // If it's after the last DST change but in the same year... it's ok... // Just use the last offset. dt.SetTimeZone(Last.Offset, true); return true; } LAssert(!"No valid DST range for this date."); return false; } int LDateTime::DayOfWeek() const { int Index = 0; int Day = IsLeapYear() ? 29 : 28; switch (_Year / 100) { case 19: { Index = 3; break; } case 20: { Index = 2; break; } } // get year right int y = _Year % 100; int r = y % 12; Index = (Index + (y / 12) + r + (r / 4)) % 7; // get month right if (_Month % 2 == 0) { // even month if (_Month > 2) Day = _Month; } else { // odd month switch (_Month) { case 1: { Day = 31; if (IsLeapYear()) { Index = Index > 0 ? Index - 1 : Index + 6; } break; } case 11: case 3: { Day = 7; break; } case 5: { Day = 9; break; } case 7: { Day = 11; break; } case 9: { Day = 5; break; } } } // get day right int Diff = Index - (Day - _Day); while (Diff < 0) Diff += 7; return Diff % 7; } LDateTime LDateTime::Now() { LDateTime dt; dt.SetNow(); return dt; } -void LDateTime::SetNow() +LDateTime &LDateTime::SetNow() { #ifdef WIN32 - SYSTEMTIME stNow; - FILETIME ftNow; + SYSTEMTIME stNow; + FILETIME ftNow; - GetSystemTime(&stNow); - SystemTimeToFileTime(&stNow, &ftNow); - uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; - Set(i64); + GetSystemTime(&stNow); + SystemTimeToFileTime(&stNow, &ftNow); + uint64 i64 = ((uint64)ftNow.dwHighDateTime << 32) | ftNow.dwLowDateTime; + Set(i64); #else - time_t now; - time(&now); - struct tm *time = localtime(&now); - if (time) - *this = time; - #ifndef LGI_STATIC - else - { - LgiTrace("%s:%i - Error: localtime failed, now=%u\n", _FL, now); - } - #endif + time_t now; + time(&now); + struct tm *time = localtime(&now); + if (time) + *this = time; + #ifndef LGI_STATIC + else + { + LgiTrace("%s:%i - Error: localtime failed, now=%u\n", _FL, now); + } + #endif #endif + + return *this; } #define Convert24HrTo12Hr(h) ( (h) == 0 ? 12 : (h) > 12 ? (h) % 12 : (h) ) #define Convert24HrToAmPm(h) ( (h) >= 12 ? "p" : "a" ) LString LDateTime::GetDate() const { char s[32]; int Ch = GetDate(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetDate(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_DATE_MASK) { case GDTF_MONTH_DAY_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%2.2i" :"%i" , _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; default: case GDTF_DAY_MONTH_YEAR: Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%2.2i" :"%i" , _Day); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, "%c%i", DefaultSeparator, _Year); break; case GDTF_YEAR_MONTH_DAY: Ch += sprintf_s(Str+Ch, SLen-Ch, "%i", _Year); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_MONTH_LEADINGZ?"%c%2.2i":"%c%i", DefaultSeparator, _Month); Ch += sprintf_s(Str+Ch, SLen-Ch, _Format&GDTF_DAY_LEADINGZ ?"%c%2.2i":"%c%i", DefaultSeparator, _Day); break; } } return Ch; } LString LDateTime::GetTime() const { char s[32]; int Ch = GetTime(s, sizeof(s)); return LString(s, Ch); } int LDateTime::GetTime(char *Str, size_t SLen) const { int Ch = 0; if (Str && SLen > 0) { switch (_Format & GDTF_TIME_MASK) { case GDTF_12HOUR: default: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i%s", Convert24HrTo12Hr(_Hours), _Minutes, _Seconds, Convert24HrToAmPm(_Hours)); break; } case GDTF_24HOUR: { Ch += sprintf_s(Str, SLen, "%i:%2.2i:%2.2i", _Hours, _Minutes, _Seconds); break; } } } return Ch; } uint64 LDateTime::Ts() const { uint64 ts = 0; Get(ts); return ts; } bool LDateTime::SetUnix(uint64 s) { #if defined(WINDOWS) return Set(s * LDateTime::Second64Bit + 116445168000000000LL); #else return Set((s + Offset1800) * LDateTime::Second64Bit); #endif } bool LDateTime::Set(uint64 s) { #if defined WIN32 FILETIME Utc; SYSTEMTIME System; // Adjust to the desired timezone uint64 u = s + ((int64)_Tz * 60 * Second64Bit); Utc.dwHighDateTime = u >> 32; Utc.dwLowDateTime = u & 0xffffffff; if (!FileTimeToSystemTime(&Utc, &System)) return false; _Year = System.wYear; _Month = System.wMonth; _Day = System.wDay; _Hours = System.wHour; _Minutes = System.wMinute; _Seconds = System.wSecond; _Thousands = System.wMilliseconds; return true; #else time_t t = (time_t) (((int64)(s / Second64Bit)) - Offset1800); Set(t); _Thousands = s % Second64Bit; return true; #endif } bool LDateTime::Set(time_t tt) { struct tm *t; #if !defined(_MSC_VER) || _MSC_VER < _MSC_VER_VS2005 if (_Tz) { tt += _Tz * 60; } t = gmtime(&tt); if (t) #else struct tm tmp; if (_localtime64_s(t = &tmp, &tt) == 0) #endif { _Year = t->tm_year + 1900; _Month = t->tm_mon + 1; _Day = t->tm_mday; _Hours = t->tm_hour; _Minutes = t->tm_min; _Seconds = t->tm_sec; _Thousands = 0; // _Tz = SystemTimeZone(); return true; } return false; } -bool LDateTime::Get(uint64 &s) const +uint64_t LDateTime::OsTime() const { #ifdef WINDOWS FILETIME Utc; SYSTEMTIME System; System.wYear = _Year; System.wMonth = limit(_Month, 1, 12); System.wDay = limit(_Day, 1, 31); System.wHour = limit(_Hours, 0, 23); System.wMinute = limit(_Minutes, 0, 59); System.wSecond = limit(_Seconds, 0, 59); System.wMilliseconds = limit(_Thousands, 0, 999); System.wDayOfWeek = DayOfWeek(); - BOOL b1; - if (b1 = SystemTimeToFileTime(&System, &Utc)) + if (SystemTimeToFileTime(&System, &Utc)) + { + uint64_t s = ((uint64_t)Utc.dwHighDateTime << 32) | Utc.dwLowDateTime; + + if (_Tz) + // Adjust for timezone + s -= (int64)_Tz * 60 * Second64Bit; + + return s; + } + else { - // Convert to 64bit - s = ((uint64)Utc.dwHighDateTime << 32) | Utc.dwLowDateTime; + DWORD Err = GetLastError(); + LAssert(!"SystemTimeToFileTime failed."); + } + + #else + + if (_Year < MIN_YEAR) + return 0; + + struct tm t; + ZeroObj(t); + t.tm_year = _Year - 1900; + t.tm_mon = _Month - 1; + t.tm_mday = _Day; - // Adjust for timezone - s -= (int64)_Tz * 60 * Second64Bit; + t.tm_hour = _Hours; + t.tm_min = _Minutes; + t.tm_sec = _Seconds; + t.tm_isdst = -1; + + time_t sec = timegm(&t); + if (sec == -1) + return 0; + + if (_Tz) + { + // Adjust the output to UTC from the current timezone. + sec -= _Tz * 60; + } + + return sec; + + #endif - return true; + return 0; +} + +bool LDateTime::Get(uint64 &s) const +{ + #ifdef WINDOWS + + if (!IsValid()) + { + LAssert(!"Needs a valid date."); + return false; } - DWORD Err = GetLastError(); - s = 0; - LAssert(!"SystemTimeToFileTime failed."); - return false; + s = OsTime(); + if (!s) + return false; + + return true; #else if (_Year < MIN_YEAR) return false; - + struct tm t; ZeroObj(t); t.tm_year = _Year - 1900; t.tm_mon = _Month - 1; t.tm_mday = _Day; t.tm_hour = _Hours; t.tm_min = _Minutes; t.tm_sec = _Seconds; t.tm_isdst = -1; time_t sec = timegm(&t); #if 0 printf("timegm(y=%i m=%i d=%i h=%i m=%i s=%i)=%lli\n", t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, sec); #endif if (sec == -1) return false; if (_Tz) { // Adjust the output to UTC from the current timezone. sec -= _Tz * 60; // printf("Adjusting -= %i (%i)\n", _Tz * 60, _Tz); } s = (uint64)(sec + Offset1800) * Second64Bit + _Thousands; return true; #endif } LString LDateTime::Get() const { char buf[32]; int Ch = GetDate(buf, sizeof(buf)); buf[Ch++] = ' '; Ch += GetTime(buf+Ch, sizeof(buf)-Ch); return LString(buf, Ch); } void LDateTime::Get(char *Str, size_t SLen) const { if (Str) { GetDate(Str, SLen); size_t len = strlen(Str); if (len < SLen - 1) { Str[len++] = ' '; GetTime(Str+len, SLen-len); } } } bool LDateTime::Set(const char *Str) { if (!Str) return false; char Local[256]; strcpy_s(Local, sizeof(Local), Str); char *Sep = strchr(Local, ' '); if (Sep) { *Sep++ = 0; if (!SetTime(Sep)) return false; } if (!SetDate(Local)) return false; return true; } void LDateTime::Month(char *m) { int i = IsMonth(m); if (i >= 0) _Month = i + 1; } int DateComponent(const char *s) { int64 i = Atoi(s); return i ? (int)i : LDateTime::IsMonth(s); } bool LDateTime::SetDate(const char *Str) { bool Status = false; if (Str) { auto T = LString(Str).SplitDelimit("/-.,_\\"); if (T.Length() == 3) { int i[3] = { DateComponent(T[0]), DateComponent(T[1]), DateComponent(T[2]) }; int fmt = _Format & GDTF_DATE_MASK; // Do some guessing / overrides. // Don't let _Format define the format completely. if (i[0] > 1000) { fmt = GDTF_YEAR_MONTH_DAY; } else if (i[2] > 1000) { if (i[0] > 12) fmt = GDTF_DAY_MONTH_YEAR; else if (i[1] > 12) fmt = GDTF_MONTH_DAY_YEAR; } switch (fmt) { case GDTF_MONTH_DAY_YEAR: { _Month = i[0]; _Day = i[1]; _Year = i[2]; break; } case GDTF_DAY_MONTH_YEAR: { _Day = i[0]; _Month = i[1]; _Year = i[2]; break; } case GDTF_YEAR_MONTH_DAY: { _Year = i[0]; _Month = i[1]; _Day = i[2]; break; } default: { _Year = i[2]; if ((DefaultFormat & GDTF_DATE_MASK) == GDTF_MONTH_DAY_YEAR) { // Assume m/d/yyyy _Day = i[1]; _Month = i[0]; } else { // Who knows??? // Assume d/m/yyyy _Day = i[0]; _Month = i[1]; } break; } } if (_Year < 100) { LAssert(_Day < 1000 && _Month < 1000); if (_Year >= 80) _Year += 1900; else _Year += 2000; } Status = true; } else { // Fall back to fuzzy matching auto T = LString(Str).SplitDelimit(" ,"); MonthHash Lut; int FMonth = 0; int FDay = 0; int FYear = 0; for (unsigned i=0; i 0) { if (i >= 1000) { FYear = i; } else if (i < 32) { FDay = i; } } } else { int i = Lut.Find(p); if (i) FMonth = i; } } if (FMonth && FDay) { Day(FDay); Month(FMonth); } if (FYear) { Year(FYear); } else { LDateTime Now; Now.SetNow(); Year(Now.Year()); } } } return Status; } bool LDateTime::SetTime(const char *Str) { if (!Str) return false; auto T = LString(Str).SplitDelimit(":."); if (T.Length() < 2 || T.Length() > 4) return false; _Hours = (int)T[0].Int(); _Minutes = (int)T[1].Int(); if (_Hours < 0 || _Minutes < 0) return false; char *s = T[2]; if (s) _Seconds = atoi(s); else _Seconds = 0; _Thousands = 0; s = T.Last(); if (s) { if (strchr(s, 'p') || strchr(s, 'P')) { if (_Hours != 12) _Hours += 12; } else if (strchr(s, 'a') || strchr(s, 'A')) { if (_Hours == 12) _Hours -= 12; } } if (T.Length() > 3) { LString t = "0."; t += s; _Thousands = (int) (t.Float() * 1000); } return true; } int LDateTime::IsWeekDay(const char *s) { for (unsigned n=0; n= 4) { Year((int)t[0].Int()); Month((int)t[1].Int()); Day((int)t[2].Int()); } else if (t[2].Length() >= 4) { Day((int)t[0].Int()); Month((int)t[1].Int()); Year((int)t[2].Int()); } else { LAssert(!"Unknown date format?"); return false; } } } else if (a[i].Length() == 4) Year((int)a[i].Int()); else if (!Day()) Day((int)a[i].Int()); } else if (IsAlpha(*c)) { int WkDay = IsWeekDay(c); if (WkDay >= 0) continue; int Mnth = IsMonth(c); if (Mnth >= 0) Month(Mnth + 1); } else if (*c == '-' || *c == '+') { c++; if (strlen(c) == 4) { // Timezone.. int64 Tz = a[i].Int(); int Hrs = (int) (Tz / 100); int Min = (int) (Tz % 100); SetTimeZone(Hrs * 60 + Min, false); } } } return IsValid(); } int LDateTime::Sizeof() { return sizeof(int) * 7; } bool LDateTime::Serialize(LFile &f, bool Write) { int32 i; if (Write) { #define wf(fld) i = fld; f << i; wf(_Day); wf(_Month); wf(_Year); wf(_Thousands); wf(_Seconds); wf(_Minutes); wf(_Hours); } else { #define rf(fld) f >> i; fld = i; rf(_Day); rf(_Month); rf(_Year); rf(_Thousands); rf(_Seconds); rf(_Minutes); rf(_Hours); } return true; } /* bool LDateTime::Serialize(ObjProperties *Props, char *Name, bool Write) { #ifndef LGI_STATIC if (Props && Name) { struct _Date { uint8_t Day; uint8_t Month; int16_t Year; uint8_t Hour; uint8_t Minute; uint16_t ThouSec; }; LAssert(sizeof(_Date) == 8); if (Write) { _Date d; d.Day = _Day; d.Month = _Month; d.Year = _Year; d.Hour = _Hours; d.Minute = _Minutes; d.ThouSec = (_Seconds * 1000) + _Thousands; return Props->Set(Name, &d, sizeof(d)); } else // Read { void *Ptr; int Len; if (Props->Get(Name, Ptr, Len) && sizeof(_Date) == Len) { _Date *d = (_Date*) Ptr; _Day = d->Day; _Month = d->Month; _Year = d->Year; _Hours = d->Hour; _Minutes = d->Minute; _Seconds = d->ThouSec / 1000; _Thousands = d->ThouSec % 1000; return true; } } } #endif return false; } */ int LDateTime::Compare(const LDateTime *Date) const { // this - *Date auto ThisTs = IsValid() ? Ts() : 0; auto DateTs = Date->IsValid() ? Date->Ts() : 0; // If these ever fire, the cast to int64_t will overflow LAssert((ThisTs & 0x800000000000000) == 0); LAssert((DateTs & 0x800000000000000) == 0); int64_t Diff = (int64_t)ThisTs - DateTs; if (Diff < 0) return -1; return Diff > 0 ? 1 : 0; } #define DATETIME_OP(op) \ bool LDateTime::operator op(const LDateTime &dt) const \ { \ auto a = Ts(); \ auto b = dt.Ts(); \ return a op b; \ } DATETIME_OP(<) DATETIME_OP(<=) DATETIME_OP(>) DATETIME_OP(>=) bool LDateTime::operator ==(const LDateTime &dt) const { return _Year == dt._Year && _Month == dt._Month && _Day == dt._Day && _Hours == dt._Hours && _Minutes == dt._Minutes && _Seconds == dt._Seconds && _Thousands == dt._Thousands; } bool LDateTime::operator !=(const LDateTime &dt) const { return _Year != dt._Year || _Month != dt._Month || _Day != dt._Day || _Hours != dt._Hours || _Minutes != dt._Minutes || _Seconds != dt._Seconds || _Thousands != dt._Thousands; } int LDateTime::DiffMonths(const LDateTime &dt) { int a = (Year() * 12) + Month(); int b = (dt.Year() * 12) + dt.Month(); return b - a; } LDateTime LDateTime::operator -(const LDateTime &dt) { uint64 a, b; Get(a); dt.Get(b); /// Resolution of a second when using 64 bit timestamps int64 Sec = Second64Bit; int64 Min = 60 * Sec; int64 Hr = 60 * Min; int64 Day = 24 * Hr; int64 d = (int64)a - (int64)b; LDateTime r; r._Day = (int16) (d / Day); d -= r._Day * Day; r._Hours = (int16) (d / Hr); d -= r._Hours * Hr; r._Minutes = (int16) (d / Min); d -= r._Minutes * Min; r._Seconds = (int16) (d / Sec); #ifdef WIN32 d -= r._Seconds * Sec; r._Thousands = (int16) (d / 10000); #else r._Thousands = 0; #endif return r; } LDateTime LDateTime::operator +(const LDateTime &dt) { LDateTime s = *this; s.AddMonths(dt.Month()); s.AddDays(dt.Day()); s.AddHours(dt.Hours()); s.AddMinutes(dt.Minutes()); // s.AddSeconds(dt.Seconds()); return s; } LDateTime &LDateTime::operator =(const LDateTime &t) { _Day = t._Day; _Year = t._Year; _Thousands = t._Thousands; _Month = t._Month; _Seconds = t._Seconds; _Minutes = t._Minutes; _Hours = t._Hours; _Tz = t._Tz; _Format = t._Format; return *this; } LDateTime &LDateTime::operator =(struct tm *time) { if (time) { _Seconds = time->tm_sec; _Minutes = time->tm_min; _Hours = time->tm_hour; _Day = time->tm_mday; _Month = time->tm_mon + 1; _Year = time->tm_year + 1900; } else Empty(); return *this; } bool LDateTime::IsSameDay(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month() && Year() == d.Year(); } bool LDateTime::IsSameMonth(LDateTime &d) const { return Day() == d.Day() && Month() == d.Month(); } bool LDateTime::IsSameYear(LDateTime &d) const { return Year() == d.Year(); } bool LDateTime::IsLeapYear(int Year) const { if (Year < 0) Year = _Year; if (Year % 4 != 0) { return false; } if (Year % 400 == 0) { return true; } if (Year % 100 == 0) { return false; } return true; } int LDateTime::DaysInMonth() const { if (_Month == 2 && IsLeapYear()) { return 29; } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; return _Month >= 1 && _Month <= 12 ? DaysInMonth[_Month-1] : 0; } #define MinutesInDay (60*24) void LDateTime::AddSeconds(int64 Seconds) { uint64 i; if (Get(i)) { i += Seconds * Second64Bit; Set(i); } } void LDateTime::AddMinutes(int64 Minutes) { uint64 i; if (Get(i)) { int64 delta = Minutes * 60 * Second64Bit; uint64 n = i + delta; // printf("AddMin " LPrintfInt64 " + " LPrintfInt64 " = " LPrintfInt64 "\n", i, delta, n); Set(n); #if 0 uint64 i2; Get(i2); int64 diff = (int64)i2-(int64)i; #endif } } void LDateTime::AddHours(int64 Hours) { uint64 i; if (Get(i)) { i += Hours * LDateTime::HourLength * Second64Bit; Set(i); } } bool LDateTime::AddDays(int64 Days) { if (!Days) return true; uint64 Ts; if (!Get(Ts)) return false; Ts += Days * LDateTime::DayLength * Second64Bit; bool b = Set(Ts); return b; } void LDateTime::AddMonths(int64 Months) { int64 m = _Month + Months; do { if (m < 1) { _Year--; m += 12; } else if (m > 12) { _Year++; m -= 12; } else { break; } } while (1); _Month = (int16) m; if (_Day > DaysInMonth()) _Day = DaysInMonth(); } -LString LDateTime::DescribePeriod(LDateTime to) +LString LDateTime::DescribePeriod(double seconds) { - auto ThisTs = Ts(); - auto ToTs = to.Ts(); - auto diff = ThisTs < ToTs ? ToTs - ThisTs : ThisTs - ToTs; - - auto seconds = diff / LDateTime::Second64Bit; int mins = (int) (seconds / 60); seconds -= mins * 60; int hrs = mins / 60; mins -= hrs * 60; int days = hrs / 24; hrs -= days * 24; LString s; if (days > 0) s.Printf("%id %ih %im %is", days, hrs, mins, (int)seconds); else if (hrs > 0) s.Printf("%ih %im %is", hrs, mins, (int)seconds); else if (mins > 0) s.Printf("%im %is", mins, (int)seconds); else s.Printf("%is", (int)seconds); return s; } +LString LDateTime::DescribePeriod(LDateTime to) +{ + auto ThisTs = Ts(); + auto ToTs = to.Ts(); + auto diff = ThisTs < ToTs ? ToTs - ThisTs : ThisTs - ToTs; + + auto seconds = (double)diff / LDateTime::Second64Bit; + return DescribePeriod(seconds); +} + int LDateTime::MonthFromName(const char *Name) { if (Name) { for (int m=0; m<12; m++) { if (strnicmp(Name, MonthsShort[m], strlen(MonthsShort[m])) == 0) { return m + 1; break; } } } return -1; } bool LDateTime::Decode(const char *In) { // Test data: // // Tue, 6 Dec 2005 1:25:32 -0800 Empty(); if (!In) { LAssert(0); return false; } bool Status = false; // Tokenize delimited by whitespace LString::Array T = LString(In).SplitDelimit(", \t\r\n"); if (T.Length() < 2) { if (T[0].IsNumeric()) { // Some sort of timestamp? uint64_t Ts = Atoi(T[0].Get()); if (Ts > 0) { return SetUnix(Ts); } else return false; } else { // What now? return false; } } else { bool GotDate = false; for (unsigned i=0; i 31) { // Y/M/D? Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else if (Date[2].Int() > 31) { // D/M/Y? Day((int)Date[0].Int()); Year((int)Date[2].Int()); } else { // Ambiguous year... bool YrFirst = true; if (Date[0].Length() == 1) YrFirst = false; // else we really can't tell.. just go with year first if (YrFirst) { Year((int)Date[0].Int()); Day((int)Date[2].Int()); } else { Day((int)Date[0].Int()); Year((int)Date[2].Int()); } LDateTime Now; Now.SetNow(); if (Year() + 2000 <= Now.Year()) Year(2000 + Year()); else Year(1900 + Year()); } if (Date[1].IsNumeric()) Month((int)Date[1].Int()); else { int m = MonthFromName(Date[1]); if (m > 0) Month(m); } GotDate = true; Status = true; } else if (s.Find(":") >= 0) { // whole time // Do some validation bool Valid = true; for (char *c = s; *c && Valid; c++) { if (!(IsDigit(*c) || *c == ':')) Valid = false; } if (Valid) { LString::Array Time = s.Split(":"); if (Time.Length() == 2 || Time.Length() == 3) { // Hour int i = (int) Time[0].Int(); if (i >= 0) Hours(i); if (s.Lower().Find("p") >= 0) { if (Hours() < 12) Hours(Hours() + 12); } // Minute i = (int) Time[1].Int(); if (i >= 0) Minutes(i); if (Time.Length() == 3) { // Second i = (int) Time[2].Int(); if (i >= 0) Seconds(i); } Status = true; } } } else if (IsAlpha(s(0))) { // text int m = MonthFromName(s); if (m > 0) Month(m); } else if (strchr("+-", *s)) { // timezone DoTimeZone: LDateTime Now; double OurTmz = (double)Now.SystemTimeZone() / 60; if (s && strchr("-+", *s) && strlen(s) == 5) { #if 1 int i = atoi(s); int hr = i / 100; int min = i % 100; SetTimeZone(hr * 60 + min, false); #else // adjust for timezone char Buf[32]; memcpy(Buf, s, 3); Buf[3] = 0; double TheirTmz = atof(Buf); memcpy(Buf+1, s + 3, 2); TheirTmz += (atof(Buf) / 60); if (Tz) { *Tz = TheirTmz; } double AdjustHours = OurTmz - TheirTmz; AddMinutes((int) (AdjustHours * 60)); #endif } else { // assume GMT AddMinutes((int) (OurTmz * 60)); } } else if (s.IsNumeric()) { int Count = 0; for (char *c = s; *c; c++) { if (!IsDigit(*c)) break; Count++; } if (Count <= 2) { if (Day()) { // We already have a day... so this might be // a 2 digit year... LDateTime Now; Now.SetNow(); int Yr = atoi(s); if (2000 + Yr <= Now.Year()) Year(2000 + Yr); else Year(1900 + Yr); } else { // A day number (hopefully)? Day((int)s.Int()); } } else if (Count == 4) { if (!Year()) { // A year! Year((int)s.Int()); Status = true; } else { goto DoTimeZone; } // My one and only Y2K fix // d.Year((Yr < 100) ? (Yr > 50) ? 1900+Yr : 2000+Yr : Yr); } } } } return Status; } bool LDateTime::GetVariant(const char *Name, LVariant &Dst, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: // Type: Int32 Dst = Year(); break; case DateMonth: // Type: Int32 Dst = Month(); break; case DateDay: // Type: Int32 Dst = Day(); break; case DateHour: // Type: Int32 Dst = Hours(); break; case DateMinute: // Type: Int32 Dst = Minutes(); break; case DateSecond: // Type: Int32 Dst = Seconds(); break; case DateDate: // Type: String { char s[32]; GetDate(s, sizeof(s)); Dst = s; break; } case DateTime: // Type: String { char s[32]; GetTime(s, sizeof(s)); Dst = s; break; } case TypeString: // Type: String case DateDateAndTime: // Type: String { char s[32]; Get(s, sizeof(s)); Dst = s; break; } case TypeInt: // Type: Int64 case DateTimestamp: // Type: Int64 { uint64 i = 0; Get(i); Dst = (int64)i; break; } case DateSecond64Bit: { Dst = Second64Bit; break; } default: { return false; } } return true; } bool LDateTime::SetVariant(const char *Name, LVariant &Value, char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case DateYear: Year(Value.CastInt32()); break; case DateMonth: Month(Value.CastInt32()); break; case DateDay: Day(Value.CastInt32()); break; case DateHour: Hours(Value.CastInt32()); break; case DateMinute: Minutes(Value.CastInt32()); break; case DateSecond: Seconds(Value.CastInt32()); break; case DateDate: SetDate(Value.Str()); break; case DateTime: SetTime(Value.Str()); break; case DateDateAndTime: Set(Value.Str()); break; case DateTimestamp: Set((uint64)Value.CastInt64()); break; default: return false; } return true; } bool LDateTime::CallMethod(const char *Name, LVariant *ReturnValue, LArray &Args) { switch (LStringToDomProp(Name)) { case DateSetNow: SetNow(); if (ReturnValue) *ReturnValue = true; break; case DateSetStr: if (Args.Length() < 1) return false; bool Status; if (Args[0]->Type == GV_INT64) Status = Set((uint64) Args[0]->Value.Int64); else Status = Set(Args[0]->Str()); if (ReturnValue) *ReturnValue = Status; break; case DateGetStr: { char s[256] = ""; Get(s, sizeof(s)); if (ReturnValue) *ReturnValue = s; break; } default: return false; } return true; } #ifdef _DEBUG #define DATE_ASSERT(i) \ if (!(i)) \ { \ LAssert(!"LDateTime unit test failed."); \ return false; \ } bool LDateTime_Test() { // Check 64bit get/set LDateTime t("1/1/2017 0:0:0"); uint64 i; DATE_ASSERT(t.Get(i)); LgiTrace("Get='%s'\n", t.Get().Get()); uint64 i2 = i + (24ULL * 60 * 60 * LDateTime::Second64Bit); LDateTime t2; t2.SetFormat(GDTF_DAY_MONTH_YEAR); t2.Set(i2); LString s = t2.Get(); LgiTrace("Set='%s'\n", s.Get()); DATE_ASSERT(!stricmp(s, "2/1/2017 12:00:00a") || !stricmp(s, "2/01/2017 12:00:00a")); t.SetNow(); LgiTrace("Now.Local=%s Tz=%.2f\n", t.Get().Get(), t.GetTimeZoneHours()); t2 = t; t2.ToUtc(); LgiTrace("Now.Utc=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); t2.ToLocal(); LgiTrace("Now.Local=%s Tz=%.2f\n", t2.Get().Get(), t2.GetTimeZoneHours()); DATE_ASSERT(t == t2); return true; } #endif diff --git a/src/common/Lgi/Css.cpp b/src/common/Lgi/Css.cpp --- a/src/common/Lgi/Css.cpp +++ b/src/common/Lgi/Css.cpp @@ -1,3028 +1,3030 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/LgiCommon.h" #define DEBUG_CSS_LOGGING 0 #define IsWhite(s) ((s) && strchr(WhiteSpace, (s)) != NULL) #define SkipWhite(s) while (IsWhite(*s)) s++; #undef IsNumeric #define IsNumeric(s) \ ( \ (*(s)) && \ ( \ strchr("-.", *(s)) || \ (*(s) >= '0' && *(s) <= '9') \ ) \ ) #undef IsAlpha #define IsAlpha(s) \ ( \ ((s) >= 'a' && (s) <= 'z') || \ ((s) >= 'A' && (s) <= 'Z') || \ (((uint8_t)s) > 0xa0) \ ) #define CopyPropOnSave(Type, Id) \ { \ Type *e = (Type*)Props.Find(Id); \ if (e) *e = *(t); \ else Props.Add(Id, new Type(*(t))); \ } #define ReleasePropOnSave(Type, Id) \ { \ Type *e = (Type*)Props.Find(Id); \ if (e) *e = *(t); \ else Props.Add(Id, (t).Release()); \ } LHashTbl, LCss::PropType> LCss::Lut; LHashTbl, LCss::PropType> LCss::ParentProp; const char *LCss::PropName(PropType p) { // const char *s; // for (PropType t = Lut.First(&s); t; t = Lut.Next(&s)) for (auto i : Lut) { if (p == i.value) return i.key; } LAssert(!"Add this field to the LUT"); return 0; } float LCss::FontSizeTable[7] = { 0.6f, // SizeXXSmall 0.75f, // SizeXSmall 0.85f, // SizeSmall 1.0f, // SizeMedium 1.2f, // SizeLarge 1.5f, // SizeXLarge 2.0f, // SizeXXLarge }; ///////////////////////////////////////////////////////////////////////////// static bool ParseWord(const char *&s, const char *word) { const char *doc = s; while (*doc && *word) { if (tolower(*doc) == tolower(*word)) { doc++; word++; } else return false; } if (*word) return false; if (*doc && (IsAlpha(*doc) || IsDigit(*doc))) return false; s = doc; return true; } bool ParseProp(char *&s, char *w) { char *doc = s; char *word = w; while (*doc && *word) { if (tolower(*doc) == tolower(*word)) { doc++; word++; } else return false; } if (*word) return false; SkipWhite(doc); if (*doc != ':') return false; s = doc + 1; return true; } static int ParseComponent(const char *&s) { int ret = 0; SkipWhite(s); if (!strnicmp(s, "0x", 2)) { ret = htoi(s); while (*s && (IsDigit(*s) || *s == 'x' || *s == 'X')) s++; } else { ret = atoi(s); while (*s && (IsDigit(*s) || *s == '.' || *s == '-')) s++; SkipWhite(s); if (*s == '%') { s++; ret = ret * 255 / 100; } } SkipWhite(s); return ret; } static char *ParseString(const char *&s) { char *ret = 0; if (*s == '\'' || *s == '\"') { char delim = *s++; char *e = strchr((char*)s, delim); if (!e) return 0; ret = NewStr(s, e - s); s = e + 1; } else { const char *e = s; while (*e && (IsAlpha(*e) || IsDigit(*e) || strchr("_-", *e))) e++; ret = NewStr(s, e - s); s = e; } return ret; } ///////////////////////////////////////////////////////////////////////////// void LCss::Init() { ReadOnly = false; if (Lut.Length() == 0) { Lut.Add("letter-spacing", PropLetterSpacing); Lut.Add("word-wrap", PropWordWrap); Lut.Add("list-style", PropListStyle); Lut.Add("list-style-type", PropListStyleType); Lut.Add("text-align", PropTextAlign); Lut.Add("text-decoration", PropTextDecoration); Lut.Add("display", PropDisplay); Lut.Add("float", PropFloat); Lut.Add("position", PropPosition); Lut.Add("overflow", PropOverflow); Lut.Add("visibility", PropVisibility); Lut.Add("font", PropFont); Lut.Add("font-size", PropFontSize); Lut.Add("fontsize", PropFontSize); // Really, do we need this in the real world?? Lut.Add("font-style", PropFontStyle); Lut.Add("font-variant", PropFontVariant); Lut.Add("font-weight", PropFontWeight); Lut.Add("z-index", PropZIndex); Lut.Add("width", PropWidth); Lut.Add("min-width", PropMinWidth); Lut.Add("max-width", PropMaxWidth); Lut.Add("height", PropHeight); Lut.Add("min-height", PropMinHeight); Lut.Add("max-height", PropMaxHeight); Lut.Add("top", PropTop); Lut.Add("right", PropRight); Lut.Add("bottom", PropBottom); Lut.Add("left", PropLeft); Lut.Add("margin", PropMargin); Lut.Add("margin-top", PropMarginTop); Lut.Add("margin-right", PropMarginRight); Lut.Add("margin-bottom", PropMarginBottom); Lut.Add("margin-left", PropMarginLeft); Lut.Add("padding", PropPadding); Lut.Add("padding-top", PropPaddingTop); Lut.Add("padding-right", PropPaddingRight); Lut.Add("padding-bottom", PropPaddingBottom); Lut.Add("padding-left", PropPaddingLeft); Lut.Add("background", PropBackground); Lut.Add("background-color", PropBackgroundColor); Lut.Add("background-image", PropBackgroundImage); Lut.Add("background-repeat", PropBackgroundRepeat); Lut.Add("background-attachment", PropBackgroundAttachment); Lut.Add("background-x", PropBackgroundX); Lut.Add("background-y", PropBackgroundY); Lut.Add("background-position", PropBackgroundPos); Lut.Add("border", PropBorder); Lut.Add("border-style", PropBorderStyle); Lut.Add("border-color", PropBorderColor); Lut.Add("border-radius", PropBorderRadius); Lut.Add("border-collapse", PropBorderCollapse); Lut.Add("border-spacing", PropBorderSpacing); Lut.Add("border-top", PropBorderTop); Lut.Add("border-top-color", PropBorderTopColor); Lut.Add("border-top-style", PropBorderTopStyle); Lut.Add("border-top-width", PropBorderTopWidth); Lut.Add("border-right", PropBorderRight); Lut.Add("border-right-color", PropBorderRightColor); Lut.Add("border-right-style", PropBorderRightStyle); Lut.Add("border-right-width", PropBorderRightWidth); Lut.Add("border-bottom", PropBorderBottom); Lut.Add("border-bottom-color", PropBorderBottomColor); Lut.Add("border-bottom-style", PropBorderBottomStyle); Lut.Add("border-bottom-width", PropBorderBottomWidth); Lut.Add("border-left", PropBorderLeft); Lut.Add("border-left-color", PropBorderLeftColor); Lut.Add("border-left-style", PropBorderLeftStyle); Lut.Add("border-left-width", PropBorderLeftWidth); Lut.Add("line-height", PropLineHeight); Lut.Add("vertical-align", PropVerticalAlign); Lut.Add("clip", PropClip); Lut.Add("x-rect", PropXSubRect); Lut.Add("color", PropColor); Lut.Add("no-paint-color", PropNoPaintColor); Lut.Add("font-family", PropFontFamily); Lut.Add("cellpadding", Prop_CellPadding); } if (ParentProp.Length() == 0) { ParentProp.Add(PropBorderTopColor, PropBorderTop); ParentProp.Add(PropBorderTopStyle, PropBorderTop); ParentProp.Add(PropBorderTopWidth, PropBorderTop); ParentProp.Add(PropBorderLeftColor, PropBorderLeft); ParentProp.Add(PropBorderLeftStyle, PropBorderLeft); ParentProp.Add(PropBorderLeftWidth, PropBorderLeft); ParentProp.Add(PropBorderRightColor, PropBorderRight); ParentProp.Add(PropBorderRightStyle, PropBorderRight); ParentProp.Add(PropBorderRightWidth, PropBorderRight); ParentProp.Add(PropBorderBottomColor, PropBorderBottom); ParentProp.Add(PropBorderBottomStyle, PropBorderBottom); ParentProp.Add(PropBorderBottomWidth, PropBorderBottom); /* ParentProp.Add(PropBackgroundColor, PropBackground); ParentProp.Add(PropBackgroundImage, PropBackground); ParentProp.Add(PropBackgroundRepeat, PropBackground); ParentProp.Add(PropBackgroundAttachment, PropBackground); ParentProp.Add(PropBackgroundX, PropBackground); ParentProp.Add(PropBackgroundY, PropBackground); */ } } LCss::LCss() : Props(32) { Init(); } LCss::LCss(const LCss &c) { Init(); *this = c; } LCss::~LCss() { Empty(); } int LCss::Len::ToPx(int Box, LFont *Font, int Dpi) { switch (Type) { default: case LenInherit: { return 0; } case LenAuto: case LenNormal: case LenPx: { return (int) Value; } case LenPt: { int EffectiveDpi = Dpi > 0 ? Dpi : LScreenDpi().x; return (int) (Value * EffectiveDpi / 72.0); } case LenCm: { int EffectiveDpi = Dpi > 0 ? Dpi : LScreenDpi().x; return (int) (Value * EffectiveDpi / 2.54); } case LenEm: { int FntHt = Font ? Font->GetHeight() : 18; return (int) (Value * FntHt); } case LenEx: { double FntAsc = Font ? Font->Ascent() : 18.0; return (int) (Value * FntAsc); // haha I don't care. } case LenPercent: { if (Box > 0) return (int) (Box * Value / 100.0); else return 0; // No idea... } } } LCss::Len LCss::Len::operator *(const Len &b) const { if (b.IsDynamic()) { LCss::Len a; switch (b.Type) { case LenPercent: { a.Type = Type; a.Value = Value * b.Value / 100.0f; break; } default: { LAssert(!"Impl me."); return b; } } return a; } return b; } bool LCss::Len::ToString(LStream &p) const { const char *Unit = 0; switch (Type) { case LenPx: Unit = "px"; break; case LenPt: Unit = "pt"; break; case LenEm: Unit = "em"; break; case LenEx: Unit = "ex"; break; case LenPercent: Unit = "%"; break; case LenCm: Unit = "cm"; break; default: break; } if (Unit) { return p.Print("%g%s", Value, Unit) > 0; } switch (Type) { case LenInherit: Unit = "Inherit"; break; case LenAuto: Unit = "Auto"; break; case LenNormal: Unit = "Normal"; break; case AlignLeft: Unit = "left"; break; case AlignRight: Unit = "right"; break; case AlignCenter: Unit = "center"; break; case AlignJustify: Unit = "justify"; break; case VerticalBaseline: Unit = "baseline"; break; case VerticalSub: Unit = "sub"; break; case VerticalSuper: Unit = "super"; break; case VerticalTop: Unit = "top"; break; case VerticalTextTop: Unit = "text-top"; break; case VerticalMiddle: Unit = "middle"; break; case VerticalBottom: Unit = "bottom"; break; case VerticalTextBottom: Unit = "text-bottom"; break; default: break; } if (!Unit) { LAssert(!"Impl missing length enum."); return false; } return p.Print("%s", Unit) > 0; } bool LCss::ColorDef::ToString(LStream &p) { switch (Type) { case ColorInherit: { p.Print("inherit"); break; } case ColorTransparent: { p.Print("transparent"); break; } case ColorRgb: { p.Print("#%02.2x%02.2x%02.2x", R32(Rgb32), G32(Rgb32), B32(Rgb32)); break; } default: { LAssert(!"Impl me."); return false; } } return true; } const char *LCss::ToString(DisplayType dt) { switch (dt) { case DispInherit: return "inherit"; case DispBlock: return "block"; case DispInline: return "inline"; case DispInlineBlock: return "inline-block"; case DispListItem: return "list-item"; case DispNone: return "none"; default: return NULL; } } LAutoString LCss::ToString() { LStringPipe p; // PropType Prop; // for (void *v = Props.First((int*)&Prop); v; v = Props.Next((int*)&Prop)) for (auto v : Props) { PropType Prop = (PropType) v.key; switch (v.key >> 8) { case TypeEnum: { const char *s = 0; const char *Name = PropName(Prop); switch (v.key) { case PropFontWeight: { FontWeightType *b = (FontWeightType*)v.value; switch (*b) { case FontWeightInherit: s = "inherit"; break; case FontWeightNormal: s = "normal"; break; case FontWeightBold: s = "bold"; break; case FontWeightBolder: s = "bolder"; break; case FontWeightLighter: s = "lighter"; break; case FontWeight100: s = "100"; break; case FontWeight200: s = "200"; break; case FontWeight300: s = "300"; break; case FontWeight400: s = "400"; break; case FontWeight500: s = "500"; break; case FontWeight600: s = "600"; break; case FontWeight700: s = "700"; break; case FontWeight800: s = "800"; break; case FontWeight900: s = "900"; break; } break; } case PropFontStyle: { FontStyleType *t = (FontStyleType*)v.value; switch (*t) { case FontStyleInherit: s = "inherit"; break; case FontStyleNormal: s = "normal"; break; case FontStyleItalic: s = "italic"; break; case FontStyleOblique: s = "oblique"; break; } break; } case PropTextDecoration: { TextDecorType *d = (TextDecorType*)v.value; switch (*d) { case TextDecorInherit: s= "inherit"; break; case TextDecorNone: s= "none"; break; case TextDecorUnderline: s= "underline"; break; case TextDecorOverline: s= "overline"; break; case TextDecorLineThrough: s= "line-Through"; break; case TextDecorSquiggle: s= "squiggle"; break; } break; } case PropDisplay: { DisplayType *d = (DisplayType*)v.value; s = ToString(*d); break; } case PropFloat: { FloatType *d = (FloatType*)v.value; switch (*d) { case FloatInherit: s = "inherit"; break; case FloatLeft: s = "left"; break; case FloatRight: s = "right"; break; case FloatNone: s = "none"; break; } break; } case PropPosition: { PositionType *d = (PositionType*)v.value; switch (*d) { case PosInherit: s = "inherit"; break; case PosStatic: s = "static"; break; case PosRelative: s = "relative"; break; case PosAbsolute: s = "absolute"; break; case PosFixed: s = "fixed"; break; } break; } case PropOverflow: { OverflowType *d = (OverflowType*)v.value; switch (*d) { case OverflowInherit: s = "inherit"; break; case OverflowVisible: s = "visible"; break; case OverflowHidden: s = "hidden"; break; case OverflowScroll: s = "scroll"; break; case OverflowAuto: s = "auto"; break; } break; } case PropVisibility: { VisibilityType *d = (VisibilityType*)v.value; switch (*d) { case VisibilityInherit: s = "inherit"; break; case VisibilityVisible: s = "visible"; break; case VisibilityHidden: s = "hidden"; break; case VisibilityCollapse: s = "collapse"; break; } break; } case PropFontVariant: { FontVariantType *d = (FontVariantType*)v.value; switch (*d) { case FontVariantInherit: s = "inherit"; break; case FontVariantNormal: s = "normal"; break; case FontVariantSmallCaps: s = "small-caps"; break; } break; } case PropBackgroundRepeat: { RepeatType *d = (RepeatType*)v.value; switch (*d) { default: s = "inherit"; break; case RepeatBoth: s = "repeat"; break; case RepeatX: s = "repeat-x"; break; case RepeatY: s = "repeat-y"; break; case RepeatNone: s = "none"; break; } break; } case PropBackgroundAttachment: { AttachmentType *d = (AttachmentType*)v.value; switch (*d) { default: s = "inherit"; break; case AttachmentScroll: s = "scroll"; break; case AttachmentFixed: s = "fixed"; break; } break; } case PropListStyleType: { ListStyleTypes *w = (ListStyleTypes*)v.value; switch (*w) { default: s = "inherit"; break; case ListNone: s = "none"; break; case ListDisc: s = "disc"; break; case ListCircle: s = "circle"; break; case ListSquare: s = "square"; break; case ListDecimal: s = "decimal"; break; case ListDecimalLeadingZero: s = "decimalleadingzero"; break; case ListLowerRoman: s = "lowerroman"; break; case ListUpperRoman: s = "upperroman"; break; case ListLowerGreek: s = "lowergreek"; break; case ListUpperGreek: s = "uppergreek"; break; case ListLowerAlpha: s = "loweralpha"; break; case ListUpperAlpha: s = "upperalpha"; break; case ListArmenian: s = "armenian"; break; case ListGeorgian: s = "georgian"; break; } break; } case PropBorderCollapse: { BorderCollapseType *w = (BorderCollapseType*)v.value; switch (*w) { default: s = "inherit"; break; case CollapseCollapse: s = "Collapse"; break; case CollapseSeparate: s = "Separate"; break; } break; } default: { LAssert(!"Impl me."); break; } } if (s) p.Print("%s: %s;\n", Name, s); break; } case TypeLen: { Len *l = (Len*)v.value; const char *Name = PropName(Prop); p.Print("%s: ", Name); l->ToString(p); p.Print(";\n"); break; } case TypeGRect: { LRect *r = (LRect*)v.value; const char *Name = PropName(Prop); p.Print("%s: rect(%s);\n", Name, r->GetStr()); break; } case TypeColor: { ColorDef *c = (ColorDef*)v.value; const char *Name = PropName(Prop); p.Print("%s: ", Name); c->ToString(p); p.Print(";\n"); break; } case TypeImage: { ImageDef *i = (ImageDef*)v.value; const char *Name = PropName(Prop); switch (i->Type) { case ImageInherit: { p.Print("%s: inherit;\n", Name); break; } case ImageOwn: case ImageRef: { if (i->Uri) p.Print("%s: url(%s);\n", Name, i->Uri.Get()); break; } default: break; } break; } case TypeBorder: { BorderDef *b = (BorderDef*)v.value; const char *Name = PropName(Prop); p.Print("%s:", Name); b->ToString(p); const char *s = 0; switch (b->Style) { case BorderNone: s = "none"; break; case BorderHidden: s = "hidden"; break; case BorderDotted: s = "dotted"; break; case BorderDashed: s = "dashed"; break; case BorderDouble: s = "double"; break; case BorderGroove: s = "groove"; break; case BorderRidge: s = "ridge"; break; case BorderInset: s = "inset"; break; case BorderOutset: s = "outset"; break; default: case BorderSolid: s = "solid"; break; } p.Print(" %s", s); if (b->Color.Type != ColorInherit) { p.Print(" "); b->Color.ToString(p); } p.Print(";\n"); break; } case TypeStrings: { StringsDef *s = (StringsDef*)v.value; const char *Name = PropName(Prop); p.Print("%s: ", Name); for (int i=0; iLength(); i++) { p.Print("%s%s", i?",":"", (*s)[i]); } p.Print(";\n"); break; } default: { LAssert(!"Invalid type."); break; } } } return LAutoString(p.NewStr()); } bool LCss::InheritCollect(LCss &c, PropMap &Contrib) { int StillInherit = 0; for (auto a : Contrib) { switch (a.key >> 8) { #define InheritEnum(prop, type, inherit) \ case prop: \ { \ type *Mine = (type*)Props.Find(a.key); \ if (!Mine || *Mine == inherit) \ { \ type *Theirs = (type*)c.Props.Find(a.key); \ if (Theirs) \ { \ if (!Mine) Props.Add(a.key, Mine = new type); \ *Mine = *Theirs; \ } \ else StillInherit++; \ } \ break; \ } #define InheritClass(prop, type, inherit) \ case prop: \ { \ type *Mine = (type*)Props.Find(a.key); \ if (!Mine || Mine->Type == inherit) \ { \ type *Theirs = (type*)c.Props.Find(a.key); \ if (Theirs) \ { \ if (!Mine) Props.Add(a.key, Mine = new type); \ *Mine = *Theirs; \ } \ else StillInherit++; \ } \ break; \ } case TypeEnum: { switch (a.key) { InheritEnum(PropFontStyle, FontStyleType, FontStyleInherit); InheritEnum(PropFontVariant, FontVariantType, FontVariantInherit); InheritEnum(PropFontWeight, FontWeightType, FontWeightInherit); InheritEnum(PropTextDecoration, TextDecorType, TextDecorInherit); default: { LAssert(!"Not impl."); break; } } break; } case TypeLen: { Len *Mine = (Len*)Props.Find(a.key); if (!Mine || Mine->IsDynamic()) { Len *Cur = (Len*)c.Props.Find(a.key); if (Cur && Cur->Type != LenInherit) { if (!Mine) Props.Add(a.key, Mine = new Len); *Mine = *Cur; a.value->Add(Cur); } else StillInherit++; } break; } case TypeGRect: { LRect *Mine = (LRect*)Props.Find(a.key); if (!Mine || !Mine->Valid()) { LRect *Theirs = (LRect*)c.Props.Find(a.key); if (Theirs) { if (!Mine) Props.Add(a.key, Mine = new LRect); *Mine = *Theirs; } else StillInherit++; } break; } case TypeStrings: { StringsDef *Mine = (StringsDef*)Props.Find(a.key); if (!Mine || Mine->Length() == 0) { StringsDef *Theirs = (StringsDef*)c.Props.Find(a.key); if (Theirs) { if (!Mine) Props.Add(a.key, Mine = new StringsDef); *Mine = *Theirs; } else StillInherit++; } break; } InheritClass(TypeColor, ColorDef, ColorInherit); InheritClass(TypeImage, ImageDef, ImageInherit); InheritClass(TypeBorder, BorderDef, LenInherit); default: { LAssert(!"Not impl."); break; } } } return StillInherit > 0; } bool LCss::InheritResolve(PropMap &Contrib) { // int p; // for (PropArray *a = Contrib.First(&p); a; a = Contrib.Next(&p)) for (auto a : Contrib) { switch (a.key >> 8) { case TypeLen: { Len *Mine = (Len*)Props.Find(a.key); for (ssize_t i=a.value->Length()-1; i>=0; i--) { Len *Cur = (Len*)(*a.value)[i]; if (!Mine) { Props.Add(a.key, Mine = new Len(*Cur)); continue; } if (Cur->IsDynamic()) { switch (Cur->Type) { case LenAuto: { i = 0; break; } case SizeSmaller: { switch (Mine->Type) { case LenPt: case LenPx: { Mine->Value = Mine->Value - 1; break; } case LenEm: { Mine->Value = Mine->Value * 0.8f; break; } case SizeXXSmall: { // No smaller sizes.. break; } case SizeXSmall: { Mine->Value = SizeXXSmall; break; } case SizeSmall: { Mine->Value = SizeXSmall; break; } case SizeMedium: { Mine->Value = SizeSmall; break; } case SizeLarge: { Mine->Value = SizeMedium; break; } case SizeXLarge: { Mine->Value = SizeLarge; break; } case SizeXXLarge: { Mine->Value = SizeXLarge; break; } case SizeSmaller: { // Just stick with smaller... break; } default: { LAssert(!"Not impl"); break; } } break; } case SizeLarger: { switch (Mine->Type) { case LenPt: case LenPx: { Mine->Value = Mine->Value + 1; break; } case LenEm: { Mine->Value = Mine->Value * 1.2f; break; } case SizeXXSmall: { Mine->Value = SizeXSmall; break; } case SizeXSmall: { Mine->Value = SizeSmall; break; } case SizeSmall: { Mine->Value = SizeMedium; break; } case SizeMedium: { Mine->Value = SizeLarge; break; } case SizeLarge: { Mine->Value = SizeXLarge; break; } case SizeXLarge: { Mine->Value = SizeXXLarge; break; } case SizeXXLarge: { // No higher sizess... break; } case SizeLarger: { // Just stick with larger.... break; } default: { LAssert(!"Not impl"); break; } } break; } case LenPercent: { if (Cur->Value == 100) break; switch (Mine->Type) { case LenPt: case LenPercent: case LenPx: case LenEm: case LenEx: case LenCm: { Mine->Value *= Cur->Value / 100; break; } case SizeXXSmall: case SizeXSmall: case SizeSmall: case SizeMedium: case SizeLarge: case SizeXLarge: case SizeXXLarge: { int Idx = (int)Mine->Type - SizeXXSmall; if (Idx >= 0 && Idx < CountOf(FontSizeTable)) { double Sz = FontSizeTable[Idx]; double NewSz = Sz * Cur->Value / 100; Mine->Value = (float) NewSz; Mine->Type = LenEm; } else LAssert(0); break; } default: { LAssert(!"Not impl"); break; } } break; } default: { LAssert(!"Not impl"); break; } } } else { *Mine = *Cur; } } break; } } } return false; } LCss &LCss::operator -=(const LCss &c) { // Removes all props in 'cc' from this Css store... LCss &cc = (LCss&)c; // int Prop; // for (void *p=cc.Props.First(&Prop); p; p=cc.Props.Next(&Prop)) for (auto p : cc.Props) { DeleteProp((PropType)p.key); } return *this; } bool LCss::CopyStyle(const LCss &c) { LCss &cc = (LCss&)c; // int Prop; // for (void *p=cc.Props.First(&Prop); p; p=cc.Props.Next(&Prop)) for (auto p : cc.Props) { switch (p.key >> 8) { #define CopyProp(TypeId, Type) \ case TypeId: \ { \ Type *n = (Type*)Props.Find(p.key); \ if (!n) n = new Type; \ *n = *(Type*)p.value; \ Props.Add(p.key, n); \ break; \ } case TypeEnum: { void *n = new DisplayType; *(uint32_t*)n = *(uint32_t*)p.value; Props.Add(p.key, n); break; } CopyProp(TypeLen, Len); CopyProp(TypeGRect, LRect); CopyProp(TypeColor, ColorDef); CopyProp(TypeImage, ImageDef); CopyProp(TypeBorder, BorderDef); CopyProp(TypeStrings, StringsDef); default: { LAssert(!"Invalidate property type."); return false; } } } return true; } bool LCss::operator ==(LCss &c) { if (Props.Length() != c.Props.Length()) return false; // Check individual types bool Eq = true; // PropType Prop; // for (void *Local=Props.First((int*)&Prop); Local && Eq; Local=Props.Next((int*)&Prop)) for (auto p : Props) { void *Other = c.Props.Find(p.key); if (!Other) return false; switch (p.key >> 8) { #define CmpType(id, type) \ case id: \ { \ if ( *((type*)p.value) != *((type*)Other)) \ Eq = false; \ break; \ } CmpType(TypeEnum, uint32_t); CmpType(TypeLen, Len); CmpType(TypeGRect, LRect); CmpType(TypeColor, ColorDef); CmpType(TypeImage, ImageDef); CmpType(TypeBorder, BorderDef); CmpType(TypeStrings, StringsDef); default: LAssert(!"Unknown type."); break; } } return Eq; } void LCss::DeleteProp(PropType p) { void *Data = Props.Find(p); if (Data) { DeleteProp(p, Data); Props.Delete(p); } } void LCss::DeleteProp(PropType Prop, void *Data) { if (!Data) return; int Type = Prop >> 8; switch (Type) { case TypeEnum: delete ((PropType*)Data); break; case TypeLen: delete ((Len*)Data); break; case TypeGRect: delete ((LRect*)Data); break; case TypeColor: delete ((ColorDef*)Data); break; case TypeImage: delete ((ImageDef*)Data); break; case TypeBorder: delete ((BorderDef*)Data); break; case TypeStrings: delete ((StringsDef*)Data); break; default: LAssert(!"Unknown property type."); break; } } void LCss::Empty() { // int Prop; // for (void *Data=Props.First(&Prop); Data; Data=Props.Next(&Prop)) for (auto p : Props) { DeleteProp((PropType)p.key, p.value); } Props.Empty(); } /// This returns true if there is a non default font style. Useful for checking if you /// need to create a font... bool LCss::HasFontStyle() { auto Fam = FontFamily(); if (Fam.Length() > 0) return true; auto Sz = FontSize(); if (Sz.IsValid()) return true; auto Style = FontStyle(); if (Style != FontStyleInherit) return true; auto Var = FontVariant(); if (Var != FontVariantInherit) return true; auto Wt = FontWeight(); if (Wt != FontWeightInherit && Wt != FontWeightNormal) return true; auto Dec = TextDecoration(); if (Dec != TextDecorInherit) return true; return false; } void LCss::OnChange(PropType Prop) { } bool LCss::ParseFontStyle(PropType PropId, const char *&s) { FontStyleType *w = (FontStyleType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new FontStyleType); if (ParseWord(s, "inherit")) *w = FontStyleInherit; else if (ParseWord(s, "normal")) *w = FontStyleNormal; else if (ParseWord(s, "italic")) *w = FontStyleItalic; else if (ParseWord(s, "oblique")) *w = FontStyleOblique; else { Props.Delete(PropId); DeleteObj(w); return false; } return true; } bool LCss::ParseFontVariant(PropType PropId, const char *&s) { FontVariantType *w = (FontVariantType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new FontVariantType); if (ParseWord(s, "inherit")) *w = FontVariantInherit; else if (ParseWord(s, "normal")) *w = FontVariantNormal; else if (ParseWord(s, "small-caps")) *w = FontVariantSmallCaps; else { Props.Delete(PropId); DeleteObj(w); return false; } return true; } bool LCss::ParseFontWeight(PropType PropId, const char *&s) { FontWeightType *w = (FontWeightType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new FontWeightType); if (ParseWord(s, "Inherit")) *w = FontWeightInherit; else if (ParseWord(s, "Normal")) *w = FontWeightNormal; else if (ParseWord(s, "Bold")) *w = FontWeightBold; else if (ParseWord(s, "Bolder")) *w = FontWeightBolder; else if (ParseWord(s, "Lighter")) *w = FontWeightLighter; else if (ParseWord(s, "100")) *w = FontWeight100; else if (ParseWord(s, "200")) *w = FontWeight200; else if (ParseWord(s, "300")) *w = FontWeight300; else if (ParseWord(s, "400")) *w = FontWeight400; else if (ParseWord(s, "500")) *w = FontWeight500; else if (ParseWord(s, "600")) *w = FontWeight600; else if (ParseWord(s, "700")) *w = FontWeight700; else if (ParseWord(s, "800")) *w = FontWeight800; else if (ParseWord(s, "900")) *w = FontWeight900; else { Props.Delete(PropId); DeleteObj(w); return false; } return true; } bool LCss::ParseBackgroundRepeat(const char *&s) { RepeatType *w = (RepeatType*)Props.Find(PropBackgroundRepeat); if (!w) Props.Add(PropBackgroundRepeat, w = new RepeatType); if (ParseWord(s, "inherit")) *w = RepeatInherit; else if (ParseWord(s, "repeat-x")) *w = RepeatX; else if (ParseWord(s, "repeat-y")) *w = RepeatY; else if (ParseWord(s, "no-repeat")) *w = RepeatNone; else if (ParseWord(s, "repeat")) *w = RepeatBoth; else return false; return true; } bool LCss::ParseDisplayType(const char *&s) { DisplayType *t = (DisplayType*)Props.Find(PropDisplay); if (!t) Props.Add(PropDisplay, t = new DisplayType); if (ParseWord(s, "block")) *t = DispBlock; else if (ParseWord(s, "inline-block")) *t = DispInlineBlock; else if (ParseWord(s, "inline")) *t = DispInline; else if (ParseWord(s, "list-item")) *t = DispListItem; else if (ParseWord(s, "none")) *t = DispNone; else { *t = DispInherit; return false; } return true; } void LCss::ParsePositionType(const char *&s) { PositionType *t = (PositionType*)Props.Find(PropPosition); if (!t) Props.Add(PropPosition, t = new PositionType); if (ParseWord(s, "static")) *t = PosStatic; else if (ParseWord(s, "relative")) *t = PosRelative; else if (ParseWord(s, "absolute")) *t = PosAbsolute; else if (ParseWord(s, "fixed")) *t = PosFixed; else *t = PosInherit; } bool LCss::Parse(const char *&s, ParsingStyle Type) { if (!s) return false; while (*s && *s != '}') { // Parse the prop name out while (*s && !IsAlpha(*s) && !strchr("-_", *s)) s++; char Prop[64], *p = Prop, *end = Prop + sizeof(Prop) - 1; if (!*s) break; while (*s && (IsAlpha(*s) || strchr("-_", *s))) { if (p < end) *p++ = *s++; else s++; } *p++ = 0; SkipWhite(s); if (*s != ':') return false; s++; PropType PropId = Lut.Find(Prop); PropTypes PropType = (PropTypes)((int)PropId >> 8); SkipWhite(s); // const char *ValueStart = s; // Do the data parsing based on type switch (PropType) { case TypeEnum: { switch (PropId) { case PropDisplay: ParseDisplayType(s); break; case PropPosition: ParsePositionType(s); break; case PropFloat: { FloatType *t = (FloatType*)Props.Find(PropId); if (!t) Props.Add(PropId, t = new FloatType); if (ParseWord(s, "left")) *t = FloatLeft; else if (ParseWord(s, "right")) *t = FloatRight; else if (ParseWord(s, "none")) *t = FloatNone; else *t = FloatInherit; break; } case PropFontStyle: { ParseFontStyle(PropId, s); break; } case PropFontVariant: { ParseFontVariant(PropId, s); break; } case PropFontWeight: { ParseFontWeight(PropId, s); break; } case PropTextDecoration: { TextDecorType *w = (TextDecorType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new TextDecorType); if (ParseWord(s, "inherit")) *w = TextDecorInherit; else if (ParseWord(s, "none")) *w = TextDecorNone; else if (ParseWord(s, "underline")) *w = TextDecorUnderline; else if (ParseWord(s, "overline")) *w = TextDecorOverline; else if (ParseWord(s, "line-through")) *w = TextDecorLineThrough; else if (ParseWord(s, "squiggle")) *w = TextDecorSquiggle; break; } case PropWordWrap: { WordWrapType *w = (WordWrapType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new WordWrapType); if (ParseWord(s, "break-word")) *w = WrapBreakWord; else *w = WrapNormal; break; } case PropBorderCollapse: { BorderCollapseType *w = (BorderCollapseType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new BorderCollapseType); if (ParseWord(s, "inherit")) *w = CollapseInherit; else if (ParseWord(s, "Collapse")) *w = CollapseCollapse; else if (ParseWord(s, "Separate")) *w = CollapseSeparate; break; } case PropBackgroundRepeat: { ParseBackgroundRepeat(s); break; } case PropListStyle: { // Fall thru } case PropListStyleType: { ListStyleTypes *w = (ListStyleTypes*)Props.Find(PropListStyleType); if (!w) Props.Add(PropListStyleType, w = new ListStyleTypes); if (ParseWord(s, "none")) *w = ListNone; else if (ParseWord(s, "disc")) *w = ListDisc; else if (ParseWord(s, "circle")) *w = ListCircle; else if (ParseWord(s, "square")) *w = ListSquare; else if (ParseWord(s, "decimal")) *w = ListDecimal; else if (ParseWord(s, "decimalleadingzero")) *w = ListDecimalLeadingZero; else if (ParseWord(s, "lowerroman")) *w = ListLowerRoman; else if (ParseWord(s, "upperroman")) *w = ListUpperRoman; else if (ParseWord(s, "lowergreek")) *w = ListLowerGreek; else if (ParseWord(s, "uppergreek")) *w = ListUpperGreek; else if (ParseWord(s, "loweralpha")) *w = ListLowerAlpha; else if (ParseWord(s, "upperalpha")) *w = ListUpperAlpha; else if (ParseWord(s, "armenian")) *w = ListArmenian; else if (ParseWord(s, "georgian")) *w = ListGeorgian; else *w = ListInherit; break; } case PropLetterSpacing: { // Fixme: do not care right now... break; } case PropBackgroundAttachment: { AttachmentType *w = (AttachmentType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new AttachmentType); if (ParseWord(s, "inherit")) *w = AttachmentInherit; else if (ParseWord(s, "scroll")) *w = AttachmentScroll; else if (ParseWord(s, "fixed")) *w = AttachmentFixed; break; } case PropOverflow: { OverflowType *w = (OverflowType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new OverflowType); if (ParseWord(s, "inherit")) *w = OverflowInherit; else if (ParseWord(s, "visible")) *w = OverflowVisible; else if (ParseWord(s, "hidden")) *w = OverflowHidden; else if (ParseWord(s, "scroll")) *w = OverflowScroll; else if (ParseWord(s, "auto")) *w = OverflowAuto; break; } case PropVisibility: { VisibilityType *w = (VisibilityType*)Props.Find(PropId); if (!w) Props.Add(PropId, w = new VisibilityType); if (ParseWord(s, "inherit")) *w = VisibilityInherit; else if (ParseWord(s, "visible")) *w = VisibilityVisible; else if (ParseWord(s, "hidden")) *w = VisibilityHidden; else if (ParseWord(s, "collapse")) *w = VisibilityCollapse; break; } case PropBorderTopStyle: case PropBorderRightStyle: case PropBorderBottomStyle: case PropBorderLeftStyle: { LCss::PropType Parent = ParentProp.Find(PropId); if (Parent) { BorderDef *b = (BorderDef*) Props.Find(Parent); if (!b && (b = new BorderDef)) Props.Add(Parent, b); if (b) { if (!b->ParseStyle(s)) { if (Type == ParseStrict) LAssert(!"Colour parsing failed."); } } } break; } case PropFont: { // Clear any existing style info. DeleteProp(PropFontStyle); DeleteProp(PropFontVariant); DeleteProp(PropFontWeight); DeleteProp(PropFontSize); DeleteProp(PropLineHeight); DeleteProp(PropFontFamily); bool ApplySize = true; while (*s) { // Try and guess the parts in any order... SkipWhite(s); if (*s == ';') break; if (*s == '/') { ApplySize = false; s++; } else if (*s == ',') { s++; } else { // Point size...? LAutoPtr Pt(new Len); if (Pt->Parse(s, ApplySize ? PropFontSize : PropLineHeight, Type)) { if (ApplySize) FontSize(*Pt); else LineHeight(*Pt); } else if (!ParseFontStyle(PropFontStyle, s) && !ParseFontVariant(PropFontVariant, s) && !ParseFontWeight(PropFontWeight, s)) { // Face name... LAutoPtr Fam(new StringsDef); if (Fam->Parse(s)) FontFamily(*Fam); else break; } } } break; } default: { LAssert(!"Prop parsing support not implemented."); } } break; } case TypeLen: { LArray Lengths; SkipWhite(s); while (*s && *s != ';') { LAutoPtr t(new Len); if (t->Parse(s, PropId, PropId == PropZIndex ? ParseRelaxed : Type)) { Lengths.Add(t.Release()); SkipWhite(s); } else { if (Type == ParseStrict) LAssert(!"Parsing failed."); break; } } SkipWhite(s); bool Mismatch = false; switch (PropId) { case PropBorderTopWidth: case PropBorderRightWidth: case PropBorderBottomWidth: case PropBorderLeftWidth: { LCss::PropType Parent = ParentProp.Find(PropId); if (Parent) { BorderDef *b = (BorderDef*) Props.Find(Parent); if (!b && (b = new BorderDef)) Props.Add(Parent, b); if (b && Lengths.Length() == 1) { *((LCss::Len*&)b) = *Lengths[0]; } else if (Type == ParseStrict) { LAssert(0); } } break; } case PropPadding: { if (Lengths.Length() == 4) { StoreProp(PropPaddingTop, Lengths[0], true); StoreProp(PropPaddingRight, Lengths[1], true); StoreProp(PropPaddingBottom, Lengths[2], true); StoreProp(PropPaddingLeft, Lengths[3], true); } else if (Lengths.Length() == 3) { StoreProp(PropPaddingTop, Lengths[0], true); StoreProp(PropPaddingLeft, Lengths[1], false); StoreProp(PropPaddingRight, Lengths[1], true); StoreProp(PropPaddingBottom, Lengths[2], true); } else if (Lengths.Length() == 2) { StoreProp(PropPaddingTop, Lengths[0], false); StoreProp(PropPaddingBottom, Lengths[0], true); StoreProp(PropPaddingRight, Lengths[1], false); StoreProp(PropPaddingLeft, Lengths[1], true); } else if (Lengths.Length() == 1) { StoreProp(PropPadding, Lengths[0], true); DeleteProp(PropPaddingLeft); DeleteProp(PropPaddingTop); DeleteProp(PropPaddingRight); DeleteProp(PropPaddingBottom); } else Mismatch = true; if (!Mismatch) { Lengths.Length(0); OnChange(PropPadding); } break; } case PropMargin: { if (Lengths.Length() == 4) { StoreProp(PropMarginTop, Lengths[0], true); StoreProp(PropMarginRight, Lengths[1], true); StoreProp(PropMarginBottom, Lengths[2], true); StoreProp(PropMarginLeft, Lengths[3], true); } else if (Lengths.Length() == 3) { StoreProp(PropMarginTop, Lengths[0], true); StoreProp(PropMarginLeft, Lengths[1], false); StoreProp(PropMarginRight, Lengths[1], true); StoreProp(PropMarginBottom, Lengths[2], true); } else if (Lengths.Length() == 2) { StoreProp(PropMarginTop, Lengths[0], false); StoreProp(PropMarginBottom, Lengths[0], true); StoreProp(PropMarginRight, Lengths[1], false); StoreProp(PropMarginLeft, Lengths[1], true); } else if (Lengths.Length() == 1) { StoreProp(PropMargin, Lengths[0], true); DeleteProp(PropMarginTop); DeleteProp(PropMarginBottom); DeleteProp(PropMarginRight); DeleteProp(PropMarginLeft); } else Mismatch = true; if (!Mismatch) { Lengths.Length(0); OnChange(PropMargin); } } default: { LAssert(ParentProp.Find(PropId) == PropNull); if (Lengths.Length() > 0) { StoreProp(PropId, Lengths[0], true); Lengths.DeleteAt(0); OnChange(PropId); } break; } } Lengths.DeleteObjects(); break; } case TypeBackground: { while (*s && !strchr(";}", *s)) { const char *Start = s; ImageDef Img; if (Img.Parse(s)) { BackgroundImage(Img); continue;; } Len x, y; if (x.Parse(s)) { y.Parse(s); BackgroundX(x); BackgroundY(y); continue; } if (ParseBackgroundRepeat(s)) { continue; } ColorDef Color; if (Color.Parse(s) || OnUnhandledColor(&Color, s)) { BackgroundColor(Color); continue; } SkipWhite(s); while (*s && *s != ';' && !IsWhite(*s)) s++; if (Start == s) { LAssert(!"Parsing hang."); break; } } break; } case TypeColor: { LAutoPtr t(new ColorDef); if (t->Parse(s) || OnUnhandledColor(t, s)) { switch (PropId) { case PropBorderTopColor: case PropBorderRightColor: case PropBorderBottomColor: case PropBorderLeftColor: { LCss::PropType Parent = ParentProp.Find(PropId); if (Parent) { BorderDef *b = (BorderDef*) Props.Find(Parent); if (!b && (b = new BorderDef)) Props.Add(Parent, b); if (b) b->Color = *t; } else LAssert(0); break; } default: { LAssert(ParentProp.Find(PropId) == PropNull); ColorDef *e = (ColorDef*)Props.Find(PropId); if (e) *e = *t; else Props.Add(PropId, t.Release()); break; } } } else if (Type == ParseStrict) LAssert(!"Parsing failed."); break; } case TypeStrings: { LAutoPtr t(new StringsDef); if (t->Parse(s)) { StringsDef *e = (StringsDef*)Props.Find(PropId); if (e) *e = *t; else Props.Add(PropId, t.Release()); } else LAssert(!"Parsing failed."); break; } case TypeBorder: { if (PropId == PropBorderStyle) { LCss::BorderDef b; if (b.ParseStyle(s)) { LCss::BorderDef *db; GetOrCreate(db, PropBorderLeft)->Style = b.Style; GetOrCreate(db, PropBorderRight)->Style = b.Style; GetOrCreate(db, PropBorderTop)->Style = b.Style; GetOrCreate(db, PropBorderBottom)->Style = b.Style; } } else if (PropId == PropBorderColor) { ColorDef c; if (c.Parse(s)) { LCss::BorderDef *db; GetOrCreate(db, PropBorderLeft)->Color = c; GetOrCreate(db, PropBorderRight)->Color = c; GetOrCreate(db, PropBorderTop)->Color = c; GetOrCreate(db, PropBorderBottom)->Color = c; } } else { LAutoPtr t(new BorderDef); if (t->Parse(this, s)) { ReleasePropOnSave(BorderDef, PropId); } } break; } case TypeGRect: { LRect r; if (ParseWord(s, "rect")) { SkipWhite(s); if (*s == '(') { const char *Start = ++s; while (*s && *s != ')' && *s != ';') s++; if (*s == ')') { LString tmp(Start, s - Start); r.SetStr(tmp); s++; LRect *e = (LRect*)Props.Find(PropId); if (e) *e = r; else Props.Add(PropId, new LRect(r)); } return false; } } break; } case TypeImage: { LAutoPtr Img(new ImageDef); if (Img->Parse(s)) { ImageDef *i = (ImageDef*)Props.Find(PropId); if (i) *i = *Img; else Props.Add(PropId, Img.Release()); } else if (Type == ParseStrict) { LAssert(!"Failed to parse Image definition"); return false; } break; } default: { if (Type == ParseStrict) { LAssert(!"Unsupported property type."); return false; } else { #if DEBUG_CSS_LOGGING LgiTrace("%s:%i - Unsupported CSS property: %s\n", _FL, Prop); #endif } break; } } // End of property delimiter while (*s && *s != ';') s++; if (*s != ';') break; s++; } return true; } ///////////////////////////////////////////////////////////////////////////// bool LCss::Len::Parse(const char *&s, PropType Prop, ParsingStyle ParseType) { if (!s) return false; SkipWhite(s); if (ParseWord(s, "inherit")) Type = LenInherit; else if (ParseWord(s, "auto")) Type = LenAuto; + else if (ParseWord(s, "min-content")) Type = LenMinContent; + else if (ParseWord(s, "max-context")) Type = LenMaxContent; else if (ParseWord(s, "normal")) Type = LenNormal; else if (ParseWord(s, "center")) Type = AlignCenter; else if (ParseWord(s, "left")) Type = AlignLeft; else if (ParseWord(s, "right")) Type = AlignRight; else if (ParseWord(s, "justify")) Type = AlignJustify; else if (ParseWord(s, "xx-small")) Type = SizeXXSmall; else if (ParseWord(s, "x-small")) Type = SizeXSmall; else if (ParseWord(s, "small")) Type = SizeSmall; else if (ParseWord(s, "medium")) Type = SizeMedium; else if (ParseWord(s, "large")) Type = SizeLarge; else if (ParseWord(s, "x-large")) Type = SizeXLarge; else if (ParseWord(s, "xx-large")) Type = SizeXXLarge; else if (ParseWord(s, "smaller")) Type = SizeSmaller; else if (ParseWord(s, "larger")) Type = SizeLarger; else if (ParseWord(s, "baseline")) Type = VerticalBaseline; else if (ParseWord(s, "sub")) Type = VerticalSub; else if (ParseWord(s, "super")) Type = VerticalSuper; else if (ParseWord(s, "top")) Type = VerticalTop; else if (ParseWord(s, "text-top")) Type = VerticalTextTop; else if (ParseWord(s, "middle")) Type = VerticalMiddle; else if (ParseWord(s, "bottom")) Type = VerticalBottom; else if (ParseWord(s, "text-bottom")) Type = VerticalTextBottom; else if (IsNumeric(s)) { Value = (float) atof(s); while (IsNumeric(s)) s++; SkipWhite(s); if (*s == '%') { Type = LenPercent; s++; } else if (ParseWord(s, "px"))Type = LenPx; else if (ParseWord(s, "pt")) Type = LenPt; else if (ParseWord(s, "em")) Type = LenEm; else if (ParseWord(s, "ex")) Type = LenEx; else if (ParseWord(s, "cm")) Type = LenCm; else if (IsAlpha(*s)) { // Unknown unit, in the case of a missing ';' we should // reset to "inherit" as it's less damaging to the layout Type = LenInherit; return false; } else if (ParseType == ParseRelaxed) { if (Prop == PropLineHeight) { Type = LenPercent; Value *= 100; } else { Type = LenPx; } } else return false; } else return false; return true; } bool LCss::ColorDef::Parse(const char *&s) { if (!s) return false; #define NamedColour(Name, Value) \ else if (ParseWord(s, #Name)) { Type = ColorRgb; Rgb32 = Value; return true; } #define ParseExpect(s, ch) \ if (*s != ch) return false; \ else s++; SkipWhite(s); if (*s == '#') { s++; int v = 0; const char *e = s; while (*e && e < s + 6) { if (*e >= 'a' && *e <= 'f') { v <<= 4; v |= *e - 'a' + 10; e++; } else if (*e >= 'A' && *e <= 'F') { v <<= 4; v |= *e - 'A' + 10; e++; } else if (*e >= '0' && *e <= '9') { v <<= 4; v |= *e - '0'; e++; } else break; } if (e == s + 3) { Type = ColorRgb; int r = (v >> 8) & 0xf; int g = (v >> 4) & 0xf; int b = v & 0xf; Rgb32 = Rgb32( (r << 4 | r), (g << 4 | g), (b << 4 | b) ); s = e; } else if (e == s + 6) { Type = ColorRgb; int r = (v >> 16) & 0xff; int g = (v >> 8) & 0xff; int b = v & 0xff; Rgb32 = Rgb32(r, g, b); s = e; } else return false; } else if (ParseWord(s, "transparent")) { Type = ColorTransparent; } else if (ParseWord(s, "rgb") && *s == '(') { s++; int r = ParseComponent(s); ParseExpect(s, ','); int g = ParseComponent(s); ParseExpect(s, ','); int b = ParseComponent(s); ParseExpect(s, ')'); Type = ColorRgb; Rgb32 = Rgb32(r, g, b); } else if (ParseWord(s, "rgba") && *s == '(') { s++; int r = ParseComponent(s); ParseExpect(s, ','); int g = ParseComponent(s); ParseExpect(s, ','); int b = ParseComponent(s); ParseExpect(s, ','); int a = ParseComponent(s); ParseExpect(s, ')'); Type = ColorRgb; Rgb32 = Rgba32(r, g, b, a); } else if (ParseWord(s, "-webkit-gradient(")) { LAutoString GradientType(ParseString(s)); ParseExpect(s, ','); if (!GradientType) return false; if (!stricmp(GradientType, "radial")) { } else if (!stricmp(GradientType, "linear")) { Len StartX, StartY, EndX, EndY; if (!StartX.Parse(s, PropNull) || !StartY.Parse(s, PropNull)) return false; ParseExpect(s, ','); if (!EndX.Parse(s, PropNull) || !EndY.Parse(s, PropNull)) return false; ParseExpect(s, ','); SkipWhite(s); while (*s) { if (*s == ')') { Type = ColorLinearGradient; break; } else { LAutoString Stop(ParseString(s)); if (!Stop) return false; if (!stricmp(Stop, "from")) { } else if (!stricmp(Stop, "to")) { } else if (!stricmp(Stop, "stop")) { } else return false; } } } else return false; } NamedColour(black, Rgb32(0x00, 0x00, 0x00)) NamedColour(white, Rgb32(0xff, 0xff, 0xff)) NamedColour(gray, Rgb32(0x80, 0x80, 0x80)) NamedColour(red, Rgb32(0xff, 0x00, 0x00)) NamedColour(yellow, Rgb32(0xff, 0xff, 0x00)) NamedColour(green, Rgb32(0x00, 0x80, 0x00)) NamedColour(orange, Rgb32(0xff, 0xa5, 0x00)) NamedColour(blue, Rgb32(0x00, 0x00, 0xff)) NamedColour(maroon, Rgb32(0x80, 0x00, 0x00)) NamedColour(olive, Rgb32(0x80, 0x80, 0x00)) NamedColour(purple, Rgb32(0x80, 0x00, 0x80)) NamedColour(fuchsia, Rgb32(0xff, 0x00, 0xff)) NamedColour(lime, Rgb32(0x00, 0xff, 0x00)) NamedColour(navy, Rgb32(0x00, 0x00, 0x80)) NamedColour(aqua, Rgb32(0x00, 0xff, 0xff)) NamedColour(teal, Rgb32(0x00, 0x80, 0x80)) NamedColour(silver, Rgb32(0xc0, 0xc0, 0xc0)) NamedColour(ThreeDFace, LColour(L_MED).c32()) else return false; return true; } LCss::ImageDef::~ImageDef() { if (Type == ImageOwn) DeleteObj(Img); } bool LCss::ImageDef::IsValid() { if (Type == ImageUri) return Uri.Get() != NULL; if (Type == ImageOwn) return Img != NULL; return false; } bool LCss::ImageDef::Parse(const char *&s) { SkipWhite(s); if (*s == '-') s++; if (ParseWord(s, "initial")) { Type = ImageInherit; } else if (ParseWord(s, "none")) { Type = ImageNone; } else if (!strnicmp(s, "url(", 4)) { s += 4; char *e = strchr((char*)s, ')'); if (!e) return false; Uri.Set(s, e - s); s = e + 1; Type = ImageRef; } else { return false; } return true; } bool LCss::ImageDef::operator !=(const ImageDef &i) { if (Type != i.Type) return false; if (Uri.Get() && i.Uri.Get()) return Uri.Equals(i.Uri); return true; } LCss::ImageDef &LCss::ImageDef::operator =(const ImageDef &o) { if (Type == ImageOwn) DeleteObj(Img); if (o.Img) { Img = o.Img; Type = ImageRef; } else if ((Uri = o.Uri)) { Type = ImageUri; } return *this; } bool LCss::BorderDef::Parse(LCss *Css, const char *&s) { if (!s) return false; const char *Start = NULL; while (*s && *s != ';') { SkipWhite(s); if (Start == s) { LAssert(0); return false; } Start = s; if (Len::Parse(s, PropBorder, ParseRelaxed)) continue; if (ParseStyle(s)) continue; if (Color.Parse(s)) continue; // Ok running out of ideas here.... // Is it a weird colour? if (Css && Css->OnUnhandledColor(&Color, s)) continue; // Unknown token... try and parse over it? while (*s && *s != ';' && !strchr(WhiteSpace, *s)) s++; } return true; } bool LCss::BorderDef::ParseStyle(const char *&s) { if (ParseWord(s, "Hidden")) Style = BorderHidden; else if (ParseWord(s, "Solid")) Style = BorderSolid; else if (ParseWord(s, "Dotted")) Style = BorderDotted; else if (ParseWord(s, "Dashed")) Style = BorderDashed; else if (ParseWord(s, "Double")) Style = BorderDouble; else if (ParseWord(s, "Groove")) Style = BorderGroove; else if (ParseWord(s, "Ridge")) Style = BorderRidge; else if (ParseWord(s, "Inset")) Style = BorderInset; else if (ParseWord(s, "Outset")) Style = BorderOutset; else if (ParseWord(s, "None")) Style = BorderNone; else if (ParseWord(s, "!important")) Important = false; else return false; return true; } ///////////////////////////////////////////////////////////////////////////////////////// LCss::Selector &LCss::Selector::operator =(const LCss::Selector &s) { Parts.Length(0); for (int i=0; i"; case CombAdjacent: return "+"; default: break; } return ""; }; LAutoString LCss::Selector::Print() { LStringPipe p; for (int i=0; i=0; i--) { Part &p = Parts[i]; switch (p.Type) { case SelType: case SelPseudo: s[0]++; break; case SelClass: case SelAttrib: s[1]++; break; case SelID: s[2]++; break; case CombDesc: case CombChild: case CombAdjacent: i = 0; // exit loop break; default: break; } } return ((uint32_t)s[3]<<24) | ((uint32_t)s[2]<<16) | ((uint32_t)s[1]<<8) | ((uint32_t)s[0]); } bool LCss::Selector::ToString(LStream &p) { // Output the selector parts... for (unsigned i=0; i"); break; case CombAdjacent: p.Print("+"); break; default: LAssert(0); break; } } // And now the rules... p.Print(" {\n%s\n}\n", Style ? Style : ""); return true; } bool LCss::Selector::Parse(const char *&s) { if (!s) return false; const char *Start = s, *Prev = s; LArray Offsets; LStringPipe p; while (*s) { SkipWhite(s); if (!*s) break; if (*s == '{' || *s == ',') { break; } else if (*s == '/') { if (s[1] != '*') return false; s += 2; char *End = strstr((char*)s, "*/"); if (!End) return false; s = End + 2; continue; } else if (*s == '<') { if (s[1] != '!' && s[2] != '-' && s[3] != '-') return false; s += 4; char *End = strstr((char*)s, "-->"); if (!End) return false; s = End + 3; continue; } else if (*s == ':') { s++; if (*s == ':') { s++; } else if (!IsAlpha(*s)) { s++; break; } Part &n = Parts.New(); n.Type = SelPseudo; if (!TokString(n.Value, s)) return false; if (*s == '(') { char *e = strchr(s + 1, ')'); if (e && e - s < 100) { s++; n.Param.Reset(NewStr(s, e - s)); s = e + 1; } } } else if (*s == '#') { s++; Part &n = Parts.New(); n.Type = SelID; if (!TokString(n.Value, s)) return false; } else if (*s == '.') { s++; while (*s && !IsAlpha(*s)) s++; Part &n = Parts.New(); n.Type = SelClass; if (!TokString(n.Value, s)) return false; } else if (*s == '@') { s++; Part &n = Parts.New(); n.Media = MediaNull; LAutoString Str; if (!TokString(Str, s)) return false; if (!Str) return false; if (!_stricmp(Str, "media")) n.Type = SelMedia; else if (!_stricmp(Str, "font-face")) n.Type = SelFontFace; else if (!_stricmp(Str, "page")) n.Type = SelPage; else if (!_stricmp(Str, "list")) n.Type = SelList; else if (!_stricmp(Str, "import")) n.Type = SelImport; else if (!_stricmp(Str, "keyframes")) n.Type = SelKeyFrames; else n.Type = SelIgnored; SkipWhite(s); while (*s && !strchr(";{", *s)) { if (*s == '(') { const char *e = strchr(s, ')'); if (e) { s = e + 1; SkipWhite(s); continue; } } if (*s == ',') { s++; SkipWhite(s); continue; } if (!TokString(Str, s)) { // Skip bad char... s++; break; } SkipWhite(s); if (!Str) break; if (!stricmp(Str, "screen")) n.Media |= MediaScreen; else if (!stricmp(Str, "print")) n.Media |= MediaPrint; } } else if (s[0] == '*') { s++; Part &n = Parts.New(); n.Type = SelUniversal; } else if (IsAlpha(*s)) { Part &n = Parts.New(); n.Type = SelType; if (!TokString(n.Value, s)) return false; } else if (*s == '[') { s++; Part &n = Parts.New(); n.Type = SelAttrib; char *End = strchr((char*)s, ']'); if (!End) return false; n.Value.Reset(NewStr(s, End - s)); s = End + 1; } else { // Unexpected character s++; continue; } const char *Last = s; SkipWhite(s); if (*s == '+') { s++; LAssert(Parts.Length() > 0); Combs.Add(Parts.Length()); Part &n = Parts.New(); n.Type = CombAdjacent; } else if (*s == '>') { s++; LAssert(Parts.Length() > 0); Combs.Add(Parts.Length()); Part &n = Parts.New(); n.Type = CombChild; } else if (s > Last && (IsAlpha(*s) || strchr(".:#", *s))) { LAssert(Parts.Length() > 0); Combs.Add(Parts.Length()); Part &n = Parts.New(); n.Type = CombDesc; } if (*s && s == Prev) { LAssert(!"Parsing is stuck."); return false; } Prev = s; } Raw.Reset(NewStr(Start, s - Start)); return Parts.Length() > 0; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////// #define SkipWhiteSpace(s) while (*s && strchr(" \t\r\n", *s)) s++; static const char *SkipComment(const char *c) { // Skip comment SkipWhiteSpace(c); if (c[0] == '/' && c[1] == '*') { char *e = strstr((char*)c + 2, "*/"); if (e) c = e + 2; else c += 2; SkipWhiteSpace(c); } return c; } bool LCss::Store::Dump(LStream &out) { const char *MapNames[] = {"TypeMap", "ClassMap", "IdMap", NULL}; SelectorMap *Maps[] = {&TypeMap, &ClassMap, &IdMap, NULL}; for (int i=0; Maps[i]; i++) { SelectorMap *m = Maps[i]; out.Print("%s = {\n", MapNames[i]); // const char *Key; // for (SelArray *a = m->First(&Key); a; a = m->Next(&Key)) for (auto a : *m) { out.Print("\t'%s' -> ", a.key); for (int n=0; nLength(); n++) { LCss::Selector *sel = (*a.value)[n]; if (n) out.Print("\t\t"); out.Print("%i of %i: %s\n", n, a.value->Length(), sel->Raw.Get()); // out.Print("\t\t{ %s }\n", sel->Style); } } } return true; } int LCssSelectorCmp(class LCss::Selector **a, LCss::Selector **b) { uint32_t as = (*a)->GetSpecificity(); uint32_t bs = (*b)->GetSpecificity(); if (as == bs) return (*a)->SourceIndex - (*b)->SourceIndex; return as - bs; } void LCss::Store::SortStyles(LCss::SelArray &Styles) { if (Styles.Length() > 1) Styles.Sort(LCssSelectorCmp); } bool LCss::Store::ToString(LStream &p) { SelectorMap *Maps[] = {&TypeMap, &ClassMap, &IdMap, NULL}; for (int i=0; Maps[i]; i++) { SelectorMap *m = Maps[i]; // for (SelArray *a = m->First(); a; a = m->Next()) for (auto a : *m) { for (unsigned n=0; nLength(); n++) { LCss::Selector *sel = (*a.value)[n]; if (!sel->ToString(p)) return false; } } } // Output all the other styles not in the maps... for (unsigned n=0; nToString(p)) return false; } return true; } bool LCss::Store::Parse(const char *&c, int Depth) { int SelectorIndex = 1; if (!c) return false; // const char *Start = c; c = SkipComment(c); if (!strncmp(c, "", 3)) break; SkipWhiteSpace(c); if (*c == '}' && Depth > 0) { c++; return true; } // read selector LArray> Selectors; LCss::Selector *Cur = new LCss::Selector; if (Cur->Parse(c)) { Cur->SourceIndex = SelectorIndex++; Selectors.New().Reset(Cur); } else { DeleteObj(Cur); if (*c) return false; } while (*c) { SkipWhiteSpace(c); if (*c == ',') { c++; Cur = new LCss::Selector; if (Cur && Cur->Parse(c)) { Cur->SourceIndex = SelectorIndex++; Selectors.New().Reset(Cur); } else { DeleteObj(Cur); } } else if (*c == '/') { const char *n = SkipComment(c); if (n == c) c++; else c = n; } else break; } SkipWhiteSpace(c); // read styles if (*c == '{') { c++; SkipWhiteSpace(c); if (Cur && Cur->IsAtMedia()) { // At media rules, so create a child store and put all the rules in there... if (Cur->Children.Reset(new LCss::Store)) { if (!Cur->Children->Parse(c, Depth + 1)) return false; } } else { // Normal rule... const char *Start = c; while (*c && *c != '}') c++; char *Style = NewStr(Start, c - Start); Styles.Add(Style); if (*c) c++; for (int i=0; iStyle = Style; ssize_t n = s->GetSimpleIndex(); if (n >= (ssize_t) s->Parts.Length()) { Error.Printf("ErrSimpleIndex %i>=%zi @ '%.80s'", n, s->Parts.Length(), c); LAssert(!"Part index too high."); return false; } LCss::Selector::Part &p = s->Parts[n]; switch (p.Type) { case LCss::Selector::SelType: { if (p.Value) { SelArray *a = TypeMap.Get(p.Value); a->Add(s.Release()); } break; } case LCss::Selector::SelClass: { if (p.Value) { SelArray *a = ClassMap.Get(p.Value); a->Add(s.Release()); } break; } case LCss::Selector::SelID: { if (p.Value) { SelArray *a = IdMap.Get(p.Value); a->Add(s.Release()); } break; } default: { Other.Add(s.Release()); break; } } } } } else { Selectors.Empty(); break; } } return true; } diff --git a/src/common/Lgi/GuiUtils.cpp b/src/common/Lgi/GuiUtils.cpp --- a/src/common/Lgi/GuiUtils.cpp +++ b/src/common/Lgi/GuiUtils.cpp @@ -1,284 +1,284 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #endif ////////////////////////////////////////////////////////////////////////////////// #if !defined(LK_CONTEXTKEY) #if defined(WINDOWS) #define LK_CONTEXTKEY 0x5d #elif defined(MAC) #define LK_CONTEXTKEY VK_APPS #else #define LK_CONTEXTKEY 0x5d #warning "Check local platform def for app menu key." #endif #endif bool LKey::IsContextMenu() const { #if WINNATIVE || defined(LINUX) return !IsChar && vkey == LK_CONTEXTKEY; #else return false; #endif } ////////////////////////////////////////////////////////////////////////////////// LString LMouse::ToString() const { LString s; s.Printf("LMouse(pos=%i,%i view=%p/%s btns=%i/%i/%i/%i/%i dwn=%i dbl=%i " "ctrl=%i alt=%i sh=%i sys=%i)", x, y, // pos Target, Target?Target->GetClass():NULL, // view Left(), Middle(), Right(), Button1(), Button2(), // btns Down(), Double(), // dwn Ctrl(), Alt(), Shift(), System()); // mod keys return s; } bool LMouse::IsContextMenu() const { if (Right()) return true; - #if defined(MAC) || defined(LINUX) + #if defined(MAC) if (Left() && Ctrl()) return true; #endif return false; } bool LMouse::ToScreen() { if (ViewCoords) { if (!Target) { printf("%s:%i - ToScreen Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToScreen(p); x = p.x; y = p.y; ViewCoords = false; } return true; } bool LMouse::ToView() { if (!ViewCoords) { if (!Target) { printf("%s:%i - ToView Error: Target=%p ViewCoords=%i\n", _FL, Target, ViewCoords); return false; } LPoint p(x, y); Target->PointToView(p); x = p.x; y = p.y; ViewCoords = true; } return true; } #if WINNATIVE #if !defined(DM_POSITION) #define DM_POSITION 0x00000020L #endif typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesA)(PVOID, DWORD, PDISPLAY_DEVICEA, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplayDevicesW)(PVOID, DWORD, PDISPLAY_DEVICEW, DWORD); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsA)(LPCSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEA lpDevMode); typedef WINUSERAPI BOOL (WINAPI *pEnumDisplaySettingsW)(LPCWSTR lpszDeviceName, DWORD iModeNum, LPDEVMODEW lpDevMode); #endif LPointF GDisplayInfo::Scale() { LPointF p((double)Dpi.x / 96.0, (double)Dpi.y / 96.0); return p; } bool LGetDisplays(::LArray &Displays, LRect *AllDisplays) { #if WINNATIVE if (AllDisplays) AllDisplays->ZOff(-1, -1); LLibrary User32("User32"); DISPLAY_DEVICEW disp; ZeroObj(disp); disp.cb = sizeof(disp); pEnumDisplayDevicesW EnumDisplayDevicesW = (pEnumDisplayDevicesW) User32.GetAddress("EnumDisplayDevicesW"); pEnumDisplaySettingsW EnumDisplaySettingsW = (pEnumDisplaySettingsW) User32.GetAddress("EnumDisplaySettingsW"); for (int i=0; EnumDisplayDevicesW(0, i, &disp, 0); i++) { DEVMODEW mode; ZeroObj(mode); mode.dmSize = sizeof(mode); mode.dmDriverExtra = sizeof(mode); if (EnumDisplaySettingsW(disp.DeviceName, ENUM_CURRENT_SETTINGS, &mode)) { GDisplayInfo *Dsp = new GDisplayInfo; if (Dsp) { Dsp->r.ZOff(mode.dmPelsWidth-1, mode.dmPelsHeight-1); if (mode.dmFields & DM_POSITION) { Dsp->r.Offset(mode.dmPosition.x, mode.dmPosition.y); } if (AllDisplays) { if (AllDisplays->Valid()) AllDisplays->Union(&Dsp->r); else *AllDisplays = Dsp->r; } disp.cb = sizeof(disp); Dsp->BitDepth = mode.dmBitsPerPel; Dsp->Refresh = mode.dmDisplayFrequency; Dsp->Name = disp.DeviceString; Dsp->Device = disp.DeviceName; Dsp->Dpi.x = Dsp->Dpi.y = mode.dmLogPixels; DISPLAY_DEVICEW temp = disp; if (EnumDisplayDevicesW(temp.DeviceName, 0, &disp, 0)) Dsp->Monitor = disp.DeviceString; Displays.Add(Dsp); } } disp.cb = sizeof(disp); } #elif defined __GTK_H__ Gtk::GdkDisplay *Dsp = Gtk::gdk_display_get_default(); #if GtkVer(3, 22) int monitors = Gtk::gdk_display_get_n_monitors(Dsp); for (int i=0; ir = geometry; di->Device = Gtk::gdk_monitor_get_manufacturer(m); di->Name = Gtk::gdk_monitor_get_model(m); di->Refresh = Gtk::gdk_monitor_get_refresh_rate(m); Displays.Add(di); } #endif #elif LGI_COCOA for (NSScreen *s in [NSScreen screens]) { GDisplayInfo *di = new GDisplayInfo; di->r = s.frame; Displays.Add(di); } #endif return Displays.Length() > 0; } void GetChildrenList(LViewI *w, List &l) { if (!w) return; for (auto v: w->IterateViews()) { #if WINNATIVE int Style = GetWindowLong(v->Handle(), GWL_STYLE); if (TestFlag(Style, WS_VISIBLE) && !TestFlag(Style, WS_DISABLED)) { if (TestFlag(Style, WS_TABSTOP)) { l.Insert(v); } GetChildrenList(v, l); } #else if (v->Visible() && v->Enabled()) { if (v->GetTabStop()) { l.Insert(v); } GetChildrenList(v, l); } #endif } } LViewI *GetNextTabStop(LViewI *v, bool Back) { if (v) { LWindow *Wnd = v->GetWindow(); if (Wnd) { List All; GetChildrenList(Wnd, All); ssize_t MyIndex = All.IndexOf(v); if (MyIndex >= 0) { int Inc = Back ? -1 : 1; size_t NewIndex = (MyIndex + All.Length() + Inc) % All.Length(); return All.ItemAt(NewIndex); } else { return All[0]; } } } return 0; } diff --git a/src/common/Lgi/LgiCommon.cpp b/src/common/Lgi/LgiCommon.cpp --- a/src/common/Lgi/LgiCommon.cpp +++ b/src/common/Lgi/LgiCommon.cpp @@ -1,2806 +1,2806 @@ // // Cross platform LGI functions // #if LGI_COCOA #import #endif #define _WIN32_WINNT 0x501 #include #include #include #include #ifdef WINDOWS #include #include "lgi/common/RegKey.h" #include #include #else #include #define _getcwd getcwd #endif #include "lgi/common/Lgi.h" #include "lgi/common/Capabilities.h" #if defined(LINUX) && !defined(LGI_SDL) #include "LgiWinManGlue.h" #elif defined(WINDOWS) #include "lgi/common/RegKey.h" #endif #if defined POSIX #include #include #include #include #include "lgi/common/SubProcess.h" #endif #ifdef HAIKU #include #include #else #include "SymLookup.h" #endif #include "lgi/common/Library.h" #include "lgi/common/Net.h" #if defined(__GTK_H__) namespace Gtk { #include "LgiWidget.h" } #endif ////////////////////////////////////////////////////////////////////////// // Misc stuff #if LGI_COCOA || defined(__GTK_H__) LString LgiArgsAppPath; #endif #if defined MAC #import #if defined LGI_CARBON bool _get_path_FSRef(FSRef &fs, LStringPipe &a) { HFSUniStr255 Name; ZeroObj(Name); FSRef Parent; FSCatalogInfo Cat; ZeroObj(Cat); OSErr e = FSGetCatalogInfo(&fs, kFSCatInfoVolume|kFSCatInfoNodeID, &Cat, &Name, NULL, &Parent); if (!e) { if (_get_path_FSRef(Parent, a)) { LAutoString u((char*)LNewConvertCp("utf-8", Name.unicode, "utf-16", Name.length * sizeof(Name.unicode[0]) )); // printf("CatInfo = '%s' %x %x\n", u.Get(), Cat.nodeID, Cat.volume); if (u && Cat.nodeID > 2) { a.Print("%s%s", DIR_STR, u.Get()); } } return true; } return false; } LAutoString FSRefPath(FSRef &fs) { LStringPipe a; if (_get_path_FSRef(fs, a)) { return LAutoString(a.NewStr()); } return LAutoString(); } #endif #endif bool LPostEvent(OsView Wnd, int Event, LMessage::Param a, LMessage::Param b) { #if LGI_SDL SDL_Event e; e.type = SDL_USEREVENT; e.user.code = Event; e.user.data1 = Wnd; e.user.data2 = a || b ? new LMessage::EventParams(a, b) : NULL; /* printf("LPostEvent Wnd=%p, Event=%i, a/b: %i/%i\n", Wnd, Event, (int)a, (int)b); */ return SDL_PushEvent(&e) == 0; #elif WINNATIVE return PostMessage(Wnd, Event, a, b) != 0; #elif defined(__GTK_H__) LAssert(Wnd); LViewI *View = (LViewI*) g_object_get_data(GtkCast(Wnd, g_object, GObject), "LViewI"); if (View) { LMessage m(0); m.Set(Event, a, b); return m.Send(View); } else printf("%s:%i - Error: LPostEvent can't cast OsView to LViewI\n", _FL); #elif defined(MAC) && !LGI_COCOA #if 0 int64 Now = LCurrentTime(); static int64 Last = 0; static int Count = 0; Count++; if (Now > Last + 1000) { printf("Sent %i events in the last %ims\n", Count, (int)(Now-Last)); Last = Now; Count = 0; } #endif EventRef Ev; OSStatus e = CreateEvent(NULL, kEventClassUser, kEventUser, 0, // EventTime kEventAttributeNone, &Ev); if (e) { printf("%s:%i - CreateEvent failed with %i\n", _FL, (int)e); } else { EventTargetRef t = GetControlEventTarget(Wnd); e = SetEventParameter(Ev, kEventParamLgiEvent, typeUInt32, sizeof(Event), &Event); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiA, typeUInt32, sizeof(a), &a); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = SetEventParameter(Ev, kEventParamLgiB, typeUInt32, sizeof(b), &b); if (e) printf("%s:%i - error %i\n", _FL, (int)e); bool Status = false; EventQueueRef q = GetMainEventQueue(); e = SetEventParameter(Ev, kEventParamPostTarget, typeEventTargetRef, sizeof(t), &t); if (e) printf("%s:%i - error %i\n", _FL, (int)e); e = PostEventToQueue(q, Ev, kEventPriorityStandard); if (e) printf("%s:%i - error %i\n", _FL, (int)e); else Status = true; // printf("PostEventToQueue %i,%i,%i -> %p\n", Event, a, b, q); ReleaseEvent(Ev); return Status; } #else LAssert(!"Not impl."); #endif return false; } void LExitApp() { exit(0); } ////////////////////////////////////////////////////////////////////////// #ifdef WIN32 bool RegisterActiveXControl(char *Dll) { LLibrary Lib(Dll); if (Lib.IsLoaded()) { #ifdef _MSC_VER typedef HRESULT (STDAPICALLTYPE *p_DllRegisterServer)(void); p_DllRegisterServer DllRegisterServer = (p_DllRegisterServer)Lib.GetAddress("DllRegisterServer"); if (DllRegisterServer) { return DllRegisterServer() == S_OK; } #else LAssert(!"Not impl."); #endif } return false; } #endif ////////////////////////////////////////////////////////////////////////// #ifdef WINDOWS #include #pragma comment(lib, "netapi32.lib") #endif /// \brief Returns the operating system that Lgi is running on. /// \sa Returns one of the defines starting with LGI_OS_UNKNOWN in LgiDefs.h int LGetOs ( /// Returns the version of the OS or NULL if you don't care LArray *Ver ) { #if defined(WIN32) || defined(WIN64) static int Os = LGI_OS_UNKNOWN; static int Version = 0, Revision = 0; if (Os == LGI_OS_UNKNOWN) { #if defined(WIN64) BOOL IsWow64 = TRUE; #elif defined(WIN32) BOOL IsWow64 = FALSE; IsWow64Process(GetCurrentProcess(), &IsWow64); #endif SERVER_INFO_101 *v = NULL; auto r = NetServerGetInfo(NULL, 101, (LPBYTE*)&v); if (r == NERR_Success) { Version = v->sv101_version_major; Revision = v->sv101_version_minor; Os = (v->sv101_version_major >= 6) ? #ifdef WIN32 (IsWow64 ? LGI_OS_WIN64 : LGI_OS_WIN32) #else LGI_OS_WIN64 #endif : LGI_OS_WIN9X; NetApiBufferFree(v); } else LAssert(0); } if (Ver) { Ver->Add(Version); Ver->Add(Revision); } return Os; #elif defined LINUX if (Ver) { utsname Buf; if (!uname(&Buf)) { auto t = LString(Buf.release).SplitDelimit("."); for (int i=0; iAdd(atoi(t[i])); } } } return LGI_OS_LINUX; #elif defined MAC #if !defined(__GTK_H__) if (Ver) { NSOperatingSystemVersion v = [[NSProcessInfo processInfo] operatingSystemVersion]; Ver->Add((int)v.majorVersion); Ver->Add((int)v.minorVersion); Ver->Add((int)v.patchVersion); } #endif return LGI_OS_MAC_OS_X; #else return LGI_OS_UNKNOWN; #endif } const char *LGetOsName() { const char *Str[LGI_OS_MAX] = { "Unknown", "Win9x", "Win32", "Win64", "Haiku", "Linux", "MacOSX", }; return Str[LGetOs()]; } #ifdef WIN32 #define RecursiveFileSearch_Wildcard "*.*" #else // unix'ish OS #define RecursiveFileSearch_Wildcard "*" #endif bool LRecursiveFileSearch(const char *Root, LArray *Ext, LArray *Files, uint64 *Size, uint64 *Count, std::function Callback, LCancel *Cancel) { // validate args if (!Root) return false; // get directory enumerator LDirectory Dir; bool Status = true; // enumerate the directory contents for (auto Found = Dir.First(Root); Found && (!Cancel || !Cancel->IsCancelled()); Found = Dir.Next()) { char Name[300]; if (!Dir.Path(Name, sizeof(Name))) continue; if (Callback && !Callback(Name, &Dir)) continue; if (Dir.IsDir()) { // dir LRecursiveFileSearch( Name, Ext, Files, Size, Count, Callback, Cancel); } else { // process file bool Match = true; // if no Ext's then default to match if (Ext) { for (int i=0; iLength(); i++) { const char *e = (*Ext)[i]; char *RawFile = strrchr(Name, DIR_CHAR); if (RawFile && (Match = MatchStr(e, RawFile+1))) { break; } } } if (Match) { // file matched... process: if (Files) Files->Add(NewStr(Name)); if (Size) *Size += Dir.GetSize(); if (Count) (*Count)++; Status = true; } } } return Status; } #define LGI_TRACE_TO_FILE // #include #ifndef WIN32 #define _vsnprintf vsnprintf #endif static LStreamI *_LgiTraceStream = NULL; void LgiTraceSetStream(LStreamI *stream) { _LgiTraceStream = stream; } bool LgiTraceGetFilePath(char *LogPath, int BufLen) { if (!LogPath) return false; auto Exe = LGetExeFile(); if (Exe) { #ifdef MAC char *Dir = strrchr(Exe, DIR_CHAR); if (Dir) { char Part[256]; strcpy_s(Part, sizeof(Part), Dir+1); LMakePath(LogPath, BufLen, "~/Library/Logs", Dir+1); strcat_s(LogPath, BufLen, ".txt"); } else #endif { char *Dot = strrchr(Exe, '.'); if (Dot && !strchr(Dot, DIR_CHAR)) sprintf_s(LogPath, BufLen, "%.*s.txt", (int)(Dot - Exe.Get()), Exe.Get()); else sprintf_s(LogPath, BufLen, "%s.txt", Exe.Get()); } LFile f; if (f.Open(LogPath, O_WRITE)) { f.Close(); } else { char Leaf[64]; char *Dir = strrchr(LogPath, DIR_CHAR); if (Dir) { strcpy_s(Leaf, sizeof(Leaf), Dir + 1); LGetSystemPath(LSP_APP_ROOT, LogPath, BufLen); if (!LDirExists(LogPath)) FileDev->CreateFolder(LogPath); LMakePath(LogPath, BufLen, LogPath, Leaf); } else goto OnError; } #if 0 LFile tmp; if (tmp.Open("c:\\temp\\log.txt", O_WRITE)) { tmp.SetSize(0); tmp.Write(LogPath, strlen(LogPath)); } #endif } else { // Well what to do now? I give up OnError: strcpy_s(LogPath, BufLen, "trace.txt"); return false; } return true; } void LgiTrace(const char *Msg, ...) { #if defined _INC_MALLOC && WINNATIVE if (_heapchk() != _HEAPOK) return; #endif if (!Msg) return; #ifdef WIN32 static LMutex Sem("LgiTrace"); Sem.Lock(_FL, true); #endif char Buffer[2049] = ""; #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream && LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); #endif va_list Arg; va_start(Arg, Msg); #ifdef LGI_TRACE_TO_FILE int Ch = #endif vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); #ifdef LGI_TRACE_TO_FILE LStreamI *Output = NULL; if (_LgiTraceStream) Output = _LgiTraceStream; else { if (!f.IsOpen() && f.Open(LogPath, O_WRITE)) f.Seek(0, SEEK_END); Output = &f; } if (Output && Ch > 0) { Output->ChangeThread(); Output->Write(Buffer, Ch); } if (!_LgiTraceStream) { #ifdef WINDOWS // Windows can take AGES to close a file when there is anti-virus on, like 100ms. // We can't afford to wait here so just keep the file open but flush the // buffers if we can. FlushFileBuffers(f.Handle()); #else f.Close(); #endif } #endif #if defined WIN32 OutputDebugStringA(Buffer); Sem.Unlock(); #else printf("%s", Buffer); #endif } #ifndef LGI_STATIC #define STACK_SIZE 12 void LStackTrace(const char *Msg, ...) { #ifndef HAIKU LSymLookup::Addr Stack[STACK_SIZE]; ZeroObj(Stack); LSymLookup *Lu = LAppInst ? LAppInst->GetSymLookup() : NULL; if (!Lu) { printf("%s:%i - Failed to get sym lookup object.\n", _FL); return; } int Frames = Lu ? Lu->BackTrace(0, 0, Stack, STACK_SIZE) : 0; if (Msg) { #ifdef LGI_TRACE_TO_FILE static LFile f; static char LogPath[MAX_PATH_LEN] = ""; if (!_LgiTraceStream) { if (LogPath[0] == 0) LgiTraceGetFilePath(LogPath, sizeof(LogPath)); f.Open(LogPath, O_WRITE); } #endif va_list Arg; va_start(Arg, Msg); char Buffer[2049] = ""; int Len = vsnprintf(Buffer, sizeof(Buffer)-1, Msg, Arg); va_end(Arg); Lu->Lookup(Buffer+Len, sizeof(Buffer)-Len-1, Stack, Frames); #ifdef LGI_TRACE_TO_FILE if (f.IsOpen()) { f.Seek(0, SEEK_END); f.Write(Buffer, (int)strlen(Buffer)); f.Close(); } #endif #if defined WIN32 OutputDebugStringA(Buffer); #else printf("Trace: %s", Buffer); #endif } #endif } #endif bool LTrimDir(char *Path) { if (!Path) return false; char *p = strrchr(Path, DIR_CHAR); if (!p) return false; if (p[1] == 0) // Trailing DIR_CHAR doesn't count... do it again. { *p = 0; p = strrchr(Path, DIR_CHAR); if (!p) return false; } *p = 0; return true; } LString LMakeRelativePath(const char *Base, const char *Path) { LStringPipe Status; if (Base && Path) { #ifdef WIN32 bool SameNs = strnicmp(Base, Path, 3) == 0; #else bool SameNs = true; #endif if (SameNs) { auto b = LString(Base + 1).SplitDelimit(":\\/"); auto p = LString(Path + 1).SplitDelimit(":\\/"); int Same = 0; while (Same < b.Length() && Same < p.Length() && stricmp(b[Same], p[Same]) == 0) { Same++; } for (int i = Same; i= b.Length()) { Status.Print(".%s", DIR_STR); } for (int n = Same; n Same) { Status.Push(DIR_STR); } Status.Push(p[n]); } } } return Status.NewGStr(); } bool LIsRelativePath(const char *Path) { if (!Path) return false; if (*Path == '.') return true; #ifdef WIN32 if (IsAlpha(Path[0]) && Path[1] == ':') // Drive letter path return false; #endif if (*Path == DIR_CHAR) return false; if (strstr(Path, "://")) // Protocol def return false; return true; // Correct or not??? } bool LMakePath(char *Str, int StrSize, const char *Path, const char *File) { if (!Str || StrSize <= 0 || !Path || !File) { printf("%s:%i - Invalid LMakePath(%p,%i,%s,%s) param\n", _FL, Str, StrSize, Path, File); return false; } if (StrSize <= 4) { printf("%s:%i - LgiMakeFile buf size=%i?\n", _FL, StrSize); } if (Str && Path && File) { char Dir[] = { '/', '\\', 0 }; if (Path[0] == '~') { auto Parts = LString(Path).SplitDelimit(Dir, 2); char *s = Str, *e = Str + StrSize; for (auto p: Parts) { if (p.Equals("~")) { LGetSystemPath(LSP_HOME, s, e - s); s += strlen(s); } else s += sprintf_s(s, e - s, "%s%s", DIR_STR, p.Get()); } } else if (Str != Path) { strcpy_s(Str, StrSize, Path); } #define EndStr() Str[strlen(Str)-1] #define EndDir() if (!strchr(Dir, EndStr())) strcat(Str, DIR_STR); size_t Len = strlen(Str); char *End = Str + (Len ? Len - 1 : 0); if (strchr(Dir, *End) && End > Str) { *End = 0; } auto T = LString(File).SplitDelimit(Dir); for (int i=0; i= StrSize - 1) return false; Str[Len++] = DIR_CHAR; Str[Len] = 0; } size_t SegLen = strlen(T[i]); if (Len + SegLen + 1 > StrSize) { return false; } strcpy_s(Str + Len, StrSize - Len, T[i]); } } } return true; } bool LgiGetTempPath(char *Dst, int DstSize) { return LGetSystemPath(LSP_TEMP, Dst, DstSize); } bool LGetSystemPath(LSystemPath Which, char *Dst, ssize_t DstSize) { if (!Dst || DstSize <= 0) return false; LFile::Path p; LString s = p.GetSystem(Which, 0); if (!s) return false; strcpy_s(Dst, DstSize, s); return true; } LString LGetSystemPath(LSystemPath Which, int WordSize) { LFile::Path p; return p.GetSystem(Which, WordSize); } LFile::Path::State LFile::Path::Exists() { if (Length() == 0) return TypeNone; #ifdef WINDOWS struct _stat64 s; int r = _stat64(GetFull(), &s); if (r) return TypeNone; if (s.st_mode & _S_IFDIR) return TypeFolder; if (s.st_mode & _S_IFREG) return TypeFile; #else struct stat s; int r = stat(GetFull(), &s); if (r) return TypeNone; if (S_ISDIR(s.st_mode)) return TypeFolder; if (S_ISREG(s.st_mode)) return TypeFile; #endif return TypeNone; } LString LFile::Path::PrintAll() { LStringPipe p; #define _(name) \ p.Print(#name ": '%s'\n", GetSystem(name).Get()); _(LSP_OS) _(LSP_OS_LIB) _(LSP_TEMP) _(LSP_COMMON_APP_DATA) _(LSP_USER_APP_DATA) _(LSP_LOCAL_APP_DATA) _(LSP_DESKTOP) _(LSP_HOME) _(LSP_USER_APPS) _(LSP_EXE) _(LSP_TRASH) _(LSP_APP_INSTALL) _(LSP_APP_ROOT) _(LSP_USER_DOCUMENTS) _(LSP_USER_MUSIC) _(LSP_USER_VIDEO) _(LSP_USER_DOWNLOADS) _(LSP_USER_LINKS) _(LSP_USER_PICTURES) #undef _ #if LGI_COCOA int Domains[] = {NSUserDomainMask, NSSystemDomainMask}; const char *DomainName[] = {"User", "System"}; for (int i=0; i 0) \ { \ LString s = [paths objectAtIndex:0]; \ p.Print("%s." #name ": '%s'\n", DomainName[i], s.Get()); \ } \ else p.Print("%s." #name ": null\n", DomainName[i]); \ } \ } _(NSApplicationDirectory) _(NSDemoApplicationDirectory) _(NSDeveloperApplicationDirectory) _(NSAdminApplicationDirectory) _(NSLibraryDirectory) _(NSDeveloperDirectory) _(NSUserDirectory) _(NSDocumentationDirectory) _(NSDocumentDirectory) _(NSCoreServiceDirectory) _(NSAutosavedInformationDirectory) _(NSDesktopDirectory) _(NSCachesDirectory) _(NSApplicationSupportDirectory) _(NSDownloadsDirectory) _(NSInputMethodsDirectory) _(NSMoviesDirectory) _(NSMusicDirectory) _(NSPicturesDirectory) _(NSPrinterDescriptionDirectory) _(NSSharedPublicDirectory) _(NSPreferencePanesDirectory) _(NSApplicationScriptsDirectory) _(NSItemReplacementDirectory) _(NSAllApplicationsDirectory) _(NSAllLibrariesDirectory) _(NSTrashDirectory) #undef _ } #endif return p.NewGStr(); } LString LFile::Path::GetSystem(LSystemPath Which, int WordSize) { LString Path; #if defined(WIN32) #ifndef CSIDL_PROFILE #define CSIDL_PROFILE 0x0028 #endif #if !defined(CSIDL_MYDOCUMENTS) #define CSIDL_MYDOCUMENTS 0x0005 #endif #if !defined(CSIDL_MYMUSIC) #define CSIDL_MYMUSIC 0x000d #endif #if !defined(CSIDL_MYVIDEO) #define CSIDL_MYVIDEO 0x000e #endif #if !defined(CSIDL_LOCAL_APPDATA) #define CSIDL_LOCAL_APPDATA 0x001c #endif #if !defined(CSIDL_COMMON_APPDATA) #define CSIDL_COMMON_APPDATA 0x0023 #endif #if !defined(CSIDL_APPDATA) #define CSIDL_APPDATA 0x001a #endif #endif /* #if defined(LINUX) && !defined(LGI_SDL) // Ask our window manager add-on if it knows the path LLibrary *WmLib = LAppInst ? LAppInst->GetWindowManagerLib() : NULL; if (WmLib) { Proc_LgiWmGetPath WmGetPath = (Proc_LgiWmGetPath) WmLib->GetAddress("LgiWmGetPath"); char Buf[MAX_PATH_LEN]; if (WmGetPath && WmGetPath(Which, Buf, sizeof(Buf))) { Path = Buf; return Path; } } #endif */ switch (Which) { default: break; case LSP_USER_DOWNLOADS: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOWNLOAD); Path = p; #elif defined(WIN32) && defined(_MSC_VER) // OMG!!!! Really? #ifndef REFKNOWNFOLDERID typedef GUID KNOWNFOLDERID; #define REFKNOWNFOLDERID const KNOWNFOLDERID & GUID FOLDERID_Downloads = {0x374DE290,0x123F,0x4565,{0x91,0x64,0x39,0xC4,0x92,0x5E,0x46,0x7B}}; #endif LLibrary Shell("Shell32.dll"); typedef HRESULT (STDAPICALLTYPE *pSHGetKnownFolderPath)(REFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); pSHGetKnownFolderPath SHGetKnownFolderPath = (pSHGetKnownFolderPath)Shell.GetAddress("SHGetKnownFolderPath"); if (SHGetKnownFolderPath) { PWSTR ptr = NULL; HRESULT r = SHGetKnownFolderPath(FOLDERID_Downloads, 0, NULL, &ptr); if (SUCCEEDED(r)) { LAutoString u8(WideToUtf8(ptr)); if (u8) Path = u8; CoTaskMemFree(ptr); } } if (!Path.Get()) { LRegKey k(false, "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"); char *p = k.GetStr("{374DE290-123F-4565-9164-39C4925E467B}"); if (LDirExists(p)) Path = p; } if (!Path.Get()) { LString MyDoc = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); if (MyDoc) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), MyDoc, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } if (!Path.Get()) { LString Profile = WinGetSpecialFolderPath(CSIDL_PROFILE); if (Profile) { char Buf[MAX_PATH_LEN]; LMakePath(Buf, sizeof(Buf), Profile, "Downloads"); if (LDirExists(Buf)) Path = Buf; } } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDownloadsDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #elif defined(HAIKU) #else LAssert(!"Not implemented"); #endif break; } case LSP_USER_LINKS: { LString Home = LGetSystemPath(LSP_HOME); #if defined(WIN32) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, "Links")) Path = p; #elif defined(LINUX) char p[MAX_PATH_LEN]; if (LMakePath(p, sizeof(p), Home, ".config/gtk-3.0")) Path = p; #endif break; } case LSP_USER_PICTURES: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); Path = p; #elif defined(WIN32) Path = WinGetSpecialFolderPath(CSIDL_MYPICTURES); if (Path) return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSPicturesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif // Default to ~/Pictures char hm[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(hm, sizeof(hm), Home, "Pictures")) Path = hm; break; } case LSP_USER_DOCUMENTS: { #if defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DOCUMENTS); Path = p; #elif defined(WIN32) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_MYDOCUMENTS); if (Path) return Path; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif // Default to ~/Documents char hm[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(hm, sizeof(hm), Home, "Documents")) Path = hm; break; } case LSP_USER_MUSIC: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYMUSIC); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_MUSIC); Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMusicDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMusicDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif if (!Path) { // Default to ~/Music char p[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(p, sizeof(p), Home, "Music")) Path = p; } break; } case LSP_USER_VIDEO: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_MYVIDEO); #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_VIDEOS); Path = p; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kMovieDocumentsFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString a = FSRefPath(Ref); if (a) Path = a.Get(); } #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSMoviesDirectory, NSUserDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #endif if (!Path) { // Default to ~/Video char p[MAX_PATH_LEN]; LString Home = LGetSystemPath(LSP_HOME); if (LMakePath(p, sizeof(p), Home, "Video")) Path = p; } break; } case LSP_USER_APPS: { #if defined WIN32 int Id = #ifdef WIN64 CSIDL_PROGRAM_FILES #else CSIDL_PROGRAM_FILESX86 #endif ; if (WordSize == 32) Id = CSIDL_PROGRAM_FILESX86; else if (WordSize == 64) Id = CSIDL_PROGRAM_FILES; Path = WinGetSpecialFolderPath(Id); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_APPS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationDirectory, NSSystemDomainMask, YES); if (paths) { if ([paths count]) Path = [paths objectAtIndex:0]; } #elif defined MAC Path = "/Applications"; #elif defined LINUX Path = "/usr/bin"; #else LAssert(!"Impl me."); #endif break; } case LSP_APP_INSTALL: { Path = LGetSystemPath(LSP_EXE); if (Path) { size_t Last = Path.RFind(DIR_STR); if (Last > 0) { LString s = Path(Last, -1); if ( stristr(s, #ifdef _DEBUG "Debug" #else "Release" #endif ) ) Path = Path(0, Last); } } break; } case LSP_APP_ROOT: { #ifndef LGI_STATIC const char *Name = NULL; // Try and get the configured app name: if (LAppInst) Name = LAppInst->LBase::Name(); if (!Name) { // Use the exe name? LString Exe = LGetExeFile(); char *l = LGetLeaf(Exe); if (l) { #ifdef WIN32 char *d = strrchr(l, '.'); *d = NULL; #endif Name = l; // printf("%s:%i - name '%s'\n", _FL, Name); } } if (!Name) { LAssert(0); break; } #if defined MAC #if LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); if (paths) Path = [[paths objectAtIndex:0] UTF8String]; #elif LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) { printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); LAssert(0); } else { LAutoString Base = FSRefPath(Ref); Path = Base.Get(); } #else struct passwd *pw = getpwuid(getuid()); if (!pw) return false; Path.Printf("%s/Library", pw->pw_dir); #endif #elif defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LINUX char Dot[128]; snprintf(Dot, sizeof(Dot), ".%s", Name); Name = Dot; struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; else LAssert(0); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY , volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(0); #endif if (Path) { Path += DIR_STR; Path += Name; } #endif break; } case LSP_OS: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetWindowsDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kSystemFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; LTrimDir(Path); } #elif defined LINUX Path = "/boot"; // it'll do for now... #endif break; } case LSP_OS_LIB: { #if defined WIN32 char16 p[MAX_PATH_LEN]; if (GetSystemDirectoryW(p, CountOf(p)) > 0) Path = p; #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_SYSTEM_LIB_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined MAC Path = "/Library"; #elif defined LINUX Path = "/lib"; // it'll do for now... #endif break; } case LSP_TEMP: { #if defined WIN32 char16 t[MAX_PATH_LEN]; if (GetTempPathW(CountOf(t), t) > 0) { LAutoString utf(WideToUtf8(t)); if (utf) Path = utf; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTemporaryFolderType, kCreateFolder, &Ref); if (e) LgiTrace("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) { Path = u.Get(); } else LgiTrace("%s:%i - FSRefPath failed.\n", _FL); } #elif defined LGI_COCOA NSString *tempDir = NSTemporaryDirectory(); if (tempDir) Path = tempDir; else LAssert(!"No tmp folder?"); #elif defined LINUX Path = "/tmp"; // it'll do for now... #else LAssert(!"Impl me."); #endif break; } case LSP_COMMON_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_COMMON_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnSystemDisk, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSSystemDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #else LAssert(!"Impl me."); #endif break; } case LSP_USER_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_APPDATA); #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kDomainLibraryFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { auto u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LINUX Path = "/usr"; #elif defined HAIKU dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_SETTINGS_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #else LAssert(!"Impl me."); #endif break; } case LSP_LOCAL_APP_DATA: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_LOCAL_APPDATA); #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #else LAssert(!"Impl me."); #endif break; } case LSP_DESKTOP: { #if defined(WINDOWS) && defined(_MSC_VER) Path = WinGetSpecialFolderPath(CSIDL_DESKTOPDIRECTORY); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_DESKTOP_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined(__GTK_H__) auto p = Gtk::g_get_user_special_dir(Gtk::G_USER_DIRECTORY_DESKTOP); Path = p; #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDesktopDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kOnAppropriateDisk, kDesktopFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", __FILE__, __LINE__, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) { #ifdef LINUX WindowManager wm = LGetWindowManager(); if (wm == WM_Gnome) { Path.Printf("%s/.gnome-desktop", pw->pw_dir); } #endif if (!LDirExists(Path)) { Path.Printf("%s/Desktop", pw->pw_dir); } } #else #error "Impl me." #endif break; } case LSP_HOME: { #if defined WIN32 Path = WinGetSpecialFolderPath(CSIDL_PROFILE); #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_USER_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_COCOA NSString *home = NSHomeDirectory(); if (home) Path = home; else LAssert(!"No home path?"); #elif defined POSIX struct passwd *pw = getpwuid(getuid()); if (pw) Path = pw->pw_dir; #else #error "Impl me." #endif break; } case LSP_EXE: { Path = LGetExeFile(); if (!Path) break; auto p = Path.RFind(DIR_STR); if (p > 0) Path.Length(p); break; } case LSP_TRASH: { #if defined LINUX switch (LGetWindowManager()) { case WM_Kde: { static char KdeTrash[256] = ""; if (!ValidStr(KdeTrash)) { // Ask KDE where the current trash is... LStringPipe o; LSubProcess p("kde-config", "--userpath trash"); if (p.Start()) { p.Communicate(&o); char *s = o.NewStr(); if (s) { // Store it.. strcpy_s(KdeTrash, sizeof(KdeTrash), s); DeleteArray(s); // Clear out any crap at the end... char *e = KdeTrash + strlen(KdeTrash) - 1; while (e > KdeTrash && strchr(" \r\n\t/", *e)) { *e-- = 0; } } else { printf("%s:%i - No output from 'kde-config'.\n", _FL); } } else { printf("%s:%i - Run 'kde-config' failed.\n", _FL); } } if (ValidStr(KdeTrash)) Path = KdeTrash; break; } default: { LString Home = LGetSystemPath(LSP_HOME); if (!Home) { LgiTrace("%s:%i - Can't get LSP_HOME.\n", _FL); break; } char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), Home, ".local/share/Trash/files") || !LDirExists(p)) { LgiTrace("%s:%i - '%s' doesn't exist.\n", _FL, p); break; } Path = p; break; } } #elif defined(HAIKU) dev_t volume = dev_for_path("/boot"); char path[MAX_PATH_LEN] = ""; if (find_directory(B_TRASH_DIRECTORY, volume, true, path, sizeof(path)) == B_OK) Path = path; #elif defined LGI_CARBON FSRef Ref; OSErr e = FSFindFolder(kUserDomain, kTrashFolderType, kDontCreateFolder, &Ref); if (e) printf("%s:%i - FSFindFolder failed e=%i\n", _FL, e); else { LAutoString u = FSRefPath(Ref); if (u) Path = u.Get(); } #elif defined LGI_COCOA NSArray *paths = NSSearchPathForDirectoriesInDomains( NSTrashDirectory, NSUserDomainMask, YES); if (paths) { Path = [paths objectAtIndex:0]; } #elif defined(WIN32) LAssert(0); #endif break; } } return Path; } LString LGetExeFile() { #if defined WIN32 char16 Exe[MAX_PATH_LEN]; if (GetModuleFileNameW(NULL, Exe, CountOf(Exe)) > 0) { LString e = Exe; if (e) { return e; } else { LgiMsg(0, "LgiFromNativeCp returned 0, ANSI CodePage=%i (%s)", "LgiGetExeFile Error", MB_OK, GetACP(), LAnsiToLgiCp()); return LString(); } } LString m; m.Printf("GetModuleFileName failed err: %08.8X", GetLastError()); MessageBoxA(0, m, "LgiGetExeFile Error", MB_OK); LExitApp(); #elif defined HAIKU // Copy the string so as to not allow callers to change it return LgiArgsAppPath.Get(); #elif defined LINUX static char ExePathCache[MAX_PATH_LEN] = ""; bool Status = false; // this is _REALLY_ lame way to do it... but hey there aren't any // other better ways to get the full path of the running executable if (!ExePathCache[0]) { // First try the self method int Len = readlink("/proc/self/exe", ExePathCache, sizeof(ExePathCache)); Status = LFileExists(ExePathCache); // printf("readlink=%i Status=%i Exe='%s'\n", Len, Status, ExePathCache); if (!Status) { ExePathCache[0] = 0; // Next try the map file method char ProcFile[256]; sprintf_s(ProcFile, sizeof(ProcFile), "/proc/%i/maps", getpid()); int fd = open(ProcFile, O_RDONLY); if (fd >= 0) { int Len = 16 << 10; // is this enough? // no better way of determining the length of proc info? char *Buf = new char[Len+1]; if (Buf) { int r = read(fd, Buf, Len); Buf[r] = 0; char *s = strchr(Buf, '/'); if (s) { char *e = strchr(s, '\n'); if (e) { *e = 0; strcpy_s(ExePathCache, sizeof(ExePathCache), s); Status = true; } } DeleteArray(Buf); } close(fd); } else { // Non proc system (like cygwin for example) // char Cmd[256]; // sprintf_s(Cmd, sizeof(Cmd), "ps | grep \"%i\"", getpid()); LStringPipe Ps; LSubProcess p("ps"); if (p.Start()) { p.Communicate(&Ps); char *PsOutput = Ps.NewStr(); if (PsOutput) { auto n = LString(PsOutput).SplitDelimit("\r\n"); for (int i=0; !Status && i 7) { int LinePid = 0; for (int i=0; iReset(NewStr(Path)); return; } #ifdef WIN32 if (PathLen < sizeof(Path) - 4) { strcat(Path, ".lnk"); if (LResolveShortcut(Path, Path, sizeof(Path))) { if (GStr) *GStr = Path; else if (AStr) AStr->Reset(NewStr(Path)); return; } } #endif } // General search fall back... LArray Ext; LArray Files; Ext.Add((char*)Name); if (LRecursiveFileSearch(Exe, &Ext, &Files) && Files.Length()) { if (GStr) *GStr = Files[0]; else { AStr->Reset(Files[0]); Files.DeleteAt(0); } } Files.DeleteArrays(); } LString LFindFile(const char *Name) { LString s; _LFindFile(Name, &s, NULL); return s; } #if defined WIN32 static LARGE_INTEGER Freq = {0}; static bool CurTimeInit = false; #endif uint64_t LCurrentTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000 / Freq.QuadPart; } // Now what?? Give up and go back to tick count I guess. Freq.QuadPart = 0; } // Fall back for systems without a performance counter. return GetTickCount(); #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); uint64 i = ((uint64)t.hi << 32) | t.lo; return i / 1000; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); #endif } uint64_t LgiMicroTime() { #if defined WIN32 if (!CurTimeInit) { CurTimeInit = true; if (!QueryPerformanceFrequency(&Freq)) Freq.QuadPart = 0; } if (Freq.QuadPart) { // Return performance counter in ms LARGE_INTEGER i; if (QueryPerformanceCounter(&i)) { return i.QuadPart * 1000000 / Freq.QuadPart; } } return 0; #elif defined LGI_CARBON UnsignedWide t; Microseconds(&t); return ((uint64)t.hi << 32) | t.lo; #else timeval tv; gettimeofday(&tv, 0); return (tv.tv_sec * 1000000) + tv.tv_usec; #endif } int LIsReleaseBuild() { #ifdef _DEBUG return 0; #else return 1; #endif } bool LIsVolumeRoot(const char *Path) { if (!Path) return false; #ifdef WIN32 if ( IsAlpha(Path[0]) && Path[1] == ':' && ( (Path[2] == 0) || (Path[2] == '\\' && Path[3] == 0) ) ) { return true; } #else auto t = LString(Path).SplitDelimit(DIR_STR); if (t.Length() == 0) return true; #ifdef MAC if (!stricmp(t[0], "Volumes") && t.Length() == 2) return true; #else if (!stricmp(t[0], "mnt") && t.Length() == 2) return true; #endif #endif return false; } LString LFormatSize(int64_t Size) { char Buf[64]; LFormatSize(Buf, sizeof(Buf), Size); return Buf; } void LFormatSize(char *Str, int SLen, int64_t Size) { - uint64 K = 1024; - uint64 M = K * K; - uint64 G = K * M; - uint64 T = K * G; + int64_t K = 1024; + int64_t M = K * K; + int64_t G = K * M; + int64_t T = K * G; if (Size == 1) { strcpy_s(Str, SLen, "1 byte"); } else if (Size < K) { sprintf_s(Str, SLen, "%u bytes", (int)Size); } else if (Size < 10 * K) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f KiB", d / K); } else if (Size < M) { sprintf_s(Str, SLen, "%u KiB", (int) (Size / K)); } else if (Size < G) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f MiB", d / M); } else if (Size < T) { double d = (double)Size; sprintf_s(Str, SLen, "%.2f GiB", d / G); } else { double d = (double)Size; sprintf_s(Str, SLen, "%.2f TiB", d / T); } } char *LTokStr(const char *&s) { char *Status = 0; if (s && *s) { // Skip whitespace static char Delim[] = ", \t\r\n"; while (*s && strchr(Delim, *s)) s++; if (*s) { if (strchr("\'\"", *s)) { char Delim = *s++; const char *e = strchr(s, Delim); if (!e) { // error, no end delimiter e = s; while (*e) e++; } Status = NewStr(s, e - s); s = *e ? e + 1 : e; } else { const char *e = s; while (*e && !strchr(Delim, *e)) e++; Status = NewStr(s, e - s); s = e; } } } return Status; } LString LGetEnv(const char *Var) { #ifdef _MSC_VER char *s = NULL; size_t sz; errno_t err = _dupenv_s(&s, &sz, Var); if (err) return LString(); LString ret(s); free(s); return ret; #else return getenv("PATH"); #endif } LString::Array LGetPath() { LString::Array Paths; #ifdef MAC // OMG, WHY?! Seriously? // // The GUI application path is NOT the same as what is configured for the terminal. // At least in 10.12. And I don't know how to make them the same. This works around // that for the time being. #if 1 LFile EctPaths("/etc/paths", O_READ); Paths = EctPaths.Read().Split("\n"); #else LFile::Path Home(LSP_HOME); Home += ".profile"; if (!Home.Exists()) { Home--; Home += ".zprofile"; } auto Profile = LFile(Home, O_READ).Read().Split("\n"); for (auto Ln : Profile) { auto p = Ln.SplitDelimit(" =", 2); if (p.Length() == 3 && p[0].Equals("export") && p[1].Equals("PATH")) { Paths = p[2].Strip("\"").SplitDelimit(LGI_PATH_SEPARATOR); Paths.SetFixedLength(false); for (auto &p : Paths) { if (p.Equals("$PATH")) { auto SysPath = LGetEnv("PATH").SplitDelimit(LGI_PATH_SEPARATOR); for (unsigned i=0; i 0) { Period = p; } } bool DoEvery::DoNow() { int64 Now = LCurrentTime(); if (LastTime + Period < Now) { LastTime = Now; return true; } return false; } ////////////////////////////////////////////////////////////////////// bool LCapabilityClient::NeedsCapability(const char *Name, const char *Param) { for (int i=0; iNeedsCapability(Name, Param); return Targets.Length() > 0; } LCapabilityClient::~LCapabilityClient() { for (int i=0; iClients.Delete(this); } void LCapabilityClient::Register(LCapabilityTarget *t) { if (t && !Targets.HasItem(t)) { Targets.Add(t); t->Clients.Add(this); } } LCapabilityTarget::~LCapabilityTarget() { for (int i=0; iTargets.Delete(this); } ///////////////////////////////////////////////////////////////////// #define BUF_SIZE (4 << 10) #define PROFILE_MICRO 1 LProfile::LProfile(const char *Name, int HideMs) { MinMs = HideMs; Used = 0; Buf = NULL; Add(Name); } LProfile::~LProfile() { Add("End"); uint64 TotalMs = s.Last().Time - s[0].Time; if (MinMs > 0) { if (TotalMs < MinMs #if PROFILE_MICRO * 1000 #endif ) { return; } } uint64 accum = 0; for (int i=0; i BUF_SIZE - 64) { LAssert(0); return; } char *Name = Buf + Used; Used += sprintf_s(Name, BUF_SIZE - Used, "%s:%i", File, Line) + 1; s.Add(Sample( #if PROFILE_MICRO LgiMicroTime(), #else LCurrentTime(), #endif Name)); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// bool LIsValidEmail(LString Email) { // Local part char buf[321]; char *o = buf; char *e = Email; if (!Email || *e == '.') return false; #define OutputChar() \ if (o - buf >= sizeof(buf) - 1) \ return false; \ *o++ = *e++ // Local part while (*e) { if (strchr("!#$%&\'*+-/=?^_`.{|}~", *e) || IsAlpha((uchar)*e) || IsDigit((uchar)*e)) { OutputChar(); } else if (*e == '\"') { // Quoted string OutputChar(); bool quote = false; while (*e && !quote) { quote = *e == '\"'; OutputChar(); } } else if (*e == '\\') { // Quoted character e++; if (*e < ' ' || *e >= 0x7f) return false; OutputChar(); } else if (*e == '@') { break; } else { // Illegal character return false; } } // Process the '@' if (*e != '@' || o - buf > 64) return false; OutputChar(); // Domain part... if (*e == '[') { // IP addr OutputChar(); // Initial char must by a number if (!IsDigit(*e)) return false; // Check the rest... char *Start = e; while (*e) { if (IsDigit(*e) || *e == '.') { OutputChar(); } else { return false; } } // Not a valid IP if (e - Start > 15) return false; if (*e != ']') return false; OutputChar(); } else { // Hostname, check initial char if (!IsAlpha(*e) && !IsDigit(*e)) return false; // Check the rest. while (*e) { if (IsAlpha(*e) || IsDigit(*e) || strchr(".-", *e)) { OutputChar(); } else { return false; } } } // Remove any trailing dot/dash while (strchr(".-", o[-1])) o--; // Output *o = 0; LAssert(o - buf <= sizeof(buf)); if (strcmp(Email, buf)) Email.Set(buf, o - buf); return true; } ////////////////////////////////////////////////////////////////////////// LString LGetAppForProtocol(const char *Protocol) { LString App; if (!Protocol) return App; #ifdef WINDOWS LRegKey k(false, "HKEY_CLASSES_ROOT\\%s\\shell\\open\\command", Protocol); if (k.IsOk()) { const char *p = k.GetStr(); if (p) { LAutoString a(LTokStr(p)); App = a.Get(); } } #elif defined(LINUX) const char *p = NULL; if (stricmp(Protocol, "mailto")) p = "xdg-email"; else p = "xdg-open"; LString Path = LGetEnv("PATH"); LString::Array a = Path.SplitDelimit(LGI_PATH_SEPARATOR); for (auto i : a) { LFile::Path t(i); t += p; if (t.Exists()) { App = t.GetFull(); break; } } #elif defined(__GTK_H__) LAssert(!"What to do?"); #elif defined(MAC) // Get the handler type LString s; s.Printf("%s://domain/path", Protocol); auto str = s.NsStr(); auto type = [NSURL URLWithString:str]; [str release]; auto handlerUrl = [[NSWorkspace sharedWorkspace] URLForApplicationToOpenURL:type]; [type release]; if (handlerUrl) { // Convert to app path s = [handlerUrl absoluteString]; LUri uri(s); if (uri.sProtocol.Equals("file")) App = uri.sPath.RStrip("/"); else LgiTrace("%s:%i - Error: unknown protocol '%s'\n", _FL, uri.sProtocol.Get()); } [handlerUrl release]; #else #warning "Impl me." #endif return App; } diff --git a/src/common/Lgi/Mru.cpp b/src/common/Lgi/Mru.cpp --- a/src/common/Lgi/Mru.cpp +++ b/src/common/Lgi/Mru.cpp @@ -1,472 +1,447 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Mru.h" #include "lgi/common/Variant.h" #include "lgi/common/Menu.h" //////////////////////////////////////////////////////////////////// #define DEBUG_LOG 0 #define M_MRU_BASE (M_USER+0x3500) -struct GMruEntry +struct LMruEntry { LString Display; LString Raw; - - GMruEntry &operator =(const GMruEntry &e) - { - Display = e.Display; - Raw = e.Raw; - return *this; - } }; class LMruPrivate { public: - int Size; - LArray Items; - LSubMenu *Parent; - LFileType *SelectedType; + int Size = 10; + LArray Items; + LSubMenu *Parent = NULL; + LFileType *SelectedType = NULL; LMruPrivate() { - Parent = 0; - Size = 10; - SelectedType = 0; } ~LMruPrivate() { Items.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////// LMru::LMru() { d = new LMruPrivate; } LMru::~LMru() { DeleteObj(d); } bool LMru::SerializeEntry ( /// The displayable version of the reference (this should have any passwords blanked out) LString *Display, /// The form passed to the client software to open/save. (passwords NOT blanked) LString *Raw, /// The form safe to write to disk, if a password is present it must be encrypted. LString *Stored ) { if (Raw && Raw->Get()) { if (Stored) *Stored = *Raw; if (Display) *Display = *Raw; } else if (Stored && Stored->Get()) { if (Display) *Display = *Stored; if (Raw) *Raw = *Stored; } return true; } void LMru::GetFileTypes(LFileSelect *Dlg, bool Write) { Dlg->Type("All Files", LGI_ALL_FILES); } -char *LMru::_GetCurFile() +const char *LMru::_GetCurFile() { if (d->Items.Length()) return d->Items[0]->Raw; return NULL; } LFileType *LMru::GetSelectedType() { return d->SelectedType; } bool LMru::_OpenFile(const char *File, bool ReadOnly) { bool Status = OpenFile(File, ReadOnly); if (Status) - { AddFile(File, true); - } else - { RemoveFile(File); - } return Status; } bool LMru::_SaveFile(const char *FileName) { - bool Status = false; + if (!FileName) + return false; - if (FileName) + char File[MAX_PATH_LEN]; + strcpy_s(File, sizeof(File), FileName); + + LFileType *st; + if (!LFileExists(File) && + (st = GetSelectedType()) && + st->Extension()) { - char File[MAX_PATH_LEN]; - strcpy_s(File, sizeof(File), FileName); - - LFileType *st; - if (!LFileExists(File) && - (st = GetSelectedType()) && - st->Extension()) + char *Cur = LGetExtension(File); + if (!Cur) { - char *Cur = LGetExtension(File); - if (!Cur) + // extract extension + LString::Array a = LString(st->Extension()).Split(LGI_PATH_SEPARATOR); + for (auto e: a) { - // extract extension - LString::Array a = LString(st->Extension()).Split(LGI_PATH_SEPARATOR); - for (auto e: a) + LString::Array p = e.RSplit(".", 1); + if (!p.Last().Equals("*")) { - LString::Array p = e.RSplit(".", 1); - if (!p.Last().Equals("*")) + // bung the extension from the file type if not there + char *Dot = strrchr(File, '.'); + if (Dot) + Dot++; + else { - // bung the extension from the file type if not there - char *Dot = strrchr(File, '.'); - if (Dot) - Dot++; - else - { - Dot = File + strlen(File); - *Dot++ = '.'; - } + Dot = File + strlen(File); + *Dot++ = '.'; + } - strcpy_s(Dot, File+sizeof(File)-Dot, p.Last()); - break; - } + strcpy_s(Dot, File+sizeof(File)-Dot, p.Last()); + break; } } } - - Status = SaveFile(File); + } + + auto Status = SaveFile(File); - if (Status) - { - AddFile(File); - } - else - { - RemoveFile(File); - } - } + if (Status) + AddFile(File); + else + RemoveFile(File); return Status; } void LMru::_Update() { if (d->Items.Length() > d->Size) - { d->Items.Length(d->Size); - } if (d->Parent) { // remove existing items. d->Parent->Empty(); // add current items if (d->Items.Length() > 0) { for (int i=0; iItems.Length(); i++) { - GMruEntry *c = d->Items[i]; + LMruEntry *c = d->Items[i]; d->Parent->AppendItem(c->Display ? c->Display : c->Raw, M_MRU_BASE + i, true); } } else { d->Parent->AppendItem("(none)", -1, false); } } } bool LMru::Set(LSubMenu *parent, int size) { d->Parent = parent; if (size > 0) - { d->Size = size; - } - _Update(); return true; } const char *LMru::AddFile(const char *FileName, bool Update) { #if DEBUG_LOG LgiTrace("%s:%i - AddFile(%s,%i)\n", _FL, FileName, Update); #endif if (!FileName) return NULL; auto Status = FileName; - GMruEntry *c = NULL; + LMruEntry *c = NULL; for (int i=0; iItems.Length(); i++) { - GMruEntry *e = d->Items[i]; + LMruEntry *e = d->Items[i]; #if DEBUG_LOG LgiTrace("[%i] cmp '%s' '%s'\n", i, e->Raw.Get(), FileName); #endif if (!LFileCompare(e->Raw, FileName)) { // exact string being added.. just move to the top // no need to reallocate if (strcmp(e->Raw, FileName) && e->Raw.Length() == strlen(FileName)) { e->Raw = FileName; // This fixes any changes in case... #if DEBUG_LOG LgiTrace("Updating raw case\n"); #endif } else { #if DEBUG_LOG LgiTrace("Moving to the top\n"); #endif } d->Items.DeleteAt(i, true); d->Items.AddAt(0, e); c = e; break; } } if (!c) { - c = new GMruEntry; + c = new LMruEntry; c->Raw = FileName; if (SerializeEntry(&c->Display, &c->Raw, NULL)) { #if DEBUG_LOG LgiTrace("Adding new entry %s %s\n", c->Raw.Get(), c->Display.Get()); #endif d->Items.AddAt(0, c); } else { LAssert(0); } } if (Update) { // update _Update(); } return Status; } void LMru::RemoveFile(const char *FileName, bool Update) { // remove from list if there for (int i=0; iItems.Length(); i++) { - GMruEntry *e = d->Items[i]; + LMruEntry *e = d->Items[i]; if (stricmp(e->Raw, FileName) == 0) { d->Items.DeleteAt(i); DeleteObj(e); break; } } if (Update) { _Update(); } } void LMru::DoFileDlg(LFileSelect &Select, bool Open, std::function OnSelect) { GetFileTypes(&Select, false); Select.ShowReadOnly(Open); auto Cb = [&](auto s, bool ok) { if (ok) { d->SelectedType = s->TypeAt(s->SelectedType()); if (Open) _OpenFile(s->Name(), s->ReadOnly()); else _SaveFile(s->Name()); } if (OnSelect) OnSelect(ok); }; if (Open) Select.Open(Cb); else Select.Save(Cb); } void LMru::OnCommand(int Cmd, std::function OnStatus) { bool Status = false; LViewI *Wnd = d->Parent->GetMenu() ? d->Parent->GetMenu()->WindowHandle() : 0; if (Wnd) { LFileSelect Select; Select.Parent(Wnd); Select.ClearTypes(); d->SelectedType = 0; if (_GetCurFile()) { if (LFileExists(_GetCurFile())) Select.Name(_GetCurFile()); char Path[256]; strcpy_s(Path, sizeof(Path), _GetCurFile()); LTrimDir(Path); if (LDirExists(Path)) Select.InitialDir(Path); } auto Process = [&](bool ok) { if (Cmd >= M_MRU_BASE && Cmd < M_MRU_BASE + d->Items.Length()) { int Index = Cmd - M_MRU_BASE; GMruEntry *c = d->Items[Index]; if (c) { Status &= _OpenFile(c->Raw, false); } } if (OnStatus) OnStatus(ok); }; if (Cmd == IDM_OPEN) DoFileDlg(Select, true, Process); else if (Cmd == IDM_SAVEAS) DoFileDlg(Select, false, Process); } } LMessage::Result LMru::OnEvent(LMessage *Msg) { /* if (d->Parent && MsgCode(Msg) == M_COMMAND) { #ifdef BEOS int32 Cmd = 0; int32 Event = 0; Msg->FindInt32("Cmd", &Cmd); Msg->FindInt32("Event", &Event); #else int Cmd = MsgA(Msg) & 0xffff; #endif OnCommand(Cmd); } */ return false; } bool LMru::Serialize(LDom *Store, const char *Prefix, bool Write) { bool Status = false; LVariant v; if (Store && Prefix) { if (Write) { // add our keys int Idx = 0; char Key[64]; LHashTbl, bool> Saved; for (int i=0; iItems.Length(); i++) { - GMruEntry *e = d->Items[i]; + LMruEntry *e = d->Items[i]; LAssert(e->Raw.Get() != NULL); if (!Saved.Find(e->Raw)) { Saved.Add(e->Raw, true); LString Stored; if (SerializeEntry(NULL, &e->Raw, &Stored)) // Convert Raw -> Stored { sprintf_s(Key, sizeof(Key), "%s.Item%i", Prefix, Idx++); Store->SetValue(Key, v = Stored.Get()); } else LAssert(0); } } sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); Store->SetValue(Key, v = (int)Idx); } else { // clear ourself d->Items.DeleteObjects(); // read our keys in char Key[64]; sprintf_s(Key, sizeof(Key), "%s.Items", Prefix); LVariant i; if (Store->GetValue(Key, i)) { for (int n=0; nGetValue(Key, File)) { LString Stored = File.Str(); LAssert(Stored.Get() != NULL); - LAutoPtr e(new GMruEntry); + LAutoPtr e(new LMruEntry); if (SerializeEntry(&e->Display, &e->Raw, &Stored)) // Convert Stored -> Raw { d->Items.Add(e.Release()); } } } } _Update(); } } return Status; } 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,2345 @@ #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->AddRef(); + 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; } bool 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 != 0; } 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->AddRef(); + 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); } _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); } _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) { if (!MethodName || !ReturnValue) 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()]; 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; } diff --git a/src/common/Lgi/ViewCommon.cpp b/src/common/Lgi/ViewCommon.cpp --- a/src/common/Lgi/ViewCommon.cpp +++ b/src/common/Lgi/ViewCommon.cpp @@ -1,2766 +1,2765 @@ /// \file /// \author Matthew Allen #ifdef LINUX #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Button.h" #include "lgi/common/Css.h" #include "lgi/common/LgiRes.h" #include "lgi/common/EventTargetThread.h" #include "lgi/common/Popup.h" #include "lgi/common/CssTools.h" #include "ViewPriv.h" #if 0 #define DEBUG_CAPTURE(...) printf(__VA_ARGS__) #else #define DEBUG_CAPTURE(...) #endif ////////////////////////////////////////////////////////////////////////////////////// // Helper LPoint lgi_view_offset(LViewI *v, bool Debug = false) { LPoint Offset; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) break; LRect pos = p->GetPos(); LRect cli = p->GetClient(false); if (Debug) { const char *cls = p->GetClass(); LgiTrace(" Off[%s] += %i,%i = %i,%i (%s)\n", cls, pos.x1, pos.y1, Offset.x + pos.x1, Offset.y + pos.y1, cli.GetStr()); } Offset.x += pos.x1 + cli.x1; Offset.y += pos.y1 + cli.y1; } return Offset; } LMouse &lgi_adjust_click(LMouse &Info, LViewI *Wnd, bool Capturing, bool Debug) { static LMouse Temp; Temp = Info; if (Wnd) { if (Debug #if 0 || Capturing #endif ) LgiTrace("AdjustClick Target=%s -> Wnd=%s, Info=%i,%i\n", Info.Target?Info.Target->GetClass():"", Wnd?Wnd->GetClass():"", Info.x, Info.y); if (Temp.Target != Wnd) { if (Temp.Target) { auto *TargetWnd = Temp.Target->GetWindow(); auto *WndWnd = Wnd->GetWindow(); if (TargetWnd == WndWnd) { LPoint TargetOff = lgi_view_offset(Temp.Target, Debug); if (Debug) LgiTrace(" WndOffset:\n"); LPoint WndOffset = lgi_view_offset(Wnd, Debug); Temp.x += TargetOff.x - WndOffset.x; Temp.y += TargetOff.y - WndOffset.y; #if 0 LRect c = Wnd->GetClient(false); Temp.x -= c.x1; Temp.y -= c.y1; if (Debug) LgiTrace(" CliOff -= %i,%i\n", c.x1, c.y1); #endif Temp.Target = Wnd; } } else { LPoint Offset; Temp.Target = Wnd; if (Wnd->WindowVirtualOffset(&Offset)) { LRect c = Wnd->GetClient(false); Temp.x -= Offset.x + c.x1; Temp.y -= Offset.y + c.y1; } } } } LAssert(Temp.Target != NULL); return Temp; } ////////////////////////////////////////////////////////////////////////////////////// // LView class methods LViewI *LView::_Capturing = 0; LViewI *LView::_Over = 0; #if LGI_VIEW_HASH struct ViewTbl : public LMutex { typedef LHashTbl, int> T; private: T Map; public: ViewTbl() : Map(2000), LMutex("ViewTbl") { } T *Lock() { if (!LMutex::Lock(_FL)) return NULL; return ⤅ } } ViewTblInst; bool LView::LockHandler(LViewI *v, LView::LockOp Op) { ViewTbl::T *m = ViewTblInst.Lock(); if (!m) return false; int Ref = m->Find(v); bool Status = false; switch (Op) { case OpCreate: { if (Ref == 0) Status = m->Add(v, 1); else LAssert(!"Already exists?"); break; } case OpDelete: { if (Ref == 1) Status = m->Delete(v); else LAssert(!"Either locked or missing."); break; } case OpExists: { Status = Ref > 0; break; } } ViewTblInst.Unlock(); return Status; } #endif LView::LView(OsView view) { #ifdef _DEBUG _Debug = false; #endif d = new LViewPrivate(this); #ifdef LGI_SDL _View = this; #elif LGI_VIEW_HANDLE && !defined(HAIKU) _View = view; #endif Pos.ZOff(-1, -1); WndFlags = GWF_VISIBLE; #ifndef LGI_VIEW_HASH #error "LGI_VIEW_HASH needs to be defined" #elif LGI_VIEW_HASH LockHandler(this, OpCreate); // printf("Adding %p to hash\n", (LViewI*)this); #endif } LView::~LView() { if (d->SinkHnd >= 0) { LEventSinkMap::Dispatch.RemoveSink(this); d->SinkHnd = -1; } #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif for (unsigned i=0; iOwner == this) pu->Owner = NULL; } _Delete(); // printf("%p::~LView delete %p th=%u\n", this, d, GetCurrentThreadId()); DeleteObj(d); // printf("%p::~LView\n", this); } int LView::AddDispatch() { if (d->SinkHnd < 0) d->SinkHnd = LEventSinkMap::Dispatch.AddSink(this); return d->SinkHnd; } LString LView::CssStyles(const char *Set) { if (Set) { d->Styles = Set; d->CssDirty = true; } return d->Styles; } LString::Array *LView::CssClasses() { return &d->Classes; } LArray LView::IterateViews() { LArray a; for (auto c: Children) a.Add(c); return a; } bool LView::AddView(LViewI *v, int Where) { LAssert(!Children.HasItem(v)); bool Add = Children.Insert(v, Where); if (Add) { LView *gv = v->GetGView(); if (gv && gv->_Window != _Window) { LAssert(!_InLock); gv->_Window = _Window; } v->SetParent(this); v->OnAttach(); OnChildrenChanged(v, true); } return Add; } bool LView::DelView(LViewI *v) { bool Has = Children.HasItem(v); bool b = Children.Delete(v); if (Has) OnChildrenChanged(v, false); Has = Children.HasItem(v); LAssert(!Has); return b; } bool LView::HasView(LViewI *v) { return Children.HasItem(v); } OsWindow LView::WindowHandle() { auto w = GetWindow(); auto h = w ? w->WindowHandle() : NULL; return h; } LWindow *LView::GetWindow() { if (!_Window) { // Walk up parent list and find someone who has a window auto *w = d->GetParent(); for (; w; w = w->d ? w->d->GetParent() : NULL) { if (w->_Window) { LAssert(!_InLock); _Window = w->_Window; break; } } } return dynamic_cast(_Window); } bool LView::Lock(const char *file, int line, int TimeOut) { #ifdef HAIKU if (!d || !d->Hnd) { // printf("%s:%i - no handle %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } if (d->Hnd->Parent() == NULL) { // printf("%s:%p - Lock() no parent.\n", GetClass(), this); return true; } if (TimeOut >= 0) { auto r = d->Hnd->LockLooperWithTimeout(TimeOut * 1000); if (r == B_OK) { _InLock++; // printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent()); return true; } printf("%s:%i - Lock(%i) failed with %x.\n", _FL, TimeOut, r); return false; } auto r = d->Hnd->LockLooper(); if (r) { _InLock++; // printf("%s:%p - Lock() cnt=%i par=%p.\n", GetClass(), this, _InLock, d->Hnd->Parent()); return true; } printf("%s:%i - Lock(%s:%i) failed.\n", _FL, file, line); return false; #else if (!_Window) GetWindow(); _InLock++; // LgiTrace("%s::%p Lock._InLock=%i %s:%i\n", GetClass(), this, _InLock, file, line); if (_Window && _Window->_Lock) { if (TimeOut < 0) { return _Window->_Lock->Lock(file, line); } else { return _Window->_Lock->LockWithTimeout(TimeOut, file, line); } } return true; #endif } void LView::Unlock() { #ifdef HAIKU if (!d || !d->Hnd) { printf("%s:%i - Unlock() error, no hnd.\n", _FL); return; } if (!d->Hnd->Parent()) { // printf("%s:%p - Unlock() no parent.\n", GetClass(), this); return; } if (_InLock > 0) { // printf("%s:%p - Calling UnlockLooper: %i.\n", GetClass(), this, _InLock); d->Hnd->UnlockLooper(); _InLock--; // printf("%s:%p - UnlockLooper done: %i.\n", GetClass(), this, _InLock); } else { printf("%s:%i - Unlock() without lock.\n", _FL); } #else if (_Window && _Window->_Lock) { _Window->_Lock->Unlock(); } _InLock--; // LgiTrace("%s::%p Unlock._InLock=%i\n", GetClass(), this, _InLock); #endif } void LView::OnMouseClick(LMouse &m) { } void LView::OnMouseEnter(LMouse &m) { } void LView::OnMouseExit(LMouse &m) { } void LView::OnMouseMove(LMouse &m) { } bool LView::OnMouseWheel(double Lines) { return false; } bool LView::OnKey(LKey &k) { return false; } void LView::OnAttach() { List::I it = Children.begin(); for (LViewI *v = *it; v; v = *++it) { if (!v->GetParent()) v->SetParent(this); } #if 0 // defined __GTK_H__ if (_View && !DropTarget()) { // If one of our parents is drop capable we need to set a dest here LViewI *p; for (p = GetParent(); p; p = p->GetParent()) { if (p->DropTarget()) { break; } } if (p) { Gtk::gtk_drag_dest_set( _View, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); // printf("%s:%i - Drop dest for '%s'\n", _FL, GetClass()); } else { Gtk::gtk_drag_dest_unset(_View); // printf("%s:%i - Not setting drop dest '%s'\n", _FL, GetClass()); } } #endif } void LView::OnCreate() { } void LView::OnDestroy() { } void LView::OnFocus(bool f) { - printf("%s::OnFocus(%i)\n", GetClass(), f); + // printf("%s::OnFocus(%i)\n", GetClass(), f); } void LView::OnPulse() { } void LView::OnPosChange() { } bool LView::OnRequestClose(bool OsShuttingDown) { return true; } int LView::OnHitTest(int x, int y) { return -1; } void LView::OnChildrenChanged(LViewI *Wnd, bool Attaching) { } void LView::OnPaint(LSurface *pDC) { auto c = GetClient(); LCssTools Tools(this); Tools.PaintContent(pDC, c); } int LView::OnNotify(LViewI *Ctrl, LNotification Data) { if (!Ctrl) return 0; if (Ctrl == (LViewI*)this && Data.Type == LNotifyActivate) { // Default activation is to focus the current control. Focus(true); } else if (d && d->Parent) { // default behaviour is just to pass the // notification up to the parent // FIXME: eventually we need to call the 'LNotification' parent fn... return d->Parent->OnNotify(Ctrl, Data); } return 0; } int LView::OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void LView::OnNcPaint(LSurface *pDC, LRect &r) { int Border = Sunken() || Raised() ? _BorderSize : 0; if (Border == 2) { LEdge e; if (Sunken()) e = Focus() ? EdgeWin7FocusSunken : DefaultSunkenEdge; else e = DefaultRaisedEdge; #if WINNATIVE if (d->IsThemed) DrawThemeBorder(pDC, r); else #endif LWideBorder(pDC, r, e); } else if (Border == 1) { LThinBorder(pDC, r, Sunken() ? DefaultSunkenEdge : DefaultRaisedEdge); } } #if LGI_COCOA || defined(__GTK_H__) /* uint64 nPaint = 0; uint64 PaintTime = 0; */ void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { /* uint64 StartTs = Update ? LCurrentTime() : 0; d->InPaint = true; */ // Create temp DC if needed... LAutoPtr Local; if (!pDC) { if (!Local.Reset(new LScreenDC(this))) return; pDC = Local; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif // Non-Client drawing LRect r; if (Offset) { r = Pos; r.Offset(Offset); } else { r = GetClient().ZeroTranslate(); } pDC->SetClient(&r); LRect zr1 = r.ZeroTranslate(), zr2 = zr1; OnNcPaint(pDC, zr1); pDC->SetClient(NULL); if (zr2 != zr1) { r.x1 -= zr2.x1 - zr1.x1; r.y1 -= zr2.y1 - zr1.y1; r.x2 -= zr2.x2 - zr1.x2; r.y2 -= zr2.y2 - zr1.y2; } LPoint o(r.x1, r.y1); // Origin of client // Paint this view's contents... pDC->SetClient(&r); #if 0 if (_Debug) { #if defined(__GTK_H__) Gtk::cairo_matrix_t matrix; cairo_get_matrix(pDC->Handle(), &matrix); double ex[4]; cairo_clip_extents(pDC->Handle(), ex+0, ex+1, ex+2, ex+3); ex[0] += matrix.x0; ex[1] += matrix.y0; ex[2] += matrix.x0; ex[3] += matrix.y0; LgiTrace("%s::_Paint, r=%s, clip=%g,%g,%g,%g - %g,%g\n", GetClass(), r.GetStr(), ex[0], ex[1], ex[2], ex[3], matrix.x0, matrix.y0); #elif LGI_COCOA auto Ctx = pDC->Handle(); CGAffineTransform t = CGContextGetCTM(Ctx); LRect cr = CGContextGetClipBoundingBox(Ctx); printf("%s::_Paint() pos=%s transform=%g,%g,%g,%g-%g,%g clip=%s r=%s\n", GetClass(), GetPos().GetStr(), t.a, t.b, t.c, t.d, t.tx, t.ty, cr.GetStr(), r.GetStr()); #endif } #endif OnPaint(pDC); pDC->SetClient(NULL); // Paint all the children... for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { if (!w->Pos.Valid()) continue; #if 0 if (w->_Debug) LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), o.x, o.y); #endif w->_Paint(pDC, &o); } } } #else void LView::_Paint(LSurface *pDC, LPoint *Offset, LRect *Update) { // Create temp DC if needed... LAutoPtr Local; if (!pDC) { Local.Reset(new LScreenDC(this)); pDC = Local; } if (!pDC) { printf("%s:%i - No context to draw in.\n", _FL); return; } #if 0 // This is useful for coverage checking pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif bool HasClient = false; LRect r(0, 0, Pos.X()-1, Pos.Y()-1), Client; LPoint o; if (Offset) o = *Offset; #if WINNATIVE if (!_View) #endif { // Non-Client drawing Client = r; OnNcPaint(pDC, Client); HasClient = GetParent() && (Client != r); if (HasClient) { Client.Offset(o.x, o.y); pDC->SetClient(&Client); } } r.Offset(o.x, o.y); // Paint this view's contents if (Update) { LRect OldClip = pDC->ClipRgn(); pDC->ClipRgn(Update); OnPaint(pDC); pDC->ClipRgn(OldClip.Valid() ? &OldClip : NULL); } else { OnPaint(pDC); } #if PAINT_VIRTUAL_CHILDREN // Paint any virtual children for (auto i : Children) { LView *w = i->GetGView(); if (w && w->Visible()) { #if LGI_VIEW_HANDLE if (!w->Handle()) #endif { LRect p = w->GetPos(); p.Offset(o.x, o.y); if (HasClient) p.Offset(Client.x1 - r.x1, Client.y1 - r.y1); LPoint co(p.x1, p.y1); // LgiTrace("%s::_Paint %i,%i\n", w->GetClass(), p.x1, p.y1); pDC->SetClient(&p); w->_Paint(pDC, &co); pDC->SetClient(NULL); } } } #endif if (HasClient) pDC->SetClient(0); } #endif LViewI *LView::GetParent() { ThreadCheck(); return d ? d->Parent : NULL; } void LView::SetParent(LViewI *p) { ThreadCheck(); d->Parent = p ? p->GetGView() : NULL; d->ParentI = p; } void LView::SendNotify(LNotification note) { LViewI *n = d->Notify ? d->Notify : d->Parent; if (n) { if ( #if LGI_VIEW_HANDLE && !defined(HAIKU) !_View || #endif InThread()) { n->OnNotify(this, note); } else { // We are not in the same thread as the target window. So we post a message // across to the view. if (GetId() <= 0) { // We are going to generate a control Id to help the receiver of the // M_CHANGE message find out view later on. The reason for doing this // instead of sending a pointer to the object, is that the object // _could_ be deleted between the message being sent and being received. // Which would result in an invalid memory access on that object. LViewI *p = GetWindow(); if (!p) { // No window? Find the top most parent we can... p = this; while (p->GetParent()) p = p->GetParent(); } if (p) { // Give the control a valid ID int i; for (i=10; i<1000; i++) { if (!p->FindControl(i)) { printf("Giving the ctrl '%s' the id '%i' for SendNotify\n", GetClass(), i); SetId(i); break; } } } else { // Ok this is really bad... go random (better than nothing) SetId(5000 + LRand(2000)); } } LAssert(GetId() > 0); // We must have a valid ctrl ID at this point, otherwise // the receiver will never be able to find our object. // printf("Post M_CHANGE %i %i\n", GetId(), Data); n->PostEvent(M_CHANGE, (LMessage::Param) GetId(), (LMessage::Param) new LNotification(note)); } } } LViewI *LView::GetNotify() { ThreadCheck(); return d->Notify; } void LView::SetNotify(LViewI *p) { ThreadCheck(); d->Notify = p; } #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 int IsAdjacent(LRect &a, LRect &b) { if ( (a.x1 == b.x2 + 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_LEFT; } if ( (a.x2 == b.x1 - 1) && !(a.y1 > b.y2 || a.y2 < b.y1)) { return ADJ_RIGHT; } if ( (a.y1 == b.y2 + 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_UP; } if ( (a.y2 == b.y1 - 1) && !(a.x1 > b.x2 || a.x2 < b.x1)) { return ADJ_DOWN; } return 0; } LRect JoinAdjacent(LRect &a, LRect &b, int Adj) { LRect t; switch (Adj) { case ADJ_LEFT: case ADJ_RIGHT: { t.y1 = MAX(a.y1, b.y1); t.y2 = MIN(a.y2, b.y2); t.x1 = MIN(a.x1, b.x1); t.x2 = MAX(a.x2, b.x2); break; } case ADJ_UP: case ADJ_DOWN: { t.y1 = MIN(a.y1, b.y1); t.y2 = MAX(a.y2, b.y2); t.x1 = MAX(a.x1, b.x1); t.x2 = MIN(a.x2, b.x2); break; } } return t; } LRect *LView::FindLargest(LRegion &r) { ThreadCheck(); int Pixels = 0; LRect *Best = 0; static LRect Final; // do initial run through the list to find largest single region for (LRect *i = r.First(); i; i = r.Next()) { int Pix = i->X() * i->Y(); if (Pix > Pixels) { Pixels = Pix; Best = i; } } if (Best) { Final = *Best; Pixels = Final.X() * Final.Y(); int LastPixels = Pixels; LRect LastRgn = Final; int ThisPixels = Pixels; LRect ThisRgn = Final; LRegion TempList; for (LRect *i = r.First(); i; i = r.Next()) { TempList.Union(i); } TempList.Subtract(Best); do { LastPixels = ThisPixels; LastRgn = ThisRgn; // search for adjoining rectangles that maybe we can add for (LRect *i = TempList.First(); i; i = TempList.Next()) { int Adj = IsAdjacent(ThisRgn, *i); if (Adj) { LRect t = JoinAdjacent(ThisRgn, *i, Adj); int Pix = t.X() * t.Y(); if (Pix > ThisPixels) { ThisPixels = Pix; ThisRgn = t; TempList.Subtract(i); } } } } while (LastPixels < ThisPixels); Final = ThisRgn; } else return 0; return &Final; } LRect *LView::FindSmallestFit(LRegion &r, int Sx, int Sy) { ThreadCheck(); int X = 1000000; int Y = 1000000; LRect *Best = 0; for (LRect *i = r.First(); i; i = r.Next()) { if ((i->X() >= Sx && i->Y() >= Sy) && (i->X() < X || i->Y() < Y)) { X = i->X(); Y = i->Y(); Best = i; } } return Best; } LRect *LView::FindLargestEdge(LRegion &r, int Edge) { LRect *Best = 0; ThreadCheck(); for (LRect *i = r.First(); i; i = r.Next()) { if (!Best) { Best = i; } if ( ((Edge & GV_EDGE_TOP) && (i->y1 < Best->y1)) || ((Edge & GV_EDGE_RIGHT) && (i->x2 > Best->x2)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 > Best->y2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 < Best->x1)) ) { Best = i; } if ( ( ((Edge & GV_EDGE_TOP) && (i->y1 == Best->y1)) || ((Edge & GV_EDGE_BOTTOM) && (i->y2 == Best->y2)) ) && ( i->X() > Best->X() ) ) { Best = i; } if ( ( ((Edge & GV_EDGE_RIGHT) && (i->x2 == Best->x2)) || ((Edge & GV_EDGE_LEFT) && (i->x1 == Best->x1)) ) && ( i->Y() > Best->Y() ) ) { Best = i; } } return Best; } LViewI *LView::FindReal(LPoint *Offset) { ThreadCheck(); if (Offset) { Offset->x = 0; Offset->y = 0; } #if !LGI_VIEW_HANDLE LViewI *w = GetWindow(); #endif LViewI *p = d->Parent; while (p && #if !LGI_VIEW_HANDLE p != w #else !p->Handle() #endif ) { if (Offset) { Offset->x += Pos.x1; Offset->y += Pos.y1; } p = p->GetParent(); } if (p && #if !LGI_VIEW_HANDLE p == w #else p->Handle() #endif ) { return p; } return NULL; } bool LView::HandleCapture(LView *Wnd, bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::HandleCapture(%i)=%i\n", GetClass(), c, (int)(_Capturing == Wnd)); if (c) { if (_Capturing == Wnd) { DEBUG_CAPTURE(" %s already has capture\n", _Capturing?_Capturing->GetClass():0); } else { DEBUG_CAPTURE(" _Capturing=%s -> %s\n", _Capturing?_Capturing->GetClass():0, Wnd?Wnd->GetClass():0); _Capturing = Wnd; #if defined(__GTK_H__) if (d->CaptureThread) d->CaptureThread->Cancel(); d->CaptureThread = new LCaptureThread(this); #elif WINNATIVE LPoint Offset; LViewI *v = _Capturing->Handle() ? _Capturing : FindReal(&Offset); HWND h = v ? v->Handle() : NULL; if (h) SetCapture(h); else LAssert(0); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_TRUE); #else LAppInst->CaptureMouse(true); #endif #endif } } else if (_Capturing) { DEBUG_CAPTURE(" _Capturing=%s -> NULL\n", _Capturing?_Capturing->GetClass():0); _Capturing = NULL; #if defined(__GTK_H__) if (d->CaptureThread) { d->CaptureThread->Cancel(); d->CaptureThread = NULL; // It'll delete itself... } #elif WINNATIVE ReleaseCapture(); #elif defined(LGI_SDL) #if SDL_VERSION_ATLEAST(2, 0, 4) SDL_CaptureMouse(SDL_FALSE); #else LAppInst->CaptureMouse(false); #endif #endif } return true; } bool LView::IsCapturing() { - ThreadCheck(); // DEBUG_CAPTURE("%s::IsCapturing()=%p==%p==%i\n", GetClass(), _Capturing, this, (int)(_Capturing == this)); return _Capturing == this; } bool LView::Capture(bool c) { ThreadCheck(); DEBUG_CAPTURE("%s::Capture(%i)\n", GetClass(), c); return HandleCapture(this, c); } bool LView::Enabled() { ThreadCheck(); #if WINNATIVE if (_View) return IsWindowEnabled(_View) != 0; #endif return !TestFlag(GViewFlags, GWF_DISABLED); } void LView::Enabled(bool i) { ThreadCheck(); if (!i) SetFlag(GViewFlags, GWF_DISABLED); else ClearFlag(GViewFlags, GWF_DISABLED); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE EnableWindow(_View, i); #elif defined LGI_CARBON if (i) { OSStatus e = EnableControl(_View); if (e) printf("%s:%i - Error enabling control (%i)\n", _FL, (int)e); } else { OSStatus e = DisableControl(_View); if (e) printf("%s:%i - Error disabling control (%i)\n", _FL, (int)e); } #endif } #endif Invalidate(); } bool LView::Visible() { // This is a read only operation... which is kinda thread-safe... // ThreadCheck(); #if WINNATIVE if (_View) /* This takes into account all the parent windows as well... Which is kinda not what I want. I want this to reflect just this window. return IsWindowVisible(_View); */ return (GetWindowLong(_View, GWL_STYLE) & WS_VISIBLE) != 0; #endif return TestFlag(GViewFlags, GWF_VISIBLE); } void LView::Visible(bool v) { ThreadCheck(); if (v) SetFlag(GViewFlags, GWF_VISIBLE); else ClearFlag(GViewFlags, GWF_VISIBLE); #if defined(HAIKU) LLocker lck(d->Hnd, _FL); if (!IsAttached() || lck.Lock()) { const int attempts = 3; // printf("%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); if (v) { bool parentHidden = false; for (auto p = d->Hnd->Parent(); p; p = p->Parent()) { if (p->IsHidden()) { parentHidden = true; break; } } if (!parentHidden) // Don't try and show if one of the parent's is hidden. { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Show\n", this); d->Hnd->Show(); } if (d->Hnd->IsHidden()) { printf("%s:%i - Failed to show %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } } else { for (int i=0; iHnd->IsHidden(); i++) { // printf("\t%p Hide\n", this); d->Hnd->Hide(); } if (!d->Hnd->IsHidden()) { printf("%s:%i - Failed to hide %s.\n", _FL, GetClass()); for (auto p = d->Hnd->Parent(); p; p = p->Parent()) printf("\tparent: %s/%p ishidden=%i\n", p->Name(), p, p->IsHidden()); } } // printf("\t%s/%p:Visible(%i) hidden=%i\n", GetClass(), this, v, d->Hnd->IsHidden()); } else LgiTrace("%s:%i - Can't lock.\n", _FL); #elif LGI_VIEW_HANDLE if (_View) { #if WINNATIVE ShowWindow(_View, (v) ? SW_SHOWNORMAL : SW_HIDE); #elif LGI_COCOA LAutoPool Pool; [_View.p setHidden:!v]; #elif LGI_CARBON Boolean is = HIViewIsVisible(_View); if (v != is) { OSErr e = HIViewSetVisible(_View, v); if (e) printf("%s:%i - HIViewSetVisible(%p,%i) failed with %i (class=%s)\n", _FL, _View, v, e, GetClass()); } #endif } else #endif { Invalidate(); } } bool LView::Focus() { ThreadCheck(); bool Has = false; #if defined(__GTK_H__) LWindow *w = GetWindow(); if (w) { bool Active = w->IsActive(); if (Active) Has = w->GetFocus() == static_cast(this); } #elif defined(HAIKU) LLocker lck(d->Hnd, _FL); if (lck.Lock()) { Has = d->Hnd->IsFocus(); lck.Unlock(); } #elif defined(WINNATIVE) if (_View) { HWND hFocus = GetFocus(); Has = hFocus == _View; } #elif LGI_COCOA Has = TestFlag(WndFlags, GWF_FOCUS); #elif LGI_CARBON LWindow *w = GetWindow(); if (w) { ControlRef Cur; OSErr e = GetKeyboardFocus(w->WindowHandle(), &Cur); if (e) LgiTrace("%s:%i - GetKeyboardFocus failed with %i\n", _FL, e); else Has = (Cur == _View); } #endif #if !LGI_CARBON if (Has) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); #endif return Has; } void LView::Focus(bool i) { ThreadCheck(); if (i) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); auto *Wnd = GetWindow(); if (Wnd) Wnd->SetFocus(this, i ? LWindow::GainFocus : LWindow::LoseFocus); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) #endif { #if defined(HAIKU) _Focus(i); #elif defined(LGI_SDL) || defined(__GTK_H__) // Nop: Focus is all handled by Lgi's LWindow class. #elif WINNATIVE if (i) { HWND hCur = GetFocus(); if (hCur != _View) { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus %p (%-30s)\n", _FL, Handle(), Name()); } SetFocus(_View); } } else { if (In_SetWindowPos) { assert(0); LgiTrace("%s:%i - SetFocus(%p)\n", _FL, GetDesktopWindow()); } SetFocus(GetDesktopWindow()); } #elif defined LGI_CARBON LViewI *Wnd = GetWindow(); if (Wnd && i) { OSErr e = SetKeyboardFocus(Wnd->WindowHandle(), _View, 1); if (e) { // e = SetKeyboardFocus(Wnd->WindowHandle(), _View, kControlFocusNextPart); // if (e) { HIViewRef p = HIViewGetSuperview(_View); // errCouldntSetFocus printf("%s:%i - SetKeyboardFocus failed: %i (%s, %p)\n", _FL, e, GetClass(), p); } } // else printf("%s:%i - SetFocus v=%p(%s)\n", _FL, _View, GetClass()); } else printf("%s:%i - no window?\n", _FL); #endif } } LDragDropSource *LView::DropSource(LDragDropSource *Set) { if (Set) d->DropSource = Set; return d->DropSource; } LDragDropTarget *LView::DropTarget(LDragDropTarget *Set) { if (Set) d->DropTarget = Set; return d->DropTarget; } #if defined LGI_CARBON extern pascal OSStatus LgiViewDndHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData); #endif #if defined __GTK_H__ // Recursively add drag dest to all view and all children bool GtkAddDragDest(LViewI *v, bool IsTarget) { if (!v) return false; LWindow *w = v->GetWindow(); if (!w) return false; auto wid = GtkCast(w->WindowHandle(), gtk_widget, GtkWidget); if (IsTarget) { Gtk::gtk_drag_dest_set( wid, (Gtk::GtkDestDefaults)0, NULL, 0, Gtk::GDK_ACTION_DEFAULT); } else { Gtk::gtk_drag_dest_unset(wid); } for (LViewI *c: v->IterateViews()) GtkAddDragDest(c, IsTarget); return true; } #endif bool LView::DropTarget(bool t) { ThreadCheck(); bool Status = false; if (t) SetFlag(GViewFlags, GWF_DROP_TARGET); else ClearFlag(GViewFlags, GWF_DROP_TARGET); #if WINNATIVE if (_View) { if (t) { if (!d->DropTarget) DragAcceptFiles(_View, t); else Status = RegisterDragDrop(_View, (IDropTarget*) d->DropTarget) == S_OK; } else { if (_View && d->DropTarget) Status = RevokeDragDrop(_View) == S_OK; } } #elif defined MAC && !defined(LGI_SDL) LWindow *Wnd = dynamic_cast(GetWindow()); if (Wnd) { Wnd->SetDragHandlers(t); if (!d->DropTarget) d->DropTarget = t ? Wnd : 0; } #if LGI_COCOA LWindow *w = GetWindow(); if (w) { OsWindow h = w->WindowHandle(); if (h) { NSMutableArray *a = [[NSMutableArray alloc] init]; if (a) { [a addObject:(NSString*)kUTTypeItem]; for (id item in NSFilePromiseReceiver.readableDraggedTypes) [a addObject:item]; [h.p.contentView registerForDraggedTypes:a]; [a release]; } } } #elif LGI_CARBON if (t) { static EventTypeSpec DragEvents[] = { { kEventClassControl, kEventControlDragEnter }, { kEventClassControl, kEventControlDragWithin }, { kEventClassControl, kEventControlDragLeave }, { kEventClassControl, kEventControlDragReceive }, }; if (!d->DndHandler) { OSStatus e = ::InstallControlEventHandler( _View, NewEventHandlerUPP(LgiViewDndHandler), GetEventTypeCount(DragEvents), DragEvents, (void*)this, &d->DndHandler); if (e) LgiTrace("%s:%i - InstallEventHandler failed (%i)\n", _FL, e); } SetControlDragTrackingEnabled(_View, true); } else { SetControlDragTrackingEnabled(_View, false); } #endif #elif defined __GTK_H__ Status = GtkAddDragDest(this, t); if (Status && !d->DropTarget) d->DropTarget = t ? GetWindow() : 0; #endif return Status; } bool LView::Sunken() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE); #else return TestFlag(GViewFlags, GWF_SUNKEN); #endif } void LView::Sunken(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_CLIENTEDGE); else ClearFlag(d->WndExStyle, WS_EX_CLIENTEDGE); if (_View) SetWindowLong(_View, GWL_EXSTYLE, d->WndExStyle); #else if (i) SetFlag(GViewFlags, GWF_SUNKEN); else ClearFlag(GViewFlags, GWF_SUNKEN); #endif if (i) { if (!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } bool LView::Flat() { // ThreadCheck(); #if WINNATIVE return !TestFlag(d->WndExStyle, WS_EX_CLIENTEDGE) && !TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return !TestFlag(GViewFlags, GWF_SUNKEN) && !TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Flat(bool i) { ThreadCheck(); #if WINNATIVE ClearFlag(d->WndExStyle, (WS_EX_CLIENTEDGE|WS_EX_WINDOWEDGE)); #else ClearFlag(GViewFlags, (GWF_RAISED|GWF_SUNKEN)); #endif } bool LView::Raised() { // ThreadCheck(); #if WINNATIVE return TestFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else return TestFlag(GViewFlags, GWF_RAISED); #endif } void LView::Raised(bool i) { ThreadCheck(); #if WINNATIVE if (i) SetFlag(d->WndExStyle, WS_EX_WINDOWEDGE); else ClearFlag(d->WndExStyle, WS_EX_WINDOWEDGE); #else if (i) SetFlag(GViewFlags, GWF_RAISED); else ClearFlag(GViewFlags, GWF_RAISED); #endif if (i) { if (!!_BorderSize) _BorderSize = 2; } else _BorderSize = 0; } int LView::GetId() { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. return d->CtrlId; } void LView::SetId(int i) { // This is needed by SendNotify function which is thread safe. // So no thread safety check here. d->CtrlId = i; #if WINNATIVE if (_View) SetWindowLong(_View, GWL_ID, d->CtrlId); #elif defined __GTK_H__ #elif defined MAC #endif } bool LView::GetTabStop() { ThreadCheck(); #if WINNATIVE return TestFlag(d->WndStyle, WS_TABSTOP); #else return d->TabStop; #endif } void LView::SetTabStop(bool b) { ThreadCheck(); #if WINNATIVE - if (b) - SetFlag(d->WndStyle, WS_TABSTOP); - else - ClearFlag(d->WndStyle, WS_TABSTOP); + if (b) + SetFlag(d->WndStyle, WS_TABSTOP); + else + ClearFlag(d->WndStyle, WS_TABSTOP); #else - d->TabStop = b; - #endif - #if 0 // def __GTK_H__ - if (_View) - { - #if GtkVer(2, 18) - ThreadCheck(); - gtk_widget_set_can_focus(_View, b); - #else - LgiTrace("Error: no api to set tab stop.\n"); - #endif - } + d->TabStop = b; #endif } int64 LView::GetCtrlValue(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); - if (!w) - printf("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Value() : 0; } void LView::SetCtrlValue(int Id, int64 i) { ThreadCheck(); LViewI *w = FindControl(Id); - // if (!w) printf("%s:%i - Ctrl %i not found.\n", _FL, Id); if (w) w->Value(i); } const char *LView::GetCtrlName(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); - // if (!w) printf("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Name() : 0; } void LView::SetCtrlName(int Id, const char *s) { - ThreadCheck(); - - LViewI *w = FindControl(Id); - // if (!w) printf("%s:%i - Ctrl %i not found.\n", _FL, Id); - if (w) w->Name(s); + if (!IsAttached() || InThread()) + { + if (auto w = FindControl(Id)) + w->Name(s); + } + else + { + PostEvent( M_SET_CTRL_NAME, + (LMessage::Param)Id, + (LMessage::Param)new LString(s)); + } } bool LView::GetCtrlEnabled(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); - // if (!w) printf("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Enabled() : 0; } void LView::SetCtrlEnabled(int Id, bool Enabled) { - ThreadCheck(); - - LViewI *w = FindControl(Id); - // if (!w) printf("%s:%i - Ctrl %i not found.\n", _FL, Id); - if (w) w->Enabled(Enabled); + if (!IsAttached() || InThread()) + { + if (auto w = FindControl(Id)) + w->Enabled(Enabled); + } + else + { + PostEvent( M_SET_CTRL_ENABLE, + (LMessage::Param)Id, + (LMessage::Param)Enabled); + } } bool LView::GetCtrlVisible(int Id) { ThreadCheck(); LViewI *w = FindControl(Id); if (!w) LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); return (w) ? w->Visible() : 0; } void LView::SetCtrlVisible(int Id, bool v) { - ThreadCheck(); - - LViewI *w = FindControl(Id); - if (!w) - LgiTrace("%s:%i - Ctrl %i not found.\n", _FL, Id); + if (!IsAttached() || InThread()) + { + if (auto w = FindControl(Id)) + w->Visible(v); + } else - w->Visible(v); + { + PostEvent( M_SET_CTRL_VISIBLE, + (LMessage::Param)Id, + (LMessage::Param)v); + } } bool LView::AttachChildren() { for (auto c: Children) { bool a = c->IsAttached(); if (!a) { if (!c->Attach(this)) { LgiTrace("%s:%i - failed to attach %s\n", _FL, c->GetClass()); return false; } } } return true; } LFont *LView::GetFont() { if (!d->Font && d->Css && LResources::GetLoadStyles()) { LFontCache *fc = LAppInst->GetFontCache(); if (fc) { LFont *f = fc->GetFont(d->Css); if (f) { if (d->FontOwnType == GV_FontOwned) DeleteObj(d->Font); d->Font = f; d->FontOwnType = GV_FontCached; } } } return d->Font ? d->Font : LSysFont; } void LView::SetFont(LFont *Font, bool OwnIt) { bool Change = d->Font != Font; if (Change) { if (d->FontOwnType == GV_FontOwned) { LAssert(d->Font != LSysFont); DeleteObj(d->Font); } d->FontOwnType = OwnIt ? GV_FontOwned : GV_FontPtr; d->Font = Font; #if WINNATIVE if (_View) SendMessage(_View, WM_SETFONT, (WPARAM) (Font ? Font->Handle() : 0), 0); #endif for (LViewI *p = GetParent(); p; p = p->GetParent()) { LTableLayout *Tl = dynamic_cast(p); if (Tl) { Tl->InvalidateLayout(); break; } } Invalidate(); } } bool LView::IsOver(LMouse &m) { return (m.x >= 0) && (m.y >= 0) && (m.x < Pos.X()) && (m.y < Pos.Y()); } bool LView::WindowVirtualOffset(LPoint *Offset) { bool Status = false; if (Offset) { Offset->x = 0; Offset->y = 0; for (LViewI *Wnd = this; Wnd; Wnd = Wnd->GetParent()) { #if !LGI_VIEW_HANDLE auto IsWnd = dynamic_cast(Wnd); if (!IsWnd) #else if (!Wnd->Handle()) #endif { LRect r = Wnd->GetPos(); LViewI *Par = Wnd->GetParent(); if (Par) { LRect c = Par->GetClient(false); Offset->x += r.x1 + c.x1; Offset->y += r.y1 + c.y1; } else { Offset->x += r.x1; Offset->y += r.y1; } Status = true; } else break; } } return Status; } LString _ViewDesc(LViewI *v) { LString s; s.Printf("%s/%s/%i", v->GetClass(), v->Name(), v->GetId()); return s; } LViewI *LView::WindowFromPoint(int x, int y, int DebugDepth) { char Tabs[64]; if (DebugDepth) { memset(Tabs, 9, DebugDepth); Tabs[DebugDepth] = 0; LgiTrace("%s%s %i\n", Tabs, _ViewDesc(this).Get(), Children.Length()); } // We iterate over the child in reverse order because if they overlap the // end of the list is on "top". So they should get the click or whatever // before the the lower windows. auto it = Children.rbegin(); int n = (int)Children.Length() - 1; for (LViewI *c = *it; c; c = *--it) { LRect CPos = c->GetPos(); if (CPos.Overlap(x, y) && c->Visible()) { LRect CClient; CClient = c->GetClient(false); int Ox = CPos.x1 + CClient.x1; int Oy = CPos.y1 + CClient.y1; if (DebugDepth) { LgiTrace("%s[%i] %s Pos=%s Client=%s m(%i,%i)->(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), CClient.GetStr(), x, y, x - Ox, y - Oy); } LViewI *Child = c->WindowFromPoint(x - Ox, y - Oy, DebugDepth ? DebugDepth + 1 : 0); if (Child) return Child; } else if (DebugDepth) { LgiTrace("%s[%i] MISSED %s Pos=%s m(%i,%i)\n", Tabs, n--, _ViewDesc(c).Get(), CPos.GetStr(), x, y); } } if (x >= 0 && y >= 0 && x < Pos.X() && y < Pos.Y()) { return this; } return NULL; } LColour LView::StyleColour(int CssPropType, LColour Default, int Depth) { LColour c = Default; if ((CssPropType >> 8) == LCss::TypeColor) { LViewI *v = this; for (int i=0; v && iGetParent()) { auto Style = v->GetCss(); if (Style) { auto Colour = (LCss::ColorDef*) Style->PropAddress((LCss::PropType)CssPropType); if (Colour) { if (Colour->Type == LCss::ColorRgb) { c.Set(Colour->Rgb32, 32); break; } else if (Colour->Type == LCss::ColorTransparent) { c.Empty(); break; } } } if (dynamic_cast(v) || dynamic_cast(v)) break; } } return c; } bool LView::InThread() { #if WINNATIVE HWND Hnd = _View; for (LViewI *p = GetParent(); p && !Hnd; p = p->GetParent()) { Hnd = p->Handle(); } auto CurThreadId = GetCurrentThreadId(); auto GuiThreadId = LAppInst->GetGuiThreadId(); DWORD ViewThread = Hnd ? GetWindowThreadProcessId(Hnd, NULL) : GuiThreadId; return CurThreadId == ViewThread; #elif defined(HAIKU) return true; #else OsThreadId Me = GetCurrentThreadId(); OsThreadId Gui = LAppInst ? LAppInst->GetGuiThreadId() : 0; #if 0 if (Gui != Me) LgiTrace("%s:%i - Out of thread:" #ifdef LGI_COCOA "%llx, %llx" #else "%x, %x" #endif "\n", _FL, Gui, Me); #endif return Gui == Me; #endif } bool LView::PostEvent(int Cmd, LMessage::Param a, LMessage::Param b, int64_t timeoutMs) { #ifdef LGI_SDL return LPostEvent(this, Cmd, a, b); #elif defined(HAIKU) if (!d || !d->Hnd) { // printf("%s:%i - Bad pointers %p %p\n", _FL, d, d ? d->Hnd : NULL); return false; } auto start = LCurrentTime(); bool locked = false; BView *lockView = NULL; BWindow *lockWindow = NULL; LWindow *wnd = dynamic_cast(this); if (wnd) { lockWindow = wnd->WindowHandle(); if (!lockWindow) { printf("%s:%i - No window to lock (%s)\n", _FL, GetClass()); return false; } do { int64_t elapsedMs = LCurrentTime() - start; int64_t remainingMs = timeoutMs >= 0 ? timeoutMs - elapsedMs : 2000; auto r = lockWindow->LockWithTimeout(remainingMs * 1000); if (r == B_OK) { locked = true; break; } else if (r == B_BAD_VALUE) { printf("%s:%i - Lock destroyed.\n", _FL); return false; } else { if (timeoutMs >= 0) return false; printf("%s waiting on lock for %gs (r=%x, me=%i, locker=%i)\n", GetClass(), (double)(LCurrentTime()-start)/1000.0, r, GetCurrentThreadId(), lockWindow->LockingThread()); } } while (true); } else { // Look for an attached view to lock... for (LViewI *v = this; v; v = v->GetParent()) { auto vhnd = v->Handle(); if (vhnd && ::IsAttached(vhnd)) { lockView = vhnd; break; } } // Try and lock the looper... if (!lockView) { #if 0 auto wnd = lockView ? lockView->Window() : NULL; auto par = lockView ? lockView->Parent() : NULL; printf("%s:%i - Failed to locklooper: %p %i %p %p cls=%s\n", _FL, lockView, locked, wnd, par, GetClass()); #endif return false; } do { int64_t elapsedMs = LCurrentTime() - start; int64_t remainingMs = timeoutMs >= 0 ? timeoutMs - elapsedMs : 2000; auto r = lockView->LockLooperWithTimeout(remainingMs * 1000); if (r == B_OK) { locked = true; break; } else if (r == B_BAD_VALUE) { printf("%s:%i - Lock destroyed.\n", _FL); return false; } else { if (timeoutMs >= 0) return false; printf("%s waiting on lock for %gs (r=%x, me=%i, locker=%i)\n", GetClass(), (double)(LCurrentTime()-start)/1000.0, r, GetCurrentThreadId(), lockWindow->LockingThread()); } } while (true); } BMessage *m = new BMessage(Cmd); if (!m) { printf("%s:%i - alloc failed.\n", _FL); return false; } auto r = m->AddInt64(LMessage::PropA, a); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); r = m->AddInt64(LMessage::PropB, b); if (r != B_OK) printf("%s:%i - AddUInt64 failed.\n", _FL); if (lockView != d->Hnd) { r = m->AddPointer(LMessage::PropView, this); if (r != B_OK) printf("%s:%i - AddPointer failed.\n", _FL); } if (lockView) { r = d->Hnd->Window()->PostMessage(m, lockView); if (r != B_OK) printf("%s:%i - PostMessage failed.\n", _FL); } else if (lockWindow) { r = lockWindow->PostMessage(m); if (r != B_OK) printf("%s:%i - PostMessage failed.\n", _FL); } else { r = B_ERROR; printf("%s:%i - No window?\n", _FL); } if (lockView) lockView->UnlockLooper(); else if (lockWindow) lockWindow->UnlockLooper(); return r == B_OK; #elif WINNATIVE if (!_View) return false; BOOL Res = ::PostMessage(_View, Cmd, a, b); if (!Res) { auto Err = GetLastError(); int asd=0; } return Res != 0; #elif !LGI_VIEW_HANDLE return LAppInst->PostEvent(this, Cmd, a, b); #else if (_View) return LPostEvent(_View, Cmd, a, b); LAssert(0); return false; #endif } bool LView::Invalidate(LRegion *r, bool Repaint, bool NonClient) { if (r) { for (int i=0; iLength(); i++) { bool Last = i == r->Length()-1; Invalidate((*r)[i], Last ? Repaint : false, NonClient); } return true; } return false; } LButton *FindDefault(LViewI *w) { LButton *But = 0; for (auto c: w->IterateViews()) { But = dynamic_cast(c); if (But && But->Default()) { break; } But = FindDefault(c); if (But) { break; } } return But; } bool LView::Name(const char *n) { LBase::Name(n); #if LGI_VIEW_HANDLE && !defined(HAIKU) if (_View) { #if WINNATIVE auto Temp = LBase::NameW(); SetWindowTextW(_View, Temp ? Temp : L""); #endif } #endif Invalidate(); return true; } const char *LView::Name() { #if WINNATIVE if (_View) { LView::NameW(); } #endif return LBase::Name(); } bool LView::NameW(const char16 *n) { LBase::NameW(n); #if WINNATIVE if (_View && n) { auto Txt = LBase::NameW(); SetWindowTextW(_View, Txt); } #endif Invalidate(); return true; } const char16 *LView::NameW() { #if WINNATIVE if (_View) { int Length = GetWindowTextLengthW(_View); if (Length > 0) { char16 *Buf = new char16[Length+1]; if (Buf) { Buf[0] = 0; int Chars = GetWindowTextW(_View, Buf, Length+1); Buf[Chars] = 0; LBase::NameW(Buf); } DeleteArray(Buf); } else { LBase::NameW(0); } } #endif return LBase::NameW(); } LViewI *LView::FindControl(int Id) { LAssert(Id != -1); if (GetId() == Id) { return this; } for (auto c : Children) { LViewI *Ctrl = c->FindControl(Id); if (Ctrl) { return Ctrl; } } return 0; } LPoint LView::GetMinimumSize() { return d->MinimumSize; } void LView::SetMinimumSize(LPoint Size) { d->MinimumSize = Size; bool Change = false; LRect p = Pos; if (X() < d->MinimumSize.x) { p.x2 = p.x1 + d->MinimumSize.x - 1; Change = true; } if (Y() < d->MinimumSize.y) { p.y2 = p.y1 + d->MinimumSize.y - 1; Change = true; } if (Change) { SetPos(p); } } bool LView::SetColour(LColour &c, bool Fore) { LCss *css = GetCss(true); if (!css) return false; if (Fore) { if (c.IsValid()) css->Color(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropColor); } else { if (c.IsValid()) css->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, c.c32())); else css->DeleteProp(LCss::PropBackgroundColor); } return true; } /* bool LView::SetCssStyle(const char *CssStyle) { if (!d->Css && !d->Css.Reset(new LCss)) return false; const char *Defs = CssStyle; bool b = d->Css->Parse(Defs, LCss::ParseRelaxed); if (b && d->FontOwnType == GV_FontCached) { d->Font = NULL; d->FontOwnType = GV_FontPtr; } return b; } */ void LView::SetCss(LCss *css) { d->Css.Reset(css); } LCss *LView::GetCss(bool Create) { if (Create && !d->Css) d->Css.Reset(new LCss); if (d->CssDirty && d->Css) { const char *Defs = d->Styles; if (d->Css->Parse(Defs, LCss::ParseRelaxed)) d->CssDirty = false; } return d->Css; } LPoint &LView::GetWindowBorderSize() { static LPoint s; ZeroObj(s); #if WINNATIVE if (_View) { RECT Wnd, Client; GetWindowRect(Handle(), &Wnd); GetClientRect(Handle(), &Client); s.x = (Wnd.right-Wnd.left) - (Client.right-Client.left); s.y = (Wnd.bottom-Wnd.top) - (Client.bottom-Client.top); } #elif defined __GTK_H__ #elif defined MAC s.x = 0; s.y = 22; #endif return s; } #ifdef _DEBUG #if defined(LGI_CARBON) void DumpHiview(HIViewRef v, int Depth = 0) { char Sp[256]; memset(Sp, ' ', Depth << 2); Sp[Depth<<2] = 0; printf("%sHIView=%p", Sp, v); if (v) { Boolean vis = HIViewIsVisible(v); Boolean en = HIViewIsEnabled(v, NULL); HIRect pos; HIViewGetFrame(v, &pos); char cls[128]; ZeroObj(cls); GetControlProperty(v, 'meme', 'clas', sizeof(cls), NULL, cls); printf(" vis=%i en=%i pos=%g,%g-%g,%g cls=%s", vis, en, pos.origin.x, pos.origin.y, pos.size.width, pos.size.height, cls); } printf("\n"); for (HIViewRef c = HIViewGetFirstSubview(v); c; c = HIViewGetNextView(c)) { DumpHiview(c, Depth + 1); } } #elif defined(__GTK_H__) void DumpGtk(Gtk::GtkWidget *w, Gtk::gpointer Depth = NULL) { using namespace Gtk; if (!w) return; char Sp[65] = {0}; if (Depth) memset(Sp, ' ', *((int*)Depth)*2); auto *Obj = G_OBJECT(w); LViewI *View = (LViewI*) g_object_get_data(Obj, "LViewI"); GtkAllocation a; gtk_widget_get_allocation(w, &a); LgiTrace("%s%p(%s) = %i,%i-%i,%i\n", Sp, w, View?View->GetClass():G_OBJECT_TYPE_NAME(Obj), a.x, a.y, a.width, a.height); if (GTK_IS_CONTAINER(w)) { auto *c = GTK_CONTAINER(w); if (c) { int Next = Depth ? *((int*)Depth)+1 : 1; gtk_container_foreach(c, DumpGtk, &Next); } } } #elif defined(HAIKU) || defined(WINDOWS) template void _Dump(int Depth, T v) { LString Sp, Name; Sp.Length(Depth<<1); memset(Sp.Get(), ' ', Depth<<1); Sp.Get()[Depth<<1] = 0; #if defined(HAIKU) LRect Frame = v->Frame(); Name = v->Name(); bool IsHidden = v->IsHidden(); #else RECT rc; GetWindowRect(v, &rc); LRect Frame = rc; wchar_t txt[256]; GetWindowTextW(v, txt, CountOf(txt)); Name = txt; bool IsHidden = !(GetWindowLong(v, GWL_STYLE) & WS_VISIBLE); #endif LgiTrace("%s%p::%s frame=%s vis=%i\n", Sp.Get(), v, Name.Get(), Frame.GetStr(), !IsHidden); #if defined(HAIKU) for (int32 i=0; iCountChildren(); i++) _Dump(Depth + 1, v->ChildAt(i)); #else for (auto h = GetWindow(v, GW_CHILD); h; h = GetWindow(h, GW_HWNDNEXT)) _Dump(Depth + 1, h); #endif } #endif void LView::_Dump(int Depth) { char Sp[65] = {0}; memset(Sp, ' ', Depth*2); #if 0 char s[256]; sprintf_s(s, sizeof(s), "%s%p::%s %s (_View=%p)\n", Sp, this, GetClass(), GetPos().GetStr(), _View); LgiTrace(s); List::I i = Children.Start(); for (LViewI *c = *i; c; c = *++i) { LView *v = c->GetGView(); if (v) v->_Dump(Depth+1); } #elif defined(LGI_CARBON) DumpHiview(_View); #elif defined(__GTK_H__) // DumpGtk(_View); #else #if defined(HAIKU) LLocker lck(WindowHandle(), _FL); if (lck.Lock()) #endif ::_Dump(0, WindowHandle()); #endif } #endif //////////////////////////////////////////////////////////////////////////////////////////////////// static LArray *AllFactories = NULL; #if defined(WIN32) static HANDLE FactoryEvent; #else static pthread_once_t FactoryOnce = PTHREAD_ONCE_INIT; static void GFactoryInitFactories() { AllFactories = new LArray; } #endif LViewFactory::LViewFactory() { #if defined(WIN32) char16 Name[64]; swprintf_s(Name, CountOf(Name), L"LgiFactoryEvent.%i", GetCurrentProcessId()); HANDLE h = CreateEventW(NULL, false, false, Name); DWORD err = GetLastError(); if (err != ERROR_ALREADY_EXISTS) { FactoryEvent = h; AllFactories = new LArray; } else { LAssert(AllFactories != NULL); } #else pthread_once(&FactoryOnce, GFactoryInitFactories); #endif if (AllFactories) AllFactories->Add(this); } LViewFactory::~LViewFactory() { if (AllFactories) { AllFactories->Delete(this); if (AllFactories->Length() == 0) { DeleteObj(AllFactories); #if defined(WIN32) CloseHandle(FactoryEvent); #endif } } } LView *LViewFactory::Create(const char *Class, LRect *Pos, const char *Text) { if (ValidStr(Class) && AllFactories) { for (int i=0; iLength(); i++) { LView *v = (*AllFactories)[i]->NewView(Class, Pos, Text); if (v) { return v; } } } return 0; } #ifdef _DEBUG #if defined(__GTK_H__) using namespace Gtk; #include "LgiWidget.h" #endif void LView::Debug() { _Debug = true; #if defined LGI_COCOA d->ClassName = GetClass(); #endif } #endif diff --git a/src/common/Net/Net.cpp b/src/common/Net/Net.cpp --- a/src/common/Net/Net.cpp +++ b/src/common/Net/Net.cpp @@ -1,1857 +1,1856 @@ /*hdr ** FILE: INet.cpp ** AUTHOR: Matthew Allen ** DATE: 28/5/98 ** DESCRIPTION: Internet sockets ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 #if defined(LINUX) #include #include #include #include #elif defined(MAC) #include #include #include #include #include #endif #include #include "lgi/common/File.h" #include "lgi/common/Net.h" #include "lgi/common/LgiString.h" #include "lgi/common/LgiCommon.h" #include "LgiOsClasses.h" #include "lgi/common/RegKey.h" #define USE_BSD_SOCKETS 1 #define DEBUG_CONNECT 0 #define ETIMEOUT 400 #define PROTO_UDP 0x100 #define PROTO_BROADCAST 0x200 #if defined WIN32 #include #include #include #include typedef HOSTENT HostEnt; typedef int socklen_t; typedef unsigned long in_addr_t; typedef BOOL option_t; #define LPrintSock "%I64x" #define MSG_NOSIGNAL 0 #ifndef EWOULDBLOCK #define EWOULDBLOCK WSAEWOULDBLOCK #endif #ifndef EISCONN #define EISCONN WSAEISCONN #endif #define OsAddr S_un.S_addr #elif defined POSIX #include #include #include #include #include #include #include #include #include - #include "lgi/common/LgiCommon.h" #include - + #define SOCKET_ERROR -1 #define LPrintSock "%x" typedef hostent HostEnt; typedef int option_t; #define OsAddr s_addr #endif #include "lgi/common/LgiNetInc.h" /////////////////////////////////////////////////////////////////////////////// #ifdef WIN32 static bool SocketsOpen = false; #endif bool StartNetworkStack() { #ifdef WIN32 #ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif if (!SocketsOpen) { // Start sockets WSADATA WsaData; int Result = WSAStartup(WINSOCK_VERSION, &WsaData); if (!Result) { if (WsaData.wVersion == WINSOCK_VERSION) { SocketsOpen = Result == 0; } else { WSACleanup(); return false; } } } #endif return true; } void StopNetworkStack() { #ifdef WIN32 if (SocketsOpen) { WSACleanup(); SocketsOpen = false; } #endif } #ifdef WIN32 #include "..\..\Win\INet\MibAccess.h" #include #pragma comment(lib, "iphlpapi.lib") #endif bool LSocket::EnumInterfaces(LArray &Out) { bool Status = false; StartNetworkStack(); #ifdef WIN32 #if 0 PMIB_IF_TABLE2 Tbl; SecureZeroMemory(&Tbl, sizeof(Tbl)); auto r = GetIfTable2(&Tbl); for (size_t i=0; iNumEntries; i++) { auto &e = Tbl->Table[i]; WCHAR w[256] = {0}; r = ConvertInterfaceLuidToNameW(&e.InterfaceLuid, w, 256); MIB_UNICASTIPADDRESS_ROW Addr; InitializeUnicastIpAddressEntry(&Addr); Addr.Address.si_family = AF_INET; Addr.InterfaceLuid = e.InterfaceLuid; Addr.InterfaceIndex = e.InterfaceIndex; r = GetUnicastIpAddressEntry(&Addr); if (r == NO_ERROR) { int asd=0; } } FreeMibTable(Tbl); #else MibII m; m.Init(); MibInterface Intf[16] = {0}; int Count = m.GetInterfaces(Intf, CountOf(Intf)); if (Count) { for (int i=0; iifa_next) { if (a->ifa_addr && a->ifa_addr->sa_family == AF_INET) { sockaddr_in *in = (sockaddr_in*)a->ifa_addr; sockaddr_in *mask = (sockaddr_in*)a->ifa_netmask; auto &Intf = Out.New(); Intf.Ip4 = ntohl(in->sin_addr.s_addr); Intf.Netmask4 = ntohl(mask->sin_addr.s_addr); Intf.Name = a->ifa_name; Status = true; } } freeifaddrs(addrs); } #endif return Status; } //////////////////////////////////////////////////////////////////////////////////////////////// class LSocketImplPrivate : public LCancel { public: // Data int Blocking : 1; int NoDelay : 1; int Udp : 1; int Broadcast : 1; int LogType = NET_LOG_NONE; LString LogFile; int Timeout = -1; OsSocket Socket = INVALID_SOCKET; int LastError = 0; LCancel *Cancel = NULL; LString ErrStr; LSocketImplPrivate() { Cancel = this; Blocking = true; NoDelay = false; Udp = false; Broadcast = false; } ~LSocketImplPrivate() { } bool Select(int TimeoutMs, bool Read) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = Socket; if (ValidSocket(s) && !Cancel->IsCancelled()) { struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set set; FD_ZERO(&set); FD_SET(s, &set); int ret = select( (int)s+1, Read ? &set : NULL, !Read ? &set : NULL, NULL, TimeoutMs >= 0 ? &t : NULL); if (ret > 0 && FD_ISSET(s, &set)) { return true; } } return false; } // This is the timing granularity of the SelectWithCancel loop. Ie the // number of milliseconds between checking the Cancel object. constexpr static int CancelCheckMs = 50; bool SelectWithCancel(int TimeoutMs, bool Read) { if (!Cancel) // Regular select where we just wait the whole timeout... return Select(TimeoutMs, false); // Because select can't check out 'Cancel' value during the waiting we run the select // call in a much smaller timeout and a loop so that we can respond to Cancel being set // in a timely manner. auto Now = LCurrentTime(); auto End = Now + TimeoutMs; do { // Do the cancel check... if (Cancel->IsCancelled()) break; // How many ms to wait? Now = LCurrentTime(); auto Remain = MIN(CancelCheckMs, (int)(End - Now)); if (Remain <= 0) break; if (Select(Remain, Read)) return true; } while (Now < End); return false; } }; LSocket::LSocket(LStreamI *logger, void *unused_param) { StartNetworkStack(); BytesWritten = 0; BytesRead = 0; d = new LSocketImplPrivate; } LSocket::~LSocket() { Close(); DeleteObj(d); } bool LSocket::IsOK() { return #ifndef __llvm__ this != 0 && #endif d != 0; } LCancel *LSocket::GetCancel() { return d->Cancel; } void LSocket::SetCancel(LCancel *c) { d->Cancel = c; } void LSocket::OnDisconnect() { } OsSocket LSocket::ReleaseHandle() { auto h = d->Socket; d->Socket = INVALID_SOCKET; return h; } OsSocket LSocket::Handle(OsSocket Set) { if (Set != INVALID_SOCKET) { d->Socket = Set; } return d->Socket; } bool LSocket::IsOpen() { if (ValidSocket(d->Socket) && !d->Cancel->IsCancelled()) { return true; } return false; } int LSocket::GetTimeout() { return d->Timeout; } void LSocket::SetTimeout(int ms) { d->Timeout = ms; } bool LSocket::IsReadable(int TimeoutMs) { // Assign to local var to avoid a thread changing it // on us between the validity check and the select. // Which is important because a socket value of -1 // (ie invalid) will crash the FD_SET macro. OsSocket s = d->Socket; if (ValidSocket(s) && !d->Cancel->IsCancelled()) { #ifdef LINUX // Because Linux doesn't return from select() when the socket is // closed elsewhere we have to do something different... damn Linux, // why can't you just like do the right thing? struct pollfd fds; fds.fd = s; fds.events = POLLIN | POLLRDHUP | POLLERR; fds.revents = 0; int r = poll(&fds, 1, TimeoutMs); if (r > 0) { return fds.revents != 0; } else if (r < 0) { Error(); } #else struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000}; fd_set r; FD_ZERO(&r); FD_SET(s, &r); int v = select((int)s+1, &r, 0, 0, &t); if (v > 0 && FD_ISSET(s, &r)) { return true; } else if (v < 0) { Error(); } #endif } else LgiTrace("%s:%i - Not a valid socket.\n", _FL); return false; } bool LSocket::IsWritable(int TimeoutMs) { return d->SelectWithCancel(TimeoutMs, false); } bool LSocket::CanAccept(int TimeoutMs) { return IsReadable(TimeoutMs); } bool LSocket::IsBlocking() { return d->Blocking != 0; } void LSocket::IsBlocking(bool block) { if (d->Blocking ^ block) { d->Blocking = block; #if defined WIN32 ulong NonBlocking = !block; ioctlsocket(d->Socket, FIONBIO, &NonBlocking); #elif defined POSIX fcntl(d->Socket, F_SETFL, d->Blocking ? 0 : O_NONBLOCK); #else #error Impl me. #endif } } bool LSocket::IsDelayed() { return !d->NoDelay; } void LSocket::IsDelayed(bool Delay) { bool NoDelay = !Delay; if (d->NoDelay ^ NoDelay) { d->NoDelay = NoDelay; option_t i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&i, sizeof(i)); } } int LSocket::GetLocalPort() { struct sockaddr_in addr; socklen_t size; ZeroObj(addr); size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) >= 0) { return ntohs(addr.sin_port); } return 0; } bool LSocket::GetLocalIp(char *IpAddr) { if (IpAddr) { struct sockaddr_in addr; socklen_t size; size = sizeof(addr); if ((getsockname(Handle(), (sockaddr*)&addr, &size)) < 0) return false; if (addr.sin_addr.s_addr == INADDR_ANY) return false; uchar *a = (uchar*)&addr.sin_addr.s_addr; sprintf_s( IpAddr, 16, "%i.%i.%i.%i", a[0], a[1], a[2], a[3]); return true; } return false; } bool LSocket::GetRemoteIp(uint32_t *IpAddr) { if (IpAddr) { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { *IpAddr = ntohl(a.sin_addr.s_addr); return true; } } return false; } bool LSocket::GetRemoteIp(char *IpAddr) { if (!IpAddr) return false; uint32_t Ip = 0; if (!GetRemoteIp(&Ip)) return false; sprintf_s( IpAddr, 16, "%u.%u.%u.%u", (Ip >> 24) & 0xff, (Ip >> 16) & 0xff, (Ip >> 8) & 0xff, (Ip) & 0xff); return false; } int LSocket::GetRemotePort() { struct sockaddr_in a; socklen_t addrlen = sizeof(a); if (!getpeername(Handle(), (sockaddr*)&a, &addrlen)) { return a.sin_port; } return 0; } int LSocket::Open(const char *HostAddr, int Port) { int Status = -1; Close(); if (HostAddr) { BytesWritten = 0; BytesRead = 0; sockaddr_in RemoteAddr; HostEnt *Host = 0; in_addr_t IpAddress = 0; ZeroObj(RemoteAddr); #ifdef WIN32 d->Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED); #else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); #endif if (ValidSocket(d->Socket)) { LArray Buf(512); #if !defined(MAC) option_t i; socklen_t sz = sizeof(i); int r = getsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char*)&i, &sz); if (d->NoDelay ^ i) { i = d->NoDelay != 0; setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i)); } #endif if (IsDigit(*HostAddr) && strchr(HostAddr, '.')) { // Ip address // IpAddress = inet_addr(HostAddr); if (!inet_pton(AF_INET, HostAddr, &IpAddress)) { Error(); return 0; } /* This seems complete unnecessary? -fret Dec 2018 #if defined(WIN32) Host = c((const char*) &IpAddress, 4, AF_INET); if (!Host) Error(); #else Host = gethostbyaddr ( #ifdef MAC HostAddr, #else &IpAddress, #endif 4, AF_INET ); #endif */ } else { // Name address #ifdef LINUX Host = new HostEnt; if (Host) { memset(Host, 0, sizeof(*Host)); HostEnt *Result = 0; int Err = 0; int Ret; while ( ( !GetCancel() || !GetCancel()->IsCancelled() ) && ( Ret = gethostbyname_r(HostAddr, Host, &Buf[0], Buf.Length(), &Result, &Err) ) == ERANGE ) { Buf.Length(Buf.Length() << 1); } if (Ret) { char *ErrStr = GetErrorName(Err); printf("%s:%i - gethostbyname_r('%s') returned %i, %i, %s\n", _FL, HostAddr, Ret, Err, ErrStr); DeleteObj(Host); } } #if DEBUG_CONNECT printf("%s:%i - Host=%p\n", __FILE__, __LINE__, Host); #endif #else Host = gethostbyname(HostAddr); #endif if (!Host) { Error(); Close(); return false; } } if (1) { RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons(Port); if (Host) { if (Host->h_addr_list && Host->h_addr_list[0]) { memcpy(&RemoteAddr.sin_addr, Host->h_addr_list[0], sizeof(in_addr) ); } else return false; } else { memcpy(&RemoteAddr.sin_addr, &IpAddress, sizeof(IpAddress) ); } #ifdef WIN32 if (d->Timeout < 0) { // Do blocking connect Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); } else #endif { #define CONNECT_LOGGING 0 // Setup the connect bool Block = IsBlocking(); if (Block) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Setting non blocking\n", d->Socket); #endif IsBlocking(false); } // Do initial connect to kick things off.. #if CONNECT_LOGGING LgiTrace(LPrintSock " - Doing initial connect to %s:%i\n", d->Socket, HostAddr, Port); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Initial connect=%i Block=%i\n", d->Socket, Status, Block); #endif // Wait for the connect to finish? if (Status && Block) { Error(Host); #ifdef WIN32 // yeah I know... wtf? (http://itamarst.org/writings/win32sockets.html) #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == WSAEINVAL || d->LastError == WSAEWOULDBLOCK) #else #define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == EINPROGRESS) #endif #if CONNECT_LOGGING LgiTrace(LPrintSock " - IsWouldBlock()=%i d->LastError=%i\n", d->Socket, IsWouldBlock(), d->LastError); #endif int64 End = LCurrentTime() + (d->Timeout > 0 ? d->Timeout : 30000); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket) && IsWouldBlock()) { int64 Remaining = End - LCurrentTime(); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Remaining " LPrintfInt64 "\n", d->Socket, Remaining); #endif if (Remaining < 0) { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Leaving loop\n", d->Socket); #endif break; } if (IsWritable((int)MIN(Remaining, 1000))) { // Should be ready to connect now... #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect...\n", d->Socket); #endif Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in)); #if CONNECT_LOGGING LgiTrace(LPrintSock " - Secondary connect=%i\n", d->Socket, Status); #endif if (Status != 0) { Error(Host); if (d->LastError == EISCONN #ifdef WIN32 || d->LastError == WSAEISCONN // OMG windows, really? #endif ) { Status = 0; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connect=%i Err=%i\n", d->Socket, Status, d->LastError); #endif if (IsWouldBlock()) continue; } break; } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Connected...\n", d->Socket); #endif break; } } else { #if CONNECT_LOGGING LgiTrace(LPrintSock " - Timout...\n", d->Socket); #endif } } } if (Block) IsBlocking(true); } if (!Status) { char Info[256]; sprintf_s(Info, sizeof(Info), "[INET] Socket Connect: %s [%i.%i.%i.%i], port: %i", HostAddr, (RemoteAddr.sin_addr.s_addr) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 8) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 16) & 0xFF, (RemoteAddr.sin_addr.s_addr >> 24) & 0xFF, Port); OnInformation(Info); } } if (Status) { #ifdef WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; } } else { Error(); } } return Status == 0; } bool LSocket::Bind(int Port, bool reuseAddr) { if (!ValidSocket(d->Socket)) { OnError(0, "Attempt to use invalid socket to bind."); return false; } if (reuseAddr) { int so_reuseaddr = 1; if (setsockopt(Handle(), SOL_SOCKET, SO_REUSEADDR, (const char *)&so_reuseaddr, sizeof so_reuseaddr)) OnError(0, "Attempt to set SO_REUSEADDR failed."); // This might not be fatal... so continue on. } sockaddr_in add; add.sin_family = AF_INET; add.sin_addr.s_addr = htonl(INADDR_ANY); add.sin_port = htons(Port); int ret = bind(Handle(), (sockaddr*)&add, sizeof(add)); if (ret) { Error(); } return ret == 0; } bool LSocket::Listen(int Port) { Close(); d->Socket = socket(AF_INET, SOCK_STREAM, 0); if (d->Socket >= 0) { BytesWritten = 0; BytesRead = 0; sockaddr Addr; sockaddr_in *a = (sockaddr_in*) &Addr; ZeroObj(Addr); a->sin_family = AF_INET; a->sin_port = htons(Port); a->sin_addr.OsAddr = INADDR_ANY; if (bind(d->Socket, &Addr, sizeof(Addr)) >= 0) { if (listen(d->Socket, SOMAXCONN) != SOCKET_ERROR) { return true; } else { Error(); } } else { Error(); } } else { Error(); } return false; } bool LSocket::Accept(LSocketI *c) { if (!c) { LAssert(0); return false; } OsSocket NewSocket = INVALID_SOCKET; sockaddr Address; /* int Length = sizeof(Address); NewSocket = accept(d->Socket, &Address, &Length); */ // int Loop = 0; socklen_t Length = sizeof(Address); uint64 Start = LCurrentTime(); while ( !d->Cancel->IsCancelled() && ValidSocket(d->Socket)) { if (IsReadable(100)) { NewSocket = accept(d->Socket, &Address, &Length); break; } else if (d->Timeout > 0) { uint64 Now = LCurrentTime(); if (Now - Start >= d->Timeout) { LString s; s.Printf("Accept timeout after %.1f seconds.", ((double)(Now-Start)) / 1000.0); OnInformation(s); return false; } } } if (!ValidSocket(NewSocket)) return false; return ValidSocket(c->Handle(NewSocket)); } int LSocket::Close() { if (ValidSocket(d->Socket)) { #if defined WIN32 closesocket(d->Socket); #else close(d->Socket); #endif d->Socket = INVALID_SOCKET; OnDisconnect(); } return true; } void LSocket::Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len) { if (d->LogFile) { LFile f; if (f.Open(d->LogFile, O_WRITE)) { f.Seek(f.GetSize(), SEEK_SET); switch (d->LogType) { case NET_LOG_HEX_DUMP: { char s[256]; f.Write(s, sprintf_s(s, sizeof(s), "%s = %i\r\n", Msg, (int)Ret)); for (int i=0; iSocket) || !Data || d->Cancel->IsCancelled()) return -1; int Status = 0; if (d->Timeout < 0 || IsWritable(d->Timeout)) { Status = (int)send ( d->Socket, (char*)Data, (int) Len, Flags #ifndef MAC | MSG_NOSIGNAL #endif ); } if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesWritten += Status; OnWrite((char*)Data, Status); } return Status; } ssize_t LSocket::Read(void *Data, ssize_t Len, int Flags) { if (!ValidSocket(d->Socket) || !Data || d->Cancel->IsCancelled()) return -1; ssize_t Status = -1; if (d->Timeout < 0 || IsReadable(d->Timeout)) { Status = recv(d->Socket, (char*)Data, (int) Len, Flags #ifdef MSG_NOSIGNAL | MSG_NOSIGNAL #endif ); } Log("Read", (int)Status, (char*)Data, Status>0 ? Status : 0); if (Status < 0) Error(); else if (Status == 0) OnDisconnect(); else { if (Status < Len) { // Just in case it's a string lets be safe ((char*)Data)[Status] = 0; } BytesRead += Status; OnRead((char*)Data, (int)Status); } return (int)Status; } void LSocket::OnError(int ErrorCode, const char *ErrorDescription) { d->ErrStr.Printf("Error(%i): %s", ErrorCode, ErrorDescription); } const char *LSocket::GetErrorString() { return d->ErrStr; } int LSocket::Error(void *Param) { // Get the most recent error. if (!(d->LastError = #ifdef WIN32 WSAGetLastError() #else errno #endif )) return 0; // These are not really errors... if (d->LastError == EWOULDBLOCK || d->LastError == EISCONN) return 0; static class ErrorMsg { public: int Code; const char *Msg; } ErrorCodes[] = { {0, "Socket disconnected."}, #if defined WIN32 {WSAEACCES, "Permission denied."}, {WSAEADDRINUSE, "Address already in use."}, {WSAEADDRNOTAVAIL, "Cannot assign requested address."}, {WSAEAFNOSUPPORT, "Address family not supported by protocol family."}, {WSAEALREADY, "Operation already in progress."}, {WSAECONNABORTED, "Software caused connection abort."}, {WSAECONNREFUSED, "Connection refused."}, {WSAECONNRESET, "Connection reset by peer."}, {WSAEDESTADDRREQ, "Destination address required."}, {WSAEFAULT, "Bad address."}, {WSAEHOSTDOWN, "Host is down."}, {WSAEHOSTUNREACH, "No route to host."}, {WSAEINPROGRESS, "Operation now in progress."}, {WSAEINTR, "Interrupted function call."}, {WSAEINVAL, "Invalid argument."}, {WSAEISCONN, "Socket is already connected."}, {WSAEMFILE, "Too many open files."}, {WSAEMSGSIZE, "Message too long."}, {WSAENETDOWN, "Network is down."}, {WSAENETRESET, "Network dropped connection on reset."}, {WSAENETUNREACH, "Network is unreachable."}, {WSAENOBUFS, "No buffer space available."}, {WSAENOPROTOOPT, "Bad protocol option."}, {WSAENOTCONN, "Socket is not connected."}, {WSAENOTSOCK, "Socket operation on non-socket."}, {WSAEOPNOTSUPP, "Operation not supported."}, {WSAEPFNOSUPPORT, "Protocol family not supported."}, {WSAEPROCLIM, "Too many processes."}, {WSAEPROTONOSUPPORT,"Protocol not supported."}, {WSAEPROTOTYPE, "Protocol wrong type for socket."}, {WSAESHUTDOWN, "Cannot send after socket shutdown."}, {WSAESOCKTNOSUPPORT,"Socket type not supported."}, {WSAETIMEDOUT, "Connection timed out."}, {WSAEWOULDBLOCK, "Operation would block."}, {WSAHOST_NOT_FOUND, "Host not found."}, {WSANOTINITIALISED, "Successful WSAStartup not yet performed."}, {WSANO_DATA, "Valid name, no data record of requested type."}, {WSANO_RECOVERY, "This is a non-recoverable error."}, {WSASYSNOTREADY, "Network subsystem is unavailable."}, {WSATRY_AGAIN, "Non-authoritative host not found."}, {WSAVERNOTSUPPORTED,"WINSOCK.DLL version out of range."}, {WSAEDISCON, "Graceful shutdown in progress."}, #else {EACCES, "Permission denied."}, {EADDRINUSE, "Address already in use."}, {EADDRNOTAVAIL, "Cannot assign requested address."}, {EAFNOSUPPORT, "Address family not supported by protocol family."}, {EALREADY, "Operation already in progress."}, {ECONNABORTED, "Software caused connection abort."}, {ECONNREFUSED, "Connection refused."}, {ECONNRESET, "Connection reset by peer."}, {EFAULT, "Bad address."}, {EHOSTUNREACH, "No route to host."}, {EINPROGRESS, "Operation now in progress."}, {EINTR, "Interrupted function call."}, {EINVAL, "Invalid argument."}, {EISCONN, "Socket is already connected."}, {EMFILE, "Too many open files."}, {EMSGSIZE, "Message too long."}, {ENETDOWN, "Network is down."}, {ENETRESET, "Network dropped connection on reset."}, {ENETUNREACH, "Network is unreachable."}, {ENOBUFS, "No buffer space available."}, {ENOPROTOOPT, "Bad protocol option."}, {ENOTCONN, "Socket is not connected."}, {ENOTSOCK, "Socket operation on non-socket."}, {EOPNOTSUPP, "Operation not supported."}, {EPFNOSUPPORT, "Protocol family not supported."}, {EPROTONOSUPPORT, "Protocol not supported."}, {EPROTOTYPE, "Protocol wrong type for socket."}, {ESHUTDOWN, "Cannot send after socket shutdown."}, {ETIMEDOUT, "Connection timed out."}, {EWOULDBLOCK, "Resource temporarily unavailable."}, {HOST_NOT_FOUND, "Host not found."}, {NO_DATA, "Valid name, no data record of requested type."}, {NO_RECOVERY, "This is a non-recoverable error."}, {TRY_AGAIN, "Non-authoritative host not found."}, {ETIMEOUT, "Operation timed out."}, {EDESTADDRREQ, "Destination address required."}, {EHOSTDOWN, "Host is down."}, #ifndef HAIKU {ESOCKTNOSUPPORT, "Socket type not supported."}, #endif #endif {-1, 0} }; ErrorMsg *Error = ErrorCodes; while (Error->Code >= 0 && Error->Code != d->LastError) { Error++; } if (d->LastError == 10060 && Param) { HostEnt *He = (HostEnt*)Param; char s[256]; sprintf_s(s, sizeof(s), "%s (gethostbyname returned '%s')", Error->Msg, He->h_name); OnError(d->LastError, s); } else #if defined(MAC) if (d->LastError != 36) #endif { OnError(d->LastError, (Error->Code >= 0) ? Error->Msg : ""); } switch (d->LastError) { case 0: #ifdef WIN32 case 183: // I think this is a XP 'disconnect' ??? case WSAECONNABORTED: case WSAECONNRESET: case WSAENETRESET: #else case ECONNABORTED: case ECONNRESET: case ENETRESET: #endif { Close(); break; } } return d->LastError; } bool LSocket::GetUdp() { return d->Udp != 0; } void LSocket::SetUdp(bool isUdp) { if (d->Udp ^ isUdp) { d->Udp = isUdp; if (!ValidSocket(d->Socket)) { if (d->Udp) d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); else d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } if (d->Broadcast) { option_t enabled = d->Broadcast != 0; setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); } } } void LSocket::SetBroadcast(bool isBroadcast) { d->Broadcast = isBroadcast; } bool LSocket::AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface) { if (!MulticastIp) return false; struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = htonl(MulticastIp); // your multicast address mreq.imr_interface.s_addr = htonl(LocalInterface); // your incoming interface IP int r = setsockopt(Handle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*) &mreq, sizeof(mreq)); if (!r) { LgiTrace("AddMulticastMember(%s, %s)\n", LIpToStr(MulticastIp).Get(), LIpToStr(LocalInterface).Get()); return true; } Error(); return false; } bool LSocket::SetMulticastInterface(uint32_t Interface) { if (!Interface) return false; struct sockaddr_in addr; addr.sin_addr.s_addr = Interface; auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (const char*) &addr, sizeof(addr)); if (!r) return true; Error(); return false; } bool LSocket::CreateUdpSocket() { if (!ValidSocket(d->Socket)) { d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (ValidSocket(d->Socket)) { if (d->Broadcast) { option_t enabled = d->Broadcast != 0; auto r = setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled)); if (r) Error(); } } } return ValidSocket(d->Socket); } int LSocket::ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip, uint16_t *Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; socklen_t AddrSize = sizeof(a); ZeroObj(a); a.sin_family = AF_INET; if (Port) a.sin_port = htons(*Port); #if defined(WINDOWS) a.sin_addr.S_un.S_addr = INADDR_ANY; #else a.sin_addr.s_addr = INADDR_ANY; #endif auto b = recvfrom(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, &AddrSize); if (b > 0) { OnRead((char*)Buffer, (int)b); if (Ip) *Ip = ntohl(a.sin_addr.OsAddr); if (Port) *Port = ntohs(a.sin_port); } return (int)b; } int LSocket::WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port) { if (!Buffer || Size < 0) return -1; CreateUdpSocket(); sockaddr_in a; ZeroObj(a); a.sin_family = AF_INET; a.sin_port = htons(Port); a.sin_addr.OsAddr = htonl(Ip); ssize_t b = sendto(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, sizeof(a)); if (b > 0) { OnWrite((char*)Buffer, (int)b); } else { printf("%s:%i - sendto failed with %i.\n", _FL, errno); } return (int)b; } ////////////////////////////////////////////////////////////////////////////// bool HaveNetConnection() { bool Status = false; // Check for dial up connection #if defined WIN32 typedef DWORD (__stdcall *RasEnumConnections_Proc)(LPRASCONN lprasconn, LPDWORD lpcb, LPDWORD lpcConnections); typedef DWORD (__stdcall *RasGetConnectStatus_Proc)(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus); HMODULE hRas = (HMODULE) LoadLibraryA("rasapi32.dll"); if (hRas) { RASCONN Con[10]; DWORD Connections = 0; DWORD Bytes = sizeof(Con); ZeroObj(Con); Con[0].dwSize = sizeof(Con[0]); RasEnumConnections_Proc pRasEnumConnections = (RasEnumConnections_Proc) GetProcAddress(hRas, "RasEnumConnectionsA"); RasGetConnectStatus_Proc pRasGetConnectStatus = (RasGetConnectStatus_Proc) GetProcAddress(hRas, "RasGetConnectStatusA"); if (pRasEnumConnections && pRasGetConnectStatus) { pRasEnumConnections(Con, &Bytes, &Connections); for (unsigned i=0; i>24)&0xff, (ip>>16)&0xff, (ip>>8)&0xff, (ip)&0xff); return s; } uint32_t LIpToInt(LString str) { auto p = str.Split("."); if (p.Length() != 4) return 0; uint32_t ip = 0; for (auto &s : p) { ip <<= 8; auto n = s.Int(); if (n > 255) { LAssert(0); return 0; } ip |= (uint8_t)s.Int(); } return ip; } uint32_t LHostnameToIp(const char *Host) { if (!Host) return 0; // struct addrinfo hints = {}; struct addrinfo *res = NULL; - auto result = getaddrinfo(Host, NULL, NULL, &res); + getaddrinfo(Host, NULL, NULL, &res); if (!res) return 0; uint32_t ip = 0; if (res->ai_addr) { auto fam = res->ai_addr->sa_family; if (fam == AF_INET) { auto a = (sockaddr_in*)res->ai_addr; ip = ntohl(a->sin_addr.s_addr); } } freeaddrinfo(res); return ip; } bool WhatsMyIp(LAutoString &Ip) { bool Status = false; StartNetworkStack(); #if defined WIN32 char hostname[256]; HostEnt *e = NULL; if (gethostname(hostname, sizeof(hostname)) != SOCKET_ERROR) { e = gethostbyname(hostname); } if (e) { int Which = 0; for (; e->h_addr_list[Which]; Which++); Which--; char IpAddr[32]; sprintf_s(IpAddr, sizeof(IpAddr), "%i.%i.%i.%i", (uchar)e->h_addr_list[Which][0], (uchar)e->h_addr_list[Which][1], (uchar)e->h_addr_list[Which][2], (uchar)e->h_addr_list[Which][3]); Status = Ip.Reset(NewStr(IpAddr)); } #endif return Status; } ////////////////////////////////////////////////////////////////////// #define SOCKS5_VER 5 #define SOCKS5_CMD_CONNECT 1 #define SOCKS5_CMD_BIND 2 #define SOCKS5_CMD_ASSOCIATE 3 #define SOCKS5_ADDR_IPV4 1 #define SOCKS5_ADDR_DOMAIN 3 #define SOCKS5_ADDR_IPV6 4 #define SOCKS5_AUTH_NONE 0 #define SOCKS5_AUTH_GSSAPI 1 #define SOCKS5_AUTH_USER_PASS 2 LSocks5Socket::LSocks5Socket() { Socks5Connected = false; } void LSocks5Socket::SetProxy(const LSocks5Socket *s) { Proxy.Reset(s ? NewStr(s->Proxy) : 0); Port = s ? s->Port : 0; UserName.Reset(s ? NewStr(s->UserName) : 0); Password.Reset(s ? NewStr(s->Password) : 0); } void LSocks5Socket::SetProxy(char *proxy, int port, char *username, char *password) { Proxy.Reset(NewStr(proxy)); Port = port; UserName.Reset(NewStr(username)); Password.Reset(NewStr(password)); } int LSocks5Socket::Open(const char *HostAddr, int port) { bool Status = false; if (HostAddr) { char Msg[256]; sprintf_s(Msg, sizeof(Msg), "[SOCKS5] Connecting to proxy server '%s'", HostAddr); OnInformation(Msg); Status = LSocket::Open(Proxy, Port) != 0; if (Status) { char Buf[1024]; Buf[0] = SOCKS5_VER; Buf[1] = 2; // methods Buf[2] = SOCKS5_AUTH_NONE; Buf[3] = SOCKS5_AUTH_USER_PASS; // No idea how to implement this. // AuthReq[3] = SOCKS5_AUTH_GSSAPI; OnInformation("[SOCKS5] Connected, Requesting authentication type."); LSocket::Write(Buf, 4, 0); if (LSocket::Read(Buf, 2, 0) == 2) { if (Buf[0] == SOCKS5_VER) { bool Authenticated = false; switch (Buf[1]) { case SOCKS5_AUTH_NONE: { Authenticated = true; OnInformation("[SOCKS5] No authentication needed."); break; } case SOCKS5_AUTH_USER_PASS: { OnInformation("[SOCKS5] User/Pass authentication needed."); if (UserName && Password) { char *b = Buf; *b++ = 1; // ver of sub-negotiation ?? size_t NameLen = strlen(UserName); LAssert(NameLen < 0x80); *b++ = (char)NameLen; b += sprintf_s(b, NameLen+1, "%s", UserName.Get()); size_t PassLen = strlen(Password); LAssert(PassLen < 0x80); *b++ = (char)PassLen; b += sprintf_s(b, PassLen+1, "%s", Password.Get()); LSocket::Write(Buf, (int)(3 + NameLen + PassLen)); if (LSocket::Read(Buf, 2, 0) == 2) { Authenticated = (Buf[0] == 1 && Buf[1] == 0); } if (!Authenticated) { OnInformation("[SOCKS5] User/Pass authentication failed."); } } break; } } if (Authenticated) { OnInformation("[SOCKS5] Authentication successful."); int HostPort = htons(port); // Header char *b = Buf; *b++ = SOCKS5_VER; *b++ = SOCKS5_CMD_CONNECT; *b++ = 0; // reserved long IpAddr = inet_addr(HostAddr); if (IpAddr != -1) { // Ip *b++ = SOCKS5_ADDR_IPV4; memcpy(b, &IpAddr, 4); b += 4; } else { // Domain Name *b++ = SOCKS5_ADDR_DOMAIN; size_t Len = strlen(HostAddr); LAssert(Len < 0x80); *b++ = (char)Len; strcpy_s(b, Buf+sizeof(Buf)-b, HostAddr); b += Len; } // Port memcpy(b, &HostPort, 2); b += 2; LSocket::Write(Buf, (int)(b - Buf), 0); if (LSocket::Read(Buf, 10, 0) == 10) { if (Buf[0] == SOCKS5_VER) { switch (Buf[1]) { case 0: Socks5Connected = true; OnInformation("[SOCKS5] Connected!"); break; case 1: OnInformation("[SOCKS5] General SOCKS server failure"); break; case 2: OnInformation("[SOCKS5] Connection not allowed by ruleset"); break; case 3: OnInformation("[SOCKS5] Network unreachable"); break; case 4: OnInformation("[SOCKS5] Host unreachable"); break; case 5: OnInformation("[SOCKS5] Connection refused"); break; case 6: OnInformation("[SOCKS5] TTL expired"); break; case 7: OnInformation("[SOCKS5] Command not supported"); break; case 8: OnInformation("[SOCKS5] Address type not supported"); break; default: OnInformation("[SOCKS5] Unknown SOCKS server failure"); break; } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Connection request read failed."); } } else { OnInformation("[SOCKS5] Not authenticated."); } } else { OnInformation("[SOCKS5] Wrong socks version."); } } else { OnInformation("[SOCKS5] Authentication type read failed."); } Status = Socks5Connected; if (!Status) { LSocket::Close(); OnInformation("[SOCKS5] Failure: Disconnecting."); } } } return Status; } diff --git a/src/common/Text/DeEscape.cpp b/src/common/Text/DeEscape.cpp --- a/src/common/Text/DeEscape.cpp +++ b/src/common/Text/DeEscape.cpp @@ -1,137 +1,132 @@ #include "lgi/common/Lgi.h" #include "lgi/common/DeEscape.h" char *SkipEscape(char *c) { if (*c != 0x1b) { LAssert(!"Not an escape seq."); return NULL; } c++; if (*c == ']') { // OS cmd c++; while (*c >= '0' && *c <= '9') c++; if (*c == ';') c++; return c; } if ( (*c >= '@' && *c <= 'Z') || (*c >= '\\' && *c <= '_') ) return c + 1; if (*c != '[') { LgiTrace("%s:%i - Error: Expecting bracket.\n", _FL); return c; } c++; while (*c >= '0' && *c <= '?') c++; while (*c >= ' ' && *c <= '/') c++; if (*c >= '@' && *c <= '~') c++; return c; } bool IsNewLineEscape(char *s, char *e) { auto ch = e - s; if (e[-1] == 'H' && ch > 3) return true; return false; } bool IsOsCmd(char *s, char *e) { if (s[1] == ']') return true; return false; } void DeEscape(LString &s) { if (!s.Length()) return; char *c = s, *out = s, *end = s.Get() + s.Length(); bool echo = true; while (c < end) { if (*c == 0x1b) { auto e = SkipEscape(c); if (!e) break; echo = !IsOsCmd(c, e); if (IsNewLineEscape(c, e) && *e != '\n') *out++ = '\n'; c = e; } else if (*c == 7) { - /* - // Delete last line? - auto start = s.Get(); - while (out > start && out[-1] != '\n') - out--; - */ c++; // skip BEL char + echo = true; } else if (echo) *out++ = *c++; else c++; } size_t new_len = out - s.Get(); if (new_len < s.Length()) s.Length(new_len); } void DeEscape(char *s, ssize_t *bytes) { char *c = s, *out = s; bool echo = true; while (*c) { if (*c == 0x1b) { auto e = SkipEscape(c); if (!e) goto OnError; echo = !IsOsCmd(c, e); if (IsNewLineEscape(c, e)) *out++ = '\n'; c = e; } else if (*c == 7) { /* // Delete last line? auto start = s; while (out > start && out[-1] != '\n') out--; */ c++; // Don't output bell } else if (echo) *out++ = *c++; else c++; } OnError: *out = 0; if (bytes) *bytes = out-s; } diff --git a/src/common/Text/Html.cpp b/src/common/Text/Html.cpp --- a/src/common/Text/Html.cpp +++ b/src/common/Text/Html.cpp @@ -1,9469 +1,9482 @@ #include #include #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Html.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Variant.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Unicode.h" #include "lgi/common/Emoji.h" #include "lgi/common/ClipBoard.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/GdcTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/Path.h" #include "lgi/common/CssTools.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Net.h" #include "lgi/common/Base64.h" #include "lgi/common/Menu.h" #include "lgi/common/FindReplaceDlg.h" #include "lgi/common/Homoglyphs.h" #include "lgi/common/Charset.h" #include "HtmlPriv.h" #define DEBUG_TABLE_LAYOUT 1 #define DEBUG_DRAW_TD 0 #define DEBUG_RESTYLE 0 #define DEBUG_TAG_BY_POS 0 #define DEBUG_SELECTION 0 #define DEBUG_TEXT_AREA 0 #define ENABLE_IMAGE_RESIZING 1 #define DOCUMENT_LOAD_IMAGES 1 #define MAX_RECURSION_DEPTH 300 #define ALLOW_TABLE_GROWTH 1 #define LGI_HTML_MAXPAINT_TIME 350 // ms #define FLOAT_TOLERANCE 0.001 #define CRASH_TRACE 0 #ifdef MAC #define HTML_USE_DOUBLE_BUFFER 0 #else #define HTML_USE_DOUBLE_BUFFER 1 #endif #define GT_TRANSPARENT 0x00000000 #ifndef IDC_HAND #define IDC_HAND MAKEINTRESOURCE(32649) #endif #undef CellSpacing #define DefaultCellSpacing 0 #define DefaultCellPadding 1 #ifdef MAC #define MinimumPointSize 9 #define MinimumBodyFontSize 12 #else #define MinimumPointSize 8 #define MinimumBodyFontSize 11 #endif // #define DefaultFont "font-family: Times; font-size: 16pt;" #define DefaultBodyMargin "5px" #define DefaultImgSize 16 #define DefaultMissingCellColour GT_TRANSPARENT // Rgb32(0xf0,0xf0,0xf0) #define ShowNbsp 0 #define FontPxHeight(fnt) (fnt->GetHeight() - (int)(fnt->Leading() + 0.5)) #if 0 // def _DEBUG #define DefaultTableBorder Rgb32(0xf8, 0xf8, 0xf8) #else #define DefaultTableBorder GT_TRANSPARENT #endif #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DEBUG_LOG(...) if (Table->Debug) LgiTrace(__VA_ARGS__) #else #define DEBUG_LOG(...) #endif #define IsTableCell(id) ( ((id) == TAG_TD) || ((id) == TAG_TH) ) #define IsTableTag() (TagId == TAG_TABLE || TagId == TAG_TR || TagId == TAG_TD || TagId == TAG_TH) #define GetCssLen(a, b) a().Type == LCss::LenInherit ? b() : a() static char WordDelim[] = ".,<>/?[]{}()*&^%$#@!+|\'\""; static char16 WhiteW[] = {' ', '\t', '\r', '\n', 0}; #if 0 static char DefaultCss[] = { "a { color: blue; text-decoration: underline; }" "body { margin: 8px; }" "strong { font-weight: bolder; }" "pre { font-family: monospace }" "h1 { font-size: 2em; margin: .67em 0px; }" "h2 { font-size: 1.5em; margin: .75em 0px; }" "h3 { font-size: 1.17em; margin: .83em 0px; }" "h4, p," "blockquote, ul," "fieldset, form," "ol, dl, dir," "menu { margin: 1.12em 0px; }" "h5 { font-size: .83em; margin: 1.5em 0px; }" "h6 { font-size: .75em; margin: 1.67em 0px; }" "strike, del { text-decoration: line-through; }" "hr { border: 1px inset; }" "center { text-align: center; }" "h1, h2, h3, h4," "h5, h6, b," "strong { font-weight: bolder; }" }; #endif template void RemoveChars(T *str, T *remove_list) { T *i = str, *o = str, *c; while (*i) { for (c = remove_list; *c; c++) { if (*c == *i) break; } if (*c == 0) *o++ = *i; i++; } *o++ = NULL; } ////////////////////////////////////////////////////////////////////// using namespace Html1; namespace Html1 { class LHtmlPrivate { public: LHashTbl, LTag*> Loading; LHtmlStaticInst Inst; bool CursorVis; LRect CursorPos; LPoint Content; bool WordSelectMode; bool LinkDoubleClick; LAutoString OnLoadAnchor; bool DecodeEmoji; LAutoString EmojiImg; int NextCtrlId; uint64 SetScrollTime; int DeferredLoads; bool IsParsing; bool IsLoaded; bool StyleDirty; // Paint time limits... bool MaxPaintTimeout = false; int MaxPaintTime = LGI_HTML_MAXPAINT_TIME; // Find settings LAutoWString FindText; bool MatchCase; LHtmlPrivate() { IsLoaded = false; StyleDirty = false; IsParsing = false; LinkDoubleClick = true; WordSelectMode = false; NextCtrlId = 2000; SetScrollTime = 0; CursorVis = false; CursorPos.ZOff(-1, -1); DeferredLoads = 0; char EmojiPng[MAX_PATH_LEN]; #ifdef MAC LMakePath(EmojiPng, sizeof(EmojiPng), LGetExeFile(), "Contents/Resources/Emoji.png"); #else LGetSystemPath(LSP_APP_INSTALL, EmojiPng, sizeof(EmojiPng)); LMakePath(EmojiPng, sizeof(EmojiPng), EmojiPng, "resources/emoji.png"); #endif if (LFileExists(EmojiPng)) { DecodeEmoji = true; EmojiImg.Reset(NewStr(EmojiPng)); } else DecodeEmoji = false; } ~LHtmlPrivate() { } }; class InputButton : public LButton { LTag *Tag; public: InputButton(LTag *tag, int Id, const char *Label) : LButton(Id, 0, 0, -1, -1, Label) { Tag = tag; } void OnClick(const LMouse &m) { Tag->OnClick(m); } }; class LFontCache { LHtml *Owner; List Fonts; public: LFontCache(LHtml *owner) { Owner = owner; } ~LFontCache() { Fonts.DeleteObjects(); } LFont *FontAt(int i) { return Fonts.ItemAt(i); } LFont *FindMatch(LFont *m) { for (auto f: Fonts) { if (*f == *m) { return f; } } return 0; } LFont *GetFont(LCss *Style) { if (!Style) return NULL; LFont *Default = Owner->GetFont(); LCss::StringsDef Face = Style->FontFamily(); if (Face.Length() < 1 || !ValidStr(Face[0])) { Face.Empty(); const char *DefFace = Default->Face(); LAssert(ValidStr(DefFace)); Face.Add(NewStr(DefFace)); } LAssert(ValidStr(Face[0])); LCss::Len Size = Style->FontSize(); LCss::FontWeightType Weight = Style->FontWeight(); bool IsBold = Weight == LCss::FontWeightBold || Weight == LCss::FontWeightBolder || Weight > LCss::FontWeight400; bool IsItalic = Style->FontStyle() == LCss::FontStyleItalic; bool IsUnderline = Style->TextDecoration() == LCss::TextDecorUnderline; if (Size.Type == LCss::LenInherit || Size.Type == LCss::LenNormal) { Size.Type = LCss::LenPt; Size.Value = (float)Default->PointSize(); } auto Scale = Owner->GetDpiScale(); if (Size.Type == LCss::LenPx) { Size.Value *= (float) Scale.y; int RequestPx = (int) Size.Value; // Look for cached fonts of the right size... for (auto f: Fonts) { if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { int Px = FontPxHeight(f); int Diff = Px - RequestPx; if (Diff >= 0 && Diff <= 2) return f; } } } else if (Size.Type == LCss::LenPt) { double Pt = Size.Value; for (auto f: Fonts) { if (!f->Face() || Face.Length() == 0) { LAssert(0); break; } auto FntSz = f->Size(); if (f->Face() && _stricmp(f->Face(), Face[0]) == 0 && FntSz.Type == LCss::LenPt && std::abs(FntSz.Value - Pt) < FLOAT_TOLERANCE && f->Bold() == IsBold && f->Italic() == IsItalic && f->Underline() == IsUnderline) { // Return cached font return f; } } } else if (Size.Type == LCss::LenPercent) { // Most of the percentages will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize() / 100.0f; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::LenEm) { // Most of the relative sizes will be resolved in the "Apply" stage // of the CSS calculations, any that appear here have no "font-size" // in their parent tree, so we just use the default font size times // the requested percent Size.Type = LCss::LenPt; Size.Value *= Default->PointSize(); if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeXXSmall || Size.Type == LCss::SizeXSmall || Size.Type == LCss::SizeSmall || Size.Type == LCss::SizeMedium || Size.Type == LCss::SizeLarge || Size.Type == LCss::SizeXLarge || Size.Type == LCss::SizeXXLarge) { int Idx = Size.Type-LCss::SizeXXSmall; LAssert(Idx >= 0 && Idx < CountOf(LCss::FontSizeTable)); Size.Type = LCss::LenPt; Size.Value = Default->PointSize() * LCss::FontSizeTable[Idx]; if (Size.Value < MinimumPointSize) Size.Value = MinimumPointSize; } else if (Size.Type == LCss::SizeSmaller) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() - 1); } else if (Size.Type == LCss::SizeLarger) { Size.Type = LCss::LenPt; Size.Value = (float)(Default->PointSize() + 1); } else LAssert(!"Not impl."); LFont *f; if ((f = new LFont)) { auto ff = ValidStr(Face[0]) ? Face[0] : Default->Face(); f->Face(ff); f->Size(Size.IsValid() ? Size : Default->Size()); f->Bold(IsBold); f->Italic(IsItalic); f->Underline(IsUnderline); // printf("Add cache font %s,%i %i,%i,%i\n", f->Face(), f->PointSize(), f->Bold(), f->Italic(), f->Underline()); if (std::abs(Size.Value) < FLOAT_TOLERANCE) ; else if (!f->Create((char*)0, 0)) { // Broken font... f->Face(Default->Face()); LFont *DefMatch = FindMatch(f); // printf("Falling back to default face for '%s:%i', DefMatch=%p\n", ff, f->PointSize(), DefMatch); if (DefMatch) { DeleteObj(f); return DefMatch; } else { if (!f->Create((char*)0, 0)) { DeleteObj(f); return Fonts[0]; } } } // Not already cached Fonts.Insert(f); if (!f->Face()) { LAssert(0); } return f; } return 0; } }; class LFlowRegion { LCss::LengthType Align = LCss::LenInherit; List Line; // These pointers aren't owned by the flow region // When the line is finish, all the tag regions // will need to be vertically aligned struct LFlowStack { int LeftAbs; int RightAbs; int TopAbs; }; LArray Stack; public: LHtml *Html; int x1, x2; // Left and right margins int y1; // Current y position int y2; // Maximum used y position int cx; // Current insertion point int my; // How much of the area above y2 was just margin LPoint MAX; // Max dimensions int Inline; int InBody; LFlowRegion(LHtml *html, bool inbody) { Html = html; x1 = x2 = y1 = y2 = cx = my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LHtml *html, LRect r, bool inbody) { Html = html; MAX.x = cx = x1 = r.x1; MAX.y = y1 = y2 = r.y1; x2 = r.x2; my = 0; Inline = 0; InBody = inbody; } LFlowRegion(LFlowRegion &r) { Html = r.Html; x1 = r.x1; x2 = r.x2; y1 = r.y1; MAX.x = cx = r.cx; MAX.y = y2 = r.y2; my = r.my; Inline = r.Inline; InBody = r.InBody; } LString ToString() { LString s; s.Printf("Flow: x=%i(%i)%i y=%i,%i my=%i inline=%i", x1, cx, x2, y1, y2, my, Inline); return s; } int X() { return x2 - cx; } void X(int newx) { x2 = x1 + newx - 1; } int Width() { return x2 - x1 + 1; } LFlowRegion &operator +=(LRect r) { x1 += r.x1; cx += r.x1; x2 -= r.x2; y1 += r.y1; y2 += r.y1; return *this; } LFlowRegion &operator -=(LRect r) { x1 -= r.x1; cx -= r.x1; x2 += r.x2; y1 += r.y2; y2 += r.y2; return *this; } void AlignText(); void FinishLine(bool Margin = false); void EndBlock(); void Insert(LFlowRect *Tr, LCss::LengthType Align); LRect *LineBounds(); void Indent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Left.IsValid() ? ResolveX(Left, Tag, IsMargin) : 0; Fs.RightAbs = Right.IsValid() ? ResolveX(Right, Tag, IsMargin) : 0; Fs.TopAbs = Top.IsValid() ? ResolveY(Top, Tag, IsMargin) : 0; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Indent(LRect &Px, bool IsMargin) { LFlowRegion This(*this); LFlowStack &Fs = Stack.New(); Fs.LeftAbs = Px.x1; Fs.RightAbs = Px.x2; Fs.TopAbs = Px.y1; x1 += Fs.LeftAbs; cx += Fs.LeftAbs; x2 -= Fs.RightAbs; y1 += Fs.TopAbs; y2 += Fs.TopAbs; if (IsMargin) my += Fs.TopAbs; } void Outdent(LRect &Px, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int &BottomAbs = Px.y2; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } void Outdent(LTag *Tag, LCss::Len Left, LCss::Len Top, LCss::Len Right, LCss::Len Bottom, bool IsMargin) { LFlowRegion This = *this; ssize_t len = Stack.Length(); if (len > 0) { LFlowStack &Fs = Stack[len-1]; int BottomAbs = Bottom.IsValid() ? ResolveY(Bottom, Tag, IsMargin) : 0; x1 -= Fs.LeftAbs; cx -= Fs.LeftAbs; x2 += Fs.RightAbs; y2 += BottomAbs; if (IsMargin) my += BottomAbs; Stack.Length(len-1); } else LAssert(!"Nothing to pop."); } int ResolveX(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { default: case LCss::LenInherit: return IsMargin ? 0 : X(); case LCss::LenPx: // return MIN((int)l.Value, X()); return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().x / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().x / 2.54); case LCss::LenEm: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int)(l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { LAssert(!"No font?"); f = LSysFont; } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { int my_x = X(); int px = (int) (l.Value * my_x / 100.0); return px; } case LCss::LenAuto: { if (IsMargin) return 0; else return X(); break; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } } return 0; } bool LimitX(int &x, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; if (Min.IsValid()) { int Px = Min.ToPx(x2 - x1 + 1, f, false); if (Px > x) { x = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(x2 - x1 + 1, f, false); if (Px < x) { x = Px; Limited = true; } } return Limited; } int ResolveY(LCss::Len l, LTag *t, bool IsMargin) { LFont *f = t->GetFont(); switch (l.Type) { case LCss::LenInherit: case LCss::LenAuto: case LCss::LenNormal: case LCss::LenPx: return (int)l.Value; case LCss::LenPt: return (int) (l.Value * LScreenDpi().y / 72.0); case LCss::LenCm: return (int) (l.Value * LScreenDpi().y / 2.54); case LCss::LenEm: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight()); } case LCss::LenEx: { if (!f) { f = LSysFont; LAssert(!"No font"); } return (int) (l.Value * f->GetHeight() / 2); // More haha, who uses 'ex' anyway? } case LCss::LenPercent: { // Walk up tree of tags to find an absolute size... LCss::Len Ab; for (LTag *p = ToTag(t->Parent); p; p = ToTag(p->Parent)) { auto h = p->Height(); if (h.IsValid() && !h.IsDynamic()) { Ab = h; break; } } if (!Ab.IsValid()) { LAssert(Html != NULL); Ab.Type = LCss::LenPx; Ab.Value = (float)Html->Y(); } LCss::Len m = Ab * l; return (int)m.ToPx(0, f);; } case LCss::SizeSmall: { return 1; // px } case LCss::SizeMedium: { return 2; // px } case LCss::SizeLarge: { return 3; // px } case LCss::AlignLeft: case LCss::AlignRight: case LCss::AlignCenter: case LCss::AlignJustify: case LCss::VerticalBaseline: case LCss::VerticalSub: case LCss::VerticalSuper: case LCss::VerticalTop: case LCss::VerticalTextTop: case LCss::VerticalMiddle: case LCss::VerticalBottom: case LCss::VerticalTextBottom: { // Meaningless in this context break; } default: { LAssert(!"Not supported."); break; } } return 0; } bool LimitY(int &y, LCss::Len Min, LCss::Len Max, LFont *f) { bool Limited = false; int TotalY = Html ? Html->Y() : 0; if (Min.IsValid()) { int Px = Min.ToPx(TotalY, f, false); if (Px > y) { y = Px; Limited = true; } } if (Max.IsValid()) { int Px = Max.ToPx(TotalY, f, false); if (Px < y) { y = Px; Limited = true; } } return Limited; } LRect ResolveMargin(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->MarginLeft(), Tag, true); r.y1 = ResolveY(Src->MarginTop(), Tag, true); r.x2 = ResolveX(Src->MarginRight(), Tag, true); r.y2 = ResolveY(Src->MarginBottom(), Tag, true); return r; } LRect ResolveBorder(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->BorderLeft(), Tag, true); r.y1 = ResolveY(Src->BorderTop(), Tag, true); r.x2 = ResolveX(Src->BorderRight(), Tag, true); r.y2 = ResolveY(Src->BorderBottom(), Tag, true); return r; } LRect ResolvePadding(LCss *Src, LTag *Tag) { LRect r; r.x1 = ResolveX(Src->PaddingLeft(), Tag, true); r.y1 = ResolveY(Src->PaddingTop(), Tag, true); r.x2 = ResolveX(Src->PaddingRight(), Tag, true); r.y2 = ResolveY(Src->PaddingBottom(), Tag, true); return r; } }; }; ////////////////////////////////////////////////////////////////////// static bool ParseDistance(char *s, float &d, char *units = 0) { if (!s) return false; while (*s && IsWhiteSpace(*s)) s++; if (!IsDigit(*s) && !strchr("-.", *s)) return false; d = (float)atof(s); while (*s && (IsDigit(*s) || strchr("-.", *s))) s++; while (*s && IsWhiteSpace(*s)) s++; char _units[128]; char *o = units = units ? units : _units; while (*s && (IsAlpha(*s) || *s == '%')) { *o++ = *s++; } *o++ = 0; return true; } LHtmlLength::LHtmlLength() { d = 0; PrevAbs = 0; u = LCss::LenInherit; } LHtmlLength::LHtmlLength(char *s) { Set(s); } bool LHtmlLength::IsValid() { return u != LCss::LenInherit; } bool LHtmlLength::IsDynamic() { return u == LCss::LenPercent || d == 0.0; } LHtmlLength::operator float () { return d; } LHtmlLength &LHtmlLength::operator =(float val) { d = val; u = LCss::LenPx; return *this; } LCss::LengthType LHtmlLength::GetUnits() { return u; } void LHtmlLength::Set(char *s) { if (ValidStr(s)) { char Units[256] = ""; if (ParseDistance(s, d, Units)) { if (Units[0]) { if (strchr(Units, '%')) { u = LCss::LenPercent; } else if (stristr(Units, "pt")) { u = LCss::LenPt; } else if (stristr(Units, "em")) { u = LCss::LenEm; } else if (stristr(Units, "ex")) { u = LCss::LenEx; } else { u = LCss::LenPx; } } else { u = LCss::LenPx; } } } } float LHtmlLength::Get(LFlowRegion *Flow, LFont *Font, bool Lock) { switch (u) { default: break; case LCss::LenEm: { return PrevAbs = d * (Font ? Font->GetHeight() : 14); break; } case LCss::LenEx: { return PrevAbs = (Font ? Font->GetHeight() * d : 14) / 2; break; } case LCss::LenPercent: { if (Lock || PrevAbs == 0.0) { return PrevAbs = (Flow->X() * d / 100); } else { return PrevAbs; } break; } } float FlowX = Flow ? Flow->X() : d; return PrevAbs = MIN(FlowX, d); } LHtmlLine::LHtmlLine() { LineStyle = -1; LineReset = 0x80000000; } LHtmlLine::~LHtmlLine() { } LHtmlLine &LHtmlLine::operator =(int i) { d = (float)i; return *this; } void LHtmlLine::Set(char *s) { LToken t(s, " \t"); LineReset = 0x80000000; LineStyle = -1; char *Style = 0; for (unsigned i=0; iColourMap.Find(c) ) { LHtmlParser::ParseColour(c, Colour); } else if (_strnicmp(c, "rgb(", 4) == 0) { char Buf[256]; strcpy_s(Buf, sizeof(Buf), c); while (!strchr(c, ')') && (c = t[++i])) { strcat(Buf, c); } LHtmlParser::ParseColour(Buf, Colour); } else if (IsDigit(*c)) { LHtmlLength::Set(c); } else if (_stricmp(c, "none") == 0) { Style = 0; } else if ( _stricmp(c, "dotted") == 0 || _stricmp(c, "dashed") == 0 || _stricmp(c, "solid") == 0 || _stricmp(c, "float") == 0 || _stricmp(c, "groove") == 0 || _stricmp(c, "ridge") == 0 || _stricmp(c, "inset") == 0 || _stricmp(c, "outse") == 0) { Style = c; } else { // ??? } } if (Style && _stricmp(Style, "dotted") == 0) { switch ((int)d) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } } ////////////////////////////////////////////////////////////////////// LRect LTag::GetRect(bool Client) { LRect r(Pos.x, Pos.y, Pos.x + Size.x - 1, Pos.y + Size.y - 1); if (!Client) { for (LTag *p = ToTag(Parent); p; p=ToTag(p->Parent)) { r.Offset(p->Pos.x, p->Pos.y); } } return r; } LCss::LengthType LTag::GetAlign(bool x) { for (LTag *t = this; t; t = ToTag(t->Parent)) { LCss::Len l; if (x) { if (IsTableCell(TagId) && Cell && Cell->XAlign) l.Type = Cell->XAlign; else l = t->TextAlign(); } else { l = t->VerticalAlign(); } if (l.Type != LenInherit) { return l.Type; } if (t->TagId == TAG_TABLE) break; } return LenInherit; } ////////////////////////////////////////////////////////////////////// void LFlowRegion::EndBlock() { if (cx > x1) FinishLine(); } void LFlowRegion::AlignText() { if (Align != LCss::AlignLeft) { int Used = 0; for (auto l : Line) Used += l->X(); int Total = x2 - x1 + 1; if (Used < Total) { int Offset = 0; if (Align == LCss::AlignCenter) Offset = (Total - Used) / 2; else if (Align == LCss::AlignRight) Offset = Total - Used; if (Offset) for (auto l : Line) { if (l->Tag->Display() != LCss::DispInlineBlock) l->Offset(Offset, 0); } } } } void LFlowRegion::FinishLine(bool Margin) { // AlignText(); if (y2 > y1) { my = Margin ? y2 - y1 : 0; y1 = y2; } else { int fy = Html->DefFont()->GetHeight(); my = Margin ? fy : 0; y1 += fy; } cx = x1; y2 = y1; Line.Empty(); } LRect *LFlowRegion::LineBounds() { auto It = Line.begin(); LFlowRect *Prev = *It; LFlowRect *r=Prev; if (r) { LRect b; b = *r; int Ox = r->Tag->AbsX(); int Oy = r->Tag->AbsY(); b.Offset(Ox, Oy); // int Ox = 0, Oy = 0; while ((r = *(++It) )) { LRect c = *r; Ox = r->Tag->AbsX(); Oy = r->Tag->AbsY(); c.Offset(Ox, Oy); /* Ox += r->Tag->Pos.x - Prev->Tag->Pos.x; Oy += r->Tag->Pos.y - Prev->Tag->Pos.y; c.Offset(Ox, Oy); */ b.Union(&c); Prev = r; } static LRect Rgn; Rgn = b; return &Rgn; } return 0; } void LFlowRegion::Insert(LFlowRect *Tr, LCss::LengthType align) { if (Tr) { Align = align; Line.Insert(Tr); } } ////////////////////////////////////////////////////////////////////// LTag::LTag(LHtml *h, LHtmlElement *p) : LHtmlElement(p), Attr(8) { Ctrl = 0; CtrlType = CtrlNone; TipId = 0; Display(DispInline); Html = h; ImageResized = false; Cursor = -1; Selection = -1; Font = 0; LineHeightCache = -1; HtmlId = NULL; // TableBorder = 0; Cell = NULL; TagId = CONTENT; Info = 0; Pos.x = Pos.y = 0; #ifdef _DEBUG Debug = false; #endif } LTag::~LTag() { if (Html->Cursor == this) { Html->Cursor = 0; } if (Html->Selection == this) { Html->Selection = 0; } DeleteObj(Ctrl); Attr.DeleteArrays(); DeleteObj(Cell); } void LTag::OnChange(PropType Prop) { } bool LTag::OnClick(const LMouse &m) { if (!Html->Environment) return false; const char *OnClick = NULL; if (Get("onclick", OnClick)) { Html->Environment->OnExecuteScript(Html, (char*)OnClick); } else { OnNotify(LNotification(m)); } return true; } void LTag::Set(const char *attr, const char *val) { char *existing = Attr.Find(attr); if (existing) DeleteArray(existing); if (val) Attr.Add(attr, NewStr(val)); } bool LTag::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: // Type: LCssStyle { Value = &StyleDom; return true; } case ObjTextContent: // Type: String { Value = Text(); return true; } default: { char *a = Attr.Find(Name); if (a) { Value = a; return true; } break; } } return false; } bool LTag::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ObjStyle: { const char *Defs = Value.Str(); if (!Defs) return false; return Parse(Defs, ParseRelaxed); } case ObjTextContent: { const char *s = Value.Str(); if (s) { LAutoWString w(CleanText(s, strlen(s), "utf-8", true, true)); Txt = w; return true; } break; } case ObjInnerHtml: // Type: String { // Clear out existing tags.. Children.DeleteObjects(); char *Doc = Value.CastString(); if (Doc) { // Create new tags... bool BackOut = false; while (Doc && *Doc) { LTag *t = new LTag(Html, this); if (t) { Doc = Html->ParseHtml(t, Doc, 1, false, &BackOut); if (!Doc) break; } else break; } } else return false; break; } default: { Set(Name, Value.CastString()); SetStyle(); break; } } Html->ViewWidth = -1; return true; } ssize_t LTag::GetTextStart() { if (PreText() && TextPos.Length() > 1) { LFlowRect *t = TextPos[1]; if (t) return t->Text - Text(); } else if (TextPos.Length() > 0) { LFlowRect *t = TextPos[0]; if (t && Text()) { LAssert(t->Text >= Text() && t->Text <= Text()+2); return t->Text - Text(); } } return 0; } static bool TextToStream(LStream &Out, char16 *Text) { if (!Text) return true; uint8_t Buf[256]; uint8_t *s = Buf; ssize_t Len = sizeof(Buf); while (*Text) { #define WriteExistingContent() \ if (s > Buf) \ Out.Write(Buf, (int)(s - Buf)); \ s = Buf; \ Len = sizeof(Buf); \ Buf[0] = 0; if (*Text == '<' || *Text == '>') { WriteExistingContent(); Out.Print("&%ct;", *Text == '<' ? 'l' : 'g'); } else if (*Text == 0xa0) { WriteExistingContent(); Out.Write((char*)" ", 6); } else { LgiUtf32To8(*Text, s, Len); if (Len < 16) { WriteExistingContent(); } } Text++; } if (s > Buf) Out.Write(Buf, s - Buf); return true; } bool LTag::CreateSource(LStringPipe &p, int Depth, bool LastWasBlock) { char *Tabs = new char[Depth+1]; memset(Tabs, '\t', Depth); Tabs[Depth] = 0; if (ValidStr(Tag)) { if (IsBlock()) { p.Print("%s%s<%s", TagId != TAG_HTML ? "\n" : "", Tabs, Tag.Get()); } else { p.Print("<%s", Tag.Get()); } if (Attr.Length()) { // const char *a; // for (char *v = Attr.First(&a); v; v = Attr.Next(&a)) for (auto v : Attr) { if (_stricmp(v.key, "style")) p.Print(" %s=\"%s\"", v.key, v.value); } } if (Props.Length()) { LCss *Css = this; LCss Tmp; #define DelProp(p) \ if (Css == this) { Tmp = *Css; Css = &Tmp; } \ Css->DeleteProp(p); // Clean out any default CSS properties where we can... LHtmlElemInfo *i = LHtmlStatic::Inst->GetTagInfo(Tag); if (i) { if (Props.Find(PropDisplay) && ( (!i->Block() && Display() == DispInline) || (i->Block() && Display() == DispBlock) )) { DelProp(PropDisplay); } switch (TagId) { default: break; case TAG_A: { LCss::ColorDef Blue(LCss::ColorRgb, Rgb32(0, 0, 255)); if (Props.Find(PropColor) && Color() == Blue) DelProp(PropColor); if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration) break; } case TAG_BODY: { LCss::Len FivePx(LCss::LenPx, 5.0f); if (Props.Find(PropPaddingLeft) && PaddingLeft() == FivePx) DelProp(PropPaddingLeft) if (Props.Find(PropPaddingTop) && PaddingTop() == FivePx) DelProp(PropPaddingTop) if (Props.Find(PropPaddingRight) && PaddingRight() == FivePx) DelProp(PropPaddingRight) break; } case TAG_B: { if (Props.Find(PropFontWeight) && FontWeight() == LCss::FontWeightBold) DelProp(PropFontWeight); break; } case TAG_U: { if (Props.Find(PropTextDecoration) && TextDecoration() == LCss::TextDecorUnderline) DelProp(PropTextDecoration); break; } case TAG_I: { if (Props.Find(PropFontStyle) && FontStyle() == LCss::FontStyleItalic) DelProp(PropFontStyle); break; } } } // Convert CSS props to a string and emit them... auto s = Css->ToString(); if (ValidStr(s)) { // Clean off any trailing whitespace... char *e = s ? s + strlen(s) : NULL; while (e && strchr(WhiteSpace, e[-1])) *--e = 0; // Print them to the tags attributes... p.Print(" style=\"%s\"", s.Get()); } } } if (Children.Length() || TagId == TAG_STYLE) // { if (Tag) { p.Write((char*)">", 1); TextToStream(p, Text()); } bool Last = IsBlock(); for (unsigned i=0; iCreateSource(p, Parent ? Depth+1 : 0, Last); Last = c->IsBlock(); } if (Tag) { if (IsBlock()) { if (Children.Length()) p.Print("\n%s", Tabs); } p.Print("", Tag.Get()); } } else if (Tag) { if (Text()) { p.Write((char*)">", 1); TextToStream(p, Text()); p.Print("", Tag.Get()); } else { p.Print("/>\n"); } } else { TextToStream(p, Text()); } DeleteArray(Tabs); return true; } void LTag::SetTag(const char *NewTag) { Tag.Reset(NewStr(NewTag)); if (NewTag) { Info = Html->GetTagInfo(Tag); if (Info) { TagId = Info->Id; Display(Info->Flags & LHtmlElemInfo::TI_BLOCK ? LCss::DispBlock : LCss::DispInline); } } else { Info = NULL; TagId = CONTENT; } SetStyle(); } LColour LTag::_Colour(bool f) { for (LTag *t = this; t; t = ToTag(t->Parent)) { ColorDef c = f ? t->Color() : t->BackgroundColor(); if (c.Type != ColorInherit) { return LColour(c.Rgb32, 32); } #if 1 if (!f && t->TagId == TAG_TABLE) break; #else /* This implements some basic level of colour inheritance for background colours. See test case 'cisra-cqs.html'. */ if (!f && t->TagId == TAG_TABLE) break; #endif } return LColour(); } void LTag::CopyClipboard(LMemQueue &p, bool &InSelection) { ssize_t Min = -1; ssize_t Max = -1; if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection); Max = MAX(Cursor, Selection); } else if (InSelection) { Max = MAX(Cursor, Selection); } else { Min = MAX(Cursor, Selection); } ssize_t Off = -1; ssize_t Chars = 0; auto Start = GetTextStart(); if (Min >= 0 && Max >= 0) { Off = Min + Start; Chars = Max - Min; } else if (Min >= 0) { Off = Min + Start; Chars = StrlenW(Text()) - Min; InSelection = true; } else if (Max >= 0) { Off = Start; Chars = Max; InSelection = false; } else if (InSelection) { Off = Start; Chars = StrlenW(Text()); } if (Off >= 0 && Chars > 0) { p.Write((uchar*) (Text() + Off), Chars * sizeof(char16)); } if (InSelection) { switch (TagId) { default: break; case TAG_BR: { char16 NL[] = {'\n', 0}; p.Write((uchar*) NL, sizeof(char16)); break; } case TAG_P: { char16 NL[] = {'\n', '\n', 0}; p.Write((uchar*) NL, sizeof(char16) * 2); break; } } } for (unsigned i=0; iCopyClipboard(p, InSelection); } } static char* _DumpColour(LCss::ColorDef c) { static char Buf[4][32]; #ifdef _MSC_VER static LONG Cur = 0; LONG Idx = InterlockedIncrement(&Cur); #else static int Cur = 0; int Idx = __sync_fetch_and_add(&Cur, 1); #endif char *b = Buf[Idx % 4]; if (c.Type == LCss::ColorInherit) strcpy_s(b, 32, "Inherit"); else sprintf_s(b, 32, "%2.2x,%2.2x,%2.2x(%2.2x)", R32(c.Rgb32),G32(c.Rgb32),B32(c.Rgb32),A32(c.Rgb32)); return b; } void LTag::_Dump(LStringPipe &Buf, int Depth) { LString Tabs; Tabs.Set(NULL, Depth); memset(Tabs.Get(), '\t', Depth); const char *Empty = ""; char *ElementName = TagId == CONTENT ? (char*)"Content" : (TagId == ROOT ? (char*)"Root" : Tag); Buf.Print( "%s%s(%p)%s%s%s (%i) Pos=%i,%i Size=%i,%i Color=%s/%s", Tabs.Get(), ElementName, this, HtmlId ? "#" : Empty, HtmlId ? HtmlId : Empty, #ifdef _DEBUG Debug ? " debug" : Empty, #else Empty, #endif WasClosed, Pos.x, Pos.y, Size.x, Size.y, _DumpColour(Color()), _DumpColour(BackgroundColor())); for (unsigned i=0; iText, Tr->Len)); if (Utf8) { size_t Len = strlen(Utf8); if (Len > 40) { Utf8[40] = 0; } } else if (Tr->Text) { Utf8.Reset(NewStr("")); } Buf.Print("Tr(%i,%i %ix%i '%s') ", Tr->x1, Tr->y1, Tr->X(), Tr->Y(), Utf8.Get()); } Buf.Print("\r\n"); for (unsigned i=0; i_Dump(Buf, Depth+1); } if (Children.Length()) { Buf.Print("%s/%s\r\n", Tabs.Get(), ElementName); } } LAutoWString LTag::DumpW() { LStringPipe Buf; // Buf.Print("Html pos=%s\n", Html?Html->GetPos().GetStr():0); _Dump(Buf, 0); LAutoString a(Buf.NewStr()); LAutoWString w(Utf8ToWide(a)); return w; } LAutoString LTag::DescribeElement() { LStringPipe s(256); s.Print("%s", Tag ? Tag.Get() : "CONTENT"); if (HtmlId) s.Print("#%s", HtmlId); for (unsigned i=0; iDefFont(); } return f; } LFont *LTag::GetFont() { if (!Font) { if (PropAddress(PropFontFamily) != 0 || FontSize().Type != LenInherit || FontStyle() != FontStyleInherit || FontVariant() != FontVariantInherit || FontWeight() != FontWeightInherit || TextDecoration() != TextDecorInherit) { LCss c; LCss::PropMap Map; Map.Add(PropFontFamily, new LCss::PropArray); Map.Add(PropFontSize, new LCss::PropArray); Map.Add(PropFontStyle, new LCss::PropArray); Map.Add(PropFontVariant, new LCss::PropArray); Map.Add(PropFontWeight, new LCss::PropArray); Map.Add(PropTextDecoration, new LCss::PropArray); for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_IFRAME) break; if (!c.InheritCollect(*t, Map)) break; } c.InheritResolve(Map); Map.DeleteObjects(); if ((Font = Html->FontCache->GetFont(&c))) return Font; } else { LTag *t = this; while (!t->Font && t->Parent) { t = ToTag(t->Parent); } if (t->Font) return t->Font; } Font = Html->DefFont(); } return Font; } LTag *LTag::PrevTag() { if (Parent) { ssize_t i = Parent->Children.IndexOf(this); if (i >= 0) { return ToTag(Parent->Children[i - 1]); } } return 0; } void LTag::Invalidate() { LRect p = GetRect(); for (LTag *t=ToTag(Parent); t; t=ToTag(t->Parent)) { p.Offset(t->Pos.x, t->Pos.y); } Html->Invalidate(&p); } LTag *LTag::IsAnchor(LString *Uri) { LTag *a = 0; for (LTag *t = this; t; t = ToTag(t->Parent)) { if (t->TagId == TAG_A) { a = t; break; } } if (a && Uri) { const char *u = 0; if (a->Get("href", u)) { LAutoWString w(CleanText(u, strlen(u), "utf-8")); if (w) { *Uri = w; } } } return a; } bool LTag::OnMouseClick(LMouse &m) { bool Processed = false; if (m.IsContextMenu()) { LString Uri; const char *ImgSrc = NULL; LTag *a = IsAnchor(&Uri); bool IsImg = TagId == TAG_IMG; if (IsImg) Get("src", ImgSrc); bool IsAnchor = a && ValidStr(Uri); if (IsAnchor || IsImg) { LSubMenu RClick; #define IDM_COPY_LINK 100 #define IDM_COPY_IMG 101 if (Html->GetMouse(m, true)) { int Id = 0; if (IsAnchor) RClick.AppendItem(LLoadString(L_COPY_LINK_LOCATION, "&Copy Link Location"), IDM_COPY_LINK, Uri != NULL); if (IsImg) RClick.AppendItem("Copy Image Location", IDM_COPY_IMG, ImgSrc != NULL); if (Html->GetEnv()) Html->GetEnv()->AppendItems(&RClick, Uri); switch (Id = RClick.Float(Html, m.x, m.y)) { case IDM_COPY_LINK: { LClipBoard Clip(Html); Clip.Text(Uri); break; } case IDM_COPY_IMG: { LClipBoard Clip(Html); Clip.Text(ImgSrc); break; } default: { if (Html->GetEnv()) Html->GetEnv()->OnMenu(Html, Id, a); break; } } } Processed = true; } } else if (m.Down() && m.Left()) { #ifdef _DEBUG if (m.Ctrl()) { auto Style = ToString(); LStringPipe p(256); p.Print("Tag: %s\n", Tag ? Tag.Get() : "CONTENT"); if (Class.Length()) { p.Print("Class(es): "); for (unsigned i=0; iParent; t=ToTag(t->Parent)) { LStringPipe Tmp; Tmp.Print(" %s", t->Tag ? t->Tag.Get() : "CONTENT"); if (t->HtmlId) { Tmp.Print("#%s", t->HtmlId); } for (unsigned i=0; iClass.Length(); i++) { Tmp.Print(".%s", t->Class[i].Get()); } LAutoString Txt(Tmp.NewStr()); p.Print("%s", Txt.Get()); LDisplayString Ds(LSysFont, Txt); int Px = 170 - Ds.X(); int Chars = Px / Sp.X(); for (int c=0; cPos.x, t->Pos.y, t->Size.x, t->Size.y); } LAutoString a(p.NewStr()); LgiMsg( Html, "%s", Html->GetClass(), MB_OK, a.Get()); } else #endif { LString Uri; if (Html && Html->Environment) { if (IsAnchor(&Uri)) { if (Uri) { if (!Html->d->LinkDoubleClick || m.Double()) { Html->Environment->OnNavigate(Html, Uri); Processed = true; } } const char *OnClk = NULL; if (!Processed && Get("onclick", OnClk)) { Html->Environment->OnExecuteScript(Html, (char*)OnClk); } } else { Processed = OnClick(m); } } } } return Processed; } LTag *LTag::GetBlockParent(ssize_t *Idx) { if (IsBlock()) { if (Idx) *Idx = 0; return this; } for (LTag *t = this; t; t = ToTag(t->Parent)) { if (ToTag(t->Parent)->IsBlock()) { if (Idx) { *Idx = t->Parent->Children.IndexOf(t); } return ToTag(t->Parent); } } return 0; } LTag *LTag::GetAnchor(char *Name) { if (!Name) return 0; const char *n; if (IsAnchor(0) && Get("name", n) && n && !_stricmp(Name, n)) { return this; } for (unsigned i=0; iGetAnchor(Name); if (Result) return Result; } return 0; } LTag *LTag::GetTagByName(const char *Name) { if (Name) { if (Tag && _stricmp(Tag, Name) == 0) { return this; } for (unsigned i=0; iGetTagByName(Name); if (Result) return Result; } } return 0; } static int IsNearRect(LRect *r, int x, int y) { if (r->Overlap(x, y)) { return 0; } else if (x >= r->x1 && x <= r->x2) { if (y < r->y1) return r->y1 - y; else return y - r->y2; } else if (y >= r->y1 && y <= r->y2) { if (x < r->x1) return r->x1 - x; else return x - r->x2; } int64 dx = 0; int64 dy = 0; if (x < r->x1) { if (y < r->y1) { // top left dx = r->x1 - x; dy = r->y1 - y; } else { // bottom left dx = r->x1 - x; dy = y - r->y2; } } else { if (y < r->y1) { // top right dx = x - r->x2; dy = r->y1 - y; } else { // bottom right dx = x - r->x2; dy = y - r->y2; } } return (int) sqrt( (double) ( (dx * dx) + (dy * dy) ) ); } ssize_t LTag::NearestChar(LFlowRect *Tr, int x, int y) { LFont *f = GetFont(); if (f) { LDisplayString ds(f, Tr->Text, Tr->Len); ssize_t c = ds.CharAt(x - Tr->x1); if (Tr->Text == PreText()) { return 0; } else { char16 *t = Tr->Text + c; size_t Len = StrlenW(Text()); if (t >= Text() && t <= Text() + Len) { return (t - Text()) - GetTextStart(); } else { LgiTrace("%s:%i - Error getting char at position.\n", _FL); } } } return -1; } void LTag::GetTagByPos(LTagHit &TagHit, int x, int y, int Depth, bool InBody, bool DebugLog) { /* InBody: Originally I had this test in the code but it seems that some test cases have actual content after the body. And testing for "InBody" breaks functionality for those cases (see "spam4.html" and the unsubscribe link at the end of the doc). */ if (TagId == TAG_IMG) { LRect img(0, 0, Size.x - 1, Size.y - 1); if (/*InBody &&*/ img.Overlap(x, y)) { TagHit.Direct = this; TagHit.Block = 0; } } else if (/*InBody &&*/ TextPos.Length()) { for (unsigned i=0; i= Tr->y1 && y <= Tr->y2; int Near = IsNearRect(Tr, x, y); if (Near >= 0 && Near < 100) { if ( !TagHit.NearestText || ( SameRow && !TagHit.NearSameRow ) || ( SameRow == TagHit.NearSameRow && Near < TagHit.Near ) ) { TagHit.NearestText = this; TagHit.NearSameRow = SameRow; TagHit.Block = Tr; TagHit.Near = Near; TagHit.Index = NearestChar(Tr, x, y); if (DebugLog) { LgiTrace("%i:GetTagByPos HitText %s #%s, idx=%i, near=%i, txt='%S'\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near, Tr->Text); } if (!TagHit.Near) { TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; } } } } } else if ( TagId != TAG_TR && Tag && x >= 0 && y >= 0 && x < Size.x && y < Size.y // && InBody ) { // Direct hit TagHit.Direct = this; TagHit.LocalCoords.x = x; TagHit.LocalCoords.y = y; if (DebugLog) { LgiTrace("%i:GetTagByPos DirectHit %s #%s, idx=%i, near=%i\n", Depth, Tag.Get(), HtmlId, TagHit.Index, TagHit.Near); } } if (TagId == TAG_BODY) InBody = true; for (unsigned i=0; iPos.x >= 0 && t->Pos.y >= 0) { t->GetTagByPos(TagHit, x - t->Pos.x, y - t->Pos.y, Depth + 1, InBody, DebugLog); } } } int LTag::OnNotify(LNotification n) { if (!Ctrl || !Html->InThread()) return 0; switch (CtrlType) { case CtrlSubmit: { LTag *Form = this; while (Form && Form->TagId != TAG_FORM) Form = ToTag(Form->Parent); if (Form) Html->OnSubmitForm(Form); break; } default: { CtrlValue = Ctrl->Name(); break; } } return 0; } void LTag::CollectFormValues(LHashTbl,char*> &f) { if (CtrlType != CtrlNone) { const char *Name; if (Get("name", Name)) { char *Existing = f.Find(Name); if (Existing) DeleteArray(Existing); char *Val = CtrlValue.Str(); if (Val) { LStringPipe p(256); for (char *v = Val; *v; v++) { if (*v == ' ') p.Write("+", 1); else if (IsAlpha(*v) || IsDigit(*v) || *v == '_' || *v == '.') p.Write(v, 1); else p.Print("%%%02.2X", *v); } f.Add(Name, p.NewStr()); } else { f.Add(Name, NewStr("")); } } } for (unsigned i=0; iCollectFormValues(f); } } LTag *LTag::FindCtrlId(int Id) { if (Ctrl && Ctrl->GetId() == Id) return this; for (unsigned i=0; iFindCtrlId(Id); if (f) return f; } return NULL; } void LTag::Find(int TagType, LArray &Out) { if (TagId == TagType) { Out.Add(this); } for (unsigned i=0; iFind(TagType, Out); } } void LTag::SetImage(const char *Uri, LSurface *Img) { if (Img) { if (TagId != TAG_IMG) { ImageDef *Def = (ImageDef*)LCss::Props.Find(PropBackgroundImage); if (Def) { Def->Type = ImageOwn; DeleteObj(Def->Img); Def->Img = Img; } } else { if (Img->GetColourSpace() == CsIndex8) { if (Image.Reset(new LMemDC(Img->X(), Img->Y(), System32BitColourSpace))) { Image->Colour(0, 32); Image->Rectangle(); Image->Blt(0, 0, Img); } else LgiTrace("%s:%i - SetImage can't promote 8bit image to 32bit.\n", _FL); } else Image.Reset(Img); LRect r = XSubRect(); if (r.Valid()) { LAutoPtr t(new LMemDC(r.X(), r.Y(), Image->GetColourSpace())); if (t) { t->Blt(0, 0, Image, &r); Image = t; } } } for (unsigned i=0; iCell) { t->Cell->MinContent = 0; t->Cell->MaxContent = 0; } } } else { Html->d->Loading.Add(Uri, this); } } void LTag::LoadImage(const char *Uri) { #if DOCUMENT_LOAD_IMAGES if (!Html->Environment) return; LUri u(Uri); bool LdImg = Html->GetLoadImages(); bool IsRemote = u.sProtocol && ( !_stricmp(u.sProtocol, "http") || !_stricmp(u.sProtocol, "https") || !_stricmp(u.sProtocol, "ftp") ); if (IsRemote && !LdImg) { Html->NeedsCapability("RemoteContent"); return; } else if (u.IsProtocol("data")) { if (!u.sPath) return; const char *s = u.sPath; if (*s++ != '/') return; LAutoString Type(LTokStr(s)); if (*s++ != ',') return; auto p = LString(Type).SplitDelimit(",;:"); if (p.Length() != 2 || !p.Last().Equals("base64")) return; LString Name = LString("name.") + p[0]; auto Filter = LFilterFactory::New(Name, FILTER_CAP_READ, NULL); if (!Filter) return; auto slen = strlen(s); auto blen = BufferLen_64ToBin(slen); LMemStream bin; bin.SetSize(blen); ConvertBase64ToBinary((uint8_t*)bin.GetBasePtr(), blen, s, slen); bin.SetPos(0); if (!Image.Reset(new LMemDC)) return; auto result = Filter->ReadImage(Image, &bin); if (result != LFilter::IoSuccess) Image.Reset(); return; } LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); j->Uri.Reset(NewStr(Uri)); j->Env = Html->Environment; j->UserData = this; j->UserUid = Html->GetDocumentUid(); // LgiTrace("%s:%i - new job %p, %p\n", _FL, j, j->UserData); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { SetImage(Uri, j->pDC.Release()); } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } #endif } void LTag::LoadImages() { const char *Uri = 0; if (Html->Environment && TagId == TAG_IMG && !Image) { if (Get("src", Uri)) LoadImage(Uri); } for (unsigned i=0; iLoadImages(); } } void LTag::ImageLoaded(char *uri, LSurface *Img, int &Used) { const char *Uri = 0; if (!Image && Get("src", Uri)) { if (strcmp(Uri, uri) == 0) { if (Used == 0) { SetImage(Uri, Img); } else { SetImage(Uri, new LMemDC(Img)); } Used++; } } for (unsigned i=0; iImageLoaded(uri, Img, Used); } } struct LTagElementCallback : public LCss::ElementCallback { const char *Val; const char *GetElement(LTag *obj) { return obj->Tag; } const char *GetAttr(LTag *obj, const char *Attr) { if (obj->Get(Attr, Val)) return Val; return NULL; } bool GetClasses(LString::Array &Classes, LTag *obj) { Classes = obj->Class; return Classes.Length() > 0; } LTag *GetParent(LTag *obj) { return ToTag(obj->Parent); } LArray GetChildren(LTag *obj) { LArray c; for (unsigned i=0; iChildren.Length(); i++) c.Add(ToTag(obj->Children[i])); return c; } }; void LTag::RestyleAll() { Restyle(); for (unsigned i=0; iRestyleAll(); } } // After CSS has changed this function scans through the CSS and applies any rules // that match the current tag. void LTag::Restyle() { // Use the matching built into the LCss Store. LCss::SelArray Styles; LTagElementCallback Context; if (Html->CssStore.Match(Styles, &Context, this)) { for (unsigned i=0; iStyle); } } // Do the element specific styles const char *s; if (Get("style", s)) SetCssStyle(s); #if DEBUG_RESTYLE && defined(_DEBUG) if (Debug) { auto Style = ToString(); LgiTrace(">>>> %s <<<<:\n%s\n\n", Tag.Get(), Style.Get()); } #endif } void LTag::SetStyle() { const static float FntMul[] = { 0.6f, // size=1 0.89f, // size=2 1.0f, // size=3 1.2f, // size=4 1.5f, // size=5 2.0f, // size=6 3.0f // size=7 }; const char *s = 0; #ifdef _DEBUG if (Get("debug", s)) { if ((Debug = atoi(s))) { LgiTrace("Debug Tag: %p '%s'\n", this, Tag ? Tag.Get() : "CONTENT"); } } #endif if (Get("Color", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { Color(Def); } } if (Get("Background", s) || Get("bgcolor", s)) { ColorDef Def; if (LHtmlParser::ParseColour(s, Def)) { BackgroundColor(Def); } else { LCss::ImageDef Img; Img.Type = ImageUri; Img.Uri = s; BackgroundImage(Img); BackgroundRepeat(RepeatBoth); } } switch (TagId) { default: { if (!Stricmp(Tag.Get(), "o:p")) Display(LCss::DispNone); break; } case TAG_LINK: { const char *Type, *Href; if (Html->Environment && Get("type", Type) && Get("href", Href) && !Stricmp(Type, "text/css") && !Html->CssHref.Find(Href)) { LDocumentEnv::LoadJob *j = Html->Environment->NewJob(); if (j) { LAssert(Html != NULL); LTag *t = this; j->Uri.Reset(NewStr(Href)); j->Env = Html->Environment; j->UserData = t; j->UserUid = Html->GetDocumentUid(); LDocumentEnv::LoadType Result = Html->Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { LStreamI *s = j->GetStream(); if (s) { int Len = (int)s->GetSize(); if (Len > 0) { LAutoString a(new char[Len+1]); ssize_t r = s->Read(a, Len); a[r] = 0; Html->CssHref.Add(Href, true); Html->OnAddStyle("text/css", a); } } } else if (Result == LDocumentEnv::LoadDeferred) { Html->d->DeferredLoads++; } DeleteObj(j); } } break; } case TAG_BLOCKQUOTE: { MarginTop(Len("8px")); MarginBottom(Len("8px")); MarginLeft(Len("16px")); if (Get("Type", s)) { if (_stricmp(s, "cite") == 0) { BorderLeft(BorderDef(this, "1px solid blue")); PaddingLeft(Len("0.5em")); /* ColorDef Def; Def.Type = ColorRgb; Def.Rgb32 = Rgb32(0x80, 0x80, 0x80); Color(Def); */ } } break; } case TAG_P: { MarginBottom(Len("1em")); break; } case TAG_A: { const char *Href; if (Get("href", Href)) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = Rgb32(0, 0, 255); Color(c); TextDecoration(TextDecorUnderline); } break; } case TAG_TABLE: { Len l; if (!Cell) Cell = new TblCell; if (Get("border", s)) { BorderDef b; if (b.Parse(this, s)) { BorderLeft(b); BorderRight(b); BorderTop(b); BorderBottom(b); } } if (Get("cellspacing", s) && l.Parse(s, PropBorderSpacing, ParseRelaxed)) { BorderSpacing(l); } else { // BorderSpacing(LCss::Len(LCss::LenPx, 2.0f)); } if (Get("cellpadding", s) && l.Parse(s, Prop_CellPadding, ParseRelaxed)) { _CellPadding(l); } if (Get("align", s)) { Len l; if (l.Parse(s)) Cell->XAlign = l.Type; } break; } case TAG_TD: case TAG_TH: { if (!Cell) Cell = new TblCell; LTag *Table = GetTable(); if (Table) { Len l = Table->_CellPadding(); if (!l.IsValid()) { l.Type = LCss::LenPx; l.Value = DefaultCellPadding; } PaddingLeft(l); PaddingRight(l); PaddingTop(l); PaddingBottom(l); } if (TagId == TAG_TH) FontWeight(LCss::FontWeightBold); break; } case TAG_BODY: { MarginLeft(Len(Get("leftmargin", s) ? s : DefaultBodyMargin)); MarginTop(Len(Get("topmargin", s) ? s : DefaultBodyMargin)); MarginRight(Len(Get("rightmargin", s) ? s : DefaultBodyMargin)); if (Get("text", s)) { ColorDef c; if (c.Parse(s)) { Color(c); } } break; } case TAG_OL: case TAG_UL: { MarginLeft(Len("16px")); break; } case TAG_STRONG: case TAG_B: { FontWeight(FontWeightBold); break; } case TAG_I: { FontStyle(FontStyleItalic); break; } case TAG_U: { TextDecoration(TextDecorUnderline); break; } case TAG_SUP: { VerticalAlign(VerticalSuper); FontSize(SizeSmaller); break; } case TAG_SUB: { VerticalAlign(VerticalSub); FontSize(SizeSmaller); break; } case TAG_TITLE: { Display(LCss::DispNone); break; } } if (Get("width", s)) { Len l; if (l.Parse(s, PropWidth, ParseRelaxed)) { Width(l); } } if (Get("height", s)) { Len l; if (l.Parse(s, PropHeight, ParseRelaxed)) Height(l); } if (Get("align", s)) { if (_stricmp(s, "left") == 0) TextAlign(Len(AlignLeft)); else if (_stricmp(s, "right") == 0) TextAlign(Len(AlignRight)); else if (_stricmp(s, "center") == 0) TextAlign(Len(AlignCenter)); } if (Get("valign", s)) { if (_stricmp(s, "top") == 0) VerticalAlign(Len(VerticalTop)); else if (_stricmp(s, "middle") == 0) VerticalAlign(Len(VerticalMiddle)); else if (_stricmp(s, "bottom") == 0) VerticalAlign(Len(VerticalBottom)); } Get("id", HtmlId); if (Get("class", s)) { Class = LString(s).SplitDelimit(" \t"); } Restyle(); switch (TagId) { default: break; case TAG_BIG: { LCss::Len l; l.Type = SizeLarger; FontSize(l); break; } /* case TAG_META: { LAutoString Cs; const char *s; if (Get("http-equiv", s) && _stricmp(s, "Content-Type") == 0) { const char *ContentType; if (Get("content", ContentType)) { char *CharSet = stristr(ContentType, "charset="); if (CharSet) { char16 *cs = NULL; Html->ParsePropValue(CharSet + 8, cs); Cs.Reset(WideToUtf8(cs)); DeleteArray(cs); } } } if (Get("name", s) && _stricmp(s, "charset") == 0 && Get("content", s)) { Cs.Reset(NewStr(s)); } else if (Get("charset", s)) { Cs.Reset(NewStr(s)); } if (Cs) { if (Cs && _stricmp(Cs, "utf-16") != 0 && _stricmp(Cs, "utf-32") != 0 && LGetCsInfo(Cs)) { // Html->SetCharset(Cs); } } break; } */ case TAG_BODY: { LCss::ColorDef Bk = BackgroundColor(); if (Bk.Type != ColorInherit) { // Copy the background up to the LHtml wrapper Html->GetCss(true)->BackgroundColor(Bk); } /* LFont *f = GetFont(); if (FontSize().Type == LenInherit) { FontSize(Len(LenPt, (float)f->PointSize())); } */ break; } case TAG_HEAD: { Display(DispNone); break; } case TAG_PRE: { LFontType Type; if (Type.GetSystemFont("Fixed")) { LAssert(ValidStr(Type.GetFace())); FontFamily(StringsDef(Type.GetFace())); } break; } case TAG_TR: break; case TAG_TD: case TAG_TH: { LAssert(Cell != NULL); const char *s; if (Get("colspan", s)) Cell->Span.x = atoi(s); else Cell->Span.x = 1; if (Get("rowspan", s)) Cell->Span.y = atoi(s); else Cell->Span.y = 1; Cell->Span.x = MAX(Cell->Span.x, 1); Cell->Span.y = MAX(Cell->Span.y, 1); if (Display() == DispInline || Display() == DispInlineBlock) { Display(DispBlock); // Inline-block TD??? Nope. } break; } case TAG_IMG: { const char *Uri; if (Html->Environment && Get("src", Uri)) { // printf("Uri: %s\n", Uri); LoadImage(Uri); } break; } case TAG_H1: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[5])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H2: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[4])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H3: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[3])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H4: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[2])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H5: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[1])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_H6: { char s[32]; sprintf_s(s, sizeof(s), "%ipt", (int)((float)Html->DefFont()->PointSize() * FntMul[0])); FontSize(Len(s)); FontWeight(FontWeightBold); break; } case TAG_FONT: { const char *s = 0; if (Get("Face", s)) { char16 *cw = CleanText(s, strlen(s), "utf-8", true); char *c8 = WideToUtf8(cw); DeleteArray(cw); LToken Faces(c8, ","); DeleteArray(c8); char *face = TrimStr(Faces[0]); if (ValidStr(face)) { FontFamily(face); DeleteArray(face); } else { LgiTrace("%s:%i - No face for font tag.\n", __FILE__, __LINE__); } } if (Get("Size", s)) { bool Digit = false, NonW = false; for (auto *c = s; *c; c++) { if (IsDigit(*c) || *c == '-') Digit = true; else if (!IsWhiteSpace(*c)) NonW = true; } if (Digit && !NonW) { auto Sz = atoi(s); switch (Sz) { case 1: FontSize(Len(LCss::LenEm, 0.63f)); break; case 2: FontSize(Len(LCss::LenEm, 0.82f)); break; case 3: FontSize(Len(LCss::LenEm, 1.0f)); break; case 4: FontSize(Len(LCss::LenEm, 1.13f)); break; case 5: FontSize(Len(LCss::LenEm, 1.5f)); break; case 6: FontSize(Len(LCss::LenEm, 2.0f)); break; case 7: FontSize(Len(LCss::LenEm, 3.0f)); break; } } else { FontSize(Len(s)); } } break; } case TAG_SELECT: { if (!Html->InThread()) break; LAssert(!Ctrl); Ctrl = new LCombo(Html->d->NextCtrlId++, 0, 0, 100, LSysFont->GetHeight() + 8, NULL); CtrlType = CtrlSelect; break; } case TAG_INPUT: { if (!Html->InThread()) break; LAssert(!Ctrl); const char *Type, *Value = NULL; Get("value", Value); LAutoWString CleanValue(Value ? CleanText(Value, strlen(Value), "utf-8", true, true) : NULL); if (CleanValue) { CtrlValue = CleanValue; } if (Get("type", Type)) { if (!_stricmp(Type, "password")) CtrlType = CtrlPassword; else if (!_stricmp(Type, "email")) CtrlType = CtrlEmail; else if (!_stricmp(Type, "text")) CtrlType = CtrlText; else if (!_stricmp(Type, "button")) CtrlType = CtrlButton; else if (!_stricmp(Type, "submit")) CtrlType = CtrlSubmit; else if (!_stricmp(Type, "hidden")) CtrlType = CtrlHidden; DeleteObj(Ctrl); if (CtrlType == CtrlEmail || CtrlType == CtrlText || CtrlType == CtrlPassword) { LEdit *Ed; LAutoString UtfCleanValue(WideToUtf8(CleanValue)); Ctrl = Ed = new LEdit(Html->d->NextCtrlId++, 0, 0, 60, LSysFont->GetHeight() + 8, UtfCleanValue); if (Ctrl) { Ed->Sunken(false); Ed->Password(CtrlType == CtrlPassword); } } else if (CtrlType == CtrlButton || CtrlType == CtrlSubmit) { LAutoString UtfCleanValue(WideToUtf8(CleanValue)); if (UtfCleanValue) { Ctrl = new InputButton(this, Html->d->NextCtrlId++, UtfCleanValue); } } } break; } } if (IsBlock()) { LCss::ImageDef bk = BackgroundImage(); if (bk.Type == LCss::ImageUri && ValidStr(bk.Uri) && !bk.Uri.Equals("transparent")) { LoadImage(bk.Uri); } } if (Ctrl) { LFont *f = GetFont(); if (f) Ctrl->SetFont(f, false); } } void LTag::OnStyleChange(const char *name) { if (!Stricmp(name, "display") && Html) { Html->Layout(true); Html->Invalidate(); } } void LTag::SetCssStyle(const char *Style) { if (Style) { // Strip out comments char *Comment = NULL; while ((Comment = strstr((char*)Style, "/*"))) { char *End = strstr(Comment+2, "*/"); if (!End) break; for (char *c = Comment; cDocCharSet && Html->Charset) { DocAndCsTheSame = _stricmp(Html->DocCharSet, Html->Charset) == 0; } if (SourceCs) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, SourceCs, Len); } else if (Html->DocCharSet && Html->Charset && !DocAndCsTheSame && !Html->OverideDocCharset) { char *DocText = (char*)LNewConvertCp(Html->DocCharSet, s, Html->Charset, Len); t = (char16*) LNewConvertCp(LGI_WideCharset, DocText, Html->DocCharSet, -1); DeleteArray(DocText); } else if (Html->DocCharSet) { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->DocCharSet, Len); } else { t = (char16*) LNewConvertCp(LGI_WideCharset, s, Html->Charset.Get() ? Html->Charset.Get() : DefaultCs, Len); } if (t && ConversionAllowed) { char16 *o = t; for (char16 *i=t; *i; ) { switch (*i) { case '&': { i++; if (*i == '#') { // Unicode Number char n[32] = "", *p = n; i++; if (*i == 'x' || *i == 'X') { // Hex number i++; while ( *i && ( IsDigit(*i) || (*i >= 'A' && *i <= 'F') || (*i >= 'a' && *i <= 'f') ) && (p - n) < 31) { *p++ = (char)*i++; } } else { // Decimal number while (*i && IsDigit(*i) && (p - n) < 31) { *p++ = (char)*i++; } } *p++ = 0; char16 Ch = atoi(n); if (Ch) { *o++ = Ch; } if (*i && *i != ';') i--; } else { // Named Char char16 *e = i; while (*e && IsAlpha(*e) && *e != ';') { e++; } LAutoWString Var(NewStrW(i, e-i)); char16 Char = LHtmlStatic::Inst->VarMap.Find(Var); if (Char) { *o++ = Char; i = e; } else { i--; *o++ = *i; } } break; } case '\r': { break; } case ' ': case '\t': case '\n': { if (KeepWhiteSpace) { *o++ = *i; } else { *o++ = ' '; // Skip furthur whitespace while (i[1] && IsWhiteSpace(i[1])) { i++; } } break; } default: { // Normal char *o++ = *i; break; } } if (*i) i++; else break; } *o++ = 0; } if (t && !*t) { DeleteArray(t); } return t; } char *LTag::ParseText(char *Doc) { ColorDef c; c.Type = ColorRgb; c.Rgb32 = LColour(L_WORKSPACE).c32(); BackgroundColor(c); TagId = TAG_BODY; Tag.Reset(NewStr("body")); Info = Html->GetTagInfo(Tag); char *OriginalCp = NewStr(Html->Charset); LStringPipe Utf16; char *s = Doc; while (s) { if (*s == '\r') { s++; } else if (*s == '<') { // Process tag char *e = s; e++; while (*e && *e != '>') { if (*e == '\"' || *e == '\'') { char *q = strchr(e + 1, *e); if (q) e = q + 1; else e++; } else e++; } if (*e == '>') e++; // Output tag Html->SetCharset("iso-8859-1"); char16 *t = CleanText(s, e - s, NULL, false); if (t) { Utf16.Push(t); DeleteArray(t); } s = e; } else if (!*s || *s == '\n') { // Output previous line char16 *Line = Utf16.NewStrW(); if (Line) { LTag *t = new LTag(Html, this); if (t) { t->Color(LColour(L_TEXT)); t->Text(Line); } } if (*s == '\n') { s++; LTag *t = new LTag(Html, this); if (t) { t->TagId = TAG_BR; t->Tag.Reset(NewStr("br")); t->Info = Html->GetTagInfo(t->Tag); } } else break; } else { // Seek end of text char *e = s; while (*e && *e != '\r' && *e != '\n' && *e != '<') e++; // Output text Html->SetCharset(OriginalCp); LAutoWString t(CleanText(s, e - s, NULL, false)); if (t) { Utf16.Push(t); } s = e; } } Html->SetCharset(OriginalCp); DeleteArray(OriginalCp); return 0; } bool LTag::ConvertToText(TextConvertState &State) { const static char *Rule = "------------------------------------------------------"; int DepthInc = 0; switch (TagId) { default: break; case TAG_P: if (State.GetPrev()) State.NewLine(); break; case TAG_UL: case TAG_OL: DepthInc = 2; break; } if (ValidStrW(Txt)) { for (int i=0; iConvertToUnicode(Txt); else u.Reset(WideToUtf8(Txt)); if (u) { size_t u_len = strlen(u); State.Write(u, u_len); } } State.Depth += DepthInc; for (unsigned i=0; iConvertToText(State); } State.Depth -= DepthInc; if (IsBlock()) { if (State.CharsOnLine) State.NewLine(); } else { switch (TagId) { case TAG_A: { // Emit the link to the anchor if it's different from the text of the span... const char *Href; if (Get("href", Href) && ValidStrW(Txt)) { if (_strnicmp(Href, "mailto:", 7) == 0) Href += 7; size_t HrefLen = strlen(Href); LAutoWString h(CleanText(Href, HrefLen, "utf-8")); if (h && StrcmpW(h, Txt) != 0) { // Href different from the text of the link State.Write(" (", 2); State.Write(Href, HrefLen); State.Write(")", 1); } } break; } case TAG_HR: { State.Write(Rule, strlen(Rule)); State.NewLine(); break; } case TAG_BR: { State.NewLine(); break; } default: break; } } return true; } char *LTag::NextTag(char *s) { while (s && *s) { char *n = strchr(s, '<'); if (n) { if (!n[1]) return NULL; if (IsAlpha(n[1]) || strchr("!/", n[1]) || n[1] == '?') { return n; } s = n + 1; } else break; } return 0; } void LHtml::CloseTag(LTag *t) { if (!t) return; OpenTags.Delete(t); } bool LTag::OnUnhandledColor(LCss::ColorDef *def, const char *&s) { const char *e = s; while (*e && (IsText(*e) || *e == '_')) e++; char tmp[256]; ssize_t len = e - s; memcpy(tmp, s, len); tmp[len] = 0; int m = LHtmlStatic::Inst->ColourMap.Find(tmp); s = e; if (m >= 0) { def->Type = LCss::ColorRgb; def->Rgb32 = Rgb24To32(m); return true; } return false; } void LTag::ZeroTableElements() { if (TagId == TAG_TABLE || TagId == TAG_TR || IsTableCell(TagId)) { Size.x = 0; Size.y = 0; if (Cell) { Cell->MinContent = 0; Cell->MaxContent = 0; } for (unsigned i=0; iZeroTableElements(); } } } void LTag::ResetCaches() { /* If during the parse process a callback causes a layout to happen then it's possible to have partial information in the LHtmlTableLayout structure, like missing TD cells. Because they haven't been parsed yet. This is called at the end of the parsing to reset all the cached info in LHtmlTableLayout. That way when the first real layout happens all the data is there. */ if (Cell) DeleteObj(Cell->Cells); for (size_t i=0; iResetCaches(); } LPoint LTag::GetTableSize() { LPoint s(0, 0); if (Cell && Cell->Cells) { Cell->Cells->GetSize(s.x, s.y); } return s; } LTag *LTag::GetTableCell(int x, int y) { LTag *t = this; while ( t && !t->Cell && !t->Cell->Cells && t->Parent) { t = ToTag(t->Parent); } if (t && t->Cell && t->Cell->Cells) { return t->Cell->Cells->Get(x, y); } return 0; } // This function gets the largest and smallest piece of content // in this cell and all it's children. bool LTag::GetWidthMetrics(LTag *Table, uint16 &Min, uint16 &Max) { bool Status = true; int MarginPx = 0; int LineWidth = 0; if (Display() == LCss::DispNone) return true; // Break the text into words and measure... if (Text()) { int MinContent = 0; int MaxContent = 0; LFont *f = GetFont(); if (f) { for (char16 *s = Text(); s && *s; ) { // Skip whitespace... while (*s && StrchrW(WhiteW, *s)) s++; // Find end of non-whitespace char16 *e = s; while (*e && !StrchrW(WhiteW, *e)) e++; // Find size of the word ssize_t Len = e - s; if (Len > 0) { LDisplayString ds(f, s, Len); MinContent = MAX(MinContent, ds.X()); } // Move to the next word. s = (*e) ? e + 1 : 0; } LDisplayString ds(f, Text()); LineWidth = MaxContent = ds.X(); } #if 0//def _DEBUG if (Debug) { LgiTrace("GetWidthMetrics Font=%p Sz=%i,%i\n", f, MinContent, MaxContent); } #endif Min = MAX(Min, MinContent); Max = MAX(Max, MaxContent); } // Specific tag handling? switch (TagId) { default: { if (IsBlock()) { MarginPx = (int)(BorderLeft().ToPx() + BorderRight().ToPx() + PaddingLeft().ToPx() + PaddingRight().ToPx()); } break; } case TAG_IMG: { Len w = Width(); if (w.IsValid()) { int x = (int) w.Value; Min = MAX(Min, x); Max = MAX(Max, x); } else if (Image) { Min = Max = Image->X(); } else { Size.x = Size.y = DefaultImgSize; Min = MAX(Min, Size.x); Max = MAX(Max, Size.x); } break; } case TAG_TD: case TAG_TH: { Len w = Width(); if (w.IsValid()) { if (w.IsDynamic()) { Min = MAX(Min, (int)w.Value); Max = MAX(Max, (int)w.Value); } else { Max = w.ToPx(0, GetFont()); } } else { LCss::BorderDef BLeft = BorderLeft(); LCss::BorderDef BRight = BorderRight(); LCss::Len PLeft = PaddingLeft(); LCss::Len PRight = PaddingRight(); MarginPx = (int)(PLeft.ToPx() + PRight.ToPx() + BLeft.ToPx()); if (Table->BorderCollapse() == LCss::CollapseCollapse) MarginPx += BRight.ToPx(); } break; } case TAG_TABLE: { Len w = Width(); if (w.IsValid() && !w.IsDynamic()) { // Fixed width table... int CellSpacing = BorderSpacing().ToPx(Min, GetFont()); int Px = ((int)w.Value) + (CellSpacing << 1); Min = MAX(Min, Px); Max = MAX(Max, Px); return true; } else { LPoint s; LHtmlTableLayout c(this); c.GetSize(s.x, s.y); // Auto layout table LArray ColMin, ColMax; for (int y=0; yGetWidthMetrics(Table, a, b)) { ColMin[x] = MAX(ColMin[x], a); ColMax[x] = MAX(ColMax[x], b); } x += t->Cell->Span.x; } else break; } } int MinSum = 0, MaxSum = 0; for (int i=0; iGetWidthMetrics(Table, Min, TagMax); LineWidth += TagMax; if (c->TagId == TAG_BR || c->TagId == TAG_LI) { Max = MAX(Max, LineWidth); LineWidth = 0; } } Max = MAX(Max, LineWidth); Min += MarginPx; Max += MarginPx; return Status; } static void DistributeSize(LArray &a, int Start, int Span, int Size, int Border) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i T Sum(LArray &a) { T s = 0; for (unsigned i=0; iCells) { #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Debug) { //int asd=0; } #endif Cell->Cells = new LHtmlTableLayout(this); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Cell->Cells && Debug) Cell->Cells->Dump(); #endif } if (Cell->Cells) Cell->Cells->LayoutTable(f, Depth); } void LHtmlTableLayout::AllocatePx(int StartCol, int Cols, int MinPx, bool HasToFillAllAvailable) { // Get the existing total size and size of the column set int CurrentTotalX = GetTotalX(); int CurrentSpanX = GetTotalX(StartCol, Cols); int MaxAdditionalPx = AvailableX - CurrentTotalX; if (MaxAdditionalPx <= 0) return; // Calculate the maximum space we have for this column set int AvailPx = (CurrentSpanX + MaxAdditionalPx) - BorderX1 - BorderX2; // Allocate any remaining space... int RemainingPx = MaxAdditionalPx; LArray Growable, NonGrowable, SizeInherit; int GrowablePx = 0; for (int x=StartCol; x 0) { GrowablePx += DiffPx; Growable.Add(x); } else if (MinCol[x] > 0) { NonGrowable.Add(x); } else if (MinCol[x] == 0 && CurrentSpanX < AvailPx) { // Growable.Add(x); } if (SizeCol[x].Type == LCss::LenInherit) SizeInherit.Add(x); } if (GrowablePx < RemainingPx && HasToFillAllAvailable) { if (Growable.Length() == 0) { // Add any suitable non-growable columns as well for (unsigned i=0; i MinCol[Largest]) Largest = i; } Growable.Add(Largest); } } if (Growable.Length()) { // Some growable columns... int Added = 0; // Reasonably increase the size of the columns... for (unsigned i=0; i 0) { AddPx = DiffPx; } else if (DiffPx > 0) { double Ratio = (double)DiffPx / GrowablePx; AddPx = (int) (Ratio * RemainingPx); } else { AddPx = RemainingPx / (int)Growable.Length(); } LAssert(AddPx >= 0); MinCol[x] += AddPx; + LAssert(MinCol[x] >= 0); Added += AddPx; } if (Added < RemainingPx && HasToFillAllAvailable) { // Still more to add, so if (SizeInherit.Length()) { Growable = SizeInherit; } else { int Largest = -1; for (unsigned i=0; i MinCol[Largest]) Largest = x; } Growable.Length(1); Growable[0] = Largest; } int AddPx = (RemainingPx - Added) / (int)Growable.Length(); for (unsigned i=0; i= 0); } else { MinCol[x] += AddPx; + LAssert(MinCol[x] >= 0); Added += AddPx; } - LAssert(MinCol[x] >= 0); } } } } struct ColInfo { int Large; int Growable; int Idx; int Px; }; int ColInfoCmp(ColInfo *a, ColInfo *b) { int LDiff = b->Large - a->Large; int LGrow = b->Growable - a->Growable; int LSize = b->Px - a->Px; return LDiff + LGrow + LSize; } void LHtmlTableLayout::DeallocatePx(int StartCol, int Cols, int MaxPx) { int TotalPx = GetTotalX(StartCol, Cols); if (TotalPx <= MaxPx || MaxPx == 0) return; int TrimPx = TotalPx - MaxPx; LArray Inf; int HalfMax = MaxPx >> 1; unsigned Interesting = 0; int InterestingPx = 0; for (int x=StartCol; x HalfMax; ci.Growable = MinCol[x] < MaxCol[x]; if (ci.Large || ci.Growable) { Interesting++; InterestingPx += ci.Px; } } Inf.Sort(ColInfoCmp); - for (unsigned i=0; i 0) + { + for (unsigned i=0; i= 0); + } + else + break; + } } } int LHtmlTableLayout::GetTotalX(int StartCol, int Cols) { if (Cols < 0) Cols = s.x; int TotalX = BorderX1 + BorderX2 + CellSpacing; for (int x=StartCol; xZeroTableElements(); MinCol.Length(0); MaxCol.Length(0); MaxRow.Length(0); SizeCol.Length(0); LCss::Len BdrSpacing = Table->BorderSpacing(); CellSpacing = BdrSpacing.IsValid() ? (int)BdrSpacing.Value : 0; // Resolve total table width. TableWidth = Table->Width(); if (TableWidth.IsValid()) AvailableX = f->ResolveX(TableWidth, Table, false); else AvailableX = f->X(); LCss::Len MaxWidth = Table->MaxWidth(); if (MaxWidth.IsValid()) { int Px = f->ResolveX(MaxWidth, Table, false); if (Px < AvailableX) AvailableX = Px; } TableBorder = f->ResolveBorder(Table, Table); if (Table->BorderCollapse() != LCss::CollapseCollapse) TablePadding = f->ResolvePadding(Table, Table); else TablePadding.ZOff(0, 0); BorderX1 = TableBorder.x1 + TablePadding.x1; BorderX2 = TableBorder.x2 + TablePadding.x2; #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("AvailableX=%i, BorderX1=%i, BorderX2=%i\n", AvailableX, BorderX1, BorderX2); #endif #ifdef _DEBUG if (Table->Debug) { printf("Table Debug\n"); } #endif // Size detection pass int y; for (y=0; yGetFont(); t->Cell->BorderPx = f->ResolveBorder(t, t); t->Cell->PaddingPx = f->ResolvePadding(t, t); if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { LCss::DisplayType Disp = t->Display(); if (Disp == LCss::DispNone) continue; LCss::Len Content = t->Width(); if (Content.IsValid() && t->Cell->Span.x == 1) { if (SizeCol[x].IsValid()) { int OldPx = f->ResolveX(SizeCol[x], t, false); int NewPx = f->ResolveX(Content, t, false); if (NewPx > OldPx) { SizeCol[x] = Content; } } else { SizeCol[x] = Content; } } if (!t->GetWidthMetrics(Table, t->Cell->MinContent, t->Cell->MaxContent)) { t->Cell->MinContent = 16; t->Cell->MaxContent = 16; } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->Span.x == 1) { int BoxPx = t->Cell->BorderPx.x1 + t->Cell->BorderPx.x2 + t->Cell->PaddingPx.x1 + t->Cell->PaddingPx.x2; MinCol[x] = MAX(MinCol[x], t->Cell->MinContent + BoxPx); + LAssert(MinCol[x] >= 0); MaxCol[x] = MAX(MaxCol[x], t->Cell->MaxContent + BoxPx); } } x += t->Cell->Span.x; } else break; } } // How much space used so far? int TotalX = GetTotalX(); if (TotalX > AvailableX) { // FIXME: // Off -> 'cisra-cqs.html' renders correctly. // On -> 'cisra_outage.html', 'steam1.html' renders correctly. #if 1 DeallocatePx(0, (int)MinCol.Length(), AvailableX); TotalX = GetTotalX(); #endif } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT #define DumpCols(msg) \ if (Table->Debug) \ { \ LgiTrace("%s Ln%i - TotalX=%i AvailableX=%i\n", msg, __LINE__, TotalX, AvailableX); \ for (unsigned i=0; iDebug) { printf("TableDebug\n"); } #endif // Process spanned cells for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) { if (t->Cell->Span.x > 1 || t->Cell->Span.y > 1) { int i; int ColMin = -CellSpacing; int ColMax = -CellSpacing; for (i=0; iCell->Span.x; i++) { ColMin += MinCol[x + i] + CellSpacing; ColMax += MaxCol[x + i] + CellSpacing; } LCss::Len Width = t->Width(); if (Width.IsValid()) { int Px = f->ResolveX(Width, t, false); t->Cell->MinContent = MAX(t->Cell->MinContent, Px); t->Cell->MaxContent = MAX(t->Cell->MaxContent, Px); } #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) LgiTrace("Content[%i,%i] MIN=%i MAX=%i\n", x, y, t->Cell->MinContent, t->Cell->MaxContent); #endif if (t->Cell->MinContent > ColMin) AllocatePx(t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MinContent, false); if (t->Cell->MaxContent > ColMax) DistributeSize(MaxCol, t->Cell->Pos.x, t->Cell->Span.x, t->Cell->MaxContent, CellSpacing); } x += t->Cell->Span.x; } else break; } } TotalX = GetTotalX(); DumpCols("AfterSpannedCells"); // Sometimes the web page specifies too many percentages: // Scale them all. float PercentSum = 0.0f; for (int i=0; i 100.0) { float Ratio = PercentSum / 100.0f; for (int i=0; iResolveX(w, Table, false); if (w.Type == LCss::LenPercent) { MaxCol[x] = Px; } else if (Px > MinCol[x]) { int RemainingPx = AvailableX - TotalX; int AddPx = Px - MinCol[x]; AddPx = MIN(RemainingPx, AddPx); TotalX += AddPx; MinCol[x] += AddPx; + LAssert(MinCol[x] >= 0); } } } } TotalX = GetTotalX(); DumpCols("AfterCssNonPercentageSizes"); if (TotalX > AvailableX) { #if !ALLOW_TABLE_GROWTH // Deallocate space if overused // Take some from the largest column int Largest = 0; for (int i=0; i MinCol[Largest]) { Largest = i; } } int Take = TotalX - AvailableX; if (Take < MinCol[Largest]) { MinCol[Largest] = MinCol[Largest] - Take; + LAssert(MinCol[Largest] >= 0); TotalX -= Take; } DumpCols("AfterSpaceDealloc"); #endif } else if (TotalX < AvailableX) { AllocatePx(0, s.x, AvailableX, TableWidth.IsValid()); DumpCols("AfterRemainingAlloc"); } // Layout cell horizontally and then flow the contents to get // the height of all the cells LArray RowPad; MaxRow.Length(s.y); for (y=0; yCell->Pos.x == x && t->Cell->Pos.y == y) - { - t->Pos.x = XPos; - t->Size.x = -CellSpacing; - XPos -= CellSpacing; + auto t = Get(x, y); + if (!t) + { + x++; + continue; + } + + if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) + { + t->Pos.x = XPos; + t->Size.x = -CellSpacing; + XPos -= CellSpacing; - RowPad[y].y1 = MAX(RowPad[y].y1, t->Cell->BorderPx.y1 + t->Cell->PaddingPx.y1); - RowPad[y].y2 = MAX(RowPad[y].y2, t->Cell->BorderPx.y2 + t->Cell->PaddingPx.y2); + RowPad[y].y1 = MAX(RowPad[y].y1, t->Cell->BorderPx.y1 + t->Cell->PaddingPx.y1); + RowPad[y].y2 = MAX(RowPad[y].y2, t->Cell->BorderPx.y2 + t->Cell->PaddingPx.y2); - LRect Box(0, 0, -CellSpacing, 0); - for (int i=0; iCell->Span.x; i++) - { - int ColSize = MinCol[x + i] + CellSpacing; - LAssert(ColSize >= 0); - if (ColSize < 0) - break; - t->Size.x += ColSize; - XPos += ColSize; - Box.x2 += ColSize; - } + LRect Box(0, 0, -CellSpacing, 0); + for (int i=0; iCell->Span.x; i++) + { + int ColSize = MinCol[x + i] + CellSpacing; + LAssert(ColSize >= 0); + if (ColSize < 0) + break; + t->Size.x += ColSize; + XPos += ColSize; + Box.x2 += ColSize; + } - LCss::Len Ht = t->Height(); - LFlowRegion r(Table->Html, Box, true); - - t->OnFlow(&r, Depth+1); - - if (r.MAX.y > r.y2) - { - t->Size.y = MAX(r.MAX.y, t->Size.y); - } + LCss::Len Ht = t->Height(); + LFlowRegion r(Table->Html, Box, true); + + t->OnFlow(&r, Depth+1); + + if (r.MAX.y > r.y2) + { + t->Size.y = MAX(r.MAX.y, t->Size.y); + } - if (Ht.IsValid() && - Ht.Type != LCss::LenPercent) - { - int h = f->ResolveY(Ht, t, false); - t->Size.y = MAX(h, t->Size.y); - - DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); - } - } - - x += t->Cell->Span.x; - } - else break; + if (Ht.IsValid() && + Ht.Type != LCss::LenPercent) + { + int h = f->ResolveY(Ht, t, false); + t->Size.y = MAX(h, t->Size.y); + + DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); + } + } + + x += t->Cell->Span.x; } } #if defined(_DEBUG) DEBUG_LOG("%s:%i - AfterCellFlow\n", _FL); for (unsigned i=0; iCell->Pos.x == x && t->Cell->Pos.y == y) { LCss::Len Ht = t->Height(); if (!(Ht.IsValid() && Ht.Type != LCss::LenPercent)) { DistributeSize(MaxRow, y, t->Cell->Span.y, t->Size.y, CellSpacing); } } x += t->Cell->Span.x; } else break; } } // Cell positioning int Cx = BorderX1 + CellSpacing; int Cy = TableBorder.y1 + TablePadding.y1 + CellSpacing; for (y=0; yParent); if (Row && Row->TagId == TAG_TR) { t = new LTag(Table->Html, Row); if (t) { t->TagId = TAG_TD; t->Tag.Reset(NewStr("td")); t->Info = Table->Html->GetTagInfo(t->Tag); if ((t->Cell = new LTag::TblCell)) { t->Cell->Pos.x = x; t->Cell->Pos.y = y; t->Cell->Span.x = 1; t->Cell->Span.y = 1; } t->BackgroundColor(LCss::ColorDef(LCss::ColorRgb, DefaultMissingCellColour)); Set(Table); } else break; } else break; } if (t) { if (t->Cell->Pos.x == x && t->Cell->Pos.y == y) { int RowPadOffset = RowPad[y].y1 - t->Cell->BorderPx.y1 - t->Cell->PaddingPx.y1; t->Pos.x = Cx; t->Pos.y = Cy + RowPadOffset; t->Size.x = -CellSpacing; for (int i=0; iCell->Span.x; i++) { int w = MinCol[x + i] + CellSpacing; t->Size.x += w; Cx += w; } t->Size.y = -CellSpacing; for (int n=0; nCell->Span.y; n++) { t->Size.y += MaxRow[y+n] + CellSpacing; } Table->Size.x = MAX(Cx + BorderX2, Table->Size.x); #if defined(_DEBUG) && DEBUG_TABLE_LAYOUT if (Table->Debug) { LgiTrace("cell(%i,%i) = pos(%i,%i)+size(%i,%i)\n", t->Cell->Pos.x, t->Cell->Pos.y, t->Pos.x, t->Pos.y, t->Size.x, t->Size.y); } #endif } else { Cx += t->Size.x + CellSpacing; } x += t->Cell->Span.x; } else break; Prev = t; } Cx = BorderX1 + CellSpacing; Cy += MaxRow[y] + CellSpacing; } switch (Table->Cell->XAlign ? Table->Cell->XAlign : ToTag(Table->Parent)->GetAlign(true)) { case LCss::AlignCenter: { int fx = f->X(); int Ox = (fx-Table->Size.x) >> 1; Table->Pos.x = f->x1 + MAX(Ox, 0); DEBUG_LOG("%s:%i - AlignCenter fx=%i ox=%i pos.x=%i size.x=%i\n", _FL, fx, Ox, Table->Pos.x, Table->Size.x); break; } case LCss::AlignRight: { Table->Pos.x = f->x2 - Table->Size.x; DEBUG_LOG("%s:%i - AlignRight f->x2=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } default: { Table->Pos.x = f->x1; DEBUG_LOG("%s:%i - AlignLeft f->x1=%i size.x=%i pos.x=%i\n", _FL, f->x2, Table->Size.x, Table->Pos.x); break; } } Table->Pos.y = f->y1; Table->Size.y = Cy + TablePadding.y2 + TableBorder.y2; } LRect LTag::ChildBounds() { LRect b(0, 0, -1, -1); for (unsigned i=0; iGetRect(); b.Union(&c); } else { b = t->GetRect(); } } return b; } LPoint LTag::AbsolutePos() { LPoint p; for (LTag *t=this; t; t=ToTag(t->Parent)) { p += t->Pos; } return p; } void LTag::SetSize(LPoint &s) { Size = s; } LHtmlArea::~LHtmlArea() { DeleteObjects(); } LRect LHtmlArea::Bounds() { LRect n(0, 0, -1, -1); for (unsigned i=0; iLength(); i++) { LRect *r = (*c)[i]; if (!Top || (r && (r->y1 < Top->y1))) { Top = r; } } return Top; } void LHtmlArea::FlowText(LTag *Tag, LFlowRegion *Flow, LFont *Font, int LineHeight, char16 *Text, LCss::LengthType Align) { if (!Flow || !Text || !Font) return; SetFixedLength(false); char16 *Start = Text; size_t FullLen = StrlenW(Text); #if 1 if (!Tag->Html->GetReadOnly() && !*Text) { // Insert a text rect for this tag, even though it's empty. // This allows the user to place the cursor on a blank line. LFlowRect *Tr = new LFlowRect; Tr->Tag = Tag; Tr->Text = Text; Tr->x1 = Flow->cx; Tr->x2 = Tr->x1 + 1; Tr->y1 = Flow->y1; Tr->y2 = Tr->y1 + Font->GetHeight(); LAssert(Tr->y2 >= Tr->y1); Flow->y2 = MAX(Flow->y2, Tr->y2+1); Flow->cx = Tr->x2 + 1; Add(Tr); Flow->Insert(Tr, Align); return; } #endif while (*Text) { LFlowRect *Tr = new LFlowRect; if (!Tr) break; Tr->Tag = Tag; Restart: Tr->x1 = Flow->cx; Tr->y1 = Flow->y1; #if 1 // I removed this at one stage but forget why. // Remove white space at start of line if not in edit mode.. if (Tag->Html->GetReadOnly() && Flow->x1 == Flow->cx && *Text == ' ') { Text++; if (!*Text) { DeleteObj(Tr); break; } } #endif Tr->Text = Text; LDisplayString ds(Font, Text, MIN(1024, FullLen - (Text-Start))); ssize_t Chars = ds.CharAt(Flow->X()); bool Wrap = false; if (Text[Chars]) { // Word wrap // Seek back to the nearest break opportunity ssize_t n = Chars; while (n > 0 && !StrchrW(WhiteW, Text[n])) n--; if (n == 0) { if (Flow->x1 == Flow->cx) { // Already started from the margin and it's too long to // fit across the entire page, just let it hang off the right edge. // Seek to the end of the word for (Tr->Len = Chars; Text[Tr->Len] && !StrchrW(WhiteW, Text[Tr->Len]); Tr->Len++) ; // Wrap... if (*Text == ' ') Text++; } else { // Not at the start of the margin Flow->FinishLine(); goto Restart; } } else { Tr->Len = n; LAssert(Tr->Len > 0); Wrap = true; } } else { // Fits.. Tr->Len = Chars; LAssert(Tr->Len > 0); } LDisplayString ds2(Font, Tr->Text, Tr->Len); Tr->x2 = ds2.X(); Tr->y2 = LineHeight > 0 ? LineHeight - 1 : 0; if (Wrap) { Flow->cx = Flow->x1; Flow->y1 += Tr->y2 + 1; Tr->x2 = Flow->x2 - Tag->RelX(); } else { Tr->x2 += Tr->x1 - 1; Flow->cx = Tr->x2 + 1; } Tr->y2 += Tr->y1; Flow->y2 = MAX(Flow->y2, Tr->y2 + 1); Add(Tr); Flow->Insert(Tr, Align); Text += Tr->Len; if (Wrap) { while (*Text == ' ') Text++; } Tag->Size.x = MAX(Tag->Size.x, Tr->x2 + 1); Tag->Size.y = MAX(Tag->Size.y, Tr->y2 + 1); Flow->MAX.x = MAX(Flow->MAX.x, Tr->x2); Flow->MAX.y = MAX(Flow->MAX.y, Tr->y2); if (Tr->Len == 0) break; } SetFixedLength(true); } char16 htoi(char16 c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; LAssert(0); return 0; } bool LTag::Serialize(LXmlTag *t, bool Write) { LRect pos; if (Write) { // Obj -> Tag if (Tag) t->SetAttr("tag", Tag); pos.ZOff(Size.x, Size.y); pos.Offset(Pos.x, Pos.y); t->SetAttr("pos", pos.GetStr()); t->SetAttr("tagid", TagId); if (Txt) { LStringPipe p(256); for (char16 *c = Txt; *c; c++) { if (*c > ' ' && *c < 127 && !strchr("%<>\'\"", *c)) p.Print("%c", (char)*c); else p.Print("%%%.4x", *c); } LAutoString Tmp(p.NewStr()); t->SetContent(Tmp); } if (Props.Length()) { auto CssStyles = ToString(); LAssert(!strchr(CssStyles, '\"')); t->SetAttr("style", CssStyles); } if (Html->Cursor == this) { LAssert(Cursor >= 0); t->SetAttr("cursor", (int64)Cursor); } else LAssert(Cursor < 0); if (Html->Selection == this) { LAssert(Selection >= 0); t->SetAttr("selection", (int64)Selection); } else LAssert(Selection < 0); for (unsigned i=0; iInsertTag(child); if (!tag->Serialize(child, Write)) { return false; } } } else { // Tag -> Obj Tag.Reset(NewStr(t->GetAttr("tag"))); TagId = (HtmlTag) t->GetAsInt("tagid"); pos.SetStr(t->GetAttr("pos")); if (pos.Valid()) { Pos.x = pos.x1; Pos.y = pos.y1; Size.x = pos.x2; Size.y = pos.y2; } if (ValidStr(t->GetContent())) { LStringPipe p(256); char *c = t->GetContent(); SkipWhiteSpace(c); for (; *c && *c > ' '; c++) { char16 ch; if (*c == '%') { ch = 0; for (int i=0; i<4 && *c; i++) { ch <<= 4; ch |= htoi(*++c); } } else ch = *c; p.Write(&ch, sizeof(ch)); } Txt.Reset(p.NewStrW()); } const char *s = t->GetAttr("style"); if (s) Parse(s, ParseRelaxed); s = t->GetAttr("cursor"); if (s) { LAssert(Html->Cursor == NULL); Html->Cursor = this; Cursor = atoi(s); LAssert(Cursor >= 0); } s = t->GetAttr("selection"); if (s) { LAssert(Html->Selection == NULL); Html->Selection = this; Selection = atoi(s); LAssert(Selection >= 0); } #ifdef _DEBUG s = t->GetAttr("debug"); if (s && atoi(s) != 0) Debug = true; #endif for (int i=0; iChildren.Length(); i++) { LXmlTag *child = t->Children[i]; if (child->IsTag("e")) { LTag *tag = new LTag(Html, NULL); if (!tag) { LAssert(0); return false; } if (!tag->Serialize(child, Write)) { return false; } Attach(tag); } } } return true; } /* /// This method centers the text in the area given to the tag. Used for inline block elements. void LTag::CenterText() { if (!Parent) return; // Find the size of the text elements. int ContentPx = 0; for (unsigned i=0; iX(); } LFont *f = GetFont(); int ParentPx = ToTag(Parent)->Size.x; int AvailPx = Size.x; // Remove the border and padding from the content area AvailPx -= BorderLeft().ToPx(ParentPx, f); AvailPx -= BorderRight().ToPx(ParentPx, f); AvailPx -= PaddingLeft().ToPx(ParentPx, f); AvailPx -= PaddingRight().ToPx(ParentPx, f); if (AvailPx > ContentPx) { // Now offset all the regions to the right int OffPx = (AvailPx - ContentPx) >> 1; for (unsigned i=0; iOffset(OffPx, 0); } } } */ void LTag::OnFlow(LFlowRegion *Flow, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH) return; DisplayType Disp = Display(); if (Disp == DispNone) return; LFont *f = GetFont(); LFlowRegion Local(*Flow); bool Restart = true; int BlockFlowWidth = 0; const char *ImgAltText = NULL; Size.x = 0; Size.y = 0; LCssTools Tools(this, f); LRect rc(Flow->X(), Html->Y()); PadPx = Tools.GetPadding(rc); if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } switch (TagId) { default: break; case TAG_BODY: { Flow->InBody++; break; } case TAG_IFRAME: { LFlowRegion Temp = *Flow; Flow->EndBlock(); Flow->Indent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); // Flow children for (unsigned i=0; iOnFlow(&Temp, Depth + 1); if (TagId == TAG_TR) { Temp.x2 -= MIN(t->Size.x, Temp.X()); } } Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); BoundParents(); return; break; } case TAG_TR: { Size.x = Flow->X(); break; } case TAG_IMG: { Size.x = Size.y = 0; LCss::Len w = Width(); LCss::Len h = Height(); // LCss::Len MinX = MinWidth(); // LCss::Len MaxX = MaxWidth(); LCss::Len MinY = MinHeight(); LCss::Len MaxY = MaxHeight(); LAutoPtr a; int ImgX, ImgY; if (Image) { ImgX = Image->X(); ImgY = Image->Y(); } else if (Get("alt", ImgAltText) && ValidStr(ImgAltText)) { LDisplayString a(f, ImgAltText); ImgX = a.X() + 4; ImgY = a.Y() + 4; } else { ImgX = DefaultImgSize; ImgY = DefaultImgSize; } double AspectRatio = ImgY != 0 ? (double)ImgX / ImgY : 1.0; bool XLimit = false, YLimit = false; double Scale = 1.0; if (w.IsValid() && w.Type != LenAuto) { Size.x = Flow->ResolveX(w, this, false); XLimit = true; } else { int Fx = Flow->x2 - Flow->x1 + 1; if (ImgX > Fx) { Size.x = Fx; // * 0.8; if (Image) Scale = (double) Fx / ImgX; } else { Size.x = ImgX; } } XLimit |= Flow->LimitX(Size.x, MinWidth(), MaxWidth(), f); if (h.IsValid() && h.Type != LenAuto) { Size.y = Flow->ResolveY(h, this, false); YLimit = true; } else { Size.y = (int) (ImgY * Scale); } YLimit |= Flow->LimitY(Size.y, MinHeight(), MaxHeight(), f); if ( (XLimit ^ YLimit) && Image ) { if (XLimit) { Size.y = (int) ceil((double)Size.x / AspectRatio); } else { Size.x = (int) ceil((double)Size.y * AspectRatio); } } if (MinY.IsValid()) { int Px = Flow->ResolveY(MinY, this, false); if (Size.y < Px) Size.y = Px; } if (MaxY.IsValid()) { int Px = Flow->ResolveY(MaxY, this, false); if (Size.y > Px) Size.y = Px; } if (Disp == DispInline || Disp == DispInlineBlock) { Restart = false; if (Flow->cx > Flow->x1 && Size.x > Flow->X()) { Flow->FinishLine(); } Pos.y = Flow->y1; Flow->y2 = MAX(Flow->y1, Pos.y + Size.y - 1); LCss::LengthType a = GetAlign(true); switch (a) { case AlignCenter: { int Fx = Flow->x2 - Flow->x1; Pos.x = Flow->x1 + ((Fx - Size.x) / 2); break; } case AlignRight: { Pos.x = Flow->x2 - Size.x; break; } default: { Pos.x = Flow->cx; break; } } } break; } case TAG_HR: { Flow->FinishLine(); Pos.x = Flow->x1; Pos.y = Flow->y1 + 7; Size.x = Flow->X(); Size.y = 2; Flow->cx ++; Flow->y2 += 16; Flow->FinishLine(); return; break; } case TAG_TABLE: { Flow->EndBlock(); LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); LayoutTable(Flow, Depth + 1); Flow->y1 += Size.y; Flow->y2 = Flow->y1; Flow->cx = Flow->x1; Flow->my = 0; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); Flow->Outdent(this, left, top, right, bottom, true); BoundParents(); return; } } if (Disp == DispBlock || Disp == DispInlineBlock) { // This is a block level element, so end the previous non-block elements if (Disp == DispBlock) Flow->EndBlock(); #ifdef _DEBUG if (Debug) LgiTrace("Before %s\n", Flow->ToString().Get()); #endif BlockFlowWidth = Flow->X(); // Indent the margin... LCss::Len left = GetCssLen(MarginLeft, Margin); LCss::Len top = GetCssLen(MarginTop, Margin); LCss::Len right = GetCssLen(MarginRight, Margin); LCss::Len bottom = GetCssLen(MarginBottom, Margin); Flow->Indent(this, left, top, right, bottom, true); // Set the width if any LCss::Len Wid = Width(); if (!IsTableCell(TagId) && Wid.IsValid()) Size.x = Flow->ResolveX(Wid, this, false); else if (TagId != TAG_IMG) { if (Disp == DispInlineBlock) // Flow->Inline) Size.x = 0; // block inside inline-block default to fit the content else Size.x = Flow->X(); } else if (Disp == DispInlineBlock) Size.x = 0; if (MaxWidth().IsValid()) { int Px = Flow->ResolveX(MaxWidth(), this, false); if (Size.x > Px) Size.x = Px; } if (MinWidth().IsValid()) { int Px = Flow->ResolveX(MinWidth(), this, false); if (Size.x < Px) Size.x = Px; } Pos.x = Disp == DispInlineBlock ? Flow->cx : Flow->x1; Pos.y = Flow->y1; Flow->y1 -= Pos.y; Flow->y2 -= Pos.y; if (Disp == DispBlock) { Flow->x1 -= Pos.x; Flow->x2 = Flow->x1 + Size.x; Flow->cx -= Pos.x; Flow->Indent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Flow->Indent(PadPx, false); } else { Flow->x2 = Flow->X(); Flow->x1 = Flow->ResolveX(BorderLeft(), this, true) + Flow->ResolveX(PaddingLeft(), this, true); Flow->cx = Flow->x1; Flow->y1 += Flow->ResolveY(BorderTop(), this, true) + Flow->ResolveY(PaddingTop(), this, true); Flow->y2 = Flow->y1; if (!IsTableTag()) Flow->Inline++; } } else { Flow->Indent(PadPx, false); } if (f) { // Clear the previous text layout... TextPos.DeleteObjects(); switch (TagId) { default: break; case TAG_LI: { // Insert the list marker if (!PreText()) { LCss::ListStyleTypes s = Parent->ListStyleType(); if (s == ListInherit) { if (Parent->TagId == TAG_OL) s = ListDecimal; else if (Parent->TagId == TAG_UL) s = ListDisc; } switch (s) { default: break; case ListDecimal: { ssize_t Index = Parent->Children.IndexOf(this); char Txt[32]; sprintf_s(Txt, sizeof(Txt), "%i. ", (int)(Index + 1)); PreText(Utf8ToWide(Txt)); break; } case ListDisc: { PreText(NewStrW(LHtmlListItem)); break; } } } if (PreText()) TextPos.FlowText(this, Flow, f, f->GetHeight(), PreText(), AlignLeft); break; } case TAG_IMG: { if (Disp == DispBlock) { Flow->cx += Size.x; Flow->y2 += Size.y; } break; } } if (Text() && Flow->InBody) { // Setup the line height cache if (LineHeightCache < 0) { LCss::Len LineHt; LFont *LineFnt = GetFont(); for (LTag *t = this; t && !LineHt.IsValid(); t = ToTag(t->Parent)) { LineHt = t->LineHeight(); if (t->TagId == TAG_TABLE) break; } if (LineFnt) { int FontPx = LineFnt->GetHeight(); if (!LineHt.IsValid() || LineHt.Type == LCss::LenAuto || LineHt.Type == LCss::LenNormal) { LineHeightCache = FontPx; // LgiTrace("LineHeight FontPx=%i Px=%i Auto\n", FontPx, LineHeightCache); } else if (LineHt.Type == LCss::LenPx) { auto Scale = Html->GetDpiScale().y; LineHt.Value *= (float)Scale; LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i (Scale=%f)\n", FontPx, LineHeightCache, Scale); } else { LineHeightCache = LineHt.ToPx(FontPx, f); // LgiTrace("LineHeight FontPx=%i Px=%i ToPx\n", FontPx, LineHeightCache); } } } // Flow in the rest of the text... char16 *Txt = Text(); LCss::LengthType Align = GetAlign(true); TextPos.FlowText(this, Flow, f, LineHeightCache, Txt, Align); #ifdef _DEBUG if (Debug) LgiTrace("%s:%i - %p.size=%p\n", _FL, this, &Size.x); #endif } } // Flow children PostFlowAlign.Length(0); for (unsigned i=0; iPosition()) { case PosStatic: case PosAbsolute: case PosFixed: { LFlowRegion old = *Flow; t->OnFlow(Flow, Depth + 1); // Try and reset the flow to how it was before... Flow->x1 = old.x1; Flow->x2 = old.x2; Flow->cx = old.cx; Flow->y1 = old.y1; Flow->y2 = old.y2; Flow->MAX.x = MAX(Flow->MAX.x, old.MAX.x); Flow->MAX.y = MAX(Flow->MAX.y, old.MAX.y); break; } default: { t->OnFlow(Flow, Depth + 1); break; } } if (TagId == TAG_TR) { Flow->x2 -= MIN(t->Size.x, Flow->X()); } } LCss::LengthType XAlign = GetAlign(true); int FlowSz = Flow->Width(); // Align the children... for (auto &group: PostFlowAlign) { int MinX = FlowSz, MaxX = 0; for (auto &a: group) { MinX = MIN(MinX, a.t->Pos.x); MaxX = MAX(MaxX, a.t->Pos.x + a.t->Size.x - 1); } int TotalX = MaxX - MinX + 1; int FirstX = group.Length() ? group[0].t->Pos.x : 0; for (auto &a: group) { if (a.XAlign == LCss::AlignCenter) { int OffX = (Size.x - TotalX) >> 1; if (OffX > 0) { a.t->Pos.x += OffX; } } else if (a.XAlign == LCss::AlignRight) { int OffX = FlowSz - FirstX - TotalX; if (OffX > 0) { a.t->Pos.x += OffX; } } } } if (Disp == DispBlock || Disp == DispInlineBlock) { LCss::Len Ht = Height(); LCss::Len MaxHt = MaxHeight(); // I dunno, there should be a better way... :-( if (MarginLeft().Type == LenAuto && MarginRight().Type == LenAuto) { XAlign = LCss::AlignCenter; } bool AcceptHt = !IsTableCell(TagId) && Ht.Type != LenPercent; if (AcceptHt) { if (Ht.IsValid()) { int HtPx = Flow->ResolveY(Ht, this, false); if (HtPx > Flow->y2) Flow->y2 = HtPx; } if (MaxHt.IsValid()) { int MaxHtPx = Flow->ResolveY(MaxHt, this, false); if (MaxHtPx < Flow->y2) { Flow->y2 = MaxHtPx; Flow->MAX.y = MIN(Flow->y2, Flow->MAX.y); } } } if (Disp == DispBlock) { Flow->EndBlock(); int OldFlowSize = Flow->x2 - Flow->x1 + 1; Flow->Outdent(this, PaddingLeft(), PaddingTop(), PaddingRight(), PaddingBottom(), false); Flow->Outdent(this, LCss::BorderLeft(), LCss::BorderTop(), LCss::BorderRight(), LCss::BorderBottom(), false); Size.y = Flow->y2 > 0 ? Flow->y2 : 0; Flow->Outdent(this, MarginLeft(), MarginTop(), MarginRight(), MarginBottom(), true); int NewFlowSize = Flow->x2 - Flow->x1 + 1; int Diff = NewFlowSize - OldFlowSize; if (Diff) Flow->MAX.x += Diff; Flow->y1 = Flow->y2; Flow->x2 = Flow->x1 + BlockFlowWidth; } else { LCss::Len Wid = Width(); int WidPx = Wid.IsValid() ? Flow->ResolveX(Wid, this, true) : 0; Size.x = MAX(WidPx, Size.x); Size.x += Flow->ResolveX(PaddingRight(), this, true); Size.x += Flow->ResolveX(BorderRight(), this, true); int MarginR = Flow->ResolveX(MarginRight(), this, true); int MarginB = Flow->ResolveX(MarginBottom(), this, true); Flow->x1 = Local.x1 - Pos.x; Flow->cx = Local.cx + Size.x + MarginR - Pos.x; Flow->x2 = Local.x2 - Pos.x; if (Height().IsValid()) { Size.y = Flow->ResolveY(Height(), this, false); Flow->y2 = MAX(Flow->y1 + Size.y + MarginB - 1, Flow->y2); } else { Flow->y2 += Flow->ResolveX(PaddingBottom(), this, true); Flow->y2 += Flow->ResolveX(BorderBottom(), this, true); Size.y = Flow->y2; } Flow->y1 = Local.y1 - Pos.y; Flow->y2 = MAX(Local.y2, Flow->y1+Size.y-1); if (!IsTableTag()) Flow->Inline--; } // Can't do alignment here because pos is used to // restart the parents flow region... } else { Flow->Outdent(PadPx, false); switch (TagId) { default: break; case TAG_SELECT: case TAG_INPUT: { if (Html->InThread() && Ctrl) { LRect r = Ctrl->GetPos(); if (Width().IsValid()) Size.x = Flow->ResolveX(Width(), this, false); else Size.x = r.X(); if (Height().IsValid()) Size.y = Flow->ResolveY(Height(), this, false); else Size.y = r.Y(); if (Html->IsAttached() && !Ctrl->IsAttached()) Ctrl->Attach(Html); } Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_IMG: { Flow->cx += Size.x; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_BR: { int OldFlowY2 = Flow->y2; Flow->FinishLine(); Size.y = Flow->y2 - OldFlowY2; Flow->y2 = MAX(Flow->y2, Flow->y1 + Size.y - 1); break; } case TAG_CENTER: { int Px = Flow->X(); for (auto e: Children) { LTag *t = ToTag(e); if (t && t->IsBlock() && t->Size.x < Px) { t->Pos.x = (Px - t->Size.x) >> 1; } } break; } } } BoundParents(); if (Restart) { Flow->x1 += Pos.x; Flow->x2 += Pos.x; Flow->cx += Pos.x; Flow->y1 += Pos.y; Flow->y2 += Pos.y; Flow->MAX.y = MAX(Flow->MAX.y, Flow->y2); } if (Disp == DispBlock || Disp == DispInlineBlock) { if (XAlign == LCss::AlignCenter || XAlign == LCss::AlignRight) { int Match = 0; auto parent = ToTag(Parent); for (auto &grp: parent->PostFlowAlign) { bool Overlaps = false; for (auto &a: grp) { if (a.Overlap(this)) { Overlaps = true; break; } } if (!Overlaps) Match++; } auto &grp = parent->PostFlowAlign[Match]; if (grp.Length() == 0) { grp.x1 = Flow->x1; grp.x2 = Flow->x2; } auto &pf = grp.New(); pf.Disp = Disp; pf.XAlign = XAlign; pf.t = this; } } if (TagId == TAG_BODY && Flow->InBody > 0) { Flow->InBody--; } } bool LTag::PeekTag(char *s, char *tag) { bool Status = false; if (s && tag) { if (*s == '<') { char *t = 0; Html->ParseName(++s, &t); if (t) { Status = _stricmp(t, tag) == 0; } DeleteArray(t); } } return Status; } LTag *LTag::GetTable() { LTag *t = 0; for (t=ToTag(Parent); t && t->TagId != TAG_TABLE; t = ToTag(t->Parent)) ; return t; } void LTag::BoundParents() { if (!Parent) return; LTag *np; for (LTag *n=this; n; n = np) { np = ToTag(n->Parent); if (!np || np->TagId == TAG_IFRAME) break; np->Size.x = MAX(np->Size.x, n->Pos.x + n->Size.x); np->Size.y = MAX(np->Size.y, n->Pos.y + n->Size.y); } } struct DrawBorder { LSurface *pDC; uint32_t LineStyle; uint32_t LineReset; uint32_t OldStyle; DrawBorder(LSurface *pdc, LCss::BorderDef &d) { LineStyle = 0xffffffff; LineReset = 0x80000000; if (d.Style == LCss::BorderDotted) { switch ((int)d.Value) { case 2: { LineStyle = 0xcccccccc; break; } case 3: { LineStyle = 0xe38e38; LineReset = 0x800000; break; } case 4: { LineStyle = 0xf0f0f0f0; break; } case 5: { LineStyle = 0xf83e0; LineReset = 0x80000; break; } case 6: { LineStyle = 0xfc0fc0; LineReset = 0x800000; break; } case 7: { LineStyle = 0xfe03f80; LineReset = 0x8000000; break; } case 8: { LineStyle = 0xff00ff00; break; } case 9: { LineStyle = 0x3fe00; LineReset = 0x20000; break; } default: { LineStyle = 0xaaaaaaaa; break; } } } pDC = pdc; OldStyle = pDC->LineStyle(); } ~DrawBorder() { pDC->LineStyle(OldStyle); } }; void LTag::GetInlineRegion(LRegion &rgn, int ox, int oy) { if (TagId == TAG_IMG) { LRect rc(0, 0, Size.x-1, Size.y-1); rc.Offset(ox + Pos.x, oy + Pos.y); rgn.Union(&rc); } else { for (unsigned i=0; iGetInlineRegion(rgn, ox + Pos.x, oy + Pos.y); } } class CornersImg : public LMemDC { public: int Px, Px2; CornersImg( float RadPx, LRect *BorderPx, LCss::BorderDef **defs, LColour &Back, bool DrawBackground) { Px = 0; Px2 = 0; //Radius.Type != LCss::LenInherit && if (RadPx > 0.0f) { Px = (int)ceil(RadPx); Px2 = Px << 1; if (Create(Px2, Px2, System32BitColourSpace)) { #if 1 Colour(0, 32); #else Colour(LColour(255, 0, 255)); #endif Rectangle(); LPointF ctr(Px, Px); LPointF LeftPt(0.0, Px); LPointF TopPt(Px, 0.0); LPointF RightPt(X(), Px); LPointF BottomPt(Px, Y()); int x_px[4] = {BorderPx->x1, BorderPx->x2, BorderPx->x2, BorderPx->x1}; int y_px[4] = {BorderPx->y1, BorderPx->y1, BorderPx->y2, BorderPx->y2}; LPointF *pts[4] = {&LeftPt, &TopPt, &RightPt, &BottomPt}; // Draw border parts.. for (int i=0; i<4; i++) { int k = (i + 1) % 4; // Setup the stops LBlendStop stops[2] = { {0.0, 0}, {1.0, 0} }; uint32_t iColour = defs[i]->Color.IsValid() ? defs[i]->Color.Rgb32 : Back.c32(); uint32_t kColour = defs[k]->Color.IsValid() ? defs[k]->Color.Rgb32 : Back.c32(); if (defs[i]->IsValid() && defs[k]->IsValid()) { stops[0].c32 = iColour; stops[1].c32 = kColour; } else if (defs[i]->IsValid()) { stops[0].c32 = stops[1].c32 = iColour; } else { stops[0].c32 = stops[1].c32 = kColour; } // Create a brush LLinearBlendBrush br ( *pts[i], *pts[k], 2, stops ); // Setup the clip LRect clip( (int)MIN(pts[i]->x, pts[k]->x), (int)MIN(pts[i]->y, pts[k]->y), (int)MAX(pts[i]->x, pts[k]->x)-1, (int)MAX(pts[i]->y, pts[k]->y)-1); ClipRgn(&clip); // Draw the arc... LPath p; p.Circle(ctr, Px); if (defs[i]->IsValid() || defs[k]->IsValid()) p.Fill(this, br); // Fill the background p.Empty(); p.Ellipse(ctr, Px-x_px[i], Px-y_px[i]); if (DrawBackground) { LSolidBrush br(Back); p.Fill(this, br); } else { LEraseBrush br; p.Fill(this, br); } ClipRgn(NULL); } #ifdef MAC ConvertPreMulAlpha(true); #endif #if 0 static int count = 0; LString file; file.Printf("c:\\temp\\img-%i.bmp", ++count); GdcD->Save(file, Corners); #endif } } } }; void LTag::PaintBorderAndBackground(LSurface *pDC, LColour &Back, LRect *BorderPx) { LArray r; LRect BorderPxRc; bool DrawBackground = !Back.IsTransparent(); #ifdef _DEBUG if (Debug) { //int asd=0; } #endif if (!BorderPx) BorderPx = &BorderPxRc; BorderPx->ZOff(0, 0); // Get all the border info and work out the pixel sizes. LFont *f = GetFont(); #define DoEdge(coord, axis, name) \ BorderDef name = Border##name(); \ BorderPx->coord = name.Style != LCss::BorderNone ? name.ToPx(Size.axis, f) : 0; #define BorderValid(name) \ ((name).IsValid() && (name).Style != LCss::BorderNone) DoEdge(x1, x, Left); DoEdge(y1, y, Top); DoEdge(x2, x, Right); DoEdge(y2, y, Bottom); LCss::BorderDef *defs[4] = {&Left, &Top, &Right, &Bottom}; if (BorderValid(Left) || BorderValid(Right) || BorderValid(Top) || BorderValid(Bottom) || DrawBackground) { // Work out the rectangles switch (Display()) { case DispInlineBlock: case DispBlock: { r[0].ZOff(Size.x-1, Size.y-1); break; } case DispInline: { LRegion rgn; GetInlineRegion(rgn); if (BorderPx) { for (int i=0; ix1 -= BorderPx->x1 + PadPx.x1; r->y1 -= BorderPx->y1 + PadPx.y1; r->x2 += BorderPx->x2 + PadPx.x2; r->y2 += BorderPx->y2 + PadPx.y2; } } r.Length(rgn.Length()); auto p = r.AddressOf(); for (auto i = rgn.First(); i; i = rgn.Next()) *p++ = *i; break; } default: return; } // If we are drawing rounded corners, draw them into a memory context LAutoPtr Corners; int Px = 0, Px2 = 0; LCss::Len Radius = BorderRadius(); float RadPx = Radius.Type == LCss::LenPx ? Radius.Value : Radius.ToPx(Size.x, GetFont()); bool HasRadius = Radius.Type != LCss::LenInherit && RadPx > 0.0f; // Loop over the rectangles and draw everything int Op = pDC->Op(GDC_ALPHA); for (unsigned i=0; i rc.Y()) { Px = rc.Y() / 2; Px2 = Px << 1; } if (!Corners || Corners->Px2 != Px2) { Corners.Reset(new CornersImg((float)Px, BorderPx, defs, Back, DrawBackground)); } // top left LRect r(0, 0, Px-1, Px-1); pDC->Blt(rc.x1, rc.y1, Corners, &r); // top right r.Set(Px, 0, Corners->X()-1, Px-1); pDC->Blt(rc.x2-Px+1, rc.y1, Corners, &r); // bottom left r.Set(0, Px, Px-1, Corners->Y()-1); pDC->Blt(rc.x1, rc.y2-Px+1, Corners, &r); // bottom right r.Set(Px, Px, Corners->X()-1, Corners->Y()-1); pDC->Blt(rc.x2-Px+1, rc.y2-Px+1, Corners, &r); #if 1 pDC->Colour(Back); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #else pDC->Colour(LColour(255, 0, 0, 0x80)); pDC->Rectangle(rc.x1+Px, rc.y1, rc.x2-Px, rc.y2); pDC->Colour(LColour(0, 255, 0, 0x80)); pDC->Rectangle(rc.x1, rc.y1+Px, rc.x1+Px-1, rc.y2-Px); pDC->Colour(LColour(0, 0, 255, 0x80)); pDC->Rectangle(rc.x2-Px+1, rc.y1+Px, rc.x2, rc.y2-Px); #endif } else if (DrawBackground) { pDC->Colour(Back); pDC->Rectangle(&rc); } LCss::BorderDef *b; if ((b = &Left) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1 + i, rc.y1+Px, rc.x1+i, rc.y2-Px); } } if ((b = &Top) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y1+i, rc.x2-Px, rc.y1+i); } } if ((b = &Right) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x2-i, rc.y1+Px, rc.x2-i, rc.y2-Px); } } if ((b = &Bottom) && BorderValid(*b)) { pDC->Colour(b->Color.Rgb32, 32); DrawBorder db(pDC, *b); for (int i=0; iValue; i++) { pDC->LineStyle(db.LineStyle, db.LineReset); pDC->Line(rc.x1+Px, rc.y2-i, rc.x2-Px, rc.y2-i); } } } pDC->Op(Op); } } static void FillRectWithImage(LSurface *pDC, LRect *r, LSurface *Image, LCss::RepeatType Repeat) { int Px = 0, Py = 0; int Old = pDC->Op(GDC_ALPHA); if (!Image) return; switch (Repeat) { default: case LCss::RepeatBoth: { for (int y=0; yY(); y += Image->Y()) { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py + y, Image); } } break; } case LCss::RepeatX: { for (int x=0; xX(); x += Image->X()) { pDC->Blt(Px + x, Py, Image); } break; } case LCss::RepeatY: { for (int y=0; yY(); y += Image->Y()) { pDC->Blt(Px, Py + y, Image); } break; } case LCss::RepeatNone: { pDC->Blt(Px, Py, Image); break; } } pDC->Op(Old); } void LTag::OnPaint(LSurface *pDC, bool &InSelection, uint16 Depth) { if (Depth >= MAX_RECURSION_DEPTH || Display() == DispNone) return; if ( #ifdef _DEBUG !Html->_Debug && #endif LCurrentTime() - Html->PaintStart > Html->d->MaxPaintTime) { Html->d->MaxPaintTimeout = true; return; } int Px, Py; pDC->GetOrigin(Px, Py); #if 0 if (Debug) { Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; Html->WindowVirtualOffset(&Offset); LRect cli; pDC->GetClient(&cli); printf("\tTag paint mx=%g,%g off=%i,%i p=%i,%i Pos=%i,%i cli=%s\n", mx.x0, mx.y0, Offset.x, Offset.y, Px, Py, Pos.x, Pos.y, cli.GetStr()); } #endif switch (TagId) { case TAG_INPUT: case TAG_SELECT: { if (Ctrl) { int64 Sx = 0, Sy = 0; int64 LineY = GetFont()->GetHeight(); Html->GetScrollPos(Sx, Sy); Sx *= LineY; Sy *= LineY; LRect r(0, 0, Size.x-1, Size.y-1), Px; LColour back = _Colour(false); PaintBorderAndBackground(pDC, back, &Px); if (!dynamic_cast(Ctrl)) { r.x1 += Px.x1; r.y1 += Px.y1; r.x2 -= Px.x2; r.y2 -= Px.y2; } r.Offset(AbsX() - (int)Sx, AbsY() - (int)Sy); Ctrl->SetPos(r); } if (TagId == TAG_SELECT) return; break; } case TAG_BODY: { auto b = _Colour(false); if (!b.IsTransparent()) { pDC->Colour(b); pDC->Rectangle(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } if (Image) { LRect r; r.ZOff(Size.x-1, Size.y-1); FillRectWithImage(pDC, &r, Image, BackgroundRepeat()); } break; } case TAG_HEAD: { // Nothing under here to draw. return; } case TAG_HR: { pDC->Colour(L_MED); pDC->Rectangle(0, 0, Size.x - 1, Size.y - 1); break; } case TAG_TR: case TAG_TBODY: case TAG_META: { // Draws nothing... break; } case TAG_IMG: { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); if (Image) { #if ENABLE_IMAGE_RESIZING if ( !ImageResized && ( Size.x != Image->X() || Size.y != Image->Y() ) ) { ImageResized = true; LColourSpace Cs = Image->GetColourSpace(); if (Cs == CsIndex8 && Image->AlphaDC()) Cs = System32BitColourSpace; LAutoPtr r(new LMemDC(Size.x, Size.y, Cs)); if (r) { if (Cs == CsIndex8) r->Palette(new LPalette(Image->Palette())); ResampleDC(r, Image); Image = r; } } #endif int Old = pDC->Op(GDC_ALPHA); pDC->Blt(0, 0, Image); pDC->Op(Old); } else if (Size.x > 1 && Size.y > 1) { LRect b(0, 0, Size.x-1, Size.y-1); LColour Fill(LColour(L_MED).Mix(LColour(L_LIGHT), 0.2f)); LColour Border(L_MED); // Border pDC->Colour(Border); pDC->Box(&b); b.Inset(1, 1); pDC->Box(&b); b.Inset(1, 1); pDC->Colour(Fill); pDC->Rectangle(&b); const char *Alt; LColour Red(LColour(255, 0, 0).Mix(Fill, 0.3f)); if (Get("alt", Alt) && ValidStr(Alt)) { LDisplayString Ds(Html->GetFont(), Alt); Html->GetFont()->Colour(Red, Fill); Ds.Draw(pDC, 2, 2, &b); } else if (Size.x >= 16 && Size.y >= 16) { // Red 'x' int Cx = b.x1 + (b.X()/2); int Cy = b.y1 + (b.Y()/2); LRect c(Cx-4, Cy-4, Cx+4, Cy+4); pDC->Colour(Red); pDC->Line(c.x1, c.y1, c.x2, c.y2); pDC->Line(c.x1, c.y2, c.x2, c.y1); pDC->Line(c.x1, c.y1 + 1, c.x2 - 1, c.y2); pDC->Line(c.x1 + 1, c.y1, c.x2, c.y2 - 1); pDC->Line(c.x1 + 1, c.y2, c.x2, c.y1 + 1); pDC->Line(c.x1, c.y2 - 1, c.x2 - 1, c.y1); } } pDC->ClipRgn(0); break; } default: { LColour fore = _Colour(true); LColour back = _Colour(false); if (Display() == DispBlock && Html->Environment) { LCss::ImageDef Img = BackgroundImage(); if (Img.Img) { LRect Clip(0, 0, Size.x-1, Size.y-1); pDC->ClipRgn(&Clip); FillRectWithImage(pDC, &Clip, Img.Img, BackgroundRepeat()); pDC->ClipRgn(NULL); back.Empty(); } } PaintBorderAndBackground(pDC, back, NULL); LFont *f = GetFont(); #if DEBUG_TEXT_AREA bool IsEditor = Html ? !Html->GetReadOnly() : false; #else bool IsEditor = false; #endif if (f && TextPos.Length()) { // This is the non-display part of the font bounding box int LeadingPx = (int)(f->Leading() + 0.5); // This is the displayable part of the font int FontPx = f->GetHeight() - LeadingPx; // This is the pixel height we're aiming to fill int EffectiveLineHt = LineHeightCache >= 0 ? MAX(FontPx, LineHeightCache) : FontPx; // This gets added to the y coord of each piece of text int LineHtOff = ((EffectiveLineHt - FontPx + 1) >> 1) - LeadingPx; #define FontColour(InSelection) \ f->Transparent(!InSelection && !IsEditor); \ if (InSelection) \ f->Colour(L_FOCUS_SEL_FORE, L_FOCUS_SEL_BACK); \ else \ { \ LColour bk(back.IsTransparent() ? LColour(L_WORKSPACE) : back); \ LColour fr(fore.IsTransparent() ? LColour(DefaultTextColour) : fore); \ if (IsEditor) \ bk = bk.Mix(LColour::Black, 0.05f); \ f->Colour(fr, bk); \ } if (Html->HasSelection() && (Selection >= 0 || Cursor >= 0) && Selection != Cursor) { ssize_t Min = -1; ssize_t Max = -1; ssize_t Base = GetTextStart(); if (Cursor >= 0 && Selection >= 0) { Min = MIN(Cursor, Selection) + Base; Max = MAX(Cursor, Selection) + Base; } else if (InSelection) { Max = MAX(Cursor, Selection) + Base; } else { Min = MAX(Cursor, Selection) + Base; } LRect CursorPos; CursorPos.ZOff(-1, -1); for (unsigned i=0; iText - Text(); ssize_t Done = 0; int x = Tr->x1; if (Tr->Len == 0) { // Is this a selection edge point? if (!InSelection && Min == 0) { InSelection = !InSelection; } else if (InSelection && Max == 0) { InSelection = !InSelection; } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } break; } while (Done < Tr->Len) { ssize_t c = Tr->Len - Done; FontColour(InSelection); // Is this a selection edge point? if ( !InSelection && Min - Start >= Done && Min - Start < Done + Tr->Len) { InSelection = !InSelection; c = Min - Start - Done; } else if ( InSelection && Max - Start >= Done && Max - Start <= Tr->Len) { InSelection = !InSelection; c = Max - Start - Done; } // Draw the text run LDisplayString ds(f, Tr->Text + Done, c); if (IsEditor) { LRect r(x, Tr->y1, x + ds.X() - 1, Tr->y2); ds.Draw(pDC, x, Tr->y1 + LineHtOff, &r); } else { ds.Draw(pDC, x, Tr->y1 + LineHtOff); } x += ds.X(); Done += c; // Is this is end of the tag? if (Tr->Len == Done) { // Is it also a selection edge? if ( !InSelection && Min - Start == Done) { InSelection = !InSelection; } else if ( InSelection && Max - Start == Done) { InSelection = !InSelection; } } if (Cursor >= 0) { // Is this the cursor, then draw it and save it's position if (Cursor == Start + Done - Base) { Html->d->CursorPos.Set(x, Tr->y1 + LineHtOff, x + 1, Tr->y2 - LineHtOff); if (Html->d->CursorPos.x1 > Tr->x2) Html->d->CursorPos.Offset(Tr->x2 - Html->d->CursorPos.x1, 0); CursorPos = Html->d->CursorPos; Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } } if (Html->d->CursorVis && CursorPos.Valid()) { pDC->Colour(L_TEXT); pDC->Rectangle(&CursorPos); } } else if (Cursor >= 0) { FontColour(InSelection); ssize_t Base = GetTextStart(); for (unsigned i=0; iText - Text()) - Base; LAssert(Tr->y2 >= Tr->y1); LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); if ( ( Tr->Text == PreText() && !ValidStrW(Text()) ) || ( Cursor >= Pos && Cursor <= Pos + Tr->Len ) ) { ssize_t Off = Tr->Text == PreText() ? StrlenW(PreText()) : Cursor - Pos; pDC->Colour(L_TEXT); LRect c; if (Off) { LDisplayString ds(f, Tr->Text, Off); int x = ds.X(); if (x >= Tr->X()) x = Tr->X()-1; c.Set(Tr->x1 + x, Tr->y1, Tr->x1 + x + 1, Tr->y1 + f->GetHeight()); } else { c.Set(Tr->x1, Tr->y1, Tr->x1 + 1, Tr->y1 + f->GetHeight()); } Html->d->CursorPos = c; if (Html->d->CursorVis) pDC->Rectangle(&c); Html->d->CursorPos.Offset(AbsX(), AbsY()); } } } else { FontColour(InSelection); for (auto &Tr: TextPos) { LDisplayString ds(f, Tr->Text, Tr->Len); ds.Draw(pDC, Tr->x1, Tr->y1 + LineHtOff, IsEditor ? Tr : NULL); } } } break; } } #if DEBUG_TABLE_LAYOUT && 0 if (IsTableCell(TagId)) { LTag *Tbl = this; while (Tbl->TagId != TAG_TABLE && Tbl->Parent) Tbl = Tbl->Parent; if (Tbl && Tbl->TagId == TAG_TABLE && Tbl->Debug) { pDC->Colour(LColour(255, 0, 0)); pDC->Box(0, 0, Size.x-1, Size.y-1); } } #endif for (unsigned i=0; iSetOrigin(Px - t->Pos.x, Py - t->Pos.y); t->OnPaint(pDC, InSelection, Depth + 1); pDC->SetOrigin(Px, Py); } #if DEBUG_DRAW_TD if (TagId == TAG_TD) { LTag *Tbl = this; while (Tbl && Tbl->TagId != TAG_TABLE) Tbl = ToTag(Tbl->Parent); if (Tbl && Tbl->Debug) { int Ls = pDC->LineStyle(LSurface::LineDot); pDC->Colour(LColour::Blue); pDC->Box(0, 0, Size.x-1, Size.y-1); pDC->LineStyle(Ls); } } #endif } ////////////////////////////////////////////////////////////////////// LHtml::LHtml(int id, int x, int y, int cx, int cy, LDocumentEnv *e) : LDocView(e), ResObject(Res_Custom), LHtmlParser(NULL) { View = this; d = new LHtmlPrivate; SetReadOnly(true); ViewWidth = -1; SetId(id); LRect r(x, y, x+cx, y+cy); SetPos(r); Cursor = 0; Selection = 0; DocumentUid = 0; _New(); } LHtml::~LHtml() { _Delete(); DeleteObj(d); if (JobSem.Lock(_FL)) { JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } } void LHtml::_New() { d->StyleDirty = false; d->IsLoaded = false; d->Content.x = d->Content.y = 0; d->DeferredLoads = 0; Tag = 0; DocCharSet.Reset(); IsHtml = true; #ifdef DefaultFont LFont *Def = new LFont; if (Def) { if (Def->CreateFromCss(DefaultFont)) SetFont(Def, true); else DeleteObj(Def); } #endif FontCache = new LFontCache(this); SetScrollBars(false, false); } void LHtml::_Delete() { LAssert(!d->IsParsing); CssStore.Empty(); CssHref.Empty(); OpenTags.Length(0); Source.Reset(); DeleteObj(Tag); DeleteObj(FontCache); } LFont *LHtml::DefFont() { return GetFont(); } void LHtml::OnAddStyle(const char *MimeType, const char *Styles) { if (Styles) { const char *c = Styles; bool Status = CssStore.Parse(c); if (Status) { d->StyleDirty = true; } #if 0 // def _DEBUG bool LogCss = false; if (!Status) { char p[MAX_PATH_LEN]; sprintf_s(p, sizeof(p), "c:\\temp\\css_parse_failure_%i.txt", LRand()); LFile f; if (f.Open(p, O_WRITE)) { f.SetSize(0); if (CssStore.Error) f.Print("Error: %s\n\n", CssStore.Error.Get()); f.Write(Styles, strlen(Styles)); f.Close(); } } if (LogCss) { LStringPipe p; CssStore.Dump(p); LAutoString a(p.NewStr()); LFile f; if (f.Open("C:\\temp\\css.txt", O_WRITE)) { f.Write(a, strlen(a)); f.Close(); } } #endif } } void LHtml::ParseDocument(const char *Doc) { if (!Tag) { Tag = new LTag(this, 0); } if (GetCss()) GetCss()->DeleteProp(LCss::PropBackgroundColor); if (Tag) { Tag->TagId = ROOT; OpenTags.Length(0); if (IsHtml) { Parse(Tag, Doc); // Add body tag if not specified... LTag *Html = Tag->GetTagByName("html"); LTag *Body = Tag->GetTagByName("body"); if (!Html && !Body) { if ((Html = new LTag(this, 0))) Html->SetTag("html"); if ((Body = new LTag(this, Html))) Body->SetTag("body"); Html->Attach(Body); if (Tag->Text()) { LTag *Content = new LTag(this, Body); if (Content) { Content->TagId = CONTENT; Content->Text(NewStrW(Tag->Text())); } } while (Tag->Children.Length()) { LTag *t = ToTag(Tag->Children.First()); Body->Attach(t, Body->Children.Length()); } DeleteObj(Tag); Tag = Html; } else if (!Body) { if ((Body = new LTag(this, Html))) Body->SetTag("body"); for (unsigned i=0; iChildren.Length(); i++) { LTag *t = ToTag(Html->Children[i]); if (t->TagId != TAG_HEAD) { Body->Attach(t); i--; } } Html->Attach(Body); } if (Html && Body) { char16 *t = Tag->Text(); if (t) { if (ValidStrW(t)) { LTag *Content = new LTag(this, 0); if (Content) { Content->Text(NewStrW(Tag->Text())); Body->Attach(Content, 0); } } Tag->Text(0); } #if 0 // Enabling this breaks the test file 'gw2.html'. for (LTag *t = Html->Tags.First(); t; ) { if (t->Tag && t->Tag[0] == '!') { Tag->Attach(t, 0); t = Html->Tags.Current(); } else if (t->TagId != TAG_HEAD && t != Body) { if (t->TagId == TAG_HTML) { LTag *c; while ((c = t->Tags.First())) { Html->Attach(c, 0); } t->Detach(); DeleteObj(t); } else { t->Detach(); Body->Attach(t); } t = Html->Tags.Current(); } else { t = Html->Tags.Next(); } } #endif if (Environment) { const char *OnLoad; if (Body->Get("onload", OnLoad)) { Environment->OnExecuteScript(this, (char*)OnLoad); } } } } else { Tag->ParseText(Source); } } ViewWidth = -1; if (Tag) Tag->ResetCaches(); Invalidate(); } bool LHtml::NameW(const char16 *s) { LAutoPtr utf(WideToUtf8(s)); return Name(utf); } const char16 *LHtml::NameW() { LBase::Name(Source); return LBase::NameW(); } bool LHtml::Name(const char *s) { int Uid = -1; if (Environment) Uid = Environment->NextUid(); if (Uid < 0) Uid = GetDocumentUid() + 1; SetDocumentUid(Uid); _Delete(); _New(); IsHtml = false; // Detect HTML const char *c = s; while ((c = strchr(c, '<'))) { char *t = 0; c = ParseName((char*) ++c, &t); if (t && GetTagInfo(t)) { DeleteArray(t); IsHtml = true; break; } DeleteArray(t); } // Parse d->IsParsing = true; ParseDocument(s); d->IsParsing = false; if (Tag && d->StyleDirty) { d->StyleDirty = false; Tag->RestyleAll(); } if (d->DeferredLoads == 0) { OnLoad(); } Invalidate(); return true; } const char *LHtml::Name() { if (!Source && Tag) { LStringPipe s(1024); Tag->CreateSource(s); Source.Reset(s.NewStr()); } return Source; } LMessage::Result LHtml::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_COPY: { Copy(); break; } case M_JOBS_LOADED: { bool Update = false; int InitDeferredLoads = d->DeferredLoads; if (JobSem.Lock(_FL)) { for (unsigned i=0; iUserData); if (j->UserUid == MyUid && j->UserData != NULL) { Html1::LTag *r = static_cast(j->UserData); if (d->DeferredLoads > 0) d->DeferredLoads--; // Check the tag is still in our tree... if (Tag->HasChild(r)) { // Process the returned data... if (r->TagId == TAG_IMG) { if (j->pDC) { r->SetImage(j->Uri, j->pDC.Release()); ViewWidth = 0; Update = true; } else if (j->Stream) { LAutoPtr pDC(GdcD->Load(dynamic_cast(j->Stream.Get()))); if (pDC) { r->SetImage(j->Uri, pDC.Release()); ViewWidth = 0; Update = true; } else LgiTrace("%s:%i - Image decode failed for '%s'\n", _FL, j->Uri.Get()); } else if (j->Status == LDocumentEnv::LoadJob::JobOk) LgiTrace("%s:%i - Unexpected job type for '%s'\n", _FL, j->Uri.Get()); } else if (r->TagId == TAG_LINK) { if (!CssHref.Find(j->Uri)) { LStreamI *s = j->GetStream(); if (s) { s->ChangeThread(); int Size = (int)s->GetSize(); LAutoString Style(new char[Size+1]); ssize_t rd = s->Read(Style, Size); if (rd > 0) { Style[rd] = 0; CssHref.Add(j->Uri, true); OnAddStyle("text/css", Style); ViewWidth = 0; Update = true; } } } } else if (r->TagId == TAG_IFRAME) { // Remote IFRAME loading not support for security reasons. } else LgiTrace("%s:%i - Unexpected tag '%s' for URI '%s'\n", _FL, r->Tag.Get(), j->Uri.Get()); } else { /* Html1::LTag *p = ToTag(r->Parent); while (p && p->Parent) p = ToTag(p->Parent); */ LgiTrace("%s:%i - No child tag for job.\n", _FL); } } // else it's from another (historical) HTML control, ignore } JobSem.Jobs.DeleteObjects(); JobSem.Unlock(); } if (InitDeferredLoads > 0 && d->DeferredLoads <= 0) { LAssert(d->DeferredLoads == 0); d->DeferredLoads = 0; OnLoad(); } if (Update) { OnPosChange(); Invalidate(); } break; } } return LDocView::OnEvent(Msg); } int LHtml::OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_VSCROLL: { int LineY = GetFont()->GetHeight(); if (Tag) Tag->ClearToolTips(); if (n.Type == LNotifyScrollBarCreate && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = d->Content.y / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } Invalidate(); break; } default: { LTag *Ctrl = Tag ? Tag->FindCtrlId(c->GetId()) : NULL; if (Ctrl) return Ctrl->OnNotify(n); break; } } return LLayout::OnNotify(c, n); } void LHtml::OnPosChange() { LLayout::OnPosChange(); if (ViewWidth != X()) { Invalidate(); } } LPoint LHtml::Layout(bool ForceLayout) { LRect Client = GetClient(); if (Tag && (ViewWidth != Client.X() || ForceLayout)) { LFlowRegion f(this, Client, false); // Flow text, width is different Tag->OnFlow(&f, 0); ViewWidth = Client.X(); d->Content.x = f.MAX.x + 1; d->Content.y = f.MAX.y + 1; // Set up scroll box bool Sy = f.y2 > Y(); int LineY = GetFont()->GetHeight(); uint64 Now = LCurrentTime(); if (Now - d->SetScrollTime > 100) { d->SetScrollTime = Now; SetScrollBars(false, Sy); if (Sy && VScroll && LineY > 0) { int y = Y(); int p = MAX(y / LineY, 1); int fy = f.y2 / LineY; VScroll->SetPage(p); VScroll->SetRange(fy); } } else { // LgiTrace("%s - Dropping SetScroll, loop detected: %i ms\n", GetClass(), (int)(Now - d->SetScrollTime)); } } return d->Content; } LPointF LHtml::GetDpiScale() { LPointF Scale(1.0, 1.0); auto Wnd = GetWindow(); if (Wnd) Scale = Wnd->GetDpiScale(); return Scale; } void LHtml::OnPaint(LSurface *ScreenDC) { // LProfile Prof("LHtml::OnPaint"); #if HTML_USE_DOUBLE_BUFFER LRect Client = GetClient(); if (ScreenDC->IsScreen()) { if (!MemDC || (MemDC->X() < Client.X() || MemDC->Y() < Client.Y())) { if (MemDC.Reset(new LMemDC)) { int Sx = Client.X() + 10; int Sy = Client.Y() + 10; if (!MemDC->Create(Sx, Sy, System32BitColourSpace)) { MemDC.Reset(); } } } if (MemDC) { MemDC->ClipRgn(NULL); #if 0//def _DEBUG MemDC->Colour(LColour(255, 0, 255)); MemDC->Rectangle(); #endif } } #endif LSurface *pDC = MemDC ? MemDC : ScreenDC; #if 0 Gtk::cairo_matrix_t mx; Gtk::cairo_get_matrix(pDC->Handle(), &mx); LPoint Offset; WindowVirtualOffset(&Offset); printf("\tHtml paint mx=%g,%g off=%i,%i\n", mx.x0, mx.y0, Offset.x, Offset.y); #endif LColour cBack; if (GetCss()) { LCss::ColorDef Bk = GetCss()->BackgroundColor(); if (Bk.Type == LCss::ColorRgb) cBack = Bk; } if (!cBack.IsValid()) cBack = LColour(Enabled() ? L_WORKSPACE : L_MED); pDC->Colour(cBack); pDC->Rectangle(); if (Tag) { Layout(); if (VScroll) { int LineY = GetFont()->GetHeight(); int Vs = (int)VScroll->Value(); pDC->SetOrigin(0, Vs * LineY); } bool InSelection = false; PaintStart = LCurrentTime(); d->MaxPaintTimeout = false; Tag->OnPaint(pDC, InSelection, 0); if (d->MaxPaintTimeout) { LgiTrace("%s:%i - Html max paint time reached: %i ms.\n", _FL, LCurrentTime() - PaintStart); } } #if HTML_USE_DOUBLE_BUFFER if (MemDC) { pDC->SetOrigin(0, 0); ScreenDC->Blt(0, 0, MemDC); } #endif if (d->OnLoadAnchor && VScroll) { LAutoString a = d->OnLoadAnchor; GotoAnchor(a); LAssert(d->OnLoadAnchor == 0); } } bool LHtml::HasSelection() { if (Cursor && Selection) { return Cursor->Cursor >= 0 && Selection->Selection >= 0 && !(Cursor == Selection && Cursor->Cursor == Selection->Selection); } return false; } void LHtml::UnSelectAll() { bool i = false; if (Cursor) { Cursor->Cursor = -1; Cursor = NULL; i = true; } if (Selection) { Selection->Selection = -1; Selection = NULL; i = true; } if (i) { Invalidate(); } } void LHtml::SelectAll() { } LTag *LHtml::GetLastChild(LTag *t) { if (t && t->Children.Length()) { for (LTag *i = ToTag(t->Children.Last()); i; ) { LTag *c = i->Children.Length() ? ToTag(i->Children.Last()) : NULL; if (c) i = c; else return i; } } return 0; } LTag *LHtml::PrevTag(LTag *t) { // This returns the previous tag in the tree as if all the tags were // listed via recursion using "in order". // Walk up the parent chain looking for a prev for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a parent? if (p->Parent) { // Prev? LTag *pp = ToTag(p->Parent); ssize_t Idx = pp->Children.IndexOf(p); LTag *Prev = Idx > 0 ? ToTag(pp->Children[Idx - 1]) : NULL; if (Prev) { LTag *Last = GetLastChild(Prev); return Last ? Last : Prev; } else { return ToTag(p->Parent); } } } return 0; } LTag *LHtml::NextTag(LTag *t) { // This returns the next tag in the tree as if all the tags were // listed via recursion using "in order". // Does this have a child tag? if (t->Children.Length() > 0) { return ToTag(t->Children.First()); } else { // Walk up the parent chain for (LTag *p = t; p; p = ToTag(p->Parent)) { // Does this tag have a next? if (p->Parent) { LTag *pp = ToTag(p->Parent); size_t Idx = pp->Children.IndexOf(p); LTag *Next = pp->Children.Length() > Idx + 1 ? ToTag(pp->Children[Idx + 1]) : NULL; if (Next) { return Next; } } } } return 0; } int LHtml::GetTagDepth(LTag *Tag) { // Returns the depth of the tag in the tree. int n = 0; for (LTag *t = Tag; t; t = ToTag(t->Parent)) { n++; } return n; } bool LHtml::IsCursorFirst() { if (!Cursor || !Selection) return false; return CompareTagPos(Cursor, Cursor->Cursor, Selection, Selection->Selection); } bool LHtml::CompareTagPos(LTag *a, ssize_t AIdx, LTag *b, ssize_t BIdx) { // Returns true if the 'a' is before 'b' point. if (!a || !b) return false; if (a == b) { return AIdx < BIdx; } else { LArray ATree, BTree; for (LTag *t = a; t; t = ToTag(t->Parent)) ATree.AddAt(0, t); for (LTag *t = b; t; t = ToTag(t->Parent)) BTree.AddAt(0, t); ssize_t Depth = MIN(ATree.Length(), BTree.Length()); for (int i=0; i 0); LTag *p = ATree[i-1]; LAssert(BTree[i-1] == p); ssize_t ai = p->Children.IndexOf(at); ssize_t bi = p->Children.IndexOf(bt); return ai < bi; } } } return false; } void LHtml::SetLoadImages(bool i) { if (i ^ GetLoadImages()) { LDocView::SetLoadImages(i); SendNotify(LNotifyShowImagesChanged); if (GetLoadImages() && Tag) { Tag->LoadImages(); } } } char *LHtml::GetSelection() { char *s = 0; if (Cursor && Selection) { LMemQueue p; bool InSelection = false; Tag->CopyClipboard(p, InSelection); int Len = (int)p.GetSize(); if (Len > 0) { char16 *t = (char16*)p.New(sizeof(char16)); if (t) { size_t Len = StrlenW(t); for (int i=0; iOnFind(Dlg); } */ void BuildTagList(LArray &t, LTag *Tag) { t.Add(Tag); for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(Tag->Children[i]); BuildTagList(t, c); } } static void FormEncode(LStringPipe &p, const char *c) { const char *s = c; while (*c) { while (*c && *c != ' ') c++; if (c > s) { p.Write(s, c - s); c = s; } if (*c == ' ') { p.Write("+", 1); s = c; } else break; } } bool LHtml::OnSubmitForm(LTag *Form) { if (!Form || !Environment) { LAssert(!"Bad param"); return false; } const char *Method = NULL; const char *Action = NULL; if (!Form->Get("method", Method) || !Form->Get("action", Action)) { LAssert(!"Missing form action/method"); return false; } LHashTbl,char*> f; Form->CollectFormValues(f); bool Status = false; if (!_stricmp(Method, "post")) { LStringPipe p(256); bool First = true; // const char *Field; // for (char *Val = f.First(&Field); Val; Val = f.Next(&Field)) for (auto v : f) { if (First) First = false; else p.Write("&", 1); FormEncode(p, v.key); p.Write("=", 1); FormEncode(p, v.value); } LAutoPtr Data(p.NewStr()); Status = Environment->OnPostForm(this, Action, Data); } else if (!_stricmp(Method, "get")) { Status = Environment->OnNavigate(this, Action); } else { LAssert(!"Bad form method."); } f.DeleteArrays(); return Status; } bool LHtml::OnFind(LFindReplaceCommon *Params) { bool Status = false; if (Params) { if (!Params->Find) return Status; d->FindText.Reset(Utf8ToWide(Params->Find)); d->MatchCase = Params->MatchCase; } if (!Cursor) Cursor = Tag; if (Cursor && d->FindText) { LArray Tags; BuildTagList(Tags, Tag); ssize_t Start = Tags.IndexOf(Cursor); for (unsigned i=1; iText()) { char16 *Hit; if (d->MatchCase) Hit = StrstrW(s->Text(), d->FindText); else Hit = StristrW(s->Text(), d->FindText); if (Hit) { // found something... UnSelectAll(); Selection = Cursor = s; Cursor->Cursor = Hit - s->Text(); Selection->Selection = Cursor->Cursor + StrlenW(d->FindText); OnCursorChanged(); if (VScroll) { // Scroll the tag into view... int y = s->AbsY(); int LineY = GetFont()->GetHeight(); int Val = y / LineY; SetVScroll(Val); } Invalidate(); Status = true; break; } } } } return Status; } void LHtml::DoFind(std::function Callback) { LFindDlg *Dlg = new LFindDlg(this, [&](auto dlg, auto action) { OnFind(dlg); delete dlg; }); Dlg->DoModal(NULL); } bool LHtml::OnKey(LKey &k) { bool Status = false; if (k.Down()) { int Dy = 0; int LineY = GetFont()->GetHeight(); int Page = GetClient().Y() / LineY; switch (k.vkey) { case LK_F3: { OnFind(NULL); break; } #ifdef WIN32 case LK_INSERT: goto DoCopy; #endif case LK_UP: { Dy = -1; Status = true; break; } case LK_DOWN: { Dy = 1; Status = true; break; } case LK_PAGEUP: { Dy = -Page; Status = true; break; } case LK_PAGEDOWN: { Dy = Page; Status = true; break; } case LK_HOME: { Dy = (int) (VScroll ? -VScroll->Value() : 0); Status = true; break; } case LK_END: { if (VScroll) { LRange r = VScroll->GetRange(); Dy = (int)(r.End() - Page); } Status = true; break; } default: { switch (k.c16) { case 'f': case 'F': { if (k.CtrlCmd()) { DoFind(NULL); Status = true; } break; } case 'c': case 'C': { #ifdef WIN32 DoCopy: #endif if (k.CtrlCmd()) { Copy(); Status = true; } break; } } break; } } if (Dy && VScroll) SetVScroll(VScroll->Value() + Dy); } return Status; } int LHtml::ScrollY() { return GetFont()->GetHeight() * (VScroll ? (int)VScroll->Value() : 0); } void LHtml::OnMouseClick(LMouse &m) { Capture(m.Down()); SetPulse(m.Down() ? 200 : -1); if (m.Down()) { Focus(true); int Offset = ScrollY(); bool TagProcessedClick = false; LTagHit Hit; if (Tag) { Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false, DEBUG_TAG_BY_POS); #if DEBUG_TAG_BY_POS Hit.Dump("MouseClick"); #endif } if (m.Left() && !m.IsContextMenu()) { if (m.Double()) { d->WordSelectMode = true; if (Cursor) { // Extend the selection out to the current word's boundaries. Selection = Cursor; Selection->Selection = Cursor->Cursor; if (Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); char16 *Text = Cursor->Text() + Base; while (Text[Cursor->Cursor]) { char16 c = Text[Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } if (Selection->Text()) { ssize_t Base = Selection->GetTextStart(); char16 *Sel = Selection->Text() + Base; while (Selection->Selection > 0) { char16 c = Sel[Selection->Selection - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Selection->Selection--; } } Invalidate(); SendNotify(LNotifySelectionChanged); } } else if (Hit.NearestText) { d->WordSelectMode = false; UnSelectAll(); Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("StartSelect Near='%20S' Idx=%i\n", Hit.NearestText->Text(), Hit.Index); #endif OnCursorChanged(); SendNotify(LNotifySelectionChanged); } else { #if DEBUG_SELECTION LgiTrace("StartSelect no text hit %p, %p\n", Cursor, Selection); #endif } } if (Hit.NearestText && Hit.Near == 0) { TagProcessedClick = Hit.NearestText->OnMouseClick(m); } else if (Hit.Direct) { TagProcessedClick = Hit.Direct->OnMouseClick(m); } #ifdef _DEBUG else if (m.Left() && m.Ctrl()) { LgiMsg(this, "No tag under the cursor.", GetClass()); } #endif if (!TagProcessedClick && m.IsContextMenu()) { LSubMenu RClick; enum ContextMenuCmds { IDM_DUMP = 100, IDM_COPY_SRC, IDM_VIEW_SRC, IDM_EXTERNAL, IDM_COPY, IDM_VIEW_IMAGES, }; #define IDM_CHARSET_BASE 10000 RClick.AppendItem (LLoadString(L_TEXTCTRL_COPY, "Copy"), IDM_COPY, HasSelection()); LMenuItem *Vs = RClick.AppendItem (LLoadString(L_VIEW_SOURCE, "View Source"), IDM_VIEW_SRC, Source != 0); RClick.AppendItem (LLoadString(L_COPY_SOURCE, "Copy Source"), IDM_COPY_SRC, Source != 0); LMenuItem *Load = RClick.AppendItem (LLoadString(L_VIEW_IMAGES, "View External Images"), IDM_VIEW_IMAGES, true); if (Load) Load->Checked(GetLoadImages()); RClick.AppendItem (LLoadString(L_VIEW_IN_DEFAULT_BROWSER, "View in Default Browser"), IDM_EXTERNAL, Source != 0); LSubMenu *Cs = RClick.AppendSub (LLoadString(L_CHANGE_CHARSET, "Change Charset")); if (Cs) { int n=0; for (LCharset *c = LGetCsList(); c->Charset; c++, n++) { Cs->AppendItem(c->Charset, IDM_CHARSET_BASE + n, c->IsAvailable()); } } if (!GetReadOnly() || // Is editor #ifdef _DEBUG 1 #else 0 #endif ) { RClick.AppendSeparator(); RClick.AppendItem("Dump Layout", IDM_DUMP, Tag != 0); } if (Vs) { Vs->Checked(!IsHtml); } if (OnContextMenuCreate(Hit, RClick) && GetMouse(m, true)) { int Id = RClick.Float(this, m.x, m.y); switch (Id) { case IDM_COPY: { Copy(); break; } case IDM_VIEW_SRC: { if (Vs) { DeleteObj(Tag); IsHtml = !IsHtml; ParseDocument(Source); } break; } case IDM_COPY_SRC: { if (Source) { LClipBoard c(this); const char *ViewCs = GetCharset(); if (ViewCs) { LAutoWString w((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); if (w) c.TextW(w); } else c.Text(Source); } break; } case IDM_VIEW_IMAGES: { SetLoadImages(!GetLoadImages()); break; } case IDM_DUMP: { if (Tag) { LAutoWString s = Tag->DumpW(); if (s) { LClipBoard c(this); c.TextW(s); } } break; } case IDM_EXTERNAL: { if (!Source) { LgiTrace("%s:%i - No HTML source code.\n", _FL); break; } char Path[MAX_PATH_LEN]; if (!LGetSystemPath(LSP_TEMP, Path, sizeof(Path))) { LgiTrace("%s:%i - Failed to get the system path.\n", _FL); break; } char f[32]; sprintf_s(f, sizeof(f), "_%i.html", LRand(1000000)); LMakePath(Path, sizeof(Path), Path, f); LFile F; if (!F.Open(Path, O_WRITE)) { LgiTrace("%s:%i - Failed to open '%s' for writing.\n", _FL, Path); break; } LStringPipe Ex; bool Error = false; F.SetSize(0); LAutoWString SrcMem; const char *ViewCs = GetCharset(); if (ViewCs) SrcMem.Reset((char16*)LNewConvertCp(LGI_WideCharset, Source, ViewCs)); else SrcMem.Reset(Utf8ToWide(Source)); for (char16 *s=SrcMem; s && *s;) { char16 *cid = StristrW(s, L"cid:"); while (cid && !strchr("\'\"", cid[-1])) { cid = StristrW(cid+1, L"cid:"); } if (cid) { char16 Delim = cid[-1]; char16 *e = StrchrW(cid, Delim); if (e) { *e = 0; if (StrchrW(cid, '\n')) { *e = Delim; Error = true; break; } else { char File[MAX_PATH_LEN] = ""; if (Environment) { LDocumentEnv::LoadJob *j = Environment->NewJob(); if (j) { j->Uri.Reset(WideToUtf8(cid)); j->Env = Environment; j->Pref = LDocumentEnv::LoadJob::FmtFilename; j->UserUid = GetDocumentUid(); LDocumentEnv::LoadType Result = Environment->GetContent(j); if (Result == LDocumentEnv::LoadImmediate) { if (j->Filename) strcpy_s(File, sizeof(File), j->Filename); } else if (Result == LDocumentEnv::LoadDeferred) { d->DeferredLoads++; } DeleteObj(j); } } *e = Delim; Ex.Push(s, cid - s); if (File[0]) { char *d; while ((d = strchr(File, '\\'))) { *d = '/'; } Ex.Push(L"file:///"); LAutoWString w(Utf8ToWide(File)); Ex.Push(w); } s = e; } } else { Error = true; break; } } else { Ex.Push(s); break; } } if (!Error) { int64 WideChars = Ex.GetSize() / sizeof(char16); LAutoWString w(Ex.NewStrW()); LAutoString u(WideToUtf8(w, WideChars)); if (u) F.Write(u, strlen(u)); F.Close(); LString Err; if (!LExecute(Path, NULL, NULL, &Err)) { LgiMsg( this, "Failed to open '%s'\n%s", LAppInst ? LAppInst->LBase::Name() : GetClass(), MB_OK, Path, Err.Get()); } } break; } default: { if (Id >= IDM_CHARSET_BASE) { LCharset *c = LGetCsList() + (Id - IDM_CHARSET_BASE); if (c->Charset) { Charset = c->Charset; OverideDocCharset = true; char *Src = Source.Release(); _Delete(); _New(); Source.Reset(Src); ParseDocument(Source); Invalidate(); SendNotify(LNotifyCharsetChanged); } } else { OnContextMenuCommand(Hit, Id); } break; } } } } } else // Up Click { if (Selection && Cursor && Selection == Cursor && Selection->Selection == Cursor->Cursor) { Selection->Selection = -1; Selection = 0; SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("NoSelect on release\n"); #endif } } } void LHtml::OnLoad() { d->IsLoaded = true; SendNotify(LNotifyDocLoaded); } LTag *LHtml::GetTagByPos(int x, int y, ssize_t *Index, LPoint *LocalCoords, bool DebugLog) { LTag *Status = NULL; if (Tag) { if (DebugLog) LgiTrace("GetTagByPos starting...\n"); LTagHit Hit; Tag->GetTagByPos(Hit, x, y, 0, DebugLog); if (DebugLog) LgiTrace("GetTagByPos Hit=%s, %i, %i...\n\n", Hit.Direct ? Hit.Direct->Tag.Get() : 0, Hit.Index, Hit.Near); Status = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (Hit.NearestText && Hit.Near < 30) { if (Index) *Index = Hit.Index; if (LocalCoords) *LocalCoords = Hit.LocalCoords; } } return Status; } void LHtml::SetVScroll(int64 v) { if (!VScroll) return; if (Tag) Tag->ClearToolTips(); VScroll->Value(v); Invalidate(); } bool LHtml::OnMouseWheel(double Lines) { if (VScroll) SetVScroll(VScroll->Value() + (int64)Lines); return true; } LCursor LHtml::GetCursor(int x, int y) { int Offset = ScrollY(); ssize_t Index = -1; LPoint LocalCoords; LTag *Tag = GetTagByPos(x, y + Offset, &Index, &LocalCoords); if (Tag) { LString Uri; if (LocalCoords.x >= 0 && LocalCoords.y >= 0 && Tag->IsAnchor(&Uri)) { LRect c = GetClient(); c.Offset(-c.x1, -c.y1); if (c.Overlap(x, y) && ValidStr(Uri)) { return LCUR_PointingHand; } } } return LCUR_Normal; } void LTag::ClearToolTips() { if (TipId) { Html->Tip.DeleteTip(TipId); TipId = 0; } for (auto c: Children) ToTag(c)->ClearToolTips(); } void LHtml::OnMouseMove(LMouse &m) { if (!Tag) return; int Offset = ScrollY(); LTagHit Hit; Tag->GetTagByPos(Hit, m.x, m.y + Offset, 0, false); if (!Hit.Direct && !Hit.NearestText) return; LString Uri; LTag *HitTag = Hit.NearestText && Hit.Near == 0 ? Hit.NearestText : Hit.Direct; if (HitTag && HitTag->TipId == 0 && Hit.LocalCoords.x >= 0 && Hit.LocalCoords.y >= 0 && HitTag->IsAnchor(&Uri) && Uri) { if (!Tip.GetParent()) { Tip.Attach(this); } LRect r = HitTag->GetRect(false); r.Offset(0, -Offset); if (!HitTag->TipId) HitTag->TipId = Tip.NewTip(Uri, r); // LgiTrace("NewTip: %s @ %s, ID=%i\n", Uri.Get(), r.GetStr(), HitTag->TipId); } if (IsCapturing() && Cursor && Hit.NearestText) { if (!Selection) { Selection = Cursor; Selection->Selection = Cursor->Cursor; Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); #if DEBUG_SELECTION LgiTrace("CreateSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif } else if ((Cursor != Hit.NearestText) || (Cursor->Cursor != Hit.Index)) { // Move the cursor to track the mouse if (Cursor) { Cursor->Cursor = -1; } Cursor = Hit.NearestText; Cursor->Cursor = Hit.Index; #if DEBUG_SELECTION LgiTrace("ExtendSelection '%20S' %i\n", Hit.NearestText->Text(), Hit.Index); #endif if (d->WordSelectMode && Cursor->Text()) { ssize_t Base = Cursor->GetTextStart(); if (IsCursorFirst()) { // Extend the cursor up the document to include the whole word while (Cursor->Cursor > 0) { char16 c = Cursor->Text()[Base + Cursor->Cursor - 1]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor--; } } else { // Extend the cursor down the document to include the whole word while (Cursor->Text()[Base + Cursor->Cursor]) { char16 c = Cursor->Text()[Base + Cursor->Cursor]; if (strchr(WordDelim, c) || StrchrW(WhiteW, c)) break; Cursor->Cursor++; } } } OnCursorChanged(); Invalidate(); SendNotify(LNotifySelectionChanged); } } } void LHtml::OnPulse() { if (VScroll && IsCapturing()) { int Fy = DefFont() ? DefFont()->GetHeight() : 16; LMouse m; if (GetMouse(m, false)) { LRect c = GetClient(); int Lines = 0; if (m.y < c.y1) { // Scroll up Lines = (c.y1 - m.y + Fy - 1) / -Fy; } else if (m.y > c.y2) { // Scroll down Lines = (m.y - c.y2 + Fy - 1) / Fy; } if (Lines && VScroll) SetVScroll(VScroll->Value() + Lines); } } } LRect *LHtml::GetCursorPos() { return &d->CursorPos; } void LHtml::SetCursorVis(bool b) { if (d->CursorVis ^ b) { d->CursorVis = b; Invalidate(); } } bool LHtml::GetCursorVis() { return d->CursorVis; } LDom *ElementById(LTag *t, char *id) { if (t && id) { const char *i; if (t->Get("id", i) && _stricmp(i, id) == 0) return t; for (unsigned i=0; iChildren.Length(); i++) { LTag *c = ToTag(t->Children[i]); LDom *n = ElementById(c, id); if (n) return n; } } return 0; } LDom *LHtml::getElementById(char *Id) { return ElementById(Tag, Id); } bool LHtml::GetLinkDoubleClick() { return d->LinkDoubleClick; } void LHtml::SetLinkDoubleClick(bool b) { d->LinkDoubleClick = b; } bool LHtml::GetFormattedContent(const char *MimeType, LString &Out, LArray *Media) { if (!MimeType) { LAssert(!"No MIME type for getting formatted content"); return false; } if (!_stricmp(MimeType, "text/html")) { // We can handle this type... LArray Imgs; if (Media) { // Find all the image tags... Tag->Find(TAG_IMG, Imgs); // Give them CID's if they don't already have them for (unsigned i=0; iGet("src", Src) && !Img->Get("cid", Cid)) { char id[256]; sprintf_s(id, sizeof(id), "%x.%x", (unsigned)LCurrentTime(), (unsigned)LRand()); Img->Set("cid", id); Img->Get("cid", Cid); } if (Src && Cid) { LFile *f = new LFile; if (f) { if (f->Open(Src, O_READ)) { // Add the exported image stream to the media array LDocView::ContentMedia &m = Media->New(); m.Id = Cid; m.Stream.Reset(f); } } } } } // Export the HTML, including the CID's from the first step Out = Name(); } else if (!_stricmp(MimeType, "text/plain")) { // Convert DOM tree down to text instead... LStringPipe p(512); if (Tag) { LTag::TextConvertState State(&p); Tag->ConvertToText(State); } Out = p.NewGStr(); } return false; } void LHtml::OnContent(LDocumentEnv::LoadJob *Res) { if (JobSem.Lock(_FL)) { JobSem.Jobs.Add(Res); JobSem.Unlock(); PostEvent(M_JOBS_LOADED); } } LHtmlElement *LHtml::CreateElement(LHtmlElement *Parent) { return new LTag(this, Parent); } bool LHtml::GetVariant(const char *Name, LVariant &Value, const char *Array) { if (!_stricmp(Name, "supportLists")) // Type: Bool Value = false; else if (!_stricmp(Name, "vml")) // Type: Bool // Vector Markup Language Value = false; else if (!_stricmp(Name, "mso")) // Type: Bool // mso = Microsoft Office Value = false; else return false; return true; } bool LHtml::EvaluateCondition(const char *Cond) { if (!Cond) return true; // This is a really bad attempt at writing an expression evaluator. // I could of course use the scripting language but that would pull // in a fairly large dependency on the HTML control. However user // apps that already have that could reimplement this virtual function // if they feel like it. LArray Str; for (const char *c = Cond; *c; ) { if (IsAlpha(*c)) { Str.Add(LTokStr(c)); } else if (IsWhiteSpace(*c)) { c++; } else { const char *e = c; while (*e && !IsWhiteSpace(*e) && !IsAlpha(*e)) e++; Str.Add(NewStr(c, e - c)); LAssert(e > c); if (e > c) c = e; else break; } } bool Result = true; bool Not = false; for (unsigned i=0; iGetAnchor(Name); if (a) { if (VScroll) { int LineY = GetFont()->GetHeight(); int Ay = a->AbsY(); int Scr = Ay / LineY; SetVScroll(Scr); VScroll->SendNotify(); } else d->OnLoadAnchor.Reset(NewStr(Name)); } } return false; } bool LHtml::GetEmoji() { return d->DecodeEmoji; } void LHtml::SetEmoji(bool i) { d->DecodeEmoji = i; } void LHtml::SetMaxPaintTime(int Ms) { d->MaxPaintTime = Ms; } bool LHtml::GetMaxPaintTimeout() { return d->MaxPaintTimeout; } //////////////////////////////////////////////////////////////////////// class LHtml_Factory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (_stricmp(Class, "LHtml") == 0) { return new LHtml(-1, 0, 0, 100, 100, new LDefaultDocumentEnv); } return 0; } } LHtml_Factory; ////////////////////////////////////////////////////////////////////// struct BuildContext { LHtmlTableLayout *Layout; LTag *Table; LTag *TBody; LTag *CurTr; LTag *CurTd; int cx, cy; BuildContext() { Layout = NULL; cx = cy = 0; Table = NULL; TBody = NULL; CurTr = NULL; CurTd = NULL; } bool Build(LTag *t, int Depth) { bool RetReattach = false; switch (t->TagId) { case TAG_TABLE: { if (!Table) Table = t; else return false; break; } case TAG_TBODY: { if (TBody) return false; TBody = t; break; } case TAG_TR: { CurTr = t; break; } case TAG_TD: { CurTd = t; if (t->Parent != CurTr) { if ( !CurTr && (Table || TBody) ) { LTag *p = TBody ? TBody : Table; CurTr = new LTag(p->Html, p); if (CurTr) { CurTr->Tag.Reset(NewStr("tr")); CurTr->TagId = TAG_TR; ssize_t Idx = t->Parent->Children.IndexOf(t); t->Parent->Attach(CurTr, Idx); } } if (CurTr) { CurTr->Attach(t); RetReattach = true; } else { LAssert(0); return false; } } t->Cell->Pos.x = cx; t->Cell->Pos.y = cy; Layout->Set(t); break; } default: { if (CurTd == t->Parent) return false; break; } } for (unsigned n=0; nChildren.Length(); n++) { LTag *c = ToTag(t->Children[n]); bool Reattached = Build(c, Depth+1); if (Reattached) n--; } if (t->TagId == TAG_TR) { CurTr = NULL; cy++; cx = 0; Layout->s.y = cy; } if (t->TagId == TAG_TD) { CurTd = NULL; cx += t->Cell->Span.x; Layout->s.x = MAX(cx, Layout->s.x); } return RetReattach; } }; LHtmlTableLayout::LHtmlTableLayout(LTag *table) { Table = table; if (!Table) return; #if 0 BuildContext Ctx; Ctx.Layout = this; Ctx.Build(table, 0); #else int y = 0; LTag *FakeRow = 0; LTag *FakeCell = 0; LTag *r; for (size_t i=0; iChildren.Length(); i++) { r = ToTag(Table->Children[i]); if (r->Display() == LCss::DispNone) continue; if (r->TagId == TAG_TR) { FakeRow = 0; FakeCell = 0; } else if (r->TagId == TAG_TBODY) { ssize_t Index = Table->Children.IndexOf(r); for (size_t n=0; nChildren.Length(); n++) { LTag *t = ToTag(r->Children[n]); Table->Children.AddAt(++Index, t); t->Parent = Table; /* LgiTrace("Moving '%s'(%p) from TBODY(%p) into '%s'(%p)\n", t->Tag, t, r, t->Parent->Tag, t->Parent); */ } r->Children.Length(0); } else { if (!FakeRow) { if ((FakeRow = new LTag(Table->Html, 0))) { FakeRow->Tag.Reset(NewStr("tr")); FakeRow->TagId = TAG_TR; ssize_t Idx = Table->Children.IndexOf(r); Table->Attach(FakeRow, Idx); } } if (FakeRow) { if (!IsTableCell(r->TagId) && !FakeCell) { if ((FakeCell = new LTag(Table->Html, FakeRow))) { FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } } } ssize_t Idx = Table->Children.IndexOf(r); r->Detach(); if (IsTableCell(r->TagId)) { FakeRow->Attach(r); } else { LAssert(FakeCell != NULL); FakeCell->Attach(r); } i = Idx - 1; } } } FakeCell = NULL; for (size_t n=0; nChildren.Length(); n++) { LTag *r = ToTag(Table->Children[n]); if (r->TagId == TAG_TR) { int x = 0; for (size_t i=0; iChildren.Length(); i++) { LTag *cell = ToTag(r->Children[i]); if (!IsTableCell(cell->TagId)) { if (!FakeCell) { // Make a fake TD cell FakeCell = new LTag(Table->Html, NULL); FakeCell->Tag.Reset(NewStr("td")); FakeCell->TagId = TAG_TD; if ((FakeCell->Cell = new LTag::TblCell)) { FakeCell->Cell->Span.x = 1; FakeCell->Cell->Span.y = 1; } // Join the fake TD into the TR r->Children[i] = FakeCell; FakeCell->Parent = r; } else { // Not the first non-TD tag, so delete it from the TR. Only the // fake TD will remain in the TR. r->Children.DeleteAt(i--, true); } // Insert the tag into it as a child FakeCell->Children.Add(cell); cell->Parent = FakeCell; cell = FakeCell; } else { FakeCell = NULL; } if (IsTableCell(cell->TagId)) { if (cell->Display() == LCss::DispNone) continue; while (Get(x, y)) { x++; } cell->Cell->Pos.x = x; cell->Cell->Pos.y = y; Set(cell); x += cell->Cell->Span.x; } } y++; FakeCell = NULL; } } #endif } void LHtmlTableLayout::Dump() { int Sx, Sy; GetSize(Sx, Sy); LgiTrace("Table %i x %i cells.\n", Sx, Sy); for (int x=0; xCell->Pos.x, t->Cell->Pos.y, t->Cell->Span.x, t->Cell->Span.y); LgiTrace("%-10s", s); } LgiTrace("\n"); } LgiTrace("\n"); } void LHtmlTableLayout::GetAll(List &All) { LHashTbl, bool> Added; for (size_t y=0; y= (int) c.Length()) return NULL; CellArray &a = c[y]; if (x >= (int) a.Length()) return NULL; return a[x]; } bool LHtmlTableLayout::Set(LTag *t) { if (!t) return false; for (int y=0; yCell->Span.y; y++) { for (int x=0; xCell->Span.x; x++) { // LAssert(!c[y][x]); c[t->Cell->Pos.y + y][t->Cell->Pos.x + x] = t; } } return true; } void LTagHit::Dump(const char *Desc) { LArray d, n; LTag *t = Direct; unsigned i; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { d.AddAt(0, t); } t = NearestText; for (i=0; i<3 && t; t = ToTag(t->Parent), i++) { n.AddAt(0, t); } LgiTrace("Hit: %s Direct: ", Desc); for (i=0; i%s", d[i]->Tag ? d[i]->Tag.Get() : "CONTENT"); LgiTrace(" Nearest: "); for (i=0; i%s", n[i]->Tag ? n[i]->Tag.Get() : "CONTENT"); LgiTrace(" Local: %ix%i Index: %i Block: %s '%.10S'\n", LocalCoords.x, LocalCoords.y, Index, Block ? Block->GetStr() : NULL, Block ? Block->Text + Index : NULL); } diff --git a/src/common/Widgets/ItemContainer.cpp b/src/common/Widgets/ItemContainer.cpp --- a/src/common/Widgets/ItemContainer.cpp +++ b/src/common/Widgets/ItemContainer.cpp @@ -1,1309 +1,1312 @@ #include "lgi/common/Lgi.h" #include "lgi/common/ItemContainer.h" #include "lgi/common/DisplayString.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Edit.h" #include "lgi/common/CssTools.h" // Colours #if defined(__GTK_H__) #define DOUBLE_BUFFER_COLUMN_DRAWING 1 #else #define DOUBLE_BUFFER_COLUMN_DRAWING 0 #endif #if defined(WIN32) #if !defined(WS_EX_LAYERED) #define WS_EX_LAYERED 0x80000 #endif #if !defined(LWA_ALPHA) #define LWA_ALPHA 2 #endif typedef BOOL (__stdcall *_SetLayeredWindowAttributes)(HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags); #endif class LItemColumnPrivate { public: LRect Pos; bool Down; bool Drag; LItemContainer *Parent; char *cName; LDisplayString *Txt; int cWidth; int cType; LSurface *cIcon; int cImage; int cMark; bool OwnIcon; bool CanResize; LItemColumnPrivate(LItemContainer *parent) { Parent = parent; Txt = 0; cName = 0; cWidth = 0; cIcon = 0; cType = GIC_ASK_TEXT; cImage = -1; cMark = GLI_MARK_NONE; Down = false; OwnIcon = false; CanResize = true; Drag = false; } ~LItemColumnPrivate() { DeleteArray(cName); if (OwnIcon) { DeleteObj(cIcon); } DeleteObj(Txt); } }; static LColour cActiveCol(0x86, 0xba, 0xe9); static void FillStops(LArray &Stops, LRect &r, bool Active) { if (Active) { Stops[0].Set(0.0f, LColour(0xd0, 0xe2, 0xf5)); Stops[1].Set(2.0f / (r.Y() - 2), LColour(0x98, 0xc1, 0xe9)); Stops[2].Set(0.5f, LColour(0x86, 0xba, 0xe9)); Stops[3].Set(0.51f, LColour(0x68, 0xaf, 0xea)); Stops[4].Set(1.0f, LColour(0xbb, 0xfc, 0xff)); } else { LColour cMed(L_MED), cWs(L_WORKSPACE); if (cWs.GetGray() < 96) { cMed = cMed.Mix(LColour::White, 0.25f); cWs = cWs.Mix(LColour::White, 0.25f); } Stops[0].Set(0.0f, cWs); Stops[1].Set(0.5f, cMed.Mix(cWs)); Stops[2].Set(0.51f, cMed); Stops[3].Set(1.0f, cWs); } } ////////////////////////////////////////////////////////////////////////////////////// LItemContainer::LItemContainer() { Flags = 0; DragMode = 0; DragCol = NULL; ColClick = -1; ColumnHeaders = true; ColumnHeader.ZOff(-1, -1); Columns.SetFixedLength(true); ItemEdit = NULL; } LItemContainer::~LItemContainer() { DeleteObj(ItemEdit); DeleteObj(DragCol); Columns.DeleteObjects(); } void LItemContainer::PaintColumnHeadings(LSurface *pDC) { // Draw column headings if (!ColumnHeaders || !ColumnHeader.Valid()) return; LSurface *ColDC = pDC; LRect cr; #if DOUBLE_BUFFER_COLUMN_DRAWING LMemDC Bmp; if (!pDC->SupportsAlphaCompositing() && Bmp.Create(ColumnHeader.X(), ColumnHeader.Y(), System32BitColourSpace)) { ColDC = &Bmp; Bmp.Op(GDC_ALPHA); cr = ColumnHeader.ZeroTranslate(); } else #endif { cr = ColumnHeader; pDC->ClipRgn(&cr); } // Draw columns int cx = cr.x1; if (IconCol) { cr.x1 = cx; cr.x2 = cr.x1 + IconCol->Width() - 1; IconCol->SetPos(cr); IconCol->OnPaint(ColDC, cr); cx += IconCol->Width(); } // Draw other columns for (int i=0; iWidth() - 1; c->SetPos(cr); c->OnPaint(ColDC, cr); cx += c->Width(); } else LAssert(0); } // Draw ending piece cr.x1 = cx; cr.x2 = ColumnHeader.x2 + 2; if (cr.Valid()) { // Draw end section where there are no columns #ifdef MAC LArray Stops; LRect j(cr.x1, cr.y1, cr.x2-1, cr.y2-1); FillStops(Stops, j, false); LFillGradient(ColDC, j, true, Stops); ColDC->Colour(L_LOW); ColDC->Line(cr.x1, cr.y2, cr.x2, cr.y2); #else if (LApp::SkinEngine) { LSkinState State; State.pScreen = ColDC; State.Rect = cr; State.Enabled = Enabled(); State.View = this; LApp::SkinEngine->OnPaint_ListColumn(0, 0, &State); } else { LWideBorder(ColDC, cr, DefaultRaisedEdge); ColDC->Colour(LColour(L_MED)); ColDC->Rectangle(&cr); } #endif } #if DOUBLE_BUFFER_COLUMN_DRAWING if (!pDC->SupportsAlphaCompositing()) pDC->Blt(ColumnHeader.x1, ColumnHeader.y1, &Bmp); else #endif pDC->ClipRgn(0); } LItemColumn *LItemContainer::AddColumn(const char *Name, int Width, int Where) { LItemColumn *c = 0; if (Lock(_FL)) { c = new LItemColumn(this, Name, Width); if (c) { Columns.SetFixedLength(false); Columns.AddAt(Where, c); Columns.SetFixedLength(true); UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return c; } bool LItemContainer::AddColumn(LItemColumn *Col, int Where) { bool Status = false; if (Col && Lock(_FL)) { Columns.SetFixedLength(false); Status = Columns.AddAt(Where, Col); Columns.SetFixedLength(true); if (Status) { UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); } Unlock(); } return Status; } void LItemContainer::DragColumn(int Index) { DeleteObj(DragCol); if (Index >= 0) { DragCol = new LDragColumn(this, Index); if (DragCol) { Capture(true); DragMode = DRAG_COLUMN; } } } int LItemContainer::ColumnAtX(int x, LItemColumn **Col, int *Offset) { LItemColumn *Column = 0; if (!Col) Col = &Column; int Cx = GetImageList() ? 16 : 0; int c; for (c=0; c= Cx && x < Cx + (*Col)->Width()) { if (Offset) *Offset = Cx; return c; } Cx += (*Col)->Width(); } return -1; } void LItemContainer::EmptyColumns() { Columns.DeleteObjects(); Invalidate(&ColumnHeader); SendNotify(LNotifyItemColumnsChanged); } int LItemContainer::HitColumn(int x, int y, LItemColumn *&Resize, LItemColumn *&Over) { int Index = -1; Resize = 0; Over = 0; if (ColumnHeaders && ColumnHeader.Overlap(x, y)) { // Clicked on a column heading int cx = ColumnHeader.x1 + ((IconCol) ? IconCol->Width() : 0); for (int n = 0; n < Columns.Length(); n++) { LItemColumn *c = Columns[n]; cx += c->Width(); if (abs(x-cx) < 5) { if (c->Resizable()) { Resize = c; Index = n; break; } } else if (c->d->Pos.Overlap(x, y)) { Over = c; Index = n; break; } } } return Index; } void LItemContainer::OnColumnClick(int Col, LMouse &m) { ColClick = Col; ColMouse = m; - SendNotify(LNotifyItemColumnClicked); + + LNotification n(LNotifyItemColumnClicked); + n.Int[0] = Col; + SendNotify(n); } bool LItemContainer::GetColumnClickInfo(int &Col, LMouse &m) { if (ColClick >= 0) { Col = ColClick; m = ColMouse; return true; } return false; } void LItemContainer::GetColumnSizes(ColSizes &cs) { // Read in the current sizes cs.FixedPx = 0; cs.ResizePx = 0; for (int i=0; iResizable()) { ColInfo &Inf = cs.Info.New(); Inf.Col = c; Inf.Idx = i; Inf.ContentPx = c->GetContentSize(); Inf.WidthPx = c->Width(); cs.ResizePx += Inf.ContentPx; } else { cs.FixedPx += c->Width(); } } } LMessage::Result LItemContainer::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_RESIZE_TO_CONTENT: { ResizeColumnsToContent((int)Msg->A()); break; } default: break; } return LLayout::OnEvent(Msg); } void LItemContainer::ResizeColumnsToContent(int Border) { if (!InThread()) { PostEvent(M_RESIZE_TO_CONTENT, Border); return; } if (Lock(_FL)) { // Read in the current sizes ColSizes Sizes; GetColumnSizes(Sizes); // Allocate space int AvailablePx = GetClient().X() - 5; if (VScroll) AvailablePx -= VScroll->X(); int ExpandPx = AvailablePx - Sizes.FixedPx; Sizes.Info.Sort([](auto a, auto b) { int AGrowPx = a->GrowPx(); int BGrowPx = b->GrowPx(); return AGrowPx - BGrowPx; }); for (int i=0; iResizable()) { if (ExpandPx > Sizes.ResizePx) { // Everything fits... Inf.Col->Width(Inf.ContentPx + Border); } else { int Cx = GetClient().X(); double Ratio = Cx ? (double)Inf.ContentPx / Cx : 1.0; if (Ratio < 0.25) { Inf.Col->Width(Inf.ContentPx + Border); } else { // Need to scale to fit... int Px = Inf.ContentPx * ExpandPx / Sizes.ResizePx; Inf.Col->Width(Px + Border); } } ClearDs(Inf.Idx); } } Unlock(); } Invalidate(); } ////////////////////////////////////////////////////////////////////////////// LDragColumn::LDragColumn(LItemContainer *list, int col) { List = list; Index = col; Offset = 0; #ifdef LINUX Back = 0; #endif Col = List->ColumnAt(Index); if (Col) { Col->d->Down = false; Col->d->Drag = true; LRect r = Col->d->Pos; r.y1 = 0; r.y2 = List->Y()-1; List->Invalidate(&r, true); #if WINNATIVE LArray Ver; bool Layered = ( LGetOs(&Ver) == LGI_OS_WIN32 || LGetOs(&Ver) == LGI_OS_WIN64 ) && Ver[0] >= 5; SetStyle(WS_POPUP); SetExStyle(GetExStyle() | WS_EX_TOOLWINDOW); if (Layered) { SetExStyle(GetExStyle() | WS_EX_LAYERED | WS_EX_TRANSPARENT); } #endif Attach(0); #if WINNATIVE if (Layered) { SetWindowLong(Handle(), GWL_EXSTYLE, GetWindowLong(Handle(), GWL_EXSTYLE) | WS_EX_LAYERED); LLibrary User32("User32"); _SetLayeredWindowAttributes SetLayeredWindowAttributes = (_SetLayeredWindowAttributes)User32.GetAddress("SetLayeredWindowAttributes"); if (SetLayeredWindowAttributes) { if (!SetLayeredWindowAttributes(Handle(), 0, DRAG_COL_ALPHA, LWA_ALPHA)) { DWORD Err = GetLastError(); } } } #elif defined(__GTK_H__) Gtk::GtkWindow *w = WindowHandle(); if (w) { gtk_window_set_decorated(w, FALSE); gtk_widget_set_opacity(GtkCast(w, gtk_widget, GtkWidget), DRAG_COL_ALPHA / 255.0); } #endif LMouse m; List->GetMouse(m); Offset = m.x - r.x1; List->PointToScreen(ListScrPos); r.Offset(ListScrPos.x, ListScrPos.y); SetPos(r); Visible(true); } } LDragColumn::~LDragColumn() { Visible(false); if (Col) { Col->d->Drag = false; } List->Invalidate(); } #if LINUX_TRANS_COL void LDragColumn::OnPosChange() { Invalidate(); } #endif void LDragColumn::OnPaint(LSurface *pScreen) { #if LINUX_TRANS_COL LSurface *Buf = new LMemDC(X(), Y(), GdcD->GetBits()); LSurface *pDC = new LMemDC(X(), Y(), GdcD->GetBits()); #else LSurface *pDC = pScreen; #endif pDC->SetOrigin(Col->d->Pos.x1, 0); if (Col) Col->d->Drag = false; List->OnPaint(pDC); if (Col) Col->d->Drag = true; pDC->SetOrigin(0, 0); #if LINUX_TRANS_COL if (Buf && pDC) { LRect p = GetPos(); // Fill the buffer with the background Buf->Blt(ListScrPos.x - p.x1, 0, Back); // Draw painted column over the back with alpha Buf->Op(GDC_ALPHA); LApplicator *App = Buf->Applicator(); if (App) { App->SetVar(GAPP_ALPHA_A, DRAG_COL_ALPHA); } Buf->Blt(0, 0, pDC); // Put result on the screen pScreen->Blt(0, 0, Buf); } DeleteObj(Buf); DeleteObj(pDC); #endif } /////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////// // List column LItemColumn::LItemColumn(LItemContainer *parent, const char *name, int width) : ResObject(Res_Column) { d = new LItemColumnPrivate(parent); d->cWidth = width; if (name) Name(name); } LItemColumn::~LItemColumn() { if (d->Drag) { d->Parent->DragColumn(-1); } DeleteObj(d); } LItemContainer *LItemColumn::GetList() { return d->Parent; } void LItemColumn::Image(int i) { d->cImage = i; } int LItemColumn::Image() { return d->cImage; } bool LItemColumn::Resizable() { return d->CanResize; } void LItemColumn::Resizable(bool i) { d->CanResize = i; } bool LItemColumn::InDrag() { return d->Drag; } LRect LItemColumn::GetPos() { return d->Pos; } void LItemColumn::SetPos(LRect &r) { d->Pos = r; } void LItemColumn::Name(const char *n) { DeleteArray(d->cName); DeleteObj(d->Txt); d->cName = NewStr(n); LFont *f = d->Parent && d->Parent->GetFont() && d->Parent->GetFont()->Handle() ? d->Parent->GetFont() : LSysFont; d->Txt = new LDisplayString(f, (char*)n); if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } char *LItemColumn::Name() { return d->cName; } int LItemColumn::GetIndex() { if (d->Parent) { return (int)d->Parent->Columns.IndexOf(this); } return -1; } int LItemColumn::GetContentSize() { return d->Parent->GetContentSize(GetIndex()); } void LItemColumn::Width(int i) { if (d->cWidth != i) { d->cWidth = i; // If we are attached to a list... if (d->Parent) { /* FIXME int MyIndex = GetIndex(); // Clear all the cached strings for this column for (List::I it=d->Parent->Items.Start(); it.In(); it++) { DeleteObj((*it)->d->Display[MyIndex]); } if (d->Parent->IsAttached()) { // Update the screen from this column across LRect Up = d->Parent->GetClient(); Up.x1 = d->Pos.x1; d->Parent->Invalidate(&Up); } */ } // Notify listener auto p = d->Parent; if (p) p->SendNotify(LNotifyItemColumnsResized); } } int LItemColumn::Width() { return d->cWidth; } void LItemColumn::Mark(int i) { d->cMark = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Mark() { return d->cMark; } void LItemColumn::Type(int i) { d->cType = i; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } int LItemColumn::Type() { return d->cType; } void LItemColumn::Icon(LSurface *i, bool Own) { if (d->OwnIcon) { DeleteObj(d->cIcon); } d->cIcon = i; d->OwnIcon = Own; if (d->Parent) { d->Parent->Invalidate(&d->Parent->ColumnHeader); } } LSurface *LItemColumn::Icon() { return d->cIcon; } bool LItemColumn::Value() { return d->Down; } void LItemColumn::Value(bool i) { d->Down = i; } void LItemColumn::OnPaint_Content(LSurface *pDC, LRect &r, bool FillBackground) { if (!d->Drag) { LCssTools Tools(d->Parent); auto Fore = Tools.GetFore(); auto cMed = LColour(L_MED); int Off = d->Down ? 1 : 0; int Mx = r.x1 + 8, My = r.y1 + ((r.Y() - 8) / 2); if (d->cIcon) { if (FillBackground) { pDC->Colour(cMed); pDC->Rectangle(&r); } int x = (r.X()-d->cIcon->X()) / 2; pDC->Blt( r.x1 + x + Off, r.y1 + ((r.Y()-d->cIcon->Y())/2) + Off, d->cIcon); if (d->cMark) { Mx += x + d->cIcon->X() + 4; } } else if (d->cImage >= 0 && d->Parent) { LColour Background = cMed; if (FillBackground) { pDC->Colour(Background); pDC->Rectangle(&r); } if (d->Parent->GetImageList()) { LRect *b = d->Parent->GetImageList()->GetBounds(); int x = r.x1; int y = r.y1; if (b) { b += d->cImage; x = r.x1 + ((r.X()-b->X()) / 2) - b->x1; y = r.y1 + ((r.Y()-b->Y()) / 2) - b->y1; } d->Parent->GetImageList()->Draw(pDC, x + Off, y + Off, d->cImage, Background); } if (d->cMark) { Mx += d->Parent->GetImageList()->TileX() + 4; } } else if (ValidStr(d->cName) && d->Txt) { LFont *f = d->Txt->GetFont(); if (!f) { LAssert(0); return; } LColour cText = Fore; #ifdef MAC // Contrast check if (d->cMark && (cText - cActiveCol) < 64) cText = cText.Invert(); #endif f->Transparent(!FillBackground); f->Colour(cText, cMed); int ty = d->Txt->Y(); int ry = r.Y(); int y = r.y1 + ((ry - ty) >> 1); d->Txt->Draw(pDC, r.x1 + Off + 3, y + Off, &r); if (d->cMark) { Mx += d->Txt->X(); } } else { if (FillBackground) { pDC->Colour(cMed); pDC->Rectangle(&r); } } #define ARROW_SIZE 9 pDC->Colour(Fore); Mx += Off; My += Off - 1; switch (d->cMark) { case GLI_MARK_UP_ARROW: { pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); pDC->Line(Mx, My + 2, Mx + 2, My); pDC->Line(Mx + 2, My, Mx + 4, My + 2); break; } case GLI_MARK_DOWN_ARROW: { pDC->Line(Mx + 2, My, Mx + 2, My + ARROW_SIZE - 1); pDC->Line( Mx, My + ARROW_SIZE - 3, Mx + 2, My + ARROW_SIZE - 1); pDC->Line( Mx + 2, My + ARROW_SIZE - 1, Mx + 4, My + ARROW_SIZE - 3); break; } } } } void ColumnPaint(void *UserData, LSurface *pDC, LRect &r, bool FillBackground) { ((LItemColumn*)UserData)->OnPaint_Content(pDC, r, FillBackground); } void LItemColumn::OnPaint(LSurface *pDC, LRect &Rgn) { LRect r = Rgn; if (d->Drag) { pDC->Colour(DragColumnColour); pDC->Rectangle(&r); } else { #ifdef MAC LArray Stops; LRect j(r.x1, r.y1, r.x2-1, r.y2-1); FillStops(Stops, j, d->cMark != 0); LFillGradient(pDC, j, true, Stops); if (d->cMark) pDC->Colour(Rgb24(0x66, 0x93, 0xc0), 24); else pDC->Colour(Rgb24(178, 178, 178), 24); pDC->Line(r.x1, r.y2, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x2, r.y2); LRect n = r; n.Inset(2, 2); OnPaint_Content(pDC, n, false); #else if (LApp::SkinEngine) { LSkinState State; State.pScreen = pDC; State.ptrText = &d->Txt; State.Rect = Rgn; State.Value = Value(); State.Enabled = GetList()->Enabled(); State.View = d->Parent; LApp::SkinEngine->OnPaint_ListColumn(ColumnPaint, this, &State); } else { if (d->Down) { LThinBorder(pDC, r, DefaultSunkenEdge); LFlatBorder(pDC, r, 1); } else { LWideBorder(pDC, r, DefaultRaisedEdge); } OnPaint_Content(pDC, r, true); } #endif } } /////////////////////////////////////////////////////////////////////////////////////////// LItem::LItem() { SelectionStart = SelectionEnd = -1; } LItem::~LItem() { } LView *LItem::EditLabel(int Col) { LItemContainer *c = GetContainer(); if (!c) return NULL; c->Capture(false); if (!c->ItemEdit) { c->ItemEdit = new LItemEdit(c, this, Col, SelectionStart, SelectionEnd); SelectionStart = SelectionEnd = -1; } return c->ItemEdit; } void LItem::OnEditLabelEnd() { LItemContainer *c = GetContainer(); if (c) c->ItemEdit = NULL; } void LItem::SetEditLabelSelection(int SelStart, int SelEnd) { SelectionStart = SelStart; SelectionEnd = SelEnd; } //////////////////////////////////////////////////////////////////////////////////////////// #define M_END_POPUP (M_USER+0x1500) #define M_LOSING_FOCUS (M_USER+0x1501) class LItemEditBox : public LEdit { LItemEdit *ItemEdit; public: LItemEditBox(LItemEdit *i, int x, int y, const char *s) : LEdit(100, 1, 1, x-3, y-3, s) { ItemEdit = i; Sunken(false); MultiLine(false); #ifndef LINUX SetPos(GetPos()); #endif } const char *GetClass() { return "LItemEditBox"; } void OnCreate() { LEdit::OnCreate(); Focus(true); } void OnFocus(bool f) { if (!f && GetParent()) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEditBox posting M_LOSING_FOCUS\n", _FL); #endif GetParent()->PostEvent(M_LOSING_FOCUS); } LEdit::OnFocus(f); } bool OnKey(LKey &k) { /* This should be handled by LEdit::OnKey now. Which will send a LNotifyEscapeKey or LNotifyReturnKey up to the ItemEdit OnNotify handler. switch (k.vkey) { case LK_RETURN: case LK_ESCAPE: { if (k.Down()) ItemEdit->OnNotify(this, k.c16); return true; } } */ return LEdit::OnKey(k); } bool SetScrollBars(bool x, bool y) { return false; } }; ////////////////////////////////////////////////////////////////////////////////////////// class LItemEditPrivate { public: LItem *Item; LEdit *Edit; int Index; bool Esc; LItemEditPrivate() { Esc = false; Item = 0; Index = 0; } }; LItemEdit::LItemEdit(LView *parent, LItem *item, int index, int SelStart, int SelEnd) : LPopup(parent) { d = new LItemEditPrivate; d->Item = item; d->Index = index; _BorderSize = 0; Sunken(false); Raised(false); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit(%p/%s, %i, %i, %i)\n", _FL, parent, parent?parent->GetClass():0, index, SelStart, SelEnd); #endif LPoint p; SetParent(parent); GetParent()->PointToScreen(p); LRect r = d->Item->GetPos(d->Index); int MinY = 6 + LSysFont->GetHeight(); if (r.Y() < MinY) r.y2 = r.y1 + MinY - 1; r.Offset(p.x, p.y); SetPos(r); if (Attach(parent)) { d->Edit = new LItemEditBox(this, r.X(), r.Y(), d->Item->GetText(d->Index)); if (d->Edit) { d->Edit->Attach(this); d->Edit->Focus(true); if (SelStart >= 0) { d->Edit->Select(SelStart, SelEnd-SelStart+1); } } Visible(true); } } LItemEdit::~LItemEdit() { if (d->Item) { if (d->Edit && !d->Esc) { auto Str = d->Edit->Name(); #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - ~LItemEdit, updating item(%i) with '%s'\n", _FL, d->Index, Str); #endif LItemContainer *c = d->Item->GetContainer(); if (d->Item->SetText(Str, d->Index)) { d->Item->Update(); } else { // Item is deleting itself... // Make sure there is no dangling ptr on the container.. if (c) c->ItemEdit = NULL; // And we don't touch the no longer existant item.. d->Item = NULL; } } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Edit=%p Esc=%i\n", _FL, d->Edit, d->Esc); #endif if (d->Item) d->Item->OnEditLabelEnd(); } #if DEBUG_EDIT_LABEL else LgiTrace("%s:%i - Error: No item?\n", _FL); #endif DeleteObj(d); } LItem *LItemEdit::GetItem() { return d->Item; } void LItemEdit::OnPaint(LSurface *pDC) { pDC->Colour(L_BLACK); pDC->Rectangle(); } int LItemEdit::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case 100: { if (n.Type == LNotifyEscapeKey) { d->Esc = true; #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got escape\n", _FL); #endif } if (n.Type == LNotifyEscapeKey || n.Type == LNotifyReturnKey) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit hiding on esc/enter\n", _FL); #endif d->Edit->KeyProcessed(); Visible(false); } break; } } return 0; } void LItemEdit::Visible(bool i) { LPopup::Visible(i); if (!i) { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit posting M_END_POPUP\n", _FL); #endif PostEvent(M_END_POPUP); } } bool LItemEdit::OnKey(LKey &k) { if (d->Edit) return d->Edit->OnKey(k); return false; } void LItemEdit::OnFocus(bool f) { if (f && d->Edit) d->Edit->Focus(true); } LMessage::Result LItemEdit::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_LOSING_FOCUS: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit get M_LOSING_FOCUS\n", _FL); #endif // One of us has to retain focus... don't care which control. if (Focus() || d->Edit->Focus()) break; // else fall thru to end the popup #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit falling thru to M_END_POPUP\n", _FL); #endif } case M_END_POPUP: { #if DEBUG_EDIT_LABEL LgiTrace("%s:%i - LItemEdit got M_END_POPUP, quiting\n", _FL); #endif if (d->Item && d->Item->GetContainer()) { d->Item->GetContainer()->Focus(true); } Quit(); return 0; } } return LPopup::OnEvent(Msg); } diff --git a/src/common/Widgets/ProgressDlg.cpp b/src/common/Widgets/ProgressDlg.cpp --- a/src/common/Widgets/ProgressDlg.cpp +++ b/src/common/Widgets/ProgressDlg.cpp @@ -1,639 +1,643 @@ /*hdr ** FILE: LProgressDlg.cpp ** AUTHOR: Matthew Allen ** DATE: 11/11/98 ** DESCRIPTION: Progress stuff ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/ProgressDlg.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/TableLayout.h" #include "lgi/common/LgiRes.h" ///////////////////////////////////////////////////////////////////////////////////////// Progress::Progress() : LMutex("Progress") { } Progress::Progress(char *desc, int64 l, int64 h, char *type, double scale) : LMutex("Progress") { Description = desc; Val = Low = l; High = h; Type = type; Scale = scale; } Progress::~Progress() { } LString Progress::GetDescription() { LString r; LMutex::Auto lck(this, _FL); if (!lck) LAssert(0); else r = Description.Get(); return r; } void Progress::SetDescription(const char *d) { LMutex::Auto lck(this, _FL); if (!lck) { LAssert(0); return; } if (d != Description) Description = d; } LString Progress::GetType() { LString s; { LMutex::Auto lck(this, _FL); if (lck) s = Type.Get(); } return s; } void Progress::SetType(const char *t) { LMutex::Auto lck(this, _FL); Type = t; } Progress &Progress::operator =(Progress &p) { SetDescription(p.GetDescription()); SetRange(p.GetRange()); SetScale(p.GetScale()); SetType(p.GetType()); Value(p.Value()); return *this; } ///////////////////////////////////////////////////////////////////////////////////////// #define IDC_DESCRIPTION 100 #define IDC_VALUE 101 #define IDC_RATE 102 #define IDC_PROGRESS 103 #define IDC_BUTTON 104 #define IDC_TABLE 105 #define IDC_PANE 106 #define IDC_REMAINING 107 #define PANE_X 300 #define PANE_Y 100 LProgressPane::LProgressPane(LProgressDlg *dlg) : Dlg(dlg) { LRect r(0, 0, PANE_X-1, PANE_Y-1); SetPos(r); Name(LLoadString(L_PROGRESSDLG_PROGRESS, "Progress")); SetId(IDC_PANE); Ref = 0; if (AddView(t = new LTableLayout(IDC_TABLE))) { OnPosChange(); #define PAD c->Padding(LCss::Len(LCss::LenPx, 1)); #define COLUMNS 3 int Row = 0, Col = 0; LLayoutCell *c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->Height("1.2em"); // This stops the layout flickering c->Add(Desc = new LTextLabel(IDC_DESCRIPTION, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row); PAD c->Add(ValText = new LTextLabel(IDC_VALUE, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row); PAD c->Add(Rate = new LTextLabel(IDC_RATE, 0, 0, -1, -1, "##")); c = t->GetCell(Col++, Row++); PAD c->Add(Remaining = new LTextLabel(IDC_REMAINING, 0, 0, -1, -1, "##")); c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->Add(Bar = new LProgressView(IDC_PROGRESS, 0, 0, PANE_X - 14, 10, "Progress")); c = t->GetCell(0, Row++, true, COLUMNS, 1); PAD c->TextAlign(LCss::Len(LCss::AlignCenter)); c->Add(But = new LButton(IDC_BUTTON, 0, 0, -1, -1, LLoadString(L_PROGRESSDLG_REQ_ABORT, "Request Abort"))); } } LProgressPane::~LProgressPane() { } bool LProgressPane::SetRange(const LRange &r) { UiDirty = true; Progress::SetRange(r); if (Bar) Bar->SetRange(r); if (InThread()) { LProgressDlg *Pd = dynamic_cast(GetParent()); if (Pd && But) But->Enabled(Pd->CanCancel); } return true; } +void LProgressPane::SetStartTs() +{ + Start = LCurrentTime(); + StartDt.SetNow(); +} + void LProgressPane::UpdateUI() { if (!UiDirty) return; char Str[256]; bool Update = false; UiDirty = false; uint64 Now = LCurrentTime(); if (Start == 0) { // initialize the clock Start = Now; StartDt.SetNow(); } else if (Rate) { // calc rate double ElapsedSeconds = ((double)(Now - Start)) / 1000.0; double PerSec = 0.0; if (ElapsedSeconds != 0.0) PerSec = ((double) Val - Low) / ElapsedSeconds; sprintf_s(Str, sizeof(Str), LLoadString(L_PROGRESSDLG_RATE_FMT, "@ %.2f %s / sec"), PerSec * Scale, (Type) ? Type.Get() : ""); Update |= Rate->Name(Str); - if (Remaining && PerSec > 0.0) + if (Remaining && PerSec > 0.0 && StartDt.IsValid()) { auto TotalSeconds = (High - Low + 1) / PerSec; // auto RemainingSeconds = TotalSeconds - ElapsedSeconds; LDateTime End; End.Set(StartDt.Ts() + (uint64_t)(TotalSeconds * LDateTime::Second64Bit)); if (auto Dur = LDateTime::Now().DescribePeriod(End)) Update |= Remaining->Name(Dur); } } if (ValText) { auto ValFmt = LLoadString(L_PROGRESSDLG_VALUE_FMT, "%g of %g %s"); sprintf_s(Str, sizeof(Str), ValFmt, (double)Val * Scale, (double)(High - Low) * Scale, (Type) ? Type.Get() : ""); Update |= ValText->Name(Str); } if (Bar) { if (High != Low) { #ifdef ALT_SCALE double Raw = ((double) v - Low) / ((double) High - Low); Bar->Value((int)(Raw * ALT_SCALE)); #else Bar->Value(Value()); #endif Bar->Invalidate(); } else { Bar->Value(0); } } if (ValText) ValText->SendNotify(LNotifyTableLayoutRefresh); } void LProgressPane::Value(int64 v) { Progress::Value(v); UiDirty = true; if (Dlg) Dlg->TimeCheck(); } void LProgressPane::OnCreate() { AttachChildren(); } LProgressPane &LProgressPane::operator++(int) { Value(Progress::Value() + 1); return *this; } LProgressPane &LProgressPane::operator--(int) { Value(Progress::Value() - 1); return *this; } int LProgressPane::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_TABLE: { if (n.Type == LNotifyTableLayoutChanged) { LRect p = GetPos(); LRect tbl_pos = t->GetPos(); LRect tbl_req = t->GetUsedArea(); if (tbl_req.Valid() && tbl_req.Y() > tbl_pos.Y()) { p.y2 = p.y1 + (tbl_req.Y() + LTableLayout::CellSpacing * 2); SetPos(p); SendNotify(LNotifyTableLayoutChanged); } } break; } case IDC_BUTTON: { Cancel(true); if (But) - { But->Name("Waiting..."); - } break; } } return 0; } void LProgressPane::OnPaint(LSurface *pDC) { LRect r = GetClient(); LThinBorder(pDC, r, DefaultRaisedEdge); pDC->Colour(L_MED); pDC->Rectangle(&r); } void LProgressPane::OnPosChange() { if (t) { LRect cr = GetClient(); cr.Inset(LTableLayout::CellSpacing, LTableLayout::CellSpacing); t->SetPos(cr); } } LFont *LProgressPane::GetFont() { // GdcBeTtf *Fnt = LSysFont; // return (Fnt) ? Fnt->Handle() : 0; return 0; } void LProgressPane::SetDescription(const char *d) { Progress::SetDescription(d); if (Desc) { Desc->Name(d); Desc->SendNotify(LNotifyTableLayoutRefresh); } } ///////////////////////////////////////////////////////////////////////////////////////// // #ifdef WIN32 // #define DefX ((GetSystemMetrics(SM_CXDLGFRAME) * 2)) // #define DefY ((GetSystemMetrics(SM_CYDLGFRAME) * 2) + GetSystemMetrics(SM_CYCAPTION)) // #else #define DefX LAppInst->GetMetric(LGI_MET_DECOR_X) #define DefY LAppInst->GetMetric(LGI_MET_DECOR_Y) // #endif LProgressDlg::LProgressDlg(LView *parent, uint64 timeout) { Ts = LCurrentTime(); Timeout = timeout; CanCancel = true; SetParent(parent); Resize(); if (parent) MoveSameScreen(parent); else MoveToCenter(); Name(LLoadString(L_PROGRESSDLG_PROGRESS, "Progress")); if (Timeout == 0) DoModeless(); else Push(); } LProgressDlg::~LProgressDlg() { if (Visible()) EndModeless(true); } bool LProgressDlg::OnRequestClose(bool OsClose) { for (auto p: Panes) p->Cancel(true); return false; } void LProgressDlg::Resize() { LRect r, c = GetPos(); int DecorX = LAppInst->GetMetric(LGI_MET_DECOR_X); int DecorY = LAppInst->GetMetric(LGI_MET_DECOR_Y); size_t Items = MAX(1, Panes.Length()); int Width = DecorX + PANE_X; int Height = (int) (DecorY + (PANE_Y * Items)); r.ZOff(Width - 1, Height - 1); r.Offset(c.x1, c.y1); SetPos(r); // Layout all the panes... int y = 0; for (auto p: Panes) { LRect r(0, y, PANE_X - 1, y + PANE_Y - 1); p->SetPos(r); p->Visible(true); y = r.y2 + 1; } } void LProgressDlg::OnCreate() { if (Panes.Length() == 0) Push(); SetPulse(500); } void LProgressDlg::OnPulse() { for (auto p: Panes) p->UpdateUI(); } void LProgressDlg::OnPosChange() { LRect c = GetClient(); // Layout all the panes... int y = 0; for (auto p: Panes) { LRect r = p->GetPos(); r.Offset(0, y-r.y1); r.x2 = c.x2; p->SetPos(r); p->Visible(true); y = r.y2 + 1; } } int LProgressDlg::OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_PANE && n.Type == LNotifyTableLayoutChanged) { // This code recalculates the size needed by all the progress panes // and then resizes the window to contain them all. LRect u(0, 0, -1, -1); for (auto p: Panes) { LRect r = p->GetPos(); if (u.Valid()) u.Union(&r); else u = r; } if (u.Valid()) { int x = u.X(); int y = u.Y(); LRect p = GetPos(); p.SetSize(x + LAppInst->GetMetric(LGI_MET_DECOR_X), y + LAppInst->GetMetric(LGI_MET_DECOR_Y)); SetPos(p); } } return 0; } LMessage::Result LProgressDlg::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #ifdef WIN32 case WM_CLOSE: { return 0; } #endif } return LDialog::OnEvent(Msg); } LProgressPane *LProgressDlg::ItemAt(int i) { return Panes.ItemAt(i); } LProgressPane *LProgressDlg::Push() { LProgressPane *Pane = new LProgressPane(this); if (Pane) { // Attach the new pane.. if (Visible()) Pane->Attach(this); else AddView(Pane); Panes.Add(Pane); Resize(); } return Pane; } void LProgressDlg::Pop(LProgressPane *p) { LProgressPane *Pane = (p) ? p : Panes.Last(); if (Pane) { Pane->Detach(); Panes.Delete(Pane); LView::Invalidate(); DeleteObj(Pane); Resize(); } } void LProgressDlg::SetCanCancel(bool cc) { CanCancel = cc; } LString LProgressDlg::GetDescription() { LString s; if (Panes.Length()) s = Panes.First()->GetDescription(); return s; } void LProgressDlg::SetDescription(const char *d) { if (Panes.Length()) Panes.First()->SetDescription(d); } LRange LProgressDlg::GetRange() { if (Panes.Length()) return Panes.First()->GetRange(); return LRange(); } bool LProgressDlg::SetRange(const LRange &r) { if (!Panes.Length()) return false; Panes.First()->SetRange(r); return true; } LProgressDlg &LProgressDlg::operator++(int) { if (Panes.Length()) { auto p = Panes.First(); (*p)++; } return *this; } LProgressDlg &LProgressDlg::operator--(int) { if (Panes.Length()) { auto p = Panes.First(); (*p)--; } return *this; } void LProgressDlg::TimeCheck() { if (!InThread()) return; uint64 Now = LCurrentTime(); if (Timeout) { if (Now - Ts >= Timeout) { DoModeless(); Timeout = 0; } } } int64 LProgressDlg::Value() { return Panes.Length() ? Panes.First()->Value() : -1; } void LProgressDlg::Value(int64 v) { if (Panes.Length()) Panes.First()->Value(v); } double LProgressDlg::GetScale() { return Panes.Length() ? Panes.First()->GetScale() : 0.0; } void LProgressDlg::SetScale(double s) { if (Panes.Length()) Panes.First()->SetScale(s); } LString LProgressDlg::GetType() { return Panes.Length() ? Panes.First()->GetType() : NULL; } void LProgressDlg::SetType(const char *t) { if (Panes.Length()) Panes.First()->SetType(t); } bool LProgressDlg::IsCancelled() { for (auto p: Panes) { if (p->IsCancelled()) return true; } return false; } void LProgressDlg::OnPaint(LSurface *pDC) { pDC->Colour(L_MED); pDC->Rectangle(); } diff --git a/src/common/Widgets/TableLayout.cpp b/src/common/Widgets/TableLayout.cpp --- a/src/common/Widgets/TableLayout.cpp +++ b/src/common/Widgets/TableLayout.cpp @@ -1,2524 +1,2529 @@ #include "lgi/common/Lgi.h" #include "lgi/common/TableLayout.h" #include "lgi/common/CssTools.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/Variant.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Button.h" #include "lgi/common/Edit.h" #include "lgi/common/Combo.h" #include "lgi/common/List.h" #include "lgi/common/Tree.h" #include "lgi/common/CheckBox.h" #include "lgi/common/RadioGroup.h" #include "lgi/common/Bitmap.h" #include "lgi/common/TabView.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Css.h" #undef _FL #define _FL LGetLeaf(__FILE__), __LINE__ enum CellFlag { // A null value when the call flag is not known yet SizeUnknown, // The cell contains fixed size objects SizeFixed, // The cell contains objects that have variable size SizeGrow, // The cell contains objects that will use all available space SizeFill, }; #define Izza(c) dynamic_cast(v) -// #define DEBUG_LAYOUT 20 +// #define DEBUG_LAYOUT 105 #define DEBUG_PROFILE 0 #define DEBUG_DRAW_CELLS 0 -// #define DEBUG_CTRL_ID 506 +// #define DEBUG_CTRL_ID 105 #ifdef DEBUG_CTRL_ID static LString Indent(int Depth) { return LString(" ") * (Depth << 2); } #endif struct LPrintfLogger : public LStream { public: ssize_t Write(const void *Ptr, ssize_t Size, int Flags = 0) { if (!Ptr) return 0; + + #ifdef WINNATIVE + LgiTrace("%.*s", (int)Size, (const char*)Ptr); + return Size; + #else return printf("%.*s", (int)Size, (const char*)Ptr); + #endif } } PrintfLogger; int LTableLayout::CellSpacing = 4; const char *FlagToString(CellFlag f) { switch (f) { case SizeUnknown: return "Unknown"; case SizeFixed: return "Fixed"; case SizeGrow: return "Grow"; case SizeFill: return "Fill"; } return "error"; } template T CountRange(LArray &a, ssize_t Start, ssize_t End) { T c = 0; for (ssize_t i=Start; i<=End; i++) { c += a[i]; } return c; } struct UnderInfo { int Priority; int Col; // index of column int Grow; // in px }; static void DistributeUnusedSpace( LArray &Min, LArray &Max, LArray &Flags, int Total, int CellSpacing, LStream *Debug = NULL) { // Now allocate unused space int Borders = (int)Min.Length() - 1; int Sum = CountRange(Min, 0, Borders) + (Borders * CellSpacing); if (Sum >= Total) return; int i, Avail = Total - Sum; // Do growable ones first LArray Unders; int UnknownGrow = 0; for (i=0; iPrint("\t\tAdding[%i] fill, pri=%i\n", i, u.Priority); */ } else if (Max[i] > Min[i]) { UnderInfo &u = Unders.New(); u.Col = i; u.Grow = Max[i] - Min[i]; if (u.Grow > Avail) { u.Grow = 0; u.Priority = 2; UnknownGrow++; } else { u.Priority = u.Grow < Avail >> 1 ? 0 : 1; } /* if (Debug) Debug->Print("\t\tAdding[%i] grow, pri=%i\n", i, u.Priority); */ } } Unders.Sort([](auto a, auto b) { if (a->Priority != b->Priority) return a->Priority - b->Priority; return b->Grow - a->Grow; }); int UnknownSplit = 0; for (i=0; Avail>0 && iPrint("\t\tGrow[%i] %i += %i\n", i, Min[u.Col], u.Grow); */ Min[u.Col] += u.Grow; Avail -= u.Grow; } else { if (!UnknownSplit) UnknownSplit = Avail / UnknownGrow; if (!UnknownSplit) UnknownSplit = Avail; /* if (Debug) Debug->Print("\t\tFill[%i] %i += %i\n", i, Min[u.Col], UnknownSplit); */ Min[u.Col] += UnknownSplit; Avail -= UnknownSplit; } } } static void DistributeSize( LArray &a, LArray &Flags, int Start, int Span, int Size, int Border, LStream *Debug = NULL) { // Calculate the current size of the cells int Cur = -Border; for (int i=0; i= Size) return; // Get list of growable cells LArray Grow, Fill, Fixed, Unknown; int ExistingGrowPx = 0; int ExistingFillPx = 0; int UnknownPx = 0; for (int i=Start; i 0 && Unknown.Length() > 0) { // Distribute size amongst the unknown cells int AdditionalSize = Size - Cur; for (int i=0; i Children; LCss::DisplayType Disp; LString ClassName; TableCell(LTableLayout *t, int Cx, int Cy); LTableLayout *GetTable() override { return Table; } bool Add(LView *v) override; bool Remove(LView *v) override; LArray GetChildren() override; bool RemoveAll(); Child *HasView(LView *v); LStream &Log(); bool IsSpanned(); bool GetVariant(const char *Name, LVariant &Value, const char *Array) override; bool SetVariant(const char *Name, LVariant &Value, const char *Array) override; int MaxCellWidth(); /// Calculates the minimum and maximum widths this cell can occupy. void LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag); /// Calculate the height of the cell based on the given width void LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags); /// Called after the layout has been done to move the controls into place void LayoutPost(int Depth); void OnPaint(LSurface *pDC); void OnChange(PropType Prop) override; }; class LTableLayoutPrivate { friend class TableCell; bool InLayout; bool DebugLayout; public: LPoint PrevSize, Dpi; bool LayoutDirty; LArray Rows, Cols; LArray Cells; int BorderSpacing; LRect LayoutBounds; int LayoutMinX, LayoutMaxX; LTableLayout *Ctrl; LStream &Log() { return PrintfLogger; } // Object LTableLayoutPrivate(LTableLayout *ctrl); ~LTableLayoutPrivate(); // Utils bool IsInLayout() { return InLayout; } TableCell *GetCellAt(int cx, int cy); void Empty(LRect *Range = NULL); bool CollectRadioButtons(LArray &Btns); void InitBorderSpacing(); double Scale() { if (!Dpi.x) { auto Wnd = Ctrl->GetWindow(); if (Wnd) Dpi = Wnd->GetDpi(); else Dpi.x = Dpi.y = 96; } LAssert(Dpi.x > 0); return Dpi.x / 96.0; } // Layout temporary values LArray MinCol, MaxCol; LArray MinRow, MaxRow; LArray ColFlags, RowFlags; // Layout staged methods, call in order to complete the layout void LayoutHorizontal(const LRect Client, int Depth, int *MinX = NULL, int *MaxX = NULL, CellFlag *Flag = NULL); void LayoutVertical(const LRect Client, int Depth, int *MinY = NULL, int *MaxY = NULL, CellFlag *Flag = NULL); void LayoutPost(const LRect Client, int Depth); // This does the whole layout, basically calling all the stages for you void Layout(const LRect Client, int Depth); }; /////////////////////////////////////////////////////////////////////////////////////////// TableCell::TableCell(LTableLayout *t, int Cx, int Cy) { TextAlign(AlignLeft); VerticalAlign(VerticalTop); Table = t; Cell.ZOff(0, 0); Cell.Offset(Cx, Cy); Padding.ZOff(0, 0); Pos.ZOff(-1, -1); Disp = LCss::DispBlock; Children.SetFixedLength(true); } void TableCell::OnChange(PropType Prop) { if (Prop == PropDisplay) { bool Vis = Display() != LCss::DispNone; for (auto c: Children) c.View->Visible(Vis); } } LStream &TableCell::Log() { /* if (!TopLevelLog) { // Find the top most table layout LTableLayout *Top = Table; for (LViewI* t = Top; t; t = t->GetParent()) { auto tbl = dynamic_cast(t); if (tbl) Top = tbl; if (t->GetId() == 24) break; } TopLevelLog = &Top->d->Dbg; } LAssert(TopLevelLog != NULL); return *TopLevelLog; */ return PrintfLogger; } TableCell::Child *TableCell::HasView(LView *v) { size_t Len = Children.Length(); if (!Len) return NULL; Child *s = &Children[0]; Child *e = s + Len; while (s < e) { if (s->View == v) return s; s++; } return NULL; } LArray TableCell::GetChildren() { LArray a; for (auto &c: Children) a.Add(c.View); return a; } bool TableCell::Add(LView *v) { if (!v || HasView(v)) return false; if (Table->IsAttached()) v->Attach(Table); else Table->AddView(v); Children.SetFixedLength(false); Children.New().View = v; Children.SetFixedLength(true); return true; } bool TableCell::Remove(LView *v) { Child *c = HasView(v); if (!c) return false; Table->DelView(v); if (Children.Length()) { int Idx = (int) (c - &Children.First()); Children.DeleteAt(Idx, true); } return true; } bool TableCell::RemoveAll() { Child *c = &Children[0]; for (unsigned i=0; i 1 || Cell.Y() > 1; } bool TableCell::GetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (!Value.SetList()) return false; for (unsigned i=0; iType = GV_GVIEW; v->Value.View = c.View; Value.Value.Lst->Insert(v); } } break; } case ContainerSpan: // Type: LRect { Value = Cell.GetStr(); break; } case ContainerAlign: // Type: String { LStringPipe p(128); if (TextAlign().ToString(p)) Value.OwnStr(p.NewStr()); else return false; break; } case ContainerVAlign: // Type: String { LStringPipe p(128); if (VerticalAlign().ToString(p)) Value.OwnStr(ToString()); else return false; break; } case ObjClass: // Type: String { Value = ClassName; break; } case ObjStyle: // Type: String { Value.OwnStr(ToString()); break; } case ObjDebug: // Type: Boolean { Value = Debug; break; } default: return false; } return true; } bool TableCell::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty Fld = LStringToDomProp(Name); switch (Fld) { case ContainerChildren: // Type: LView[] { if (Value.Type != GV_LIST) { LAssert(!"Should be a list."); return false; } // LProfile p("ContainerChildren"); for (auto v: *Value.Value.Lst) { if (v->Type != GV_VOID_PTR) continue; ResObject *o = (ResObject*)v->Value.Ptr; if (!o) continue; LView *gv = dynamic_cast(o); if (!gv) continue; // p.Add("c1"); Children.SetFixedLength(false); Children.New().View = gv; Children.SetFixedLength(true); // p.Add("c2"); Table->AddView(gv); // p.Add("c3"); gv->SetParent(Table); // p.Add("c4"); LTextLabel *t = dynamic_cast(gv); if (t) t->SetWrap(true); } break; } case ContainerSpan: { LRect r; if (r.SetStr(Value.Str())) Cell = r; else return false; break; } case ContainerAlign: { TextAlign ( Len ( ConvertAlign(Value.Str(), true) ) ); break; } case ContainerVAlign: { VerticalAlign ( Len ( ConvertAlign(Value.Str(), false) ) ); break; } case ObjClass: { ClassName = Value.Str(); LResources *r = LgiGetResObj(); if (r) { LCss::SelArray *a = r->CssStore.ClassMap.Find(ClassName); if (a) { for (int i=0; iLength(); i++) { LCss::Selector *s = (*a)[i]; // This is not exactly a smart matching algorithm. if (s && s->Parts.Length() == 1) { const char *style = s->Style; Parse(style, ParseRelaxed); } } } } break; } case ObjStyle: { const char *style = Value.Str(); if (style) Parse(style, ParseRelaxed); break; } case ObjDebug: { Debug = Value.CastInt32() != 0; break; } default: return false; } return true; } int TableCell::MaxCellWidth() { // Table size minus padding LCssTools t(Table->GetCss(), Table->GetFont()); LRect cli = Table->GetClient(); cli = t.ApplyPadding(cli); // Work out any borders on spanned cells... int BorderPx = Cell.X() > 1 ? (Cell.X() - 1) * Table->d->BorderSpacing : 0; // Return client - padding - border size. return cli.X() - BorderPx; } /// Calculates the minimum and maximum widths this cell can occupy. void TableCell::LayoutWidth(int Depth, int &MinX, int &MaxX, CellFlag &Flag) { int MaxBtnX = 0; int TotalBtnX = 0; int Min = 0, Max = 0; Pos = Pos.ZeroTranslate(); // Calculate CSS padding #define CalcCssPadding(Prop, Axis, Edge) \ { \ Len l = Prop(); \ if (l.Type == LCss::LenInherit) l = LCss::Padding(); \ if (l.Type) \ Padding.Edge = l.ToPx(Table->Axis(), Table->GetFont()); \ else \ Padding.Edge = 0; \ } Disp = Display(); if (Disp == DispNone) return; CalcCssPadding(PaddingLeft, X, x1) CalcCssPadding(PaddingRight, X, x2) CalcCssPadding(PaddingTop, Y, y1) CalcCssPadding(PaddingBottom, Y, y2) // Cell CSS size: auto Wid = Width(); auto MinWid = MinWidth(); auto MaxWid = MaxWidth(); auto Fnt = Table->GetFont(); int Tx = Table->X(); if (MinWid.IsValid()) Min = MAX(Min, MinWid.ToPx(Tx, Fnt)); if (Wid.IsValid()) { if (Wid.Type == LCss::LenAuto) Flag = SizeFill; else { // If we set Min here too the console app breaks because the percentage // is over-subscribe (95%) to force one cell to grow to fill available space. Max = Wid.ToPx(Tx, Fnt) - Padding.x1 - Padding.x2; if (!Wid.IsDynamic()) { Flag = SizeFixed; Min = Max; /* This is breaking normal usage, where 'Min' == 0. Need to redesign for edge case. if (Padding.x1 + Padding.x2 > Min) { // Remove padding as it's going to oversize the cell Padding.x1 = Padding.x2 = 0; } */ } else { Flag = SizeGrow; } } } if (!Wid.IsValid() || Flag != SizeFixed) { Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; ZeroObj(c->Inf); c->r = v->GetPos().ZeroTranslate(); // Child view CSS size: LCss *Css = v->GetCss(); LCss::Len ChildWid; // LCss::Len ChildMin, ChildMax; if (Css) { ChildWid = Css->Width(); // ChildMin = Css->MinWidth(); // ChildMax = Css->MaxWidth(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (ChildWid.IsValid()) { int MaxPx = MaxCellWidth(); c->Inf.Width.Min = c->Inf.Width.Max = ChildWid.ToPx(MaxPx, v->GetFont()); Min = MAX(Min, c->Inf.Width.Min); Max = MAX(Max, c->Inf.Width.Min); c->r = v->GetPos(); c->r.x2 = c->r.x1 + c->Inf.Width.Min - 1; } else if (v->OnLayout(c->Inf)) { if (c->Inf.Width.Max < 0) { if (Flag != SizeFixed) Flag = SizeFill; } else if (Max) Max += c->Inf.Width.Max + LTableLayout::CellSpacing; else Max = c->Inf.Width.Max; if (c->Inf.Width.Min) { Min = MAX(Min, c->Inf.Width.Min); if (c->Inf.Width.Max > c->Inf.Width.Min && Flag == SizeUnknown) Flag = SizeGrow; } } else if (Izza(LButton)) { LDisplayString ds(v->GetFont(), v->Name()); auto Scale = Table->d->Scale(); LPoint Pad((int)(Scale * LButton::Overhead.x + 0.5), (int)(Scale * LButton::Overhead.y + 0.5)); c->Inf.Width.Min = c->Inf.Width.Max = ds.X() + Pad.x; c->Inf.Height.Min = c->Inf.Height.Max = ds.Y() + Pad.y; MaxBtnX = MAX(MaxBtnX, c->Inf.Width.Min); TotalBtnX = TotalBtnX ? TotalBtnX + LTableLayout::CellSpacing + c->Inf.Width.Min : c->Inf.Width.Min; if (Flag < SizeFixed) Flag = SizeFixed; } else if (Izza(LEdit) || Izza(LScrollBar)) { Min = MAX(Min, 40); if (Flag != SizeFixed) Flag = SizeFill; } else if (Izza(LCombo)) { int PadX = LCombo::Pad.x1 + LCombo::Pad.x2; LCombo *Cbo = Izza(LCombo); LFont *f = Cbo->GetFont(); int min_x = -1, max_x = 0; char *t; for (int i=0; i < Cbo->Length() && (t = (*Cbo)[i]); i++) { LDisplayString ds(f, t); int x = ds.X(); min_x = min_x < 0 ? x : MIN(min_x, x); max_x = MAX(x + 4, max_x); } Min = MAX(Min, min_x + PadX); Max = MAX(Max, max_x + PadX); if (Flag < SizeGrow) Flag = SizeGrow; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { Min = MAX(Min, Dc->X() + 4); Max = MAX(Max, Dc->X() + 4); } else { Min = MAX(Min, 16); Max = MAX(Max, 16); if (Flag < SizeFill) Flag = SizeFill; } } else if (Izza(LList)) { LList *Lst = Izza(LList); int m = 0; for (int i=0; iGetColumns(); i++) { m += Lst->ColumnAt(i)->Width(); } m = MAX(m, 40); Min = MAX(Min, 40); Flag = SizeFill; } else if (Izza(LTree) || Izza(LTabView)) { Min = MAX(Min, 40); Flag = SizeFill; } else { LTableLayout *Tbl = Izza(LTableLayout); if (Tbl) { Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(Table->GetClient(), Depth+1, &Min, &Max, &Flag); if (Wid.IsDynamic()) { if (Min > Max) Min = Max; } } else { Min = MAX(Min, v->X()); Max = MAX(Max, v->X()); } } } } if (MaxBtnX) { Min = MAX(Min, MaxBtnX); Max = MAX(Max, TotalBtnX); } if (MaxWid.IsValid()) { int Tx = Table->X(); int Px = MaxWid.ToPx(Tx, Table->GetFont()) - Padding.x1 - Padding.x2; if (Min > Px) Min = Px; if (Max > Px) Max = Px; } MinX = MAX(MinX, Min + Padding.x1 + Padding.x2); MaxX = MAX(MaxX, Max + Padding.x1 + Padding.x2); } /// Calculate the height of the cell based on the given width void TableCell::LayoutHeight(int Depth, int Width, int &MinY, int &MaxY, CellFlag &Flags) { Pos.ZOff(Width-1, 0); if (Disp == DispNone) return; Len Ht = Height(); if (Ht.Type != LenInherit) { if (Ht.IsDynamic()) { MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); if (Flags < SizeGrow) Flags = SizeGrow; } else { MinY = MaxY = Ht.ToPx(Table->Y(), Table->GetFont()); Pos.y2 = MinY - 1; if (Flags < SizeFixed) Flags = SizeFixed; return; } } LPoint Cur(Pos.x1, Pos.y1); int NextY = Pos.y1; LCss::Len CssWidth = LCss::Width(); Width -= Padding.x1 + Padding.x2; LAssert(Width >= 0); Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); #endif if (CssWidth.Type != LenInherit) { // In the case where the CSS width is set, we need to default the // OnLayout info with valid widths otherwise the OnLayout calls will // not fill out the height, but initialize the width instead. // Without this !zero check the Bayesian Filter setting dialog in Scribe // has the wrong Help button size. ie if the PreLayout step gave a valid // layout size, don't override it here. if (!c->Inf.Width.Min) c->Inf.Width.Min = Width; if (!c->Inf.Width.Max) c->Inf.Width.Max = Width; } LTableLayout *Tbl = NULL; LCss *Css = v->GetCss(); LCss::Len Ht; if (Css) Ht = Css->Height(); if (!Izza(LButton) && i) Pos.y2 += Table->d->BorderSpacing; if (c->Inf.Width.Min > Width) c->Inf.Width.Min = Width; if (c->Inf.Width.Max > Width) c->Inf.Width.Max = Width; if (Ht.IsValid()) { int CtrlHeight = Ht.ToPx(Table->Y(), v->GetFont()); c->Inf.Height.Min = c->Inf.Height.Max = CtrlHeight; if (MaxY < CtrlHeight) MaxY = CtrlHeight; if (!Ht.IsDynamic() && MinY < CtrlHeight) MinY = CtrlHeight; c->r = v->GetPos().ZeroTranslate(); c->r.y2 = c->r.y1 + CtrlHeight - 1; Pos.y2 = MAX(Pos.y2, c->r.Y()-1); } else if (v->OnLayout(c->Inf)) { // Supports layout info c->r = v->GetPos(); // Process height if (c->Inf.Height.Max < 0) Flags = SizeFill; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; // Process width if (c->Inf.Width.Max < 0) c->r.x2 = c->r.x1 + Width - 1; else c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; if (Cur.x > Pos.x1 && Cur.x + c->r.X() > Pos.x2) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x - c->r.x1, Cur.y - c->r.y1); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); c->IsLayout = true; } else if (Izza(LScrollBar)) { Pos.y2 += 15; } else if (Izza(LButton)) { c->r.ZOff(c->Inf.Width.Min-1, c->Inf.Height.Min-1); if (Cur.x + c->r.X() > Width) { // Wrap Cur.x = Pos.x1; Cur.y = NextY + LTableLayout::CellSpacing; } c->r.Offset(Cur.x, Cur.y); Cur.x = c->r.x2 + 1; NextY = MAX(NextY, c->r.y2 + 1); Pos.y2 = MAX(Pos.y2, c->r.y2); } else if (Izza(LEdit) || Izza(LCombo)) { LFont *f = v->GetFont(); int y = (f ? f : LSysFont)->GetHeight() + 8; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; if (Izza(LEdit) && Izza(LEdit)->MultiLine()) { Flags = SizeFill; // MaxY = MAX(MaxY, 1000); } } else if (Izza(LRadioButton)) { int y = v->GetFont()->GetHeight() + 2; c->r = v->GetPos(); c->r.y2 = c->r.y1 + y - 1; Pos.y2 += y; } else if (Izza(LList) || Izza(LTree) || Izza(LTabView)) { Pos.y2 += v->GetFont()->GetHeight() + 8; // MaxY = MAX(MaxY, 1000); Flags = SizeFill; } else if (Izza(LBitmap)) { LBitmap *b = Izza(LBitmap); LSurface *Dc = b->GetSurface(); if (Dc) { MaxY = MAX(MaxY, Dc->Y() + 4); } else { MaxY = MAX(MaxY, 1000); } } else if ((Tbl = Izza(LTableLayout))) { auto Children = Tbl->IterateViews(); c->r.ZOff(Width-1, Table->Y()-1); LCssTools tools(Tbl->GetCss(), Tbl->GetFont()); auto client = tools.ApplyBorder(c->r); Tbl->d->InitBorderSpacing(); Tbl->d->LayoutHorizontal(client, Depth+1); Tbl->d->LayoutVertical(client, Depth+1, &MinY, &MaxY, &Flags); Tbl->d->LayoutPost(client, Depth+1); Pos.y2 += MinY; c->Inf.Height.Min = MinY; c->Inf.Height.Max = MaxY; } else { // Doesn't support layout info Pos.y2 += v->Y(); } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i pos=%s c->r=%s (%s:%i)\n", v->GetId(), Pos.GetStr(), c->r.GetStr(), _FL); } #endif } // Fix: This if statement is needed to stop LFileSelect dialogs only growing in size, and // the Ok/Cancel shifting off the bottom of the dialog if you shrink the window. if (Flags != SizeFill) { MinY = MAX(MinY, Pos.Y() + Padding.y1 + Padding.y2); MaxY = MAX(MaxY, Pos.Y() + Padding.y1 + Padding.y2); } } /// Called after the layout has been done to move the controls into place void TableCell::LayoutPost(int Depth) { int Cx = Padding.x1; int Cy = Padding.y1; int MaxY = Padding.y1; int RowStart = 0; LArray New; int WidthPx = Pos.X() - Padding.x1 - Padding.x2; int HeightPx = Pos.Y() - Padding.y1 - Padding.y2; #ifdef DEBUG_CTRL_ID bool HasDebugCtrl = false; #endif Child *c = Children.AddressOf(); for (int i=0; iView; if (!v) continue; if (Disp == DispNone) { v->Visible(false); continue; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s padding=%s cur=%i,%i (%s:%i)\n", v->GetId(), c->r.GetStr(), Padding.GetStr(), Cx, Cy, _FL); } #endif LTableLayout *Tbl = Izza(LTableLayout); if (i > 0 && Cx + c->r.X() > Pos.X()) { // Do wrapping int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } for (int n=RowStart; n<=i; n++) { New[n].Offset(OffsetX, 0); } RowStart = i + 1; Cx = Padding.x1; Cy = MaxY + Table->d->BorderSpacing; } #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i offset %i %i %i, %i %i %i %s:%i\n", v->GetId(), Pos.x1, c->r.x1, Cx, Pos.y1, c->r.y1, Cy, _FL); } #endif c->r.Offset(Pos.x1 - c->r.x1 + Cx, Pos.y1 - c->r.y1 + Cy); if (c->Inf.Width.Max >= WidthPx) c->Inf.Width.Max = WidthPx; if (c->Inf.Height.Max >= HeightPx) c->Inf.Height.Max = HeightPx; if (c->r.Y() > HeightPx) { c->r.y2 = c->r.y1 + HeightPx - 1; } if (Tbl) { c->r.SetSize(Pos.X(), MIN(Pos.Y(), c->Inf.Height.Min)); } else if ( Izza(LList) || Izza(LTree) || Izza(LTabView) || (Izza(LEdit) && Izza(LEdit)->MultiLine()) ) { c->r.y2 = Pos.y2; if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } else if (c->IsLayout) { if (c->Inf.Height.Max < 0) c->r.y2 = Pos.y2; else c->r.y2 = c->r.y1 + c->Inf.Height.Max - 1; } - else - // if (!Izza(LButton) && !Tbl) + else { if (c->Inf.Width.Max <= 0 || c->Inf.Width.Max >= WidthPx) c->r.x2 = c->r.x1 + WidthPx - 1; else if (c->Inf.Width.Max) c->r.x2 = c->r.x1 + c->Inf.Width.Max - 1; } New[i] = c->r; #ifdef DEBUG_CTRL_ID if (v->GetId() == DEBUG_CTRL_ID) { HasDebugCtrl = true; Log().Print("\t\tdbgCtrl=%i c->r=%s (%s:%i)\n", v->GetId(), c->r.GetStr(), _FL); } #endif MaxY = MAX(MaxY, c->r.y2 - Pos.y1); Cx += c->r.X() + Table->d->BorderSpacing; } if (Disp == DispNone) { return; } int n; int Wid = Cx - Table->d->BorderSpacing; int OffsetX = 0; if (TextAlign().Type == AlignCenter) { OffsetX = (Pos.X() - Wid) / 2; } else if (TextAlign().Type == AlignRight) { OffsetX = Pos.X() - Wid; } if (OffsetX) { for (n=RowStart; nd->DebugLayout) { Log().Print("\tCell[%i,%i]=%s (%ix%i)\n", Cell.x1, Cell.y1, Pos.GetStr(), Pos.X(), Pos.Y()); } #endif for (n=0; nGetId() == DEBUG_CTRL_ID) { Log().Print("\t\tdbgCtrl=%i %s[%i]=%s, %ix%i, Offy=%i %s (%s:%i)\n", v->GetId(), v->GetClass(), n, New[n].GetStr(), New[n].X(), New[n].Y(), OffsetY, v->Name(), _FL); } #endif v->SetPos(New[n]); } } void TableCell::OnPaint(LSurface *pDC) { LCssTools t(this, Table->GetFont()); LRect r = Pos; t.PaintBorder(pDC, r); LColour Trans; auto bk = t.GetBack(&Trans); if (bk.IsValid()) { pDC->Colour(bk); pDC->Rectangle(&r); } } //////////////////////////////////////////////////////////////////////////// LTableLayoutPrivate::LTableLayoutPrivate(LTableLayout *ctrl) { PrevSize.Set(-1, -1); LayoutDirty = true; InLayout = false; DebugLayout = false; Ctrl = ctrl; BorderSpacing = LTableLayout::CellSpacing; LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } LTableLayoutPrivate::~LTableLayoutPrivate() { Empty(); } bool LTableLayoutPrivate::CollectRadioButtons(LArray &Btns) { for (LViewI *i: Ctrl->IterateViews()) { LRadioButton *b = dynamic_cast(i); if (b) Btns.Add(b); } return Btns.Length() > 0; } void LTableLayoutPrivate::Empty(LRect *Range) { if (Range) { // Clear a range of cells.. for (int i=0; iOverlap(&c->Cell)) { c->RemoveAll(); Cells.DeleteAt(i--, true); DeleteObj(c); } } } else { // Clear all the cells Ctrl->IterateViews().DeleteObjects(); Cells.DeleteObjects(); Rows.Length(0); Cols.Length(0); } PrevSize.Set(-1, -1); LayoutBounds.ZOff(-1, -1); LayoutMinX = LayoutMaxX = 0; } TableCell *LTableLayoutPrivate::GetCellAt(int cx, int cy) { for (int i=0; iCell.Overlap(cx, cy)) return Cells[i]; return NULL; } void LTableLayoutPrivate::LayoutHorizontal(const LRect Client, int Depth, int *MinX, int *MaxX, CellFlag *Flag) { // This only gets called when you nest LTableLayout controls. It's // responsible for doing pre layout stuff for an entire control of cells. int Cx, Cy, i; LString::Array Ps; Ps.SetFixedLength(false); LAutoPtr Prof(/*Debug ? new LProfile("Layout") :*/ NULL); // Zero everything to start with MinCol.Length(0); MaxCol.Length(0); MinRow.Length(0); MaxRow.Length(0); ColFlags.Length(0); RowFlags.Length(0); #if DEBUG_LAYOUT if (DebugLayout) { - // int asd=0; + int asd=0; } #endif // Do pre-layout to determine minimum and maximum column widths for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() == 1) { if (Prof) { LString &s = Ps.New(); s.Printf("pre layout %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } int &MinC = MinCol[Cx]; int &MaxC = MaxCol[Cx]; CellFlag &ColF = ColFlags[Cx]; c->LayoutWidth(Depth, MinC, MaxC, ColF); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { Log().Print("\tLayout Id=%i, Size=%i,%i (%s:%i)\n", Ctrl->GetId(), Client.X(), Client.Y(), _FL); for (i=0; iAdd("Pre layout spanned"); // Pre-layout column width for spanned cells for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.X() > 1) { int Min = 0, Max = 0; CellFlag Flag = SizeUnknown; if (Prof) { LString &s = Ps.New(); s.Printf("spanned %i,%i", c->Cell.x1, c->Cell.y1); Prof->Add(s); } if (c->Width().IsValid()) { LCss::Len l = c->Width(); if (l.Type == LCss::LenAuto) { for (int i=c->Cell.x1; i<=c->Cell.x2; i++) { ColFlags[i] = SizeFill; } } else { int Px = l.ToPx(Client.X(), Ctrl->GetFont());; if (l.IsDynamic()) { c->LayoutWidth(Depth, Min, Max, Flag); } else { Min = Max = Px; } } } else { c->LayoutWidth(Depth, Min, Max, Flag); } // Log().Print("Spanned cell: %i,%i\n", Min, Max); if (Max > Client.X()) Max = Client.X(); if (Flag) { bool HasFlag = false; bool HasUnknown = false; for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (ColFlags[i] == Flag) { HasFlag = true; } else if (ColFlags[i] == SizeUnknown) { HasUnknown = true; } } if (!HasFlag) { for (i=c->Cell.x1; i<=c->Cell.x2; i++) { if (HasUnknown) { if (ColFlags[i] == SizeUnknown) ColFlags[i] = Flag; } else { if (ColFlags[i] < Flag) ColFlags[i] = Flag; } } } } // This is the total size of all the px currently allocated int AllPx = CountRange(MinCol, 0, Cols.Length()-1) + (((int)Cols.Length() - 1) * BorderSpacing); // This is the minimum size of this cell's cols int MyPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int Remaining = Client.X() - AllPx; // Log().Print("AllPx=%i MyPx=%i, Remaining=%i\n", AllPx, MyPx, Remaining); // This is the total remaining px we could add... if (Remaining > 0) { // Limit the max size of this cell to the existing + remaining px Max = MIN(Max, MyPx + Remaining); // Distribute the max px across the cell's columns. DistributeSize(MinCol, ColFlags, c->Cell.x1, c->Cell.X(), Min, BorderSpacing); DistributeSize(MaxCol, ColFlags, c->Cell.x1, c->Cell.X(), Max, BorderSpacing); } } Cx += c->Cell.X(); } else Cx++; } } LayoutMinX = -BorderSpacing; LayoutMaxX = -BorderSpacing; for (i=0; iAdd("DistributeUnusedSpace"); DistributeUnusedSpace(MinCol, MaxCol, ColFlags, Client.X(), BorderSpacing, DebugLayout?&Log():NULL); #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iAdd("Collect together our sizes"); int Spacing = BorderSpacing * ((int)MinCol.Length() - 1); auto Css = Ctrl->GetCss(); if (MinX) { int x = CountRange(MinCol, 0, MinCol.Length()-1) + Spacing; *MinX = MAX(*MinX, x); if (Css) { auto MinWid = Css->MinWidth(); if (MinWid.IsValid()) { int px = MinWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MinX = MAX(*MinX, px); } } } if (MaxX) { int x = CountRange(MaxCol, 0, MinCol.Length()-1) + Spacing; *MaxX = MAX(*MaxX, x); if (Css) { auto MaxWid = Css->MaxWidth(); if (MaxWid.IsValid()) { int px = MaxWid.ToPx(Ctrl->X(), Ctrl->GetFont()); *MaxX = MIN(*MaxX, px); } } } if (Flag) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() == 1) { int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int &Min = MinRow[Cy]; int &Max = MaxRow[Cy]; CellFlag &Flags = RowFlags[Cy]; c->LayoutHeight(Depth, x, Min, Max, Flags); } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iCell.x1 == Cx && c->Cell.y1 == Cy && c->Cell.Y() > 1) { int WidthPx = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int InitMinY = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); int InitMaxY = CountRange(MaxRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); //int AllocY = CountRange(MinRow, 0, Rows.Length()-1) + ((Rows.Length() - 1) * BorderSpacing); int MinY = InitMinY; int MaxY = InitMaxY; // int RemainingY = Client.Y() - AllocY; CellFlag RowFlag = SizeUnknown; c->LayoutHeight(Depth, WidthPx, MinY, MaxY, RowFlag); // This code stops the max being set on spanned cells. LArray AddTo; for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if ( RowFlags[y] != SizeFixed && ( RowFlags[y] != SizeGrow || MaxRow[y] > MinRow[y] ) ) AddTo.Add(y); } if (AddTo.Length() == 0) { for (int y=c->Cell.y1; y<=c->Cell.y2; y++) { if (!AddTo.HasItem(y)) { if (RowFlags[y] != SizeFixed) AddTo.Add(y); } } } if (AddTo.Length()) { if (MinY > InitMinY) { // Allocate any extra min px somewhere.. int Amt = (MinY - InitMinY) / (int)AddTo.Length(); for (int i=0; i InitMaxY) { // Allocate any extra max px somewhere.. int Amt = (MaxY - InitMaxY) / (int)AddTo.Length(); for (int i=0; i SizeUnknown) { // Apply the size flag somewhere... for (int y=c->Cell.y2; y>=c->Cell.y1; y++) { if (RowFlags[y] == SizeUnknown) { RowFlags[y] = SizeFill; break; } } } } else { // Last chance... stuff extra px in last cell... MaxRow[c->Cell.y2] = MAX(MaxY, MaxRow[c->Cell.y2]); } } Cx += c->Cell.X(); } else Cx++; } } #if DEBUG_LAYOUT if (DebugLayout) { for (i=0; iGetCss(); if (MinY) { int y = CountRange(MinRow, 0, MinRow.Length()-1) + (((int)MinRow.Length()-1) * BorderSpacing); *MinY = MAX(*MinY, y); if (Css) { auto MinHt = Css->MinHeight(); if (MinHt.IsValid()) { auto px = MinHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MinY = MAX(*MinY, px); } } } if (MaxY) { int y = CountRange(MaxRow, 0, MinRow.Length()-1) + (((int)MaxRow.Length()-1) * BorderSpacing); *MaxY = MAX(*MaxY, y); if (Css) { auto MaxHt = Css->MaxHeight(); if (MaxHt.IsValid()) { auto px = MaxHt.ToPx(Ctrl->Y(), Ctrl->GetFont()); *MaxY = MIN(*MaxY, px); } } } if (Flag) { for (i=0; iGetFont(); // Move cells into their final positions #if DEBUG_LAYOUT if (DebugLayout && Cols.Length() == 7) Log().Print("\tLayoutPost %ix%i\n", Cols.Length(), Rows.Length()); #endif for (Cy=0; CyCell.x1 == Cx && c->Cell.y1 == Cy) { LCss::PositionType PosType = c->Position(); int x = CountRange(MinCol, c->Cell.x1, c->Cell.x2) + ((c->Cell.X() - 1) * BorderSpacing); int y = CountRange(MinRow, c->Cell.y1, c->Cell.y2) + ((c->Cell.Y() - 1) * BorderSpacing); // Set the height of the cell c->Pos.x2 = c->Pos.x1 + x - 1; c->Pos.y2 = c->Pos.y1 + y - 1; if (PosType == LCss::PosAbsolute) { // Hmm this is a bit of a hack... we'll see LCss::Len Left = c->Left(); LCss::Len Top = c->Top(); int LeftPx = Left.IsValid() ? Left.ToPx(Client.X(), Fnt) : Px; int TopPx = Top.IsValid() ? Top.ToPx(Client.Y(), Fnt) : Py; c->Pos.Offset(Client.x1 + LeftPx, Client.y1 + TopPx); } else { c->Pos.Offset(Client.x1 + Px, Client.y1 + Py); #if 0//def DEBUG_CTRL_ID Log().Print("\t\tdbgCtrl=%i Client=%s pos=%s p=%i,%i (%s:%i)\n", DEBUG_CTRL_ID, Client.GetStr(), c->Pos.GetStr(), Px, Py, _FL); #endif } c->LayoutPost(Depth); MaxY = MAX(MaxY, c->Pos.y2); #if DEBUG_LAYOUT if (DebugLayout) { c->Log().Print("\tCell[%i][%i]: %s %s\n", Cx, Cy, c->Pos.GetStr(), Client.GetStr()); } #endif } Px = c->Pos.x2 + BorderSpacing - Client.x1 + 1; Cx += c->Cell.X(); } else { Px = MinCol[Cx] + BorderSpacing; Cx++; } } Py += MinRow[Cy] + BorderSpacing; } LayoutBounds.ZOff(Px-1, Py-1); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("\tLayoutBounds: %s\n", LayoutBounds.GetStr()); #endif } void LTableLayoutPrivate::InitBorderSpacing() { BorderSpacing = LTableLayout::CellSpacing; if (Ctrl->GetCss()) { LCss::Len bs = Ctrl->GetCss()->BorderSpacing(); if (bs.Type != LCss::LenInherit) BorderSpacing = bs.ToPx(Ctrl->X(), Ctrl->GetFont()); } } void LTableLayoutPrivate::Layout(const LRect Client, int Depth) { if (InLayout) { LAssert(!"In layout, no recursion should happen."); return; } InLayout = true; InitBorderSpacing(); #if DEBUG_LAYOUT int CtrlId = Ctrl->GetId(); // auto CtrlChildren = Ctrl->IterateViews(); DebugLayout = CtrlId == DEBUG_LAYOUT && Ctrl->IterateViews().Length() > 0; if (DebugLayout) { int asd=0; } #endif #if DEBUG_PROFILE int64 Start = LCurrentTime(); #endif LString s; s.Printf("Layout id %i: %i x %i", Ctrl->GetId(), Client.X(), Client.Y()); LAutoPtr Prof(/*Debug ? new LProfile(s) :*/ NULL); #if DEBUG_LAYOUT if (DebugLayout) Log().Print("%s\n", s.Get()); #endif if (Prof) Prof->Add("Horz"); LayoutHorizontal(Client, Depth); if (Prof) Prof->Add("Vert"); LayoutVertical(Client, Depth); if (Prof) Prof->Add("Post"); LayoutPost(Client, Depth); if (Prof) Prof->Add("Notify"); #if DEBUG_PROFILE Log().Print("LTableLayout::Layout(%i) = %i ms\n", Ctrl->GetId(), (int)(LCurrentTime()-Start)); #endif InLayout = false; Ctrl->SendNotify(LNotifyTableLayoutChanged); } LTableLayout::LTableLayout(int id) : ResObject(Res_Table) { d = new LTableLayoutPrivate(this); SetPourLargest(true); Name("LTableLayout"); SetId(id); } LTableLayout::~LTableLayout() { DeleteObj(d); } void LTableLayout::OnFocus(bool b) { if (b) { LViewI *v = GetNextTabStop(this, false); if (v) v->Focus(true); } } void LTableLayout::OnCreate() { LResources::StyleElement(this); AttachChildren(); } int LTableLayout::CellX() { return (int)d->Cols.Length(); } int LTableLayout::CellY() { return (int)d->Rows.Length(); } LLayoutCell *LTableLayout::CellAt(int x, int y) { return d->GetCellAt(x, y); } bool LTableLayout::SizeChanged() { LRect r = GetClient(); return r.X() != d->PrevSize.x || r.Y() != d->PrevSize.y; } void LTableLayout::OnPosChange() { LRect r = GetClient(); bool Up = SizeChanged() || d->LayoutDirty; // LgiTrace("%s:%i - Up=%i for Id=%i\n", _FL, Up, GetId()); if (Up) { d->PrevSize.x = r.X(); d->PrevSize.y = r.Y(); if (d->PrevSize.x > 0) { if (GetCss()) { LCssTools t(GetCss(), GetFont()); r = t.ApplyBorder(r); r = t.ApplyPadding(r); } d->LayoutDirty = false; d->Layout(r, 0); } } } LRect LTableLayout::GetUsedArea() { if (SizeChanged()) { OnPosChange(); } LRect r(0, 0, -1, -1); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; if (i) r.Union(&c->Pos); else r = c->Pos; } return r; } void LTableLayout::InvalidateLayout() { if (!d->LayoutDirty) { d->LayoutDirty = true; for (auto p = GetParent(); p; p = p->GetParent()) { LTableLayout *t = dynamic_cast(p); if (t) t->InvalidateLayout(); } if (IsAttached()) PostEvent(M_TABLE_LAYOUT); } if (!IsAttached()) Invalidate(); } LMessage::Result LTableLayout::OnEvent(LMessage *m) { switch (m->Msg()) { case M_TABLE_LAYOUT: { OnPosChange(); Invalidate(); return 0; } } return LLayout::OnEvent(m); } void LTableLayout::OnPaint(LSurface *pDC) { if (SizeChanged() || d->LayoutDirty) { if (GetId() == 20) Log().Print("20 : clearing layout dirty LGI_VIEW_HANDLE=%i\n", LGI_VIEW_HANDLE); #if LGI_VIEW_HANDLE if (!Handle()) #endif OnPosChange(); #if LGI_VIEW_HANDLE else if (PostEvent(M_TABLE_LAYOUT)) return; else LAssert(!"Post event failed."); #endif if (GetId() == 20) Log().Print("20 : painting\n"); } d->Dpi = GetWindow()->GetDpi(); LCssTools Tools(this); LRect Client = GetClient(); Client = Tools.PaintBorder(pDC, Client); Tools.PaintContent(pDC, Client); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; c->OnPaint(pDC); } #if 0 // DEBUG_DRAW_CELLS pDC->Colour(LColour(255, 0, 0)); pDC->Box(); #endif #if DEBUG_DRAW_CELLS #if defined(DEBUG_LAYOUT) if (GetId() == DEBUG_LAYOUT) #endif { pDC->LineStyle(LSurface::LineDot); for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; LRect r = c->Pos; pDC->Colour(c->Debug ? Rgb24(255, 222, 0) : Rgb24(192, 192, 222), 24); pDC->Box(&r); pDC->Line(r.x1, r.y1, r.x2, r.y2); pDC->Line(r.x2, r.y1, r.x1, r.y2); } pDC->LineStyle(LSurface::LineSolid); } #endif } bool LTableLayout::GetVariant(const char *Name, LVariant &Value, const char *Array) { return false; } bool ConvertNumbers(LArray &a, char *s) { for (auto &i: LString(s).SplitDelimit(",")) a.Add(i.Float()); return a.Length() > 0; } bool LTableLayout::SetVariant(const char *Name, LVariant &Value, const char *Array) { LDomProperty p = LStringToDomProp(Name); switch (p) { case TableLayoutCols: return ConvertNumbers(d->Cols, Value.Str()); case TableLayoutRows: return ConvertNumbers(d->Rows, Value.Str()); case ObjStyle: { const char *Defs = Value.Str(); if (Defs) GetCss(true)->Parse(Defs, LCss::ParseRelaxed); break; } case TableLayoutCell: { auto Coords = LString(Array).SplitDelimit(","); if (Coords.Length() != 2) return false; auto Cx = Coords[0].Int(); auto Cy = Coords[1].Int(); TableCell *c = new TableCell(this, (int)Cx, (int)Cy); if (!c) return false; d->Cells.Add(c); if (Value.Type == GV_VOID_PTR) { LDom **d = (LDom**)Value.Value.Ptr; if (d) *d = c; } break; } default: { LAssert(!"Unsupported property."); return false; } } return true; } void LTableLayout::OnChildrenChanged(LViewI *Wnd, bool Attaching) { InvalidateLayout(); if (Attaching) return; for (int i=0; iCells.Length(); i++) { TableCell *c = d->Cells[i]; for (int n=0; nChildren.Length(); n++) { if (c->Children[n].View == Wnd) { c->Children.DeleteAt(n); return; } } } } int LTableLayout::OnNotify(LViewI *c, LNotification n) { if (n.Type == LNotifyTableLayoutRefresh) { bool hasTableParent = false; for (LViewI *p = this; p; p = p->GetParent()) { auto tbl = dynamic_cast(p); if (tbl) { hasTableParent = true; tbl->d->LayoutDirty = true; tbl->Invalidate(); break; } } if (hasTableParent) { // One of the parent controls is a table layout, which when it receives this // notification will lay this control out too... so don't do it twice. // LgiTrace("%s:%i - Ignoring LNotifyTableLayoutRefresh because hasTableParent.\n", _FL); } SendNotify(LNotifyTableLayoutRefresh); return 0; } return LLayout::OnNotify(c, n); } int64 LTableLayout::Value() { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue()) return i; } } return -1; } void LTableLayout::Value(int64 v) { LArray Btns; if (d->CollectRadioButtons(Btns)) { for (int i=0; iValue(i == v); } } void LTableLayout::Empty(LRect *Range) { d->Empty(Range); } LLayoutCell *LTableLayout::GetCell(int cx, int cy, bool create, int colspan, int rowspan) { TableCell *c = d->GetCellAt(cx, cy); if (!c && create) { c = new TableCell(this, cx, cy); if (c) { d->LayoutDirty = true; if (colspan > 1) c->Cell.x2 += colspan - 1; if (rowspan > 1) c->Cell.y2 += rowspan - 1; d->Cells.Add(c); while (d->Cols.Length() <= c->Cell.x2) d->Cols.Add(1); while (d->Rows.Length() <= c->Cell.y2) d->Rows.Add(1); } } return c; } LStream <ableLayout::Log() { return PrintfLogger; } diff --git a/src/common/Widgets/Tree.cpp b/src/common/Widgets/Tree.cpp --- a/src/common/Widgets/Tree.cpp +++ b/src/common/Widgets/Tree.cpp @@ -1,2258 +1,2250 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/Tree.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Palette.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" #define TREE_BLOCK 16 #define DRAG_THRESHOLD 4 #define DRAG_SCROLL_EDGE 20 #define DRAG_SCROLL_X 8 #define DRAG_SCROLL_Y 1 #define TreeUpdateNow false #define TREELOCK LMutex::Auto Lck(d, _FL); #define ForAll(Items) for (auto c : Items) ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility class LTreePrivate : public LMutex { public: // Private data int LineFlags[4]; bool LayoutDirty; LPoint Limit; LPoint LastClick; LPoint DragStart; int DragData; LMemDC *IconCache; bool InPour; int64 DropSelectTime; int8 IconTextGap; int LastLayoutPx; LMouse *CurrentClick; LTreeItem *ScrollTo; // Visual style LTree::ThumbStyle Btns; bool JoiningLines; // Pointers into items... be careful to clear when deleting items... LTreeItem *LastHit; List Selection; LTreeItem *DropTarget; LTreePrivate() : LMutex("LTreePrivate") { CurrentClick = NULL; LastLayoutPx = -1; DropSelectTime = 0; InPour = false; LastHit = 0; DropTarget = 0; IconCache = 0; LayoutDirty = true; IconTextGap = 0; ScrollTo = NULL; Btns = LTree::TreeTriangle; JoiningLines = false; } ~LTreePrivate() { DeleteObj(IconCache); } }; class LTreeItemPrivate { LArray Ds; LArray ColPx; public: LTreeItem *Item; LRect Pos; LRect Thumb; LRect Text; LRect Icon; bool Open; bool Selected; bool Visible; bool Last; int Depth; LTreeItemPrivate(LTreeItem *it) { Item = it; Ds = NULL; Pos.ZOff(-1, -1); Open = false; Selected = false; Visible = false; Last = false; Depth = 0; Text.ZOff(-1, -1); Icon.ZOff(-1, -1); } ~LTreeItemPrivate() { Ds.DeleteObjects(); } LDisplayString *GetDs(int Col, int FixPx) { if (!Ds[Col]) { LFont *f = Item->GetTree() ? Item->GetTree()->GetFont() : LSysFont; Ds[Col] = new LDisplayString(f, Item->GetText(Col)); if (Ds[Col]) { ColPx[Col] = Ds[Col]->X(); if (FixPx > 0) { Ds[Col]->TruncateWithDots(FixPx); } } } return Ds[Col]; } void ClearDs(int Col = -1) { if (Col >= 0) { delete Ds[Col]; Ds[Col] = NULL; } else { Ds.DeleteObjects(); } } int GetColumnPx(int Col) { int BasePx = 0; GetDs(Col, 0); if (Col == 0) { BasePx = (Depth + 1) * TREE_BLOCK; } return ColPx[Col] + BasePx; } }; ////////////////////////////////////////////////////////////////////////////// LTreeNode::LTreeNode() { Parent = NULL; Tree = NULL; } LTreeNode::~LTreeNode() { } void LTreeNode::SetLayoutDirty() { Tree->d->LayoutDirty = true; } void LTreeNode::_Visible(bool v) { for (LTreeItem *i=GetChild(); i; i=i->GetNext()) { LAssert(i != this); i->OnVisible(v); i->_Visible(v); } } void LTreeNode::_ClearDs(int Col) { List::I it = Items.begin(); for (LTreeItem *c = *it; c; c = *++it) { c->_ClearDs(Col); } } LItemContainer *LTreeItem::GetContainer() { return Tree; } LTreeItem *LTreeNode::Insert(LTreeItem *Obj, ssize_t Idx) { LAssert(Obj != this); if (Obj && Obj->Tree) Obj->Remove(); LTreeItem *NewObj = Obj ? Obj : new LTreeItem; if (NewObj) { NewObj->Parent = Item(); NewObj->_SetTreePtr(Tree); Items.Delete(NewObj); Items.Insert(NewObj, Idx); if (Tree) { Tree->d->LayoutDirty = true; if (Pos() && Pos()->Y() > 0) Tree->_UpdateBelow(Pos()->y1); else Tree->Invalidate(); } } return NewObj; } void LTreeNode::Detach() { if (Parent) { LTreeItem *It = Item(); if (It) { LAssert(Parent->Items.HasItem(It)); Parent->Items.Delete(It); } Parent = 0; } if (Tree) { Tree->d->LayoutDirty = true; Tree->Invalidate(); } if (Item()) Item()->_SetTreePtr(0); } void LTreeNode::Remove() { int y = 0; if (Parent) { LTreeItem *i = Item(); if (i && i->IsRoot()) { LRect *p = Pos(); LTreeItem *Prev = GetPrev(); if (Prev) { y = Prev->d->Pos.y1; } else { y = p->y1; } } else { y = Parent->d->Pos.y1; } } LTree *t = Tree; if (Item()) Item()->_Remove(); if (t) { t->_UpdateBelow(y); } } bool LTreeNode::IsRoot() { return Parent == 0 || (LTreeNode*)Parent == (LTreeNode*)Tree; } size_t LTreeNode::Length() { return Items.Length(); } bool LTreeNode::HasItem(LTreeItem *obj, bool recurse) { if (!obj) return false; if (this == (LTreeNode*)obj) return true; for (auto i: Items) { if (i == obj) return true; if (recurse && i->HasItem(obj, recurse)) return true; } return false; } int LTreeNode::ForEach(std::function Fn) { int Count = 0; for (auto t : Items) { Fn(t); Count += t->ForEach(Fn); } return Count + 1; } ssize_t LTreeNode::IndexOf() { if (Parent) { return Parent->Items.IndexOf(Item()); } else if (Tree) { return Tree->Items.IndexOf(Item()); } return -1; } LTreeItem *LTreeNode::GetChild() { return Items.Length() ? Items[0] : NULL; } LTreeItem *LTreeNode::GetPrev() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index-1); } } return 0; } LTreeItem *LTreeNode::GetNext() { List *l = (Parent) ? &Parent->Items : (Tree) ? &Tree->Items : 0; if (l) { ssize_t Index = l->IndexOf(Item()); if (Index >= 0) { return l->ItemAt(Index+1); } } return 0; } ////////////////////////////////////////////////////////////////////////////// LTreeItem::LTreeItem() { d = new LTreeItemPrivate(this); - Str = 0; - Sys_Image = -1; } LTreeItem::~LTreeItem() { if (Tree) { if (Tree->d->DropTarget == this) - { Tree->d->DropTarget = 0; - } if (Tree->d->LastHit == this) - { Tree->d->LastHit = 0; - } if (Tree->IsCapturing()) Tree->Capture(false); } int y = 0; LTree *t = 0; if (Parent && (LTreeNode*)Parent != (LTreeNode*)Tree) { t = Tree; y = Parent->d->Pos.y1; } else if ((LTreeNode*)this != (LTreeNode*)Tree) { t = Tree; LTreeItem *p = GetPrev(); if (p) - { y = p->d->Pos.y1; - } else - { y = d->Pos.y1; - } } _Remove(); while (Items.Length()) { auto It = Items.begin(); delete *It; } DeleteObj(d); if (t) - { t->_UpdateBelow(y); - } } int LTreeItem::GetColumnSize(int Col) { int Px = d->GetColumnPx(Col); if (Expanded()) { ForAll(Items) { int ChildPx = c->GetColumnSize(Col); Px = MAX(ChildPx, Px); } } return Px; } LRect *LTreeItem::Pos() { return &d->Pos; } LPoint LTreeItem::_ScrollPos() { LPoint p; if (Tree) p = Tree->_ScrollPos(); return p; } LRect *LTreeItem::_GetRect(LTreeItemRect Which) { switch (Which) { case TreeItemPos: return &d->Pos; case TreeItemThumb: return &d->Thumb; case TreeItemText: return &d->Text; case TreeItemIcon: return &d->Icon; } return 0; } bool LTreeItem::IsDropTarget() { LTree *t = GetTree(); if (t && t->d && t->d->DropTarget == this) return true; return false; } LRect *LTreeItem::GetPos(int Col) { if (!d->Pos.Valid() && Tree) Tree->_Pour(); static LRect r; r = d->Pos; if (Col >= 0) { LItemColumn *Column = 0; int Cx = Tree->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Tree->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } return &r; } void LTreeItem::_RePour() { if (Tree) Tree->_Pour(); } void LTreeItem::ScrollTo() { if (!Tree) return; if (Tree->VScroll) { LRect c = Tree->GetClient(); LRect p = d->Pos; int y = d->Pos.Y() ? d->Pos.Y() : 16; p.Offset(0, (int) (-Tree->VScroll->Value() * y)); if (p.y1 < c.y1) { int Lines = (c.y1 - p.y1 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() - Lines); } else if (p.y2 > c.y2) { int Lines = (p.y2 - c.y2 + y - 1) / y; Tree->VScroll->Value(Tree->VScroll->Value() + Lines); } } else { Tree->d->ScrollTo = this; if (Tree->IsAttached()) Tree->PostEvent(M_SCROLL_TO); } } void LTreeItem::_SetTreePtr(LTree *t) { if (Tree && !t) { // Clearing tree pointer, must remove all references to this item that // the tree might still have. if (d->Selected) { Tree->d->Selection.Delete(this); d->Selected = false; } if (Tree->d->LastHit == this) { Tree->d->LastHit = 0; } if (Tree->d->DropTarget == this) { Tree->d->DropTarget = 0; } } Tree = t; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) { i->_SetTreePtr(t); } } void LTreeItem::_Remove() { if ((LTreeNode*)this != (LTreeNode*)Tree) { if (Parent) { LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); } else if (Tree) { LAssert(Tree->Items.HasItem(this)); Tree->Items.Delete(this); } if (Tree) { LAssert(Tree->d != NULL); Tree->d->LayoutDirty = true; if (Tree->IsCapturing()) Tree->Capture(false); } } Parent = 0; _SetTreePtr(0); } void LTreeItem::_PourText(LPoint &Size) { LFont *f = Tree ? Tree->GetFont() : LSysFont; auto *Txt = GetText(); #if defined(_WIN64) && defined(_DEBUG) if ((void*)Txt == (void*)0xfeeefeeefeeefeee || (void*)Txt == (void*)0xcdcdcdcdcdcdcdcd) { LAssert(!"Yeah nah..."); } #endif LDisplayString ds(f, Txt); Size.x = ds.X() + 4; Size.y = 0; } void LTreeItem::_PaintText(LItem::ItemPaintCtx &Ctx) { const char *Text = GetText(); if (Text) { LDisplayString *Ds = d->GetDs(0, d->Text.X()); LFont *f = Tree ? Tree->GetFont() : LSysFont; int Tab = f->TabSize(); f->TabSize(0); f->Transparent(false); f->Colour(Ctx.Fore, Ctx.TxtBack); if (Ds) { Ds->Draw(Ctx.pDC, d->Text.x1 + 2, d->Text.y1 + 1, &d->Text); if (Ctx.x2 > d->Text.x2) { LRect r = Ctx; r.x1 = d->Text.x2 + 1; Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&r); } } f->TabSize(Tab); } else { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&Ctx); } } void LTreeItem::_Pour(LPoint *Limit, int ColumnPx, int Depth, bool Visible) { auto css = GetCss(false); auto display = css ? css->Display() != LCss::DispNone : true; d->Visible = display && Visible; d->Depth = Depth; if (d->Visible) { LPoint TextSize; _PourText(TextSize); LImageList *ImgLst = Tree->GetImageList(); // int IconX = (ImgLst && GetImage() >= 0) ? ImgLst->TileX() + Tree->d->IconTextGap : 0; int IconY = (ImgLst && GetImage() >= 0) ? ImgLst->TileY() : 0; int Height = MAX(TextSize.y, IconY); if (!Height) Height = 16; LDisplayString *Ds = d->GetDs(0, 0); d->Pos.ZOff(ColumnPx - 1, (Ds ? MAX(Height, Ds->Y()) : Height) - 1); d->Pos.Offset(0, Limit->y); if (!d->Pos.Valid()) { printf("Invalid pos: %s, ColumnPx=%i\n", d->Pos.GetStr(), ColumnPx); } Limit->x = MAX(Limit->x, d->Pos.x2 + 1); Limit->y = MAX(Limit->y, d->Pos.y2 + 1); } else { d->Pos.ZOff(-1, -1); } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(Limit, ColumnPx, Depth+1, d->Open && d->Visible); } } void LTreeItem::_ClearDs(int Col) { d->ClearDs(Col); LTreeNode::_ClearDs(Col); } const char *LTreeItem::GetText(int i) { return Str[i]; } bool LTreeItem::SetText(const char *s, int i) { - if (!Str[i].Reset(NewStr(s))) - return false; + LAutoPtr Lck; + + if (Tree) + Lck.Reset(new LMutex::Auto(Tree->d, -1, _FL)); + + Str[i] = s; if (Tree) Update(); return true; } int LTreeItem::GetImage(int Flags) { return Sys_Image; } void LTreeItem::SetImage(int i) { Sys_Image = i; } void LTreeItem::Update() { if (Tree) { LRect p = d->Pos; p.x2 = 10000; d->ClearDs(); Tree->_Update(&p, TreeUpdateNow); } } bool LTreeItem::Select() { return d->Selected; } void LTreeItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; if (b) { LTreeItem *p = this; while ((p = p->GetParent())) { p->Expanded(true); } } Update(); if (b && Tree) { Tree->_OnSelect(this); Tree->OnItemSelect(this); } } } bool LTreeItem::Expanded() { return d->Open; } void LTreeItem::Expanded(bool b) { if (d->Open != b) { d->Open = b; if (Items.Length() > 0) { if (Tree) { Tree->d->LayoutDirty = true; Tree->_UpdateBelow(d->Pos.y1); } OnExpand(b); } } } void LTreeItem::OnExpand(bool b) { _Visible(b); } LTreeItem *LTreeItem::_HitTest(int x, int y, bool Debug) { LTreeItem *Status = 0; if (d->Pos.Overlap(x, y) && x > (d->Depth*TREE_BLOCK)) { Status = this; } if (d->Open) { List::I it = Items.begin(); for (LTreeItem *i=*it; i && !Status; i=*++it) { Status = i->_HitTest(x, y, Debug); } } return Status; } void LTreeItem::_MouseClick(LMouse &m) { if (m.Down()) { if ((Items.Length() > 0 && d->Thumb.Overlap(m.x, m.y)) || m.Double()) { Expanded(!Expanded()); } LRect rText = d->Text; if (Tree && Tree->Columns.Length() > 0) rText.x2 = Tree->X(); if (rText.Overlap(m.x, m.y) || d->Icon.Overlap(m.x, m.y)) { Select(true); if (Tree) Tree->OnItemClick(this, m); } } } void LTreeItem::OnPaint(ItemPaintCtx &Ctx) { LAssert(Tree != NULL); if (!d->Visible) return; // background up to text LSurface *&pDC = Ctx.pDC; pDC->Colour(Ctx.Back); pDC->Rectangle(0, d->Pos.y1, (d->Depth*TREE_BLOCK)+TREE_BLOCK, d->Pos.y2); // draw trunk LRect Pos = d->Pos; Pos.x2 = Pos.x1 + Ctx.ColPx[0] - 1; int x = 0; LColour Ws(L_WORKSPACE); LColour Lines = Ws.Invert().Mix(Ws); pDC->Colour(Lines); if (Tree->d->JoiningLines) { for (int i=0; iDepth; i++) { if (Tree->d->LineFlags[0] & (1 << i)) pDC->Line(x + 8, Pos.y1, x + 8, Pos.y2); x += TREE_BLOCK; } } else { x += TREE_BLOCK * d->Depth; } // draw node int cy = Pos.y1 + (Pos.Y() >> 1); if (Items.Length() > 0) { d->Thumb.ZOff(8, 8); d->Thumb.Offset(x + 4, cy - 4); switch (Tree->d->Btns) { case LTree::TreePlus: { // plus/minus symbol pDC->Colour(L_LOW); pDC->Box(&d->Thumb); pDC->Colour(L_WHITE); pDC->Rectangle(d->Thumb.x1+1, d->Thumb.y1+1, d->Thumb.x2-1, d->Thumb.y2-1); pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+2, d->Thumb.y1+4, d->Thumb.x1+6, d->Thumb.y1+4); if (!d->Open) { // not open, so draw the cross bar making the '-' into a '+' pDC->Colour(L_SHADOW); pDC->Line( d->Thumb.x1+4, d->Thumb.y1+2, d->Thumb.x1+4, d->Thumb.y1+6); } break; } case LTree::TreeTriangle: { // Triangle style expander pDC->Colour(Lines); int Off = 2; if (d->Open) { for (int y=0; yThumb.Y(); y++) { int x1 = d->Thumb.x1 + y; int x2 = d->Thumb.x2 - y; if (x2 < x1) break; pDC->HLine(x1, x2, d->Thumb.y1 + y + Off); } } else { for (int x=0; xThumb.X(); x++) { int y1 = d->Thumb.y1 + x; int y2 = d->Thumb.y2 - x; if (y2 < y1) break; pDC->VLine(d->Thumb.x1 + x + Off, y1, y2); } } break; } } pDC->Colour(Lines); if (Tree->d->JoiningLines) { if (Parent || IndexOf() > 0) // draw line to item above pDC->Line(x + 8, Pos.y1, x + 8, d->Thumb.y1-1); // draw line to leaf beside pDC->Line(d->Thumb.x2+1, cy, x + (TREE_BLOCK-1), cy); if (!d->Last) // draw line to item below pDC->Line(x + 8, d->Thumb.y2+1, x + 8, Pos.y2); } } else if (Tree->d->JoiningLines) { // leaf node pDC->Colour(L_MED); if (d->Last) pDC->Rectangle(x + 8, Pos.y1, x + 8, cy); else pDC->Rectangle(x + 8, Pos.y1, x + 8, Pos.y2); pDC->Rectangle(x + 8, cy, x + (TREE_BLOCK-1), cy); } x += TREE_BLOCK; // draw icon int Image = GetImage(Select()); LImageList *Lst = Tree->GetImageList(); if (Image >= 0 && Lst) { d->Icon.ZOff(Lst->TileX() + Tree->d->IconTextGap - 1, Pos.Y() - 1); d->Icon.Offset(x, Pos.y1); pDC->Colour(Ctx.Back); if (Tree->d->IconCache) { // no flicker LRect From; From.ZOff(Lst->TileX()-1, Tree->d->IconCache->Y()-1); From.Offset(Lst->TileX()*Image, 0); pDC->Blt(d->Icon.x1, d->Icon.y1, Tree->d->IconCache, &From); pDC->Rectangle(d->Icon.x1 + Lst->TileX(), d->Icon.y1, d->Icon.x2, d->Icon.y2); } else { // flickers... int Px = d->Icon.y1 + ((Lst->TileY()-Pos.Y()) >> 1); pDC->Rectangle(&d->Icon); Tree->GetImageList()->Draw(pDC, d->Icon.x1, Px, Image, Ctx.Back); } x += d->Icon.X(); } LColour SelFore(Tree->Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Tree->Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); bool IsSelected = (Tree->d->DropTarget == this) || (Tree->d->DropTarget == NULL && Select()); LColour Fore = Ctx.Fore; LColour TxtBack = Ctx.TxtBack; auto Css = GetCss(); LCss::ColorDef f, b; if (Css) { f = Css->Color(); b = Css->BackgroundColor(); } // text: first column Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : (IsSelected ? SelFore : Fore); Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : (IsSelected ? SelBack : Ctx.Back); auto ColourDiff = abs(Ctx.Fore.GetGray() - Ctx.TxtBack.GetGray()); if (ColourDiff < 32) // Check if the colours are too similar and then disambiguate... { // LgiTrace("%s %s are too similar %i\n", Ctx.Fore.GetStr(), Ctx.TxtBack.GetStr(), (int)ColourDiff); Ctx.TxtBack = Ctx.TxtBack.Mix(L_WORKSPACE); } LPoint TextSize; _PourText(TextSize); d->Text.ZOff(TextSize.x-1, Pos.Y()-1); d->Text.Offset(x, Pos.y1); (LRect&)Ctx = d->Text; Ctx.x2 = Ctx.ColPx[0] - 1; _PaintText(Ctx); x = Pos.x2 + 1; // text: other columns Ctx.Fore = f.Type == LCss::ColorRgb ? (LColour)f : Fore; Ctx.TxtBack = b.Type == LCss::ColorRgb ? (LColour)b : Ctx.Back; for (int i=1; iColumns[i]); x = Ctx.x2 + 1; } Ctx.Fore = Fore; Ctx.TxtBack = TxtBack; // background after text pDC->Colour(Ctx.Back); pDC->Rectangle(x, Pos.y1, MAX(Tree->X(), Tree->d->Limit.x), Pos.y2); // children if (d->Open) { if (!d->Last) Tree->d->LineFlags[0] |= 1 << d->Depth; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->OnPaint(Ctx); Tree->d->LineFlags[0] &= ~(1 << d->Depth); } } void LTreeItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LDisplayString *ds = d->GetDs(i, Ctx.ColPx[i]); if (ds) { LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } } ////////////////////////////////////////////////////////////////////////////// LTree::LTree(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_TreeView) { d = new LTreePrivate; SetId(id); LRect e(x, y, x+cx, y+cy); SetPos(e); if (name) Name(name); else Name("LGI.LTree"); Sunken(true); Tree = this; Lines = true; Buttons = true; LinesAtRoot = true; EditLabels = false; ColumnHeaders = false; rItems.ZOff(-1, -1); #if WINNATIVE SetStyle(GetStyle() | WS_CHILD | WS_VISIBLE | WS_TABSTOP); #endif SetTabStop(true); LResources::StyleElement(this); } LTree::~LTree() { Empty(); DeleteObj(d); } bool LTree::Lock(const char *file, int line, int TimeOut) { if (TimeOut > 0) return d->LockWithTimeout(TimeOut, file, line); return d->Lock(file, line); } void LTree::Unlock() { return d->Unlock(); } // Internal tree methods List *LTree::GetSelLst() { return &d->Selection; } void LTree::_Update(LRect *r, bool Now) { TREELOCK if (r) { LRect u = *r; LPoint s = _ScrollPos(); LRect c = GetClient(); u.Offset(c.x1-s.x, c.y1-s.y); Invalidate(&u, Now && !d->InPour); } else { Invalidate((LRect*)0, Now && !d->InPour); } } void LTree::_UpdateBelow(int y, bool Now) { TREELOCK LPoint s = _ScrollPos(); LRect c = GetClient(); LRect u(c.x1, y - s.y + c.y1, X()-1, Y()-1); Invalidate(&u, Now); } void LTree::ClearDs(int Col) { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { TREELOCK LPoint Status; Status.x = (HScroll) ? (int)HScroll->Value() : 0; Status.y = (VScroll) ? (int)VScroll->Value() * TREE_BLOCK : 0; return Status; } void LTree::_UpdateScrollBars() { static bool Processing = false; if (!Processing) { Processing = true; { TREELOCK LPoint Old = _ScrollPos(); LRect Client = GetClient(); bool x = d->Limit.x > Client.X(); bool y = d->Limit.y > Client.Y(); SetScrollBars(x, y); Client = GetClient(); // x scroll... in pixels if (HScroll) { HScroll->SetRange(d->Limit.x); HScroll->SetPage(Client.X()); int Max = d->Limit.x - Client.X(); if (HScroll->Value() > Max) { HScroll->Value(Max+1); } } // y scroll... in items if (VScroll) { int All = (d->Limit.y + TREE_BLOCK - 1) / TREE_BLOCK; int Visible = Client.Y() / TREE_BLOCK; VScroll->SetRange(All); VScroll->SetPage(Visible); /* Why is this commented out? -fret Dec2018 int Max = All - Visible + 1; if (VScroll->Value() > Max) VScroll->Value(Max); */ } LPoint New = _ScrollPos(); if (Old.x != New.x || Old.y != New.y) { Invalidate(); } } Processing = false; } } void LTree::_OnSelect(LTreeItem *Item) { TREELOCK if ( !MultiSelect() || !d->CurrentClick || ( d->CurrentClick && !d->CurrentClick->Ctrl() ) ) { for (auto i: d->Selection) { if (i != Item) i->Select(false); } d->Selection.Empty(); } else { d->Selection.Delete(Item); } d->Selection.Insert(Item); } void LTree::_Pour() { TREELOCK d->InPour = true; d->Limit.x = rItems.x1; d->Limit.y = rItems.y1; int ColumnPx = 0; if (Columns.Length()) { for (int i=0; iWidth(); } } else { ColumnPx = d->LastLayoutPx = GetClient().X(); if (ColumnPx < 16) ColumnPx = 16; } LTreeItem *n; List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=n) { n = *++it; i->d->Last = n == 0; i->_Pour(&d->Limit, ColumnPx, 0, true); } _UpdateScrollBars(); d->LayoutDirty = false; d->InPour = false; } // External methods and events void LTree::OnItemSelect(LTreeItem *Item) { if (!Item) return; TREELOCK Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { TREELOCK if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { TREELOCK LTreeItem *Ret = NULL; if (i) { if (Down) { LTreeItem *n = i->GetChild(); if (!n || !n->d->Visible) { for (n = i; n; ) { LTreeItem *p = n->GetParent(); if (p) { ssize_t Index = n->IndexOf(); if (Index < (ssize_t)p->Items.Length()-1) { n = n->GetNext(); break; } else { n = p; } } else { n = n->GetNext(); break; } } } Ret = n; } else { LTreeItem *p = i->GetParent() ? i->GetParent() : 0; ssize_t Index = i->IndexOf(); if (p) { LTreeItem *n = p; if (Index > 0) { n = i->GetPrev(); while ( n->GetChild() && n->GetChild()->d->Visible) { n = n->Items.ItemAt(n->Items.Length()-1); } } Ret = n; } else if (Index > 0) { p = i->GetTree()->ItemAt(Index - 1); while (p->GetChild() && p->GetChild()->d->Visible) { if (p->Items.Length()) { p = p->Items.ItemAt(p->Items.Length()-1); } else break; } Ret = p; } } } return Ret; } bool LTree::OnKey(LKey &k) { if (!Lock(_FL)) return false; bool Status = false; LTreeItem *i = d->Selection[0]; if (!i) { i = Items[0]; if (i) i->Select(); } if (k.Down()) { switch (k.vkey) { case LK_PAGEUP: case LK_PAGEDOWN: { if (i && i->d->Pos.Y() > 0) { int Page = GetClient().Y() / i->d->Pos.Y(); for (int j=0; jSelect(true); i->ScrollTo(); } } Status = true; break; } case LK_HOME: { LTreeItem *i; if ((i = Items[0])) { i->Select(true); i->ScrollTo(); } Status = true; break; } case LK_END: { LTreeItem *n = i, *p = 0; while ((n = GetAdjacent(n, true))) { p = n; } if (p) { p->Select(true); p->ScrollTo(); } Status = true; break; } case LK_LEFT: { if (i) { if (i->Items.Length() && i->Expanded()) { i->Expanded(false); break; } else { LTreeItem *p = i->GetParent(); if (p) { p->Select(true); p->Expanded(false); _Pour(); break; } } } // fall thru } case LK_UP: { LTreeItem *n = GetAdjacent(i, false); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_RIGHT: { if (i) { i->Expanded(true); if (d->LayoutDirty) { _Pour(); break; } } // fall thru } case LK_DOWN: { LTreeItem *n = GetAdjacent(i, true); if (n) { n->Select(true); n->ScrollTo(); } Status = true; break; } case LK_DELETE: { if (k.Down()) { Unlock(); // before potentially being deleted...? SendNotify(LNotification(k)); // This might delete the item... so just return here. return true; } break; } #ifdef VK_APPS case VK_APPS: { LTreeItem *s = Selection(); if (s) { LRect *r = &s->d->Text; if (r) { LMouse m; m.x = r->x1 + (r->X() >> 1); m.y = r->y1 + (r->Y() >> 1); m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); s->OnMouseClick(m); } } break; } #endif default: { switch (k.c16) { case 'F': case 'f': { if (k.Ctrl()) SendNotify(LNotifyContainerFind); break; } } break; } } } if (i && i != (LTreeItem*)this) { i->OnKey(k); } Unlock(); return Status; } LTreeItem *LTree::ItemAtPoint(int x, int y, bool Debug) { TREELOCK LPoint s = _ScrollPos(); List::I it = Items.begin(); LTreeItem *Hit = NULL; for (LTreeItem *i = *it; i; i=*++it) { Hit = i->_HitTest(s.x + x, s.y + y, Debug); if (Hit) break; } return Hit; } bool LTree::OnMouseWheel(double Lines) { TREELOCK if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { TREELOCK d->CurrentClick = &m; if (m.Down()) { DragMode = DRAG_NONE; if (ColumnHeaders && ColumnHeader.Overlap(m.x, m.y)) { d->DragStart.x = m.x; d->DragStart.y = m.y; // Clicked on a column heading LItemColumn *Resize; LItemColumn *Over = NULL; HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { Resize->Width(Resize->GetContentSize() + DEFAULT_COLUMN_SPACING); Invalidate(); } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } /* else { DragMode = CLICK_COLUMN; d->DragData = Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } */ } else if (rItems.Overlap(m.x, m.y)) { Focus(true); Capture(true); d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y, true); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } else { SendNotify(LNotification(m, LNotifyContainerClick)); } } } else if (IsCapturing()) { Capture(false); if (rItems.Overlap(m.x, m.y)) { d->LastClick.x = m.x; d->LastClick.y = m.y; d->LastHit = ItemAtPoint(m.x, m.y); if (d->LastHit) { LPoint c = _ScrollPos(); m.x += c.x; m.y += c.y; d->LastHit->_MouseClick(m); } } } d->CurrentClick = NULL; } void LTree::OnMouseMove(LMouse &m) { if (!IsCapturing()) return; TREELOCK switch (DragMode) { /* case DRAG_COLUMN: { if (DragCol) { LPoint p; PointToScreen(p); LRect r = DragCol->GetPos(); r.Offset(-p.x, -p.y); // to view co-ord r.Offset(m.x - DragCol->GetOffset() - r.x1, 0); if (r.x1 < 0) r.Offset(-r.x1, 0); if (r.x2 > X()-1) r.Offset((X()-1)-r.x2, 0); r.Offset(p.x, p.y); // back to screen co-ord DragCol->SetPos(r, true); r = DragCol->GetPos(); } break; } */ case RESIZE_COLUMN: { LItemColumn *c = Columns[d->DragData]; if (c) { // int OldWidth = c->Width(); int NewWidth = m.x - c->GetPos().x1; c->Width(MAX(NewWidth, 4)); _ClearDs(d->DragData); Invalidate(); } break; } default: { if (rItems.Overlap(m.x, m.y)) { if (abs(d->LastClick.x - m.x) > DRAG_THRESHOLD || abs(d->LastClick.y - m.y) > DRAG_THRESHOLD) { OnItemBeginDrag(d->LastHit, m); Capture(false); } } break; } } } void LTree::OnPosChange() { TREELOCK if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { TREELOCK LCssTools Tools(this); #if 0 // coverage testing... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif rItems = GetClient(); LFont *f = GetFont(); if (ShowColumnHeader()) { ColumnHeader.ZOff(rItems.X()-1, f->GetHeight() + 4); PaintColumnHeadings(pDC); rItems.y1 = ColumnHeader.y2 + 1; } else { ColumnHeader.ZOff(-1, -1); } d->IconTextGap = GetFont()->GetHeight() / 6; auto cText = LColour(L_TEXT); auto cWs = LColour(L_WORKSPACE); LColour Fore = Tools.GetFore(&cText); LColour Background = Tools.GetBack(&cWs, 0); // icon cache if (GetImageList() && !d->IconCache) { int CacheHeight = MAX(LSysFont->GetHeight(), GetImageList()->Y()); d->IconCache = new LMemDC; if (d->IconCache && d->IconCache->Create(GetImageList()->X(), CacheHeight, GdcD->GetColourSpace())) { if (d->IconCache->GetColourSpace() == CsIndex8) { d->IconCache->Palette(new LPalette(GdcD->GetGlobalColour()->GetPalette())); } d->IconCache->Colour(Background); d->IconCache->Rectangle(); d->IconCache->Op(GDC_ALPHA); GetImageList()->Lock(); int DrawY = (CacheHeight - GetImageList()->TileY()) >> 1; LAssert(DrawY >= 0); for (int i=0; iGetItems(); i++) { GetImageList()->Draw(d->IconCache, i * GetImageList()->TileX(), DrawY, i, Background); } GetImageList()->Unlock(); d->IconCache->Unlock(); } } // scroll LPoint s = _ScrollPos(); int Ox, Oy; pDC->GetOrigin(Ox, Oy); pDC->SetOrigin(Ox + s.x, Oy + s.y); // selection colour LArray ColPx; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; if (Columns.Length() > 0) { Ctx.Columns = (int)Columns.Length(); for (int i=0; iWidth(); } else { Ctx.Columns = 1; ColPx[0] = rItems.X(); } Ctx.ColPx = &ColPx[0]; Ctx.Fore = Fore; Ctx.Back = Background; Ctx.TxtBack = Background; LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : L_NON_FOCUS_SEL_BACK); // layout items if (d->LayoutDirty) { _Pour(); } // paint items ZeroObj(d->LineFlags); List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) i->OnPaint(Ctx); pDC->SetOrigin(Ox, Oy); if (d->Limit.y-s.y < rItems.Y()) { // paint after items pDC->Colour(Background); pDC->Rectangle(rItems.x1, d->Limit.y - s.y, rItems.x2, rItems.y2); } } int LTree::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_HSCROLL: case IDC_VSCROLL: { TREELOCK if (Flags == LNotifyScrollBarCreate) { _UpdateScrollBars(); if (VScroll) { if (HasItem(d->ScrollTo)) d->ScrollTo->ScrollTo(); d->ScrollTo = NULL; } } Invalidate(); break; } } return LLayout::OnNotify(Ctrl, n); } LMessage::Result LTree::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case M_SCROLL_TO: { LTreeItem *Item = (LTreeItem*)Msg->A(); if (!HasItem(Item)) break; if (VScroll) Item->ScrollTo(); break; } } return LItemContainer::OnEvent(Msg); } LTreeItem *LTree::Insert(LTreeItem *Obj, ssize_t Pos) { TREELOCK LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { TREELOCK if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { TREELOCK List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { TREELOCK LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; TREELOCK if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { TREELOCK if (d->DropTarget) { int64 p = LCurrentTime() - d->DropSelectTime; if (p >= 1000) { SetPulse(); if (!d->DropTarget->Expanded() && d->DropTarget->GetChild()) { d->DropTarget->Expanded(true); } } } if (InsideDragOp()) { LMouse m; if (GetMouse(m)) { if (!m.Left() && !m.Right() && !m.Middle()) { // Be robust against missing drag exit events (Mac specific?) InsideDragOp(false); } else { LRect c = GetClient(); if (VScroll) { if (m.y < DRAG_SCROLL_EDGE) { // Scroll up... VScroll->Value(VScroll->Value() - DRAG_SCROLL_Y); } else if (m.y > c.Y() - DRAG_SCROLL_EDGE) { // Scroll down... VScroll->Value(VScroll->Value() + DRAG_SCROLL_Y); } } if (HScroll) { if (m.x < DRAG_SCROLL_EDGE) { // Scroll left... HScroll->Value(HScroll->Value() - DRAG_SCROLL_X); } else if (m.x > c.X() - DRAG_SCROLL_EDGE) { // Scroll right... HScroll->Value(HScroll->Value() + DRAG_SCROLL_X); } } } } } } int LTree::GetContentSize(int ColumnIdx) { TREELOCK int MaxPx = 0; List::I it = Items.begin(); for (LTreeItem *i = *it; i; i=*++it) { int ItemPx = i->GetColumnSize(ColumnIdx); MaxPx = MAX(ItemPx, MaxPx); } return MaxPx; } LCursor LTree::GetCursor(int x, int y) { TREELOCK LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { TREELOCK InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { TREELOCK InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { TREELOCK if (Item != d->DropTarget) { bool Update = (d->DropTarget != 0) ^ (Item != 0); LTreeItem *Old = d->DropTarget; d->DropTarget = Item; if (Old) { Old->Update(); } if (d->DropTarget) { d->DropTarget->Update(); d->DropSelectTime = LCurrentTime(); } if (Update) { OnFocus(true); } } } bool LTree::Select(LTreeItem *Obj) { TREELOCK bool Status = false; if (Obj && IsAttached()) { Obj->Select(true); Status = true; } else if (d->Selection.Length()) { d->Selection.Empty(); OnItemSelect(0); Status = true; } return Status; } LTreeItem *LTree::Selection() { TREELOCK return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { TREELOCK return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { TREELOCK // errors during deletion of the control can cause // this to be called after the destructor if (d) { List::I it = d->Selection.begin(); for (LTreeItem *i=*it; i; i=*++it) i->Update(); } } static void LTreeItemUpdateAll(LTreeNode *n) { for (LTreeItem *i=n->GetChild(); i; i=i->GetNext()) { i->Update(); LTreeItemUpdateAll(i); } } void LTree::UpdateAllItems() { TREELOCK d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { TREELOCK d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/src/common/Widgets/ZoomView.cpp b/src/common/Widgets/ZoomView.cpp --- a/src/common/Widgets/ZoomView.cpp +++ b/src/common/Widgets/ZoomView.cpp @@ -1,1851 +1,1849 @@ /* Notes: The scroll bar position is always in document co-ordinates. Not zoomed co-ords. */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ZoomView.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/ThreadEvent.h" #include "lgi/common/Palette.h" #define MAX_FACTOR 32 #define DEBUG_TILE_BOUNDARIES 0 #define RIGHT_BOTTOM_WHITESPACE 2 enum Messages { M_RENDER_FINISHED = M_USER + 800, }; static float ZoomToScale(int Zoom) { if (Zoom < 0) return 1.0f / (1 - Zoom); else if (Zoom > 1) return (float) (Zoom + 1); return 1.0f; } struct ZoomTile : public LMemDC { bool Dirty; ZoomTile(int Size, LColourSpace Cs) : LMemDC(Size, Size, Cs) { Dirty = true; } }; typedef ZoomTile *SuperTilePtr; class LZoomViewPriv { int Zoom; LAutoPtr TileCache; public: /// If this is true, then we own the pDC object. bool OwnDC = false; /// This is set if there is recursion in the scroll bar update.. bool ScrollBarsDirty = false; /// The image surface we are displaying LSurface *pDC; /// The parent view that owns us LZoomView *View; /// The callback object... LZoomViewCallback *Callback; /// This is the zoom factor. However: /// -3 = 1/4 (scale down) /// -2 = 1/3 /// -1 = 1/2 /// 0 = exact pixels /// 1 = 2x /// 2 = 3x /// 3 = 4x (scale up) float Scale() { return ZoomToScale(Zoom); } int Factor() { return Zoom < 0 ? 1 - Zoom : Zoom + 1; } /// Number of tiles allocated int TotalTiles; /// Width and height of the tile... int TileSize; /// Size of image in tiles LPoint Tiles; /// The array of tiles. Unused entries will be NULL. ZoomTile ***Tile; // Type of sampling to use LZoomView::SampleMode SampleMode; // Default zooming behaviour LZoomView::DefaultZoomMode DefaultZoom; // Threading stuff enum WorkerMode { ExitNow, Waiting, Active, Stopping, Stopped, } Mode; LZoomViewPriv(LZoomView *view, LZoomViewCallback *callback) { View = view; Callback = callback; Mode = Waiting; Zoom = 0; TileSize = 128; Tiles.x = Tiles.y = 0; Tile = NULL; OwnDC = false; pDC = NULL; SampleMode = LZoomView::SampleNearest; DefaultZoom = LZoomView::ZoomFitBothAxis; } ~LZoomViewPriv() { if (OwnDC) DeleteObj(pDC); EmptyTiles(); } LRect DocToScreen(LRect s) { int f = Factor(); if (Zoom < 0) { // Scaling down (zoomed out) s.x1 /= f; s.y1 /= f; s.x2 = ((s.x2 + 1) / f) - 1; s.y2 = ((s.y2 + 1) / f) - 1; } else if (Zoom > 0) { // Scaling up (zooming in) s.x1 *= f; s.y1 *= f; s.x2 = ((s.x2 + 1) * f) - 1; s.y2 = ((s.y2 + 1) * f) - 1; } return s; } LRect ScreenToDoc(LRect s) { int f = Factor(); if (Zoom > 0) { // Scaling down (zoomed out) s.x1 /= f; s.y1 /= f; s.x2 = (s.x2 + f - 1) / f; s.y2 = (s.y2 + f - 1) / f; } else if (Zoom < 0) { // Scaling up (zooming in) s.x1 *= f; s.y1 *= f; s.x2 *= f; s.y2 *= f; } return s; } LPoint ScreenToDoc(LPoint p) { int f = Factor(); if (GetZoom() > 0) { // Image is scaled up, so divide to get doc coords p.x = (p.x + f - 1) / f; p.y = (p.y + f - 1) / f; } else if (GetZoom() < 0) { // Image is scaled down p.x *= f; p.y *= f; } return p; } LPoint DocToScreen(LPoint p) { int f = Factor(); if (GetZoom() < 0) { // Scaled down p.x = (p.x + f - 1) / f; p.y = (p.y + f - 1) / f; } else if (GetZoom() > 0) { // Scaled up p.x *= f; p.y *= f; } return p; } int GetZoom() { return Zoom; } void SetZoom(int z) { if (Zoom == z) return; Zoom = z; EmptyTiles(); View->UpdateScrollBars(); View->Invalidate(); } void SetDefaultZoom() { if (!pDC) return; LRect c = View->GetClient(); int z; bool Loop = true; for (z=-20; Loop && z<=0; z++) { float s = ZoomToScale(z); int x = (int) (s * pDC->X()); int y = (int) (s * pDC->Y()); switch (DefaultZoom) { case LZoomView::ZoomFitBothAxis: { if (x >= c.X() || y >= c.Y()) { z--; Loop = false; } break; } case LZoomView::ZoomFitX: { if (x >= c.X()) { z--; Loop = false; } break; } case LZoomView::ZoomFitY: { if (y >= c.Y()) { z--; Loop = false; } break; } } } SetZoom(MIN(z, 0)); ResetTiles(); } void EmptyTiles() { if (!Tile) return; for (int x=0; xBounds()); // Pick a suitable tile size TileSize = 128; if (Zoom >= 2) { // When scaling up the tile size needs to be // an even multiple of the factor. Otherwise // we have part of a scaled pixel in one tile // and part in another. Which is messy to handle. while (TileSize % Factor()) TileSize++; } Tiles.x = (full.X() + TileSize - 1) / TileSize; Tiles.y = (full.Y() + TileSize - 1) / TileSize; Tile = new SuperTilePtr*[Tiles.x]; for (int x=0; x void ScaleDown24(OutPx *out, InPx *in, int len, int Factor) { if (SampleMode == LZoomView::SampleNearest) { InPx *end = in + len; while (in < end) { out->r = in->r; out->g = in->g; out->b = in->b; in += Factor; out++; } } /* else if (SampleMode == LZoomView::SampleAverage) { System24BitPixel *src[MAX_FACTOR]; for (int i=0; ir; g += src[f]->g; b += src[f]->b; src[f]++; } } dst->r = r / Fsq; dst->g = g / Fsq; dst->b = b / Fsq; dst++; } } */ } template void ScaleDown24To32(OutPx *out, InPx *in, int len, int Factor) { if (SampleMode == LZoomView::SampleNearest) { InPx *end = in + len; while (in < end) { out->r = in->r; out->g = in->g; out->b = in->b; out->a = 0xff; in += Factor; out++; } } } template void ScaleDown24(LColourSpace outCs, void *out, InPx *in, int len, int Factor) { switch (outCs) { #define DownCase(type) \ case Cs##type: \ ScaleDown24((L##type*)out, in, len, Factor); \ break; DownCase(Rgb24) DownCase(Bgr24) DownCase(Rgbx32) DownCase(Bgrx32) DownCase(Xrgb32) DownCase(Xbgr32) #undef DownCase #define DownCase(type) \ case Cs##type: \ ScaleDown24To32((L##type*)out, in, len, Factor); \ break; DownCase(Rgba32) DownCase(Bgra32) DownCase(Argb32) DownCase(Abgr32) #undef DownCase default: LAssert(0); break; } } void ScaleDown(LSurface *Dst, LSurface *Src, int Sx, int Sy, int Factor) { // ImageViewTarget *App = View->GetApp(); uchar *DivLut = Div255Lut; #if 0 // def _DEBUG Dst->Colour(LColour(255, 0, 255)); Dst->Rectangle(); #endif Pal32 pal[256]; if (Src->GetColourSpace() == CsIndex8) { LPalette *inPal = Src->Palette(); for (int i=0; i<256; i++) { GdcRGB *rgb = inPal ? (*inPal)[i] : NULL; if (rgb) { pal[i].p32.r = rgb->r; pal[i].p32.g = rgb->g; pal[i].p32.b = rgb->b; } else { pal[i].p32.r = i; pal[i].p32.g = i; pal[i].p32.b = i; } pal[i].p32.a = 255; } } // Now copy the right pixels over using the selected sampling method... for (int y=0; yY(); y++) { int yy = Sy + (y * Factor); if (yy >= Src->Y()) continue; int Ex = Sx + (TileSize * Factor); if (Ex >= Src->X()) Ex = Src->X(); switch (Src->GetColourSpace()) { case CsIndex8: { if (SampleMode == LZoomView::SampleNearest) { uint8_t *src = (*Src)[yy]; uint8_t *end = src + Ex; uint32_t *dst = (uint32_t*) (*Dst)[y]; src += Sx; // LAssert(Dst->GetColourSpace() == System32BitColourSpace); while (src < end) { *dst++ = pal[*src].u32; src += Factor; } } else if (SampleMode == LZoomView::SampleMax) { uint8_t *s[32]; LAssert(Factor < 32); for (int f=0; f val) val = *src; src++; } s[oy] = src; } *dst++ = pal[val].u32; } } else { LAssert(!"Impl me."); } break; } case CsRgb15: case CsRgb16: case CsBgr15: case CsBgr16: { uint16 *src = (uint16*) (*Src)[yy]; uint16 *dst = (uint16*) (*Dst)[y]; uint16 *end = src + Ex; src += Sx; if (SampleMode == LZoomView::SampleNearest) { while (src < end) { *dst++ = *src; src += Factor; } } else { LAssert(!"Impl me."); } break; } #define ScaleDownCase(type, bits) \ case Cs##type: \ ScaleDown##bits(Dst->GetColourSpace(), (*Dst)[y], (L##type*) (*Src)[yy] + Sx, Ex - Sx, Factor); \ break; ScaleDownCase(Rgbx32, 24); ScaleDownCase(Xrgb32, 24); ScaleDownCase(Bgrx32, 24); ScaleDownCase(Xbgr32, 24); ScaleDownCase(Rgb24, 24); ScaleDownCase(Bgr24, 24); case System32BitColourSpace: { System32BitPixel *src = (System32BitPixel*) (*Src)[yy]; System32BitPixel *dst = (System32BitPixel*) (*Dst)[y]; System32BitPixel *end = src + Ex; src += Sx; #define rop(c) dst->c = (DivLut[DivLut[dst->c * dst->a] * o] + DivLut[src->c * src->a]) * 255 / ra; if (SampleMode == LZoomView::SampleNearest) { // if (Dst->GetColourSpace() == Src->GetColourSpace()) while (src < end) { if (src->a == 255) { *dst = *src; } else if (src->a) { uint8_t o = 255 - src->a; int ra = (dst->a + src->a) - DivLut[dst->a * src->a]; rop(r); rop(g); rop(b); dst->a = ra; } dst++; src += Factor; } } else { LAssert(!"Impl me."); } #undef rop break; } case CsBgr48: { LBgr48 *src = (LBgr48*) (*Src)[yy]; LBgr24 *dst = (LBgr24*) (*Dst)[y]; LBgr48 *end = src + Ex; src += Sx; LAssert(Dst->GetColourSpace() == CsBgr24); if (SampleMode == LZoomView::SampleNearest) { while (src < end) { dst->r = src->r >> 8; dst->g = src->g >> 8; dst->b = src->b >> 8; dst++; src += Factor; } } else { LAssert(!"Impl me."); } break; } case CsBgra64: { LBgra64 *s = (LBgra64*) (*Src)[yy]; LBgra32 *d = (LBgra32*) (*Dst)[y]; LBgra64 *end = s + Ex; s += Sx; LAssert(Dst->GetColourSpace() == CsBgra32); if (SampleMode == LZoomView::SampleNearest) { while (s < end) { if (s->a == 0xffff) { // Copy pixel d->r = s->r >> 8; d->g = s->g >> 8; d->b = s->b >> 8; d->a = s->a >> 8; } else if (s->a) { // Composite with dest uint8_t o = (0xffff - s->a) >> 8; uint8_t dc, sc; Rgb16to8PreMul(r); Rgb16to8PreMul(g); Rgb16to8PreMul(b); } d++; s += Factor; } } else { LAssert(!"Impl me."); } break; } default: { LAssert(!"Not impl"); break; } } } } /// Scale pixels up into a tile void ScaleUp ( /// The tile to write to LSurface *Dst, /// The source bitmap we are displaying LSurface *Src, /// X offset into source for tile's data int Sx, /// Y offset into source int Sy, /// The scaling factor to apply int Factor ) { COLOUR Dark = GdcD->GetColour(Rgb24(128, 128, 128), Dst); COLOUR Light = GdcD->GetColour(Rgb24(192, 192, 192), Dst); int f = Factor - 1; int HasGrid = Callback ? Callback->DisplayGrid() : false; int HasTile = Callback ? Callback->DisplayTile() : false; // Now copy the right pixels over... int Pix = TileSize / Factor; LAssert(Dst->X() % Factor == 0); Pal32 pal[256]; switch (Src->GetColourSpace()) { default: { LAssert(0); break; } case CsIndex8: { LPalette *inPal = Src->Palette(); for (int i=0; i<256; i++) { GdcRGB *rgb = inPal ? (*inPal)[i] : NULL; if (rgb) { pal[i].p32.r = rgb->r; pal[i].p32.g = rgb->g; pal[i].p32.b = rgb->b; } else { pal[i].p32.r = i; pal[i].p32.g = i; pal[i].p32.b = i; } pal[i].p32.a = 255; } break; } case System16BitColourSpace: case System24BitColourSpace: case System32BitColourSpace: { if (Callback && (!TileCache || TileCache->X() != TileSize || TileCache->Y() != TileSize)) { TileCache.Reset(new LMemDC(TileSize, TileSize, System32BitColourSpace)); } if (TileCache) { LRect s; s.ZOff(TileSize-1, TileSize-1); s.Offset(Sx, Sy); LPoint off(Sx, Sy); Callback->DrawBackground(View, TileCache, off, NULL); TileCache->Op(GDC_ALPHA); TileCache->Blt(0, 0, Src, &s); } break; } } for (int y=0; y= Src->Y()) continue; int EndX = Sx + Pix; if (EndX > Src->X()) EndX = Src->X(); switch (Src->GetColourSpace()) { case CsIndex8: { uint8_t *src = (*Src)[SrcY]; uint8_t *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(pal[*src++].u32, 32); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; } break; } case CsRgb15: case CsRgb16: case CsBgr15: case CsBgr16: { uint16 *src = (uint16*) (*Src)[SrcY]; uint16 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(*src++); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; } break; } case System24BitColourSpace: { System24BitPixel *src = ((System24BitPixel*) (*Src)[SrcY]); System24BitPixel *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r, src->g, src->b)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsBgr48: { LBgr48 *src = ((LBgr48*) (*Src)[SrcY]); LBgr48 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r >> 8, src->g >> 8, src->b >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsRgb48: { LRgb48 *src = ((LRgb48*) (*Src)[SrcY]); LRgb48 *end = src + EndX; src += Sx; while (src < end) { Dst->Colour(Rgb24(src->r >> 8, src->g >> 8, src->b >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsRgba64: { LRgba64 *src = ((LRgba64*) (*Src)[SrcY]); LRgba64 *end = src + EndX; src += Sx; Dst->Op(GDC_ALPHA); while (src < end) { Dst->Colour(Rgba32(src->r >> 8, src->g >> 8, src->b >> 8, src->a >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case CsBgra64: { LBgra64 *src = ((LBgra64*) (*Src)[SrcY]); LBgra64 *end = src + EndX; src += Sx; Dst->Op(GDC_ALPHA); while (src < end) { Dst->Colour(Rgba32(src->r >> 8, src->g >> 8, src->b >> 8, src->a >> 8)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } break; } case System32BitColourSpace: { System32BitPixel *src, *end; if (TileCache) { src = (System32BitPixel*) (*TileCache)[y]; end = src + (EndX - Sx); } else { src = (System32BitPixel*) (*Src)[SrcY]; end = src + EndX; src += Sx; } if (HasGrid) { uint32_t *s = (uint32_t*)src; uint32_t *e = (uint32_t*)end; if (Factor == 2) { uint32_t *dst0 = (uint32_t*) (*Dst)[DstY]; uint32_t *dst1 = (uint32_t*) (*Dst)[DstY+1]; LAssert(Src->GetColourSpace() == Dst->GetColourSpace()); if (dst1) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst1++ = *s++; *dst1++ = Light; } } else if (dst0) { while (s < e) { *dst0++ = *s; *dst0++ = *s++; } } } else if (Factor == 3) { uint32_t *dst0 = (uint32_t*) (*Dst)[DstY]; uint32_t *dst1 = (uint32_t*) (*Dst)[DstY+1]; uint32_t *dst2 = (uint32_t*) (*Dst)[DstY+2]; LAssert(Src->GetColourSpace() == Dst->GetColourSpace()); if (dst2) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s; *dst1++ = *s; *dst1++ = *s; *dst1++ = Light; *dst2++ = *s++; *dst2++ = Light; *dst2++ = Dark; } } else if (dst1) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s; *dst1++ = *s; *dst1++ = *s++; *dst1++ = Light; } } else if (dst0) { while (s < e) { *dst0++ = *s; *dst0++ = *s; *dst0++ = *s++; } } } else { // General case int ff = f - 1; while (src < end) { Dst->Colour(Rgba32(src->r, src->g, src->b, src->a)); Dst->Rectangle(DstX, DstY, DstX+ff, DstY+ff); DstX += Factor; src++; } ((LMemDC*)Dst)->HorzLine(0, Dst->X(), DstY+f, Light, Dark); } } else { while (src < end) { Dst->Colour(Rgba32(src->r, src->g, src->b, src->a)); Dst->Rectangle(DstX, DstY, DstX+f, DstY+f); DstX += Factor; src++; } } break; } default: { LAssert(!"Not impl"); break; } } } LMemDC *Mem = dynamic_cast(Dst); if (Mem && Factor > 3) { if (HasGrid) { for (int x = Factor - 1; xX(); x += Factor) { Mem->VertLine(x, 0, Dst->Y(), Light, Dark); } for (int y = Factor - 1; yY(); y += Factor) { Mem->HorzLine(0, Dst->X(), y, Light, Dark); } } if (HasTile) { COLOUR DarkBlue = GdcD->GetColour(Rgb24(0, 0, 128), Dst); COLOUR LightBlue = GdcD->GetColour(Rgb24(0, 0, 255), Dst); int TileX = 16; int TileY = 16; if (Callback) { switch (Callback->TileType()) { case 0: { TileX = TileY = 16; break; } case 1: { TileX = TileY = 32; break; } default: { TileX = Callback->TileX(); TileY = Callback->TileY(); break; } } } int i = TileX - (Sx % TileX); for (int x = i * Factor - 1; xX(); x += Factor * TileX) { Mem->VertLine(x, 0, Dst->Y(), LightBlue, DarkBlue); } i = TileY - (Sy % TileY); for (int y = i * Factor - 1; yY(); y += Factor * TileY) { Mem->HorzLine(0, Dst->X(), y, LightBlue, DarkBlue); } } } } void UpdateTile(int x, int y) { LAssert(x >= 0 && x < Tiles.x); LAssert(y >= 0 && y < Tiles.y); ZoomTile *Dst = Tile[x][y]; LSurface *Src = pDC; if (Dst && Src) { // Draw background if (Callback) { LRect r = Dst->Bounds(); LPoint off(x, y); Callback->DrawBackground(View, Dst, off, &r); } else { Dst->Colour(L_WORKSPACE); Dst->Rectangle(); } int f = Factor(); if (Zoom < 0) { ScaleDown( Dst, Src, (x * TileSize) * f, (y * TileSize) * f, f); } else if (Zoom > 0) { ScaleUp( Dst, Src, (x * TileSize) / f, (y * TileSize) / f, f); } else { // 1:1 LRect s; s.ZOff(TileSize-1, TileSize-1); s.Offset(x * TileSize, y * TileSize); #if 0 LRect dd = s; dd.Bound(&Src->Bounds()); dd.Offset(-x * TileSize, -y * TileSize); Dst->Colour(LColour(0xbb, 0, 0xbb)); Dst->Rectangle(); int sy = s.Y(); auto sm2 = (*Src)[s.y2-1]; auto sm1 = (*Src)[s.y2]; auto dm2 = (*Dst)[dd.y2-1]; auto dm1 = (*Dst)[dd.y2]; auto dm0 = (*Dst)[dd.y2+1]; #endif Dst->Op(GDC_ALPHA); Dst->Blt(0, 0, Src, &s); } // Draw any foreground elements if (Callback) { LRect r = Dst->Bounds(); LPoint off(x, y); Callback->DrawForeground(View, Dst, off, &r); } Dst->Dirty = false; } } }; LZoomView::LZoomView(LZoomViewCallback *callback) : ResObject(Res_Custom) { d = new LZoomViewPriv(this, callback); // Sunken(true); } LZoomView::~LZoomView() { DeleteObj(d); } bool LZoomView::OnLayout(LViewLayoutInfo &Inf) { Inf.Width.Min = -1; Inf.Width.Max = -1; Inf.Height.Min = -1; Inf.Height.Max = -1; return true; } void LZoomView::UpdateScrollBars(LPoint *MaxScroll, bool ResetPos) { LSurface *Src = d->pDC; if (!Src) { SetScrollBars(false, false); return; } static bool Updating = false; if (!Updating) { Updating = true; LRect c = GetClient(); LPoint DocSize(Src->X(), Src->Y()); LPoint DocClientSize(c.X(), c.Y()); DocClientSize = d->ScreenToDoc(DocClientSize); // This can change the client area... // Which will cause a OnPosChange event to occur. SetScrollBars(DocSize.x > DocClientSize.x, DocSize.y > DocClientSize.y); #if 0 LgiTrace("Scroll bars=%i,%i cli=%i,%i(raw=%s) doc=%i,%i\n", DocSize.x > DocClientSize.x, DocSize.y > DocClientSize.y, DocClientSize.x, DocClientSize.y, c.GetStr(), DocSize.x, DocSize.y); #endif if (HScroll) { HScroll->SetRange(DocSize.x + RIGHT_BOTTOM_WHITESPACE); HScroll->SetPage(DocClientSize.x); if (ResetPos) HScroll->Value(0); } if (VScroll) { VScroll->SetRange(DocSize.y + RIGHT_BOTTOM_WHITESPACE); VScroll->SetPage(DocClientSize.y); if (ResetPos) VScroll->Value(0); } if (MaxScroll) { MaxScroll->x = DocSize.x - DocClientSize.x; MaxScroll->y = DocSize.y - DocClientSize.y; } Updating = false; } else { // LgiTrace("LZoomView::UpdateScrollBars is already updating..\n"); d->ScrollBarsDirty = true; // Update them later } } void LZoomView::OnPosChange() { LLayout::OnPosChange(); UpdateScrollBars(); } void LZoomView::SetSurface(LSurface *dc, bool own) { if (d->OwnDC) DeleteObj(d->pDC); d->pDC = dc; d->OwnDC = own; Reset(); } LSurface *LZoomView::GetSurface() { return d->pDC; } void LZoomView::Update(LRect *Where) { #if DEBUG_THREADING if (!d->Dirty) LgiTrace("%i setting DIRTY\n", LGetCurrentThread()); #endif LRect w; if (Where) { // Some tiles only w = d->DocToScreen(*Where); w.x1 /= d->TileSize; w.y1 /= d->TileSize; w.x2 /= d->TileSize; w.y2 /= d->TileSize; LRect b(0, 0, d->Tiles.x-1, d->Tiles.y-1); w.Bound(&b); } else { // All tiles w.ZOff(d->Tiles.x-1, d->Tiles.y-1); } if (d->Tile && w.Valid()) { for (int y=w.y1; y<=w.y2; y++) { for (int x=w.x1; x<=w.x2; x++) { if (d->Tile[x][y]) { d->Tile[x][y]->Dirty = true; } } } } Invalidate(); } void LZoomView::Reset() { d->EmptyTiles(); d->SetDefaultZoom(); UpdateScrollBars(NULL, true); Invalidate(); } int LZoomView::GetBlockSize() { return d->GetZoom() > 0 ? 16 : 1; } LZoomView::ViewportInfo LZoomView::GetViewport() { ViewportInfo v; v.Zoom = d->GetZoom(); v.Sy = VScroll ? (int)VScroll->Value() : 0; v.Sx = HScroll ? (int)HScroll->Value() : 0; v.TilePx = d->TileSize; return v; } int LZoomView::GetZoom() { return d->GetZoom(); } void LZoomView::SetZoom(int z) { return d->SetZoom(z); } void LZoomView::SetDefaultZoomMode(DefaultZoomMode m) { d->DefaultZoom = m; } LZoomView::DefaultZoomMode LZoomView::GetDefaultZoomMode() { return d->DefaultZoom; } void LZoomView::ScrollToPoint(LPoint DocCoord) { if (!d->pDC) return; if (HScroll) { int64 DocX = d->pDC->X(); int64 Page = HScroll->Page(); int64 MaxVal = DocX - (Page - 1); int64 x1 = HScroll->Value(); int64 x2 = x1 + Page; if (DocCoord.x < x1) HScroll->Value(MAX(DocCoord.x, 0)); else if (DocCoord.x > x2) HScroll->Value(MIN(DocCoord.x-Page+1, MaxVal)); } if (VScroll) { int DocY = d->pDC->Y(); int Page = (int)VScroll->Page(); int MaxVal = DocY - (Page - 1); int64 y1 = VScroll->Value(); int64 y2 = y1 + Page; if (DocCoord.y < y1) VScroll->Value(MAX(DocCoord.y, 0)); else if (DocCoord.y > y2) VScroll->Value(MIN(DocCoord.y-Page+1, MaxVal)); } } void LZoomView::SetSampleMode(SampleMode sm) { d->SampleMode = sm; } void LZoomView::SetViewport(ViewportInfo i) { d->SetZoom(i.Zoom); if (!d->Tile) d->ResetTiles(); LSurface *Src = d->pDC; if (Src) { LPoint MaxScroll; UpdateScrollBars(&MaxScroll); if (HScroll) { if (i.Sx < 0) i.Sx = 0; if (i.Sx > MaxScroll.x) i.Sx = MaxScroll.x; HScroll->Value(i.Sx); } if (VScroll) { if (i.Sy < 0) i.Sy = 0; if (i.Sy > MaxScroll.y) i.Sy = MaxScroll.y; VScroll->Value(i.Sy); } // FIXME... mark stuff dirty Invalidate(); } } void LZoomView::SetCallback(LZoomViewCallback *cb) { d->Callback = cb; } bool LZoomView::Convert(LPointF &p, int x, int y) { int64 Sx = 0, Sy = 0; int64 Factor = d->Factor(); GetScrollPos(Sx, Sy); if (d->GetZoom() > 0) { // Scaled up p.x = (double)Sx + ((double)x / Factor); p.y = (double)Sy + ((double)y / Factor); } else if (d->GetZoom() < 0) { // Scaled down p.x = (double)Sx + (x * Factor); p.y = (double)Sy + (y * Factor); } else { p.x = (double)Sx + x; p.y = (double)Sy + y; } LSurface *Src = d->pDC; if (Src && p.x >= 0 && p.x <= Src->X() && p.y >= 0 && p.y <= Src->Y()) { return true; } return false; } void LZoomView::OnMouseClick(LMouse &m) { if (m.Down()) Focus(true); } bool LZoomView::OnMouseWheel(double Lines) { LMouse m; GetMouse(m); if (m.Ctrl()) { LPointF DocPt; bool In = Convert(DocPt, m.x, m.y); // Zoom the graphic if (Lines < 0) { d->SetZoom(d->GetZoom() + 1); } else if (Lines > 0) { d->SetZoom(d->GetZoom() - 1); } else { LAssert(!"No lines value?"); return true; } d->ResetTiles(); if (d->Callback) { char Msg[256]; sprintf_s( Msg, sizeof(Msg), "Zoom: %s%i", d->GetZoom() < 0 ? "1/" : (d->GetZoom() == 0 ? "1:" : ""), d->Factor()); d->Callback->SetStatusText(Msg); } // Is the cursor over the image??? if (In) { LSurface *Src = d->pDC; if (Src) { - LRect c = GetClient(); int Factor = d->Factor(); int NewSx, NewSy; if (d->GetZoom() > 0) { // Scale up NewSx = (int) (DocPt.x - (m.x / Factor)); NewSy = (int) (DocPt.y - (m.y / Factor)); } else if (d->GetZoom() < 0) { // Scale down NewSx = (int)DocPt.x - (m.x * Factor); NewSy = (int)DocPt.y - (m.y * Factor); } else { // 1:1 NewSx = (int) (DocPt.x - m.x); NewSy = (int) (DocPt.y - m.y); } UpdateScrollBars(); } // else printf("%s:%i - No 'src'?\n", _FL); } // else printf("%s:%i - No 'in'?\n", _FL); // Update the screen // FIXME... mark stuff dirty Invalidate(); SendNotify(LNotifyViewportChanged); } else { // Normal wheel if (m.Shift()) { // Scroll in the X direction... if (HScroll) { HScroll->Value((int64) (HScroll->Value() + (Lines * 5))); SendNotify(LNotifyViewportChanged); } } else { // Scroll in the Y direction... if (VScroll) { VScroll->Value((int64) (VScroll->Value() + (Lines * 5))); SendNotify(LNotifyViewportChanged); } } } return true; } void LZoomView::OnPulse() { LMouse m; if (!GetMouse(m)) { LgiTrace("%s:%i - GetMouse failed.\n", _FL); return; } LRect c = GetClient(); bool Inside = c.Overlap(m.x, m.y); if (!Inside) { float s = ZoomToScale(d->GetZoom()); // Scroll window to show pixel under the mouse... bool Update = false; if (VScroll) { int Amount = 0; if (m.y < 0) Amount = (int) (m.y / s); else if (m.y > c.y2) Amount = (int) ((m.y - c.y2) / s); if (Amount) { VScroll->Value(VScroll->Value() + Amount); SendNotify(LNotifyViewportChanged); Update = true; } } if (HScroll) { int Amount = 0; if (m.x < 0) Amount = (int) (m.x / s); else if (m.x > c.x2) Amount = (int) ((m.x - c.x2) / s); if (Amount) { HScroll->Value(HScroll->Value() + Amount); SendNotify(LNotifyViewportChanged); Update = true; } } if (Update) { OnMouseMove(m); } } } LMessage::Param LZoomView::OnEvent(LMessage *m) { switch (m->Msg()) { case M_RENDER_FINISHED: { return 0; } } return LLayout::OnEvent(m); } int LZoomView::OnNotify(LViewI *v, LNotification n) { switch (v->GetId()) { case IDC_VSCROLL: case IDC_HSCROLL: { Invalidate(); #if WINNATIVE UpdateWindow(Handle()); #endif SendNotify(LNotifyViewportChanged); + #if 0 if (HScroll) { auto r = HScroll->GetRange(); auto p = HScroll->Page(); auto v = HScroll->Value(); - - #if 0 LgiTrace("Notify v=%i p=%i r=%i,%i exp=%i\n", (int)v, (int)p, (int)r.Start, (int)r.End(), (int)(r.End()-p)); - #endif } + #endif break; } } return 0; } void LZoomView::OnPaint(LSurface *pDC) { #if 0 // coverage test pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif if (d->ScrollBarsDirty) { d->ScrollBarsDirty = false; UpdateScrollBars(); } LRect c = GetClient(); LRegion Rgn(c); LSurface *Src = d->pDC; if (Src) { if (!d->Tile) d->ResetTiles(); int64 Sx = 0, Sy = 0; GetScrollPos(Sx, Sy); // Get the image bounds and scroll it into position (view coords) LRect ScaledDocSize = Src->Bounds(); ScaledDocSize = d->DocToScreen(ScaledDocSize); LRect s = ScaledDocSize; // Scroll positions are in doc px, so scale them here to screen LPoint ScaledScroll((int)Sx, (int)Sy); ScaledScroll = d->DocToScreen(ScaledScroll); s.Offset(-ScaledScroll.x, -ScaledScroll.y); #if 0 LgiTrace("Paint scroll=%i,%i cli=%s doc=%s(%ix%i)\n", Sx, Sy, d->ScreenToDoc(c).GetStr(), d->ScreenToDoc(s).GetStr(), Src->X(), Src->Y()); #endif // Work out the visible tiles... LRect vis = s; vis.Bound(&c); vis.Offset(ScaledScroll.x, ScaledScroll.y); LRect tile; tile.x1 = vis.x1 / d->TileSize; tile.y1 = vis.y1 / d->TileSize; tile.x2 = vis.x2 / d->TileSize; tile.y2 = vis.y2 / d->TileSize; // Check the visible tiles are in range LAssert(tile.x1 >= 0 && tile.x1 < d->Tiles.x); LAssert(tile.y1 >= 0 && tile.y1 < d->Tiles.y); LAssert(tile.x2 >= 0 && tile.x2 < d->Tiles.x); LAssert(tile.y2 >= 0 && tile.y2 < d->Tiles.y); // Make sure we have tiles available and they are up to date LColourSpace Cs = Src->GetBits() <= 8 ? System32BitColourSpace : Src->GetColourSpace(); for (int y=tile.y1; y<=tile.y2; y++) { for (int x=tile.x1; x<=tile.x2; x++) { if (!d->Tile[x][y] || d->Tile[x][y]->GetColourSpace() != Cs) { DeleteObj(d->Tile[x][y]); d->Tile[x][y] = new ZoomTile(d->TileSize, Cs); } if (d->Tile[x][y] && d->Tile[x][y]->Dirty) { d->UpdateTile(x, y); } } } // Paint our tiles... pDC->SetOrigin(ScaledScroll.x, ScaledScroll.y); for (int y=tile.y1; y<=tile.y2; y++) { bool LastY = y == d->Tiles.y - 1; for (int x=tile.x1; x<=tile.x2; x++) { if (d->Tile[x][y]) { int px = x * d->TileSize; int py = y * d->TileSize; LRect r(px, py, px + d->TileSize - 1, py + d->TileSize - 1); if (LastY || x == d->Tiles.x - 1) { // We have to clip off the part of the tile that doesn't // contain image. // Work out how much image is in the tile LRect img = r; if (img.x2 >= ScaledDocSize.X()) img.x2 = ScaledDocSize.X() - 1; if (img.y2 >= ScaledDocSize.Y()) img.y2 = ScaledDocSize.Y() - 1; // Work out the image pixels in tile co-ords LRect tile_source(0, 0, img.X()-1, img.Y()-1); // Also work out where this is on the screen LRect screen_source = tile_source; screen_source.Offset(px, py); // Blt the tile image pixels to the screen LSurface *pTile = d->Tile[x][y]; pDC->Blt(px, py, pTile, &tile_source); #if 0 LgiTrace("Zoom: %i,%i %s %s->%s (%s)\n", px, py, tile_source.GetStr(), LColourSpaceToString(pTile->GetColourSpace()), LColourSpaceToString(pDC->GetColourSpace()), LColourSpaceToString(GdcD->GetColourSpace())); #endif #if DEBUG_TILE_BOUNDARIES uint32_t old = pDC->LineStyle(LSurface::LineAlternate); pDC->Colour(LColour(0, 0, 255)); pDC->Box(&screen_source); pDC->LineStyle(old); #endif screen_source.Offset(-ScaledScroll.x, -ScaledScroll.y); Rgn.Subtract(&screen_source); } else { // Full tile of image data pDC->Blt(px, py, d->Tile[x][y]); #if DEBUG_TILE_BOUNDARIES pDC->Colour(LColour(64, 0, 255)); pDC->Box(&r); #endif r.Offset(-ScaledScroll.x, -ScaledScroll.y); Rgn.Subtract(&r); } } else LAssert(!"Missing tile?"); } } } pDC->SetOrigin(0, 0); for (LRect *r = Rgn.First(); r; r = Rgn.Next()) { pDC->Colour(L_MED); pDC->Rectangle(r); #if 0 pDC->Colour(LColour(255, 0, 255)); pDC->Box(r); #endif } } class LZoomViewFactory : public LViewFactory { public: LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!_stricmp(Class, "LZoomView")) return new LZoomView(NULL); return NULL; } } ZoomViewFactory; diff --git a/src/linux/General/File.cpp b/src/linux/General/File.cpp --- a/src/linux/General/File.cpp +++ b/src/linux/General/File.cpp @@ -1,1762 +1,1783 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ #include #include #include #include #include #include #include #include #ifndef _MSC_VER #include #endif #include "lgi/common/LgiDefs.h" #include "lgi/common/File.h" #include "lgi/common/Containers.h" #include "lgi/common/Token.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiCommon.h" #include "lgi/common/LgiString.h" #include "lgi/common/DateTime.h" #if defined(WIN32) #include "errno.h" #endif /****************************** Defines ***********************************/ // #define FILEDEBUG #define FLOPPY_360K 0x0001 #define FLOPPY_720K 0x0002 #define FLOPPY_1_2M 0x0004 #define FLOPPY_1_4M 0x0008 #define FLOPPY_5_25 (FLOPPY_360K | FLOPPY_1_2M) #define FLOPPY_3_5 (FLOPPY_720K | FLOPPY_1_4M) /****************************** Globals ***********************************/ LString LFile::Path::Sep(DIR_STR); struct ErrorCodeType { const char *Name; int Code; const char *Desc; } ErrorCodes[] = { #if defined(WIN32) {"EPERM", 1, "Not owner"}, {"ENOENT", 2, "No such file"}, {"ESRCH", 3, "No such process"}, {"EINTR", 4, "Interrupted system"}, {"EIO", 5, "I/O error"}, {"ENXIO", 6, "No such device"}, {"E2BIG", 7, "Argument list too long"}, {"ENOEXEC", 8, "Exec format error"}, {"EBADF", 9, "Bad file number"}, {"ECHILD", 10, "No children"}, {"EAGAIN", 11, "No more processes"}, {"ENOMEM", 12, "Not enough core"}, {"EACCES", 13, "Permission denied"}, {"EFAULT", 14, "Bad address"}, {"ENOTBLK", 15, "Block device required"}, {"EBUSY", 16, "Mount device busy"}, {"EEXIST", 17, "File exists"}, {"EXDEV", 18, "Cross-device link"}, {"ENODEV", 19, "No such device"}, {"ENOTDIR", 20, "Not a directory"}, {"EISDIR", 21, "Is a directory"}, {"EINVAL", 22, "Invalid argument"}, {"ENFILE", 23, "File table overflow"}, {"EMFILE", 24, "Too many open file"}, {"ENOTTY", 25, "Not a typewriter"}, {"ETXTBSY", 26, "Text file busy"}, {"EFBIG", 27, "File too large"}, {"ENOSPC", 28, "No space left on"}, {"ESPIPE", 29, "Illegal seek"}, {"EROFS", 30, "Read-only file system"}, {"EMLINK", 31, "Too many links"}, {"EPIPE", 32, "Broken pipe"}, {"EWOULDBLOCK", 35, "Operation would block"}, {"EINPROGRESS", 36, "Operation now in progress"}, {"EALREADY", 37, "Operation already in progress"}, {"ENOTSOCK", 38, "Socket operation on"}, {"EDESTADDRREQ", 39, "Destination address required"}, {"EMSGSIZE", 40, "Message too long"}, {"EPROTOTYPE", 41, "Protocol wrong type"}, {"ENOPROTOOPT", 42, "Protocol not available"}, {"EPROTONOSUPPORT", 43, "Protocol not supported"}, {"ESOCKTNOSUPPORT", 44, "Socket type not supported"}, {"EOPNOTSUPP", 45, "Operation not supported"}, {"EPFNOSUPPORT", 46, "Protocol family not supported"}, {"EAFNOSUPPORT", 47, "Address family not supported"}, {"EADDRINUSE", 48, "Address already in use"}, {"EADDRNOTAVAIL", 49, "Can't assign requested address"}, {"ENETDOWN", 50, "Network is down"}, {"ENETUNREACH", 51, "Network is unreachable"}, {"ENETRESET", 52, "Network dropped connection"}, {"ECONNABORTED", 53, "Software caused connection"}, {"ECONNRESET", 54, "Connection reset by peer"}, {"ENOBUFS", 55, "No buffer space available"}, {"EISCONN", 56, "Socket is already connected"}, {"ENOTCONN", 57, "Socket is not connected" }, {"ESHUTDOWN", 58, "Can't send after shutdown"}, {"ETOOMANYREFS", 59, "Too many references"}, {"ETIMEDOUT", 60, "Connection timed out"}, {"ECONNREFUSED", 61, "Connection refused"}, {"ELOOP", 62, "Too many levels of nesting"}, {"ENAMETOOLONG", 63, "File name too long" }, {"EHOSTDOWN", 64, "Host is down"}, {"EHOSTUNREACH", 65, "No route to host"}, {"ENOTEMPTY", 66, "Directory not empty"}, {"EPROCLIM", 67, "Too many processes"}, {"EUSERS", 68, "Too many users"}, {"EDQUOT", 69, "Disc quota exceeded"}, {"ESTALE", 70, "Stale NFS file handle"}, {"EREMOTE", 71, "Too many levels of remote in the path"}, {"ENOSTR", 72, "Device is not a stream"}, {"ETIME", 73, "Timer expired"}, {"ENOSR", 74, "Out of streams resources"}, {"ENOMSG", 75, "No message"}, {"EBADMSG", 76, "Trying to read unreadable message"}, {"EIDRM", 77, "Identifier removed"}, {"EDEADLK", 78, "Deadlock condition"}, {"ENOLCK", 79, "No record locks available"}, {"ENONET", 80, "Machine is not on network"}, {"ERREMOTE", 81, "Object is remote"}, {"ENOLINK", 82, "The link has been severed"}, {"EADV", 83, "ADVERTISE error"}, {"ESRMNT", 84, "SRMOUNT error"}, {"ECOMM", 85, "Communication error"}, {"EPROTO", 86, "Protocol error"}, {"EMULTIHOP", 87, "Multihop attempted"}, {"EDOTDOT", 88, "Cross mount point"}, {"EREMCHG", 89, "Remote address change"}, #elif defined(LINUX) || defined(__GTK_H__) {"EPERM", EPERM, "Operation not permitted"}, {"ENOENT", ENOENT, "No such file or directory"}, {"ESRCH", ESRCH, "No such process"}, {"EINTR", EINTR, "Interrupted system call"}, {"EIO", EIO, "I/O error"}, {"ENXIO", ENXIO, "No such device or address"}, {"E2BIG", E2BIG, "Argument list too long"}, {"ENOEXEC", ENOEXEC, "Exec format error"}, {"EBADF", EBADF, "Bad file number"}, {"ECHILD", ECHILD, "No child processes"}, {"EAGAIN", EAGAIN, "Try again"}, {"ENOMEM", ENOMEM, "Out of memory"}, {"EACCES", EACCES, "Permission denied"}, {"EFAULT", EFAULT, "Bad address"}, {"ENOTBLK", ENOTBLK, "Block device required"}, {"EBUSY", EBUSY, "Device or resource busy"}, {"EEXIST", EEXIST, "File exists"}, {"EXDEV", EXDEV, "Cross-device link"}, {"ENODEV", ENODEV, "No such device"}, {"ENOTDIR", ENOTDIR, "Not a directory"}, {"EISDIR", EISDIR, "Is a directory"}, {"EINVAL", EINVAL, "Invalid argument"}, {"ENFILE", ENFILE, "File table overflow"}, {"EMFILE", EMFILE, "Too many open files"}, {"ENOTTY", ENOTTY, "Not a typewriter"}, {"ETXTBSY", ETXTBSY, "Text file busy"}, {"EFBIG", EFBIG, "File too large"}, {"ENOSPC", ENOSPC, "No space left on device"}, {"ESPIPE", ESPIPE, "Illegal seek"}, {"EROFS", EROFS, "Read-only file system"}, {"EMLINK", EMLINK, "Too many links"}, {"EPIPE", EPIPE, "Broken pipe"}, {"EDOM", EDOM, "Math argument out of domain of func"}, {"ERANGE", ERANGE, "Math result not representable"}, {"EDEADLK", EDEADLK, "Resource deadlock would occur"}, {"ENAMETOOLONG", ENAMETOOLONG, "File name too long"}, {"ENOLCK", ENOLCK, "No record locks available"}, {"ENOSYS", ENOSYS, "Function not implemented"}, {"ENOTEMPTY", ENOTEMPTY, "Directory not empty"}, {"ELOOP", ELOOP, "Too many symbolic links encountered"}, {"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"}, {"ENOMSG", ENOMSG, "No message of desired type"}, {"EIDRM", EIDRM, "Identifier removed"}, {"EREMOTE", EREMOTE, "Object is remote"}, {"ENOLINK", ENOLINK, "Link has been severed"}, {"ENOSTR", ENOSTR, "Device not a stream"}, {"ENODATA", ENODATA, "No data available"}, {"ETIME", ETIME, "Timer expired"}, {"ENOSR", ENOSR, "Out of streams resources"}, {"EPROTO", EPROTO, "Protocol error"}, {"EMULTIHOP", EMULTIHOP, "Multihop attempted"}, {"EBADMSG", EBADMSG, "Not a data message"}, {"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"}, {"EILSEQ", EILSEQ, "Illegal byte sequence"}, {"EUSERS", EUSERS, "Too many users"}, {"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"}, {"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"}, {"EMSGSIZE", EMSGSIZE, "Message too long"}, {"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"}, {"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"}, {"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"}, {"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"}, {"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"}, {"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"}, {"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"}, {"EADDRINUSE", EADDRINUSE, "Address already in use"}, {"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"}, {"ENETDOWN", ENETDOWN, "Network is down"}, {"ENETUNREACH", ENETUNREACH, "Network is unreachable"}, {"ENETRESET", ENETRESET, "Network dropped connection because of reset"}, {"ECONNABORTED", ECONNABORTED, "Software caused connection abort"}, {"ECONNRESET", ECONNRESET, "Connection reset by peer"}, {"ENOBUFS", ENOBUFS, "No buffer space available"}, {"EISCONN", EISCONN, "Transport endpoint is already connected"}, {"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"}, {"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"}, {"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"}, {"ETIMEDOUT", ETIMEDOUT, "Connection timed out"}, {"ECONNREFUSED", ECONNREFUSED, "Connection refused"}, {"EHOSTDOWN", EHOSTDOWN, "Host is down"}, {"EHOSTUNREACH", EHOSTUNREACH, "No route to host"}, {"EALREADY", EALREADY, "Operation already in progress"}, {"EINPROGRESS", EINPROGRESS, "Operation now in progress"}, {"ESTALE", ESTALE, "Stale NFS file handle"}, #ifndef __GTK_H__ {"EDQUOT", EDQUOT, "Quota exceeded"}, {"ENOMEDIUM", ENOMEDIUM, "No medium found"}, {"EMEDIUMTYPE", EMEDIUMTYPE, "Wrong medium type"}, {"EUCLEAN", EUCLEAN, "Structure needs cleaning"}, {"ENOTNAM", ENOTNAM, "Not a XENIX named type file"}, {"ENAVAIL", ENAVAIL, "No XENIX semaphores available"}, {"EISNAM", EISNAM, "Is a named type file"}, {"EREMOTEIO", EREMOTEIO, "Remote I/O error"}, {"ERESTART", ERESTART, "Interrupted system call should be restarted"}, {"ESTRPIPE", ESTRPIPE, "Streams pipe error"}, {"ECOMM", ECOMM, "Communication error on send"}, {"EDOTDOT", EDOTDOT, "RFS specific error"}, {"ENOTUNIQ", ENOTUNIQ, "Name not unique on network"}, {"EBADFD", EBADFD, "File descriptor in bad state"}, {"EREMCHG", EREMCHG, "Remote address changed"}, {"ELIBACC", ELIBACC, "Can not access a needed shared library"}, {"ELIBBAD", ELIBBAD, "Accessing a corrupted shared library"}, {"ELIBSCN", ELIBSCN, ".lib section in a.out corrupted"}, {"ELIBMAX", ELIBMAX, "Attempting to link in too many shared libraries"}, {"ELIBEXEC", ELIBEXEC, "Cannot exec a shared library directly"}, {"ECHRNG", ECHRNG, "Channel number out of range"}, {"EL2NSYNC", EL2NSYNC, "Level 2 not synchronized"}, {"EL3HLT", EL3HLT, "Level 3 halted"}, {"EL3RST", EL3RST, "Level 3 reset"}, {"ELNRNG", ELNRNG, "Link number out of range"}, {"EUNATCH", EUNATCH, "Protocol driver not attached"}, {"ENOCSI", ENOCSI, "No CSI structure available"}, {"EL2HLT", EL2HLT, "Level 2 halted"}, {"EBADE", EBADE, "Invalid exchange"}, {"EBADR", EBADR, "Invalid request descriptor"}, {"EXFULL", EXFULL, "Exchange full"}, {"ENOANO", ENOANO, "No anode"}, {"EBADRQC", EBADRQC, "Invalid request code"}, {"EBADSLT", EBADSLT, "Invalid slot"}, {"EBFONT", EBFONT, "Bad font file format"}, {"EADV", EADV, "Advertise error"}, {"ESRMNT", ESRMNT, "Srmount error"}, {"ENONET", ENONET, "Machine is not on the network"}, {"ENOPKG", ENOPKG, "Package not installed"}, #endif #endif {"NONE", 0, "No error"}, }; const char *GetErrorName(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Name; } } static char s[32]; sprintf(s, "Unknown(%i)", e); return s; } const char *GetErrorDesc(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) return c->Desc; } return 0; } /****************************** Helper Functions **************************/ char *LReadTextFile(const char *File) { char *s = 0; LFile f; if (File && f.Open(File, O_READ)) { int Len = f.GetSize(); s = new char[Len+1]; if (s) { int Read = f.Read(s, Len); s[Read] = 0; } } return s; } int64 LFileSize(const char *FileName) { struct stat64 s; if (FileName && stat64(FileName, &s) == 0) { return s.st_size; } return 0; } bool LDirExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... int r = lstat(FileName, &s); if (r == 0) { Status = S_ISDIR(s.st_mode) || S_ISLNK(s.st_mode); // printf("DirStatus(%s) lstat = %i, %i\n", FileName, Status, s.st_mode); } else { r = stat(FileName, &s); if (r == 0) { Status = S_ISDIR(s.st_mode) || S_ISLNK(s.st_mode); // printf("DirStatus(%s) stat ok = %i, %i\n", FileName, Status, s.st_mode); } else { // printf("DirStatus(%s) lstat and stat failed, r=%i, errno=%i\n", FileName, r, errno); } } } return Status; } bool LFileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = !S_ISDIR(s.st_mode); } else if (CorrectCase) { // Look for altenate case by enumerating the directory char d[256]; strcpy(d, FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while ((De = readdir(Dir))) { if (De->d_type != DT_DIR && stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = (char*) strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - LFileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { if (!LinkFile || !Path || Len <= 0) return false; ssize_t r = readlink(LinkFile, Path, Len); return r > 0; } void WriteStr(LFile &f, const char *s) { uint32_t Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(ulong) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } ///////////////////////////////////////////////////////////////////////// #include #include #include struct LVolumePriv { LVolume *Owner = NULL; int64 Size = 0, Free = 0; int Type = VT_NONE, Flags = 0; LSystemPath SysPath = LSP_ROOT; LString Name, Path; LVolume *NextVol = NULL, *ChildVol = NULL; LVolumePriv(LVolume *owner, const char *path) : Owner(owner) { Path = path; Name = LGetLeaf(path); Type = VT_FOLDER; } LVolumePriv(LVolume *owner, LSystemPath sys, const char *name) : Owner(owner) { SysPath = sys; Name = name; if (SysPath == LSP_ROOT) Path = "/"; else Path = LGetSystemPath(SysPath); if (Path) Type = sys == LSP_DESKTOP ? VT_DESKTOP : VT_FOLDER; - if (sys == LSP_DESKTOP) + if (sys == LSP_ROOT) { struct statvfs s = {0}; int r = statvfs(Path, &s); if (r) LgiTrace("%s:%i - statvfs failed with %i\n", _FL, r); else { Size = (uint64_t) s.f_blocks * s.f_frsize; Free = (uint64_t) s.f_bfree * s.f_frsize; } } } ~LVolumePriv() { DeleteObj(NextVol); DeleteObj(ChildVol); } void Insert(LVolume *vol, LVolume *newVol) { if (!vol || !newVol) return; if (vol->d->ChildVol) { for (auto v = vol->d->ChildVol; v; v = v->d->NextVol) { if (!v->d->NextVol) { LAssert(newVol != v->d->Owner); v->d->NextVol = newVol; // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); break; } } } else { LAssert(newVol != vol->d->Owner); vol->d->ChildVol = newVol; // printf("Insert %p:%s into %p:%s\n", newVol, newVol->Name(), vol, vol->Name()); } } LVolume *First() { if (SysPath == LSP_DESKTOP && !ChildVol) { // Get various shortcuts to points of interest Insert(Owner, new LVolume(LSP_ROOT, "Root")); struct passwd *pw = getpwuid(getuid()); if (pw) Insert(Owner, new LVolume(LSP_HOME, "Home")); LSystemPath p[] = { LSP_USER_DOCUMENTS, LSP_USER_MUSIC, LSP_USER_VIDEO, LSP_USER_DOWNLOADS, LSP_USER_PICTURES }; for (int i=0; id->Path = Path; v->d->Name = *Parts.rbegin(); v->d->Type = VT_FOLDER; Insert(Owner, v); } } } return ChildVol; } LVolume *Next() { if (SysPath == LSP_DESKTOP && !NextVol) { NextVol = new LVolume(LSP_MOUNT_POINT, "Mounts"); // Get mount list // this is just a hack at this stage to establish some base // functionality. I would appreciate someone telling me how // to do this properly. Till then... LFile f; if (f.Open("/etc/fstab", O_READ)) { auto Buf= f.Read(); f.Close(); auto Lines = Buf.SplitDelimit("\r\n"); for (auto ln: Lines) { auto M = ln.Strip().SplitDelimit(" \t"); if (M[0](0) != '#' && M.Length() > 2) { auto &Device = M[0]; auto &Mount = M[1]; auto &FileSys = M[2]; if ( (Device.Find("/dev/") == 0 || Mount.Find("/mnt/") == 0) && Device.Lower().Find("/by-uuid/") < 0 && Mount.Length() > 1 && !FileSys.Equals("swap") ) { auto v = new LVolume(0); if (v) { char *MountName = strrchr(Mount, '/'); v->d->Name = (MountName ? MountName + 1 : Mount.Get()); v->d->Path = Mount; v->d->Type = VT_HARDDISK; struct statvfs s = {0}; int r = statvfs(Mount, &s); if (r) { LgiTrace("%s:%i - statvfs(%s) failed.\n", _FL, Mount); } else { v->d->Size = (uint64_t) s.f_blocks * s.f_frsize; v->d->Free = (uint64_t) s.f_bfree * s.f_frsize; } char *Device = M[0]; if (stristr(Device, "fd")) v->d->Type = VT_FLOPPY; else if (stristr(Device, "cdrom")) v->d->Type = VT_CDROM; Insert(NextVol, v); } } } } } } return NextVol; } }; LVolume::LVolume(const char *Path = NULL) { d = new LVolumePriv(this, Path); } LVolume::LVolume(LSystemPath SysPath, const char *Name) { d = new LVolumePriv(this, SysPath, Name); } LVolume::~LVolume() { DeleteObj(d); } const char *LVolume::Name() const { return d->Name; } const char *LVolume::Path() const { return d->Path; } int LVolume::Type() const { return d->Type; } int LVolume::Flags() const { return d->Flags; } uint64 LVolume::Size() const { return d->Size; } uint64 LVolume::Free() const { return d->Free; } LSurface *LVolume::Icon() const { return NULL; } bool LVolume::IsMounted() const { return true; } bool LVolume::SetMounted(bool Mount) { return Mount; } LVolume *LVolume::First() { return d->First(); } LVolume *LVolume::Next() { return d->Next(); } void LVolume::Insert(LAutoPtr v) { d->Insert(this, v.Release()); } LDirectory *LVolume::GetContents() { LDirectory *Dir = 0; if (d->Path) { Dir = new LDirectory; if (Dir && !Dir->First(d->Path)) DeleteObj(Dir); } return Dir; } /////////////////////////////////////////////////////////////////////////////// LFileSystem *LFileSystem::Instance = 0; LFileSystem::LFileSystem() { Instance = this; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); } void LFileSystem::OnDeviceChange(char *Reserved) { } LVolume *LFileSystem::GetRootVolume() { if (!Root) Root = new LVolume(LSP_DESKTOP, "Desktop"); return Root; } bool LFileSystem::Copy(const char *From, const char *To, LError *Status, CopyFileCallback Callback, void *Token) { LArray Buf; if (Status) *Status = 0; if (Buf.Length(2 << 20)) { LFile In, Out; if (!In.Open(From, O_READ)) { if (Status) *Status = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (Status) *Status = Out.GetError(); return false; } int64 Size = In.GetSize(), Done = 0; for (int64 i=0; i 0) { int w = Out.Write(&Buf[0], r); if (w <= 0) break; r -= w; Done += w; if (Callback) Callback(Token, Done, Size); } if (r > 0) break; } return Done == Size; } return false; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { char p[MAX_PATH_LEN]; if (LGetSystemPath(LSP_TRASH, p, sizeof(p))) { for (int i=0; i f; f.Add(FileName); return Delete(f, 0, ToTrash); } return false; } bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentTree, LError *ErrorCode) { int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; if (CreateParentTree) { LFile::Path p(PathName); LString dir = DIR_STR; for (size_t i=1; i<=p.Length(); i++) { LFile::Path Par(dir + dir.Join(p.Slice(0, i))); if (!Par.Exists()) { const char *ParDir = Par; r = mkdir(ParDir, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; break; } LgiTrace("%s:%i - Created '%s'\n", _FL, Par.GetFull().Get()); } } if (p.Exists()) return true; } LgiTrace("%s:%i - mkdir('%s') failed with %i, errno=%i\n", _FL, PathName, r, errno); } return r == 0; } bool LFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { LDirectory *Dir = new LDirectory; if (Dir && Dir->First(PathName)) { do { char Str[256]; Dir->Path(Str, sizeof(Str)); if (Dir->IsDir()) { RemoveFolder(Str, Recurse); } else { Delete(Str, false); } } while (Dir->Next()); } DeleteObj(Dir); } return rmdir(PathName) == 0; } bool LFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (rename(OldName, NewName)) { printf("%s:%i - rename(%s,%s) error: %s(%i)\n", _FL, OldName, NewName, GetErrorName(errno), errno); return false; } return true; } /* bool Match(char *Name, char *Mask) { strupr(Name); strupr(Mask); while (*Name && *Mask) { if (*Mask == '*') { if (*Name == *(Mask+1)) { Mask++; } else { Name++; } } else if (*Mask == '?' || *Mask == *Name) { Mask++; Name++; } else { return false; } } while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; return (*Name == 0 && *Mask == 0); } */ short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int LeapYear(int year) { if (year & 3) { return 0; } if ((year % 100 == 0) && !(year % 400 == 0)) { return 0; } return 1; } ///////////////////////////////////////////////////////////////////////////////// bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const { time_t k = Time; struct tm *t = localtime(&k); if (t) { strftime(Str, SLen, "%I:%M:%S", t); return true; } return false; } bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const { time_t k = Time; struct tm *t = localtime(&k); if (t) { strftime(Str, SLen, "%d/%m/%y", t); return true; } return false; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// struct LDirectoryPriv { char BasePath[MAX_PATH_LEN]; DIR *Dir; struct dirent *De; struct stat Stat; LString Pattern; LDirectoryPriv() { Dir = 0; De = 0; BasePath[0] = 0; } bool Ignore() { return De && ( strcmp(De->d_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } int LDirectory::First(const char *Name, const char *Pattern) { Close(); if (!Name) return 0; strcpy_s(d->BasePath, sizeof(d->BasePath), Name); if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = Dir; } } } } else { d->Pattern = Pattern; } d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore() && !Next()) return false; } } return d->Dir != NULL && d->De != NULL; } int LDirectory::Next() { int Status = false; while (d->Dir && d->De) { if ((d->De = readdir(d->Dir))) { char s[MaxPathLen]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } return Status; } int LDirectory::Close() { if (d->Dir) { closedir(d->Dir); d->Dir = 0; } d->De = 0; return true; } const char *LDirectory::FullPath() { static char s[MAX_PATH_LEN]; #warning this should really be optimized, and thread safe... Path(s, sizeof(s)); return s; } LString LDirectory::FileName() const { return GetName(); } bool LDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LMakePath(s, BufLen, d->BasePath, GetName()); } int LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int LDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool LDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool LDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool LDirectory::IsDir() const { int a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } bool LDirectory::IsSymLink() const { int a = GetAttributes(); return S_ISLNK(a); } long LDirectory::GetAttributes() const { return d->Stat.st_mode; } char *LDirectory::GetName() const { return (d->De) ? d->De->d_name : NULL; } #define UNIX_TO_LGI(unixTs) \ (((uint64) unixTs + LDateTime::Offset1800) * LDateTime::Second64Bit) uint64 LDirectory::GetCreationTime() const { return UNIX_TO_LGI(d->Stat.st_ctime); } uint64 LDirectory::GetLastAccessTime() const { return UNIX_TO_LGI(d->Stat.st_atime); } uint64 LDirectory::GetLastWriteTime() const { return UNIX_TO_LGI(d->Stat.st_mtime); } uint64 LDirectory::GetSize() const { return (uint32_t)d->Stat.st_size; } int64 LDirectory::GetSizeOnDisk() { return (uint32_t)d->Stat.st_size; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// File /////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int ErrorCode; LFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; ErrorCode = 0; } ~LFilePrivate() { DeleteArray(Name); } }; LFile::LFile(const char *Path, int Mode) { d = new LFilePrivate; if (Path) Open(Path, Mode); } LFile::~LFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile LFile::Handle() { return d->hFile; } int LFile::GetError() { return d->ErrorCode; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } #define DEBUG_OPEN_FILES 0 #if DEBUG_OPEN_FILES LMutex Lck; LArray OpenFiles; #endif int LFile::Open(const char *File, int Mode) { int Status = false; if (File) { if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode #ifdef O_LARGEFILE | O_LARGEFILE #endif , S_IRUSR | S_IWUSR); if (ValidHandle(d->hFile)) { d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } Status = true; d->Status = true; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { if (!OpenFiles.HasItem(this)) OpenFiles.Add(this); Lck.Unlock(); } #endif } else { d->ErrorCode = errno; #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { for (unsigned i=0; iGetName()); Lck.Unlock(); } #endif printf("LFile::Open failed\n\topen(%s,%08.8x) = %i\n\terrno=%s (%s)\n", File, Mode, d->hFile, GetErrorName(d->ErrorCode), GetErrorDesc(d->ErrorCode)); } } return Status; } int LFile::Close() { if (ValidHandle(d->hFile)) { close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); #if DEBUG_OPEN_FILES if (Lck.Lock(_FL)) { OpenFiles.Delete(this); Lck.Unlock(); } #endif } return true; } +uint64_t LFile::GetModifiedTime() +{ + struct stat s; + stat(d->Name, &s); + return s.st_mtime; +} + +bool LFile::SetModifiedTime(uint64_t dt) +{ + struct stat s; + stat(d->Name, &s); + + struct timeval times[2] = {}; + times[0].tv_sec = s.st_atime; + times[1].tv_sec = dt; + + int r = utimes(d->Name, times); + + return r == 0; +} + void LFile::ChangeThread() { } #define CHUNK 0xFFF0 ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { int Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); } d->Status = Red == Size; return MAX(Red, 0); } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { int Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); } d->Status = Written == Size; return MAX(Written, 0); } #ifdef LINUX #define LINUX64 1 #endif int64 LFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 LFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(" LPrintfHex64 ") failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 LFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 LFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else int64 Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 LFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool LFile::Eof() { return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; Buf = &Buf[Size-1]; while (Size--) { r = read(d->hFile, Buf--, 1); i += r; } return i; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w = 0; Buf = &Buf[Size-1]; while (Size--) { w = write(d->hFile, Buf--, 1); i += w; } return i; } ssize_t LFile::ReadStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t r = 0; if (Buf && Size > 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } const char *LFile::GetName() { return d->Name; } #define GFileOp(type) LFile &LFile::operator >> (type &i) { d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) { d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); return *this; } GFileOps(); #undef GFileOp ////////////////////////////////////////////////////////////////////////////// bool LFile::Path::FixCase() { LString Prev; bool Changed = false; // printf("FixCase '%s'\n", GetFull().Get()); for (size_t i=0; i %s\n", part.Get(), name); Part = Name; Next = (Prev ? Prev : "") + Sep + Part; Changed = true; } } } Prev = Next; } return Changed; } diff --git a/src/linux/Lgi/Layout.cpp b/src/linux/Lgi/Layout.cpp --- a/src/linux/Lgi/Layout.cpp +++ b/src/linux/Lgi/Layout.cpp @@ -1,307 +1,307 @@ /* ** FILE: LLayout.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/1998 ** DESCRIPTION: Standard Views ** ** Copyright (C) 1998-2001, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Notifications.h" #define M_SET_SCROLL (M_USER + 0x2000) ////////////////////////////////////////////////////////////////////////////// LLayout::LLayout() { _PourLargest = false; VScroll = 0; HScroll = 0; } LLayout::~LLayout() { DeleteObj(HScroll); DeleteObj(VScroll); } LViewI *LLayout::FindControl(int Id) { if (VScroll && VScroll->GetId() == Id) return VScroll; if (HScroll && HScroll->GetId() == Id) return HScroll; return LView::FindControl(Id); } bool LLayout::GetPourLargest() { return _PourLargest; } void LLayout::SetPourLargest(bool i) { _PourLargest = i; } bool LLayout::Pour(LRegion &r) { if (_PourLargest) { LRect *Best = FindLargest(r); if (Best) { SetPos(*Best); return true; } } return false; } void LLayout::GetScrollPos(int64 &x, int64 &y) { x = HScroll ? HScroll->Value() : 0; y = VScroll ? VScroll->Value() : 0; } void LLayout::SetScrollPos(int64 x, int64 y) { if (HScroll) HScroll->Value(x); if (VScroll) VScroll->Value(y); } bool LLayout::Attach(LViewI *p) { bool Status = LView::Attach(p); AttachScrollBars(); return Status; } bool LLayout::Detach() { return LView::Detach(); } void LLayout::OnCreate() { AttachScrollBars(); OnPosChange(); } void LLayout::AttachScrollBars() { if (HScroll && !HScroll->IsAttached()) { // LRect r = HScroll->GetPos(); HScroll->Attach(this); HScroll->SetNotify(this); } if (VScroll && !VScroll->IsAttached()) { // LRect r = VScroll->GetPos(); VScroll->Attach(this); VScroll->SetNotify(this); } } bool LLayout::SetScrollBars(bool x, bool y) { #ifdef M_SET_SCROLL if (x ^ (HScroll != NULL) || y ^ (VScroll != NULL)) { if (_SetScroll.x != x || _SetScroll.y != y || !_SetScroll.SentMsg) { // This is to filter out masses of duplicate messages // before they have a chance to be processed. Esp important on // GTK systems where the message handling isn't very fast. _SetScroll.x = x; _SetScroll.y = y; _SetScroll.SentMsg = true; return PostEvent(M_SET_SCROLL, x, y); } // Duplicate... ignore... return true; } #else _SetScrollBars(x, y); #endif return true; } bool LLayout::_SetScrollBars(bool x, bool y) { static bool Processing = false; if (!Processing) { if ( ((HScroll!=0) ^ x) || ((VScroll!=0) ^ y) ) { Processing = true; int ScrollPx = LScrollBar::GetScrollSize(); if (x) { if (!HScroll) { HScroll = new LScrollBar(IDC_HSCROLL, 0, 0, 100, ScrollPx-1, "LLayout.HScroll"); if (HScroll) { HScroll->SetVertical(false); HScroll->Visible(false); } } } else { DeleteObj(HScroll); } if (y) { if (!VScroll) { VScroll = new LScrollBar(IDC_VSCROLL, 0, 0, ScrollPx-1, 100, "LLayout.VScroll"); if (VScroll) VScroll->Visible(false); } } else { DeleteObj(VScroll); } AttachScrollBars(); OnPosChange(); Invalidate(); Processing = false; } // else no change... } else { LgiTrace("%s:%i - _SetScrollBars is processing???!?!\n", _FL); } return true; } int LLayout::OnNotify(LViewI *c, LNotification n) { - return LView::OnNotify(c, n.Type); + return LView::OnNotify(c, n); } void LLayout::OnPosChange() { auto ScrollPx = LScrollBar::GetScrollSize(); LRect r = LView::GetClient(); LRect v(r.x2 - ScrollPx + 1, r.y1, r.x2, r.y2); LRect h(r.x1, r.y2 - ScrollPx + 1, r.x2, r.y2); if (VScroll && HScroll) { h.x2 = v.x1 - 1; v.y2 = h.y1 - 1; } if (VScroll) { VScroll->Visible(true); VScroll->SetPos(v, true); } if (HScroll) { HScroll->Visible(true); HScroll->SetPos(h, true); } } void LLayout::OnNcPaint(LSurface *pDC, LRect &r) { LView::OnNcPaint(pDC, r); if (VScroll && VScroll->Visible()) { r.x2 -= VScroll->X(); } if (HScroll && HScroll->Visible()) { r.y2 -= HScroll->Y(); } if (VScroll && VScroll->Visible() && HScroll && HScroll->Visible()) { // Draw square at the end of each scroll bar LRect s( VScroll->GetPos().x1, HScroll->GetPos().y1, VScroll->GetPos().x2, HScroll->GetPos().y2); pDC->Colour(L_MED); pDC->Rectangle(&s); } } LRect &LLayout::GetClient(bool ClientSpace) { static LRect r; r = LView::GetClient(ClientSpace); if (VScroll && VScroll->Visible()) { r.x2 = VScroll->GetPos().x1 - 1; } if (HScroll && HScroll->Visible()) { r.y2 = HScroll->GetPos().y1 - 1; } return r; } LMessage::Param LLayout::OnEvent(LMessage *Msg) { #ifdef M_SET_SCROLL if (Msg->Msg() == M_SET_SCROLL) { _SetScroll.SentMsg = false; _SetScrollBars(Msg->A(), Msg->B()); if (HScroll) HScroll->SendNotify(LNotifyScrollBarCreate); if (VScroll) VScroll->SendNotify(LNotifyScrollBarCreate); return 0; } #endif // if (VScroll) VScroll->OnEvent(Msg); // if (HScroll) HScroll->OnEvent(Msg); int Status = LView::OnEvent(Msg); if (Msg->Msg() == M_CHANGE && Status == -1 && GetParent()) { Status = GetParent()->OnEvent(Msg); } return Status; } diff --git a/src/linux/Lgi/Menu.cpp b/src/linux/Lgi/Menu.cpp --- a/src/linux/Lgi/Menu.cpp +++ b/src/linux/Lgi/Menu.cpp @@ -1,1791 +1,1577 @@ /*hdr ** FILE: LMenuGtk.cpp ** AUTHOR: Matthew Allen ** DATE: 19/4/2013 ** DESCRIPTION: Gtk menus ** ** Copyright (C) 2013, Matthew Allen ** fret@memecode.com */ #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" using namespace Gtk; #define DEBUG_MENUS 0 #if DEBUG_MENUS #define LOG(...) printf(__VA_ARGS__) #else #define LOG(...) #endif /////////////////////////////////////////////////////////////////////////////////////////////// static ::LArray Active; void SubMenuDestroy(LSubMenu *Item) { // LgiTrace("DestroySub %p %p\n", Item, Item->Info); Item->Info = NULL; } LSubMenu::LSubMenu(const char *name, bool Popup) { Menu = NULL; Parent = NULL; Info = NULL; _ContextMenuId = NULL; ActiveTs = 0; InLoop = false; if (name) { Info = GtkCast(Gtk::gtk_menu_new(), gtk_menu_shell, GtkMenuShell); // LgiTrace("CreateSub %p %p\n", this, Info); Gtk::g_signal_connect_data( Info, "destroy", (Gtk::GCallback) SubMenuDestroy, this, NULL, Gtk::G_CONNECT_SWAPPED); } Active.Add(this); } LSubMenu::~LSubMenu() { Active.Delete(this); while (Items.Length()) { LMenuItem *i = Items[0]; LAssert(i->Parent == this); DeleteObj(i); } Info.Destroy([](GtkMenuShell *s){ gtk_widget_destroy(GTK_WIDGET(s)); }); } // This will be run in the GUI thread.. gboolean LSubMenuClick(LMouse *m) { if (!m) return false; bool OverMenu = false, HasVisible = false; uint64 ActiveTs = 0; for (auto s: Active) { auto w = GTK_WIDGET(s->Handle()); auto vis = gtk_widget_is_visible(w); if (vis) { // auto src = gtk_widget_get_screen(w); // auto hnd = gdk_screen_get_root_window(src); auto hnd = gtk_widget_get_window(w); ActiveTs = MAX(s->ActiveTs, ActiveTs); HasVisible = true; GdkRectangle a; gdk_window_get_frame_extents(hnd, &a); /* LgiTrace("SubClk down=%i, pos=%i,%i sub=%i,%i-%i,%i\n", m->Down(), m->x, m->y, a.x, a.y, a.width, a.height); */ LRect rc = a; if (rc.Overlap(m->x, m->y)) OverMenu = true; } } if (m->Down() && !OverMenu && HasVisible && ActiveTs > 0) { uint64 Now = LCurrentTime(); uint64 Since = Now - ActiveTs; LgiTrace("%s:%i - LSubMenuClick, Since=" LPrintfInt64 "\n", _FL, Since); if (Since > 500) { for (auto s: Active) { auto w = GTK_WIDGET(s->Handle()); auto vis = gtk_widget_is_visible(w); if (vis && s->ActiveTs) { gtk_widget_hide(w); s->ActiveTs = 0; } } } } else LOG("LSubMenuClick Down=%i OverMenu=%i HasVIsible=%i ActiveTs=%i\n", m->Down(), OverMenu, HasVisible, ActiveTs > 0); DeleteObj(m); return false; } // This is not called in the GUI thread.. void LSubMenu::SysMouseClick(LMouse &m) { LMouse *ms = new LMouse; *ms = m; g_idle_add((GSourceFunc) LSubMenuClick, ms); } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where < 0 ? Items.Length() : Where, Shortcut); if (i) { i->Enabled(Enabled); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { gtk_menu_shell_append(Info, item); gtk_widget_show(item); } return i; } return 0; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *i = new LMenuItem; if (i) { i->Parent = this; i->Menu = Menu; i->Id(-2); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { gtk_menu_shell_append(Info, item); gtk_widget_show(item); } return i; } return 0; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { LBase::Name(Str); LMenuItem *i = new LMenuItem(Menu, this, Str, Where < 0 ? Items.Length() : Where, NULL); if (i) { i->Id(-1); Items.Insert(i, Where); GtkWidget *item = GTK_WIDGET(i->Handle()); LAssert(item); if (item) { if (Where < 0) gtk_menu_shell_append(Info, item); else gtk_menu_shell_insert(Info, item, Where); gtk_widget_show(item); } i->Child = new LSubMenu(Str); if (i->Child) { i->Child->Parent = i; i->Child->Menu = Menu; i->Child->Window = Window; GtkWidget *sub = GTK_WIDGET(i->Child->Handle()); LAssert(sub); if (i->Handle() && sub) { gtk_menu_item_set_submenu(i->Handle(), sub); gtk_widget_show(sub); } else LgiTrace("%s:%i Error: gtk_menu_item_set_submenu(%p,%p) failed\n", _FL, i->Handle(), sub); } return i->Child; } return 0; } void LSubMenu::ClearHandle() { Info = NULL; for (auto i: Items) { i->ClearHandle(); } } void LSubMenu::Empty() { LMenuItem *i; while ((i = Items[0])) { RemoveItem(i); DeleteObj(i); } } bool LSubMenu::RemoveItem(int i) { return RemoveItem(Items.ItemAt(i)); } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item)) { return Item->Remove(); } return false; } bool LSubMenu::IsContext(LMenuItem *Item) { if (!_ContextMenuId) { LMenuItem *i = GetParent(); LSubMenu *s = i ? i->GetParent() : NULL; if (s) // Walk up the chain of menus to find the top... return s->IsContext(Item); else // Ok we got to the top return false; } *_ContextMenuId = Item->Id(); Gtk::gtk_main_quit(); return true; } void GtkDeactivate(Gtk::GtkMenuShell *widget, LSubMenu *Sub) { Sub->OnActivate(false); } void LSubMenu::OnActivate(bool a) { if (!a) { if (_ContextMenuId) *_ContextMenuId = 0; if (InLoop) { Gtk::gtk_main_quit(); InLoop = false; } } } int LSubMenu::Float(LView *From, int x, int y, int Button) { #ifdef __GTK_H__ LWindow *Wnd = From->GetWindow(); if (!Wnd) return -1; Wnd->Capture(false); #else while (From && !From->Handle()) { From = dynamic_cast(From->GetParent()); } if (!From || !From->Handle()) { LOG("%s:%i - No menu handle\n", _FL); return -1; } if (From->IsCapturing()) From->Capture(false); #endif ActiveTs = LCurrentTime(); // This signal handles the case where the user cancels the menu by clicking away from it. Info.Connect("deactivate", (GCallback)GtkDeactivate, this); auto Widget = GTK_WIDGET(Info.obj); gtk_widget_show_all(Widget); int MenuId = 0; _ContextMenuId = &MenuId; LPoint Pos(x, y); gtk_menu_popup(GTK_MENU(Info.obj), NULL, NULL, NULL, NULL, Button, gtk_get_current_event_time()); InLoop = true; gtk_main(); _ContextMenuId = NULL; return MenuId; } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); // LOG("Find(%i) '%s' %i sub=%p\n", Id, i->Name(), i->Id(), Sub); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: bool StartUnderline; // Underline the first display string bool HasAccel; // The last display string should be right aligned List Strs; // Draw each alternate display string with underline // except the last in the case of HasAccel==true. - ::LString Shortcut; + LString Shortcut; LMenuItemPrivate() { HasAccel = false; StartUnderline = false; } ~LMenuItemPrivate() { Strs.DeleteObjects(); } void UpdateStrings(LFont *Font, char *n) { // Build up our display strings, Strs.DeleteObjects(); StartUnderline = false; char *Tab = strrchr(n, '\t'); if (Tab) { *Tab = 0; } char *Amp = 0, *a = n; while (a = strchr(a, '&')) { if (a[1] != '&') { Amp = a; break; } a++; } if (Amp) { // Before amp Strs.Insert(new LDisplayString(Font, n, Amp - n )); // Amp'd letter char *e = LSeekUtf8(++Amp, 1); Strs.Insert(new LDisplayString(Font, Amp, e - Amp )); // After Amp if (*e) { Strs.Insert(new LDisplayString(Font, e)); } } else { Strs.Insert(new LDisplayString(Font, n)); } if (HasAccel = (Tab != 0)) { Strs.Insert(new LDisplayString(Font, Tab + 1)); *Tab = '\t'; } else if (HasAccel = (Shortcut.Get() != 0)) { Strs.Insert(new LDisplayString(Font, Shortcut)); } } }; static LAutoString MenuItemParse(const char *s) { char buf[256], *out = buf; const char *in = s; while (in && *in && out < buf + sizeof(buf) - 1) { if (*in == '_') { *out++ = '_'; *out++ = '_'; } else if (*in != '&' || in[1] == '&') { *out++ = *in; } else { *out++ = '_'; } in++; } *out++ = 0; char *tab = strrchr(buf, '\t'); if (tab) { *tab++ = 0; } return LAutoString(NewStr(buf)); } static void MenuItemActivate(GtkMenuItem *MenuItem, LMenuItem *Item) { Item->OnGtkEvent("activate"); } static void MenuItemDestroy(GtkWidget *widget, LMenuItem *Item) { Item->OnGtkEvent("destroy"); } void LMenuItem::OnGtkEvent(::LString Event) { if (Event.Equals("activate")) { if (!Sub() && !InSetCheck) { LSubMenu *Parent = GetParent(); if (!Parent || !Parent->IsContext(this)) { auto m = GetMenu(); if (m) { // Attached to a menu, so send an event to the window LViewI *w = m->WindowHandle(); if (w) w->PostEvent(M_COMMAND, Id()); else LAssert(!"No window for menu to send to"); } else { // Could be just a popup menu... in which case do nothing. } } } } else if (Event.Equals("destroy")) { // LgiTrace("DestroyItem %p %p\n", this, Info); Info = NULL; } } LMenuItem::LMenuItem() { d = new LMenuItemPrivate(); Info = NULL; Child = NULL; Menu = NULL; Parent = NULL; InSetCheck = false; Handle(GTK_MENU_ITEM(gtk_separator_menu_item_new())); Position = -1; _Flags = 0; _Icon = -1; _Id = 0; } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *txt, int id, int Pos, const char *shortcut) { d = NULL; LAutoString Txt = MenuItemParse(txt); LBase::Name(txt); Info = NULL; Handle(GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(Txt))); Child = NULL; Menu = m; Parent = p; InSetCheck = false; Position = Pos; _Flags = 0; _Icon = -1; _Id = id; ShortCut = shortcut; ScanForAccel(); } void LMenuItem::Handle(GtkMenuItem *mi) { LAssert(Info == NULL); if (Info != mi) { Info = mi; Gtk::g_signal_connect(Info, "activate", (Gtk::GCallback) MenuItemActivate, this); Gtk::g_signal_connect(Info, "destroy", (Gtk::GCallback) MenuItemDestroy, this); } } LMenuItem::~LMenuItem() { Remove(); Info.Destroy([](GtkMenuItem *i){ gtk_widget_destroy(GTK_WIDGET(i)); }); DeleteObj(Child); DeleteObj(d); } #if GtkVer(2, 24) #include #endif #ifndef GDK_F1 enum { GDK_F1 = 0xffbe, GDK_F2, GDK_F3, GDK_F4, GDK_F5, GDK_F6, GDK_F7, GDK_F8, GDK_F9, GDK_F10, GDK_F11, GDK_F12 }; #endif // /usr/include/gtk-3.0/gdk/gdkkeysyms-compat.h Gtk::gint LgiKeyToGtkKey(int Key, const char *ShortCut) { #ifdef GDK_a LAssert(GDK_a == 'a'); LAssert(GDK_A == 'A'); #endif /* if (Key >= 'a' && Key <= 'z') return Key; */ if (Key >= 'A' && Key <= 'Z') return Key; if (Key >= '0' && Key <= '9') return Key; switch (Key) { #ifdef GDK_Delete case LK_DELETE: return GDK_Delete; #endif #ifdef GDK_Insert case LK_INSERT: return GDK_Insert; #endif #ifdef GDK_Home case LK_HOME: return GDK_Home; #endif #ifdef GDK_End case LK_END: return GDK_End; #endif #ifdef GDK_Page_Up case LK_PAGEUP: return GDK_Page_Up; #endif #ifdef GDK_Page_Down case LK_PAGEDOWN: return GDK_Page_Down; #endif #ifdef GDK_BackSpace case LK_BACKSPACE: return GDK_BackSpace; #endif #ifdef GDK_Escape case LK_ESCAPE: return GDK_Escape; #else #warning "GDK_Escape not defined." #endif case LK_UP: return GDK_Up; case LK_DOWN: return GDK_Down; case LK_LEFT: return GDK_Left; case LK_RIGHT: return GDK_Right; case LK_F1: return GDK_F1; case LK_F2: return GDK_F2; case LK_F3: return GDK_F3; case LK_F4: return GDK_F4; case LK_F5: return GDK_F5; case LK_F6: return GDK_F6; case LK_F7: return GDK_F7; case LK_F8: return GDK_F8; case LK_F9: return GDK_F9; case LK_F10: return GDK_F10; case LK_F11: return GDK_F11; case LK_F12: return GDK_F12; case ' ': #ifdef GDK_space return GDK_space; #else return ' '; #endif case ',': return GDK_comma; default: LgiTrace("Unhandled menu accelerator: 0x%x (%s)\n", Key, ShortCut); break; } return 0; } -#if 1 - bool LMenuItem::ScanForAccel() { if (!Menu) return false; const char *Sc = ShortCut; if (!Sc) { char *n = LBase::Name(); if (n) { char *Tab = strchr(n, '\t'); if (Tab) Sc = Tab + 1; } } if (!Sc) return false; - LToken Keys(Sc, "+-"); - if (Keys.Length() > 0) + auto Keys = LString(Sc).SplitDelimit("+-"); + if (Keys.Length() <= 0) + return false; + + int Flags = 0; + int Vkey = 0; + int Chr = 0; + + for (int i=0; iAccelGrp, - GtkKey, - mod, - Gtk::GTK_ACCEL_VISIBLE - ); - gtk_widget_show_all(w); - } - else - { - LOG("%s:%i - No gtk key for '%s'\n", _FL, Sc); - } - - auto Ident = Id(); - LAssert(Ident > 0); - Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) ); + Vkey = LK_RIGHT; + } + else if (stricmp(k, "Down") == 0) + { + Vkey = LK_DOWN; + } + else if (!stricmp(k, "Esc") || !stricmp(k, "Escape")) + { + Vkey = LK_ESCAPE; + } + else if (stricmp(k, "Space") == 0) + { + Chr = ' '; + } + else if (k[0] == 'F' && isdigit(k[1])) + { + int Idx = atoi(k+1); + Vkey = LK_F1 + Idx - 1; + } + else if (isalpha(k[0])) + { + Chr = toupper(k[0]); + } + else if (isdigit(k[0]) || strchr(",", k[0])) + { + Chr = k[0]; } else { - LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Sc); - return false; - } - } - - return true; -} - -#else - -bool LMenuItem::ScanForAccel() -{ - ::LString Accel; - - if (d->Shortcut) - { - Accel = d->Shortcut; - } - else - { - char *n = LBase::Name(); - if (n) - { - char *Tab = strchr(n, '\t'); - if (Tab) - Accel = Tab + 1; + LgiTrace("%s:%i - Unknown part '%s' in shortcut '%s'\n", _FL, k, Sc); } } - - if (!Accel) - return false; - - auto Keys = Accel.SplitDelimit("-+"); - if (Keys.Length() == 0) - return false; - - int Flags = 0; - uchar Key = 0; - bool AccelDirty = false; - for (int i=0; i 0); + Gtk::gint GtkKey = LgiKeyToGtkKey(Vkey ? Vkey : Chr, Sc); + + LOG("scan(%s) vkey=%i, chr=%i, gtk=%i, id=%i\n", Sc, Vkey, Chr, GtkKey, Ident); + if (GtkKey) { GtkWidget *w = GtkCast(Info.obj, gtk_widget, GtkWidget); Gtk::GdkModifierType mod = (Gtk::GdkModifierType) ( (TestFlag(Flags, LGI_EF_CTRL) ? Gtk::GDK_CONTROL_MASK : 0) | (TestFlag(Flags, LGI_EF_SHIFT) ? Gtk::GDK_SHIFT_MASK : 0) | (TestFlag(Flags, LGI_EF_ALT) ? Gtk::GDK_MOD1_MASK : 0) | (TestFlag(Flags, LGI_EF_SYSTEM) ? Gtk::GDK_META_MASK : 0) ); - const char *Signal = "activate"; - gtk_widget_add_accelerator( w, - Signal, + "activate", Menu->AccelGrp, GtkKey, mod, Gtk::GTK_ACCEL_VISIBLE ); gtk_widget_show_all(w); } else { - LOG("%s:%i - No gtk key for '%s'\n", _FL, Accel.Get()); + LOG("%s:%i - No gtk key for '%s'\n", _FL, Sc); } - auto Ident = Id(); - LAssert(Ident > 0); - Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) ); - - return true; + Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Ident) ); } else { - LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Accel.Get()); + LOG("%s:%i - Accel scan failed, str='%s'\n", _FL, Sc); return false; } + + return true; } -#endif - LSubMenu *LMenuItem::GetParent() { return Parent; } void LMenuItem::ClearHandle() { Info = NULL; if (Child) Child->ClearHandle(); } bool LMenuItem::Remove() { if (!Parent) { return false; } if (Info) { Gtk::GtkWidget *w = GTK_WIDGET(Info.obj); if (Gtk::gtk_widget_get_parent(w)) { Gtk::GtkContainer *c = GtkCast(Parent->Info.obj, gtk_container, GtkContainer); Gtk::gtk_container_remove(c, w); } } LAssert(Parent->Items.HasItem(this)); Parent->Items.Delete(this); Parent = NULL; return true; } void LMenuItem::Id(int i) { _Id = i; } void LMenuItem::Separator(bool s) { if (s) { _Id = -2; } } struct MenuItemIndex { Gtk::GtkWidget *w; int Current; int Index; MenuItemIndex() { Index = -1; Current = 0; w = NULL; } }; static void FindMenuItemIndex(Gtk::GtkWidget *w, Gtk::gpointer data) { MenuItemIndex *d = (MenuItemIndex*)data; LOG("w=%p d->w=%p name=%s cur=%i d->Index=%i\n", w, d->w, G_OBJECT_TYPE_NAME(w), d->Current, d->Index); if (w == d->w) d->Index = d->Current; d->Current++; } int gtk_container_get_child_index(GtkContainer *c, GtkWidget *child) { MenuItemIndex Idx; if (c && child) { Idx.w = child; gtk_container_foreach(c, FindMenuItemIndex, &Idx); } return Idx.Index; } bool LMenuItem::Replace(Gtk::GtkWidget *newWid) { if (!newWid || !Info) { LgiTrace("%s:%i - Error: New=%p Old=%p\n", newWid, Info.obj); return false; } // Get widget GtkWidget *w = GTK_WIDGET(Info.obj); // Is is attach already? if (gtk_widget_get_parent(w)) { // Yes! GtkContainer *c = GTK_CONTAINER(Parent->Info.obj); if (c) { // Find index int PIdx = Parent->Items.IndexOf(this); LAssert(PIdx >= 0); // Remove old item gtk_container_remove(c, w); // Add new item gtk_menu_shell_insert(Parent->Info, newWid, PIdx); gtk_widget_show(newWid); } else LgiTrace("%s:%i - GTK_CONTAINER failed.\n", _FL); } else { // Delete it g_object_unref(Info); } Handle(GTK_MENU_ITEM(newWid)); return Info != NULL; } LImageList *LMenuItem::GetImageList() { if (GetMenu()) return GetMenu()->GetImageList(); // Search tree of parents for an image list... for (auto p = GetParent(); p; ) { auto lst = p->GetImageList(); if (lst) return lst; auto pmi = p->GetParent(); if (pmi) p = pmi->GetParent(); else break; } return NULL; } gboolean LgiMenuItemDraw(GtkWidget *widget, cairo_t *cr, LMenuItem *mi) { auto cls = GTK_WIDGET_GET_CLASS(widget); cls->draw(widget, cr); mi->PaintIcon(cr); return true; } void LMenuItem::PaintIcon(Gtk::cairo_t *cr) { if (_Icon < 0) return; auto il = GetImageList(); if (!il) return; auto wid = GTK_WIDGET(Info.obj); GtkAllocation a; gtk_widget_get_allocation(wid, &a); GdkRGBA bk = {0}; gtk_style_context_get_background_color( gtk_widget_get_style_context(wid), gtk_widget_get_state_flags(wid), &bk); LScreenDC Dc(cr, a.width, a.height); il->Draw(&Dc, 7, 5, _Icon, bk.alpha ? LColour(bk) : LColour::White); } void LMenuItem::Icon(int i) { _Icon = i; if (Info) Info.Connect("draw", (GCallback)LgiMenuItemDraw, this); } void LMenuItem::Checked(bool c) { if (c) SetFlag(_Flags, ODS_CHECKED); else ClearFlag(_Flags, ODS_CHECKED); if (Info) { InSetCheck = true; // Is the item a checked menu item? if (!GTK_IS_CHECK_MENU_ITEM(Info.obj) && c) { // Create a checkable menu item... LAutoString Txt = MenuItemParse(Name()); GtkWidget *w = gtk_check_menu_item_new_with_mnemonic(Txt); // Attach our signal gulong ret = g_signal_connect_data( w, "activate", (GCallback) MenuItemActivate, this, NULL, G_CONNECT_SWAPPED); // Replace the existing menu item with this new one if (w) Replace(w); else LAssert(!"No new widget."); } if (GTK_IS_CHECK_MENU_ITEM(Info.obj)) { // Now mark is checked GtkCheckMenuItem *chk = GTK_CHECK_MENU_ITEM(Info.obj); if (chk) { Gtk::gtk_check_menu_item_set_active(chk, c); } } InSetCheck = false; } } bool LMenuItem::Name(const char *n) { bool Status = LBase::Name(n); #if GtkVer(2, 16) LAssert(Info); LAutoString Txt = MenuItemParse(n); ScanForAccel(); gtk_menu_item_set_label(Info, Txt); #else LgiTrace("Warning: can't set label after creation."); Status = false; #endif return Status; } void LMenuItem::Enabled(bool e) { if (e) ClearFlag(_Flags, ODS_DISABLED); else SetFlag(_Flags, ODS_DISABLED); if (Info) { gtk_widget_set_sensitive(GtkCast(Info.obj, gtk_widget, GtkWidget), e); } } void LMenuItem::Focus(bool f) { } void LMenuItem::Sub(LSubMenu *s) { Child = s; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return _Id; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return _Id == -2; } bool LMenuItem::Checked() { return TestFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Enabled() { return !TestFlag(_Flags, ODS_DISABLED); } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return 0; } LSubMenu *LMenuItem::Sub() { return Child; } int LMenuItem::Icon() { return _Icon; } /////////////////////////////////////////////////////////////////////////////////////////////// struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; class LMenuPrivate { public: }; LMenu::LMenu(const char *AppName) : LSubMenu("", false) { Menu = this; d = NULL; AccelGrp = Gtk::gtk_accel_group_new(); Info = GtkCast(Gtk::gtk_menu_bar_new(), gtk_menu_shell, GtkMenuShell); } LMenu::~LMenu() { Accel.DeleteObjects(); } LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); if (MenuFont.f) { // MenuFont.f->CodePage(LSysFont->CodePage()); } else { LOG("LMenu::GetFont Couldn't create menu font.\n"); } } else { LOG("LMenu::GetFont Couldn't get menu typeface.\n"); } if (!MenuFont.f) { MenuFont.f = new LFont; if (MenuFont.f) { *MenuFont.f = *LSysFont; } } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { if (!p) { LAssert(0); return false; } LWindow *Wnd = p->GetWindow(); if (!Wnd) { LAssert(0); return false; } Window = Wnd; if (Wnd->_VBox) { LAssert(!"Already has a menu"); return false; } Gtk::GtkWidget *menubar = GTK_WIDGET(Info.obj); Wnd->_VBox = Gtk::gtk_box_new(Gtk::GTK_ORIENTATION_VERTICAL, 0); Gtk::GtkBox *vbox = GTK_BOX(Wnd->_VBox); Gtk::GtkContainer *wndcontainer = GTK_CONTAINER(Wnd->Wnd); g_object_ref(Wnd->_Root); gtk_container_remove(wndcontainer, Wnd->_Root); gtk_box_pack_start(vbox, menubar, false, false, 0); gtk_box_pack_end(vbox, Wnd->_Root, true, true, 0); gtk_container_add(wndcontainer, Wnd->_VBox); gtk_widget_show_all(GTK_WIDGET(Wnd->Wnd)); g_object_unref(Wnd->_Root); gtk_window_add_accel_group(Wnd->Wnd, AccelGrp); #if 0 gtk_widget_queue_resize(GTK_WIDGET(vbox)); #else GdkRectangle allocation = Wnd->GetClient(); g_signal_emit_by_name(G_OBJECT(vbox), "size-allocate", GTK_WIDGET(vbox), &allocation, NULL, NULL); #endif return true; } bool LMenu::Detach() { bool Status = false; return Status; } bool LMenu::SetPrefAndAboutItems(int a, int b) { return false; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down()) { for (auto a: Accel) { if (a->Match(k)) { LAssert(a->GetId() > 0); Window->OnCommand(a->GetId(), 0, 0); return true; } } if (k.Alt() && !dynamic_cast(v) && !dynamic_cast(v)) { bool Hide = false; for (auto s: Items) { if (!s->Separator()) { if (Hide) { // s->Info->HideSub(); } else { char *n = s->Name(); if (ValidStr(n)) { char *Amp = strchr(n, '&'); if (Amp) { while (Amp && Amp[1] == '&') Amp = strchr(Amp + 2, '&'); char16 Accel = tolower(Amp[1]); char16 Press = tolower(k.c16); if (Accel == Press) Hide = true; } } if (Hide) { // s->Info->ShowSub(); } else { // s->Info->HideSub(); } } } } if (Hide) { return true; } } } return false; } //////////////////////////////////////////////////////////////////////////// -LAccelerator::LAccelerator(int flags, int key, int id) +LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; - Key = key; + Vkey = vkey; + Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { - int Press = (uint) k.vkey; - if (k.vkey == LK_RSHIFT || k.vkey == LK_LSHIFT || k.vkey == LK_RCTRL || k.vkey == LK_LCTRL || k.vkey == LK_RALT || k.vkey == LK_RALT) { return false; } #if 1 - LOG("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s%s\n", - Press, - Press>=' '?Press:'.', + LOG("LAccelerator::Match %i(%i)%s%s%s = %i(%i)%s%s%s%s\n", + + k.vkey, + k.c16, k.Ctrl()?" ctrl":"", k.Alt()?" alt":"", k.Shift()?" shift":"", - Key, - Key>=' '?Key:'.', + + Vkey, + Chr, TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"", TestFlag(Flags, LGI_EF_ALT)?" alt":"", TestFlag(Flags, LGI_EF_SHIFT)?" shift":"", TestFlag(Flags, LGI_EF_SYSTEM)?" system":"" ); #endif - if (toupper(Press) == (uint)Key) + if + ( + (Chr != 0 && toupper(k.c16) == toupper(Chr)) + || + (Vkey != 0 && k.vkey == Vkey) + ) { + LOG("...key match.\n"); if ( ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0) && ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0) && ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0) && ((TestFlag(Flags, LGI_EF_SYSTEM) ^ k.System()) == 0) ) { + LOG("...mod match: Id=%i\n", Id); return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { Flags = GWF_VISIBLE; Id = 0; ToolButton = 0; MenuItem = 0; TipHelp = 0; PrevValue = false; } LCommand::~LCommand() { DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; } diff --git a/src/linux/Lgi/Thread.cpp b/src/linux/Lgi/Thread.cpp --- a/src/linux/Lgi/Thread.cpp +++ b/src/linux/Lgi/Thread.cpp @@ -1,139 +1,142 @@ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/EventTargetThread.h" OsThreadId GetCurrentThreadId() { #ifdef SYS_gettid return syscall(SYS_gettid); #else LAssert(0); return 0; #endif } //////////////////////////////////////////////////////////////////////////// void *ThreadEntryPoint(void *i) { if (i) { LThread *Thread = (LThread*) i; Thread->ThreadId = GetCurrentThreadId(); // Make sure we have finished executing the setup while (Thread->State == LThread::THREAD_INIT) { LSleep(1); } pthread_detach(Thread->hThread); LString Nm = Thread->Name; if (Nm) pthread_setname_np(pthread_self(), Nm); // Do thread's work Thread->OnBeforeMain(); Thread->ReturnValue = Thread->Main(); Thread->OnAfterMain(); // Shutdown... Thread->State = LThread::THREAD_EXITED; bool DelayDelete = false; if (Thread->ViewHandle >= 0) { // If DeleteOnExit is set AND ViewHandle then the LView::OnEvent handle will // process the delete... don't do it here. DelayDelete = PostThreadEvent(Thread->ViewHandle, M_THREAD_COMPLETED, (LMessage::Param)Thread); // However if PostThreadEvent fails... do honour DeleteOnExit. } if (Thread->DeleteOnExit) { DeleteObj(Thread); } pthread_exit(0); } return 0; } const OsThread LThread::InvalidHandle = NULL; const OsThreadId LThread::InvalidId = 0; LThread::LThread(const char *ThreadName, int viewHandle) { Name = ThreadName; ViewHandle = viewHandle; ThreadId = InvalidId; State = LThread::THREAD_INIT; ReturnValue = -1; hThread = InvalidHandle; DeleteOnExit = false; } LThread::~LThread() { if (!IsExited()) { Terminate(); } } int LThread::ExitCode() { return ReturnValue; } bool LThread::IsExited() { return State == LThread::THREAD_EXITED; } void LThread::Run() { + Terminate(); + if (!hThread) { State = LThread::THREAD_INIT; static int Creates = 0; int e; if (!(e = pthread_create(&hThread, NULL, ThreadEntryPoint, (void*)this))) { Creates++; State = LThread::THREAD_RUNNING; } else { const char *Err = "(unknown)"; switch (e) { case EAGAIN: Err = "EAGAIN"; break; case EINVAL: Err = "EINVAL"; break; case EPERM: Err = "EPERM"; break; case ENOMEM: Err = "ENOMEM"; break; } printf("%s,%i - pthread_create failed with the error %i (%s) (After %i creates)\n", __FILE__, __LINE__, e, Err, Creates); State = LThread::THREAD_EXITED; } } } void LThread::Terminate() { if (hThread && pthread_cancel(hThread) == 0) { State = LThread::THREAD_EXITED; + hThread = NULL; } } int LThread::Main() { return 0; } diff --git a/src/linux/Lgi/View.cpp b/src/linux/Lgi/View.cpp --- a/src/linux/Lgi/View.cpp +++ b/src/linux/Lgi/View.cpp @@ -1,1103 +1,1119 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Linux LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Edit.h" #include "lgi/common/Popup.h" #include "lgi/common/Css.h" #include "lgi/common/EventTargetThread.h" #include "ViewPriv.h" using namespace Gtk; #include "LgiWidget.h" #define DEBUG_MOUSE_EVENTS 0 #if 0 #define DEBUG_INVALIDATE(...) printf(__VA_ARGS__) #else #define DEBUG_INVALIDATE(...) #endif #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 #if GtkVer(2, 14) #else #define gtk_widget_get_window(widget) ((widget)->window) #endif struct CursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32_t CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; //////////////////////////////////////////////////////////////////////////// void _lgi_yield() { LAppInst->Yield(); } bool LgiIsKeyDown(int Key) { LAssert(0); return false; } LKey::LKey(int vkey, uint32_t flags) { } /////////////////////////////////////////////////////////////////////////////////////////////////// LCaptureThread::LCaptureThread(LView *v) : LThread("CaptureThread") { view = v->AddDispatch(); DeleteOnExit = true; Run(); } LCaptureThread::~LCaptureThread() { Cancel(); // Don't wait.. the thread will exit and delete itself asnyronously. } int LCaptureThread::Main() { while (!IsCancelled()) { LSleep(EventMs); if (!IsCancelled()) PostThreadEvent(view, M_CAPTURE_PULSE); } return 0; } //////////////////////////////////////////////////////////////////////////////////////////////////// LViewPrivate::LViewPrivate(LView *view) : View(view) { PrevMouse.x = PrevMouse.y = -1; } LViewPrivate::~LViewPrivate() { LAssert(PulseThread == 0); if (Font && FontOwnType == GV_FontOwned) DeleteObj(Font); } void LView::OnGtkRealize() { if (!d->GotOnCreate) { d->GotOnCreate = true; if (d->WantsFocus) { d->WantsFocus = false; if (GetWindow()) GetWindow()->SetFocus(this, LWindow::GainFocus); } OnCreate(); } for (auto c : Children) { auto gv = c->GetGView(); if (gv) gv->OnGtkRealize(); } } void LView::_Focus(bool f) { ThreadCheck(); if (f) SetFlag(WndFlags, GWF_FOCUS); else ClearFlag(WndFlags, GWF_FOCUS); OnFocus(f); Invalidate(); if (f) { auto w = GetWindow(); if (w && w->_Root) gtk_widget_grab_focus(w->_Root); else d->WantsFocus = f; } } void LView::_Delete() { ThreadCheck(); SetPulse(); // Remove static references to myself if (_Over == this) _Over = NULL; if (_Capturing == this) _Capturing = NULL; auto *Wnd = GetWindow(); if (Wnd && Wnd->GetFocus() == static_cast(this)) Wnd->SetFocus(this, LWindow::ViewDelete); if (LAppInst && LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; // Hierarchy LViewI *c; while ((c = Children[0])) { if (c->GetParent() != (LViewI*)this) { printf("%s:%i - ~LView error, %s not attached correctly: %p(%s) Parent: %p(%s)\n", _FL, c->GetClass(), c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(c); } DeleteObj(c); } Detach(); // Misc Pos.ZOff(-1, -1); } LView *&LView::PopupChild() { return d->Popup; } void LgiToGtkCursor(LViewI *v, LCursor c) { static LCursor CurrentCursor = LCUR_Normal; if (!v || c == CurrentCursor) return; CurrentCursor = c; GdkCursorType type = GDK_ARROW; switch (c) { // No cursor #ifdef GDK_BLANK_CURSOR case LCUR_Blank: type = GDK_BLANK_CURSOR; break; #endif /// Normal arrow case LCUR_Normal: type = GDK_ARROW; break; /// Upwards arrow case LCUR_UpArrow: type = GDK_SB_UP_ARROW; break; /// Downwards arrow case LCUR_DownArrow: type = GDK_SB_DOWN_ARROW; break; /// Left arrow case LCUR_LeftArrow: type = GDK_SB_LEFT_ARROW; break; /// Right arrow case LCUR_RightArrow: type = GDK_SB_RIGHT_ARROW; break; /// Crosshair case LCUR_Cross: type = GDK_CROSSHAIR; break; /// Hourglass/watch case LCUR_Wait: type = GDK_WATCH; break; /// Ibeam/text entry case LCUR_Ibeam: type = GDK_XTERM; break; /// Vertical resize (|) case LCUR_SizeVer: type = GDK_DOUBLE_ARROW; break; /// Horizontal resize (-) case LCUR_SizeHor: type = GDK_SB_H_DOUBLE_ARROW; break; /// Diagonal resize (/) case LCUR_SizeBDiag: type = GDK_BOTTOM_LEFT_CORNER; break; /// Diagonal resize (\) case LCUR_SizeFDiag: type = GDK_BOTTOM_RIGHT_CORNER; break; /// All directions resize case LCUR_SizeAll: type = GDK_FLEUR; break; /// A pointing hand case LCUR_PointingHand: type = GDK_HAND2; break; /// A slashed circle case LCUR_Forbidden: type = GDK_X_CURSOR; break; default: type = GDK_ARROW; break; /* case LCUR_SplitV: case LCUR_SplitH: case LCUR_DropCopy: case LCUR_DropMove: LAssert(0); break; */ } OsView h = NULL; auto *w = v->GetWindow(); if (w) h = GTK_WIDGET(w->WindowHandle()); LAssert(v->InThread()); auto wnd = gtk_widget_get_window(h); // LAssert(wnd); if (wnd) { if (type == GDK_ARROW) gdk_window_set_cursor(wnd, NULL); else { GdkCursor *cursor = gdk_cursor_new_for_display(gdk_display_get_default(), type); if (cursor) gdk_window_set_cursor(wnd, cursor); else gdk_window_set_cursor(wnd, NULL); } } } bool LView::_Mouse(LMouse &m, bool Move) { ThreadCheck(); #if 0 if (!Move) { m.Trace("_Mouse"); ::LArray _m; for (LViewI *i=this; i; i=i->GetParent()) { _m.Add(i); } for (int n=0; n<_m.Length(); n++) { LViewI *i=_m[_m.Length()-1-n]; char s[256]; ZeroObj(s); memset(s, ' ', (n+1)*2); LgiTrace("%s%s %s\n", s, i->GetClass(), i->GetPos().GetStr()); } } #endif #if DEBUG_MOUSE_EVENTS if (!Move) LgiTrace("%s:%i - _Mouse([%i,%i], %i) View=%p/%s\n", _FL, m.x, m.y, Move, _Over, _Over ? _Over->GetClass() : NULL); #endif if ( /* !_View || */ ( GetWindow() && !GetWindow()->HandleViewMouse(this, m) ) ) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - HandleViewMouse consumed event, cls=%s\n", _FL, GetClass()); #endif return false; } #if DEBUG_MOUSE_EVENTS // if (!Move) LgiTrace("%s:%i - _Capturing=%p/%s\n", _FL, _Capturing, _Capturing ? _Capturing->GetClass() : NULL); #endif if (Move) { auto *o = m.Target; if (_Over != o) { #if DEBUG_MOUSE_EVENTS // if (!o) WindowFromPoint(m.x, m.y, true); LgiTrace("%s:%i - _Over changing from %p/%s to %p/%s\n", _FL, _Over, _Over ? _Over->GetClass() : NULL, o, o ? o->GetClass() : NULL); #endif if (_Over) _Over->OnMouseExit(lgi_adjust_click(m, _Over)); _Over = o; if (_Over) _Over->OnMouseEnter(lgi_adjust_click(m, _Over)); } } LView *Target = NULL; if (_Capturing) Target = dynamic_cast(_Capturing); else Target = dynamic_cast(_Over ? _Over : this); if (!Target) return false; LRect Client = Target->LView::GetClient(false); m = lgi_adjust_click(m, Target, !Move); if (!Client.Valid() || Client.Overlap(m.x, m.y) || _Capturing) { LgiToGtkCursor(Target, Target->GetCursor(m.x, m.y)); if (Move) { Target->OnMouseMove(m); } else { #if 0 if (!Move) { char Msg[256]; sprintf(Msg, "_Mouse Target %s", Target->GetClass()); m.Trace(Msg); } #endif Target->OnMouseClick(m); } } else if (!Move) { #if DEBUG_MOUSE_EVENTS LgiTrace("%s:%i - Click outside %s %s %i,%i\n", _FL, Target->GetClass(), Client.GetStr(), m.x, m.y); #endif } return true; } const char *EventTypeToString(int i) { switch (i) { case GDK_DELETE: return "GDK_DELETE"; case GDK_DESTROY: return "GDK_DESTROY"; case GDK_EXPOSE: return "GDK_EXPOSE"; case GDK_MOTION_NOTIFY: return "GDK_MOTION_NOTIFY"; case GDK_BUTTON_PRESS: return "GDK_BUTTON_PRESS"; case GDK_2BUTTON_PRESS: return "GDK_2BUTTON_PRESS"; case GDK_3BUTTON_PRESS: return "GDK_3BUTTON_PRESS"; case GDK_BUTTON_RELEASE: return "GDK_BUTTON_RELEASE"; case GDK_KEY_PRESS: return "GDK_KEY_PRESS"; case GDK_KEY_RELEASE: return "GDK_KEY_RELEASE"; case GDK_ENTER_NOTIFY: return "GDK_ENTER_NOTIFY"; case GDK_LEAVE_NOTIFY: return "GDK_LEAVE_NOTIFY"; case GDK_FOCUS_CHANGE: return "GDK_FOCUS_CHANGE"; case GDK_CONFIGURE: return "GDK_CONFIGURE"; case GDK_MAP: return "GDK_MAP"; case GDK_UNMAP: return "GDK_UNMAP"; case GDK_PROPERTY_NOTIFY: return "GDK_PROPERTY_NOTIFY"; case GDK_SELECTION_CLEAR: return "GDK_SELECTION_CLEAR"; case GDK_SELECTION_REQUEST: return "GDK_SELECTION_REQUEST"; case GDK_SELECTION_NOTIFY: return "GDK_SELECTION_NOTIFY"; case GDK_PROXIMITY_IN: return "GDK_PROXIMITY_IN"; case GDK_PROXIMITY_OUT: return "GDK_PROXIMITY_OUT"; case GDK_DRAG_ENTER: return "GDK_DRAG_ENTER"; case GDK_DRAG_LEAVE: return "GDK_DRAG_LEAVE"; case GDK_DRAG_MOTION: return "GDK_DRAG_MOTION"; case GDK_DRAG_STATUS: return "GDK_DRAG_STATUS"; case GDK_DROP_START: return "GDK_DROP_START"; case GDK_DROP_FINISHED: return "GDK_DROP_FINISHED"; case GDK_CLIENT_EVENT: return "GDK_CLIENT_EVENT"; case GDK_VISIBILITY_NOTIFY: return "GDK_VISIBILITY_NOTIFY"; case GDK_SCROLL: return "GDK_SCROLL"; case GDK_WINDOW_STATE: return "GDK_WINDOW_STATE"; case GDK_SETTING: return "GDK_SETTING"; case GDK_OWNER_CHANGE: return "GDK_OWNER_CHANGE"; case GDK_GRAB_BROKEN: return "GDK_GRAB_BROKEN"; case GDK_DAMAGE: return "GDK_DAMAGE"; case GDK_TOUCH_BEGIN: return "GDK_TOUCH_BEGIN"; case GDK_TOUCH_UPDATE: return "GDK_TOUCH_UPDATE"; case GDK_TOUCH_END: return "GDK_TOUCH_END"; case GDK_TOUCH_CANCEL: return "GDK_TOUCH_CANCEL"; case GDK_TOUCHPAD_SWIPE: return "GDK_TOUCHPAD_SWIPE"; case GDK_TOUCHPAD_PINCH: return "GDK_TOUCHPAD_PINCH"; #ifdef GDK_PAD_BUTTON_PRESS case GDK_PAD_BUTTON_PRESS: return "GDK_PAD_BUTTON_PRESS"; #endif #ifdef GDK_PAD_BUTTON_RELEASE case GDK_PAD_BUTTON_RELEASE: return "GDK_PAD_BUTTON_RELEASE"; #endif #ifdef GDK_PAD_RING case GDK_PAD_RING: return "GDK_PAD_RING"; #endif #ifdef GDK_PAD_STRIP case GDK_PAD_STRIP: return "GDK_PAD_STRIP"; #endif #ifdef GDK_PAD_GROUP_MODE case GDK_PAD_GROUP_MODE: return "GDK_PAD_GROUP_MODE"; #endif } return "#error"; } gboolean GtkViewCallback(GtkWidget *widget, GdkEvent *event, LView *This) { #if 0 LgiTrace("GtkViewCallback, Event=%s, This=%p(%s\"%s\")\n", EventTypeToString(event->type), This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif if (event->type < 0 || (int)event->type > 1000) { printf("%s:%i - CORRUPT EVENT %i\n", _FL, event->type); return false; } return This->OnGtkEvent(widget, event); } gboolean LView::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { printf("LView::OnGtkEvent ?????\n"); return false; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; if (ClientSpace) { c.ZOff(Pos.X() - 1 - (Edge<<1), Pos.Y() - 1 - (Edge<<1)); } else { c.ZOff(Pos.X()-1, Pos.Y()-1); c.Inset(Edge, Edge); } return c; } void LView::Quit(bool DontDelete) { ThreadCheck(); if (DontDelete) { Visible(false); } else { delete this; } } bool LView::SetPos(LRect &p, bool Repaint) { if (Pos != p) { Pos = p; OnPosChange(); } return true; } LRect GtkGetPos(GtkWidget *w) { GtkAllocation a = {0}; gtk_widget_get_allocation(w, &a); return a; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { auto *ParWnd = GetWindow(); if (!ParWnd) return false; // Nothing we can do till we attach if (!InThread()) { DEBUG_INVALIDATE("%s::Invalidate out of thread\n", GetClass()); return PostEvent(M_INVALIDATE, NULL, (LMessage::Param)this); } LRect r; if (rc) { r = *rc; } else { if (Frame) r = Pos.ZeroTranslate(); else r = GetClient().ZeroTranslate(); } DEBUG_INVALIDATE("%s::Invalidate r=%s frame=%i\n", GetClass(), r.GetStr(), Frame); if (!Frame) r.Offset(_BorderSize, _BorderSize); LPoint Offset; WindowVirtualOffset(&Offset); r.Offset(Offset.x, Offset.y); DEBUG_INVALIDATE(" voffset=%i,%i = %s\n", Offset.x, Offset.y, r.GetStr()); if (!r.Valid()) { DEBUG_INVALIDATE(" error: invalid\n"); return false; } static bool Repainting = false; if (!Repainting) { Repainting = true; GtkWidget *w = GTK_WIDGET(ParWnd->WindowHandle()); if (w) { GdkWindow *h; if (gtk_widget_get_has_window(w) && (h = gtk_widget_get_window(w))) { GdkRectangle grc = r; DEBUG_INVALIDATE(" gdk_window_invalidate_rect %i,%i %i,%i\n", grc.x, grc.y, grc.width, grc.height); gdk_window_invalidate_rect(h, &grc, true); } else { DEBUG_INVALIDATE(" gtk_widget_queue_draw\n"); gtk_widget_queue_draw(w); } } Repainting = false; } else { DEBUG_INVALIDATE(" error: repainting\n"); } return true; } void LView::SetPulse(int Length) { ThreadCheck(); DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LMessage::Param LView::OnEvent(LMessage *Msg) { ThreadCheck(); int Id; switch (Id = Msg->Msg()) { + case M_SET_CTRL_NAME: + { + LAutoPtr s((LString*)Msg->B()); + SetCtrlName(Msg->A(), s ? *s : NULL); + break; + } + case M_SET_CTRL_ENABLE: + { + SetCtrlEnabled(Msg->A(), Msg->B()); + break; + } + case M_SET_CTRL_VISIBLE: + { + SetCtrlVisible(Msg->A(), Msg->B()); + break; + } case M_INVALIDATE: { if ((LView*)this == (LView*)Msg->B()) { LAutoPtr r((LRect*)Msg->A()); Invalidate(r); } break; } case M_PULSE: { OnPulse(); break; } case M_CAPTURE_PULSE: { auto wnd = GetWindow(); if (!wnd) { printf("%s:%i - No window.\n", _FL); break; } LMouse m; if (!wnd->GetMouse(m, true)) { printf("%s:%i - GetMouse failed.\n", _FL); break; } auto c = wnd->GetPos(); if (c.Overlap(m)) break; // Over the window, so we'll get normal events... if (!_Capturing) break; // Don't care now, there is no window capturing... if (d->PrevMouse.x < 0) { d->PrevMouse = m; // Initialize the previous mouse var. break; } int mask = LGI_EF_LEFT | LGI_EF_MIDDLE | LGI_EF_RIGHT; bool coordsChanged = m.x != d->PrevMouse.x || m.y != d->PrevMouse.y; bool buttonsChanged = (m.Flags & mask) != (d->PrevMouse.Flags & mask); if (!coordsChanged && !buttonsChanged) break; // Nothing changed since last poll.. int prevFlags = d->PrevMouse.Flags; d->PrevMouse = m; // Convert coords to local view... m.Target = _Capturing; #if 0 printf("%s:%i - Mouse(%i,%i) outside window(%s, %s).. %s\n", _FL, m.x, m.y, c.GetStr(), wnd->Name(), m.ToString().Get()); #endif m.ToView(); // Process events... if (coordsChanged) { // printf("Move event: %s\n", m.ToString().Get()); _Capturing->OnMouseMove(m); } /* This seems to happen anyway? Ok then... whatevs if (buttonsChanged) { m.Flags &= ~mask; // clear any existing m.Flags |= prevFlags & mask; m.Down(false); printf("Click event: %s, mask=%x flags=%x\n", m.ToString().Get(), mask, m.Flags); _Capturing->OnMouseClick(m); } */ break; } case M_CHANGE: { LViewI *Ctrl; if (GetViewById(Msg->A(), Ctrl)) return OnNotify(Ctrl, Msg->B()); break; } case M_COMMAND: { return OnCommand(Msg->A(), 0, (OsView) Msg->B()); } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } } return 0; } LPoint GtkGetOrigin(LWindow *w) { auto Hnd = w->WindowHandle(); if (Hnd) { auto Wnd = gtk_widget_get_window(GTK_WIDGET(Hnd)); if (Wnd) { GdkRectangle rect; gdk_window_get_frame_extents(Wnd, &rect); return LPoint(rect.x, rect.y); /* gint x = 0, y = 0; gdk_window_get_origin(Wnd, &x, &y); gdk_window_get_root_origin(Wnd, &x, &y); return LPoint(x, y); */ } else { LgiTrace("%s:%i - can't get Wnd for %s\n", _FL, G_OBJECT_TYPE_NAME(Hnd)); } } return LPoint(); } bool LView::PointToScreen(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p += Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x += x; p.y += y; return true; } bool LView::PointToView(LPoint &p) { ThreadCheck(); LPoint Offset; WindowVirtualOffset(&Offset); p -= Offset; auto w = GetWindow(); if (!w) return false; auto wnd = w->WindowHandle(); if (!wnd) return false; auto wid = GTK_WIDGET(wnd); auto hnd = gtk_widget_get_window(wid); gint x = 0, y = 0; gdk_window_get_origin(hnd, &x, &y); p.x -= x; p.y -= y; return true; } bool LView::GetMouse(LMouse &m, bool ScreenCoords) { bool Status = true; ThreadCheck(); auto *w = GetWindow(); GdkModifierType mask = (GdkModifierType)0; auto display = gdk_display_get_default(); auto deviceManager = gdk_display_get_device_manager(display); auto device = gdk_device_manager_get_client_pointer(deviceManager); gdouble axes[2] = {0}; if (gdk_device_get_axis_use(device, 0) != GDK_AXIS_X) gdk_device_set_axis_use(device, 0, GDK_AXIS_X); if (gdk_device_get_axis_use(device, 1) != GDK_AXIS_Y) gdk_device_set_axis_use(device, 1, GDK_AXIS_Y); if (ScreenCoords || !w) { gdk_device_get_state(device, gdk_get_default_root_window(), axes, &mask); m.x = (int)axes[0]; m.y = (int)axes[1]; } else if (auto widget = GTK_WIDGET(w->WindowHandle())) { auto wnd = gtk_widget_get_window(widget); gdk_device_get_state(device, wnd, axes, &mask); if (axes[0] == 0.0 || axes[0] > 10000) { // LgiTrace("%s:%i - gdk_device_get_state failed.\n", _FL); Status = false; } else { LPoint p; WindowVirtualOffset(&p); m.x = (int)axes[0] - p.x - _BorderSize; m.y = (int)axes[1] - p.y - _BorderSize; // printf("GetMs %g,%g %i,%i %i,%i device=%p wnd=%p\n", axes[0], axes[1], p.x, p.y, m.x, m.y, device, wnd); } } m.SetModifer(mask); m.Left((mask & GDK_BUTTON1_MASK) != 0); m.Middle((mask & GDK_BUTTON2_MASK) != 0); m.Right((mask & GDK_BUTTON3_MASK) != 0); m.Down(m.Left() || m.Middle() || m.Right()); m.ViewCoords = !ScreenCoords; return Status; } bool LView::IsAttached() { auto w = GetWindow(); if (!w) return false; w = dynamic_cast(this); if (!w) { auto p = GetParent(); return d->GotOnCreate && p && p->HasView(this); } return w->IsAttached(); } const char *LView::GetClass() { return "LView"; } bool LView::Attach(LViewI *parent) { ThreadCheck(); bool Status = false; LView *Parent = d->GetParent(); LAssert(Parent == NULL || Parent == parent); SetParent(parent); Parent = d->GetParent(); auto WndNull = _Window == NULL; _Window = Parent ? Parent->_Window : this; if (parent) { auto w = GetWindow(); if (w && TestFlag(WndFlags, GWF_FOCUS)) w->SetFocus(this, LWindow::GainFocus); Status = true; if (!Parent->HasView(this)) { OnAttach(); if (!d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); } if (_Window) OnGtkRealize(); } return Status; } bool LView::Detach() { ThreadCheck(); // Detach view if (_Window) { auto *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } LViewI *Par = GetParent(); if (Par) { // Events Par->DelView(this); Par->OnChildrenChanged(this, false); Par->Invalidate(&Pos); } d->Parent = 0; d->ParentI = 0; #if 0 // Windows is not doing a deep detach... so we shouldn't either? { int Count = Children.Length(); if (Count) { int Detached = 0; LViewI *c, *prev = NULL; while ((c = Children[0])) { LAssert(!prev || c != prev); if (c->GetParent()) c->Detach(); else Children.Delete(c); Detached++; prev = c; } LAssert(Count == Detached); } } #endif return true; } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } void LView::OnGtkDelete() { List::I it = Children.begin(); for (LViewI *c = *it; c; c = *++it) { LView *v = c->GetGView(); if (v) v->OnGtkDelete(); } } /////////////////////////////////////////////////////////////////// bool LgiIsMounted(char *Name) { return false; } bool LgiMountVolume(char *Name) { return false; } //////////////////////////////// uint32_t CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, }; diff --git a/src/linux/Lgi/Window.cpp b/src/linux/Lgi/Window.cpp --- a/src/linux/Lgi/Window.cpp +++ b/src/linux/Lgi/Window.cpp @@ -1,1854 +1,1879 @@ #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/Token.h" #include "lgi/common/Popup.h" #include "lgi/common/Panel.h" #include "lgi/common/Notifications.h" #include "lgi/common/Menu.h" #include "ViewPriv.h" using namespace Gtk; #undef Status #include "LgiWidget.h" #define DEBUG_SETFOCUS 0 #define DEBUG_HANDLEVIEWKEY 0 extern Gtk::GdkDragAction EffectToDragAction(int Effect); /////////////////////////////////////////////////////////////////////// class HookInfo { public: LWindowHookType Flags; LView *Target; }; enum LAttachState { LUnattached, LAttaching, LAttached, LDetaching, }; class LWindowPrivate { public: int Sx, Sy; bool Dynamic; LKey LastKey; ::LArray Hooks; bool SnapToEdge; ::LString Icon; LRect Decor; gulong DestroySig; LAutoPtr IconImg; LAttachState AttachState; // State GdkWindowState State; bool HadCreateEvent; // Focus stuff OsView FirstFocus; LViewI *Focus; bool Active; LWindowPrivate() { AttachState = LUnattached; DestroySig = 0; Decor.ZOff(-1, -1); FirstFocus = NULL; Focus = NULL; Active = false; State = (Gtk::GdkWindowState)0; HadCreateEvent = false; Sx = Sy = 0; Dynamic = true; SnapToEdge = false; LastKey.vkey = 0; LastKey.c16 = 0; LastKey.Data = 0; LastKey.IsChar = 0; } int GetHookIndex(LView *Target, bool Create = false) { for (int i=0; iTarget = Target; n->Flags = LNoEvents; return Hooks.Length() - 1; } } return -1; } }; /////////////////////////////////////////////////////////////////////// #define GWND_CREATE 0x0010000 LWindow::LWindow(GtkWidget *w) : LView(0) { d = new LWindowPrivate; _QuitOnClose = false; Menu = NULL; Wnd = GTK_WINDOW(w); if (Wnd) g_object_set_data(G_OBJECT(Wnd), "LViewI", (LViewI*)this); _Root = NULL; _MenuBar = NULL; _VBox = NULL; _Default = 0; _Window = this; WndFlags |= GWND_CREATE; ClearFlag(WndFlags, GWF_VISIBLE); _Lock = new ::LMutex("LWindow"); } LWindow::~LWindow() { d->AttachState = LDetaching; if (Wnd && d->DestroySig > 0) { // As we are already in the destructor, we don't want // GtkWindowDestroy to try and delete the object again. g_signal_handler_disconnect(Wnd, d->DestroySig); } if (LAppInst->AppWnd == this) LAppInst->AppWnd = NULL; if (_Root) { lgi_widget_detach(_Root); _Root = NULL; } if (Wnd) { gtk_widget_destroy(GTK_WIDGET(Wnd)); Wnd = NULL; } d->AttachState = LUnattached; DeleteObj(Menu); DeleteObj(d); DeleteObj(_Lock); } int LWindow::WaitThread() { return 0; // Nop for linux } bool LWindow::SetIcon(const char *FileName) { LString a; if (Wnd) { if (!LFileExists(FileName)) { if (a = LFindFile(FileName)) FileName = a; } if (!LFileExists(FileName)) { LgiTrace("%s:%i - SetIcon failed to find '%s'\n", _FL, FileName); return false; } else { #if defined(LINUX) LAppInst->SetApplicationIcon(FileName); #endif #if _MSC_VER GError *error = NULL; if (gtk_window_set_icon_from_file(Wnd, FileName, &error)) return true; #else // On windows this is giving a red for blue channel swap error... if (d->IconImg.Reset(GdcD->Load(a))) gtk_window_set_icon(Wnd, d->IconImg->CreatePixBuf()); #endif } } if (FileName != d->Icon.Get()) d->Icon = FileName; return d->Icon != NULL; } bool LWindow::GetSnapToEdge() { return d->SnapToEdge; } void LWindow::SetSnapToEdge(bool s) { d->SnapToEdge = s; } bool LWindow::IsActive() { return d->Active; } bool LWindow::SetActive() { if (!Wnd) return false; gtk_window_present(Wnd); return true; } bool LWindow::Visible() { return LView::Visible(); } void LWindow::Visible(bool i) { ThreadCheck(); auto w = GTK_WIDGET(Wnd); if (i) gtk_widget_show(w); else gtk_widget_hide(w); } bool LWindow::Obscured() { return d->State == GDK_WINDOW_STATE_WITHDRAWN || d->State == GDK_WINDOW_STATE_ICONIFIED; } void LWindow::_SetDynamic(bool i) { d->Dynamic = i; } void LWindow::_OnViewDelete() { if (d->Dynamic) { delete this; } } void LWindow::OnGtkRealize() { d->AttachState = LAttached; LView::OnGtkRealize(); } void LWindow::OnGtkDelete() { // Delete everything we own... // DeleteObj(Menu); #if 0 while (Children.Length()) { LViewI *c = Children.First(); c->Detach(); } #else for (unsigned i=0; iGetGView(); if (v) v->OnGtkDelete(); } #endif // These will be destroyed by GTK after returning from LWindowCallback Wnd = NULL; #ifndef __GTK_H__ _View = NULL; #endif } LRect *LWindow::GetDecorSize() { return d->Decor.x2 >= 0 ? &d->Decor : NULL; } void LWindow::SetDecor(bool Visible) { if (Wnd) gtk_window_set_decorated (Wnd, Visible); else LgiTrace("%s:%i - No window to set decor.\n", _FL); } LViewI *LWindow::WindowFromPoint(int x, int y, bool Debug) { if (!_Root) return NULL; auto rpos = GtkGetPos(_Root).ZeroTranslate(); if (!rpos.Overlap(x, y)) return NULL; return LView::WindowFromPoint(x - rpos.x1, y - rpos.y1, Debug); } bool LWindow::TranslateMouse(LMouse &m) { m.Target = WindowFromPoint(m.x, m.y, false); if (!m.Target) return false; LViewI *w = this; for (auto p = m.Target; p; p = p->GetParent()) { if (p == w) { auto ppos = GtkGetPos(GTK_WIDGET(WindowHandle())); m.x -= ppos.x1; m.y -= ppos.y1; break; } else { auto pos = p->GetPos(); m.x -= pos.x1; m.y -= pos.y1; } } return true; } gboolean LWindow::OnGtkEvent(GtkWidget *widget, GdkEvent *event) { if (!event) { printf("%s:%i - No event.\n", _FL); return FALSE; } #if 0 if (event->type != 28) LgiTrace("%s::OnGtkEvent(%i) name=%s\n", GetClass(), event->type, Name()); #endif switch (event->type) { case GDK_DELETE: { bool Close = OnRequestClose(false); if (Close) OnGtkDelete(); return !Close; } case GDK_DESTROY: { delete this; return true; } case GDK_KEY_PRESS: case GDK_KEY_RELEASE: { auto ModFlags = LAppInst->GetKeyModFlags(); auto e = &event->key; #define KEY(name) GDK_KEY_##name LKey k; k.Down(e->type == GDK_KEY_PRESS); k.c16 = k.vkey = e->keyval; k.Shift((e->state & ModFlags->Shift) != 0); k.Ctrl((e->state & ModFlags->Ctrl) != 0); k.Alt((e->state & ModFlags->Alt) != 0); k.System((e->state & ModFlags->System) != 0); #if 0//def _DEBUG if (k.vkey == GDK_KEY_Meta_L || k.vkey == GDK_KEY_Meta_R) break; #endif k.IsChar = !k.Ctrl() && !k.Alt() && !k.System() && (k.c16 >= ' ') && (k.c16 >> 8 != 0xff); if (e->keyval > 0xff && e->string != NULL) { // Convert string to unicode char auto *i = e->string; ptrdiff_t len = strlen(i); k.c16 = LgiUtf8To32((uint8_t *&) i, len); } switch (k.vkey) { case GDK_KEY_ISO_Left_Tab: case KEY(Tab): k.IsChar = true; k.c16 = k.vkey = LK_TAB; break; case KEY(Return): case KEY(KP_Enter): k.IsChar = true; k.c16 = k.vkey = LK_RETURN; break; case GDK_KEY_BackSpace: k.c16 = k.vkey = LK_BACKSPACE; k.IsChar = !k.Ctrl() && !k.Alt() && !k.System(); break; case KEY(Left): k.vkey = k.c16 = LK_LEFT; break; case KEY(Right): k.vkey = k.c16 = LK_RIGHT; break; case KEY(Up): k.vkey = k.c16 = LK_UP; break; case KEY(Down): k.vkey = k.c16 = LK_DOWN; break; case KEY(Page_Up): k.vkey = k.c16 = LK_PAGEUP; break; case KEY(Page_Down): k.vkey = k.c16 = LK_PAGEDOWN; break; case KEY(Home): k.vkey = k.c16 = LK_HOME; break; case KEY(End): k.vkey = k.c16 = LK_END; break; case KEY(Delete): k.vkey = k.c16 = LK_DELETE; break; #define KeyPadMap(gdksym, ch, is) \ case gdksym: k.c16 = ch; k.IsChar = is; break; KeyPadMap(KEY(KP_0), '0', true) KeyPadMap(KEY(KP_1), '1', true) KeyPadMap(KEY(KP_2), '2', true) KeyPadMap(KEY(KP_3), '3', true) KeyPadMap(KEY(KP_4), '4', true) KeyPadMap(KEY(KP_5), '5', true) KeyPadMap(KEY(KP_6), '6', true) KeyPadMap(KEY(KP_7), '7', true) KeyPadMap(KEY(KP_8), '8', true) KeyPadMap(KEY(KP_9), '9', true) KeyPadMap(KEY(KP_Space), ' ', true) KeyPadMap(KEY(KP_Tab), '\t', true) KeyPadMap(KEY(KP_F1), LK_F1, false) KeyPadMap(KEY(KP_F2), LK_F2, false) KeyPadMap(KEY(KP_F3), LK_F3, false) KeyPadMap(KEY(KP_F4), LK_F4, false) KeyPadMap(KEY(KP_Home), LK_HOME, false) KeyPadMap(KEY(KP_Left), LK_LEFT, false) KeyPadMap(KEY(KP_Up), LK_UP, false) KeyPadMap(KEY(KP_Right), LK_RIGHT, false) KeyPadMap(KEY(KP_Down), LK_DOWN, false) KeyPadMap(KEY(KP_Page_Up), LK_PAGEUP, false) KeyPadMap(KEY(KP_Page_Down), LK_PAGEDOWN, false) KeyPadMap(KEY(KP_End), LK_END, false) KeyPadMap(KEY(KP_Begin), LK_HOME, false) KeyPadMap(KEY(KP_Insert), LK_INSERT, false) KeyPadMap(KEY(KP_Delete), LK_DELETE, false) KeyPadMap(KEY(KP_Equal), '=', true) KeyPadMap(KEY(KP_Multiply), '*', true) KeyPadMap(KEY(KP_Add), '+', true) KeyPadMap(KEY(KP_Separator), '|', true) // is this right? KeyPadMap(KEY(KP_Subtract), '-', true) KeyPadMap(KEY(KP_Decimal), '.', true) KeyPadMap(KEY(KP_Divide), '/', true) } if (ModFlags->Debug) { :: LString Msg; Msg.Printf("e->state=%x %s", e->state, ModFlags->FlagsToString(e->state).Get()); k.Trace(Msg); } auto v = d->Focus ? d->Focus : this; if (!HandleViewKey(v->GetGView(), k)) { if (!k.Down()) return false; if (k.vkey == LK_TAB || k.vkey == KEY(ISO_Left_Tab)) { // Do tab between controls ::LArray a; BuildTabStops(this, a); int idx = a.IndexOf((LViewI*)v); if (idx >= 0) { idx += k.Shift() ? -1 : 1; int next_idx = idx == 0 ? a.Length() -1 : idx % a.Length(); LViewI *next = a[next_idx]; if (next) { // LgiTrace("Setting focus to %i of %i: %s, %s, %i\n", next_idx, a.Length(), next->GetClass(), next->GetPos().GetStr(), next->GetId()); next->Focus(true); } } } else if (k.System()) { if (ToLower(k.c16) == 'q') { auto AppWnd = LAppInst->AppWnd; auto Wnd = AppWnd ? AppWnd : this; if (Wnd->OnRequestClose(false)) { Wnd->Quit(); return true; } } } else return false; } break; } case GDK_CONFIGURE: { GdkEventConfigure *c = &event->configure; Pos.Set(c->x, c->y, c->x+c->width-1, c->y+c->height-1); // printf("%s::GDK_CONFIGURE %s\n", GetClass(), Pos.GetStr()); OnPosChange(); return FALSE; break; } case GDK_FOCUS_CHANGE: { d->Active = event->focus_change.in; #if 0 printf("%s/%s::GDK_FOCUS_CHANGE(%i)\n", GetClass(), Name(), event->focus_change.in); #endif break; } case GDK_WINDOW_STATE: { d->State = event->window_state.new_window_state; break; } case GDK_PROPERTY_NOTIFY: { gchar *Name = gdk_atom_name (event->property.atom); if (!Name) break; if (!_stricmp(Name, "_NET_FRAME_EXTENTS")) { // printf("PropChange: %i - %s\n", event->property.atom, Name); unsigned long *extents = NULL; if (gdk_property_get(event->property.window, gdk_atom_intern ("_NET_FRAME_EXTENTS", FALSE), gdk_atom_intern ("CARDINAL", FALSE), 0, sizeof (unsigned long) * 4, FALSE, NULL, NULL, NULL, (guchar **)&extents)) { d->Decor.Set(extents[0], extents[2], extents[1], extents[3]); g_free(extents); } else printf("%s:%i - Error: gdk_property_get failed.\n", _FL); } g_free(Name); break; } case GDK_UNMAP: { // LgiTrace("%s:%i - Unmap %s\n", _FL, GetClass()); break; } case GDK_VISIBILITY_NOTIFY: { // LgiTrace("%s:%i - Visible %s\n", _FL, GetClass()); break; } case GDK_DRAG_ENTER: { LgiTrace("%s:%i - GDK_DRAG_ENTER\n", _FL); break; } case GDK_DRAG_LEAVE: { LgiTrace("%s:%i - GDK_DRAG_LEAVE\n", _FL); break; } case GDK_DRAG_MOTION: { LgiTrace("%s:%i - GDK_DRAG_MOTION\n", _FL); break; } case GDK_DRAG_STATUS: { LgiTrace("%s:%i - GDK_DRAG_STATUS\n", _FL); break; } case GDK_DROP_START: { LgiTrace("%s:%i - GDK_DROP_START\n", _FL); break; } case GDK_DROP_FINISHED: { LgiTrace("%s:%i - GDK_DROP_FINISHED\n", _FL); break; } default: { printf("%s:%i - Unknown event %i\n", _FL, event->type); return false; } } return true; } static gboolean GtkWindowDestroy(GtkWidget *widget, LWindow *This) { delete This; return true; } static void GtkWindowRealize(GtkWidget *widget, LWindow *This) { #if 0 LgiTrace("GtkWindowRealize, This=%p(%s\"%s\")\n", This, (NativeInt)This > 0x1000 ? This->GetClass() : 0, (NativeInt)This > 0x1000 ? This->Name() : 0); #endif This->OnGtkRealize(); } static void GtkRootResize(GtkWidget *widget, GdkRectangle *alloc, LView *This) { LWindow *w = This->GetWindow(); if (w) w->PourAll(); } void LWindowUnrealize(GtkWidget *widget, LWindow *wnd) { // printf("%s:%i - LWindowUnrealize %s\n", _FL, wnd->GetClass()); } bool DndPointMap(LViewI *&v, LPoint &p, LDragDropTarget *&t, LWindow *Wnd, int x, int y) { LRect cli = Wnd->GetClient(); t = NULL; v = Wnd->WindowFromPoint(x - cli.x1, y - cli.y1, false); if (!v) { LgiTrace("%s:%i - @ %i,%i\n", _FL, x, y); return false; } v->WindowVirtualOffset(&p); p.x = x - p.x; p.y = y - p.y; for (LViewI *view = v; !t && view; view = view->GetParent()) t = view->DropTarget(); if (t) return true; LgiTrace("%s:%i - No target for %s\n", _FL, v->GetClass()); return false; } void LWindowDragBegin(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataDelete(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataGet(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } void LWindowDragDataReceived(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return; for (auto &d: t->Data) { auto type = gdk_atom_name(gtk_selection_data_get_data_type(data)); if (d.Format.Equals(type)) { gint length = 0; auto ptr = gtk_selection_data_get_data_with_length(data, &length); if (ptr) { d.Data[0].SetBinary(length, (void*)ptr, false); } break; } } } int GetAcceptFmts(::LString::Array &Formats, GdkDragContext *context, LDragDropTarget *t, LPoint &p) { int KeyState = 0; LDragFormats Fmts(true); int Flags = DROPEFFECT_NONE; GList *targets = gdk_drag_context_list_targets(context); Gtk::GList *i = targets; while (i) { auto a = gdk_atom_name((GdkAtom)i->data); if (a) Fmts.Supports(a); i = i->next; } Fmts.SetSource(false); Flags = t->WillAccept(Fmts, p, KeyState); auto Sup = Fmts.GetSupported(); for (auto &s: Sup) Formats.New() = s; return Flags; } gboolean LWindowDragDataDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { // Map the point to a view... LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; t->Data.Length(0); // Request the data... ::LArray Data; ::LString::Array Formats; int KeyState = 0; int Flags = GetAcceptFmts(Formats, context, t, p); for (auto f: Formats) { t->Data.New().Format = f; gtk_drag_get_data(widget, context, gdk_atom_intern(f, true), time); } // Wait for the data to arrive... uint64_t Start = LCurrentTime(); while (LCurrentTime()-Start < 2000) { int HasData = 0; for (auto d: t->Data) if (d.Data.Length() > 0) HasData++; if (HasData >= Formats.Length()) break; LYield(); } auto Result = t->OnDrop(t->Data, p, KeyState); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Result != DROPEFFECT_NONE; } void LWindowDragEnd(GtkWidget *widget, GdkDragContext *context, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); return false; } void LWindowDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, LWindow *Wnd) { LgiTrace("%s:%i - %s %s\n", _FL, Wnd->GetClass(), __func__); } gboolean LWindowDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, LWindow *Wnd) { LPoint p; LViewI *v; LDragDropTarget *t; if (!DndPointMap(v, p, t, Wnd, x, y)) return false; ::LString::Array Formats; int Flags = GetAcceptFmts(Formats, context, t, p); if (Flags != DROPEFFECT_NONE) gdk_drag_status(context, EffectToDragAction(Flags), time); return Flags != DROPEFFECT_NONE; } bool LWindow::Attach(LViewI *p) { bool Status = false; ThreadCheck(); // Setup default button... if (!_Default) _Default = FindControl(IDOK); // Create a window if needed.. if (!Wnd) Wnd = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); if (Wnd) { auto Widget = GTK_WIDGET(Wnd); LView *i = this; if (Pos.X() > 0 && Pos.Y() > 0) gtk_window_resize(Wnd, Pos.X(), Pos.Y()); gtk_window_move(Wnd, Pos.x1, Pos.y1); auto Obj = G_OBJECT(Wnd); g_object_set_data(Obj, "LViewI", (LViewI*)this); d->DestroySig = g_signal_connect(Obj, "destroy", G_CALLBACK(GtkWindowDestroy), this); g_signal_connect(Obj, "delete_event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "key-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-in-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "focus-out-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "window-state-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "property-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "configure-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "unmap-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "visibility-notify-event",G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "realize", G_CALLBACK(GtkWindowRealize), i); g_signal_connect(Obj, "unrealize", G_CALLBACK(LWindowUnrealize), i); g_signal_connect(Obj, "drag-begin", G_CALLBACK(LWindowDragBegin), i); g_signal_connect(Obj, "drag-data-delete", G_CALLBACK(LWindowDragDataDelete), i); g_signal_connect(Obj, "drag-data-get", G_CALLBACK(LWindowDragDataGet), i); g_signal_connect(Obj, "drag-data-received", G_CALLBACK(LWindowDragDataReceived), i); g_signal_connect(Obj, "drag-drop", G_CALLBACK(LWindowDragDataDrop), i); g_signal_connect(Obj, "drag-end", G_CALLBACK(LWindowDragEnd), i); g_signal_connect(Obj, "drag-failed", G_CALLBACK(LWindowDragFailed), i); g_signal_connect(Obj, "drag-leave", G_CALLBACK(LWindowDragLeave), i); g_signal_connect(Obj, "drag-motion", G_CALLBACK(LWindowDragMotion), i); #if 0 g_signal_connect(Obj, "button-press-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "button-release-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "motion-notify-event", G_CALLBACK(GtkViewCallback), i); g_signal_connect(Obj, "scroll-event", G_CALLBACK(GtkViewCallback), i); #endif gtk_widget_add_events( Widget, GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK | GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK); gtk_window_set_title(Wnd, LBase::Name()); d->AttachState = LAttaching; // g_action_map_add_action_entries (G_ACTION_MAP(Wnd), app_entries, G_N_ELEMENTS (app_entries), Wnd); if ((_Root = lgi_widget_new(this, true))) { g_signal_connect(_Root, "size-allocate", G_CALLBACK(GtkRootResize), i); GtkContainer *AttachPoint = NULL; if (GTK_IS_DIALOG(Wnd)) { auto content = gtk_dialog_get_content_area(GTK_DIALOG(Wnd)); if (!content) { LAssert(!"No content area"); return false; } AttachPoint = GTK_CONTAINER(content); } else { AttachPoint = GTK_CONTAINER(Wnd); } LAssert(AttachPoint != NULL); gtk_container_add(AttachPoint, _Root); // Check it actually worked... (would a return value kill you GTK? no it would not) auto p = gtk_widget_get_parent(_Root); if (!p) { LAssert(!"Add failed"); return false; } gtk_widget_show(_Root); } // This call sets up the GdkWindow handle gtk_widget_realize(Widget); // Do a rough layout of child windows PourAll(); // Add icon if (d->Icon) { SetIcon(d->Icon); d->Icon.Empty(); } + + auto p = GetParent(); + if (p) + { + auto pHnd = p->WindowHandle(); + if (!pHnd) + LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); + else + gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); + } Status = true; } return Status; } bool LWindow::OnRequestClose(bool OsShuttingDown) { if (GetQuitOnClose()) { LCloseApp(); } return LView::OnRequestClose(OsShuttingDown); } bool LWindow::HandleViewMouse(LView *v, LMouse &m) { if (m.Down() && !m.IsMove()) { bool InPopup = false; for (LViewI *p = v; p; p = p->GetParent()) { if (dynamic_cast(p)) { InPopup = true; break; } } if (!InPopup && LPopup::CurrentPopups.Length()) { for (int i=0; iVisible()) p->Visible(false); } } } for (int i=0; iHooks.Length(); i++) { if (d->Hooks[i].Flags & LMouseEvents) { if (!d->Hooks[i].Target->OnViewMouse(v, m)) { return false; } } } return true; } bool LWindow::HandleViewKey(LView *v, LKey &k) { bool Status = false; LViewI *Ctrl = 0; #if DEBUG_HANDLEVIEWKEY bool Debug = 1; // k.vkey == LK_RETURN; char SafePrint = k.c16 < ' ' ? ' ' : k.c16; // if (Debug) { LgiTrace("%s/%p::HandleViewKey=%i ischar=%i %s%s%s%s (d->Focus=%s/%p)\n", v->GetClass(), v, k.c16, k.IsChar, (char*)(k.Down()?" Down":" Up"), (char*)(k.Shift()?" Shift":""), (char*)(k.Alt()?" Alt":""), (char*)(k.Ctrl()?" Ctrl":""), d->Focus?d->Focus->GetClass():0, d->Focus); } #endif // Any window in a popup always gets the key... LViewI *p; for (p = v->GetParent(); p; p = p->GetParent()) { if (dynamic_cast(p)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tSending key to popup\n"); #endif return v->OnKey(k); } } // Give key to popups if (LAppInst && LAppInst->GetMouseHook() && LAppInst->GetMouseHook()->OnViewKey(v, k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMouseHook got key\n"); #endif goto AllDone; } // Allow any hooks to see the key... for (int i=0; iHooks.Length(); i++) { #if DEBUG_HANDLEVIEWKEY // if (Debug) LgiTrace("\tHook[%i]\n", i); #endif if (d->Hooks[i].Flags & LKeyEvents) { LView *Target = d->Hooks[i].Target; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i].Target=%p %s\n", i, Target, Target->GetClass()); #endif if (Target->OnViewKey(v, k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tHook[%i] ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", i, SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } } } // Give the key to the window... if (v->OnKey(k)) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tView ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif Status = true; goto AllDone; } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\t%s didn't eat '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", v->GetClass(), SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif // Window didn't want the key... switch (k.vkey) { case LK_RETURN: #ifdef LK_KEYPADENTER case LK_KEYPADENTER: #endif { Ctrl = _Default; break; } case LK_ESCAPE: { Ctrl = FindControl(IDCANCEL); break; } } // printf("Ctrl=%p\n", Ctrl); if (Ctrl) { if (Ctrl->Enabled()) { if (Ctrl->OnKey(k)) { Status = true; #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tDefault Button ate '%c'(%i) down=%i alt=%i ctrl=%i sh=%i\n", SafePrint, k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif goto AllDone; } // else printf("OnKey()=false\n"); } // else printf("Ctrl=disabled\n"); } #if DEBUG_HANDLEVIEWKEY else if (Debug) LgiTrace("\tNo default ctrl to handle key.\n"); #endif if (Menu) { Status = Menu->OnKey(v, k); if (Status) { #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tMenu ate '%c' down=%i alt=%i ctrl=%i sh=%i\n", k.c16, k.Down(), k.Alt(), k.Ctrl(), k.Shift()); #endif } } // Tab through controls if (k.vkey == LK_TAB && k.Down() && !k.IsChar) { LViewI *Wnd = GetNextTabStop(v, k.Shift()); #if DEBUG_HANDLEVIEWKEY if (Debug) LgiTrace("\tTab moving focus shift=%i Wnd=%p\n", k.Shift(), Wnd); #endif if (Wnd) Wnd->Focus(true); } // Control shortcut? if (k.Down() && k.Alt() && k.c16 > ' ') { ShortcutMap Map; BuildShortcuts(Map); LViewI *c = Map.Find(ToUpper(k.c16)); if (c) { c->OnNotify(c, LNotifyActivate); return true; } } AllDone: if (d) d->LastKey = k; return Status; } void LWindow::Raise() { if (Wnd) gtk_window_present(Wnd); } LWindowZoom LWindow::GetZoom() { switch (d->State) { case GDK_WINDOW_STATE_ICONIFIED: return LZoomMin; case GDK_WINDOW_STATE_MAXIMIZED: return LZoomMax; default: break; } return LZoomNormal; } void LWindow::SetZoom(LWindowZoom i) { if (!Wnd) { // LgiTrace("%s:%i - No window.\n", _FL); return; } ThreadCheck(); switch (i) { case LZoomMin: { gtk_window_iconify(Wnd); break; } case LZoomNormal: { gtk_window_deiconify(Wnd); gtk_window_unmaximize(Wnd); break; } case LZoomMax: { gtk_window_maximize(Wnd); break; } default: { LgiTrace("%s:%i - Error: unsupported zoom.\n", _FL); break; } } } LViewI *LWindow::GetDefault() { return _Default; } void LWindow::SetDefault(LViewI *v) { if (v && v->GetWindow() == this) { if (_Default != v) { LViewI *Old = _Default; _Default = v; if (Old) Old->Invalidate(); if (_Default) _Default->Invalidate(); } } else { _Default = 0; } } bool LWindow::Name(const char *n) { if (Wnd) { ThreadCheck(); gtk_window_set_title(Wnd, n); } return LBase::Name(n); } const char *LWindow::Name() { return LBase::Name(); } struct CallbackParams { LRect Menu; int Depth; CallbackParams() { Menu.ZOff(-1, -1); Depth = 0; } }; void ClientCallback(GtkWidget *w, CallbackParams *p) { const char *Name = gtk_widget_get_name(w); if (Name && !_stricmp(Name, "GtkMenuBar")) { GtkAllocation alloc = {0}; gtk_widget_get_allocation(w, &alloc); p->Menu.ZOff(alloc.width-1, alloc.height-1); // LgiTrace("GtkMenuBar = %s\n", p->Menu.GetStr()); } if (!p->Menu.Valid()) { p->Depth++; if (GTK_IS_CONTAINER(w)) gtk_container_forall(GTK_CONTAINER(w), (GtkCallback)ClientCallback, p); p->Depth--; } } LPoint LWindow::GetDpi() { return LScreenDpi(); } LPointF LWindow::GetDpiScale() { auto Dpi = GetDpi(); return LPointF((double)Dpi.x/96.0, (double)Dpi.y/96.0); } LRect &LWindow::GetClient(bool ClientSpace) { static LRect r; r = LView::GetClient(ClientSpace); if (Wnd) { CallbackParams p; gtk_container_forall(GTK_CONTAINER(Wnd), (GtkCallback)ClientCallback, &p); if (p.Menu.Valid()) { if (ClientSpace) r.y2 -= p.Menu.Y(); else r.y1 += p.Menu.Y(); } } return r; } bool LWindow::SerializeState(LDom *Store, const char *FieldName, bool Load) { if (!Store || !FieldName) return false; if (Load) { ::LVariant v; if (Store->GetValue(FieldName, v) && v.Str()) { LRect Position(0, 0, -1, -1); LWindowZoom State = LZoomNormal; // printf("SerializeState load %s\n", v.Str()); LToken t(v.Str(), ";"); for (int i=0; iName()); v = p[n]; ch += sprintf_s(s + ch, sizeof(s) - ch, "%s>%s", Buf, v->GetClass()); } return LAutoString(NewStr(s)); } #endif void LWindow::SetFocus(LViewI *ctrl, FocusType type) { #if DEBUG_SETFOCUS const char *TypeName = NULL; switch (type) { case GainFocus: TypeName = "Gain"; break; case LoseFocus: TypeName = "Lose"; break; case ViewDelete: TypeName = "Delete"; break; } #endif switch (type) { case GainFocus: { if (d->Focus == ctrl) { #if DEBUG_SETFOCUS LAutoString _ctrl = DescribeView(ctrl); LgiTrace("SetFocus(%s, %s) already has focus.\n", _ctrl.Get(), TypeName); #endif return; } if (d->Focus) { LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus LView: %s\n", _foc.Get()); #endif gv->_Focus(false); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _foc = DescribeView(d->Focus); LgiTrace(".....defocus view: %s (active=%i)\n", _foc.Get(), IsActive()); #endif d->Focus->OnFocus(false); d->Focus->Invalidate(); } } d->Focus = ctrl; if (d->Focus) { #if DEBUG_SETFOCUS static int Count = 0; #endif LView *gv = d->Focus->GetGView(); if (gv) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing LView\n", _set.Get(), TypeName, Count++); #endif gv->_Focus(true); } else if (IsActive()) { #if DEBUG_SETFOCUS LAutoString _set = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) %i focusing nonGView (active=%i)\n", _set.Get(), TypeName, Count++, IsActive()); #endif d->Focus->OnFocus(true); d->Focus->Invalidate(); } } break; } case LoseFocus: { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LAutoString _Focus = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) d->Focus=%s\n", _Ctrl.Get(), TypeName, _Focus.Get()); #endif if (ctrl == d->Focus) { d->Focus = NULL; } break; } case ViewDelete: { if (ctrl == d->Focus) { #if DEBUG_SETFOCUS LAutoString _Ctrl = DescribeView(d->Focus); LgiTrace("LWindow::SetFocus(%s, %s) on delete\n", _Ctrl.Get(), TypeName); #endif d->Focus = NULL; } break; } } } void LWindow::SetDragHandlers(bool On) { } +void LWindow::SetParent(LViewI *p) +{ + LView::SetParent(p); + if (p && Wnd) + { + auto pHnd = p->WindowHandle(); + if (!pHnd) + LgiTrace("%s:%i - SetParent() - no pHnd from %s.\n", _FL, p->GetClass()); + else if (!GTK_IS_WINDOW(Wnd)) + LgiTrace("%s:%i - SetParent() - GTK_IS_WINDOW failed.\n", _FL); + else + gtk_window_set_transient_for(GTK_WINDOW(Wnd), pHnd); + } +} + bool LWindow::IsAttached() { return d->AttachState == LAttaching || d->AttachState == LAttached; } void LWindow::OnTrayClick(LMouse &m) { if (m.Down() || m.IsContextMenu()) { LSubMenu RClick; OnTrayMenu(RClick); if (GetMouse(m, true)) { #if WINNATIVE SetForegroundWindow(Handle()); #endif int Result = RClick.Float(this, m.x, m.y); #if WINNATIVE PostMessage(Handle(), WM_NULL, 0, 0); #endif OnTrayMenuResult(Result); } } } void LWindow::Quit(bool DontDelete) { ThreadCheck(); if (Wnd) { d->AttachState = LDetaching; auto wnd = Wnd; Wnd = NULL; gtk_widget_destroy(GTK_WIDGET(wnd)); } } void LWindow::SetAlwaysOnTop(bool b) { } diff --git a/src/mac/cocoa/File.mm b/src/mac/cocoa/File.mm --- a/src/mac/cocoa/File.mm +++ b/src/mac/cocoa/File.mm @@ -1,1982 +1,2003 @@ /*hdr ** FILE: File.mm ** AUTHOR: Matthew Allen ** DATE: 8/10/2000 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 2000, Matthew Allen ** fret@memecode.com */ /****************************** Includes **********************************/ #include #include #include #include - #include #include #include +#include #include "lgi/common/File.h" #include "lgi/common/Containers.h" #include "lgi/common/Gdc2.h" #include "lgi/common/LgiCommon.h" #include "lgi/common/LgiString.h" #include "lgi/common/SubProcess.h" #if LGI_COCOA #include #endif /****************************** Defines ***********************************/ #define stat64 stat #define lseek64 lseek #define ftrucate64 ftruncate #define O_LARGEFILE 0 /****************************** Globals ***********************************/ LString LFile::Path::Sep(DIR_STR); bool LFile::Path::FixCase() { return false; } struct ErrorCodeType { const char *Name; int Code; const char *Desc; } ErrorCodes[] = { #if defined(WIN32) {"EPERM", 1, "Not owner"}, {"ENOENT", 2, "No such file"}, {"ESRCH", 3, "No such process"}, {"EINTR", 4, "Interrupted system"}, {"EIO", 5, "I/O error"}, {"ENXIO", 6, "No such device"}, {"E2BIG", 7, "Argument list too long"}, {"ENOEXEC", 8, "Exec format error"}, {"EBADF", 9, "Bad file number"}, {"ECHILD", 10, "No children"}, {"EAGAIN", 11, "No more processes"}, {"ENOMEM", 12, "Not enough core"}, {"EACCES", 13, "Permission denied"}, {"EFAULT", 14, "Bad address"}, {"ENOTBLK", 15, "Block device required"}, {"EBUSY", 16, "Mount device busy"}, {"EEXIST", 17, "File exists"}, {"EXDEV", 18, "Cross-device link"}, {"ENODEV", 19, "No such device"}, {"ENOTDIR", 20, "Not a directory"}, {"EISDIR", 21, "Is a directory"}, {"EINVAL", 22, "Invalid argument"}, {"ENFILE", 23, "File table overflow"}, {"EMFILE", 24, "Too many open file"}, {"ENOTTY", 25, "Not a typewriter"}, {"ETXTBSY", 26, "Text file busy"}, {"EFBIG", 27, "File too large"}, {"ENOSPC", 28, "No space left on"}, {"ESPIPE", 29, "Illegal seek"}, {"EROFS", 30, "Read-only file system"}, {"EMLINK", 31, "Too many links"}, {"EPIPE", 32, "Broken pipe"}, {"EWOULDBLOCK", 35, "Operation would block"}, {"EINPROGRESS", 36, "Operation now in progress"}, {"EALREADY", 37, "Operation already in progress"}, {"ENOTSOCK", 38, "Socket operation on"}, {"EDESTADDRREQ", 39, "Destination address required"}, {"EMSGSIZE", 40, "Message too long"}, {"EPROTOTYPE", 41, "Protocol wrong type"}, {"ENOPROTOOPT", 42, "Protocol not available"}, {"EPROTONOSUPPORT", 43, "Protocol not supported"}, {"ESOCKTNOSUPPORT", 44, "Socket type not supported"}, {"EOPNOTSUPP", 45, "Operation not supported"}, {"EPFNOSUPPORT", 46, "Protocol family not supported"}, {"EAFNOSUPPORT", 47, "Address family not supported"}, {"EADDRINUSE", 48, "Address already in use"}, {"EADDRNOTAVAIL", 49, "Can't assign requested address"}, {"ENETDOWN", 50, "Network is down"}, {"ENETUNREACH", 51, "Network is unreachable"}, {"ENETRESET", 52, "Network dropped connection"}, {"ECONNABORTED", 53, "Software caused connection"}, {"ECONNRESET", 54, "Connection reset by peer"}, {"ENOBUFS", 55, "No buffer space available"}, {"EISCONN", 56, "Socket is already connected"}, {"ENOTCONN", 57, "Socket is not connected" }, {"ESHUTDOWN", 58, "Can't send after shutdown"}, {"ETOOMANYREFS", 59, "Too many references"}, {"ETIMEDOUT", 60, "Connection timed out"}, {"ECONNREFUSED", 61, "Connection refused"}, {"ELOOP", 62, "Too many levels of nesting"}, {"ENAMETOOLONG", 63, "File name too long" }, {"EHOSTDOWN", 64, "Host is down"}, {"EHOSTUNREACH", 65, "No route to host"}, {"ENOTEMPTY", 66, "Directory not empty"}, {"EPROCLIM", 67, "Too many processes"}, {"EUSERS", 68, "Too many users"}, {"EDQUOT", 69, "Disc quota exceeded"}, {"ESTALE", 70, "Stale NFS file handle"}, {"EREMOTE", 71, "Too many levels of remote in the path"}, {"ENOSTR", 72, "Device is not a stream"}, {"ETIME", 73, "Timer expired"}, {"ENOSR", 74, "Out of streams resources"}, {"ENOMSG", 75, "No message"}, {"EBADMSG", 76, "Trying to read unreadable message"}, {"EIDRM", 77, "Identifier removed"}, {"EDEADLK", 78, "Deadlock condition"}, {"ENOLCK", 79, "No record locks available"}, {"ENONET", 80, "Machine is not on network"}, {"ERREMOTE", 81, "Object is remote"}, {"ENOLINK", 82, "The link has been severed"}, {"EADV", 83, "ADVERTISE error"}, {"ESRMNT", 84, "SRMOUNT error"}, {"ECOMM", 85, "Communication error"}, {"EPROTO", 86, "Protocol error"}, {"EMULTIHOP", 87, "Multihop attempted"}, {"EDOTDOT", 88, "Cross mount point"}, {"EREMCHG", 89, "Remote address change"}, #elif defined(MAC) {"EPERM", EPERM, "Operation not permitted"}, {"ENOENT", ENOENT, "No such file or directory"}, {"ESRCH", ESRCH, "No such process"}, {"EINTR", EINTR, "Interrupted system call"}, {"EIO", EIO, "I/O error"}, {"ENXIO", ENXIO, "No such device or address"}, {"E2BIG", E2BIG, "Argument list too long"}, {"ENOEXEC", ENOEXEC, "Exec format error"}, {"EBADF", EBADF, "Bad file number"}, {"ECHILD", ECHILD, "No child processes"}, {"EAGAIN", EAGAIN, "Try again"}, {"ENOMEM", ENOMEM, "Out of memory"}, {"EACCES", EACCES, "Permission denied"}, {"EFAULT", EFAULT, "Bad address"}, {"ENOTBLK", ENOTBLK, "Block device required"}, {"EBUSY", EBUSY, "Device or resource busy"}, {"EEXIST", EEXIST, "File exists"}, {"EXDEV", EXDEV, "Cross-device link"}, {"ENODEV", ENODEV, "No such device"}, {"ENOTDIR", ENOTDIR, "Not a directory"}, {"EISDIR", EISDIR, "Is a directory"}, {"EINVAL", EINVAL, "Invalid argument"}, {"ENFILE", ENFILE, "File table overflow"}, {"EMFILE", EMFILE, "Too many open files"}, {"ENOTTY", ENOTTY, "Not a typewriter"}, {"ETXTBSY", ETXTBSY, "Text file busy"}, {"EFBIG", EFBIG, "File too large"}, {"ENOSPC", ENOSPC, "No space left on device"}, {"ESPIPE", ESPIPE, "Illegal seek"}, {"EROFS", EROFS, "Read-only file system"}, {"EMLINK", EMLINK, "Too many links"}, {"EPIPE", EPIPE, "Broken pipe"}, {"EDOM", EDOM, "Math argument out of domain of func"}, {"ERANGE", ERANGE, "Math result not representable"}, {"EDEADLK", EDEADLK, "Resource deadlock would occur"}, {"ENAMETOOLONG", ENAMETOOLONG, "File name too long"}, {"ENOLCK", ENOLCK, "No record locks available"}, {"ENOSYS", ENOSYS, "Function not implemented"}, {"ENOTEMPTY", ENOTEMPTY, "Directory not empty"}, {"ELOOP", ELOOP, "Too many symbolic links encountered"}, {"EWOULDBLOCK", EWOULDBLOCK, "Operation would block"}, {"ENOMSG", ENOMSG, "No message of desired type"}, {"EIDRM", EIDRM, "Identifier removed"}, {"EREMOTE", EREMOTE, "Object is remote"}, {"EOVERFLOW", EOVERFLOW, "Value too large for defined data type"}, {"EILSEQ", EILSEQ, "Illegal byte sequence"}, {"EUSERS", EUSERS, "Too many users"}, {"ENOTSOCK", ENOTSOCK, "Socket operation on non-socket"}, {"EDESTADDRREQ", EDESTADDRREQ, "Destination address required"}, {"EMSGSIZE", EMSGSIZE, "Message too long"}, {"EPROTOTYPE", EPROTOTYPE, "Protocol wrong type for socket"}, {"ENOPROTOOPT", ENOPROTOOPT, "Protocol not available"}, {"EPROTONOSUPPORT", EPROTONOSUPPORT, "Protocol not supported"}, {"ESOCKTNOSUPPORT", ESOCKTNOSUPPORT, "Socket type not supported"}, {"EOPNOTSUPP", EOPNOTSUPP, "Operation not supported on transport endpoint"}, {"EPFNOSUPPORT", EPFNOSUPPORT, "Protocol family not supported"}, {"EAFNOSUPPORT", EAFNOSUPPORT, "Address family not supported by protocol"}, {"EADDRINUSE", EADDRINUSE, "Address already in use"}, {"EADDRNOTAVAIL", EADDRNOTAVAIL, "Cannot assign requested address"}, {"ENETDOWN", ENETDOWN, "Network is down"}, {"ENETUNREACH", ENETUNREACH, "Network is unreachable"}, {"ENETRESET", ENETRESET, "Network dropped connection because of reset"}, {"ECONNABORTED", ECONNABORTED, "Software caused connection abort"}, {"ECONNRESET", ECONNRESET, "Connection reset by peer"}, {"ENOBUFS", ENOBUFS, "No buffer space available"}, {"EISCONN", EISCONN, "Transport endpoint is already connected"}, {"ENOTCONN", ENOTCONN, "Transport endpoint is not connected"}, {"ESHUTDOWN", ESHUTDOWN, "Cannot send after transport endpoint shutdown"}, {"ETOOMANYREFS", ETOOMANYREFS, "Too many references: cannot splice"}, {"ETIMEDOUT", ETIMEDOUT, "Connection timed out"}, {"ECONNREFUSED", ECONNREFUSED, "Connection refused"}, {"EHOSTDOWN", EHOSTDOWN, "Host is down"}, {"EHOSTUNREACH", EHOSTUNREACH, "No route to host"}, {"EALREADY", EALREADY, "Operation already in progress"}, {"EINPROGRESS", EINPROGRESS, "Operation now in progress"}, {"ESTALE", ESTALE, "Stale NFS file handle"}, {"EDQUOT", EDQUOT, "Quota exceeded"}, #else #error impl me #endif {"NONE", 0, "No error"}, }; const char *GetErrorName(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Name; } } static char s[32]; sprintf(s, "Unknown(%i)", e); return s; } const char *GetErrorDesc(int e) { for (ErrorCodeType *c=ErrorCodes; c->Code; c++) { if (e == c->Code) { return c->Desc; } } return 0; } /****************************** Helper Functions **************************/ char *LReadTextFile(const char *File) { char *s = 0; LFile f; if (File && f.Open(File, O_READ)) { int64 Len = f.GetSize(); s = new char[Len+1]; if (s) { ssize_t Read = f.Read(s, (int)Len); s[Read] = 0; } } return s; } int64 LFileSize(const char *FileName) { struct stat64 s; if (FileName && stat64(FileName, &s) == 0) { return s.st_size; } return 0; } bool LDirExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = S_ISDIR(s.st_mode); } } return Status; } bool LFileExists(const char *FileName, char *CorrectCase) { bool Status = false; if (FileName) { struct stat s; // Check for exact match... if (stat(FileName, &s) == 0) { Status = true; } else if (strlen(FileName) < MAX_PATH_LEN) { // Look for altenate case by enumerating the directory char d[MAX_PATH_LEN]; strcpy_s(d, sizeof(d), FileName); char *e = strrchr(d, DIR_CHAR); if (e) { *e++ = 0; DIR *Dir = opendir(d); if (Dir) { dirent *De; while ((De = readdir(Dir))) { if (stricmp(De->d_name, e) == 0) { try { // Tell the calling program the actual case of the file... e = (char*)strrchr(FileName, DIR_CHAR); // If this crashes because the argument is read only then we get caught by the try catch strcpy(e+1, De->d_name); // It worked! Status = true; } catch (...) { // It didn't work :( #ifdef _DEBUG printf("%s,%i - FileExists(%s) found an alternate case version but couldn't return it to the caller.\n", __FILE__, __LINE__, FileName); #endif } break; } } closedir(Dir); } } } } return Status; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { return readlink (LinkFile, Path, Len) > 0; } void WriteStr(LFile &f, const char *s) { size_t Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, (int)Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32 Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LgiAssert(0); return 0; } // allocate the memory buffer #if defined(_DEBUG) && defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(uint32) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; if (Path) { struct stat s; if (lstat(Path, &s) == 0) { // printf("LGetDriveInfo dev=%i\n", s.st_dev); } } return Status; } ///////////////////////////////////////////////////////////////////////// #include #include struct LVolumePriv { LString Name; LString Path; int Type = VT_NONE; int Flags = 0; int64 Size = 0; int64 Free = 0; LAutoPtr Icon; LSystemPath SysPath = LSP_ROOT; LVolume *Next = NULL, *Child = NULL; LVolumePriv(const char *init) { if (init) { Name = LGetLeaf(init); Type = VT_FOLDER; Path = init; } } LVolumePriv(LSystemPath type, const char *name) { if (type) { Name = name; switch (SysPath = type) { case LSP_DESKTOP: Type = VT_DESKTOP; break; default: Type = VT_FOLDER; break; } Path = LGetSystemPath(type); } } void Insert(LVolume *v) { if (!Child) Child = v; else { for (auto i = Child; i; i = i->Next()) { if (!i->d->Next) { i->d->Next = v; break; } } } } }; LVolume::LVolume(const char *init) { d = new LVolumePriv(init); } LVolume::LVolume(LSystemPath syspath, const char *name) { d = new LVolumePriv(syspath, name); } LVolume::~LVolume() { DeleteObj(d->Child); DeleteObj(d->Next); DeleteObj(d); } const char *LVolume::Name() const { return d->Name; } const char *LVolume::Path() const { return d->Path; } int LVolume::Type() const { return d->Type; } int LVolume::Flags() const { return d->Flags; } uint64 LVolume::Size() const { return d->Size; } uint64 LVolume::Free() const { return d->Free; } LSurface *LVolume::Icon() const { return d->Icon; } LDirectory *LVolume::GetContents() { LDirectory *Dir = NULL; if (d->Path) { Dir = new LDirectory; if (Dir && !Dir->First(d->Path)) DeleteObj(Dir); } return Dir; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // IDGF Apple.. provide an alternative you dumbass LVolume *LVolume::First() { if (d->SysPath == LSP_DESKTOP && !d->Child) { LVolume *v = NULL; LString DesktopPath = LGetSystemPath(LSP_DESKTOP); // List any favorites UInt32 seed; auto sflRef = LSSharedFileListCreate(NULL, kLSSharedFileListFavoriteItems, NULL); auto items = LSSharedFileListCopySnapshot( sflRef, &seed ); for( size_t i = 0; i < CFArrayGetCount(items); i++ ) { auto item = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(items, i); if( !item ) continue; auto outURL = LSSharedFileListItemCopyResolvedURL(item,kLSSharedFileListNoUserInteraction, NULL); if( !outURL ) continue; CFStringRef itemPath = CFURLCopyFileSystemPath(outURL,kCFURLPOSIXPathStyle); LString s = itemPath; if (!s.Equals(DesktopPath)) // This is the root item, don't duplicate { v = new LVolume(); if (v) { v->d->Path = s; v->d->Name = LGetLeaf(s); v->d->Type = VT_FOLDER; auto IcoRef = LSSharedFileListItemCopyIconRef(item); if (IcoRef) { NSImage *img = [[NSImage alloc] initWithIconRef:IcoRef]; v->d->Icon.Reset(new LMemDC(img)); [img release]; CFRelease(IcoRef); } d->Insert(v); } } CFRelease(outURL); CFRelease(itemPath); } CFRelease(items); CFRelease(sflRef); } return d->Child; } LVolume *LVolume::Next() { if (d->SysPath == LSP_DESKTOP && !d->Next) { d->Next = new LVolume("/Volumes"); // List the local hard disks auto *ws = [NSWorkspace sharedWorkspace]; auto *vols = [ws mountedLocalVolumePaths]; auto *fm = [NSFileManager defaultManager]; for (NSString *path in vols) { NSDictionary* fsAttributes; NSString *description, *type, *volName; BOOL removable, writable, unmountable, res; res = [ws getFileSystemInfoForPath:path isRemovable:&removable isWritable:&writable isUnmountable:&unmountable description:&description type:&type]; if (!res) continue; // fsAttributes = [fm fileSystemAttributesAtPath:path]; NSError *err = nil; fsAttributes = [fm attributesOfFileSystemForPath:path error:&err]; volName = [fm displayNameAtPath:path]; NSNumber *size = [fsAttributes objectForKey:NSFileSystemSize]; NSNumber *freeSize = [fsAttributes objectForKey:NSFileSystemFreeSize]; #if 0 NSLog(@"path=%@\nname=%@\nremovable=%d\nwritable=%d\nunmountable=%d\n" "description=%@\ntype=%@, size=%@\n\n", path, name, removable, writable, unmountable, description, type, size); #endif LString s = [type UTF8String]; LString p = [path UTF8String]; LString name = [volName UTF8String]; if (!s.Equals("autofs") && p.Find("/System/Volumes") < 0 && p.Find("/private") < 0) { auto v = new LVolume(); if (v) { v->d->Path = p; v->d->Name = name; v->d->Type = VT_HARDDISK; v->d->Size = size.longLongValue; v->d->Free = freeSize.longLongValue; d->Next->d->Insert(v); // printf("Vol: s=%s p=%s name=%s\n", s.Get(), p.Get(), v->d->Name.Get()); } } } } return d->Next; } #pragma GCC diagnostic pop bool LVolume::IsMounted() const { return false; } bool LVolume::SetMounted(bool Mount) { return Mount; } void LVolume::Insert(LAutoPtr v) { d->Insert(v.Release()); } /////////////////////////////////////////////////////////////////////////////// LFileSystem *LFileSystem::Instance = 0; LFileSystem::LFileSystem() { Instance = this; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); } void LFileSystem::OnDeviceChange(char *Reserved) { } LVolume *LFileSystem::GetRootVolume() { if (!Root) Root = new LVolume(LSP_DESKTOP, "Desktop"); return Root; } int FloppyType(int Letter) { return 0; } #if 0 OSType gFinderSignature = 'MACS'; OSStatus MoveFileToTrash(CFURLRef fileURL) { AppleEvent event, reply; OSStatus err; FSRef fileRef; AliasHandle fileAlias; if (CFURLGetFSRef(fileURL, &fileRef) == false) return coreFoundationUnknownErr; err = FSNewAliasMinimal(&fileRef, &fileAlias); if (err == noErr) { err = AEBuildAppleEvent(kAECoreSuite, kAEDelete, typeApplSignature, &gFinderSignature, sizeof(OSType), kAutoGenerateReturnID, kAnyTransactionID, &event, NULL, "'----':alis(@@)", fileAlias); if (err == noErr) { err = AESendMessage(&event, &reply, kAEWaitReply, kAEDefaultTimeout); if (err == noErr) AEDisposeDesc(&reply); AEDisposeDesc(&event); } DisposeHandle((Handle)fileAlias); } return err; } #endif bool LFileSystem::Copy(const char *From, const char *To, LError *ErrorCode, CopyFileCallback Callback, void *Token) { if (!From || !To) { #if LGI_COCOA if (ErrorCode) *ErrorCode = NSFileReadInvalidFileNameError; #else if (ErrorCode) *ErrorCode = paramErr; #endif return false; } LFile In, Out; if (!In.Open(From, O_READ)) { if (ErrorCode) *ErrorCode = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (ErrorCode) *ErrorCode = Out.GetError(); return false; } if (Out.SetSize(0)) { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteUnknownError; #else writErr; #endif return false; } int64 Size = In.GetSize(); if (!Size) { return true; } int64 Block = MIN((1 << 20), Size); char *Buf = new char[Block]; if (!Buf) { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteOutOfSpaceError; #else notEnoughBufferSpace; #endif return false; } int64 i = 0; while (i < Size) { ssize_t r = In.Read(Buf, Block); if (r > 0) { int Written = 0; while (Written < r) { ssize_t w = Out.Write(Buf + Written, r - Written); if (w > 0) { Written += w; } else { if (ErrorCode) *ErrorCode = #if LGI_COCOA NSFileWriteUnknownError; #else writErr; #endif goto ExitCopyLoop; } } i += Written; if (Callback) { if (!Callback(Token, i, Size)) { break; } } } else break; } ExitCopyLoop: DeleteArray(Buf); if (i == Size) { if (ErrorCode) *ErrorCode = noErr; } else { Out.Close(); Delete(To, false); } return i == Size; /* bool Status = false; if (From AND To) { LFile In, Out; if (In.Open(From, O_READ) AND Out.Open(To, O_WRITE)) { Out.SetSize(0); int64 Size = In.GetSize(); int64 Block = min((1 << 20), Size); char *Buf = new char[Block]; if (Buf) { int64 i = 0; while (i < Size) { int r = In.Read(Buf, Block); if (r > 0) { int Written = 0; while (Written < r) { int w = Out.Write(Buf + Written, r - Written); if (w > 0) { Written += w; } else goto ExitCopyLoop; } i += Written; if (Callback) { if (!Callback(Token, i, Size)) { break; } } } else break; } ExitCopyLoop: DeleteArray(Buf); Status = i == Size; if (!Status) { Delete(To, false); } } } } return Status; */ } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Error = false; if (ToTrash) { #if defined MAC #if LGI_COCOA NSMutableArray *urls = [[NSMutableArray alloc] initWithCapacity:Files.Length()]; if (urls) { for (auto f : Files) { id u = (NSURL *)CFURLCreateFromFileSystemRepresentation(NULL, (UInt8 *)f, strlen(f), 0); [urls addObject:u]; CFRelease(u); } [[NSWorkspace sharedWorkspace] recycleURLs:urls completionHandler:NULL]; [urls release]; } #else // Apple events method for (int i=0; i f; f.Add(FileName); return Delete(f, 0, ToTrash); } return false; } bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentFolders, LError *ErrorCode) { int r = mkdir(PathName, S_IRWXU | S_IXGRP | S_IXOTH); if (r) { if (ErrorCode) *ErrorCode = errno; if (CreateParentFolders) { char Base[MAX_PATH_LEN]; strcpy_s(Base, sizeof(Base), PathName); do { char *Leaf = strrchr(Base, DIR_CHAR); if (!Leaf) return false; *Leaf = 0; } while (!LDirExists(Base)); auto Parts = LString(PathName + strlen(Base)).SplitDelimit(DIR_STR); for (int i=0; id_name, ".") == 0 || strcmp(De->d_name, "..") == 0 || ( Pattern && !MatchStr(Pattern, De->d_name) ) ); } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } int LDirectory::First(const char *Name, const char *Pattern) { Close(); if (Name) { strcpy_s(d->BasePath, sizeof(d->BasePath), Name); d->BaseEnd = d->BasePath + strlen(d->BasePath); if (!Pattern || stricmp(Pattern, LGI_ALL_FILES) == 0) { struct stat S; if (lstat(Name, &S) == 0) { if (S_ISREG(S.st_mode)) { char *Dir = strrchr(d->BasePath, DIR_CHAR); if (Dir) { *Dir++ = 0; d->Pattern = NewStr(Dir); } } } } else { d->Pattern = NewStr(Pattern); } auto e = d->BasePath + strlen(d->BasePath); while (e > d->BasePath && e[-1] == DIR_CHAR) *--e = 0; d->Dir = opendir(d->BasePath); if (d->Dir) { d->De = readdir(d->Dir); if (d->De) { char s[512]; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (d->Ignore()) { if (!Next()) { return false; } } } } else if (!Stricmp(Name, "/")) { // Really Apple? REALLY??? // Can't opendir("/")... sigh. Clearly there is more than one way to get this done. LSubProcess p("ls", "-l /"); LStringPipe o; if (p.Start() && p.Communicate(&o) == 0) { strcpy_s(d->BasePath, sizeof(d->BasePath), Name); d->BaseEnd = d->BasePath + strlen(d->BasePath); d->Cache.SetFixedLength(false); auto Lines = o.NewGStr().Split("\n"); for (auto Ln: Lines) { auto p = Ln.SplitDelimit(" \t", 8); if (p.Length() > 8) { auto Name = p.Last(); auto Lnk = Name.Split(" -> "); if (Lnk.Length() > 1) d->Cache.New().Printf("/%s", Lnk.Last().Get()); else d->Cache.New().Printf("/%s", Name.Get()); } } d->CachePos = 0; return !lstat(d->Cache[d->CachePos], &d->Stat); } else printf("%s:%i - ls failed.\n", _FL); } else { printf("%s:%i - opendir(%s) failed with %i\n", _FL, d->BasePath, errno); } } return d->Dir != 0 && d->De != 0; } int LDirectory::Next() { int Status = false; char s[512]; if (d->CachePos >= 0) { d->CachePos++; if (d->CachePos >= d->Cache.Length()) Status = false; else { *d->BaseEnd = 0; auto &c = d->Cache[d->CachePos]; if (!lstat(c, &d->Stat)) Status = true; } } else { while (d->Dir && d->De) { if ((d->De = readdir(d->Dir))) { *d->BaseEnd = 0; LMakePath(s, sizeof(s), d->BasePath, GetName()); lstat(s, &d->Stat); if (!d->Ignore()) { Status = true; break; } } } } return Status; } int LDirectory::Close() { if (d->Dir) { closedir(d->Dir); d->Dir = 0; } d->De = 0; return true; } bool LDirectory::Path(char *s, int BufLen) const { if (!s) { return false; } return LMakePath(s, BufLen, d->BasePath, GetName()); } int LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } int LDirectory::GetUser(bool Group) const { if (Group) { return d->Stat.st_gid; } else { return d->Stat.st_uid; } } bool LDirectory::IsReadOnly() const { if (getuid() == d->Stat.st_uid) { // Check user perms return !TestFlag(GetAttributes(), S_IWUSR); } else if (getgid() == d->Stat.st_gid) { // Check group perms return !TestFlag(GetAttributes(), S_IWGRP); } // Check global perms return !TestFlag(GetAttributes(), S_IWOTH); } bool LDirectory::IsSymLink() const { long a = GetAttributes(); return S_ISLNK(a); } bool LDirectory::IsHidden() const { return GetName() && GetName()[0] == '.'; } bool LDirectory::IsDir() const { long a = GetAttributes(); return !S_ISLNK(a) && S_ISDIR(a); } long LDirectory::GetAttributes() const { return d->Stat.st_mode; } char *LDirectory::GetName() const { if (d->CachePos >= 0) return d->Cache[d->CachePos]; if (d->De) return d->De->d_name; LAssert(!"Invalid state."); return NULL; } uint64 LDirectory::GetCreationTime() const { return (uint64)d->Stat.st_ctime * 1000; } uint64 LDirectory::GetLastAccessTime() const { return (uint64)d->Stat.st_atime * 1000; } uint64 LDirectory::GetLastWriteTime() const { return (uint64)d->Stat.st_mtime * 1000; } uint64 LDirectory::GetSize() const { return d->Stat.st_size; } int64 LDirectory::GetSizeOnDisk() { return d->Stat.st_size; } const char *LDirectory::FullPath() { auto n = GetName(); if (!n) return NULL; char *s = d->BaseEnd; char *e = d->BasePath + sizeof(d->BasePath); if (s > d->BasePath && s[-1] != DIR_CHAR) *s++ = DIR_CHAR; strncpy(s, n, e - s); return d->BasePath; } LString LDirectory::FileName() const { return GetName(); } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// File /////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: int hFile; char *Name; bool Swap; int Status; int Attributes; int LastError; LFilePrivate() { hFile = INVALID_HANDLE; Name = 0; Swap = false; Status = true; Attributes = 0; #if LGI_COCOA LastError = 0; #else LastError = noErr; #endif } ~LFilePrivate() { DeleteArray(Name); } }; LFile::LFile(const char *path, int mode) { d = new LFilePrivate; if (path) Open(path, mode); } LFile::~LFile() { if (d && ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } int LFile::GetError() { return d->LastError; } OsFile LFile::Handle() { return d->hFile; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } +uint64_t LFile::GetModifiedTime() +{ + struct stat s; + stat(d->Name, &s); + return s.st_mtime; +} + +bool LFile::SetModifiedTime(uint64_t dt) +{ + struct stat s; + stat(d->Name, &s); + + struct timeval times[2] = {}; + times[0].tv_sec = s.st_atime; + times[1].tv_sec = dt; + + int r = utimes(d->Name, times); + + return r == 0; +} + void LFile::ChangeThread() { } // #define _FILE_OPEN #ifdef _FILE_OPEN GSemaphore _FileLock; GHashTable _FileOpen; LgiFunc void _DumpOpenFiles() { if (_FileLock.Lock(_FL)) { char *k; int i=0; for (void *p=_FileOpen.First(&k); p; p=_FileOpen.Next(&k)) { printf("File[%i]='%s'\n", i++, k); } _FileLock.Unlock(); } } #endif int LFile::Open(const char *File, int Mode) { if (!File) { #if LGI_COCOA d->LastError = NSFileReadInvalidFileNameError; #else d->LastError = paramErr; #endif return false; } if (TestFlag(Mode, O_WRITE) || TestFlag(Mode, O_READWRITE)) { Mode |= O_CREAT; } Close(); d->hFile = open(File, Mode | O_LARGEFILE, S_IRUSR | S_IWUSR); if (!ValidHandle(d->hFile)) { d->LastError = errno; printf("LFile::Open failed\n\topen(%s,%8.8x) = %i\n\terrno=%s (%s)\n", File, Mode, d->hFile, GetErrorName(errno), GetErrorDesc(errno)); return false; } #ifdef _FILE_OPEN if (_FileLock.Lock(_FL)) {5 _FileOpen.Add(File, this); _FileLock.Unlock(); } #endif d->Attributes = Mode; d->Name = new char[strlen(File)+1]; if (d->Name) { strcpy(d->Name, File); } d->Status = true; return true; } int LFile::Close() { if (ValidHandle(d->hFile)) { #ifdef _FILE_OPEN if (_FileLock.Lock(_FL)) { _FileOpen.Delete(d->Name); _FileLock.Unlock(); } #endif close(d->hFile); d->hFile = INVALID_HANDLE; DeleteArray(d->Name); } return true; } #define CHUNK 0xFFF0 ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { ssize_t Red = 0; if (Buffer && Size > 0) { Red = read(d->hFile, Buffer, Size); #ifdef _DEBUG if (Red < 0) { int Err = errno; int64 Pos = GetPos(); printf("Read error: %i, " LPrintfInt64 "\n", Err, Pos); } #endif } d->Status = Red == Size; return MAX(Red, 0); } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { ssize_t Written = 0; if (Buffer && Size > 0) { Written = write(d->hFile, Buffer, Size); #ifdef _DEBUG if (Written < 0) { int Err = errno; int64 Pos = GetPos(); printf("Write error: %i, " LPrintfInt64 "\n", Err, Pos); } #endif } d->Status = Written == Size; return MAX(Written, 0); } int64 LFile::Seek(int64 To, int Whence) { #if LINUX64 return lseek64(d->hFile, To, Whence); // If this doesn't compile, switch off LINUX64 #else return lseek(d->hFile, To, Whence); #endif } int64 LFile::SetPos(int64 Pos) { #if LINUX64 int64 p = lseek64(d->hFile, Pos, SEEK_SET); if (p < 0) { int e = errno; printf("%s:%i - lseek64(%Lx) failed (error %i: %s).\n", __FILE__, __LINE__, Pos, e, GetErrorName(e)); } #else return lseek(d->hFile, Pos, SEEK_SET); #endif } int64 LFile::GetPos() { #if LINUX64 int64 p = lseek64(d->hFile, 0, SEEK_CUR); if (p < 0) { int e = errno; printf("%s:%i - lseek64 failed (error %i: %s).\n", __FILE__, __LINE__, e, GetErrorName(e)); } return p; #else return lseek(d->hFile, 0, SEEK_CUR); #endif } int64 LFile::GetSize() { int64 Here = GetPos(); #if LINUX64 int64 Ret = lseek64(d->hFile, 0, SEEK_END); #else off_t Ret = lseek(d->hFile, 0, SEEK_END); #endif SetPos(Here); return Ret; } int64 LFile::SetSize(int64 Size) { if (ValidHandle(d->hFile)) { int64 Pos = GetPos(); /* close(d->hFile); if (d->Name) { #if LINUX64 truncate64(Name, Size); #else truncate(Name, Size); #endif } d->hFile = open(Name, Attributes, 0); */ #if LINUX64 ftruncate64(d->hFile, Size); #else ftruncate(d->hFile, Size); #endif if (d->hFile) { SetPos(Pos); } } return GetSize(); } bool LFile::Eof() { return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { ssize_t r = Read(Buf, Size); if (r == Size) { uint8 *s = Buf, *e = Buf + r - 1; for (; s < e; s++, e--) { uchar c = *s; *s = *e; *e = c; } } else return 0; return r; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { switch (Size) { case 1: { return Write(Buf, Size); break; } case 2: { uint16 i = *((uint16*)Buf); i = LgiSwap16(i); return Write(&i, Size); break; } case 4: { uint32 i = *((uint32*)Buf); i = LgiSwap32(i); return Write(&i, Size); break; } case 8: { uint64 i = *((uint64*)Buf); i = LgiSwap64(i); return Write(&i, Size); break; } default: { ssize_t i, n; for (i=0, n=Size-1; i 0) { char c; Size--; do { r = read(d->hFile, &c, 1); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { ssize_t i = 0; ssize_t w; while (i <= Size) { w = write(d->hFile, Buf, 1); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } const char *LFile::GetName() { return d->Name; } #define GFileOp(type) LFile &LFile::operator >> (type &i) \ { \ d->Status |= ((d->Swap) ? SwapRead((uchar*) &i, sizeof(i)) : Read(&i, sizeof(i))) != sizeof(i); \ return *this; \ } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) \ { \ d->Status |= ((d->Swap) ? SwapWrite((uchar*) &i, sizeof(i)) : Write(&i, sizeof(i))) != sizeof(i); \ return *this; \ } GFileOps(); #undef GFileOp diff --git a/src/mac/cocoa/Menu.mm b/src/mac/cocoa/Menu.mm --- a/src/mac/cocoa/Menu.mm +++ b/src/mac/cocoa/Menu.mm @@ -1,1432 +1,1451 @@ /*hdr ** FILE: GuiMenu.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Gui menu system ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" #define DEBUG_INFO 0 @interface LNSMenuItem : NSMenuItem { } @property LMenuItem* item; - (id)init:(LMenuItem*)it; - (void)activate; - (BOOL)worksWhenModal; @end @implementation LNSMenuItem - (id)init:(LMenuItem*)it { if ((self = [super init]) != nil) { self.item = it; [self setTarget:self]; self.action = @selector(activate); } return self; } - (BOOL)worksWhenModal { return YES; } - (void)activate { + printf("activate\n"); self.item->OnActivate(self.item); } @end struct LShortcut { NSString *Str; public: NSString *Key; NSEventModifierFlags Mod; LShortcut(const char *s) { Key = @""; Str = nil; Mod = 0; auto Keys = LString(s).SplitDelimit("+-"); if (Keys.Length() <= 0) return; for (auto k: Keys) { if (stricmp(k, "CtrlCmd") == 0 || stricmp(k, "AltCmd") == 0 || stricmp(k, "Cmd") == 0 || stricmp(k, "Command") == 0) { Mod |= NSEventModifierFlagCommand; } else if (stricmp(k, "Ctrl") == 0 || stricmp(k, "Control") == 0) { Mod |= NSEventModifierFlagControl; } else if (stricmp(k, "Alt") == 0 || stricmp(k, "Option") == 0) { Mod |= NSEventModifierFlagOption; } else if (stricmp(k, "Shift") == 0) { Mod |= NSEventModifierFlagShift; } else if (stricmp(k, "Del") == 0 || stricmp(k, "Delete") == 0) { unichar s[] = {NSDeleteCharacter}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Ins") == 0 || stricmp(k, "Insert") == 0) { unichar s[] = {NSInsertFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Home") == 0) { unichar s[] = {NSHomeFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "End") == 0) { unichar s[] = {NSEndFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "PageUp") == 0) { unichar s[] = {NSPageUpFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "PageDown") == 0) { unichar s[] = {NSPageDownFunctionKey}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Backspace") == 0) { unichar s[] = {NSBackspaceCharacter}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (stricmp(k, "Space") == 0) { Key = @" "; } else if (k[0] == 'F' && isdigit(k[1])) { int64 index = k.Strip("F").Int(); unichar s[] = {(unichar)(NSF1FunctionKey + index - 1)}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else if (isalpha(k[0])) { Key = Str = LString(k).Lower().NsStr(); } else if (isdigit(k[0])) { Key = Str = LString(k).NsStr(); } else if (strchr(",.", k(0))) { unichar s[] = {(unichar)k(0)}; Key = Str = [[NSString alloc] initWithCharacters:s length:1]; } else { printf("%s:%i - Unhandled shortcut token '%s'\n", _FL, k.Get()); } } } ~LShortcut() { if (Str) [Str release]; } }; /////////////////////////////////////////////////////////////////////////////////////////////// LSubMenu::LSubMenu(const char *name, bool Popup) { Menu = 0; Parent = 0; Info = NULL; LBase::Name(name); Info.p = [[NSMenu alloc] init]; [Info.p setAutoenablesItems:NO]; } LSubMenu::~LSubMenu() { while (Items.Length()) { LMenuItem *i = Items[0]; if (i->Parent != this) { i->Parent = NULL; Items.Delete(i); } delete i; } if (Info) { [Info.p release]; Info = NULL; } } void LSubMenu::OnAttach(bool Attach) { for (auto i: Items) { i->OnAttach(Attach); } if (Attach && this != Menu && Parent && Parent->Parent) { } } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { LMenuItem *i = new LMenuItem(Menu, this, Str, Id, Where, Shortcut); if (!i || !Info) return NULL; Items.Insert(i, Where); auto Index = Items.IndexOf(i); // auto Max = Info.p.numberOfItems; LString s(i->LBase::Name()); auto name = s.NsStr(); if (!name) { delete i; return NULL; } LShortcut sc(Shortcut); i->Info.p = [[LNSMenuItem alloc] init:i]; if (!i->Info) { Items.Delete(i); delete i; return NULL; } [i->Info.p setTitle:name]; i->Info.p.keyEquivalent = sc.Key; i->Info.p.keyEquivalentModifierMask = sc.Mod; i->Id(Id); i->Enabled(Enabled); [Info.p insertItem:i->Info atIndex:Index]; return i; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *i = new LMenuItem; if (i) { i->Parent = this; i->Menu = Menu; i->Id(-2); Items.Insert(i, Where); if (Info) { auto Index = Items.IndexOf(i); // auto Max = Info.p.numberOfItems; // printf("Adding ----- @ %i, %i\n", (int)Index, (int)Max); i->Info = [NSMenuItem separatorItem]; [Info.p insertItem:i->Info atIndex:Index]; } else { printf("%s:%i - No menu to attach item to.\n", _FL); } return i; } return 0; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { LMenuItem *i = new LMenuItem; if (i && Str) { i->Parent = this; i->Menu = Menu; i->Id(-1); Items.Insert(i, Where); if (Info) { i->Child = new LSubMenu(Str); if (i->Child) { i->Child->Parent = i; i->Child->Menu = Menu; i->Child->Window = Window; i->Info.p = [[NSMenuItem alloc] init]; LAssert(i->Info); i->Name(Str); LString s(i->LBase::Name()); [i->Child->Info.p setTitle:s.NsStr()]; [i->Info.p setSubmenu:i->Child->Info.p]; auto Index = Items.IndexOf(i); auto IsMenu = dynamic_cast(this); auto Offset = IsMenu && Where >= 0 ? 1 : 0; // Adjust for 'Root' element "app menu" [Info.p insertItem:i->Info atIndex:Index + Offset]; } } else { printf("%s:%i - No menu to attach item to.\n", __FILE__, __LINE__); } return i->Child; } return 0; } void LSubMenu::Empty() { while (Items[0]) { if (!RemoveItem(Items[0])) break; // Otherwise we'll get an infinite loop. } } bool LSubMenu::RemoveItem(int i) { LMenuItem *Item = Items[i]; if (Item) { return Item->Remove(); } return false; } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item)) { return Item->Remove(); } return false; } bool LSubMenu::OnKey(LKey &k) { return false; } void LSubMenu::OnActivate(LMenuItem *item) { if (!item) return; if (FloatResult) *FloatResult = item->Id(); else if (Parent) Parent->OnActivate(item); else LAssert(!"Should have a float result OR a parent.."); } int LSubMenu::Float(LView *From, int x, int y, int Btns) { LPoint p(x, y); OsView v = nil; if (From) From->Capture(false); auto w = From ? From->GetWindow() : NULL; if (w) { v = w->Handle(); w->PointToView(p); p = w->Flip(p); } FloatResult.Reset(new int(0)); // auto item = Items[0]; // auto menuitem = item->Info.p; // auto en = menuitem.enabled; NSPoint loc = {(double)p.x, (double)p.y}; [Info.p popUpMenuPositioningItem:nil atLocation:loc inView:v]; return FloatResult ? *FloatResult : 0; } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: LString Shortcut; }; LMenuItem::LMenuItem() { d = new LMenuItemPrivate(); Menu = NULL; Info = NULL; Child = NULL; Parent = NULL; _Icon = -1; _Id = 0; _Flags = 0; } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Str, int Id, int Pos, const char *Shortcut) { d = new LMenuItemPrivate(); LBase::Name(Str); Menu = m; Parent = p; Info = NULL; Child = NULL; _Icon = -1; _Id = Id; _Flags = 0; d->Shortcut = Shortcut; Name(Str); ScanForAccel(); } LMenuItem::~LMenuItem() { if (Parent) { Parent->Items.Delete(this); Parent = NULL; } DeleteObj(Child); DeleteObj(d); } void LMenuItem::OnActivate(LMenuItem *item) { if (Parent) Parent->OnActivate(item); else LAssert(!"Should have a parent."); } void LMenuItem::OnAttach(bool Attach) { if (Attach) { if (_Icon >= 0) { Icon(_Icon); } if (Sub()) { Sub()->OnAttach(Attach); } } } // the following 3 functions paint the menus according the to // windows standard. but also allow for correct drawing of menuitem // icons. some implementations of windows force the program back // to the 8-bit palette when specifying the icon graphic, thus removing // control over the colours displayed. these functions remove that // limitation and also provide the application the ability to override // the default painting behaviour if desired. void LMenuItem::_Measure(LPoint &Size) { auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool BaseMenu = Parent == Menu; // true if attached to a windows menu // else is a submenu int Ht = Font->GetHeight(); // int IconX = BaseMenu ? ((24-Ht)/2)-Ht : 20; int IconX = BaseMenu ? 2 : 16; if (Separator()) { Size.x = 8; Size.y = 8; } else { // remove '&' chars for string measurement char Str[256]; const char *n = Name(), *i = n; char *o = Str; while (i && *i) { if (*i == '&') { if (i[1] == '&') { *o++ = *i++; } } else { *o++ = *i; } i++; } *o++ = 0; // check for accelerators char *Tab = strchr(Str, '\t'); if (Tab) { // string with accel int Mx, Tx; LDisplayString ds(Font, Str, Tab-Str); Mx = ds.X(); LDisplayString ds2(Font, Tab + 1); Tx = ds2.X(); Size.x = IconX + 32 + Mx + Tx; } else { // normal string LDisplayString ds(Font, Str); Size.x = IconX + ds.X() + 4; } if (!BaseMenu) { // leave room for child pointer Size.x += Child ? 8 : 0; } Size.y = MAX(IconX, Ht+2); } } #define Time(a, b) ((double)(b - a) / 1000) void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width) { auto n = Name(); if (n) { auto Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool Underline = false; const char *e = 0; for (auto s=n; s && *s; s = *e ? e : 0) { switch (*s) { case '&': { if (s[1] == '&') { e = s + 2; LDisplayString d(Font, "&"); d.Draw(pDC, x, y, 0); x += d.X(); } else { Underline = true; e = s + 1; } break; } case '\t': { LDisplayString ds(Font, e + 1); x = Width - ds.X() - 8; e = s + 1; break; } default: { if (Underline) { LgiNextUtf8(e); } else { for (e = s; *e; e++) { if (*e == '\t') break; if (*e == '&') break; } } ptrdiff_t Len = e - s; if (Len > 0) { // paint text till that point LDisplayString d(Font, s, Len); d.Draw(pDC, x, y, 0); if (Underline) { LDisplayString ds(Font, s, 1); int UnderX = ds.X(); int Ascent = (int)ceil(Font->Ascent()); pDC->Colour(Font->Fore()); pDC->Line(x, y+Ascent+1, x+MAX(UnderX-2, 1), y+Ascent+1); Underline = false; } x += d.X(); } break; } } } } } void LMenuItem::_Paint(LSurface *pDC, int Flags) { bool BaseMenu = Parent == Menu; int IconX = BaseMenu ? 5 : 20; bool Selected = TestFlag(Flags, ODS_SELECTED); bool Disabled = TestFlag(Flags, ODS_DISABLED); bool Checked = TestFlag(Flags, ODS_CHECKED); #if defined(WIN32) || defined(MAC) LRect r(0, 0, pDC->X()-1, pDC->Y()-1); #else LRect r = Info->GetClient(); #endif if (Separator()) { // Paint a separator int Cy = r.Y() / 2; pDC->Colour(L_MED); pDC->Rectangle(); pDC->Colour(L_LOW); pDC->Line(0, Cy-1, pDC->X()-1, Cy-1); pDC->Colour(L_LIGHT); pDC->Line(0, Cy, pDC->X()-1, Cy); } else { // Paint a text menu item LColour Fore(L_TEXT); LColour Back(Selected ? L_HIGH : L_MED); int x = IconX; int y = 1; // For a submenu pDC->Colour(Back); pDC->Rectangle(); // Draw the text on top LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; Font->Transparent(true); if (Disabled) { // Disabled text if (!Selected) { Font->Colour(L_LIGHT); _PaintText(pDC, x+1, y+1, r.X()); } // Else selected... don't draw the hilight // "greyed" text... Font->Colour(L_LOW); _PaintText(pDC, x, y, r.X()); } else { // Normal coloured text Font->Fore(Fore); _PaintText(pDC, x, y, r.X()); } auto ImgLst = (Menu && Menu->GetImageList()) ? Menu->GetImageList() : Parent ? Parent->GetImageList() : 0; // Draw icon/check mark if (Checked && IconX > 0) { // it's a check! int x = 4; int y = 6; pDC->Colour(Fore); pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); } else if (ImgLst && _Icon >= 0) { // it's an icon! LColour Bk(L_MED); ImgLst->Draw(pDC, 0, 0, _Icon, Bk); } // Sub menu arrow if (Child && !dynamic_cast(Parent)) { pDC->Colour(L_TEXT); int x = r.x2 - 4; int y = r.y1 + (r.Y()/2); for (int i=0; i<4; i++) { pDC->Line(x, y-i, x, y+i); x--; } } } } bool LMenuItem::ScanForAccel() { if (!d->Shortcut) return false; // printf("d->Shortcut=%s\n", d->Shortcut.Get()); auto Keys = d->Shortcut.SplitDelimit("+-"); if (Keys.Length() > 0) { int Flags = 0; - char16 Key = 0; + int Vkey = 0; + int Chr = 0; for (int i=0; iShortcut.Get()); } - if (Key) + if (Vkey || Chr) { - if ((Flags & LGI_EF_ALT) != 0 && - (Flags & LGI_EF_SYSTEM) == 0) + if + ( + ( + (Flags & LGI_EF_ALT) != 0 && + (Flags & LGI_EF_SYSTEM) == 0 + ) + || + Vkey == LK_BACKSPACE + ) { auto Ident = Id(); LAssert(Ident > 0); - Menu->Accel.Insert( new LAccelerator(Flags, Key, Ident) ); + Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Ident) ); } } else { printf("%s:%i - Accel scan failed, str='%s'\n", _FL, d->Shortcut.Get()); return false; } } return true; } LSubMenu *LMenuItem::GetParent() { return Parent; } bool LMenuItem::Remove() { if (!Parent) return false; if (Parent->Info && Info) { [Parent->Info.p removeItem:Info]; Parent->Items.Delete(this); Info = NULL; } else { Parent->Items.Delete(this); } return true; } void LMenuItem::Id(int i) { _Id = i; if (Parent && Parent->Info && Info) { #if LGI_COCOA #else SetMenuItemCommandID(Parent->Info, Info, _Id); #endif } } void LMenuItem::Separator(bool s) { if (s) { _Id = -2; } if (Parent) { #if LGI_COCOA #else if (s) ChangeMenuItemAttributes(Parent->Info, Info, kMenuItemAttrSeparator, 0); else ChangeMenuItemAttributes(Parent->Info, Info, 0, kMenuItemAttrSeparator); #endif } } void LMenuItem::Checked(bool c) { if (c) SetFlag(_Flags, ODS_CHECKED); else ClearFlag(_Flags, ODS_CHECKED); if (Info) [Info.p setState: c ? NSControlStateValueOn : NSControlStateValueOff]; } bool LMenuItem::Name(const char *n) { char *Tmp = NewStr(n); if (Tmp) { char *in = Tmp, *out = Tmp; while (*in) { if (*in != '&') *out++ = *in; in++; } *out++ = 0; } bool Status = LBase::Name(Tmp); if (Status && Info) { LString s(Tmp); [Info.p setTitle:s.NsStr()]; } DeleteArray(Tmp); return Status; } void LMenuItem::Enabled(bool e) { #if 1 if (Info && Info.p.enabled ^ e) Info.p.enabled = e; #endif } void LMenuItem::Focus(bool f) { } void LMenuItem::Sub(LSubMenu *s) { Child = s; } void LMenuItem::Icon(int i) { _Icon = i; auto Lst = Menu ? Menu->GetImageList() : Parent->GetImageList(); if (!Lst || !Info) return; if (_Icon < 0 || _Icon >= Lst->GetItems()) return; auto r = Lst->GetIconRect(_Icon); [Info.p setImage: Lst->NsImage(&r)]; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return _Id; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return _Id == -2; } bool LMenuItem::Checked() { return TestFlag(_Flags, ODS_CHECKED); } bool LMenuItem::Enabled() { if (Parent) { #if LGI_COCOA #else return IsMenuItemEnabled(Parent->Info, Info); #endif } return true; } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return 0; } LSubMenu *LMenuItem::Sub() { return Child; } int LMenuItem::Icon() { return _Icon; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuPrivate { public: int PrefId, AboutId; LMenuItem *PrefItem = NULL; LMenuItem *AboutItem = NULL; LMenuPrivate() { PrefId = AboutId = 0; } }; LMenu::LMenu(const char *AppName) : LSubMenu("", false) { Menu = this; d = new LMenuPrivate; auto s = AppendSub("Root"); if (s) { d->AboutItem = s->AppendItem("About", M_ABOUT); s->AppendSeparator(); d->PrefItem = s->AppendItem("Preferences", M_PERFERENCES, true, -1, "Cmd+,"); s->AppendItem("Hide", M_HIDE, true, -1, "Cmd+H"); s->AppendSeparator(); s->AppendItem("Quit", M_QUIT, true, -1, "Cmd+Q"); } } LMenu::~LMenu() { Accel.DeleteObjects(); DeleteObj(d); } void LMenu::OnActivate(LMenuItem *item) { if (!item) { LAssert(0); return; } switch (item->Id()) { case M_ABOUT: if (Window && d->AboutId) Window->PostEvent(M_COMMAND, d->AboutId); break; case M_PERFERENCES: if (Window && d->PrefId) Window->PostEvent(M_COMMAND, d->PrefId); break; case M_HIDE: [[NSApplication sharedApplication] hide:Info]; break; case M_QUIT: LCloseApp(); break; default: if (Window) + { + printf("%s:%i - post M_COMMAND\n", _FL); Window->PostEvent(M_COMMAND, item->Id()); + } break; } } bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId) { d->PrefId = PrefId; if (d->PrefItem) d->PrefItem->Enabled(d->PrefId > 0); d->AboutId = AboutId; if (d->AboutItem) d->AboutItem->Enabled(d->AboutId > 0); return true; } struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); if (MenuFont.f) { #ifndef MAC _Font->CodePage(SysFont->CodePage()); #endif } else { printf("LMenu::GetFont Couldn't create menu font.\n"); } } else { printf("LMenu::GetFont Couldn't get menu typeface.\n"); } if (!MenuFont.f) { MenuFont.f = new LFont; if (MenuFont.f) *MenuFont.f = *LSysFont; } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { bool Status = false; auto w = dynamic_cast(p); if (w) { Window = p; [NSApplication sharedApplication].mainMenu = Info; if (Info) { OnAttach(true); Status = true; } else { printf("%s:%i - No menu\n", _FL); } } return Status; } bool LMenu::Detach() { bool Status = false; return Status; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down()) { k.Trace("MenuKey"); + + printf("Accel.len=%i\n", (int)Accel.Length()); for (auto a: Accel) { if (a->Match(k)) { Window->OnCommand(a->GetId(), 0, NULL); return true; } } if (k.Alt() && !dynamic_cast(v) && !dynamic_cast(v)) { bool Hide = false; for (auto s: Items) { if (!s->Separator()) { if (Hide) { // s->Info->HideSub(); } else { auto n = s->Name(); if (ValidStr(n)) { char *Amp = strchr(n, '&'); while (Amp && Amp[1] == '&') { Amp = strchr(Amp + 2, '&'); } if (Amp) { char Accel = tolower(Amp[1]); char Press = tolower(k.c16); if (Accel == Press) { Hide = true; } } } if (Hide) { // s->Info->ShowSub(); } else { // s->Info->HideSub(); } } } } if (Hide) { return true; } } } return false; } //////////////////////////////////////////////////////////////////////////// -LAccelerator::LAccelerator(int flags, int key, int id) +LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; - Key = key; + Vkey = vkey; + Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { int Press = (uint) k.c16; auto Up = toupper(Press); bool Match = false; #if 1 printf("LAccelerator::Match %i(%c)%s%s%s = %i(%c)%s%s%s\n", Up, Up>=' '?Up:'.', k.Ctrl()?" ctrl":"", k.Alt()?" alt":"", k.Shift()?" shift":"", - Key, - Key>=' '?Key:'.', + Chr, + Chr>=' '?Chr:'.', TestFlag(Flags, LGI_EF_CTRL)?" ctrl":"", TestFlag(Flags, LGI_EF_ALT)?" alt":"", TestFlag(Flags, LGI_EF_SHIFT)?" shift":"" ); #endif if (k.Alt() && !k.System() && !k.Ctrl()) { switch (k.vkey) { #define _(k) case LK_##k: \ - Match = Key == #k[0]; break; + Match = Vkey == #k[0]; break; _(A) _(B) _(C) _(D) _(E) _(F) _(G) _(H) _(I) _(J) _(K) _(L) _(M) _(N) _(O) _(P) _(Q) _(R) _(S) _(T) _(U) _(V) _(W) _(X) _(Y) _(Z) default: printf("%s:%i - No case for '%i'\n", _FL, k.vkey); break; } } - else + else if (Vkey) { - Match = Up == (uint)Key; + Match = k.vkey == Vkey; + } + else if (Chr) + { + Match = Up == (uint)Chr; } if (Match) { if ( ((TestFlag(Flags, LGI_EF_CTRL) ^ k.Ctrl()) == 0) && ((TestFlag(Flags, LGI_EF_ALT) ^ k.Alt()) == 0) && ((TestFlag(Flags, LGI_EF_SHIFT) ^ k.Shift()) == 0) ) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { Flags = GWF_VISIBLE; Id = 0; ToolButton = 0; MenuItem = 0; Accelerator = 0; TipHelp = 0; PrevValue = false; } LCommand::~LCommand() { DeleteArray(Accelerator); DeleteArray(TipHelp); } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; } diff --git a/src/mac/cocoa/Thread.mm b/src/mac/cocoa/Thread.mm --- a/src/mac/cocoa/Thread.mm +++ b/src/mac/cocoa/Thread.mm @@ -1,161 +1,169 @@ #include "lgi/common/Lgi.h" #include "lgi/common/Mutex.h" #include "lgi/common/StringClass.h" #include "lgi/common/Thread.h" #include "lgi/common/EventTargetThread.h" #include #include #ifndef _PTHREAD_H #error "Pthreads not included" #endif OsThreadId GetCurrentThreadId() { uint64_t tid = LThread::InvalidId; pthread_threadid_np(NULL, &tid); return tid; } //////////////////////////////////////////////////////////////////////////// void *ThreadEntryPoint(void *i) { if (i) { LThread *Thread = (LThread*) i; Thread->ThreadId = GetCurrentThreadId(); // Make sure we have finished executing the setup while (Thread->State == LThread::THREAD_INIT) LSleep(1); pthread_detach(Thread->hThread); // Do thread's work Thread->OnBeforeMain(); Thread->ReturnValue = Thread->Main(); Thread->OnAfterMain(); // mark thread over... Thread->State = LThread::THREAD_EXITED; bool DelayDelete = false; if (Thread->ViewHandle >= 0) { // If DeleteOnExit is set AND ViewHandle then the LView::OnEvent handle will // process the delete... don't do it here. DelayDelete = PostThreadEvent(Thread->ViewHandle, M_THREAD_COMPLETED, (LMessage::Param)Thread); // However if PostThreadEvent fails... do honour DeleteOnExit. } if (!DelayDelete && Thread->DeleteOnExit) { DeleteObj(Thread); } pthread_exit(0); } return 0; } const OsThread LThread::InvalidHandle = NULL; const OsThreadId LThread::InvalidId = 0; LThread::LThread(const char *name, int viewHnd) { State = THREAD_INIT; ReturnValue = -1; hThread = InvalidHandle; ThreadId = InvalidId; ViewHandle = viewHnd; DeleteOnExit = false; Priority = ThreadPriorityNormal; } LThread::~LThread() { if (!IsExited()) { Terminate(); } } int LThread::ExitCode() { return ReturnValue; } bool LThread::IsExited() { return State == THREAD_EXITED; } void LThread::Run() { + if (State == THREAD_EXITED && + hThread) + { + pthread_join(hThread, NULL); + hThread = NULL; + } + if (!hThread) { State = THREAD_INIT; static int Creates = 0; int e; if (!(e = pthread_create(&hThread, NULL, ThreadEntryPoint, (void*)this))) { State = THREAD_RUNNING; Creates++; if (Priority != ThreadPriorityNormal) { int policy; sched_param param; e = pthread_getschedparam(hThread, &policy, ¶m); int min_pri = sched_get_priority_min(policy); int max_pri = sched_get_priority_max(policy); switch (Priority) { case ThreadPriorityIdle: param.sched_priority = min_pri; break; case ThreadPriorityNormal: break; case ThreadPriorityHigh: param.sched_priority = max_pri; break; case ThreadPriorityRealtime: param.sched_priority = max_pri; break; } e = pthread_setschedparam(hThread, policy, ¶m); } } else { const char *Err = "(unknown)"; switch (e) { case EAGAIN: Err = "EAGAIN"; break; case EINVAL: Err = "EINVAL"; break; case EPERM: Err = "EPERM"; break; case ENOMEM: Err = "ENOMEM"; break; } printf( "%s,%i - pthread_create failed with the error %i (%s) (After %i creates)\n", _FL, e, Err, Creates); State = THREAD_EXITED; } } } void LThread::Terminate() { if (hThread && pthread_cancel(hThread) == 0) { State = THREAD_EXITED; + hThread = NULL; } } int LThread::Main() { return 0; } diff --git a/src/mac/cocoa/View.mm b/src/mac/cocoa/View.mm --- a/src/mac/cocoa/View.mm +++ b/src/mac/cocoa/View.mm @@ -1,1008 +1,1011 @@ /*hdr ** FILE: LView.cpp ** AUTHOR: Matthew Allen ** DATE: 23/4/98 ** DESCRIPTION: Linux LView Implementation ** ** Copyright (C) 1998-2003, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/DragAndDrop.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/Edit.h" #include "lgi/common/Css.h" #include "lgi/common/Popup.h" #include "lgi/common/DisplayString.h" #include "ViewPriv.h" #include "LCocoaView.h" #define ADJ_LEFT 1 #define ADJ_RIGHT 2 #define ADJ_UP 3 #define ADJ_DOWN 4 // CursorData is a bitmap in an array of uint32's. This is generated from a graphics file: // ./Code/cursors.png // // The pixel values are turned into C code by a program called i.Mage: // http://www.memecode.com/image.php // // Load the graphic into i.Mage and then go Edit->CopyAsCode // Then paste the text into the CursorData variable at the bottom of this file. // // This saves a lot of time finding and loading an external resouce, and even having to // bundle extra files with your application. Which have a tendancy to get lost along the // way etc. extern uint32 CursorData[]; LInlineBmp Cursors = { 300, 20, 8, CursorData }; struct LgiCursorInfo { public: LRect Pos; LPoint HotSpot; } CursorMetrics[] = { // up arrow { LRect(0, 0, 8, 15), LPoint(4, 0) }, // cross hair { LRect(20, 0, 38, 18), LPoint(29, 9) }, // hourglass { LRect(40, 0, 51, 15), LPoint(45, 8) }, // I beam { LRect(60, 0, 66, 17), LPoint(63, 8) }, // N-S arrow { LRect(80, 0, 91, 16), LPoint(85, 8) }, // E-W arrow { LRect(100, 0, 116, 11), LPoint(108, 5) }, // NW-SE arrow { LRect(120, 0, 132, 12), LPoint(126, 6) }, // NE-SW arrow { LRect(140, 0, 152, 12), LPoint(146, 6) }, // 4 way arrow { LRect(160, 0, 178, 18), LPoint(169, 9) }, // Blank { LRect(0, 0, 0, 0), LPoint(0, 0) }, // Vertical split { LRect(180, 0, 197, 16), LPoint(188, 8) }, // Horizontal split { LRect(200, 0, 216, 17), LPoint(208, 8) }, // Hand { LRect(220, 0, 233, 13), LPoint(225, 0) }, // No drop { LRect(240, 0, 258, 18), LPoint(249, 9) }, // Copy drop { LRect(260, 0, 279, 19), LPoint(260, 0) }, // Move drop { LRect(280, 0, 299, 19), LPoint(280, 0) }, }; //////////////////////////////////////////////////////////////////////////// LView *GWindowFromHandle(OsView h) { if (!h) return 0; return 0; } //////////////////////////////////////////////////////////////////////////// bool LIsKeyDown(int Key) { LAssert(!"Not impl."); return false; } bool LIsMounted(char *Name) { LAssert(!"Not impl."); return false; } bool LMountVolume(char *Name) { LAssert(!"Not impl."); return false; } LKey::LKey(int Vkey, uint32_t flags) { c16 = vkey = Vkey; Flags = flags; Data = 0; IsChar = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// #if 0 bool LViewPrivate::CursorSet = false; LView *LViewPrivate::LastCursor = 0; #endif LViewPrivate::LViewPrivate(LView *view) : View(view) { TabStop = false; AttachEvent = false; } LViewPrivate::~LViewPrivate() { DeleteObj(Popup); LAssert(PulseThread == NULL); } const char *LView::GetClass() { return "LView"; } LView *&LView::PopupChild() { return d->Popup; } bool LView::_Mouse(LMouse &m, bool Move) { #if 0 if (Move) { static bool First = true; if (First) { First = false; //_DumpHeirarchy(GetWindow()); //_Dump(0, GetWindow(), HIViewGetRoot(GetWindow()->WindowHandle())); } printf("_Mouse %p,%s %i,%i down=%i move=%i\n", this, GetClass(), m.x, m.y, m.Down(), Move); } #endif LWindow *Wnd = GetWindow(); if (_Capturing) { // Convert root NSView coords to capture view coords LViewI *par; for (auto i = _Capturing; i && i != this; i = par) { par = i->GetParent(); LRect cli, p = i->GetPos(); if (par) cli = i->GetClient(false); m.x -= p.x1 + cli.x1; m.y -= p.y1 + cli.y1; } // if (!m.IsMove()) m.Trace("Capture"); if (Move) { LViewI *c = _Capturing; c->OnMouseMove(m); } else { if (!Wnd || Wnd->HandleViewMouse(dynamic_cast(_Capturing), m)) _Capturing->OnMouseClick(m); } } else { // Convert root NSView coords to _Over coords auto t = WindowFromPoint(m.x, m.y); if (t) { /* GColour col(255, 0, 255); GArray rc; GArray names; auto w = GetWindow(); int yy = 200; bool Debug = !stricmp(t->GetClass(), "CtrlEditbox") && w->DebugDC.X() == 0 && !Move; if (Debug && w) { w->DebugDC.Create(w->X(), w->Y(), System32BitColourSpace); w->DebugDC.Colour(0, 32); w->DebugDC.Rectangle(); w->DebugDC.Colour(col); } */ for (auto i = t; i && i != this; i = i->GetParent()) { auto p = i->GetPos(); auto cli = i->GetClient(false); /* if (Debug) { LRect r = p; r.Offset(cli.x1, cli.y1); rc.AddAt(0,r); names.AddAt(0,i->GetClass()); printf("%s %s\n", i->GetClass(), p.GetStr()); } */ m.x -= p.x1 + cli.x1; m.y -= p.y1 + cli.y1; } /* if (Debug) { int x = rc[0].x1, y = rc[0].y1; for (unsigned i=0; iDebugDC.Line(x, y, x+r.x1, y+r.y1); x += r.x1; y += r.y1; GDisplayString ds(SysFont, names[i]); SysFont->Fore(col); SysFont->Transparent(true); ds.Draw(&w->DebugDC, 120-ds.X(), yy); w->DebugDC.Line(120, yy, x, y); yy += 10 + ds.Y(); } w->Invalidate(); } */ m.Target = t; } // if (!m.IsMove()) m.Trace("NonCapture"); if (_Over != m.Target) { if (_Over) _Over->OnMouseExit(m); _Over = m.Target; if (_Over) _Over->OnMouseEnter(m); } auto Target = dynamic_cast(_Over ? _Over : m.Target); auto Lo = dynamic_cast(Target); LRect Client = Lo ? Lo->GetClient(false) : Target->LView::GetClient(false); if (!Client.Valid() || Client.Overlap(m.x, m.y)) { if (Move) { // Do cursor stuff auto cursor = Target->GetCursor(m.x, m.y); auto cc = NSCursor.arrowCursor; if (cursor != LCUR_Normal) cc = LCocoaCursor(cursor); if (cc != NSCursor.currentSystemCursor) [cc set]; // Move event Target->OnMouseMove(m); } else if (!Wnd || Wnd->HandleViewMouse(Target, m)) { // Click event Target->OnMouseClick(m); } } else return false; } return true; } LRect &LView::GetClient(bool ClientSpace) { int Edge = (Sunken() || Raised()) ? _BorderSize : 0; static LRect c; c = Pos; c.Offset(-c.x1, -c.y1); if (ClientSpace) { c.x2 -= Edge << 1; c.y2 -= Edge << 1; } else { c.Inset(Edge, Edge); } return c; } void LView::Quit(bool DontDelete) { if (DontDelete) { Visible(false); } else { Detach(); delete this; } } LPoint LView::Flip(LPoint p) { auto Parent = GetParent() ? GetParent() : GetWindow(); if (Parent) { LRect r = Parent->GetClient(false); p.y = r.y2 - p.y; } return p; } void LView::OnDealloc() { #if LGI_VIEW_HASH LockHandler(this, OpDelete); #endif SetPulse(); for (auto c: Children) { auto gv = c->GetGView(); if (gv) gv->OnDealloc(); } } LRect LView::Flip(LRect p) { auto Parent = GetParent() ? GetParent() : GetWindow(); if (Parent) { LRect r = Parent->GetClient(false); int y2 = r.y2 - p.y1; int y1 = y2 - p.Y() + 1; p.Offset(0, y1-p.y1); } return p; } bool LView::SetPos(LRect &p, bool Repaint) { Pos = p; OnPosChange(); return true; } bool LView::Invalidate(LRect *rc, bool Repaint, bool Frame) { if (!InThread()) return PostEvent(M_INVALIDATE, (LMessage::Param) (rc ? new LRect(*rc) : NULL), (LMessage::Param) this); LRect r; if (rc) r = *rc; else r = GetClient(); if (!r.Valid()) return false; auto w = GetWindow(); LPopup *popup = NULL; LViewI *v; for (v = this; v; v = v->GetParent()) { if (!v->Visible()) return true; if (v == (LViewI*)w) break; if ((popup = dynamic_cast(v))) break; auto p = v->GetPos(); r.Offset(p.x1, p.y1); } NSView *nsview = NULL; if (popup) nsview = popup->Handle().p.contentView; else if (w) nsview = w->Handle(); if (!nsview) return false; #if 0 [nsview setNeedsDisplayInRect:v->GetGView()->Flip(r)]; printf("%s::Inval r=%s\n", GetClass(), r.GetStr()); #else nsview.needsDisplay = true; #endif return true; } void LView::SetPulse(int Length) { DeleteObj(d->PulseThread); if (Length > 0) d->PulseThread = new LPulseThread(this, Length); } LCursor LView::GetCursor(int x, int y) { return LCUR_Normal; } LMessage::Result LView::OnEvent(LMessage *Msg) { switch (Msg->m) { case M_PULSE: { OnPulse(); break; } case M_CHANGE: { LViewI *Ctrl; if (GetViewById((int)Msg->A(), Ctrl)) { LNotification note((LNotifyType)Msg->B()); return OnNotify(Ctrl, note); } break; } case M_COMMAND: { + static int count = 0; + if (++count == 2) + printf("%s:%i - M_COMMAND\n", _FL); return OnCommand((int)Msg->A(), 0, (OsView)Msg->B()); } case M_INVALIDATE: { LAutoPtr rc((LRect*)Msg->A()); Invalidate(rc.Get()); break; } case M_THREAD_COMPLETED: { auto Th = (LThread*)Msg->A(); if (!Th) break; Th->OnComplete(); if (Th->GetDeleteOnExit()) delete Th; return true; } default: { break; } } return 0; } bool LView::PointToScreen(LPoint &p) { LViewI *c = this; // Find offset to window while ( c && !dynamic_cast(c) && !dynamic_cast(c)) { LRect pos = c->GetPos(); // printf("%s pos %s\n", c->GetClass(), pos.GetStr()); p.x += pos.x1; p.y += pos.y1; c = c->GetParent(); } if (c && c->WindowHandle()) { NSWindow *w = c->WindowHandle(); LRect wFrame = w.frame; LRect Screen(0, 0, -1, -1); for (NSScreen *s in [NSScreen screens]) { LRect pos = s.frame; if (wFrame.Overlap(&pos)) { Screen = pos; break; } } if (!Screen.Valid()) return false; LRect cli = c->GetClient(); p.y = cli.Y() - p.y; NSRect i = {{(double)p.x, (double)p.y}, {0.0, 0.0}}; NSRect o = [w convertRectToScreen:i]; p.x = (int)o.origin.x; p.y = Screen.Y() - (int)o.origin.y; } else return false; return true; } bool LView::PointToView(LPoint &p) { LViewI *c = this; int Ox = 0, Oy = 0; // Find offset to window while (c && c != c->GetWindow()) { LView *gv = c->GetGView(); LRect cli = gv ? gv->LView::GetClient(false) : c->GetClient(false); LRect pos = c->GetPos(); Ox += pos.x1 + cli.x1; Oy += pos.y1 + cli.y1; c = c->GetParent(); } // Apply window position if (c && c->WindowHandle()) { NSWindow *w = c->WindowHandle(); LRect wFrame = w.frame; LRect Screen(0, 0, -1, -1); for (NSScreen *s in [NSScreen screens]) { LRect pos = s.frame; if (wFrame.Overlap(&pos)) { Screen = pos; break; } } if (!Screen.Valid()) return false; // Flip into top-down to bottom up: NSRect i = {(double)p.x, (double)(Screen.Y()-p.y), 0.0, 0.0}; NSRect o = [w convertRectFromScreen:i]; p.x = (int)o.origin.x; p.y = (int)o.origin.y; // Flip back into top down.. in window space #if 1 p = c->GetGView()->Flip(p); #else LRect cli = c->GetClient(); p.y = cli.Y() - p.y; #endif // Now offset into view space. p.x += Ox; p.y += Oy; } else return false; return true; } #define DEBUG_GETMOUSE 0 bool LView::GetMouse(LMouse &m, bool ScreenCoords) { LViewI *w = GetWindow(); if (!w) return false; NSWindow *wh = w->WindowHandle(); if (!wh) return false; #if DEBUG_GETMOUSE GStringPipe log; #endif NSPoint pt = wh.mouseLocationOutsideOfEventStream; m.x = pt.x; m.y = pt.y; m.Target = this; #if DEBUG_GETMOUSE log.Print("Event=%i,%i", m.x, m.y); #endif LPoint p(pt.x, pt.y); p.y = wh.contentView.frame.size.height - p.y; #if DEBUG_GETMOUSE log.Print(" Flipped=%i,%i", p.x, p.y); #endif if (ScreenCoords) { PointToScreen(p); #if DEBUG_GETMOUSE log.Print(" Screen=%i,%i", p.x, p.y); #endif } m.x = p.x; m.y = p.y; m.Target = this; auto Btns = [NSEvent pressedMouseButtons]; m.Left(Btns & 0x1); m.Right(Btns & 0x2); m.Middle(Btns & 0x4); // Find offset to window for (LViewI *c = this; c && c != c->GetWindow(); c = c->GetParent()) { LRect pos = c->GetPos(); m.x -= pos.x1; m.y -= pos.y1; #if DEBUG_GETMOUSE log.Print(" Offset(%s,%i,%i)=%i,%i", c->GetClass(), pos.x1, pos.y1, m.x, m.y); #endif } #if DEBUG_GETMOUSE printf(" GetMouse: %s\n", log.NewGStr().Get()); #endif return true; } bool LView::IsAttached() { LWindow *w = GetWindow(); if (!w) return false; if (w == this) return WindowHandle() != 0; if (!d->AttachEvent) return false; if (GetParent() != NULL) return w->WindowHandle() != 0; return false; } void BuildTabStops(LArray &Stops, LViewI *v) { if (v && v->Visible()) { if (v->GetTabStop() && v->Enabled()) Stops.Add(v); for (LViewI *c: v->IterateViews()) BuildTabStops(Stops, c); } } void NextTabStop(LViewI *v, int dir) { LArray Stops; BuildTabStops(Stops, v->GetWindow()); ssize_t Idx = Stops.IndexOf(v); if (Idx >= 0) Idx += dir; else Idx = 0; if (Idx < 0) Idx = Stops.Length() - 1; else if (Idx >= Stops.Length()) Idx = 0; if (Idx < Stops.Length()) Stops[Idx]->Focus(true); } void SetDefaultFocus(LViewI *v) { LArray Stops; BuildTabStops(Stops, v->GetWindow()); if (Stops.Length()) { int Set = -1; for (int i=0; iFocus()) { Set = i; break; } } if (Set < 0) Set = 0; Stops[Set]->Focus(true); } } int VirtualKeyToLgi(int Virt) { switch (Virt) { // various case 122: return LK_F1; case 120: return LK_F2; case 99: return LK_F3; case 118: return LK_F4; case 96: return LK_F5; case 97: return LK_F6; case 98: return LK_F7; case 100: return LK_F8; case 101: return LK_F9; case 109: return LK_F10; case 103: return LK_F11; case 110: return LK_APPS; case 111: return LK_F12; case 123: return LK_LEFT; case 124: return LK_RIGHT; case 125: return LK_DOWN; case 126: return LK_UP; case 114: return LK_INSERT; case 116: return LK_PAGEUP; case 121: return LK_PAGEDOWN; case 53: return LK_ESCAPE; case 51: return LK_BACKSPACE; case 117: return LK_DELETE; case 115: return LK_HOME; case 119: return LK_END; // whitespace case 76: return LK_RETURN; case 36: return '\r'; case 48: return '\t'; case 49: return ' '; // delimiters case 27: return '-'; case 24: return '='; case 42: return '\\'; case 47: return '.'; case 50: return '`'; // digits case 18: return '1'; case 19: return '2'; case 20: return '3'; case 21: return '4'; case 23: return '5'; case 22: return '6'; case 26: return '7'; case 28: return '8'; case 25: return '9'; case 29: return '0'; // alpha case 0: return 'a'; case 11: return 'b'; case 8: return 'c'; case 2: return 'd'; case 14: return 'e'; case 3: return 'f'; case 5: return 'g'; case 4: return 'h'; case 34: return 'i'; case 38: return 'j'; case 40: return 'k'; case 37: return 'l'; case 46: return 'm'; case 45: return 'n'; case 31: return 'o'; case 35: return 'p'; case 12: return 'q'; case 15: return 'r'; case 1: return 's'; case 17: return 't'; case 32: return 'u'; case 9: return 'v'; case 13: return 'w'; case 7: return 'x'; case 16: return 'y'; case 6: return 'z'; default: printf("%s:%i - unimplemented virt->lgi code mapping: %d\n", _FL, (unsigned)Virt); break; } return 0; } #if 0 static int GetIsChar(LKey &k, int mods) { return k.IsChar = (mods & 0x100) == 0 && ( k.c16 >= ' ' || k.c16 == VK_RETURN || k.c16 == VK_TAB || k.c16 == VK_BACKSPACE ); } #endif bool LView::_Attach(LViewI *parent) { LAutoPool Pool; if (!parent) { LAssert(0); return false; } d->ClassName = GetClass(); d->ParentI = parent; d->Parent = d->ParentI ? parent->GetGView() : NULL; LAssert(!_InLock); _Window = d->GetParent() ? d->GetParent()->GetWindow() : 0; if (_Window) _Lock = _Window->_Lock; auto p = d->GetParent(); if (p) { LWindow *w = dynamic_cast(p); NSView *ph = nil; if (w) ph = w->WindowHandle().p.contentView; if (!d->AttachEvent) { d->AttachEvent = true; OnCreate(); } } else { LAssert(0); } if (!p->Children.HasItem(this)) { p->Children.Add(this); OnAttach(); } return true; } bool LView::Attach(LViewI *parent) { if (_Attach(parent)) { if (d->Parent && !d->Parent->HasView(this)) d->Parent->AddView(this); d->Parent->OnChildrenChanged(this, true); return true; } LgiTrace("%s:%i - Attaching '%s' failed.\n", _FL, GetClass()); return false; } void LView::_Delete() { LAutoPool Pool; if (_Over == this) _Over = 0; if (_Capturing == this) _Capturing = 0; if (LAppInst && LAppInst->AppWnd == this) { LAppInst->AppWnd = NULL; } SetPulse(); Pos.ZOff(-1, -1); LViewI *c; auto it = Children.begin(); while ((c = *it)) { LViewI *p = c->GetParent(); if (p != (LViewI*)this) { printf("Error: LView::_Delete, child not attached correctly: %p(%s) Parent: %p(%s)\n", c, c->Name(), c->GetParent(), c->GetParent() ? c->GetParent()->Name() : ""); Children.Delete(it); } DeleteObj(c); } Detach(); } bool LView::Detach() { LAutoPool Pool; bool Status = false; // Detach view if (_Window) { LWindow *Wnd = dynamic_cast(_Window); if (Wnd) Wnd->SetFocus(this, LWindow::ViewDelete); _Window = NULL; } if (d->Parent) { // Remove the view from the parent int D = 0; while (d->Parent->HasView(this)) { d->Parent->DelView(this); D++; } if (D > 1) { printf("%s:%i - Error: View %s(%p) was in %s(%p) list %i times.\n", _FL, GetClass(), this, d->Parent->GetClass(), d->Parent, D); } d->Parent->OnChildrenChanged(this, false); d->Parent = NULL; } Status = true; return Status; } //////////////////////////////// uint32 CursorData[] = { 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00010001, 0x01000001, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x00000100, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x01000001, 0x02020101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01010000, 0x02020101, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x00000102, 0x02020100, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x02010001, 0x00000001, 0x00010201, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02010102, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000102, 0x00000001, 0x02020201, 0x00010202, 0x02020100, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01010100, 0x01010101, 0x01000000, 0x02020202, 0x01000001, 0x01000000, 0x01020202, 0x02020201, 0x02020202, 0x02020101, 0x00000102, 0x00000100, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x01000001, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x00000101, 0x02020100, 0x00010202, 0x02010000, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020100, 0x01020202, 0x02010000, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020201, 0x02020202, 0x02020201, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020100, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x00010202, 0x02010000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x01020202, 0x02020100, 0x02020202, 0x00000001, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02010000, 0x00000102, 0x01010101, 0x01010000, 0x00000101, 0x02020201, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x02020201, 0x00000102, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x01020202, 0x01000000, 0x01020202, 0x02010000, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010101, 0x01010001, 0x01010101, 0x02020101, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020101, 0x01020202, 0x02020201, 0x02020202, 0x00000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x01010101, 0x01010001, 0x00010101, 0x02020100, 0x00010202, 0x01020201, 0x02010000, 0x01000102, 0x02020202, 0x01010101, 0x01010101, 0x01010100, 0x01010101, 0x02020201, 0x00010202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x01000001, 0x02020202, 0x00000001, 0x01020201, 0x02010000, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02010101, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x01000001, 0x02020100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01000001, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x01020202, 0x02020202, 0x02020202, 0x00000001, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00010202, 0x02020201, 0x02010001, 0x00010202, 0x02020201, 0x01020202, 0x01020201, 0x02010000, 0x02010102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00000000, 0x02010000, 0x02020202, 0x00000001, 0x02020201, 0x00000102, 0x00010100, 0x02010000, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x00000001, 0x01000001, 0x01020202, 0x01010101, 0x01010101, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01000102, 0x01000001, 0x02010001, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x02020201, 0x02020202, 0x01020202, 0x02020201, 0x02010001, 0x01010202, 0x02020202, 0x02020202, 0x01020201, 0x02010000, 0x02020102, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02020100, 0x02020202, 0x00000102, 0x02020201, 0x00010202, 0x00010000, 0x02020100, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01000001, 0x01000001, 0x01020202, 0x00000000, 0x01000000, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00010001, 0x00000000, 0x01000100, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02010001, 0x02010202, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x01010101, 0x01010100, 0x02020201, 0x02020202, 0x02020202, 0x00000102, 0x00000000, 0x02020201, 0x02020202, 0x00000102, 0x02020100, 0x01020202, 0x00000000, 0x02020100, 0x02010001, 0x00000102, 0x01020201, 0x01010000, 0x01000001, 0x02010001, 0x00000102, 0x01020201, 0x01010100, 0x01000001, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01010102, 0x01010001, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00000102, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01010202, 0x01010101, 0x02020202, 0x02020202, 0x00010202, 0x01010000, 0x01020202, 0x00000001, 0x02020201, 0x02020101, 0x00000102, 0x01020201, 0x00000100, 0x01000100, 0x02020101, 0x00000102, 0x01020201, 0x01000100, 0x01000100, 0x01020202, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x01010101, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x00010102, 0x02020101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x00000000, 0x02020201, 0x02020202, 0x02020202, 0x01020202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x00010202, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x00010101, 0x01000000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000000, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01000001, 0x02010000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x02020100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00000001, 0x00000000, 0x02010000, 0x02020202, 0x02020202, 0x00010202, 0x01020100, 0x00000100, 0x01000100, 0x02020202, 0x00010202, 0x01020100, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010101, 0x02010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02010001, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020201, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x00010102, 0x00000000, 0x02020101, 0x02020202, 0x02020202, 0x01020202, 0x01020201, 0x01010000, 0x01000001, 0x02020202, 0x01020202, 0x01020201, 0x01000100, 0x01000100, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020102, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x01020202, 0x00000000, 0x01000000, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, 0x02020202, 0x02020202, 0x01020202, 0x01010101, 0x01010101, }; diff --git a/src/win/General/File.cpp b/src/win/General/File.cpp --- a/src/win/General/File.cpp +++ b/src/win/General/File.cpp @@ -1,1905 +1,1935 @@ /*hdr ** FILE: File.cpp ** AUTHOR: Matthew Allen ** DATE: 11/7/95 ** DESCRIPTION: The new file subsystem ** ** Copyright (C) 1995, Matthew Allen ** fret@memecode.com */ /****************************** Includes ************************************************************************************/ #include #include #include #include #include #include #include #include #include #ifdef _MSC_VER #include #include #endif #include "lgi/common/Lgi.h" #include "lgi/common/Thread.h" /****************************** Defines *************************************************************************************/ #define FILEDEBUG #define CHUNK 0xFFF0 #define FLOPPY_360K 0x0001 #define FLOPPY_720K 0x0002 #define FLOPPY_1_2M 0x0004 #define FLOPPY_1_4M 0x0008 #define FLOPPY_5_25 (FLOPPY_360K | FLOPPY_1_2M) #define FLOPPY_3_5 (FLOPPY_720K | FLOPPY_1_4M) /****************************** Helper Functions ****************************************************************************/ LString LFile::Path::Sep(DIR_STR); bool LFile::Path::FixCase() { // Windows is case insensitive.. so meh... do nothing. return true; } char *LReadTextFile(const char *File) { char *s = 0; LFile f; if (File && f.Open(File, O_READ)) { auto Len = f.GetSize(); s = new char[Len+1]; if (s) { auto Read = f.Read(s, Len); s[Read] = 0; } } return s; } int64 LFileSize(const char *FileName) { int64 Size = -1; LDirectory Dir; if (Dir.First(FileName, 0)) { Size = Dir.GetSize(); } return Size; } bool LFileExists(const char *Name, char *CorrectCase) { bool Status = false; if (Name) { HANDLE hFind = INVALID_HANDLE_VALUE; LAutoWString n(Utf8ToWide(Name)); if (n) { WIN32_FIND_DATAW Info; hFind = FindFirstFileW(n, &Info); if (hFind != INVALID_HANDLE_VALUE) Status = StrcmpW(Info.cFileName, L".") != 0; } if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); return true; } } return false; } bool LDirExists(const char *Dir, char *CorrectCase) { LAutoWString n(Utf8ToWide(Dir)); DWORD e = GetFileAttributesW(n); return e != 0xFFFFFFFF && TestFlag(e, FILE_ATTRIBUTE_DIRECTORY); } const char *GetErrorName(int e) { static char Buf[256]; char *s = Buf; Buf[0] = 0; ::FormatMessageA( // FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_SYSTEM, NULL, e, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)Buf, sizeof(Buf), NULL); s = Buf + strlen(Buf); while (s > Buf && strchr(" \t\r\n", s[-1])) { s--; *s = 0; } return Buf[0] ? Buf : 0; } template int _GetLongPathName ( T *Short, T *Long, int LongLen ) { bool Status = false; int Len = 0; if (Short && Long) { HMODULE hDll = 0; hDll = LoadLibrary(_T("kernel32.dll")); if (hDll) { // Win98/ME/2K... short->long conversion supported #ifdef UNICODE typedef DWORD (__stdcall *Proc_GetLongPathName)(LPCWSTR, LPWSTR, DWORD cchBuffer); Proc_GetLongPathName Proc = (Proc_GetLongPathName)GetProcAddress(hDll, "GetLongPathNameW"); #else typedef DWORD (__stdcall *Proc_GetLongPathName)(LPCSTR, LPSTR, DWORD); Proc_GetLongPathName Proc = (Proc_GetLongPathName)GetProcAddress(hDll, "GetLongPathNameA"); #endif if (Proc) { Len = Proc(Short, Long, LongLen); Status = true; } FreeLibrary(hDll); } if (!Status) { // Win95/NT4 doesn't support this function... so we do the conversion ourselves TCHAR *In = Short; TCHAR *Out = Long; *Out = 0; while (*In) { // find end of segment TCHAR *e = Strchr(In, DIR_CHAR); if (!e) e = In + Strlen(In); // process segment TCHAR Old = *e; *e = 0; if (Strchr(In, ':')) { // drive specification Strcat(Long, LongLen, In); // just copy over } else { TCHAR Sep[] = {DIR_CHAR, 0}; Strcat(Out, LongLen, Sep); if (Strlen(In) > 0) { // has segment to work on WIN32_FIND_DATA Info; HANDLE Search = FindFirstFile(Short, &Info); if (Search != INVALID_HANDLE_VALUE) { // have long name segment, copy over Strcat(Long, LongLen, Info.cFileName); FindClose(Search); } else { // no long name segment... copy over short segment :( Strcat(Long, LongLen, In); } } // else is a double path char... i.e. as in a M$ network path } In = (Old) ? e + 1 : e; Out += Strlen(Out); *e = Old; } } } return Len; } bool LResolveShortcut(const char *LinkFile, char *Path, ssize_t Len) { bool Status = false; HWND hwnd = NULL; HRESULT hres; IShellLink* psl; TCHAR szGotPath[DIR_PATH_SIZE] = {0}; WIN32_FIND_DATA wfd; CoInitialize(0); hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**) &psl); if (SUCCEEDED(hres)) { IPersistFile* ppf; hres = psl->QueryInterface(IID_IPersistFile, (void**) &ppf); if (SUCCEEDED(hres)) { char16 wsz[DIR_PATH_SIZE]; MultiByteToWideChar(CP_ACP, 0, LinkFile, -1, wsz, DIR_PATH_SIZE); char16 *l = StrrchrW(wsz, '.'); if (l && StricmpW(l, L".lnk")) { StrcatW(wsz, L".lnk"); } hres = ppf->Load(wsz, STGM_READ); if (SUCCEEDED(hres)) { hres = psl->Resolve(hwnd, SLR_ANY_MATCH); if (SUCCEEDED(hres)) { hres = psl->GetPath(szGotPath, DIR_PATH_SIZE, (WIN32_FIND_DATA *)&wfd, SLGP_SHORTPATH ); if (SUCCEEDED(hres) && Strlen(szGotPath) > 0) { #ifdef UNICODE TCHAR TmpPath[MAX_PATH_LEN]; ssize_t TpLen = _GetLongPathName(szGotPath, TmpPath, CountOf(TmpPath)) * sizeof(TmpPath[0]); const void *Tp = TmpPath; ssize_t OutLen = LBufConvertCp(Path, "utf-8", Len-1, Tp, LGI_WideCharset, TpLen); Path[OutLen] = 0; #else _GetLongPathName(szGotPath, Path, Len); #endif Status = true; } } } ppf->Release(); } psl->Release(); } CoUninitialize(); return Status; } void WriteStr(LFile &f, const char *s) { auto Len = (s) ? strlen(s) : 0; f << Len; if (Len > 0) { f.Write(s, Len); } } char *ReadStr(LFile &f DeclDebugArgs) { char *s = 0; // read the strings length... uint32_t Len; f >> Len; if (Len > 0) { // 16mb sanity check.... anything over this // is _probably_ an error if (Len >= (16 << 20)) { // LAssert(0); return 0; } // allocate the memory buffer #if defined MEMORY_DEBUG s = new(_file, _line) char[Len+1]; #else s = new char[Len+1]; #endif if (s) { // read the bytes from disk f.Read(s, Len); s[Len] = 0; } else { // memory allocation error, skip the data // on disk so the caller is where they think // they are in the file. f.Seek(Len, SEEK_CUR); } } return s; } ssize_t SizeofStr(const char *s) { return sizeof(uint32_t) + ((s) ? strlen(s) : 0); } bool LGetDriveInfo ( char *Path, uint64 *Free, uint64 *Size, uint64 *Available ) { bool Status = false; ULARGE_INTEGER available = {0, 0}; ULARGE_INTEGER total = {0, 0}; ULARGE_INTEGER free = {0, 0}; if (Path) { LAutoWString w(Utf8ToWide(Path)); if (w) { char16 *d = StrchrW(w, DIR_CHAR); if (d) *d = 0; if (GetDiskFreeSpaceExW(w, &available, &total, &free)) { if (Free) *Free = free.QuadPart; if (Size) *Size = total.QuadPart; if (Available) *Available = available.QuadPart; Status = true; } } } return Status; } /****************************** Classes ***************************************/ struct LVolumePriv { LString Name; LString Path; int Type = VT_NONE; int Flags = 0; // VA_?? int64 Size = 0; int64 Free = 0; LSystemPath SysPath = LSP_ROOT; LAutoPtr Icon; LVolume *NextVol = NULL, *ChildVol = NULL; LVolumePriv(LSystemPath sysPath, const char *name) { SysPath = sysPath; Name = name; Type = VT_FOLDER; int Id = 0; switch (Type) { case LSP_HOME: { Id = CSIDL_PROFILE; break; } case LSP_DESKTOP: { Type = VT_DESKTOP; Id = CSIDL_DESKTOPDIRECTORY; break; } } if (Id) Path = WinGetSpecialFolderPath(Id); else Path = LGetSystemPath(SysPath); } LVolumePriv(const char *Drive) { int type = GetDriveTypeA(Drive); if (type != DRIVE_UNKNOWN && type != DRIVE_NO_ROOT_DIR) { char Buf[DIR_PATH_SIZE]; const char *Desc = NULL; switch (type) { case DRIVE_REMOVABLE: { Type = VT_USB_FLASH; if (GetVolumeInformationA(Drive, Buf, sizeof(Buf), 0, 0, 0, 0, 0) && ValidStr(Buf)) Desc = Buf; else Desc = "Usb Drive"; break; } case DRIVE_REMOTE: Desc = "Network"; Type = VT_NETWORK_SHARE; break; case DRIVE_CDROM: Desc = "Cdrom"; Type = VT_CDROM; break; case DRIVE_RAMDISK: Desc = "Ramdisk"; Type = VT_RAMDISK; break; case DRIVE_FIXED: default: { if (GetVolumeInformationA(Drive, Buf, sizeof(Buf), 0, 0, 0, 0, 0) && ValidStr(Buf)) Desc = Buf; else Desc = "Hard Disk"; Type = VT_HARDDISK; ULARGE_INTEGER Avail, TotalBytes, FreeBytes; if (GetDiskFreeSpaceExA(Drive, &Avail, &TotalBytes, &FreeBytes)) { Free = FreeBytes.QuadPart; Size = TotalBytes.QuadPart; } break; } } char s[DIR_PATH_SIZE]; if (Desc) sprintf_s(s, sizeof(s), "%s (%.2s)", Desc, Drive); else sprintf_s(s, sizeof(s), "%s", Drive); Name = s; Path = Drive; } } ~LVolumePriv() { DeleteObj(NextVol); DeleteObj(ChildVol); } void Insert(LVolume *newVol) { if (ChildVol) { for (auto v = ChildVol; v; v = v->d->NextVol) { if (!v->d->NextVol) { v->d->NextVol = newVol; break; } } } else ChildVol = newVol; } LVolume *First() { if (!ChildVol) { if (SysPath == LSP_DESKTOP) { // Add some basic shortcuts LSystemPath Paths[] = {LSP_HOME, LSP_USER_DOCUMENTS, LSP_USER_MUSIC, LSP_USER_VIDEO, LSP_USER_DOWNLOADS, LSP_USER_PICTURES}; const char *Names[] = {"Home", "Documents", "Music", "Video", "Downloads", "Pictures"}; for (unsigned i=0; i 0) for (char *p = Str; *p; p += strlen(p) + 1) Insert(new LVolume(p)); } } return ChildVol; } LVolume *Next() { if (SysPath == LSP_DESKTOP && !NextVol) { NextVol = new LVolume(LSP_MOUNT_POINT, "Drives"); } return NextVol; } }; LVolume::LVolume(const char *Path) { d = new LVolumePriv(Path); } LVolume::LVolume(LSystemPath SysPath, const char *Name) { d = new LVolumePriv(SysPath, Name); } LVolume::~LVolume() { DeleteObj(d); } const char *LVolume::Name() const { return d->Name; } const char *LVolume::Path() const { return d->Path; } int LVolume::Type() const { return d->Type; } // VT_?? int LVolume::Flags() const { return d->Flags; } uint64 LVolume::Size() const { return d->Size; } uint64 LVolume::Free() const { return d->Free; } LSurface *LVolume::Icon() const { return d->Icon; } bool LVolume::IsMounted() const { return false; } bool LVolume::SetMounted(bool Mount) { return Mount; } void LVolume::Insert(LAutoPtr v) { d->Insert(v.Release()); } LVolume *LVolume::First() { return d->First(); } LVolume *LVolume::Next() { return d->Next(); } LDirectory *LVolume::GetContents() { LDirectory *Dir = 0; if (d->Path.Get()) { if (Dir = new LDirectory) { if (!Dir->First(d->Path)) { DeleteObj(Dir); } } } return Dir; } //////////////////////////////////////////////////////////////////////////////// LFileSystem *LFileSystem::Instance = 0; class LFileSystemPrivate { public: LFileSystemPrivate() { } ~LFileSystemPrivate() { } }; LFileSystem::LFileSystem() { Instance = this; d = new LFileSystemPrivate; Root = 0; } LFileSystem::~LFileSystem() { DeleteObj(Root); DeleteObj(d); } void LFileSystem::OnDeviceChange(char *Reserved) { DeleteObj(Root); } LVolume *LFileSystem::GetRootVolume() { if (!Root) Root = new LVolume(LSP_DESKTOP, "Desktop"); return Root; } bool LFileSystem::Copy(const char *From, const char *To, LError *ErrorCode, CopyFileCallback Callback, void *Token) { if (!From || !To) { if (ErrorCode) *ErrorCode = ERROR_INVALID_PARAMETER; return false; } LFile In, Out; if (!In.Open(From, O_READ)) { if (ErrorCode) *ErrorCode = In.GetError(); return false; } if (!Out.Open(To, O_WRITE)) { if (ErrorCode) *ErrorCode = Out.GetError(); return false; } if (Out.SetSize(0)) { if (ErrorCode) *ErrorCode = ERROR_WRITE_FAULT; return false; } int64 Size = In.GetSize(); if (!Size) { return true; } int64 Block = min((1 << 20), Size); char *Buf = new char[Block]; if (!Buf) { if (ErrorCode) *ErrorCode = ERROR_NOT_ENOUGH_MEMORY; return false; } int64 i = 0; while (i < Size) { auto r = In.Read(Buf, Block); if (r > 0) { ssize_t Written = 0; while (Written < r) { auto w = Out.Write(Buf + Written, r - Written); if (w > 0) { Written += w; } else { if (ErrorCode) *ErrorCode = ERROR_WRITE_FAULT; goto ExitCopyLoop; } } i += Written; if (Callback) { if (!Callback(Token, i, Size)) { break; } } } else break; } ExitCopyLoop: DeleteArray(Buf); if (i == Size) { if (ErrorCode) *ErrorCode = ERROR_SUCCESS; } else { Out.Close(); Delete(To, false); } return i == Size; } bool LFileSystem::Delete(LArray &Files, LArray *Status, bool ToTrash) { bool Ret = true; if (ToTrash) { LArray Name; for (int i=0; i w(Utf8ToWide(Files[i])); auto Chars = StrlenW(w); auto Len = Name.Length(); Name.Length(Len + Chars + 1); StrcpyW(Name.AddressOf(Len), w.Get()); } - Name.Add(0); + Name.Add(NULL); SHFILEOPSTRUCTW s; ZeroObj(s); s.hwnd = 0; s.wFunc = FO_DELETE; s.pFrom = Name.AddressOf(); s.fFlags = FOF_NOCONFIRMATION; // FOF_ALLOWUNDO; int e = SHFileOperationW(&s); Ret = e == 0; - if (Status && e) + if (Status) { + int val = ERROR_SUCCESS; + if (s.fAnyOperationsAborted) + val = ERROR_CANCELLED; + else if (e) + val = ERROR_CANT_DELETE_LAST_ITEM; // There isn't a better option here... + for (int i=0; i Files; Files.Add(FileName); return Delete(Files, 0, ToTrash); } bool LFileSystem::CreateFolder(const char *PathName, bool CreateParentFoldersIfNeeded, LError *Err) { LAutoWString w(Utf8ToWide(PathName)); bool Status = ::CreateDirectoryW(w, NULL) != 0; if (!Status) { int Code = GetLastError(); if (Err) Err->Set(Code); if (CreateParentFoldersIfNeeded && Code == ERROR_PATH_NOT_FOUND) { char Base[DIR_PATH_SIZE]; strcpy_s(Base, sizeof(Base), PathName); do { char *Leaf = strrchr(Base, DIR_CHAR); if (!Leaf) return false; *Leaf = 0; } while (!LDirExists(Base)); LString::Array Parts = LString(PathName + strlen(Base)).SplitDelimit(DIR_STR); for (int i=0; iSet(GetLastError()); break; } } } } return Status; } bool LFileSystem::RemoveFolder(const char *PathName, bool Recurse) { if (Recurse) { LDirectory Dir; if (Dir.First(PathName)) { do { char Str[DIR_PATH_SIZE]; if (Dir.Path(Str, sizeof(Str))) { if (Dir.IsDir()) { RemoveFolder(Str, Recurse); } else { Delete(Str, false); } } } while (Dir.Next()); } } #ifdef UNICODE LAutoWString w(Utf8ToWide(PathName)); if (!::RemoveDirectory(w)) #else if (!::RemoveDirectory(PathName)) #endif { #ifdef _DEBUG DWORD e = GetLastError(); #endif return false; } return true; } bool LFileSystem::Move(const char *OldName, const char *NewName, LError *Err) { if (!OldName || !NewName) { if (Err) Err->Set(LErrorInvalidParam); return false; } LAutoWString New(Utf8ToWide(NewName)); LAutoWString Old(Utf8ToWide(OldName)); if (!New || !Old) { if (Err) Err->Set(LErrorNoMem); return false; } if (!::MoveFileW(Old, New)) { if (Err) Err->Set(GetLastError()); return false; } return true; } /* bool LFileSystem::GetVolumeInfomation(char Drive, VolumeInfo *pInfo) { bool Status = false; if (pInfo) { char Name[] = "A:\\"; uint32 Serial; Name[0] = Drive + 'A'; if (GetVolumeInformation( Name, pInfo->Name, sizeof(pInfo->Name), &Serial, &pInfo->MaxPath, &pInfo->Flags, pInfo->System, sizeof(pInfo->System))) { pInfo->Drive = Drive; Status = true; } } return Status; } */ void GetFlagsStr(char *flagstr, short flags) { if (flagstr) { flagstr[0] = 0; if (flags & O_WRONLY) { strcat(flagstr,"w"); } else if (flags & O_APPEND) { strcat(flagstr,"a"); } else { strcat(flagstr,"r"); } if ((flags & O_RDWR) == O_RDWR) { strcat(flagstr,"+"); } strcat(flagstr,"b"); } } bool Match(char *Name, char *Mask) { while (*Name && *Mask) { if (*Mask == '*') { if (toupper(*Name) == toupper(Mask[1])) { Mask++; } else { Name++; } } else if (*Mask == '?' || toupper(*Mask) == toupper(*Name)) { Mask++; Name++; } else { return false; } } while (*Mask && ((*Mask == '*') || (*Mask == '.'))) Mask++; return (*Name == 0 && *Mask == 0); } short DaysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int LeapYear(int year) { if (year & 3) { return 0; } if ((year % 100 == 0) && !(year % 400 == 0)) { return 0; } return 1; } // Disk Infomation typedef struct { uchar bsJump[3]; uchar bsOemName[8]; ushort bsBytePerSec; uchar bsSecPerCluster; ushort bsResSectores; uchar bsFAT; ushort bsRootDirEnts; ushort bsSectors; uchar bsMedia; ushort bsFATsecs; ushort bsSecPerTrack; ushort bsHeads; ulong bsHiddenSecs; ulong bsHugeSectoes; uchar bsDriveNumber; uchar bsReserved; uchar bsBootSig; ulong bsVolumeID; // serial number uchar bsVolumeLabel[11]; uchar bsFileSysType[8]; } BOOTSECTOR; typedef struct { ulong diStartSector; ushort diSectors; char *diBuffer; } DISKIO; ///////////////////////////////////////////////////////////////////////////////// bool LDirectory::ConvertToTime(char *Str, int SLen, uint64 Time) const { if (Str) { FILETIME t; SYSTEMTIME s; t.dwHighDateTime = Time >> 32; t.dwLowDateTime = Time & 0xFFFFFFFF; if (FileTimeToSystemTime(&t, &s)) { int Hour = (s.wHour < 1) ? s.wHour + 23 : s.wHour - 1; char AP = (Hour >= 12) ? 'a' : 'p'; Hour %= 12; if (Hour == 0) Hour = 12; sprintf_s(Str, SLen, "%i:%02.2i:%02.2i %c", Hour-1, s.wMinute, s.wSecond, AP); } } return false; } bool LDirectory::ConvertToDate(char *Str, int SLen, uint64 Time) const { if (Str) { FILETIME t; SYSTEMTIME s; t.dwHighDateTime = Time >> 32; t.dwLowDateTime = Time & 0xFFFFFFFF; if (FileTimeToSystemTime(&t, &s)) { sprintf_s(Str, SLen, "%i/%i/%i", s.wDay, s.wMonth, s.wYear); } } return false; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// Directory ////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// struct LDirectoryPriv { char BasePath[DIR_PATH_SIZE]; char *BaseEnd; int BaseRemaining; LString Utf; HANDLE Handle; WIN32_FIND_DATAW Data; LDirectoryPriv() { Handle = INVALID_HANDLE_VALUE; BasePath[0] = 0; BaseRemaining = 0; } ~LDirectoryPriv() { } }; LDirectory::LDirectory() { d = new LDirectoryPriv; } LDirectory::~LDirectory() { Close(); DeleteObj(d); } LDirectory *LDirectory::Clone() { return new LDirectory; } const char *LDirectory::FullPath() { auto n = GetName(); if (!n) return NULL; strcpy_s(d->BaseEnd, d->BaseRemaining, n); return d->BasePath; } bool LDirectory::Path(char *s, int BufLen) const { auto e = d->BasePath + strlen(d->BasePath); auto sep = e > d->BasePath && e[-1] == DIR_CHAR ? "" : DIR_STR; auto ch = sprintf_s(s, BufLen, "%s%s%s", d->BasePath, sep, GetName()); return ch > 0; } size_t Utf8To16Cpy(uint16_t *out, ssize_t outChar, uint8_t *in, ssize_t inChar = -1) { auto start = out; int32 u32; outChar <<= 1; // words to bytes if (inChar < 0) { while (*in && outChar >= 4) { ssize_t len = 6; u32 = LgiUtf8To32(in, len); LgiUtf32To16(u32, out, outChar); } } else { while (outChar >= 4 && (u32 = LgiUtf8To32(in, inChar))) { LgiUtf32To16(u32, out, outChar); } } outChar >>= 1; // bytes to words if (outChar > 0) *out = 0; return out - start; } size_t Utf16To8Cpy(uint8_t *out, ssize_t outChar, const uint16_t *in, ssize_t inChar = -1) { auto start = out; int32 u32; if (inChar < 0) { while (*in && outChar > 0) { ssize_t len = 4; u32 = LgiUtf16To32(in, len); LgiUtf32To8(u32, out, outChar); } } else { inChar <<= 1; // words to bytes while (outChar > 0 && (u32 = LgiUtf16To32(in, inChar))) { LgiUtf32To8(u32, out, outChar); } inChar >>= 1; // bytes to words } if (outChar > 0) *out = 0; return out - start; } template size_t UnicodeCpy(O *out, ssize_t outChar, I *in, ssize_t inChar = -1) { if (out == NULL || in == NULL) return 0; if (sizeof(O) == 2 && sizeof(I) == 1) return Utf8To16Cpy((uint16_t*)out, outChar, (uint8_t*)in, inChar); else if (sizeof(O) == 1 && sizeof(I) == 2) return Utf16To8Cpy((uint8_t*)out, outChar, (uint16_t*)in, inChar); else LAssert(0); return 0; } int LDirectory::First(const char *InName, const char *Pattern) { Close(); d->Utf.Empty(); if (!InName) return false; wchar_t wTmp[DIR_PATH_SIZE], wIn[DIR_PATH_SIZE]; UnicodeCpy(wTmp, CountOf(wTmp), InName); auto Chars = GetFullPathNameW(wTmp, CountOf(wTmp), wIn, NULL); UnicodeCpy(d->BasePath, sizeof(d->BasePath), wIn, Chars); d->BaseEnd = d->BasePath + strlen(d->BasePath); if (d->BaseEnd > d->BasePath && d->BaseEnd[-1] != DIR_CHAR) { *d->BaseEnd++ = DIR_CHAR; *d->BaseEnd = 0; } ssize_t Used = d->BaseEnd - d->BasePath; d->BaseRemaining = (int) (sizeof(d->BasePath) - Used); char Str[DIR_PATH_SIZE]; wchar_t *FindArg; if (Pattern) { if (!LMakePath(Str, sizeof(Str), d->BasePath, Pattern)) return false; UnicodeCpy(FindArg = wTmp, CountOf(wTmp), Str); } else { FindArg = wIn; } d->Handle = FindFirstFileW(FindArg, &d->Data); if (d->Handle != INVALID_HANDLE_VALUE) { const char *n; while ( (n = GetName()) && (stricmp(n, ".") == 0 || stricmp(n, "..") == 0)) { if (!FindNextFileW(d->Handle, &d->Data)) return false; d->Utf.Empty(); } } return d->Handle != INVALID_HANDLE_VALUE; } int LDirectory::Next() { if (d->Handle == INVALID_HANDLE_VALUE) return false; const char *n; do { d->Utf.Empty(); if (!FindNextFileW(d->Handle, &d->Data)) return false; if (!(n = GetName())) return false; } while (stricmp(n, ".") == 0 || stricmp(n, "..") == 0); return true; } int LDirectory::Close() { if (d->Handle != INVALID_HANDLE_VALUE) { FindClose(d->Handle); d->Handle = INVALID_HANDLE_VALUE; } d->Utf.Empty(); return true; } int LDirectory::GetUser(bool Group) const { return 0; } bool LDirectory::IsReadOnly() const { return (d->Data.dwFileAttributes & FA_READONLY) != 0; } bool LDirectory::IsDir() const { return (d->Data.dwFileAttributes & FA_DIRECTORY) != 0; } bool LDirectory::IsSymLink() const { return (d->Data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } bool LDirectory::IsHidden() const { return (d->Data.dwFileAttributes & FA_HIDDEN) != 0; } long LDirectory::GetAttributes() const { return d->Data.dwFileAttributes; } int LDirectory::GetType() const { return IsDir() ? VT_FOLDER : VT_FILE; } char *LDirectory::GetName() const { if (!d->Utf) d->Utf = d->Data.cFileName; return d->Utf; } LString LDirectory::FileName() const { if (!d->Utf) d->Utf = d->Data.cFileName; return d->Utf; } uint64 LDirectory::GetCreationTime() const { return ((uint64) d->Data.ftCreationTime.dwHighDateTime) << 32 | d->Data.ftCreationTime.dwLowDateTime; } uint64 LDirectory::GetLastAccessTime() const { return ((uint64) d->Data.ftLastAccessTime.dwHighDateTime) << 32 | d->Data.ftLastAccessTime.dwLowDateTime; } uint64 LDirectory::GetLastWriteTime() const { return ((uint64) d->Data.ftLastWriteTime.dwHighDateTime) << 32 | d->Data.ftLastWriteTime.dwLowDateTime; } uint64 LDirectory::GetSize() const { return ((uint64) d->Data.nFileSizeHigh) << 32 | d->Data.nFileSizeLow; } struct ClusterSizeMap { ClusterSizeMap() { ZeroObj(Sizes); } uint64 GetDriveCluserSize(char Letter) { uint64 Cs = 4096; auto letter = ToLower(Letter) - 'a'; Cs = Sizes[letter]; if (!Cs) { DWORD SectorsPerCluster, BytesPerSector; const char Drive[] = { Letter , ':', '\\', 0 }; BOOL b = GetDiskFreeSpaceA(Drive, &SectorsPerCluster, &BytesPerSector, NULL, NULL); if (b) Sizes[letter] = Cs = SectorsPerCluster * BytesPerSector; else LAssert(0); } return Cs; } private: uint64 Sizes[26]; } ClusterSizes; int64 LDirectory::GetSizeOnDisk() { auto Fp = FullPath(); if (!Fp || !IsAlpha(Fp[0])) return -1; auto ClusterSize = ClusterSizes.GetDriveCluserSize(Fp[0]); DWORD HighSize = 0; DWORD LoSize = GetCompressedFileSizeA(FullPath(), &HighSize); *d->BaseEnd = 0; auto Size = ((uint64)HighSize << 32) | LoSize; return ((Size + ClusterSize - 1) / ClusterSize) * ClusterSize; } ///////////////////////////////////////////////////////////////////////////////// //////////////////////////// File /////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// class LFilePrivate { public: OsFile hFile; LString Name; int Attributes; bool Swap; bool Status; DWORD LastError; // Threading check stuff OsThreadId CreateId, UseId; bool ThreadWarn; LFilePrivate() { hFile = INVALID_HANDLE; Attributes = 0; Swap = false; Status = false; LastError = 0; ThreadWarn = true; CreateId = LThread::InvalidId; UseId = LThread::InvalidId; } bool UseThreadCheck() { auto Cur = GetCurrentThreadId(); if (UseId == LThread::InvalidId) { UseId = Cur; } else if (Cur != UseId) { if (ThreadWarn) { ThreadWarn = false; LgiTrace("%s:%i - Warning: multi-threaded file access (me=%x, user=%x).\n", _FL, Cur, UseId); } return false; } return true; } void OnError(const char *File, int Line, DWORD Code, const char *Ctx) { LgiTrace("%s:%i - LFile::%s(%s) Err=0x%x\n", File, Line, Ctx, Name.Get(), LastError = Code); } }; LFile::LFile(const char *Path, int Mode) { d = new LFilePrivate; if (Path) Open(Path, Mode); } LFile::~LFile() { if (ValidHandle(d->hFile)) { Close(); } DeleteObj(d); } OsFile LFile::Handle() { return d->hFile; } void LFile::ChangeThread() { d->UseId = GetCurrentThreadId(); } +uint64_t LFile::GetModifiedTime() +{ + FILETIME create, access, write; + if (!GetFileTime(Handle(), &create, &access, &write)) + return 0; + + return (((uint64_t)write.dwHighDateTime) << 32) | write.dwLowDateTime; +} + +bool LFile::SetModifiedTime(uint64_t dt) +{ + FILETIME create, access, write; + if (!GetFileTime(Handle(), &create, &access, &write)) + return false; + + write.dwHighDateTime = dt >> 32; + write.dwLowDateTime = dt & 0xffffffff; + + auto result = SetFileTime(Handle(), + &create, + &access, + &write); + + return result != 0; +} + const char *LFile::GetName() { return d->Name; } void LFile::SetSwap(bool s) { d->Swap = s; } bool LFile::GetSwap() { return d->Swap; } int LFile::GetOpenMode() { return d->Attributes; } int LFile::GetBlockSize() { DWORD SectorsPerCluster; DWORD BytesPerSector = 0; DWORD NumberOfFreeClusters; DWORD TotalNumberOfClusters; #ifdef UNICODE LAutoWString n(Utf8ToWide(GetName())); #else LAutoString n(NewStr(GetName())); #endif if (n) { TCHAR *dir = n ? Strchr(n.Get(), '\\') : 0; if (dir) dir[1] = 0; GetDiskFreeSpace( n, &SectorsPerCluster, &BytesPerSector, &NumberOfFreeClusters, &TotalNumberOfClusters); } return BytesPerSector; } int LFile::Open(const char *File, int Mode) { int Status = false; bool NoCache = (Mode & O_NO_CACHE) != 0; Mode &= ~O_NO_CACHE; Close(); if (File) { bool SharedAccess = (Mode & O_SHARE) != 0; Mode &= ~O_SHARE; if (File[0] == '/' && File[1] == '/') { // This hangs the process, so just bail safely. return false; } LAutoWString n(Utf8ToWide(File)); if (n) { d->hFile = CreateFileW( n, Mode, FILE_SHARE_READ | (SharedAccess ? FILE_SHARE_WRITE : 0), 0, (Mode & O_WRITE) ? OPEN_ALWAYS : OPEN_EXISTING, NoCache ? FILE_FLAG_NO_BUFFERING : 0, NULL); } if (!ValidHandle(d->hFile)) { // d->OnError(_FL, GetLastError(), "Open"); switch (d->LastError = GetLastError()) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: { // Path/File not found; int i=0; break; } case ERROR_ACCESS_DENIED: { // Access is denied: // Read-Only or another process has the file open int i=0; break; } } } else { d->Attributes = Mode; d->Name = File; Status = true; d->Status = true; d->CreateId = GetCurrentThreadId(); } } return Status; } bool LFile::IsOpen() { return ValidHandle(d->hFile); } int LFile::GetError() { return d->LastError; } int LFile::Close() { int Status = false; d->UseThreadCheck(); if (ValidHandle(d->hFile)) { ::CloseHandle(d->hFile); d->hFile = INVALID_HANDLE; } d->Name.Empty(); return Status; } int64 LFile::GetSize() { LAssert(IsOpen()); d->UseThreadCheck(); DWORD High = -1; DWORD Low = GetFileSize(d->hFile, &High); return Low | ((int64)High<<32); } int64 LFile::SetSize(int64 Size) { LAssert(IsOpen()); d->UseThreadCheck(); LONG OldPosHigh = 0; DWORD OldPosLow = SetFilePointer(d->hFile, 0, &OldPosHigh, SEEK_CUR); LONG SizeHigh = Size >> 32; DWORD r = SetFilePointer(d->hFile, (LONG)Size, &SizeHigh, FILE_BEGIN); BOOL b = SetEndOfFile(d->hFile); if (!b) { DWORD err = GetLastError(); LgiTrace("%s:%i - SetSize(" LPrintfInt64 ") failed: 0x%x\n", _FL, Size, err); } SetFilePointer(d->hFile, OldPosLow, &OldPosHigh, FILE_BEGIN); return GetSize(); } int64 LFile::GetPos() { LAssert(IsOpen()); d->UseThreadCheck(); LONG PosHigh = 0; DWORD PosLow = SetFilePointer(d->hFile, 0, &PosHigh, FILE_CURRENT); return PosLow | ((int64)PosHigh<<32); } int64 LFile::SetPos(int64 Pos) { LAssert(IsOpen()); d->UseThreadCheck(); LONG PosHigh = Pos >> 32; DWORD PosLow = SetFilePointer(d->hFile, (LONG)Pos, &PosHigh, FILE_BEGIN); return PosLow | ((int64)PosHigh<<32); } ssize_t LFile::Read(void *Buffer, ssize_t Size, int Flags) { ssize_t Rd = 0; d->UseThreadCheck(); // This loop allows ReadFile (32bit) to read more than 4GB in one go. If need be. for (ssize_t Pos = 0; Pos < Size; ) { DWORD Bytes = 0; int BlockSz = (int) MIN( Size - Pos, 1 << 30 ); // 1 GiB blocks if (ReadFile(d->hFile, (char*)Buffer + Pos, BlockSz, &Bytes, NULL) && Bytes > 0) { Rd += Bytes; Pos += Bytes; d->Status &= Bytes > 0; } else { if (!Eof()) d->OnError(_FL, GetLastError(), "Read"); break; } } return Rd; } ssize_t LFile::Write(const void *Buffer, ssize_t Size, int Flags) { ssize_t Wr = 0; d->UseThreadCheck(); // This loop allows WriteFile (32bit) to read more than 4GB in one go. If need be. for (ssize_t Pos = 0; Pos < Size; ) { DWORD Bytes = 0; int BlockSz = (int) MIN( Size - Pos, 1 << 30 ); // 1 GiB blocks if (WriteFile(d->hFile, (const char*)Buffer + Pos, BlockSz, &Bytes, NULL)) { if (Bytes != BlockSz) { LAssert(!"Is this ok?"); } Wr += Bytes; Pos += Bytes; d->Status &= Bytes > 0; } else { d->OnError(_FL, GetLastError(), "Write"); break; } } return Wr; } int64 LFile::Seek(int64 To, int Whence) { LAssert(IsOpen()); d->UseThreadCheck(); int Mode; switch (Whence) { default: case SEEK_SET: Mode = FILE_BEGIN; break; case SEEK_CUR: Mode = FILE_CURRENT; break; case SEEK_END: Mode = FILE_END; break; } LONG ToHigh = To >> 32; DWORD ToLow = SetFilePointer(d->hFile, (LONG)To, &ToHigh, Mode); return ToLow | ((int64)ToHigh<<32); } bool LFile::Eof() { LAssert(IsOpen()); return GetPos() >= GetSize(); } ssize_t LFile::SwapRead(uchar *Buf, ssize_t Size) { DWORD r = 0; d->UseThreadCheck(); if (!ReadFile(d->hFile, Buf, (DWORD)Size, &r, NULL) || r != Size) { d->OnError(_FL, GetLastError(), "SwapRead"); return 0; } // Swap the bytes uchar *s = Buf, *e = Buf + Size - 1; while (s < e) { uchar t = *s; *s++ = *e; *e-- = t; } return r; } ssize_t LFile::SwapWrite(uchar *Buf, ssize_t Size) { int i = 0; DWORD w; Buf = &Buf[Size-1]; d->UseThreadCheck(); while (Size--) { if (!(d->Status &= WriteFile(d->hFile, Buf--, 1, &w, NULL) != 0 && w == 1)) break; i += w; } return i; } ssize_t LFile::ReadStr(char *Buf, ssize_t Size) { int i = 0; DWORD r; d->UseThreadCheck(); if (Buf && Size > 0) { char c; Size--; do { ReadFile(d->hFile, &c, 1, &r, NULL); if (Eof()) { break; } *Buf++ = c; i++; } while (i < Size - 1 && c != '\n'); *Buf = 0; } return i; } ssize_t LFile::WriteStr(char *Buf, ssize_t Size) { int i = 0; DWORD w; d->UseThreadCheck(); while (i <= Size) { WriteFile(d->hFile, Buf, 1, &w, NULL); Buf++; i++; if (*Buf == '\n') break; } return i; } void LFile::SetStatus(bool s) { d->Status = s; } bool LFile::GetStatus() { return d->Status; } #define GFileOp(type) LFile &LFile::operator >> (type &i) \ { \ if (d->Swap) SwapRead((uchar*) &i, sizeof(i)); \ else Read(&i, sizeof(i)); \ return *this; \ } GFileOps(); #undef GFileOp #define GFileOp(type) LFile &LFile::operator << (type i) \ { \ if (d->Swap) SwapWrite((uchar*) &i, sizeof(i)); \ else Write(&i, sizeof(i)); \ return *this; \ } GFileOps(); #undef GFileOp diff --git a/src/win/Lgi/Menu.cpp b/src/win/Lgi/Menu.cpp --- a/src/win/Lgi/Menu.cpp +++ b/src/win/Lgi/Menu.cpp @@ -1,1315 +1,1325 @@ /*hdr ** FILE: GuiMenu.cpp ** AUTHOR: Matthew Allen ** DATE: 18/7/98 ** DESCRIPTION: Gui menu system ** ** Copyright (C) 1998, Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Token.h" #include "lgi/common/DisplayString.h" #include "lgi/common/Menu.h" #include "lgi/common/ToolBar.h" /////////////////////////////////////////////////////////////////////////////////////////////// #define LGI_OWNER_DRAW_MENUS LColour LGetSysColor(int nIndex) { LColour c; auto i = GetSysColor(nIndex); c.Rgb(GetRValue(i), GetGValue(i), GetBValue(i)); return c; } class LMenuPrivate { public: LColour RootMenuBack; LMenuPrivate() { LArray Ver; int Os = LGetOs(&Ver); if ( ( Os == LGI_OS_WIN32 || Os == LGI_OS_WIN64 ) && ( Ver[0] > 5 || (Ver[0] == 5 && Ver[1] > 0) ) ) { #ifndef SPI_GETFLATMENU #define SPI_GETFLATMENU 0x1022 #endif BOOL Flat = true; SystemParametersInfo(SPI_GETFLATMENU, 0, &Flat, 0); if (Flat) RootMenuBack = LGetSysColor(COLOR_MENUBAR); else RootMenuBack = LGetSysColor(COLOR_MENU); // LgiTrace("NT: flat=%i, menu=%x\n", Flat, RootMenuBack); } else { RootMenuBack = LGetSysColor(COLOR_MENU); // LgiTrace("9x: menu=%x\n", RootMenuBack); } } }; /////////////////////////////////////////////////////////////////////////////////////////////// LSubMenu::LSubMenu(const char *name, bool Popup) { if (Popup) { Info = CreatePopupMenu(); } else { Info = CreateMenu(); } Menu = 0; Parent = 0; TrackHandle = 0; Window = 0; } LSubMenu::~LSubMenu() { if (TrackHandle) { // At least in Win98, if you call InvalidateRect(...) on a window // multiple times just after you use TracePopupMenu(...) on that // window the invalid region isn't updated. // // This behaviour is not by design, it is a bug. However this // update forces the window to repaint thus working around the // bug in windows. UpdateWindow(TrackHandle); TrackHandle = 0; } if (Info) { DestroyMenu(Info); Info = 0; } if (Parent) { Parent->Child = 0; } LMenuItem *i; while (i = Items[0]) { LAssert(i->Parent == this); DeleteObj(i); } } void LSubMenu::SysMouseClick(LMouse &m) { } LMenuItem *LSubMenu::AppendItem(const char *Str, int Id, bool Enabled, int Where, const char *Shortcut) { int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length())); LMenuItem *Item = new LMenuItem(Menu, this, Str, Id, Pos, Shortcut); if (Item) { Item->Enabled(Enabled); Items.Insert(Item, Where); Item->ScanForAccel(); } if (Menu) { Menu->OnChange(); } return Item; } LMenuItem *LSubMenu::AppendSeparator(int Where) { LMenuItem *Item = new LMenuItem; if (Item) { Item->Menu = Menu; Items.Insert(Item, Where); Item->Position = (int) Items.IndexOf(Item); Item->Separator(true); Item->Parent = this; Item->Insert(Item->Position); } if (Menu) { Menu->OnChange(); } return Item; } LSubMenu *LSubMenu::AppendSub(const char *Str, int Where) { int Pos = (int) (Where < 0 ? Items.Length() : min(Where, Items.Length())); LMenuItem *Item = new LMenuItem(Menu, this, Str, ItemId_Submenu, Pos); LSubMenu *Sub = new LSubMenu; if (Item && Sub) { Items.Insert(Item, Where); Item->Sub(Sub); } else { DeleteObj(Item); DeleteObj(Sub); } if (Menu) { Menu->OnChange(); } return Sub; } void LSubMenu::Empty() { while (RemoveItem(0)); } bool LSubMenu::RemoveItem(int i) { LMenuItem *Item = Items[i]; if (Item && Item->Remove()) { DeleteObj(Item); return true; } return false; } bool LSubMenu::RemoveItem(LMenuItem *Item) { if (Item && Items.HasItem(Item) && Item->Remove()) { return true; } return false; } int LSubMenu::Float(LView *From, int x, int y, int Button) { int Cmd = 0; if (From && Info) { LViewI *Wnd = From; while (Wnd && !Wnd->Handle()) { Wnd = Wnd->GetParent(); } if (Wnd && Wnd->Handle()) { Cmd = TrackPopupMenu( Info, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | ((Button == BtnLeft) ? TPM_LEFTBUTTON : TPM_RIGHTBUTTON), x, y, 0, Wnd->Handle(), NULL); } else { LgiTrace("%s:%i - No handle to track popup menu.\n", __FILE__, __LINE__); } } return Cmd; } size_t LSubMenu::Length() { return Items.Length(); } LMenuItem *LSubMenu::ItemAt(int Id) { return Items.ItemAt(Id); } LSubMenu *LSubMenu::FindSubMenu(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return Sub; } else if (Sub) { LSubMenu *m = Sub->FindSubMenu(Id); if (m) { return m; } } } return 0; } LMenuItem *LSubMenu::FindItem(int Id) { for (auto i: Items) { LSubMenu *Sub = i->Sub(); if (i->Id() == Id) { return i; } else if (Sub) { i = Sub->FindItem(Id); if (i) { return i; } } } return 0; } /////////////////////////////////////////////////////////////////////////////////////////////// class LMenuItemPrivate { public: bool StartUnderline; // Underline the first display string bool HasAccel; // The last display string should be right aligned List Strs; // Draw each alternate display string with underline // except the last in the case of HasAccel==true. LString Shortcut; LMenuItemPrivate() { HasAccel = false; StartUnderline = false; } ~LMenuItemPrivate() { Strs.DeleteObjects(); } void UpdateStrings(LFont *Font, char *n) { // Build up our display strings, Strs.DeleteObjects(); StartUnderline = false; char *Tab = strrchr(n, '\t'); if (Tab) { *Tab = 0; } char *Amp = 0, *a = n; while (a = strchr(a, '&')) { if (a[1] != '&') { Amp = a; break; } a++; } if (Amp) { // Before amp Strs.Insert(new LDisplayString(Font, n, Amp - n )); // Amp'd letter char *e = LSeekUtf8(++Amp, 1); Strs.Insert(new LDisplayString(Font, Amp, e - Amp )); // After Amp if (*e) { Strs.Insert(new LDisplayString(Font, e)); } } else { Strs.Insert(new LDisplayString(Font, n)); } if (HasAccel = (Tab != 0)) { Strs.Insert(new LDisplayString(Font, Tab + 1)); *Tab = '\t'; } else if (HasAccel = (Shortcut.Get() != 0)) { Strs.Insert(new LDisplayString(Font, Shortcut)); } } }; LMenuItem::LMenuItem() { d = new LMenuItemPrivate; Menu = NULL; Parent = NULL; Child = 0; Position = -1; _Icon = -1; ZeroObj(Info); Info.cbSize = sizeof(Info); #ifdef LGI_OWNER_DRAW_MENUS Info.fMask = MIIM_DATA; Info.fType = MFT_OWNERDRAW; #if _MSC_VER >= _MSC_VER_VS2005 Info.dwItemData = (ULONG_PTR)this; #else Info.dwItemData = (DWORD)this; #endif #endif Enabled(true); } LMenuItem::LMenuItem(LMenu *m, LSubMenu *p, const char *Txt, int id, int Pos, const char *Shortcut) { d = new LMenuItemPrivate; d->Shortcut = Shortcut; Position = Pos; Parent = NULL; Menu = m; Parent = NULL; Child = 0; _Icon = -1; ZeroObj(Info); Info.cbSize = sizeof(Info); #ifdef LGI_OWNER_DRAW_MENUS Info.fMask = MIIM_DATA; Info.fType = MFT_OWNERDRAW; #if _MSC_VER >= _MSC_VER_VS2005 Info.dwItemData = (ULONG_PTR)this; #else Info.dwItemData = (DWORD)this; #endif #endif Id(id); Enabled(true); Name(Txt); Parent = p; Insert(Position); } LMenuItem::~LMenuItem() { if (Parent) { if (Parent->Handle()) { int Index = (int)Parent->Items.IndexOf(this); RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION); } Parent->Items.Delete(this); } DeleteObj(Child); DeleteObj(d); } // the following 3 functions paint the menus according the to // windows standard. but also allow for correct drawing of menuitem // icons. some implementations of windows force the program back // to the 8-bit palette when specifying the icon graphic, thus removing // control over the colours displayed. these functions remove that // limitation and also provide the application the ability to override // the default painting behaviour if desired. void LMenuItem::_Measure(LPoint &Size) { if (Separator()) { Size.x = 8; Size.y = 8; } else { LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; bool BaseMenu = Parent == Menu; // true if attached to a windows menu // else is a submenu int Ht = Font->GetHeight(); Size.x = BaseMenu ? 0 : 20; for (auto s: d->Strs) { Size.x += s->X(); } if (d->Shortcut || strchr(Name(), '\t')) { Size.x += 32; } if (!BaseMenu) { // leave room for child pointer Size.x += Child ? 8 : 0; } Size.y = BaseMenu ? GetSystemMetrics(SM_CYMENU)+1 : Ht+2; if (Size.y < 16) Size.y = 16; } } void LMenuItem::_PaintText(LSurface *pDC, int x, int y, int Width) { bool Underline = d->StartUnderline; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; for (int i=0; i < (d->Strs.Length() - (d->HasAccel ? 1 : 0)); i++) { LDisplayString *s = d->Strs[i]; if (!s) break; s->Draw(pDC, x, y); if (Underline) { int UnderX = s->X(); pDC->Colour(Font->Fore()); pDC->Line(x, y+s->Y()-1, x+max(s->X()-2, 1), y+s->Y()-1); } Underline = !Underline; x += s->X(); } if (d->HasAccel) { LDisplayString *s = *d->Strs.rbegin(); if (s) { s->Draw(pDC, Width - s->X() - 8, y); } } } void LMenuItem::_Paint(LSurface *pDC, int Flags) { bool BaseMenu = Parent == Menu; int IconX = BaseMenu ? 5 : 20; bool Selected = TestFlag(Flags, ODS_SELECTED); bool Disabled = TestFlag(Flags, ODS_DISABLED); bool Checked = TestFlag(Flags, ODS_CHECKED); LRect r(0, 0, pDC->X()-1, pDC->Y()-1); auto Text = Name(); if (Separator()) { // paint a separator int Cy = pDC->Y() / 2; pDC->Colour(L_MENU_BACKGROUND); pDC->Rectangle(); pDC->Colour(L_LOW); pDC->Line(0, Cy-1, pDC->X()-1, Cy-1); pDC->Colour(L_LIGHT); pDC->Line(0, Cy, pDC->X()-1, Cy); } else { // paint a text menu item LColour Fore(Selected ? L_FOCUS_SEL_FORE : L_MENU_TEXT); LColour Back(BaseMenu ? Menu->d->RootMenuBack : (Selected ? LColour(L_FOCUS_SEL_BACK) : LColour(L_MENU_BACKGROUND))); int x = IconX; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; int y = (pDC->Y() - Font->GetHeight()) >> 1; // paint the background if (BaseMenu) { // for a menu, sunken on selected LRect rgn = r; if (Selected) { LThinBorder(pDC, rgn, DefaultSunkenEdge); Fore = LColour(L_MENU_TEXT); x++; y++; } // always dialog colour pDC->Colour(Back); pDC->Rectangle(&rgn); } else { // for a sub menu pDC->Colour(Back); pDC->Rectangle(); } // draw the text on top Font->Transparent(true); if (Disabled) { // disabled text if (!Selected) { Font->Colour(L_LIGHT); _PaintText(pDC, x+1, y, r.X()); } // else selected... don't draw the highlight // "grayed" text... Font->Colour(L_LOW); _PaintText(pDC, x, y-1, r.X()-1); } else { // normal coloured text Font->Colour(Fore, Back); _PaintText(pDC, x, y-1, r.X()); } LImageList *ImgLst = (Menu && Menu->GetImageList()) ? Menu->GetImageList() : Parent ? Parent->GetImageList() : 0; // draw icon/check mark if (Checked && IconX > 0) { // it's a check! int x = 4; int y = 6; pDC->Colour(Fore); pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); y++; pDC->Line(x, y, x+2, y+2); pDC->Line(x+2, y+2, x+6, y-2); } else if (ImgLst && _Icon >= 0) { // it's an icon! ImgLst->Draw(pDC, 0, 0, _Icon, Back); } } } bool LMenuItem::ScanForAccel() { LString Accel; if (d->Shortcut) { Accel = d->Shortcut; } else { auto n = LBase::Name(); if (n) { auto Tab = strchr(n, '\t'); if (Tab) Accel = Tab + 1; } } if (Accel) { auto Keys = Accel.SplitDelimit("-+"); if (Keys.Length() > 0) { int Flags = 0; - uchar Key = 0; + int Vkey = 0; + int Chr = 0; bool AccelDirty = false; for (int i=0; iShortcut = LString("+").Join(Keys); LString n = Name(); LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; d->UpdateStrings(Font, n); } - if (Key && Menu) + if ((Vkey || Chr) && Menu) { - Menu->Accel.Insert( new LAccelerator(Flags, Key, Id()) ); + Menu->Accel.Insert( new LAccelerator(Flags, Vkey, Chr, Id()) ); } } } return false; } LSubMenu *LMenuItem::GetParent() { return Parent; } bool LMenuItem::Remove() { bool Status = false; if (Parent) { int Index = (int) Parent->Items.IndexOf(this); Parent->Items.Delete(this); Status = RemoveMenu(Parent->Handle(), Index, MF_BYPOSITION) != 0; int n=0; for (auto i: Parent->Items) { i->Position = n++; } } return Status; } bool LMenuItem::Update() { bool Status = FALSE; if (Parent && Parent->Handle()) { Status = SetMenuItemInfo(Parent->Handle(), Position, true, &Info) != 0; LAssert(Status); } return Status; } void LMenuItem::Icon(int i) { _Icon = i; } int LMenuItem::Icon() { return _Icon; } void LMenuItem::Id(int i) { Info.wID = i; Info.fMask |= MIIM_ID; Update(); } void LMenuItem::Separator(bool s) { if (s) { Info.fType |= MFT_SEPARATOR; } else { Info.fType &= ~MFT_SEPARATOR; } Info.fMask |= MIIM_TYPE; Update(); } void LMenuItem::Checked(bool c) { if (c) { Info.fState |= MFS_CHECKED; } else { Info.fState &= ~MFS_CHECKED; } Info.fMask |= MIIM_STATE; Update(); } bool LMenuItem::Name(const char *Txt) { bool Status = LBase::Name(Txt); if (Status) { LString n = NewStr(Txt); if (n) { // Set OS menu structure Info.dwTypeData = (LPWSTR)LBase::NameW(); Info.cch = (UINT) StrlenW(LBase::NameW()); Info.fType |= MFT_STRING; Info.fMask |= MIIM_TYPE | MIIM_DATA; LFont *Font = Menu && Menu->GetFont() ? Menu->GetFont() : LSysFont; d->UpdateStrings(Font, n); // Tell the OS Update(); } } return Status; } void LMenuItem::Enabled(bool e) { Info.fState &= ~(MFS_ENABLED|MFS_DISABLED|MFS_GRAYED); if (!e) { Info.fState |= MFS_DISABLED|MFS_GRAYED; } Info.fMask |= MIIM_STATE; Update(); // LgiTrace("%s:%i - LMenuItem::Enabled(%i) %s\n", _FL, e, Name()); } void LMenuItem::Focus(bool f) { if (f) { Info.fState |= MFS_HILITE; } else { Info.fState &= ~MFS_HILITE; } Info.fMask |= MIIM_STATE; Update(); } void LMenuItem::Sub(LSubMenu *s) { Child = s; if (Child) { Info.hSubMenu = Child->Handle(); s->Menu = Menu; s->Parent = this; } else { Info.hSubMenu = 0; } Info.fMask |= MIIM_SUBMENU; Update(); } bool LMenuItem::Insert(int Pos) { bool Status = false; if (Parent && Parent->Handle()) { LAssert(Position >= 0); Position = Pos; Status = InsertMenuItem(Parent->Handle(), Position, true, &Info) != 0; LAssert(Status); } return Status; } void LMenuItem::Visible(bool i) { } int LMenuItem::Id() { return Info.wID; } const char *LMenuItem::Name() { return LBase::Name(); } bool LMenuItem::Separator() { return (Info.fType & MFT_SEPARATOR) != 0; } bool LMenuItem::Checked() { return (Info.fState & MF_CHECKED) != 0; } bool LMenuItem::Enabled() { return (Info.fState & MFS_DISABLED) == 0; } bool LMenuItem::Visible() { return true; } bool LMenuItem::Focus() { return (Info.fState & MFS_HILITE) != 0; } LSubMenu *LMenuItem::Sub() { return Child; } /////////////////////////////////////////////////////////////////////////////////////////////// LMenu::LMenu(const char *AppName) : LSubMenu("", false) { d = new LMenuPrivate; Menu = this; Window = NULL; } LMenu::~LMenu() { Accel.DeleteObjects(); DeleteObj(d); } void LMenu::OnChange() { if (Info && Window) { if (::GetMenu(Window->Handle()) != Info) { SetMenu(Window->Handle(), Info); } } } struct LMenuFont { LFont *f; LMenuFont() { f = NULL; } ~LMenuFont() { DeleteObj(f); } } MenuFont; LFont *LMenu::GetFont() { if (!MenuFont.f) { LFontType Type; if (Type.GetSystemFont("Menu")) { MenuFont.f = Type.Create(); } } return MenuFont.f ? MenuFont.f : LSysFont; } bool LMenu::Attach(LViewI *p) { Window = p; return true; } bool LMenu::Detach() { bool Status = FALSE; if (Window) { HMENU hWndMenu = ::GetMenu(Window->Handle()); if (hWndMenu == Info) { Status = SetMenu(Window->Handle(), NULL) != 0; if (Status) { Window = NULL; } } } return Status; } bool LMenu::OnKey(LView *v, LKey &k) { if (k.Down() && k.vkey != 17) { for (auto a: Accel) { if (a->Match(k)) { LMenuItem *i = FindItem(a->GetId()); if (!i || i->Enabled()) { int Cmd = a->GetId(); Window->OnCommand(Cmd, 0, 0); return true; } } } } return false; } int LMenu::_OnEvent(LMessage *Msg) { switch (Msg->Msg()) { case WM_MENUCHAR: { short Key = Msg->A() & 0xffff; HMENU View = (HMENU)Msg->B(); MENUITEMINFO Info; ZeroObj(Info); Info.cbSize = sizeof(Info); Info.fMask = MIIM_DATA; if (GetMenuItemInfo(View, 0, true, &Info)) { LMenuItem *Item = (LMenuItem*)Info.dwItemData; if (Item) { LSubMenu *Menu = Item->Parent; if (Menu) { int Index=0; for (auto i: Menu->Items) { auto n = i->Name(); if (n) { char *Amp = strchr(n, '&'); if (Amp && toupper(Amp[1]) == toupper(Key)) { return (MNC_EXECUTE << 16) | Index; } } Index++; } } } } break; } case WM_MEASUREITEM: { LPMEASUREITEMSTRUCT Item = (LPMEASUREITEMSTRUCT)Msg->b; if (Item) { LPoint Size; ((LMenuItem*)Item->itemData)->_Measure(Size); Item->itemWidth = Size.x; Item->itemHeight = Size.y; return true; } break; } case WM_DRAWITEM: { LPDRAWITEMSTRUCT Item = (LPDRAWITEMSTRUCT)Msg->b; if (Item && Item->CtlType == ODT_MENU) { LRect r = Item->rcItem; LScreenDC Dc(Item->hDC, Item->hwndItem); // Get the original origin. We need to do this because when // "animated menus" are on the offset starts at -3,-3 and throws // the menu items off. This is a bug in windows, but watcha // gonna do? int x, y; Dc.GetOrigin(x, y); // Clip and offset so that the menu item draws in client co-ords. Dc.SetOrigin(-r.x1+x, -r.y1+y); Dc.SetSize(r.X(), r.Y()); // Paint the item... ((LMenuItem*)Item->itemData)->_Paint(&Dc, Item->itemState); // Set the origin back the original value. Dc.SetOrigin(x, y); return true; } break; } } return 0; } bool LMenu::SetPrefAndAboutItems(int PrefId, int AboutId) { return false; } //////////////////////////////////////////////////////////////////////////// -LAccelerator::LAccelerator(int flags, int key, int id) +LAccelerator::LAccelerator(int flags, int vkey, int chr, int id) { Flags = flags; - Key = key; + Vkey = vkey; + Chr = chr; Id = id; } bool LAccelerator::Match(LKey &k) { - if (!k.IsChar && - tolower(k.vkey) == tolower(Key)) + if + ( + !k.IsChar + && + ( + (Chr != 0 && tolower(k.c16) == tolower(Chr)) + || + (Vkey != 0 && k.vkey == Vkey) + ) + ) { if ( (Ctrl() ^ k.Ctrl()) == 0 && (Alt() ^ k.Alt()) == 0 && (Shift() ^ k.Shift()) == 0 && (!TestFlag(Flags, LGI_EF_IS_CHAR) || k.IsChar) && (!TestFlag(Flags, LGI_EF_IS_NOT_CHAR) || !k.IsChar) ) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////// LCommand::LCommand() { } LCommand::~LCommand() { } bool LCommand::Enabled() { if (ToolButton) return ToolButton->Enabled(); if (MenuItem) return MenuItem->Enabled(); return false; } void LCommand::Enabled(bool e) { if (ToolButton) { ToolButton->Enabled(e); } if (MenuItem) { MenuItem->Enabled(e); } } bool LCommand::Value() { bool HasChanged = false; if (ToolButton) { HasChanged |= (ToolButton->Value() != 0) ^ PrevValue; } if (MenuItem) { HasChanged |= (MenuItem->Checked() != 0) ^ PrevValue; } if (HasChanged) { Value(!PrevValue); } return PrevValue; } void LCommand::Value(bool v) { if (ToolButton) { ToolButton->Value(v); } if (MenuItem) { MenuItem->Checked(v); } PrevValue = v; }