diff --git a/ide/src/History.cpp b/ide/src/History.cpp --- a/ide/src/History.cpp +++ b/ide/src/History.cpp @@ -1,225 +1,225 @@ #include "lgi/common/Lgi.h" #include "lgi/common/List.h" #include "History.h" ////////////////////////////////////////////////////////////// // LHistoryPopup class LHistoryPopup : public LPopup { public: LList *Lst; LString Str; int64 Index; bool Ignore; LHistoryPopup() : LPopup(0) { Lst = 0; Index = -1; LRect r(0, 0, 300, 300); SetPos(r); Children.Insert(Lst = new LList(1, 1, 1, 100, 100)); if (Lst) { Lst->MultiSelect(false); - Lst->ShowColumnHeader(false); + Lst->ColumnHeaders(false); Lst->Sunken(false); Lst->AddColumn("", 1000); } } void OnPaint(LSurface *pDC) { if (Lst) { #if LGI_VIEW_HANDLE if (!Lst->Handle()) #endif AttachChildren(); Lst->Focus(true); } pDC->Colour(L_BLACK); pDC->Box(); } void OnPosChange() { if (Lst) { LRect r = GetClient(); r.Inset(1, 1); Lst->SetPos(r); } } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl->GetId() == 1) { if (n.Type == LNotifyItemSelect) { LListItem *li = Lst->GetSelected(); if (li) { Str = li->GetText(0); Index = Lst->Value(); Visible(false); // printf("%s:%i - str=%s idx=%i\n", _FL, Str.Get(), (int)Index); } else printf("%s:%i - No selection.\n", _FL); } // else printf("%s:%i - Flags=%i\n", _FL, Flags); } else printf("%s:%i - getid=%i\n", _FL, Ctrl->GetId()); return 0; } }; ///////////////////////////////////////////////////////////// // LHistory class LHistoryPrivate { public: LHistoryPopup *Popup; int TargetId; int64 Value; LHistoryPrivate() { Popup = 0; TargetId = 0; Value = 0; } }; LHistory::LHistory() : LDropDown(-1, 0, 0, 10, 10, 0), ResObject(Res_Custom) { d = new LHistoryPrivate; SetPopup(d->Popup = new LHistoryPopup); } LHistory::~LHistory() { DeleteObj(d); DeleteArrays(); } bool LHistory::OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Min) Inf.Width.Min = Inf.Width.Max = 24; else Inf.Height.Min = Inf.Height.Max = LSysFont->GetHeight() + 6; return true; } int LHistory::GetTargetId() { return d->TargetId; } void LHistory::SetTargetId(int id) { d->TargetId = id; } int64 LHistory::Value() { if (d && d->Popup) return d->Popup->Index; return -1; } void LHistory::Value(int64 i) { if (!d || !d->Popup) { LgiTrace("%s:%i - Invalid params.\n", _FL); return; } d->Popup->Index = i; if (d->Popup->Lst) { d->Popup->Lst->Value(i); LListItem *li = d->Popup->Lst->GetSelected(); if (li) { d->Popup->Str = li->GetText(0); GetWindow()->SetCtrlName(d->TargetId, d->Popup->Str); } } else LgiTrace("%s:%i - No list?\n", _FL); } int LHistory::Add(const char *Str) { if (!Str) return -1; int Idx = 0; for (auto s: *this) { if (strcmp(s, Str) == 0) { return Idx; } Idx++; } Idx = (int)Length(); Insert(NewStr(Str)); if (d->Popup && d->Popup->Lst) { LListItem *li = new LListItem; if (li) { li->SetText(Str); d->Popup->Lst->Insert(li); } } return Idx; } void LHistory::Update() { LList *Lst = d->Popup ? d->Popup->Lst : 0; if (Lst) { Lst->Empty(); for (auto s: *this) { LListItem *i = new LListItem; if (i) { i->SetText(s); Lst->Insert(i); } } } } void LHistory::OnPopupClose() { if (!d->Popup->GetCancelled() && d->TargetId && d->Popup && d->Popup->Str) { GetWindow()->SetCtrlName(d->TargetId, d->Popup->Str); } } //////////////////////////////////////////////////////////// // Factory class LHistoryFactory : public LViewFactory { public: virtual LView *NewView ( const char *Class, LRect *Pos, const char *Text ) { if (stricmp(Class, "LHistory") == 0) { return new LHistory; } return 0; } } HistoryFactory; \ No newline at end of file diff --git a/ide/src/LgiIde.cpp b/ide/src/LgiIde.cpp --- a/ide/src/LgiIde.cpp +++ b/ide/src/LgiIde.cpp @@ -1,5043 +1,5043 @@ #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/TextView4.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 "lgi/common/PopupNotification.h" #include "LgiIde.h" #include "FtpThread.h" #include "FindSymbol.h" #include "Debugger.h" #include "ProjectNode.h" #include "IdeFindInFiles.h" #define IDM_RECENT_FILE 1000 #define IDM_RECENT_PROJECT 1100 #define IDM_WINDOWS 1200 #define IDM_MAKEFILE_BASE 1300 #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 OPT_PLATFORM "Platform" #define IsSymbolChar(c) ( IsDigit(c) || IsAlpha(c) || strchr("-_", c) ) //////////////////////////////////////////////////////////////////////// SysIncThread::SysIncThread( AppWnd* app, IdeProject* proj, std::function callback) : LThread("SysIncThread"), App(app), Callback(callback) { auto Platform = PlatformFlagsToEnum(App->GetPlatform()); App->GetOptions()->GetValue(OPT_SearchSysInc, SearchSysInc); if (SearchSysInc.CastInt32()) { IdeProject* p = proj; while (proj && proj->GetParentProject()) proj = proj->GetParentProject(); if (proj) { // Get all the nodes List All; proj->GetChildProjects(All); All.Insert(proj); for (auto Cur : All) { if (LString Lst = Cur->GetSettings()->GetStr(ProjSystemIncludes, NULL, Platform)) { auto Lines = Lst.Strip().SplitDelimit("\r\n"); for (auto path : Lines) { if (LIsRelativePath(path)) { char full[MAX_PATH_LEN]; LMakePath(full, sizeof(full), Cur->GetFileName(), ".."); LMakePath(full, sizeof(full), full, path); Paths.Add(full); } else Paths.Add(path); } } } } } Run(); } SysIncThread::~SysIncThread() { Cancel(); WaitForExit(); } void SysIncThread::Scan(LString p) { if (Map.Find(p)) return; Map.Add(p, true); LDirectory d; for (auto b = d.First(p); !IsCancelled() && b; b = d.Next()) { if (d.IsDir()) { Scan(d.FullPath()); } else { Headers.New() = d.FullPath(); } } } int SysIncThread::Main() { for (unsigned i = 0; !IsCancelled() && i < Paths.Length(); i++) { LString p = Paths[i]; if (p[0] == '`') { Paths.DeleteAt(i--, true); auto a = p.Strip("`").SplitDelimit(" \t\r\n", 1); LSubProcess sub(a[0], a[1]); if (sub.Start()) { LStringPipe out; sub.Communicate(&out); auto parts = out.NewLStr().SplitDelimit(); for (auto part : parts) { if (part.Find("-I") == 0) Paths.Add(part(2, -1)); } } else printf("%s:%i - Error starting %s\n", _FL, p.Get()); continue; } // printf("%s:%i SysIncThread: '%s'\n", _FL, p.Get()); } for (auto &p: Paths) Scan(p); // Can't use the async callback mode here, otherwise this thread will exit // before the callback can be processed. Use the blocking form. App->RunCallback([this]() { if (Callback) Callback(&Headers); return 0; }, -1, this); return 0; } ////////////////////////////////////////////////////////////////////////////////////////// class FindInProject : public LDialog { AppWnd *App = NULL; LList *Lst = NULL; bool SearchSysInc = false; static LString::Array SysHeaders; LAutoPtr Thread; public: constexpr static int SYS_HEADER_SEARCH_TIME = 500; //ms FindInProject(AppWnd *app) { Lst = NULL; App = app; if (LoadFromResource(IDD_FIND_PROJECT_FILE)) { MoveSameScreen(App); LViewI *v; if (GetViewById(IDC_TEXT, v)) v->Focus(true); if (!GetViewById(IDC_FILES, Lst)) return; LVariant s = false; App->GetOptions()->GetValue(OPT_SearchSysInc, s); SearchSysInc = s.CastInt32() != 0; SetCtrlValue(IDC_SEARCH_SYS_INCLUDES, SearchSysInc); RegisterHook(this, LKeyEvents, 0); } } ~FindInProject() { Thread.Reset(); } 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, true, NULL); } EndModal(1); return true; } break; } case LK_ESCAPE: { if (k.Down()) { EndModal(0); return true; } break; } } return false; } void Search(const char *s) { auto p = App->RootProject(); if (!p || !s) return; LArray Matches, Nodes; auto Platforms = App->GetPlatform(); List All; p->GetChildProjects(All); All.Insert(p); for (auto p: All) { p->GetAllNodes(Nodes); } FilterFiles(Matches, Nodes, s, Platforms); Lst->Empty(); for (auto m: Matches) { auto 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); } if (SysHeaders.Length() == 0) { if (!Thread) { Thread.Reset(new SysIncThread(App, App->RootProject(), [this](auto hdrs) { SysHeaders.Swap(*hdrs); Search(GetCtrlName(IDC_TEXT)); })); } } else { auto start = LCurrentTime(); for (auto h: SysHeaders) { if (Stristr(h.Get(), s)) Lst->Insert(new LListItem(h)); if (LCurrentTime() - start >= SYS_HEADER_SEARCH_TIME) break; } } 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, true, NULL); EndModal(1); } } break; } case IDC_TEXT: { if (n.Type != LNotifyReturnKey) Search(c->Name()); break; } case IDCANCEL: { EndModal(0); break; } case IDC_SEARCH_SYS_INCLUDES: { bool s = c->Value() != 0; if (s != SearchSysInc) { LVariant v = SearchSysInc = s; App->GetOptions()->SetValue(OPT_SearchSysInc, v); Search(GetCtrlName(IDC_TEXT)); } break; } } return 0; } }; LString::Array FindInProject::SysHeaders; ////////////////////////////////////////////////////////////////////////////////////////// const 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]; snprintf(s, sizeof(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); int ch = Depth * 4; memset(s, ' ', ch); snprintf(s+ch, sizeof(s)-ch, "[%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; } }; ////////////////////////////////////////////////////////////////////////////////////////// #define TextLogCls LTextLog4 class DebugTextLog : public TextLogCls { public: DebugTextLog(int id) : TextLogCls(id) { } void PourText(size_t Start, ssize_t Len) override { auto Ts = LCurrentTime(); TextLogCls::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 = NULL; LTabPage *Build = NULL; LTabPage *Output = NULL; LTabPage *Debug = NULL; LTabPage *Find = NULL; LTabPage *Ftp = NULL; LList *FtpLog = NULL; LTextLog *Txt[AppWnd::Channels::ChannelMax] = {}; LArray Buf[AppWnd::Channels::ChannelMax]; LFont Small; LFont Fixed; LTabView *DebugTab = NULL; LBox *DebugBox = NULL; LBox *DebugLog = NULL; LList *Locals = NULL, *CallStack = NULL, *Threads = NULL; LTree *Watch = NULL; LTextLog *ObjectDump = NULL, *MemoryDump = NULL, *Registers = NULL; LTableLayout *MemTable = NULL; LEdit *DebugEdit = NULL; DebugTextLog *DebuggerLog = NULL; IdeOutput(AppWnd *app) { App = app; 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->ColumnHeaders(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); + FtpLog->ColumnHeaders(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; int Platform = 0; 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, NULL); 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); // printf("Options.GetFile()='%s'\n", Options.GetFile()); } ~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, true, NULL); } } #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() { LgiGetResObj(true, AppName); 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 SplitPx = 270, OutPx = 250, v; d->Options.GetValue(OPT_SPLIT_PX, SplitPx); d->Options.GetValue(OPT_OUTPUT_PX, OutPx); if (d->Options.GetValue(OPT_PLATFORM, v)) SetPlatform(v.CastInt32()); else SetPlatform(PLATFORM_CURRENT); 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(SplitPx.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); } #if 0 else { printf("load outPx: not enough space %i > %i, cli=%s, pos=%s\n", c.Y(), OutPx.CastInt32(), c.GetStr(), GetPos().GetStr()); } #endif AttachChildren(); OnPosChange(); UpdateState(); Visible(true); DropTarget(true); SetPulse(1000); #ifdef LINUX LFinishXWindowsStartup(this); #endif } AppWnd::~AppWnd() { // Everything needs to be clean BEFORE we get here... because we can't show // any async UI to select save locations or anything. LAssert(IsClean()); // Haiku: wait for the window thread to shutdown cleanly. WaitThread(); LVariant v; if (d->HBox) d->Options.SetValue(OPT_SPLIT_PX, v = d->HBox->Value()); if (d->Output) d->Options.SetValue(OPT_OUTPUT_PX, v = d->Output->Y()); d->Options.SetValue(OPT_PLATFORM, v = d->Platform); 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(); } for (auto n: NeedsPulse) n->OnPulse(); } 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\\2019\\Community\\VC\\Tools\\MSVC\\14.29.30133\\bin\\Hostx64\\x64\\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.NewLStr().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.NewLStr().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" }; LString::Array IncPaths; if (p->BuildIncludePaths(IncPaths, NULL, 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(""); } } int AppWnd::GetPlatform() { return d->Platform; } bool AppWnd::SetPlatform(int p) { if (p == 0) p = PLATFORM_CURRENT; if (d->Platform == p) return false; d->Platform = p; if (auto m = GetMenu()) { bool all = d->Platform == PLATFORM_ALL; #define SET_CHK(id, flag) \ { auto mi = m->FindItem(id); \ if (mi) mi->Checked((d->Platform & flag) && !all); } SET_CHK(IDM_WIN, PLATFORM_WIN32); SET_CHK(IDM_LINUX, PLATFORM_LINUX); SET_CHK(IDM_MAC, PLATFORM_MAC); SET_CHK(IDM_HAIKU, PLATFORM_HAIKU); auto mi = m->FindItem(IDM_ALL_OS); if (mi) mi->Checked(all); } return true; } 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) { THREAD_WARNING 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() { THREAD_WARNING SaveAll([this](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) { THREAD_WARNING d->OnFile(File, IsProject); } IdeDoc *AppWnd::NewDocWnd(const char *FileName, NodeSource *Src) { THREAD_WARNING 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; } void AppWnd::GotoReference(const char *File, int Line, bool CurIp, bool WithHistory, std::function Callback) { if (!InThread()) { printf("\tGotoReference(%s,%i,%i,%i) out of thread\n", File, Line, CurIp, WithHistory); RunCallback ( [this, File=LString(File), Line, CurIp, WithHistory, Callback]() { GotoReference(File, Line, CurIp, WithHistory, Callback); } ); return; } printf("\tGotoReference(%s,%i,%i,%i) in thread\n", File, Line, CurIp, WithHistory); if (!WithHistory) d->InHistorySeek = true; IdeDoc *Doc = File ? OpenFile(File) : GetCurrentDoc(); if (Doc) { Doc->SetLine(Line, CurIp); Doc->Focus(true); } else { LString Msg; Msg.Printf("Failed to find '%s'", File); LPopupNotification::Message(this, Msg); } if (!WithHistory) d->InHistorySeek = false; if (Callback) Callback(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 = NULL; THREAD_WARNING 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; } FindSymbolSystem *AppWnd::GetFindSym() { return d->FindSym; } IdePlatform PlatformFlagsToEnum(int flags) { if (flags == PLATFORM_WIN32) return PlatformWin; if (flags == PLATFORM_LINUX) return PlatformLinux; if (flags == PLATFORM_MAC) return PlatformMac; if (flags == PLATFORM_HAIKU) return PlatformHaiku; return PlatformCurrent; } LString PlatformFlagsToStr(int flags) { LString::Array a; if (flags & PLATFORM_WIN32) a.New() = PlatformNames[PlatformWin]; if (flags & PLATFORM_LINUX) a.New() = PlatformNames[PlatformLinux]; if (flags & PLATFORM_MAC) a.New() = PlatformNames[PlatformMac]; if (flags & PLATFORM_HAIKU) a.New() = PlatformNames[PlatformHaiku]; return a.Length() ? LString(",").Join(a) : "EmptyFlags"; } IdeProject *AppWnd::OpenProject(const char *FileName, IdeProject *ParentProj, bool Create, ProjectNode *DepParent) { THREAD_WARNING 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, DepParent); if (!p) { LgiTrace("%s:%i - Error: mem alloc.\n", _FL); return NULL; } p->SetParentProject(ParentProj); auto Status = p->OpenFile(FileName); if (Status == OpenOk) { d->Projects.Insert(p); d->OnFile(FileName, true); if (!DepParent) { auto d = strrchr(FileName, DIR_CHAR); if (d++) { LString n; n.Printf("%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_GET_PLATFORM_FLAGS: { PostThreadEvent(m->A(), M_GET_PLATFORM_FLAGS, 0, GetPlatform()); break; } 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 Breaking = d->Running && !Running; d->Running = Running; if (Breaking && d->Output && d->Output->DebugTab) { d->Output->DebugTab->SendNotify(LNotification(LNotifyValueChanged, _FL)); } } 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"); SetCtrlName(IDC_FONT, Font.GetDescription()); 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)); // Fall through.. } case IDCANCEL: { EndModal(c->GetId()); break; } case IDC_SET_FONT: { Font.DoUI(this, [this](auto ui) { SetCtrlName(IDC_FONT, Font.GetDescription()); }); 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) { THREAD_WARNING 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) { printf("IDC_DEBUG_TAB notification: %s\n", n.Sender.Get()); 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, true, NULL); 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([this](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(IDD_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: { auto dlg = new LAbout( this, AppName, APP_VER, "\nLGI Integrated Development Environment", "icon128.png", APP_URL, "fret@memecode.com"); dlg->DoModal(NULL); 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); s->Open([this](auto s, auto ok) { if (ok) OpenFile(s->Name()); delete s; }); 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([this, Top](auto s, auto ok) { if (ok) { Top->SetFileName(s->Name(), true); d->OnFile(s->Name()); } delete s; }); } break; } case IDM_CLOSE: { IdeDoc *Top = TopDoc(); if (Top && Top->OnRequestClose(false)) Top->Quit(); 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([Inp,this](auto dlg, auto code) { if (code == IDOK) { LString s = Inp->GetStr(); LString::Array p = s.SplitDelimit(":,"); if (p.Length() == 1) { GotoReference(p[0], 1, false, true, NULL); } else if (p.Length() > 1) { LString file = p[0]; int line = (int)p[1].Int(); GotoReference(file, line, false, true, NULL); } else LgiMsg(this, "Error: No filename.", AppName); } delete dlg; }); } 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) { #if 0 LClipBoard c(this); LArray Formats; auto r = c.EnumFormats(Formats); GetBuildLog()->Print("r=%i\n", r); for (auto f: Formats) { GetBuildLog()->Print("f=%s\n", LClipBoard::FmtToStr(f).Get()); } auto html = c.Html(); GetBuildLog()->Print("html='%s'\n", html.Get()); #else Doc->PostEvent(M_PASTE); #endif } 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; } auto Dlg = new FindInFiles(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([this, Dlg, p](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(); if (d->FindParameters->Text) d->Finder->PostEvent(FindInFilesThread::M_START_SEARCH, (LMessage::Param) new FindParams(d->FindParameters)); delete Dlg; }); } break; } case IDM_FIND_SYMBOL: { IdeDoc *Doc = FocusDoc(); if (Doc) { Doc->GotoSearch(IDC_SYMBOL_SEARCH); } else { d->FindSym->OpenSearchDlg(this, [this](auto r) { if (r.File) GotoReference(r.File, r.Line, false, true, NULL); }); } 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([this, Cmd](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([this, p](auto s, auto ok) { if (ok) p->ImportDsp(s->Name()); delete s; }); } break; } case IDM_RUN: { SaveAll([this](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([this](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([this](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([this](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; } // // OS Platform // #define HANDLE_PLATFORM_ITEM(id, flag) \ case id: \ { \ auto mi = GetMenu()->FindItem(id); \ if (!mi) break; \ if (d->Platform == PLATFORM_ALL) d->Platform = 0; \ if (mi->Checked()) SetPlatform(d->Platform & ~flag); \ else SetPlatform(d->Platform | flag); \ break; \ } HANDLE_PLATFORM_ITEM(IDM_WIN, PLATFORM_WIN32) HANDLE_PLATFORM_ITEM(IDM_LINUX, PLATFORM_LINUX) HANDLE_PLATFORM_ITEM(IDM_MAC, PLATFORM_MAC) HANDLE_PLATFORM_ITEM(IDM_HAIKU, PLATFORM_HAIKU) case IDM_ALL_OS: { SetPlatform(PLATFORM_ALL); 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([i, Doc](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([i, Doc](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] : LString(); 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] : LString(); 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::GetOutputLog() { return d->Output->Txt[AppWnd::OutputTab]; } 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) { d->FindSym->Search(ResultsSinkHnd, Sym, d->Platform); } 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); } } }; void LDirTest() { LDirectory dir; for (auto b = dir.First(LGetSystemPath(LSP_HOME)); b; b = dir.Next()) { bool debug = Stricmp(dir.GetName(), "restored.mp4") == 0; auto modTime = dir.GetLastWriteTime(); if (debug) { int asd=0; } auto dt = LDirectory::TsToDateTime(modTime); LgiTrace("%-36sunix=" LPrintfInt64 ", dt=%s %g\n", dir.GetName(), LDirectory::TsToUnix(modTime), dt.Get().Get(), (double) dt.GetTimeZone() / 60.0); } } /* #include "lgi/common/TimeZoneInfo.h" void TimeZoneInfoTest() { LTimeZoneInfo tzInfo; tzInfo.Read("c:\\code\\Sydney"); LArray info; LDateTime start, end; start.SetNow(); start.Day(1); start.Month(1); end = start; end.Year(start.Year()+1); start.Year(2000); if (tzInfo.GetDaylightSavingsInfo(info, start, &end)) { for (auto &i: info) { LDateTime dt(i.UtcTimeStamp); LgiTrace("%s = %i\n", dt.Get().Get(), i.Offset); } } } */ int LgiMain(OsAppArguments &AppArgs) { printf("LgiIde v%s\n", APP_VER); LApp a(AppArgs, "LgiIde"); if (a.IsOk()) { a.AppWnd = new AppWnd; // LPlaySound("~/code/mixkit-happy-bells-notification-937.wav"); LArray ver; LGetOs(&ver); // auto testFile = "/boot/home/code/lgi/trunk/CMakeLists.txt"; // LShowFileProperties(a.AppWnd->Handle(), testFile); // LBrowseToFile(testFile); // LDirTest(); // TimeZoneInfoTest(); a.Run(); } return 0; } diff --git a/include/lgi/common/ItemContainer.h b/include/lgi/common/ItemContainer.h --- a/include/lgi/common/ItemContainer.h +++ b/include/lgi/common/ItemContainer.h @@ -1,426 +1,418 @@ #ifndef _ITEM_CONTAINER_H_ #define _ITEM_CONTAINER_H_ #include "lgi/common/Popup.h" #include "lgi/common/Notifications.h" #include "lgi/common/Css.h" #include "lgi/common/ImageList.h" class LItemContainer; #define DragColumnColour LColour(L_LOW) /// Base class for items in widget containers class LgiClass LItem : virtual public LEventsI { protected: LAutoPtr Css; int SelectionStart, SelectionEnd; public: /// Painting context struct ItemPaintCtx : public LRect { /// The surface to draw on LSurface *pDC; /// Current foreground colour LColour Fore; /// Current background colour LColour TxtBack, Back; // Horizontal alignment of content LCss::Len Align; /// Number of columns int Columns; /// Width of each column int *ColPx; }; LItem(); ~LItem(); LItem &operator =(LItem &i) { if (i.GetCss()) { LCss *c = GetCss(true); if (c) { *c = *i.GetCss(); } } return *this; } virtual const char *GetClass() { return "LItem"; } virtual LItemContainer *GetContainer() = 0; // Events /// Called when the item is selected virtual void OnSelect() {} /// Called when the item is clicked virtual void OnMouseClick(LMouse &m) {} /// Called when the item needs painting virtual void OnPaint(ItemPaintCtx &Ctx) = 0; /// Called when the item is dragged virtual bool OnBeginDrag(LMouse &m) { return false; } /// Called when the owning container needs to know the size of the item virtual void OnMeasure(LPoint *Info) {} /// Called when the item is inserted into a new container virtual void OnInsert() {} /// Called when the item is removed from it's container virtual void OnRemove() {} // Methods /// Call to tell the container that the data displayed by the item has changed virtual void Update() {} /// Moves the item on screen virtual void ScrollTo() {} /// Shows a editable label above the item allowing the user to change the value associated with the column 'Col' virtual LView *EditLabel(int Col = -1); /// Event called when the edit label ends virtual void OnEditLabelEnd(); /// Sets the default selection of text when editing a label void SetEditLabelSelection(int SelStart, int SelEnd); // call before 'EditLabel' // Data /// True if the item is selected virtual bool Select() { return false; } /// Select/Deselect the item virtual void Select(bool b) {} /// Gets the text associated with the column 'Col' virtual const char *GetText(int Col = 0) { return 0; } /// Sets the text associated with the column 'Col' virtual bool SetText(const char *s, int Col=0) { return false; } /// Gets the icon index virtual int GetImage(int Flags = 0) { return -1; } /// Sets the icon index virtual void SetImage(int Col) {} /// Gets the position virtual LRect *GetPos(int Col = -1) { return 0; } /// Gets the font for the item virtual LFont *GetFont() { return 0; } /// Reads / writes list item to XML virtual bool XmlIo(class LXmlTag *Tag, bool Write) { return false; } bool OnScriptEvent(LViewI *Ctrl) { return false; } LMessage::Result OnEvent(LMessage *Msg) { return 0; } void OnMouseEnter(LMouse &m) {} void OnMouseExit(LMouse &m) {} void OnMouseMove(LMouse &m) {} bool OnMouseWheel(double Lines) { return false; } bool OnKey(LKey &k) { return false; } void OnAttach() {} void OnCreate() {} void OnDestroy() {} void OnFocus(bool f) {} void OnPulse() {} void OnPosChange() {} bool OnRequestClose(bool OsShuttingDown) { return false; } int OnHitTest(int x, int y) { return 0; } void OnChildrenChanged(LViewI *Wnd, bool Attaching) {} int OnNotify(LViewI *Ctrl, LNotification n) { return 0; } int OnCommand(int Cmd, int Event, OsView Wnd) { return 0; } void OnPaint(LSurface *pDC) { LAssert(0); } // Style access LCss *GetCss(bool Create = false) { if (!Css && Create) Css.Reset(new LCss); return Css; } }; /// The popup label for LItem's class LItemEdit : public LPopup { class LItemEditPrivate *d; public: LItemEdit(LView *parent, LItem *item, int index, int selstart, int selend); ~LItemEdit(); LItem *GetItem(); void OnPaint(LSurface *pDC); int OnNotify(LViewI *v, LNotification n); void Visible(bool i); LMessage::Result OnEvent(LMessage *Msg); bool OnKey(LKey &k); void OnFocus(bool f); }; - -/// No marking -/// \sa LItemColumn::Mark -#define GLI_MARK_NONE 0 -/// Up arrow mark -/// \sa LItemColumn::Mark -#define GLI_MARK_UP_ARROW 1 -/// Down arrow mark -/// \sa LItemColumn::Mark -#define GLI_MARK_DOWN_ARROW 2 - /// Item container column class LgiClass LItemColumn : public ResObject, public LCss { class LItemColumnPrivate *d; friend class LDragColumn; friend class LListItem; friend class LItemContainer; public: LItemColumn(LItemContainer *parent, const char *name, int width); virtual ~LItemColumn(); // properties bool InDrag(); LRect GetPos(); void SetPos(LRect &r); /// Sets the text void Name(const char *n); /// Gets the text char *Name(); + /// Sets the width void Width(int i); /// Gets the width int Width(); - /// Sets the type of content in the header. Use one of #GIC_ASK_TEXT, #GIC_OWNER_DRAW, #GIC_ASK_IMAGE. - /// - /// Is this used?? - void Type(int i); - /// Gets the type of content. - int Type(); - /// Sets the marking, one of #GLI_MARK_NONE, #GLI_MARK_UP_ARROW or #GLI_MARK_DOWN_ARROW - void Mark(int i); - /// Gets the marking, one of #GLI_MARK_NONE, #GLI_MARK_UP_ARROW or #GLI_MARK_DOWN_ARROW - int Mark(); + + ////////////////////////////////////////////////////// + #define COLUMN_FLAGS() \ + _(HasText) \ + _(HasImage) \ + _(UpArrow) \ + _(DownArrow) + #define _(name) \ + bool name(); \ + void name(bool b); + COLUMN_FLAGS() + #undef _ + bool HasSort() { return UpArrow() | DownArrow(); } + + ////////////////////////////////////////////////////// + /// Sets the icon to display void Icon(LSurface *i, bool Own = true); /// Gets the icon displayed LSurface *Icon(); + /// True if clicked bool Value(); /// Set clicked void Value(bool i); /// Sets the index into the parent containers LImageList void Image(int i); /// Gets the index into the parent containers LImageList int Image(); + /// true if resizable bool Resizable(); /// Sets whether the user can resize the column void Resizable(bool i); /// Returns the index of the column if attached to a list int GetIndex(); /// Returns the size of the content in this column int GetContentSize(); /// Returns the list LItemContainer *GetList(); /// Paint the column header. void OnPaint(LSurface *pDC, LRect &r); - /// Draws the just the icon, text and mark. void OnPaint_Content(LSurface *pDC, LRect &r, bool FillBackground); }; -/// Client draws the content. -#define GIC_OWNER_DRAW 0x01 -/// Column header is text. -#define GIC_ASK_TEXT 0x02 -/// Column header is an image. -#define GIC_ASK_IMAGE 0x04 -/// Not used. -#define GIC_OWN_LIST 0x08 -/// Drag is over the control -#define GIC_IN_DRAG_OP 0x10 -/// Multiple item select allowed -#define GIC_MULTI_SELECT 0x20 - -#define DRAG_NONE 0 -#define SELECT_ITEMS 1 -#define RESIZE_COLUMN 2 -#define DRAG_COLUMN 3 -#define CLICK_COLUMN 4 -#define TOGGLE_ITEMS 5 -#define CLICK_ITEM 6 #define DEFAULT_COLUMN_SPACING 12 class LgiClass LItemContainer : public LLayout, public LImageListOwner { friend class LItemColumn; friend class LItem; friend class LItemEdit; public: struct ColInfo { int Idx, ContentPx, WidthPx; LItemColumn *Col; int GrowPx() { return WidthPx < ContentPx ? ContentPx - WidthPx : 0; } }; struct ColSizes { LArray Info; int FixedPx; int ResizePx; }; + enum ContainerDragMode + { + DRAG_NONE, + SELECT_ITEMS, + RESIZE_COLUMN, + DRAG_COLUMN, + CLICK_COLUMN, + TOGGLE_ITEMS, + CLICK_ITEM, + }; + protected: - int Flags; - int DragMode; - bool ColumnHeaders; + ContainerDragMode DragMode = DRAG_NONE; + LRect ColumnHeader; - int ColClick; + int ColClick = -1; LMouse ColMouse; - LItemEdit *ItemEdit; + LItemEdit *ItemEdit = NULL; LArray Columns; LAutoPtr IconCol; - class LDragColumn *DragCol; + class LDragColumn *DragCol = NULL; /// Returns size information for columns void GetColumnSizes(ColSizes &cs); /// Paints all the column headings void PaintColumnHeadings(LSurface *pDC); /// This clears all the display string cache for a given column virtual void ClearDs(int Col) = 0; public: LItemContainer(); virtual ~LItemContainer(); - // Props - bool OwnerDraw() { return TestFlag(Flags, GIC_OWNER_DRAW); } - void OwnerDraw(bool b) { if (b) SetFlag(Flags, GIC_OWNER_DRAW); else ClearFlag(Flags, GIC_OWNER_DRAW); } - bool AskText() { return TestFlag(Flags, GIC_ASK_TEXT); } - void AskText(bool b) { if (b) SetFlag(Flags, GIC_ASK_TEXT); else ClearFlag(Flags, GIC_ASK_TEXT); } - bool AskImage() { return TestFlag(Flags, GIC_ASK_IMAGE); } - void AskImage(bool b) { if (b) SetFlag(Flags, GIC_ASK_IMAGE); else ClearFlag(Flags, GIC_ASK_IMAGE); } - bool InsideDragOp() { return TestFlag(Flags, GIC_IN_DRAG_OP); } - void InsideDragOp(bool b) { if (b) SetFlag(Flags, GIC_IN_DRAG_OP); else ClearFlag(Flags, GIC_IN_DRAG_OP); } + #define DEFINE_FLAG(name, default) \ + private: \ + bool b##name = default; \ + public: \ + bool name() { return b##name; } \ + void name(bool b) { b##name = b; } + - /// Returns whether the user can select multiple items at the same time - bool MultiSelect() { return TestFlag(Flags, GIC_MULTI_SELECT); } - /// Sets whether the user can select multiple items at the same time - void MultiSelect(bool b) { if (b) SetFlag(Flags, GIC_MULTI_SELECT); else ClearFlag(Flags, GIC_MULTI_SELECT); } + // Props + DEFINE_FLAG(AskText, false) + DEFINE_FLAG(AskImage, false) + DEFINE_FLAG(OwnList, false) + DEFINE_FLAG(InDragOp, false) + DEFINE_FLAG(MultiSelect, false) + DEFINE_FLAG(InsideDragOp, false) + DEFINE_FLAG(DropOnItem, false) + DEFINE_FLAG(DropBetweenItems, false) + DEFINE_FLAG(ColumnHeaders, true) - /// Returns whether display of column headers is switched on - bool ShowColumnHeader() { return ColumnHeaders; } - - /// Turns on display of column headers - void ShowColumnHeader(bool Show) { ColumnHeaders = Show; } - /// Adds a column to the list LItemColumn *AddColumn ( /// The text for the column or NULL for no text const char *Name, /// The width of the column int Width = 50, /// The index to insert at, or -1 to append to the end int Where = -1 ); /// Adds a preexisting column to the control bool AddColumn ( /// The column object. The object once added is owned by the LList LItemColumn *Col, /// The location to insert or -1 to append int Where = -1 ); /// Deletes a column from the LList bool DeleteColumn(LItemColumn *Col); /// Deletes all the columns of the LList void EmptyColumns(); /// Returns the column at index 'Index' LItemColumn *ColumnAt(unsigned i) { return i < Columns.Length() ? Columns[i] : NULL; } /// Returns the column at horizontal offset 'x', or -1 if none matches. int ColumnAtX(int X, LItemColumn **Col = 0, int *Offset = 0); /// Returns the number of columns int GetColumns() { return (int)Columns.Length(); } /// Starts a column d'n'd operation with the column at index 'Index' /// \sa OnColumnReindex is called when the user drops the column void DragColumn(int Index); /// Returns the last column click info bool GetColumnClickInfo(int &Col, LMouse &m); int HitColumn(int x, int y, LItemColumn *&Resize, LItemColumn *&Over); + /// Sets/clears the sorting mark + void SetSortingMark( + /// Index of the column, or -1 to unset + int ColIdx = -1, + /// Which mark to set + bool Up = true); + /// Called when a column is clicked virtual void OnColumnClick ( /// The index of the column int Col, /// The mouse parameters at the time LMouse &m ); virtual void UpdateAllItems() = 0; virtual int GetContentSize(int ColumnIdx) = 0; /// Resizes all the columns to their content, allowing a little extra space for visual effect virtual void ResizeColumnsToContent(int Border = DEFAULT_COLUMN_SPACING); LMessage::Result OnEvent(LMessage *Msg); LItemContainer &operator =(const LItemContainer &i) { LAssert(!"Not impl.."); return *this; } }; //////////////////////////////////////////////////////////////////////////////////////////// #define DRAG_COL_ALPHA 0xc0 #define LINUX_TRANS_COL 0 class LDragColumn : public LWindow { LItemContainer *List; LItemColumn *Col; int Index; int Offset; LPoint ListScrPos; #ifdef LINUX LSurface *Back; #endif public: int GetOffset() { return Offset; } int GetIndex() { return Index; } LItemColumn *GetColumn() { return Col; } LDragColumn(LItemContainer *list, int col); ~LDragColumn(); void OnPaint(LSurface *pScreen); }; #endif diff --git a/include/lgi/common/PopupList.h b/include/lgi/common/PopupList.h --- a/include/lgi/common/PopupList.h +++ b/include/lgi/common/PopupList.h @@ -1,283 +1,283 @@ #ifndef _POPUP_LIST_H_ #define _POPUP_LIST_H_ #include "lgi/common/List.h" #include "lgi/common/Css.h" #include "lgi/common/CssTools.h" /// This class displays a list of search results in a /// popup connected to an editbox. To use override: /// - ToString /// - OnSelect /// /// 'T' is the type of record that maps to one list item. /// The user can select an item with [Enter] or click on it. /// [Escape] cancels the popup. template class LPopupList : public LPopup { public: enum { IDC_BROWSE_LIST = 50, }; enum PositionType { PopupAbove, PopupBelow, }; class Item : public LListItem { public: T *Value; Item(T *val = NULL) { Value = val; } }; protected: LList *Lst; LViewI *Edit; bool Registered; PositionType PosType; LWindow *HookWnd() { #if defined(LGI_CARBON)// || defined(__GTK_H__) return GetWindow(); #else return Edit->GetWindow(); #endif } public: LPopupList(LViewI *edit, PositionType pos, int width = 200, int height = 300) : LPopup(edit->GetGView()) { Registered = false; PosType = pos; LRect r(width - 1, height - 1); Edit = edit; SetPos(r); AddView(Lst = new LList(IDC_BROWSE_LIST, r.x1+1, r.y1+1, r.X()-3, r.Y()-3)); Lst->Sunken(false); Lst->AddColumn("Name", r.X()); - Lst->ShowColumnHeader(false); + Lst->ColumnHeaders(false); Lst->MultiSelect(false); // Set default border style... LCss *Css = GetCss(true); if (Css) { LCss::BorderDef b(Css, "1px solid #888;"); Css->Border(b); } Attach(Edit); } ~LPopupList() { auto Wnd = HookWnd(); if (Wnd && Registered) Wnd->UnregisterHook(this); } const char *GetClass() { return "LPopupList"; } // Events: // ------------------------------------------------------------------------ /// Override this to convert an object to a string for the list items virtual LString ToString(T *Obj) = 0; /// Override this to handle the selection of an object virtual void OnSelect(T *Obj) = 0; virtual void OnSelect(LListItem *Item) {} // Implementation: // ------------------------------------------------------------------------ void SetPosType(PositionType t) { PosType = t; } void AdjustPosition() { // Set position relative to editbox LRect r = GetPos(); LPoint p(0, PosType == PopupAbove ? 0 : Edit->Y()); Edit->PointToScreen(p); if (PosType == PopupAbove) r.Offset(p.x - r.x1, (p.y - r.Y()) - r.y1); else r.Offset(p.x - r.x1, p.y - r.y1); SetPos(r); } bool SetItems(LArray &a) { Lst->Empty(); List ins; for (auto obj: a) { if (!obj) continue; auto li = new Item(obj); if (li) { LString s = ToString(obj); if (s) { li->SetText(s); ins.Insert(li); } else LAssert(!"ToString failed."); } else LAssert(!"Alloc failed."); } return Lst->Insert(ins); } void OnPaint(LSurface *pDC) { // Draw the CSS border... (the default value is set in the constructor) LCssTools t(GetCss(true), GetFont()); LRect c = GetClient(); c = t.PaintBorder(pDC, c); // Move Lst if needed... LRect r = Lst->GetPos(); if (r != c) Lst->SetPos(c); } bool Visible() { return LPopup::Visible(); } void Visible(bool i) { if (i) AdjustPosition(); LPopup::Visible(i); if (i) { AttachChildren(); OnPosChange(); auto Wnd = HookWnd(); if (Wnd && !Registered) { Registered = true; Wnd->RegisterHook(this, LKeyEvents); } #ifdef WINNATIVE Edit->Focus(true); #else Lst->Focus(true); #endif } } int OnNotify(LViewI *Ctrl, LNotification n) { if (Lst && Ctrl == Edit && (n.Type == LNotifyValueChanged || n.Type == LNotifyDocChanged)) { auto Str = Edit->Name(); bool Has = ValidStr(Str) && Lst->Length(); bool Vis = Visible(); if (Has ^ Vis) { // printf("%s:%i - PopupLst, has=%i vis=%i\n", _FL, Has, Vis); Visible(Has); } } else if (Ctrl == Lst) { if (n.Type == LNotifyReturnKey || n.Type == LNotifyItemClick) { auto s = Lst->GetSelected(); Item *Sel = dynamic_cast(s); if (Sel) OnSelect(Sel->Value); else OnSelect(s); Visible(false); } } return 0; } bool OnViewKey(LView *v, LKey &k) { if (!Visible()) return false; #if 0 LString s; s.Printf("%s:%i - OnViewKey vis=%i", _FL, Visible()); k.Trace(s); #endif switch (k.vkey) { case LK_TAB: { Visible(false); return false; } case LK_ESCAPE: { if (!k.Down()) { Visible(false); } return true; break; } case LK_RETURN: { if (Lst) Lst->OnKey(k); return true; break; } case LK_UP: case LK_DOWN: case LK_PAGEDOWN: case LK_PAGEUP: { if (!k.IsChar) { if (Lst) Lst->OnKey(k); return true; } break; } } #if defined(MAC) && !defined(__GTK_H__) return Edit->OnKey(k); #else return false; #endif } }; #endif diff --git a/lvc/src/DropDownBtn.cpp b/lvc/src/DropDownBtn.cpp --- a/lvc/src/DropDownBtn.cpp +++ b/lvc/src/DropDownBtn.cpp @@ -1,145 +1,145 @@ #include "lgi/common/Lgi.h" #include "Lvc.h" struct DropDownBtnPriv { DropDownBtn *View; int EditId; DropDownBtnPriv(DropDownBtn *view) { View = view; EditId = -1; } LViewI *GetEdit() { LViewI *e = NULL; if (View && View->GetParent()) View->GetParent()->GetViewById(EditId, e); LAssert(e != 0); return e; } }; class DropLst : public LPopup { public: DropDownBtnPriv *d; LList *Lst; DropLst(DropDownBtnPriv *priv, LView *owner) : Lst(NULL), LPopup(owner) { d = priv; int x = 300; LRect r(0, 0, x, 200); SetPos(r); AddView(Lst = new LList(10)); Lst->Sunken(false); - Lst->ShowColumnHeader(false); + Lst->ColumnHeaders(false); Lst->AddColumn("", x); } void OnPosChange() { LRect c = GetClient(); c.Inset(1, 1); if (Lst) Lst->SetPos(c); } void OnPaint(LSurface *pDC) { pDC->Colour(LColour::Black); pDC->Box(); } int OnNotify(LViewI *c, LNotification n) { if (c->GetId() == 10 && n.Type == LNotifyItemClick) { LListItem *i = Lst->GetSelected(); LViewI *e = d->GetEdit(); if (e && i) { e->Name(i->GetText(0)); } Visible(false); } return 0; } }; DropDownBtn::DropDownBtn() : LDropDown(-1, 0, 0, 100, 24, NULL), ResObject(Res_Custom) { d = new DropDownBtnPriv(this); d->EditId = -1; SetPopup(Pu = new DropLst(d, this)); } DropDownBtn::~DropDownBtn() { DeleteObj(d); } LString::Array DropDownBtn::GetList() { LString::Array a; if (Pu && Pu->Lst) { for (auto i: *Pu->Lst) { a.New() = i->GetText(0); } } return a; } bool DropDownBtn::OnLayout(LViewLayoutInfo &Inf) { if (Inf.Width.Min) { // Vertical layout Inf.Height.Min = Inf.Height.Max = 23; } else { // Horizontal layout Inf.Width.Min = Inf.Width.Max = 23; } return true; } bool DropDownBtn::SetList(int EditCtrl, LString::Array a) { if (!Pu || !Pu->Lst) return false; Pu->Lst->Empty(); d->EditId = EditCtrl; for (auto s: a) { LListItem *i = new LListItem; i->SetText(s); Pu->Lst->Insert(i, -1, false); } Pu->Lst->UpdateAllItems(); return true; } class DropDownBtnFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (!stricmp(Class, "DropDownBtn")) return new DropDownBtn; return NULL; } } DropDownBtnFactoryInst; \ 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,1987 +1,1987 @@ #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 "lgi/common/PopupNotification.h" #include "Lvc.h" #include "resdefs.h" #ifdef WINDOWS #include "resource.h" #endif ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; #define DEFAULT_BUILD_FIX_MSG "Build fix." #define OPT_Hosts "Hosts" #define OPT_Host "Host" AppPriv::~AppPriv() { if (CurFolder) CurFolder->Empty(); } #if HAS_LIBSSH 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; } #endif VersionCtrl AppPriv::DetectVcs(VcFolder *Fld) { char p[MAX_PATH_LEN]; LUri u = Fld->GetUri(); if (!u.IsFile() || !u.sPath) { #if HAS_LIBSSH auto c = GetConnection(u.ToString()); if (!c) return VcError; auto type = c->Types.Find(u.sPath); if (type) return type; c->DetectVcs(Fld); Fld->GetCss(true)->Color(LColour::Blue); Fld->Update(); return VcPending; #else return VcError; #endif } 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() { LString 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() { LString Name; LRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); LRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(LCss::Len(LCss::LenPx, (float)r.Y())); } else LAssert(!"Missing table ctrl"); } else LAssert(!"Missing toolbar resource"); } void OnPosChange() { LTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; LString::Array GetProgramsInPath(const char *Program) { LString::Array Bin; LString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif LString::Array a = LGetPath(); for (auto p : a) { LFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public LDialog, public LXmlTreeUi { LOptionsFile &Opts; public: OptionsDlg(LViewI *Parent, LOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("svn-limit", IDC_SVN_LIMIT); Map("git-path", IDC_GIT, GV_STRING); Map("git-limit", IDC_GIT_LIMIT); Map("hg-path", IDC_HG, GV_STRING); Map("hg-limit", IDC_HG_LIMIT); Map("cvs-path", IDC_CVS, GV_STRING); Map("cvs-limit", IDC_CVS_LIMIT); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { auto s = new LFileSelect; s->Parent(this); s->Open([this, EditId](auto s, auto status) { if (status) SetCtrlName(EditId, s->Name()); delete s; }); } void BrowseFiles(LViewI *Ctrl, const char *Bin, int EditId) { LRect Pos = Ctrl->GetPos(); LPoint Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); LSubMenu s; LString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { LString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return LDialog::OnNotify(Ctrl, n); } }; int CommitDataCmp(VcCommit **_a, VcCommit **_b) { auto a = *_a; auto b = *_b; return a->GetTs().Compare(&b->GetTs()); } LString::Array AppPriv::GetCommitRange() { LString::Array r; if (Commits) { LArray Sel; Commits->GetSelection(Sel); if (Sel.Length() > 1) { Sel.Sort(CommitDataCmp); r.Add(Sel[0]->GetRev()); r.Add(Sel.Last()->GetRev()); } else if (Sel.Length() == 1) { 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 LTime: return B->GetTs().Compare(&A->GetTs()); } return 0; } struct TestThread : public LThread { public: TestThread() : LThread("test") { Run(); } int Main() { auto Path = LGetPath(); LSubProcess p("python", "/Users/matthew/CodeLib/test.py"); auto t = LString(LGI_PATH_SEPARATOR).Join(Path); for (auto s: Path) printf("s: %s\n", s.Get()); p.SetEnvironment("PATH", t); if (p.Start()) { LStringPipe s; p.Communicate(&s); printf("Test: %s\n", s.NewLStr().Get()); } return 0; } }; class RemoteFolderDlg : public LDialog { class App *app; LTree *tree; struct SshHost *root, *newhost; LXmlTreeUi Ui; public: LString Uri; RemoteFolderDlg(App *application); ~RemoteFolderDlg(); int OnNotify(LViewI *Ctrl, LNotification n); }; class VcDiffFile : public LTreeItem { AppPriv *d; LString File; uint64 modTime = 0; public: VcDiffFile(AppPriv *priv, LString file) : d(priv), File(file) { } const char *GetText(int i = 0) override { return i ? NULL : File.Get(); } LString StripFirst(LString s) { return s.Replace("\\","/").SplitDelimit("/", 1).Last(); } void Reload() { Select(true); } void OnPulse() { LDirectory dir; if (dir.First(File)) { if (modTime) { if (modTime != dir.GetLastWriteTime()) { modTime = dir.GetLastWriteTime(); Reload(); } } else { modTime = dir.GetLastWriteTime(); } } // else LgiTrace("%s:%i couldn't get stat for '%s'\n", _FL, File.Get()); } void OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { LSubMenu s; s.AppendItem("Remove", IDM_DELETE); switch (s.Float(GetTree(), m)) { case IDM_DELETE: { if (LTreeItem::Select()) d->Files->Empty(); delete this; return; } } } } void Select(bool selected) override { LTreeItem::Select(selected); if (!selected) return; d->Files->Empty(); d->Diff->Name(NULL); LFile in(File, O_READ); LString s = in.Read(); if (!s) return; LString NewLine("\n"); LString::Array a = s.Replace("\r").Split("\n"); LArray index; LString oldName, newName; LString::Array Diff; VcFile *f = NULL; bool InPreamble = false; bool InDiff = false; for (unsigned i=0; iSetDiff(NewLine.Join(Diff)); f->Select(false); } Diff.Empty(); oldName.Empty(); newName.Empty(); InDiff = false; InPreamble = true; } else if (!Strnicmp(Ln, "Index", 5)) { if (InPreamble) index = a[i].SplitDelimit(": ", 1).Slice(1); } else if (!strncmp(Ln, "--- ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) oldName = p[1]; } else if (!strncmp(Ln, "+++ ", 4)) { auto p = a[i].SplitDelimit(" \t", 1); if (p.Length() > 1) newName = p[1]; if (oldName && newName) { InDiff = true; InPreamble = false; f = d->FindFile(newName); if (!f) f = new VcFile(d, NULL, LString(), false); const char *nullFn = "dev/null"; auto Path = StripFirst(oldName); f->SetUri(LString("file:///") + Path); if (newName.Find(nullFn) >= 0) { // Delete f->SetText(Path, COL_FILENAME); f->SetText("D", COL_STATE); } else { f->SetText(Path, COL_FILENAME); if (oldName.Find(nullFn) >= 0) // Add f->SetText("A", COL_STATE); else // Modify f->SetText("M", COL_STATE); } f->GetStatus(); d->Files->Insert(f); } } else if (!_strnicmp(Ln, "------", 6)) { InPreamble = !InPreamble; } else if (!_strnicmp(Ln, "======", 6)) { InPreamble = false; InDiff = true; } else if (InDiff) { Diff.Add(a[i]); } } if (f && Diff.Length()) { f->SetDiff(NewLine.Join(Diff)); Diff.Empty(); } } }; #ifdef HAIKU enum PipeIndexes { READ_END, WRITE_END, }; #include int peekPipe(int fd, char *buf, size_t sz) { int bytesAvailable = 0; int r = ioctl(fd, FIONREAD, &bytesAvailable); // printf("ioctl=%i %i\n", r, bytesAvailable); if (r) return 0; // printf("starting read\n"); auto rd = read(fd, buf, MIN(bytesAvailable, sz)); // printf("read=%i\n", (int)rd); return rd; } LString RunProcess(const char *exe, const char *args) { LStringPipe r; int fd[2]; pipe(fd); printf("Running %s...\n", exe); auto pid = fork(); if (pid == 0) { // Child... dup2(fd[WRITE_END], STDOUT_FILENO); close(fd[READ_END]); close(fd[WRITE_END]); execlp(exe, exe, args, (char*) NULL); fprintf(stderr, "Failed to execute '%s'\n", exe); exit(1); } else { // Parent... int status; pid_t result; #if 0 // More basic... close(fd[READ_END]); close(fd[WRITE_END]); result = waitpid(pid, &status, 0); printf("waitpid=%i\n", result); #else // Read the output do { result = waitpid(pid, &status, WNOHANG); char buf[256]; auto rd = peekPipe(fd[READ_END], buf, sizeof(buf)); if (rd > 0) r.Write(buf, rd); //printf("waitpid=%i rd=%i\n", result, rd); } while (result == 0); close(fd[READ_END]); close(fd[WRITE_END]); #endif } printf("RunProcess done.\n"); return r.NewLStr(); } #endif class GetVcsVersions : public LThread { AppPriv *d = NULL; public: GetVcsVersions(AppPriv *priv) : LThread("GetVcsVersions") { d = priv; Run(); } bool ParseVersion(int Result, VersionCtrl type, LString s) { if (Result) return false; // printf("s=%s\n", s.Get()); auto p = s.SplitDelimit(); switch (type) { case VcGit: { if (p.Length() > 2) { ToolVersion[type] = Ver2Int(p[2]); d->Log->Print("Git version: %s\n", p[2].Get()); } break; } case VcSvn: { if (p.Length() > 2) { ToolVersion[type] = Ver2Int(p[2]); d->Log->Print("Svn version: %s\n", p[2].Get()); } break; } case VcHg: { if (p.Length() >= 5) { auto Ver = p[4].Strip("()"); ToolVersion[type] = Ver2Int(Ver); d->Log->Print("Hg version: %s\n", Ver.Get()); } break; } case VcCvs: { if (p.Length() > 1) { auto Ver = p[2]; ToolVersion[type] = Ver2Int(Ver); d->Log->Print("Cvs version: %s\n", Ver.Get()); } break; } default: break; } return false; } int Main() { VersionCtrl types[] = { #ifndef HAIKU // Enabling these causes lock error in the parent process... VcCvs, VcSvn, #endif VcHg, VcGit }; for (int i=0; iGetVcName(types[i]); #ifdef HAIKU // Something funky is going on with launching subprocesses on Haiku... // So lets do an absolute minimal example of fork/exec to test whether it's // something in the LSubProcess classes? auto result = RunProcess(Exe, "--version"); ParseVersion(0, types[i], result); #else LSubProcess sub(Exe, "--version"); if (sub.Start()) { LStringPipe p; auto result = sub.Communicate(&p); ParseVersion(result, types[i], p.NewLStr()); } #endif } printf("GetVcsVersions finished.\n"); return 0; } }; class App : public LWindow, public AppPriv { LAutoPtr ImgLst; LBox *FoldersBox = NULL; bool CallMethod(const char *MethodName, LScriptArguments &Args) { if (!Stricmp(MethodName, METHOD_GetContext)) { *Args.GetReturn() = (AppPriv*)this; return true; } return false; } public: App() { LString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); LRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { SetPulse(200); DropTarget(true); Visible(true); } } ~App() { SerializeState(&Opts, "WndPos", false); SaveFolders(); } void OnCreate() { 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->ColumnHeaders(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(L_WRAP_NONE); } } else LAssert(!"No ctrl?"); Tabs = new LTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); LTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); Diff->SetWrapType(L_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new LTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(L_WRAP_NONE); SetCtrlValue(IDC_UPDATE, true); AttachChildren(); LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { new GetVcsVersions(this); for (auto c: f->Children) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); } } 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(); } } void SaveFolders() { LXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) return; f->EmptyChildren(); for (auto i: *Tree) { VcFolder *vcf = dynamic_cast(i); if (vcf) f->InsertTag(vcf->Save()); } Opts.Unlock(); Opts.SerializeFile(true); } LMessage::Result OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #if HAS_LIBSSH case M_RESPONSE: { SshConnection::HandleMsg(Msg); break; } #endif case M_HANDLE_CALLBACK: { LAutoPtr Pc((ProcessCallback*)Msg->A()); if (Pc) Pc->OnComplete(); break; } } return LWindow::OnEvent(Msg); } void OnReceiveFiles(LArray &Files) { for (auto f : Files) { if (LDirExists(f)) OpenLocalFolder(f); } } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_PATCH_VIEWER: { OpenPatchViewer(this, &Opts); break; } case IDM_OPEN_LOCAL: { OpenLocalFolder(); break; } case IDM_OPEN_REMOTE: { OpenRemoteFolder(); break; } case IDM_OPEN_DIFF: { auto s = new LFileSelect; s->Parent(this); s->Open([this](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } case IDM_OPTIONS: { auto Dlg = new OptionsDlg(this, Opts); Dlg->DoModal([](auto dlg, auto ctrlId) { delete dlg; }); break; } case IDM_FIND: { auto i = new LInput(this, "", "Search string:"); i->DoModal([this, i](auto dlg, auto ctrlId) { if (ctrlId == IDOK) { LString::Array Revs; Revs.Add(i->GetStr()); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } delete dlg; }); break; } case IDM_UNTRACKED: { auto mi = GetMenu()->FindItem(IDM_UNTRACKED); if (!mi) break; mi->Checked(!mi->Checked()); LArray Flds; Tree->GetSelection(Flds); for (auto f : Flds) { f->Refresh(); } break; } case IDM_REFRESH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Refresh(); break; } case IDM_PULL: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Pull(); break; } case IDM_PUSH: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->Push(); break; } case IDM_STATUS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->FolderStatus(); break; } case IDM_UPDATE_SUBS: { LArray Flds; Tree->GetSelection(Flds); for (auto f: Flds) f->UpdateSubs(); break; break; } case IDM_EXIT: { LCloseApp(); break; } } return 0; } void OnPulse() { for (auto i: *Tree) i->OnPulse(); } void OpenLocalFolder(const char *Fld = NULL) { auto Load = [this](const char *Fld) { // Check the folder isn't already loaded... VcFolder *Has = NULL; LArray Folders; Tree->GetAll(Folders); for (auto f: Folders) { if (f->GetUri().IsFile() && !Stricmp(f->LocalPath(), Fld)) { Has = f; break; } } if (Has) { Has->Select(true); LPopupNotification::Message(this, LString::Fmt("'%s' is already open", Has->LocalPath())); } else { LUri u; u.SetFile(Fld); auto f = new VcFolder(this, u.ToString()); if (f) { Tree->Insert(f); SaveFolders(); } } }; if (!Fld) { auto s = new LFileSelect; s->Parent(this); s->OpenFolder([this, Load](auto s, auto status) { if (status) Load(s->Name()); delete s; }); } else Load(Fld); } void OpenRemoteFolder() { auto Dlg = new RemoteFolderDlg(this); Dlg->DoModal([this, Dlg](auto dlg, auto status) { if (status) { Tree->Insert(new VcFolder(this, Dlg->Uri)); SaveFolders(); } delete dlg; }); } void OpenDiff(const char *File) { Tree->Insert(new VcDiffFile(this, File)); } void OnFilterFolders() { if (!Tree) return; for (auto i = Tree->GetChild(); i; i = i->GetNext()) { auto n = i->GetText(); bool vis = !FolderFilter || Stristr(n, FolderFilter.Get()) != NULL; i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone); } Tree->UpdateAllItems(); Tree->Invalidate(); } void OnFilterCommits() { if (!Commits || !CurFolder) return; if (CommitFilter) { // Filtered logging: CurFolder->LogFilter(CommitFilter); } else { // Regular full logging: CurFolder->ClearLog(); CurFolder->Select(true); } } void OnFilterFiles() { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->FilterCurrentFiles(); } int OnNotify(LViewI *c, LNotification n) { switch (c->GetId()) { case IDC_CLEAR_FILTER_FOLDERS: { SetCtrlName(IDC_FILTER_FOLDERS, NULL); // Fall through } case IDC_FILTER_FOLDERS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FOLDERS, NULL); LString n = GetCtrlName(IDC_FILTER_FOLDERS); if (n != FolderFilter) { FolderFilter = n; OnFilterFolders(); } break; } case IDC_CLEAR_FILTER_COMMITS: { SetCtrlName(IDC_FILTER_COMMITS, NULL); // Fall through } case IDC_FILTER_COMMITS: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_COMMITS, NULL); LString n = GetCtrlName(IDC_FILTER_COMMITS); if (n != CommitFilter) { CommitFilter = n; OnFilterCommits(); } break; } case IDC_CLEAR_FILTER_FILES: { SetCtrlName(IDC_FILTER_FILES, NULL); // Fall through } case IDC_FILTER_FILES: { if (n.Type == LNotifyEscapeKey) SetCtrlName(IDC_FILTER_FILES, NULL); LString n = GetCtrlName(IDC_FILTER_FILES); if (n != FileFilter) { FileFilter = n; OnFilterFiles(); } break; } case IDC_FILES: { switch (n.Type) { case LNotifyItemColumnClicked: { int Col = -1; LMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { // Select / deselect all check boxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (auto f: n) Checked |= f->Checked() > 0; for (auto f: n) f->Checked(Checked ? 0 : 1); } } } break; } default: break; } break; } case IDC_OPEN: { OpenLocalFolder(); break; } case IDC_TREE: { switch (n.Type) { case LNotifyContainerClick: { LMouse m; c->GetMouse(m); if (m.Right()) { LSubMenu s; s.AppendItem("Add Local", IDM_ADD_LOCAL); s.AppendItem("Add Remote", IDM_ADD_REMOTE); s.AppendItem("Add Diff File", IDM_ADD_DIFF_FILE); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD_LOCAL: { OpenLocalFolder(); break; } case IDM_ADD_REMOTE: { OpenRemoteFolder(); break; } case IDM_ADD_DIFF_FILE: { auto s = new LFileSelect; s->Parent(this); s->Open([this](auto dlg, auto status) { if (status) OpenDiff(dlg->Name()); delete dlg; }); break; } } } break; } case (LNotifyType)LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case (LNotifyType)LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } default: break; } break; } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { auto BuildFix = GetCtrlValue(IDC_BUILD_FIX); const char *Msg = GetCtrlName(IDC_MSG); if (BuildFix || ValidStr(Msg)) { auto Sel = Tree->Selection(); if (Sel) { VcFolder *f = dynamic_cast(Sel); if (!f) { for (auto p = Sel->GetParent(); p; p = p->GetParent()) { f = dynamic_cast(p); if (f) break; } } if (f) { auto Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(BuildFix ? DEFAULT_BUILD_FIX_MSG : Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { LArray Folders; Tree->GetAll(Folders); bool AndUpdate = GetCtrlValue(IDC_UPDATE) != 0; for (auto f : Folders) { f->Pull(AndUpdate, LogSilo); } break; } case IDC_STATUS: { LArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } case IDC_BRANCHES: { if (n.Type == LNotifyValueChanged) { VcFolder *f = dynamic_cast(Tree->Selection()); auto branch = c->Name(); if (!f || !branch) { Log->Print("%s:%i - Missing param: %p %p\n", _FL, f, branch); break; } if (f->GetCurrentBranch() != branch) f->Checkout(branch, true); } 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->Checkout(s[0]->GetRev(), false); break; } default: break; } break; } } return 0; } }; struct SshHost : public LTreeItem { LXmlTag *t; LString Host, User, Pass; SshHost(LXmlTag *tag = NULL) { t = tag; if (t) { Serialize(false); SetText(Host); } } void Serialize(bool WriteToTag) { if (WriteToTag) { LUri u; u.sProtocol = "ssh"; u.sHost = Host; u.sUser = User; u.sPass = Pass; t->SetContent(u.ToString()); } else { LUri u(t->GetContent()); if (!Stricmp(u.sProtocol.Get(), "ssh")) { Host = u.sHost; User = u.sUser; Pass = u.sPass; } } } }; RemoteFolderDlg::RemoteFolderDlg(App *application) : app(application), root(NULL), newhost(NULL), tree(NULL) { SetParent(app); LoadFromResource(IDD_REMOTE_FOLDER); if (GetViewById(IDC_HOSTS, tree)) { printf("tree=%p\n", tree); tree->Insert(root = new SshHost()); root->SetText("Ssh Hosts"); } else return; LViewI *v; if (GetViewById(IDC_HOSTNAME, v)) v->Focus(true); Ui.Map("Host", IDC_HOSTNAME); Ui.Map("User", IDC_USER); Ui.Map("Password", IDC_PASS); LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (hosts) { SshHost *h; for (auto c: hosts->Children) if (c->IsTag(OPT_Host) && (h = new SshHost(c))) root->Insert(h); app->Opts.Unlock(); } root->Insert(newhost = new SshHost()); newhost->SetText("New Host"); root->Expanded(true); newhost->Select(true); } RemoteFolderDlg::~RemoteFolderDlg() { } int RemoteFolderDlg::OnNotify(LViewI *Ctrl, LNotification n) { SshHost *cur = tree ? dynamic_cast(tree->Selection()) : NULL; #define CHECK_SPECIAL() \ if (cur == newhost) \ { \ root->Insert(cur = new SshHost()); \ cur->Select(true); \ } \ if (cur == root) \ break; switch (Ctrl->GetId()) { case IDC_HOSTS: { switch (n.Type) { case LNotifyItemSelect: { bool isRoot = cur == root; SetCtrlEnabled(IDC_HOSTNAME, !isRoot); SetCtrlEnabled(IDC_USER, !isRoot); SetCtrlEnabled(IDC_PASS, !isRoot); SetCtrlEnabled(IDC_DELETE, !isRoot && !(cur == newhost)); SetCtrlName(IDC_HOSTNAME, cur ? cur->Host.Get() : NULL); SetCtrlName(IDC_USER, cur ? cur->User.Get() : NULL); SetCtrlName(IDC_PASS, cur ? cur->Pass.Get() : NULL); break; } default: break; } break; } case IDC_HOSTNAME: { CHECK_SPECIAL() if (cur) { cur->Host = Ctrl->Name(); cur->SetText(cur->Host ? cur->Host : ""); } break; } case IDC_DELETE: { auto sel = tree ? dynamic_cast(tree->Selection()) : NULL; if (!sel) break; LXmlTag *hosts = app->Opts.LockTag(OPT_Hosts, _FL); if (!hosts) { LAssert(!"Couldn't lock tag."); break; } if (hosts->Children.HasItem(sel->t)) { sel->t->RemoveTag(); DeleteObj(sel->t); delete sel; } app->Opts.Unlock(); break; } case IDC_USER: { CHECK_SPECIAL() if (cur) cur->User = Ctrl->Name(); break; } case IDC_PASS: { CHECK_SPECIAL() if (cur) cur->Pass = Ctrl->Name(); break; } case IDOK: { LXmlTag *hosts; if (!(hosts = app->Opts.LockTag(OPT_Hosts, _FL))) { if (!(app->Opts.CreateTag(OPT_Hosts) && (hosts = app->Opts.LockTag(OPT_Hosts, _FL)))) break; } LAssert(hosts != NULL); for (auto i = root->GetChild(); i; i = i->GetNext()) { SshHost *h = dynamic_cast(i); if (!h || h == newhost) continue; if (h->t) ; else if ((h->t = new LXmlTag(OPT_Host))) hosts->InsertTag(cur->t); else return false; h->Serialize(true); } app->Opts.Unlock(); LUri u; u.sProtocol = "ssh"; u.sHost = GetCtrlName(IDC_HOSTNAME); u.sUser = GetCtrlName(IDC_USER); u.sPass = GetCtrlName(IDC_PASS); u.sPath = GetCtrlName(IDC_REMOTE_PATH); Uri = u.ToString(); // Fall through } case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return 0; } const char* toString(VersionCtrl v) { switch (v) { case VcCvs: return "VcCvs"; case VcSvn: return "VcSvn"; case VcGit: return "VcGit"; case VcHg: return "VcHg"; case VcPending: return "VcPending"; case VcError: return "VcError"; } return "VcNone"; } ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { // LStructuredLog::UnitTest(); a.AppWnd = new App; a.Run(); DeleteObj(a.AppWnd); } LAssert(VcCommit::Instances == 0); return 0; } diff --git a/src/common/Lgi/FileSelect.cpp b/src/common/Lgi/FileSelect.cpp --- a/src/common/Lgi/FileSelect.cpp +++ b/src/common/Lgi/FileSelect.cpp @@ -1,2296 +1,2296 @@ /*hdr ** FILE: LFileSelect.cpp ** AUTHOR: Matthew Allen ** DATE: 20/5/2002 ** DESCRIPTION: Common file/directory selection dialog ** ** Copyright (C) 1998-2002, Matthew Allen ** fret@memecode.com */ #include #include #include "lgi/common/Lgi.h" #include "lgi/common/Popup.h" #include "lgi/common/List.h" #include "lgi/common/TextLabel.h" #include "lgi/common/Edit.h" #include "lgi/common/Button.h" #include "lgi/common/CheckBox.h" #include "lgi/common/Combo.h" #include "lgi/common/Tree.h" #include "lgi/common/TableLayout.h" #include "lgi/common/Box.h" #include "lgi/common/FileSelect.h" #include "lgi/common/Menu.h" #include "lgi/common/Uri.h" #define FSI_FILE 0 #define FSI_DIRECTORY 1 #define FSI_BACK 2 #define FSI_UPDIR 3 #define FSI_NEWDIR 4 #define FSI_DESKTOP 5 #define FSI_HARDDISK 6 #define FSI_CDROM 7 #define FSI_FLOPPY 8 #define FSI_NETWORK 9 enum DlgType { TypeNone, TypeOpenFile, TypeOpenFolder, TypeSaveFile }; class LFileSelectDlg; char ModuleName[] = "File Select"; uint32_t IconBits[] = { 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xC980FA8A, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430F81F, 0x84308430, 0x84308430, 0xF81F8430, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCCE0F81F, 0x9800FCF9, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738E738E, 0xCE6C9E73, 0x738EC638, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9FFF738E, 0x9FFF9FFF, 0x9FFF9FFF, 0x00009FFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0x00009CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0x04F904F9, 0xAD720313, 0xAD72AD72, 0xAD72AD72, 0xFE60CCE0, 0x00009B00, 0x0000AD72, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x738EF81F, 0x667334F3, 0xCE6C9E73, 0xC638B5B6, 0x3186C638, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF9, 0x9CE0FFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81F0000, 0x667F04F9, 0x031304F9, 0xFFFFCE73, 0xFFF9FFFF, 0xCCE0FFFF, 0x9B00FFF3, 0x04F90000, 0x00000313, 0xF81FF81F, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F738E, 0xF81FF81F, 0xF81FF81F, 0x6673738E, 0x34F36673, 0xCE736673, 0xB5B6C638, 0xB5B6DEFB, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF738E, 0xFFFFFFFF, 0xCFFFCFFF, 0x0000CFFF, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x0000FFFF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE09CE0, 0x9CE09CE0, 0x9CE09CE0, 0xF81FF81F, 0xF81FF81F, 0x9CE09CE0, 0x9CE09CE0, 0xF81F0000, 0x0000F81F, 0x0000F81F, 0x0000F81F, 0xF81FF81F, 0x04F904F9, 0xCE730313, 0xFFFFFFFF, 0xFFF9FFF9, 0xFE60CCE0, 0x00009B00, 0x667F3313, 0x00000313, 0x738EF81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0x0000738E, 0xF81FF81F, 0xF81FF81F, 0x34F98430, 0x667364F9, 0x84306679, 0xD6BAB5B6, 0xB5B6C638, 0xF81F3186, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCFFF738E, 0xCFFFCFFF, 0xCFFFCFFF, 0x0000CFFF, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xCE6CFFF3, 0xF81F0000, 0x9CE0F81F, 0xFFF9F7BE, 0xFFF3FFF3, 0x00009CE0, 0xF81FF81F, 0xF81F0000, 0xF81F0000, 0xF81FF81F, 0x031304F9, 0xFFFFCE73, 0x94B294B2, 0xCCE094B2, 0x9B00FFF3, 0xAD720000, 0x3313FFF9, 0x00000313, 0xFFFF738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9CF3FFFF, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xA53494B2, 0x667964F3, 0x00008430, 0xA5348430, 0xCE79B5B6, 0x3186CE79, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE79738E, 0xCE79C638, 0xC638B5B6, 0x0000B5B6, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xCE6CFE73, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0x9CE0CE6C, 0x9CE09CE0, 0xF81F9CE0, 0x0000F81F, 0x0000F81F, 0xCE730313, 0xFFFFFFFF, 0xFFFF94B2, 0xFE60CCE0, 0x00009B00, 0xFFF9AD72, 0xCE73CE73, 0x00003313, 0xD6BA738E, 0xC638C638, 0xC638C638, 0xC638C638, 0xB5B6C638, 0x04200660, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xB5B6B5B6, 0x8430CE79, 0xF81F0000, 0x84300000, 0xCE79CE79, 0x3186CE79, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0xC638738E, 0x84308430, 0x84308430, 0x0000C638, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x00000000, 0xFFF30000, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF9FFF9, 0xFFF3FFF9, 0x0000CE6C, 0xF81F0000, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFFF, 0xFFFF94B2, 0x9B009CEC, 0x00000000, 0xCE73FFF9, 0xAD72FFF9, 0x000094B2, 0xDEFB738E, 0xC638B5B6, 0xC638C638, 0xC638C638, 0xB5B6B5B6, 0xB5B6B5B6, 0x94B2B5B6, 0x0000738E, 0xF81FF81F, 0x738EF81F, 0xF7BEE73C, 0xB5B6E73C, 0x00008430, 0xB5B68430, 0xF7BEEF7D, 0x3186CE79, 0x8430F81F, 0xC638C638, 0xC638C638, 0xC638C638, 0xCE79738E, 0x0000738E, 0xFFFF738E, 0x0000B5B6, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0x0000F81F, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0x07FF07FF, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0x0000FFF3, 0x00000000, 0x00000000, 0xFFF3FFF3, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0x0000CE6C, 0x0000F81F, 0xF81FF81F, 0xFFFFAD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFF0000, 0x0000FFF9, 0xFFF9CE73, 0xCE73AD72, 0x000094B2, 0xDEFB738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x94B2B5B6, 0x0000738E, 0x8430F81F, 0x84308430, 0xA5348430, 0xA534B5B6, 0x8430A534, 0xDEFB34F3, 0xFFFFE73C, 0xF81F3186, 0xFFFF8430, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0xF81F0000, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x07FF0000, 0x000007FF, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFFF3, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81F0000, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73FFF9, 0xAD72CE73, 0x000094B2, 0xD6BA738E, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x94B2B5B6, 0x0000738E, 0xFFFF8430, 0xFFFFFFFF, 0xA534738E, 0xB5B6A534, 0xCE73D6BA, 0x34F96673, 0xB5B6CFFF, 0xF81F3186, 0xC6388430, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638C638, 0xC638F800, 0x738E8430, 0xF81F0000, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x000007FF, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF9AD72, 0xFFF9FFF9, 0xFFFF94B2, 0xFFFFFFFF, 0x0000FFF9, 0xCE73CE73, 0xCE73AD72, 0x000094B2, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0x738E738E, 0xF81F0000, 0xFFFF8430, 0xC638C638, 0x738EC638, 0xB5B6A534, 0xCE73CE79, 0x04F99E73, 0x000004F9, 0xF81FF81F, 0xC6388430, 0xC638C638, 0x84308430, 0x84308430, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FFF3, 0x0000FFF3, 0x00000000, 0x00000000, 0xFE730000, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FFF3, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0xFFF904F9, 0xFFF9FFF9, 0xFFF994B2, 0xFFF9FFF9, 0x0000FFF9, 0xAD72CE73, 0xAD72CE73, 0x00003313, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xFFFF8430, 0x31863186, 0x31863186, 0x84308430, 0xCE73CE79, 0x31869E73, 0x00003186, 0xF81FF81F, 0xC6388430, 0x84308430, 0x00000000, 0x00000000, 0x84308430, 0xC6388430, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD699FFFF, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFF99CE0, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFFF3FE73, 0xFE73FE73, 0xCE6CFE73, 0xF81F0000, 0xFFF99CE0, 0xFFF3FE73, 0xFE73FFF3, 0xFE73FFF3, 0xFE73FFF3, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x04F904F9, 0xFFF9FFF9, 0x00000000, 0x00000000, 0x00000000, 0xCE73AD72, 0x3313AD72, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF8430, 0x042007E0, 0xFFFFFFFF, 0xC638C638, 0x31863186, 0xC6383186, 0x0000738E, 0xF81FF81F, 0xC6388430, 0xC638C638, 0xFFFFFFFF, 0xFFFFFFFF, 0xC638C638, 0xC638C638, 0x738E8430, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xB5B6F81F, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xFFFF0000, 0xD699D699, 0xD699D699, 0xD699D699, 0xD699D699, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xF81F0000, 0xCE6C9CE0, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0xCE6CCE6C, 0x0000CE6C, 0xF81FF81F, 0xF81FF81F, 0x667F04F9, 0xFFF904F9, 0xCE73FFF9, 0xCE73FFF9, 0xAD72CE73, 0xAD72CE73, 0x667F3313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x8430738E, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x84308430, 0x00008430, 0xF81FF81F, 0xB5B6F81F, 0xB5B6F81F, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xB5B6B5B6, 0xF81FB5B6, 0xF81FB5B6, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0x03130313, 0x04F90313, 0x94B294B2, 0x94B294B2, 0x94B294B2, 0x331394B2, 0x33133313, 0x00003313, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 0xF81FF81F, 0x0000F81F, 0x0000F81F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81F0000, 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, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F, 0xF81FF81F}; LInlineBmp FileSelectIcons = { 160, 16, 16, IconBits }; enum Icons2Idx { IcoApps, IcoHome, IcoDesktop, IcoDocuments, IcoDownloads, IcoMovies, IcoMusic, IcoPhotos, IcoFolder, IcoComputer, IcoCDROM, IcoDrive, }; uint32_t Icons2[] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF7FFFFFF, 0xFFF56264, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x0E000000, 0xFFFFFF93, 0xFFFFFFFF, 0xFFFFFFFF, 0xC3FFFFFF, 0x04042368, 0xFFB86421, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x5487BCEF, 0xFFFF1821, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xBFFFFFFF, 0x06062568, 0xFFBF6825, 0xFFFFFFFF, 0xFFFFFFFF, 0x43B0FFFF, 0x31313131, 0x31313131, 0xB0413131, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x62A4F8FF, 0xA8664D45, 0xFFFFFFF8, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x180089FF, 0xFFFFFFF5, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x31DEFFFF, 0xDD2F0000, 0xFF0000FF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x9800FFFF, 0x02319498, 0xFFFF8029, 0xFFFFFFFF, 0xFFFFFFFF, 0x0445DFFF, 0x1616120C, 0x3D040C14, 0xFFFFFFD5, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x23568CCF, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0060FFFF, 0x88000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x4141D9FF, 0xB2B2A483, 0x414180A4, 0xFFFFFFD7, 0xFFFFFFFF, 0xBA35F6FF, 0xD9D9D9D9, 0xD9D9D9D9, 0x35BAD9D9, 0xFFFFFFF3, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x5E1F0E92, 0x2C4D4D76, 0xFFFF900F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000D4FF, 0xFFFFFF7C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0010B5FF, 0x0E000000, 0xFF0000B4, 0xFFFFFFFF, 0x1DC3FFFF, 0x00000000, 0x00000000, 0x00000000, 0xC11A0000, 0xFFFFFFFF, 0xFEFEFFFF, 0x9800FFFF, 0x00969898, 0xFF7E1AE3, 0xFFFFFFFF, 0xFFFFFFFF, 0x160C10C9, 0x16161616, 0x0C161616, 0xFFFFBC09, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000004, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xE500FFFF, 0x00CBE5E5, 0xFFFFFF98, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC9900CC5, 0xB4B4B4C5, 0x78B4B4B4, 0xFFFFC30B, 0xFFFFFFFF, 0xD937D3FF, 0x1212127C, 0xD5701212, 0x37D9BAB2, 0xFFFFFFD3, 0x087EFFFF, 0x00000000, 0x00000000, 0x00000000, 0x7E080000, 0xFFFFFFFF, 0x3FF3FFFF, 0x94C15A16, 0x73C17070, 0xF442285B, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x006899A9, 0xFFFFE006, 0xFFFFFFFF, 0xFFFFFFFF, 0xFDFFFFFF, 0x0E000080, 0x000EABAB, 0xFF000000, 0xFFFFFFFF, 0xC321FFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x1FC1FFFF, 0xFFFFFFFF, 0x000000FF, 0x47000000, 0x00989898, 0x7E18D6FF, 0xFFFFFFFF, 0xDFFFFFFF, 0x16160E12, 0x00000008, 0x16160800, 0xFFD7090E, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFF0000, 0xFFFFFFFF, 0x0200F3FF, 0x0014BCFF, 0x14000000, 0xFFFFFFBE, 0xFFFFFFFF, 0xE500FFFF, 0xBAE5E5E5, 0x00000060, 0x00000000, 0x64000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xD9FFFFFF, 0xE7FFAC0E, 0xB4B4B6C3, 0xB4B4B4B4, 0xFFD70D81, 0xFFFFFFFF, 0xD943C3FF, 0xD9D9D9D9, 0xA2CBD9D9, 0x43D9B2C7, 0xFFFFFFBF, 0xCD0AFFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x0ACDCDCD, 0xFFFFFFFF, 0x2339FCFF, 0x6A92AA68, 0x6BBA6B6B, 0x3C249298, 0xFFFFFFFE, 0xFFFFFFFF, 0xDCFFFFFF, 0x0E9D0004, 0xFFFF5800, 0xFFFFFFFF, 0xFFFFFFFF, 0x49ECFFFF, 0xD22D0000, 0x2BD1F2F2, 0xEB000000, 0xFFFFFFFF, 0x0800FFFF, 0x08080808, 0x08080808, 0x06060606, 0x00060606, 0xFFFFFFFF, 0xE3E300FF, 0x04025CD9, 0x0098983D, 0x25DDFFFF, 0xFFFFFF8C, 0x45FFFFFF, 0x1616160A, 0xFFFFFF00, 0x161600FF, 0xFF340B16, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x62270000, 0xFFFF007E, 0xFFFFFFFF, 0xFFFFFFFF, 0x66431FCF, 0x43666666, 0xFFFFD723, 0xFFFFFFFF, 0xE500FFFF, 0xE5E5E5E5, 0xE5E5E5E5, 0xE5E5E5E5, 0x00E5E5E5, 0xFFFFFFFF, 0x020274FF, 0x02020202, 0x02020202, 0x02020202, 0xFFFFFF74, 0x39FFFFFF, 0xFFFFFF8A, 0x76769CD5, 0xB2B4B494, 0xFF3877B2, 0xFFFFFFFF, 0xD954A8FF, 0xAAC3DBD9, 0xC7989E9E, 0x54D9AAF3, 0xFFFFFFAA, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0x641691FF, 0x6666B47A, 0x9CB66C66, 0x176365A6, 0xFFFFFF94, 0xFFFFFFFF, 0xA5FFFFFF, 0x8DEE1F7E, 0xFFC80000, 0xFFFFFFFF, 0xFFFFFFFF, 0x001FCCFF, 0x91EC5C00, 0xEB854343, 0x1D00005A, 0xFFFFFFCB, 0x9800FFFF, 0x97979798, 0x97979797, 0x96969696, 0x00969696, 0xFFFFFFFF, 0xE3E300FF, 0x8700DDE3, 0x00982502, 0x02000000, 0xFFFFFF0C, 0x02CDFFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0xB8031616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x763B0400, 0xFFFFEBB0, 0xFFFF00FF, 0xFFFFFFFF, 0x00000035, 0x2D583D12, 0x582D1212, 0x00000E39, 0xFFFF3D00, 0x1000FFFF, 0x00000000, 0x00000000, 0x00000000, 0x00100000, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xCDD1D1D1, 0xFFFFFF04, 0x41BCFFFF, 0xF6FCDBC9, 0x8A8A6074, 0xB2AE5A5E, 0xBA40B2B2, 0xFFFFFFFF, 0xD96698FF, 0xDBB2AAD5, 0xF3E5EDED, 0x66D9A6F3, 0xFFFFFF94, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0x604F10FB, 0x606068B4, 0x96FCD160, 0x92A66E60, 0xFFFFF611, 0xFFFFFFFF, 0x0AF7FFFF, 0xFDFFDE1F, 0xFF6A002F, 0xFFFFFFFF, 0xFFFFFFFF, 0x0400069B, 0x43F2F291, 0xF133DEDE, 0x00028EF1, 0xFFFF9C04, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xFE00E3E3, 0x98250AC5, 0x2D949898, 0xFFFFFF00, 0x0C74FFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0x620C1616, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE700, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x8C143966, 0x148CCFCD, 0x122F5239, 0xFFFF005E, 0x9C00FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x009CB2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x8366FFFF, 0x70C9BABA, 0xE7E7E794, 0xB25990E7, 0x6581B2B2, 0xFFFFFFFF, 0xD9767EFF, 0xCDF3BFB6, 0xF3CD8585, 0x76D99AF1, 0xFFFFFF7C, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xC9B125AC, 0x8C6060E5, 0x60DBC5B8, 0x5868A4AC, 0xFFFFA61B, 0xFFFFFFFF, 0x0082FFFF, 0xFFFFFF31, 0xE30000E1, 0xFFFFFFFF, 0xFFFFFFFF, 0xC20E0068, 0x43F2F2F2, 0xF133FFFF, 0x0AC1F1F1, 0xFFFF6600, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xFE00E3E3, 0x2B0EC9FE, 0x92969898, 0xFFFFFF00, 0x1231FFFF, 0x16161616, 0xFFFFFF00, 0x161600FF, 0x1F141616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x62DB1F49, 0xD7620A0A, 0x12314721, 0xFFFF005E, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xA823FFFF, 0x5E94B4B4, 0x5E5EBFE7, 0x925FE7BF, 0x22A6B2B2, 0xFFFFFFFF, 0xD98C68FF, 0x6CF3EDA0, 0xF370D1D5, 0x8CD9A0ED, 0xFFFFFF68, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xFF622B66, 0x7EA2B6FF, 0xBCC35254, 0x5252526C, 0xFFFF642B, 0x0818FFFF, 0x0000E231, 0x0A0A23D8, 0x35006A7E, 0x166A78B0, 0xFFFFFFFF, 0xF2007AFE, 0x43F2F2F2, 0xF1334747, 0x00F1F1F1, 0xFFFFFE78, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0x0000E3E3, 0x00060000, 0x96969898, 0xFFFFFF00, 0x1414FFFF, 0x00081216, 0xFFFFFF00, 0x080000FF, 0x04161612, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x66666600, 0x394BAE1F, 0x4B396060, 0x5E521DB0, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xB206FFFF, 0x8A76B4B4, 0xF6F65EE7, 0x7588E75E, 0x05B0B2B2, 0xFFFFFFFF, 0xD99E52FF, 0xAAF3EDA0, 0xF3A86E6E, 0x9CD9A0ED, 0xFFFFFF50, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xB8492F4D, 0x49494994, 0xA8FF7E49, 0x49494949, 0xFFFF4531, 0x0000FFFF, 0x640043AB, 0x00000064, 0x0002CD00, 0x0008A8C1, 0xFFFFFFFF, 0xF200FFFF, 0xE2F2F2F2, 0xF1DFD2D2, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x005ADDE3, 0x96969898, 0xFFFFFF00, 0x141AFFFF, 0xFF980816, 0xFFFFFFFF, 0x98FFFFFF, 0x04161608, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x64666600, 0x660ADF00, 0x0A666666, 0x666402DF, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xAC0CFFFF, 0x8A76B4B4, 0xF6F65EE7, 0x7588E55E, 0x05B2B2B2, 0xFFFFFFFF, 0xD9AC39FF, 0xF3F6C3B6, 0xF6F3EDED, 0xAED9B6C3, 0xFFFFFF39, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xA8413F49, 0x41414141, 0x83C98841, 0x41414141, 0xFFFF452B, 0x4308FFFF, 0xD20200C6, 0x0A0A0A0A, 0x8AB25A00, 0x1808C383, 0xFFFFFFFF, 0xF200FFFF, 0xF2F2F2F2, 0xF1F1F2F2, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00D9E3E3, 0x96969898, 0xFFFFFF00, 0x1235FFFF, 0xBC001216, 0xFFFFFFFF, 0x02BCFFFF, 0x21141612, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00FF, 0xFFFFFFFF, 0x64666600, 0x660CDD04, 0x0C666666, 0x666204DD, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0xA227FFFF, 0x6094B4B4, 0x605EBFE7, 0x925FE5BF, 0x21A8B2B2, 0xFFFFFFFF, 0xD9C123FF, 0xE5BAAAD5, 0xBAE3F8FA, 0xC1D9D5AA, 0xFFFFFF23, 0xCD00FFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x00CDCDCD, 0xFFFFFFFF, 0xA8435A5E, 0x3B3B3B3B, 0xB43FA83B, 0x3B3B3B3D, 0xFFFF702B, 0xF9FFFFFF, 0xFF9F0012, 0xFFFFFFFF, 0x47FFFFFF, 0xFFFF7E00, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x97979798, 0x97979797, 0x96969696, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00DDE3E3, 0x9696989A, 0xFFFFFF00, 0x0C6CFFFF, 0x0C0E1616, 0xFFFFFFDB, 0x0E0EDBFF, 0x640C1616, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFF00F5, 0xFFFFFFFF, 0x66666600, 0x392DC716, 0x2D396666, 0x666614C9, 0xFFFF0066, 0xB200FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x885EFFFF, 0x5AB4B4B4, 0xE7E7E790, 0xC76F92E5, 0x5C85B8B8, 0xFFFFFFFF, 0xD9D30AFF, 0xAAC3DBD9, 0xC3AA9E9E, 0xD5D9D9DB, 0xFFFFFF08, 0xCD0AFFFF, 0xCDCDCDCD, 0xCDCDCDCD, 0xCDCDCDCD, 0x0ACDCDCD, 0xFFFFFFFF, 0xE1A814AC, 0x33333388, 0x56337C54, 0x70416EA2, 0xFFFFA62B, 0x51FFFFFF, 0xFFFB1200, 0xFFFFFFFF, 0xCBFFFFFF, 0xFFDBB03F, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0x4B4B9798, 0x4B4B4B4B, 0x49494949, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00DDE3E3, 0x9696989A, 0xFFFFFF00, 0x02C7FFFF, 0x0C161616, 0xFFFFF323, 0x160C23F3, 0xB9041616, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0xFAFFFFFF, 0xFFFFFF00, 0x8EDDFFFF, 0xFFFF0035, 0xFFFFFFFF, 0x66666600, 0x2FD93741, 0xD92F0E0E, 0x66663F39, 0xFFFF0066, 0xB002FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B0B2B2, 0xFFFFFFFF, 0xD1D102FF, 0xD1D1D1D1, 0xD1D1D1D1, 0xD1D1D1D1, 0xFFFFFF02, 0x43BAFFFF, 0xAEB4B4B4, 0x888A5E5A, 0xFAF4735F, 0xB841C7D9, 0xFFFFFFFF, 0xD18C2FFF, 0xD9D9D9D9, 0xD9D9D9D9, 0x8ED1D9D9, 0xFFFFFF2F, 0x087EFFFF, 0x00000000, 0x00000000, 0x00000000, 0x7C080000, 0xFFFFFFFF, 0xFF720CFC, 0x2B2B49ED, 0x2B2B458E, 0x62EDFF9A, 0xFFFFF80C, 0x76F0FFFF, 0xFFFF9702, 0xFFFFFFFF, 0xFFFFFFFF, 0xE50C0094, 0xFFFFFFFF, 0xF200FFFF, 0x00F2F2F2, 0xF1006A00, 0x00F1F1F1, 0xFFFFFFFF, 0xA000FFFF, 0xFF4B9798, 0xFFFF4BFF, 0x49FFFF4B, 0x009E9696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0x00000000, 0xFFFFFF00, 0x45FFFFFF, 0x1616160A, 0xFFFF410A, 0x16160A41, 0xFF350C16, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x398CDBFF, 0xFFFFFF00, 0x000085FF, 0xFFFF0000, 0xFFFFFFFF, 0x66666600, 0xBC353966, 0x35BCDBDB, 0x66666639, 0xFFFF0066, 0xB302FFFF, 0xB2B2B2B2, 0xB2B2B2B2, 0xB2B2B2B2, 0x00B2B2B2, 0xFFFFFFFF, 0x02020202, 0x02020202, 0x02020202, 0x02020202, 0xFFFF0202, 0x39FFFFFF, 0xB4B4B478, 0x757594B4, 0xFFFFD39A, 0xFF378CFF, 0xFFFFFFFF, 0x165000FF, 0x16161616, 0x16161616, 0x50161616, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x787878FF, 0xFFFFFF78, 0xFFFFFFFF, 0xFFFFFFFF, 0xA40A90FF, 0xAA947892, 0x9E98C9FF, 0x21BFFAAC, 0xFFFFFF96, 0x376EFFFF, 0xFFFFFEB7, 0xFFFFFFFF, 0xFFFFFFFF, 0x7C000083, 0xFFFFFFFF, 0xF300FFFF, 0x00F2F2F2, 0xF1005800, 0x00F1F1F1, 0xFFFFFFFF, 0x7421FFFF, 0xFF4B9798, 0xFFFF4BFF, 0x49FFFF4B, 0x1F749696, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0xFEFEFFFE, 0xFFFFFFFF, 0xDFFFFFFF, 0x16160E12, 0x6A680816, 0x16161608, 0xFFD9080E, 0xFFFFFFFF, 0x000000FF, 0xCFCFCFCF, 0xCFCFCFCF, 0x0000CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x0000008A, 0xFFFFFF00, 0x00000CFF, 0xFFFF0C00, 0xFFFFFFFF, 0x66666600, 0x18436666, 0x43180000, 0x66666666, 0xFFFF0066, 0x9902FFFF, 0x98989899, 0x98989898, 0x98989898, 0x00989898, 0xFFFFFFFF, 0x02020280, 0x02020202, 0x02020202, 0x02020202, 0xFFFF7F02, 0xD9FFFFFF, 0xB4B4830E, 0xB2B2B2B4, 0xFFE5C1B4, 0xFFD90CAA, 0xFFFFFFFF, 0x1D9800FF, 0x98989898, 0x984B984B, 0x9898984B, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x9E9E9ED3, 0xFFFFD39E, 0xFFFFFFFF, 0xFFFFFFFF, 0x0A3BFFFF, 0x2F1D1DA4, 0x1D1D50D1, 0x3F081D1D, 0xFFFFFFFF, 0x2129FFFF, 0xFFFFFFE2, 0xFFFFFFFF, 0xFFFFFFFF, 0x450039F1, 0xFFFFFFFF, 0xF300FFFF, 0x00F2F2F2, 0xF1000000, 0x00F1F1F1, 0xFFFFFFFF, 0x10C3FFFF, 0x00000000, 0x00000000, 0x00000000, 0xBE100000, 0xFFFFFFFF, 0xE3E300FF, 0xE3E3E3E3, 0x00E3E3E3, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0x160C0EC9, 0x0C0C1616, 0x0C161616, 0xFFFFBE0A, 0xFFFFFFFF, 0x00FF00FF, 0xCFCFCFCF, 0xCFCFCFCF, 0xFF00CFCF, 0xFFFFFF00, 0xFFFFFFFF, 0x0000000C, 0xFFFFFF04, 0x00001AFF, 0xFFFF4D00, 0xFFFFFFFF, 0x00000045, 0x00000000, 0x00000000, 0x00000000, 0xFFFF4300, 0x0235FFFF, 0x00020202, 0x00000000, 0x00000000, 0x31000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xB4780CC5, 0xB2B2B2B2, 0x8CC7C3B2, 0xFFFFC30A, 0xFFFFFFFF, 0x1D9800FF, 0x98989898, 0x984B984B, 0x9800984B, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF, 0x64646464, 0xFFFF6464, 0xFFFFFFFF, 0xFFFFFFFF, 0x39F6FFFF, 0x1616391D, 0x16161694, 0xF63D0610, 0xFFFFFFFF, 0xE82BFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x665CFEFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x043DDDFF, 0x1616120C, 0x35040C14, 0xFFFFFFD5, 0xFFFFFFFF, 0x000000FF, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFF00, 0xFFFFFFFF, 0x0000001F, 0xFFFFFF43, 0x181DB8FF, 0xFFFFED5C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x4239D9FF, 0xACB0A481, 0x374180A0, 0xFFFFFFD7, 0xFFFFFFFF, 0x1D7810FF, 0x98989898, 0x984B984B, 0x7898984B, 0xFFFFFF10, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x080C1088, 0x08080C62, 0xFFFE880A, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xBEFFFFFF, 0x020A2766, 0xFFB6621F, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x6E2521B8, 0xFFFFFFF1, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xB8FFFFFF, 0x08022164, 0xFFB86427, 0xFFFFFFFF, 0xFFFFFFFF, 0x00028CFF, 0x00000000, 0x00000000, 0x02000000, 0xFFFFFF8C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x62A8FAFF, 0xAC624349, 0xFFFFFFFA, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, }; LInlineBmp TreeIconsImg = { 308, 22, 8, Icons2 }; ////////////////////////////////////////////////////////////////////////// char *LFileType::DefaultExtension() { char *Status = 0; auto T = LString(Extension()).SplitDelimit(";"); if (T.Length()) { char s[256]; strcpy(s, T[0]); char *Dir = strchr(s, '.'); if (Dir) { Status = NewStr(Dir+1); if (Status) strlwr(Status); } } return Status; } ////////////////////////////////////////////////////////////////////////// class LFolderItem : public LListItem { LFileSelectDlg *Dlg; LString Path; public: char *File; bool IsDir; LFolderItem(LFileSelectDlg *dlg, char *FullPath, LDirectory *Dir); ~LFolderItem(); void OnActivate(); const char *GetText(int i); int GetImage(int Flags); void OnSelect(); void OnDelete(bool Ask = true); void OnRename(); void OnMouseClick(LMouse &m); }; ////////////////////////////////////////////////////////////////////////// // This is just a private data container to make it easier to change the // implementation of this class without effecting headers and applications. class LFileSelectPrivate { friend class LFileSelect; friend class LFileSelectDlg; friend class LFolderList; LView *Parent = NULL; LFileSelect *Select = NULL; DlgType Type = TypeNone; LString Title; LString DefExt; bool MultiSelect = false; List Files; int CurrentType = -1; List Types; List History; bool ShowReadOnly = false; bool ReadOnly = false; bool EatClose = false; public: static LImageList *BtnIcons, *TreeIcons; static LString InitPath; static bool InitShowHiddenFiles; static LRect InitSize; LFileSelectPrivate(LFileSelect *select) { Select = select; if (!BtnIcons) BtnIcons = new LImageList(16, 16, FileSelectIcons.Create(0xF81F)); if (!TreeIcons) { LAutoPtr a(TreeIconsImg.Create(0xF81F)); LAutoPtr m(new LMemDC(a->X(), a->Y(), System32BitColourSpace)); if (a && m) { LColour fore(128, 128, 128); for (int y=0; yY(); y++) { uint8_t *i = (uint8_t*)(*a)[y]; auto *e = i + a->X(); System32BitPixel *o = (System32BitPixel*)(*m)[y]; while (i < e) { o->r = (int)fore.r() * *i / 255; o->g = (int)fore.g() * *i / 255; o->b = (int)fore.b() * *i / 255; o->a = *i; i++; o++; } } TreeIcons = new LImageList(22, 22, m.Release()); } } } virtual ~LFileSelectPrivate() { Types.DeleteObjects(); Files.DeleteArrays(); History.DeleteArrays(); } }; LImageList *LFileSelectPrivate::BtnIcons = NULL; LImageList *LFileSelectPrivate::TreeIcons = NULL; LString LFileSelectPrivate::InitPath; bool LFileSelectPrivate::InitShowHiddenFiles = false; LRect LFileSelectPrivate::InitSize(0, 0, -1, -1); ////////////////////////////////////////////////////////////////////////// // This class implements the UI for the selector. class LFileSelectDlg; class LFolderView { protected: LFileSelectDlg *Dlg; public: LFolderView(LFileSelectDlg *dlg) { Dlg = dlg; } virtual void OnFolder() {} }; class LFolderDrop : public LDropDown, public LFolderView { public: LFolderDrop(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); ~LFolderDrop(); const char *GetClass() override { return "LFolderDrop"; } void OnFolder() override; bool OnLayout(LViewLayoutInfo &Inf) override { Inf.Width.Min = Inf.Width.Max = 18; return true; } }; class LIconButton : public LLayout { LImageList *Icons; int Icon; bool Down; public: LIconButton(int Id, int x, int y, int cx, int cy, LImageList *icons, int icon) { Icons = icons; Icon = icon; SetId(Id); LRect r(x, y, x+cx, y+cy); SetPos(r); Down = false; SetTabStop(true); } const char *GetClass() override { return "LIconButton"; } void OnPaint(LSurface *pDC) override { LRect c = GetClient(); LColour Background(L_MED); c.Offset(-c.x1, -c.y1); LWideBorder(pDC, c, Down ? DefaultSunkenEdge : DefaultRaisedEdge); pDC->Colour(Background); pDC->Rectangle(&c); int x = (c.X()-Icons->TileX()) / 2; int y = (c.Y()-Icons->TileY()) / 2; if (Focus()) { #if WINNATIVE RECT r = c; DrawFocusRect(pDC->Handle(), &r); #endif } Icons->Draw(pDC, c.x1+x+Down, c.y1+y+Down, Icon, Background, !Enabled()); } void OnFocus(bool f) override { Invalidate(); } void OnMouseClick(LMouse &m) override { if (Enabled()) { bool Trigger = Down && !m.Down(); Capture(Down = m.Down()); if (Down) Focus(true); Invalidate(); if (Trigger) { LViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, LNotification(m)); } } } void OnMouseEnter(LMouse &m) override { if (IsCapturing()) { Down = true; Invalidate(); } } void OnMouseExit(LMouse &m) override { if (IsCapturing()) { Down = false; Invalidate(); } } bool OnKey(LKey &k) override { if (k.c16 == ' ' || k.c16 == LK_RETURN) { if (Enabled() && Down ^ k.Down()) { Down = k.Down(); Invalidate(); if (!Down) { LViewI *n=GetNotify()?GetNotify():GetParent(); if (n) n->OnNotify(this, LNotifyActivate); } } return true; } return false; } bool OnLayout(LViewLayoutInfo &Inf) override { Inf.Width.Min = Inf.Width.Max = Icons->TileX() + 4; Inf.Width.Max += 4; Inf.Height.Min = Inf.Height.Max = Icons->TileY() + 4; Inf.Height.Max += 4; return true; } }; class LFolderList : public LList, public LFolderView { LString FilterKey; public: LFolderList(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy); const char *GetClass() override { return "LFolderList"; } void OnFolder() override; bool OnKey(LKey &k) override; void SetFilterKey(LString s) { FilterKey = s; OnFolder(); } }; enum Ctrls { IDC_STATIC = -1, IDD_FILE_SELECT = 1000, IDC_PATH, IDC_DROP, IDC_BACK, IDC_UP, IDC_NEW, IDC_VIEW, IDC_FILE, IDC_TYPE, IDC_SHOWHIDDEN, IDC_SUB_TBL, IDC_BOOKMARKS, IDC_FILTER, IDC_FILTER_CLEAR, }; #if 1 #define USE_FOLDER_CTRL 1 enum FolderCtrlMessages { M_DELETE_EDIT = M_USER + 100, M_NOTIFY_VALUE_CHANGED, M_LOST_FOCUS, }; class FolderCtrlEdit : public LEdit { public: FolderCtrlEdit(int id, LRect c) : LEdit(id, c.x1, c.y1, c.X()-1, c.Y()-1) { } void OnFocus(bool f) { printf("OnFocus(%i)\n", f); if (!f && GetParent()) GetParent()->PostEvent(M_LOST_FOCUS); } }; class FolderCtrl : public LView { struct Part { LAutoPtr ds; LRect Arrow; LRect Text; }; LEdit *e; LArray p; Part *Over; ssize_t Cursor; Part *HitPart(int x, int y, int *Sub = NULL) { for (unsigned i=0; iGetHeight() + 4; Inf.Height.Max = Inf.Height.Min; } return true; } LString NameAt(ssize_t Level) { LString n; #ifndef WINDOWS n += "/"; #endif for (unsigned i=0; i<=Level && iTransparent(false); LDisplayString Arrow(f, ">"); for (unsigned i=0; iArrow.ZOff(Arrow.X()+1, c.Y()-1); n->Arrow.Offset(c.x1, c.y1); f->Colour(LColour(192,192,192), Bk); Arrow.DrawCenter(pDC, &n->Arrow); c.x1 = n->Arrow.x2 + 1; if (n->ds) { // Layout and draw text n->Text.ZOff(n->ds->X() + 4, c.Y()-1); n->Text.Offset(c.x1, c.y1); f->Colour(Fore, Bk); n->ds->DrawCenter(pDC, &n->Text); c.x1 = n->Text.x2 + 1; } } if (p.Length() == 0) { // Layout and draw arrow for the "root folder" f->Colour(LColour(192,192,192), L_WORKSPACE); LRect a; a.ZOff(Arrow.X()+1, c.Y()-1); a.Offset(c.x1, c.y1); Arrow.DrawCenter(pDC, &a); c.x1 = a.x2 + 1; } pDC->Colour(L_WORKSPACE); pDC->Rectangle(&c); } void ExitEditMode() { if (e) { Name(e->Name()); DeleteObj(e); PostEvent(M_DELETE_EDIT); PostEvent(M_NOTIFY_VALUE_CHANGED); Invalidate(); } } void OnMouseClick(LMouse &m) { if (m.IsContextMenu()) { } else if (m.Left()) { if (m.Down()) { Over = HitPart(m.x, m.y); if (p.PtrCheck(Over)) { // Over a path node... Cursor = Over - p.AddressOf(0); Part &o = p[Cursor]; Invalidate(); SendNotify(LNotifyValueChanged); if (o.Arrow.Overlap(m.x, m.y)) { // Show sub-menu at this level ShowMenu(Cursor); } } else if (!e) { // In empty space LRect c = GetClient(); e = new FolderCtrlEdit(GetId()+1, c); if (e) { e->Attach(this); LString s = Name(); e->Name(s); e->SetCaret(s.Length()); e->Focus(true); } } } } } void OnMouseMove(LMouse &m) { Part *o = Over; Over = HitPart(m.x, m.y); if (o != Over) Invalidate(); } void OnMouseExit(LMouse &m) { if (Over) { Over = NULL; Invalidate(); } } int OnNotify(LViewI *c, LNotification n) { if (e != NULL && c->GetId() == e->GetId()) { if (n.Type == LNotifyReturnKey) { ExitEditMode(); } } return 0; } LMessage::Result OnEvent(LMessage *m) { switch (m->Msg()) { case M_LOST_FOCUS: { ExitEditMode(); break; } case M_DELETE_EDIT: { DeleteObj(e); break; } case M_NOTIFY_VALUE_CHANGED: { SendNotify(LNotifyValueChanged); break; } } return LView::OnEvent(m); } virtual bool ShowMenu(ssize_t Level) { if (Level <= 0) return false; LString dir = NameAt(Level-1); LSubMenu s; LDirectory d; LString::Array Opts; for (int b = d.First(dir); b; b = d.Next()) { if (d.IsDir()) { Opts.New() = d.GetName(); s.AppendItem(d.GetName(), (int)Opts.Length()); } } Part &i = p[Level]; LPoint pt(i.Arrow.x1, i.Arrow.y2+1); PointToScreen(pt); int Cmd = s.Float(this, pt.x, pt.y, true); if (Cmd) { LString np; np = dir + DIR_STR + Opts[Cmd-1]; Name(np); PostEvent(M_NOTIFY_VALUE_CHANGED); } else return false; return true; } }; #else #define USE_FOLDER_CTRL 0 #endif class LFileSelectDlg : public LDialog { LRect OldPos; LRect MinSize; LArray Links; LArray Hidden; public: LFileSelectPrivate *d = NULL; LTableLayout *Tbl = NULL; LBox *Sub = NULL; LTree *Bookmarks = NULL; LTextLabel *Ctrl1 = NULL; #if USE_FOLDER_CTRL FolderCtrl *Ctrl2 = NULL; #else LEdit *Ctrl2 = NULL; #endif LFolderDrop *Ctrl3 = NULL; LIconButton *BackBtn = NULL; LIconButton *UpBtn = NULL; LIconButton *NewDirBtn = NULL; LFolderList *FileLst = NULL; LTextLabel *Ctrl8 = NULL; LTextLabel *Ctrl9 = NULL; LEdit *FileNameEdit = NULL; LCombo *FileTypeCbo = NULL; LButton *SaveBtn = NULL; LButton *CancelBtn = NULL; LCheckBox *ShowHidden = NULL; LEdit *FilterEdit = NULL; LFileSelectDlg(LFileSelectPrivate *Select); ~LFileSelectDlg(); const char *GetClass() override { return "LFileSelectDlg"; } int OnNotify(LViewI *Ctrl, LNotification n) override; void OnUpFolder(); void SetFolder(char *f); void OnFolder(); void OnFile(char *f); void OnFilter(const char *Key); void OnCreate() override; bool OnViewKey(LView *v, LKey &k) override { if (k.vkey == LK_UP && k.Alt()) { if (k.Down()) { UpBtn->SendNotify(); } return true; } return false; } void Add(LTreeItem *i, LVolume *v) { if (!i || !v) return; auto Path = v->Path(); i->SetText(v->Name()); i->SetText(Path, 1); for (unsigned n=0; nFirst(); cv; cv = cv->Next()) { LTreeItem *ci = new LTreeItem; if (ci) { i->Insert(ci); Add(ci, cv); } } i->Expanded(true); } }; LFileSelectDlg::LFileSelectDlg(LFileSelectPrivate *select) { d = select; SetParent(d->Parent); MinSize.ZOff(450, 300); if (!d->InitSize.Valid()) { auto Dpi = LScreenDpi(); auto Scale = (float)Dpi.x / 96.0; d->InitSize.Set(0, 0, (int)(650*Scale), (int)(450*Scale) + LAppInst->GetMetric(LGI_MET_DECOR_Y) ); } SetPos(d->InitSize); MoveToCenter(); } LFileSelectDlg::~LFileSelectDlg() { UnregisterHook(this); d->InitShowHiddenFiles = ShowHidden ? ShowHidden->Value() : false; d->InitSize = GetPos(); auto CurPath = GetCtrlName(IDC_PATH); if (ValidStr(CurPath)) d->InitPath = CurPath; } void LFileSelectDlg::OnCreate() { int x = 0, y = 0; AddView(Tbl = new LTableLayout); // Top Row auto *c = Tbl->GetCell(x++, y); c->Add(Ctrl1 = new LTextLabel(IDC_STATIC, 0, 0, -1, -1, "Look in:")); c->VerticalAlign(LCss::Len(LCss::VerticalMiddle)); c = Tbl->GetCell(x++, y); #if USE_FOLDER_CTRL c->Add(Ctrl2 = new FolderCtrl(IDC_PATH)); #else c->Add(Ctrl2 = new LEdit(IDC_PATH, 0, 0, 245, 21, "")); #endif c = Tbl->GetCell(x++, y); c->Add(Ctrl3 = new LFolderDrop(this, IDC_DROP, 336, 7, 16, 21)); c = Tbl->GetCell(x++, y); c->Add(BackBtn = new LIconButton(IDC_BACK, 378, 7, 27, 21, d->BtnIcons, FSI_BACK)); c = Tbl->GetCell(x++, y); c->Add(UpBtn = new LIconButton(IDC_UP, 406, 7, 27, 21, d->BtnIcons, FSI_UPDIR)); c = Tbl->GetCell(x++, y); c->Add(NewDirBtn = new LIconButton(IDC_NEW, 434, 7, 27, 21, d->BtnIcons, FSI_NEWDIR)); // Folders/items row x = 0; y++; c = Tbl->GetCell(x, y, true, 6, 1); c->Add(Sub = new LBox(IDC_SUB_TBL)); Sub->AddView(Bookmarks = new LTree(IDC_BOOKMARKS, 0, 0, -1, -1)); Bookmarks->GetCss(true)->Width(LCss::Len(LCss::LenPx, 150.0f)); Bookmarks->SetImageList(d->TreeIcons, false); LTableLayout *t; Sub->AddView(t = new LTableLayout(11)); // Filter / search row c = t->GetCell(0, 0); c->Add(new LCheckBox(IDC_FILTER_CLEAR, 0, 0, -1, -1, "Filter items:")); c->VerticalAlign(LCss::Len(LCss::VerticalMiddle)); c = t->GetCell(1, 0); c->Add(FilterEdit = new LEdit(IDC_FILTER, 0, 0, 60, 20)); c = t->GetCell(0, 1, true, 2); c->Add(FileLst = new LFolderList(this, IDC_VIEW, 14, 35, 448, 226)); // File name row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl8 = new LTextLabel(IDC_STATIC, 14, 275, -1, -1, "File name:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileNameEdit = new LEdit(IDC_FILE, 100, 268, 266, 21, "")); c = Tbl->GetCell(x, y, true, 3); c->Add(SaveBtn = new LButton(IDOK, 392, 268, 70, 21, "Ok")); // 4th row x = 0; y++; c = Tbl->GetCell(x++, y); c->Add(Ctrl9 = new LTextLabel(IDC_STATIC, 14, 303, -1, -1, "Files of type:")); c = Tbl->GetCell(x, y, true, 2); x += 2; c->Add(FileTypeCbo = new LCombo(IDC_TYPE, 100, 296, 266, 21, "")); c = Tbl->GetCell(x++, y, true, 3); c->Add(CancelBtn = new LButton(IDCANCEL, 392, 296, 70, 21, "Cancel")); // 5th row x = 0; y++; c = Tbl->GetCell(x++, y, true, 6); c->Add(ShowHidden = new LCheckBox(IDC_SHOWHIDDEN, 14, 326, -1, -1, "Show hidden files.")); // Init if (BackBtn) BackBtn->Enabled(false); if (SaveBtn) SaveBtn->Enabled(false); if (FileLst) FileLst->MultiSelect(d->MultiSelect); if (ShowHidden) ShowHidden->Value(d->InitShowHiddenFiles); // Load types if (!d->Types.Length()) { LFileType *t = new LFileType; if (t) { t->Description("All Files"); t->Extension(LGI_ALL_FILES); d->Types.Insert(t); } } for (auto t: d->Types) { char s[256]; snprintf(s, sizeof(s), "%s (%s)", t->Description(), t->Extension()); if (FileTypeCbo) FileTypeCbo->Insert(s); } d->CurrentType = 0; // File + Path char *File = d->Files[0]; if (File) { char *Dir = strrchr(File, DIR_CHAR); if (Dir) { OnFile(Dir + 1); } else { OnFile(File); } } if (d->InitPath) { SetFolder(d->InitPath); } else { SetFolder(LGetExePath()); } OnFolder(); // Size/layout AttachChildren(); RegisterHook(this, LKeyEvents); FileLst->Focus(true); LgiGetUsersLinks(Links); auto v = FileDev->GetRootVolume(); if (v) { for (auto vol = v; vol; vol = vol->Next()) { if (auto *ti = new LTreeItem) { Bookmarks->Insert(ti); Add(ti, vol); ti->Expanded(true); } } } if (Links.Length()) { if (auto *ti = new LTreeItem) { ti->SetText("Bookmarks"); Bookmarks->Insert(ti); for (unsigned n=0; nSetText(leaf?leaf+1:p, 0); ci->SetText(p, 1); ti->Insert(ci); } } ti->Expanded(true); } } // Init names and labels for the specific dialog type: switch (d->Type) { case TypeOpenFile: { Name("Open"); if (SaveBtn) SaveBtn->Name("Open"); break; } case TypeSaveFile: { Name("Save As"); SaveBtn->Name("Save As"); break; } case TypeOpenFolder: { SaveBtn->Enabled(true); FileNameEdit->Enabled(false); Name("Open Folder"); SaveBtn->Name("Open"); break; } default: { LAssert(!"Impl me."); break; } } } void LFileSelectDlg::OnFile(char *f) { if (d->Type != TypeOpenFolder) { FileNameEdit->Name(f ? f : (char*)""); SaveBtn->Enabled(ValidStr(f)); } } void LFileSelectDlg::SetFolder(char *f) { auto CurPath = GetCtrlName(IDC_PATH); if (CurPath) { d->History.Insert(NewStr(CurPath)); SetCtrlEnabled(IDC_BACK, true); } SetCtrlName(IDC_PATH, f); } void LFileSelectDlg::OnFolder() { if (Ctrl3) Ctrl3->OnFolder(); if (FileLst) FileLst->OnFolder(); auto CurPath = GetCtrlName(IDC_PATH); if (CurPath && UpBtn) UpBtn->Enabled(strlen(CurPath)>3); } void LFileSelectDlg::OnUpFolder() { auto Cur = GetCtrlName(IDC_PATH); if (Cur) { char Dir[MAX_PATH_LEN]; strcpy(Dir, Cur); if (strlen(Dir) > 3) { LTrimDir(Dir); if (!strchr(Dir, DIR_CHAR)) strcat(Dir, DIR_STR); SetFolder(Dir); OnFolder(); } } } void LFileSelectDlg::OnFilter(const char *Key) { if (FileLst) FileLst->SetFilterKey(Key); } int LFileSelectDlg::OnNotify(LViewI *Ctrl, LNotification n) { switch (Ctrl->GetId()) { case IDC_BOOKMARKS: { if (n.Type == LNotifyItemSelect && Bookmarks) { LTreeItem *s = Bookmarks->Selection(); if (s) { const char *p = s->GetText(1); if (LDirExists(p)) { SetCtrlName(IDC_PATH, p); OnFolder(); } } } break; } case IDC_PATH: { if (n.Type == LNotifyValueChanged) OnFolder(); break; } case IDC_VIEW: { if (FileLst) { /* These functions are handled by the list control's OnKey implementation if (Flags == GLIST_NOTIFY_RETURN) { List s; if (FileLst->GetSelection(s)) { LFolderItem *i = dynamic_cast(s.First()); if (i) { i->OnActivate(); } } } else if (Flags == GLIST_NOTIFY_BACKSPACE) { OnUpFolder(); } */ } break; } case IDC_FILE: { auto f = Ctrl->Name(); if (!f) break; if (n.Type == LNotifyReturnKey) { // allow user to insert new type by typing the pattern into the file name edit box and // hitting enter if (strchr(f, '?') || strchr(f, '*')) { // it's a mask, push the new type on the the type stack and // refilter the content int TypeIndex = -1; int n = 0; for (auto t: d->Types) { if (t->Extension() && stricmp(t->Extension(), f) == 0) { TypeIndex = n; break; } n++; } // insert the new type if not already there if (TypeIndex < 0) { LFileType *n = new LFileType; if (n) { n->Description(f); n->Extension(f); TypeIndex = (int)d->Types.Length(); d->Types.Insert(n); FileTypeCbo->Insert(f); } } // select the new type if (TypeIndex >= 0) { FileTypeCbo->Value(d->CurrentType = TypeIndex); } // clear the edit box Ctrl->Name(""); // Update and don't do normal save btn processing. OnFolder(); // Skip the IDOK message generated by the default button d->EatClose = true; printf("%s:%i - eat close true\n", _FL); break; } if (LDirExists(f)) { // Switch to the folder... SetCtrlName(IDC_PATH, f); OnFolder(); Ctrl->Name(NULL); d->EatClose = true; } else if (LFileExists(f)) { // Select the file... d->Files.Insert(NewStr(f)); EndModal(IDOK); break; } } bool HasFile = ValidStr(f); bool BtnEnabled = SaveBtn->Enabled(); if (HasFile ^ BtnEnabled) { SaveBtn->Enabled(HasFile); } break; } case IDC_BACK: { auto It = d->History.rbegin(); char *Dir = *It; if (Dir) { d->History.Delete(Dir); SetCtrlName(IDC_PATH, Dir); OnFolder(); DeleteArray(Dir); if (!d->History[0]) { SetCtrlEnabled(IDC_BACK, false); } } break; } case IDC_SHOWHIDDEN: { FileLst->OnFolder(); break; } case IDC_TYPE: { d->CurrentType = (int)FileTypeCbo->Value(); FileLst->OnFolder(); if (d->Type == TypeSaveFile) { // change extension of current file LFileType *Type = d->Types.ItemAt(d->CurrentType); auto File = FileNameEdit->Name(); if (Type && File) { char *Ext = strchr(File, '.'); if (Ext) { char *DefExt = Type->DefaultExtension(); if (DefExt) { Ext++; char s[256]; ZeroObj(s); memcpy(s, File, Ext-File); strcat(s, DefExt); OnFile(s); DeleteArray(DefExt); } } } } break; } case IDC_UP: { OnUpFolder(); break; } case IDC_FILTER: { const char *n = Ctrl->Name(); SetCtrlValue(IDC_FILTER_CLEAR, ValidStr(n)); OnFilter(n); break; } case IDC_FILTER_CLEAR: { if (!Ctrl->Value()) { SetCtrlName(IDC_FILTER, NULL); OnFilter(NULL); } break; } case IDC_NEW: { auto Dlg = new LInput(this, "", "Create new folder:", "New Folder"); Dlg->DoModal([this, Dlg](auto d, auto code) { char New[MAX_PATH_LEN]; strcpy(New, GetCtrlName(IDC_PATH)); if (New[strlen(New)-1] != DIR_CHAR) strcat(New, DIR_STR); strcat(New, Dlg->GetStr()); FileDev->CreateFolder(New); OnFolder(); delete d; }); break; } case IDOK: { if (d->EatClose) { printf("%s:%i - SKIPPING eat close false\n", _FL); d->EatClose = false; break; } auto Path = GetCtrlName(IDC_PATH); auto File = GetCtrlName(IDC_FILE); if (Path) { char f[MAX_PATH_LEN]; d->Files.DeleteArrays(); if (d->Type == TypeOpenFolder) { d->Files.Insert(NewStr(Path)); } else { List Sel; if (d->Type != TypeSaveFile && FileLst && FileLst->GetSelection(Sel) && Sel.Length() > 1) { for (auto i: Sel) { LMakePath(f, sizeof(f), Path, i->GetText(0)); d->Files.Insert(NewStr(f)); } } else if (ValidStr(File)) { if (strchr(File, DIR_CHAR)) strcpy_s(f, sizeof(f), File); else LMakePath(f, sizeof(f), Path, File); d->Files.Insert(NewStr(f)); } } } // fall thru } case IDCANCEL: { EndModal(Ctrl->GetId()); break; } } return 0; } ////////////////////////////////////////////////////////////////////////// class LFileSystemItem : public LTreeItem { class LFileSystemPopup *Popup; LString Path; public: LFileSystemItem(LFileSystemPopup *popup, LVolume *vol, char *path = 0); char *GetPath() { return Path; } void OnPath(const char *p); void OnMouseClick(LMouse &m); bool OnKey(LKey &k); }; #define IDC_TREE 100 class LFileSystemPopup : public LPopup { friend class LFileSystemItem; LFileSelectDlg *Dlg; LTree *Tree; LFileSystemItem *Root; public: LFileSystemPopup(LView *owner, LFileSelectDlg *dlg, int x) : LPopup(owner) { Dlg = dlg; LRect r(0, 0, x, 150); SetPos(r); Children.Insert(Tree = new LTree(IDC_TREE, 1, 1, X()-3, Y()-3)); if (Tree) { Tree->Sunken(false); LVolume *v = FileDev->GetRootVolume(); if (v) { Tree->SetImageList(Dlg->d->BtnIcons, false); Tree->Insert(Root = new LFileSystemItem(this, v)); for (auto next = v->Next(); next; next = next->Next()) { Tree->SetImageList(Dlg->d->BtnIcons, false); Tree->Insert(Root = new LFileSystemItem(this, next)); } } } } ~LFileSystemPopup() { } const char *GetClass() override { return "LFileSystemPopup"; } void Visible(bool i) override { if (i && Root) { Root->OnPath(Dlg->GetCtrlName(IDC_PATH)); } LPopup::Visible(i); } void OnPaint(LSurface *pDC) override { // Draw border LRect c = GetClient(); c.Offset(-c.x1, -c.y1); pDC->Colour(L_BLACK); pDC->Box(&c); c.Inset(1, 1); } void OnActivate(LFileSystemItem *i) { if (i) { Dlg->SetFolder(i->GetPath()); Dlg->OnFolder(); Visible(false); } } }; LFileSystemItem::LFileSystemItem(LFileSystemPopup *popup, LVolume *Vol, char *path) { Popup = popup; Expanded(true); if (Vol) { Path = Vol->Path(); SetText(Vol->Name()); switch (Vol->Type()) { case VT_FLOPPY: SetImage(FSI_FLOPPY); break; case VT_HARDDISK: case VT_RAMDISK: SetImage(FSI_HARDDISK); break; case VT_CDROM: SetImage(FSI_CDROM); break; case VT_NETWORK_SHARE: SetImage(FSI_NETWORK); break; case VT_DESKTOP: SetImage(FSI_DESKTOP); break; default: SetImage(FSI_DIRECTORY); break; } for (LVolume *v=Vol->First(); v; v=v->Next()) { Insert(new LFileSystemItem(Popup, v)); } } else { Path = NewStr(path); SetText(strrchr(Path, DIR_CHAR)+1); SetImage(FSI_DIRECTORY); } } void LFileSystemItem::OnPath(const char *p) { switch (GetImage()) { case FSI_DESKTOP: { if (p && Path && stricmp(Path, p) == 0) { Select(true); p = 0; } break; } case FSI_DIRECTORY: { return; } default: { LTreeItem *Old = Items[0]; if (Old) { Old->Remove(); DeleteObj(Old); } break; } } if (p) { auto PathLen = strlen(Path); if (Path && strnicmp(Path, p, PathLen) == 0 && (p[PathLen] == DIR_CHAR || p[PathLen] == 0) #ifdef LINUX && strcmp(Path, "/") != 0 #endif ) { LTreeItem *Item = this; if (GetImage() != FSI_DESKTOP && strlen(p) > 3) { auto Start = p + strlen(Path); if (Start) { char s[256]; strcpy(s, Path); auto T = LString(Start).SplitDelimit(DIR_STR); for (int i=0; iInsert(New); Item = New; } } } } if (Item) { Item->Select(true); } } } for (auto item: Items) { LFileSystemItem *i = dynamic_cast(item); if (i) i->OnPath(p); } } void LFileSystemItem::OnMouseClick(LMouse &m) { if (m.Left() && m.Down()) { Popup->OnActivate(this); } } bool LFileSystemItem::OnKey(LKey &k) { if ((k.c16 == ' ' || k.c16 == LK_RETURN)) { if (k.Down() && k.IsChar) { Popup->OnActivate(this); } return true; } return false; } LFolderDrop::LFolderDrop(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : LDropDown(Id, x, y, cx, cy, 0), LFolderView(dlg) { SetPopup(new LFileSystemPopup(this, dlg, cx + (dlg->Ctrl2 ? dlg->Ctrl2->X() : 0) )); } LFolderDrop::~LFolderDrop() { } void LFolderDrop::OnFolder() { } ////////////////////////////////////////////////////////////////////////// #define IDM_OPEN 1000 #define IDM_CUT 1001 #define IDM_COPY 1002 #define IDM_RENAME 1003 #define IDM_PROPERTIES 1004 #define IDM_CREATE_SHORTCUT 1005 #define IDM_DELETE 1006 LFolderItem::LFolderItem(LFileSelectDlg *dlg, char *FullPath, LDirectory *Dir) { Dlg = dlg; Path = FullPath; File = strrchr(Path, DIR_CHAR); if (File) File++; IsDir = Dir->IsDir(); } LFolderItem::~LFolderItem() { } const char *LFolderItem::GetText(int i) { return File; } int LFolderItem::GetImage(int Flags) { return IsDir ? 1 : 0; } void LFolderItem::OnSelect() { if (!IsDir && File) { Dlg->OnFile(Select() ? File : 0); } } void LFolderItem::OnDelete(bool Ask) { if (!Ask || LgiMsg(Parent, "Do you want to delete '%s'?", ModuleName, MB_YESNO, Path.Get()) == IDYES) { bool Status = false; if (IsDir) { Status = FileDev->RemoveFolder(Path, true); } else { Status = FileDev->Delete(Path); } if (Status) { Parent->Remove(this); delete this; } } } void LFolderItem::OnRename() { LInput *Inp = new LInput(Dlg, File, "New name:", Dlg->Name()); Inp->DoModal([&](auto d, auto code) { if (!code) return; char Old[MAX_PATH_LEN]; strcpy_s(Old, sizeof(Old), Path); char New[MAX_PATH_LEN]; File[0] = 0; LMakePath(New, sizeof(New), Path, Inp->GetStr()); if (FileDev->Move(Old, New)) { DeleteArray(Path); Path = NewStr(New); File = strrchr(Path, DIR_CHAR); if (File) File++; Update(); } else { LgiMsg(Dlg, "Renaming '%s' failed.", Dlg->Name(), MB_OK); } delete Inp; }); } void LFolderItem::OnActivate() { if (File) { if (IsDir) { char Dir[256]; strcpy(Dir, Dlg->GetCtrlName(IDC_PATH)); if (Dir[strlen(Dir)-1] != DIR_CHAR) strcat(Dir, DIR_STR); strcat(Dir, File); Dlg->SetFolder(Dir); Dlg->OnFolder(); } else // Is file { Dlg->OnNotify(Dlg->SaveBtn, LNotifyActivate); } } } void LFolderItem::OnMouseClick(LMouse &m) { if (m.Down()) { if (m.Left()) { if (m.Double()) { OnActivate(); } } else if (m.Right()) { LSubMenu *RClick = new LSubMenu; if (RClick) { RClick->AppendItem("Select", IDM_OPEN, true); RClick->AppendSeparator(); RClick->AppendItem("Cut", IDM_CUT, false); RClick->AppendItem("Copy", IDM_COPY, false); RClick->AppendSeparator(); RClick->AppendItem("Create Shortcut", IDM_CREATE_SHORTCUT, false); RClick->AppendItem("Delete", IDM_DELETE, true); RClick->AppendItem("Rename", IDM_RENAME, true); RClick->AppendSeparator(); RClick->AppendItem("Properties", IDM_PROPERTIES, false); if (Parent->GetMouse(m, true)) { switch (RClick->Float(Parent, m.x, m.y)) { case IDM_OPEN: { break; } case IDM_DELETE: { OnDelete(); break; } case IDM_RENAME: { OnRename(); break; } } } DeleteObj(RClick); } } } } int LFolderItemCompare(LListItem *A, LListItem *B, NativeInt Data) { LFolderItem *a = dynamic_cast(A); LFolderItem *b = dynamic_cast(B); if (a && b) { if (a->IsDir ^ b->IsDir) { if (a->IsDir) return -1; else return 1; } else if (a->File && b->File) { return stricmp(a->File, b->File); } } return 0; } LFolderList::LFolderList(LFileSelectDlg *dlg, int Id, int x, int y, int cx, int cy) : LList(Id, x, y, cx, cy), LFolderView(dlg) { SetImageList(Dlg->d->BtnIcons, false); - ShowColumnHeader(false); + ColumnHeaders(false); AddColumn("Name", cx-20); SetMode(LListColumns); } bool LFolderList::OnKey(LKey &k) { bool Status = LList::OnKey(k); switch (k.vkey) { case LK_BACKSPACE: { if (k.Down() && GetWindow()) { // Go up a directory LViewI *v = GetWindow()->FindControl(IDC_UP); if (v) { GetWindow()->OnNotify(v, LNotifyBackspaceKey); } } Status = true; break; } case LK_RETURN: #ifdef LK_KP_ENTER case LK_KP_ENTER: #endif { if (k.Down() && GetWindow()) { LFolderItem *Sel = dynamic_cast(GetSelected()); if (Sel) { if (Sel->IsDir) { auto Cur = GetWindow()->GetCtrlName(IDC_PATH); if (Cur) { char Path[256]; LMakePath(Path, sizeof(Path), Cur, Sel->GetText(0)); if (LDirExists(Path)) { GetWindow()->SetCtrlName(IDC_PATH, Path); Dlg->OnFolder(); } } } else { LViewI *Ok = GetWindow()->FindControl(IDOK); if (Ok) { GetWindow()->SetCtrlName(IDC_FILE, Sel->GetText(0)); GetWindow()->OnNotify(Ok, LNotification(k)); } } } } Status = true; break; } case LK_DELETE: { if (k.Down() && !k.IsChar && GetWindow()) { List Sel; if (GetSelection(Sel)) { LStringPipe Msg; Msg.Push("Do you want to delete:\n\n"); List Delete; for (auto i: Sel) { LFolderItem *s = dynamic_cast(i); if (s) { Delete.Insert(s); Msg.Push("\t"); Msg.Push(s->GetText(0)); Msg.Push("\n"); } } char *Mem = Msg.NewStr(); if (Mem) { if (LgiMsg(this, Mem, ModuleName, MB_YESNO) == IDYES) { for (auto d: Delete) { d->OnDelete(false); } } DeleteArray(Mem); } } } Status = true; break; } } // LgiTrace("%s:%i LFolderList::OnKey, key=%i down=%i status=%i\n", _FL, k.vkey, k.Down(), Status); return Status; } void LFolderList::OnFolder() { Empty(); LDirectory Dir; List New; // Get current type LFileType *Type = Dlg->d->Types.ItemAt(Dlg->d->CurrentType); List Ext; if (Type) { auto T = LString(Type->Extension()).SplitDelimit(";"); for (size_t i=0; iCtrl2) return; bool ShowHiddenFiles = Dlg->ShowHidden ? Dlg->ShowHidden->Value() : false; for (auto Found = Dir.First(Dlg->Ctrl2->Name()); Found; Found = Dir.Next()) { char Name[LDirectory::MaxPathLen]; Dir.Path(Name, sizeof(Name)); bool Match = true; if (!ShowHiddenFiles && Dir.IsHidden()) { Match = false; } else if (!Dir.IsDir() && Ext.Length() > 0) { Match = false; for (auto e: Ext) { bool m = MatchStr(e, Name); if (m) { Match = true; break; } } } if (FilterKey && Match) Match = stristr(Dir.GetName(), FilterKey) != NULL; if (Match) New.Insert(new LFolderItem(Dlg, Name, &Dir)); } // Sort items... New.Sort(LFolderItemCompare); // Display items... Insert(New); } ////////////////////////////////////////////////////////////////////////// LFileSelect::LFileSelect(LViewI *Window) { d = new LFileSelectPrivate(this); if (Window) Parent(Window); } LFileSelect::~LFileSelect() { DeleteObj(d); } void LFileSelect::ShowReadOnly(bool b) { d->ShowReadOnly = b;; } bool LFileSelect::ReadOnly() { return d->ReadOnly; } const char *LFileSelect::Name() { return d->Files[0]; } bool LFileSelect::Name(const char *n) { d->Files.DeleteArrays(); if (n) { d->Files.Insert(NewStr(n)); } return true; } const char *LFileSelect::operator [](size_t i) { return d->Files.ItemAt(i); } size_t LFileSelect::Length() { return d->Files.Length(); } size_t LFileSelect::Types() { return d->Types.Length(); } void LFileSelect::ClearTypes() { d->Types.DeleteObjects(); } LFileType *LFileSelect::TypeAt(ssize_t n) { return d->Types.ItemAt(n); } bool LFileSelect::Type(const char *Description, const char *Extension, int Data) { LFileType *Type = new LFileType; if (Type) { Type->Description(Description); Type->Extension(Extension); d->Types.Insert(Type); } return Type != 0; } ssize_t LFileSelect::SelectedType() { return d->CurrentType; } LViewI *LFileSelect::Parent() { return d->Parent; } void LFileSelect::Parent(LViewI *Window) { d->Parent = dynamic_cast(Window); } bool LFileSelect::MultiSelect() { return d->MultiSelect; } void LFileSelect::MultiSelect(bool Multi) { d->MultiSelect = Multi; } #define CharPropImpl(Func, Var) \ const char *LFileSelect::Func() \ { \ return Var; \ } \ void LFileSelect::Func(const char *i) \ { \ Var = i; \ } CharPropImpl(InitialDir, d->InitPath); CharPropImpl(Title, d->Title); CharPropImpl(DefaultExtension, d->DefExt); void LFileSelect::Open(SelectCb Cb) { auto Dlg = new LFileSelectDlg(d); d->Type = TypeOpenFile; // printf("LFileSelect domodal.. thread=%p\n", LCurrentThreadHnd()); Dlg->DoModal([select=this, cb=Cb](auto dlg, auto code) { // printf("LFileSelect cb.. thread=%u lock=%u\n", LCurrentThreadId(), dlg->WindowHandle()->LockingThread()); if (cb) cb(select, code == IDOK); // printf("LFileSelect deleting.. lock=%u\n", dlg->WindowHandle()->LockingThread()); delete dlg; // printf("LFileSelect deleted..\n"); }); } void LFileSelect::OpenFolder(SelectCb Cb) { auto Dlg = new LFileSelectDlg(d); d->Type = TypeOpenFolder; // printf("LFileSelect::OpenFolder domodal...\n"); Dlg->DoModal([this,Cb](auto d, auto code) { // printf("LFileSelect::OpenFolder cb, code=%i\n", code); if (Cb) Cb(this, code == IDOK); delete d; }); } void LFileSelect::Save(SelectCb Cb) { auto *Dlg = new LFileSelectDlg(d); d->Type = TypeSaveFile; // printf("LFileSelect domodal.. thread=%u\n", LCurrentThreadId()); Dlg->DoModal([this, Cb](auto dlg, auto code) { // printf("LFileSelect cb.. thread=%u, code=%i\n", LCurrentThreadId(), code); // dlg->WindowHandle()->LockingThread()); if (Cb) Cb(this, code == IDOK); // printf("LFileSelect deleting.. lock=%u\n", dlg->WindowHandle()->LockingThread()); delete dlg; // printf("LFileSelect deleted..\n"); }); } /////////////////////////////////////////////////////////////////////////////////// #if defined(LINUX) #include "lgi/common/Net.h" #endif bool LgiGetUsersLinks(LArray &Links) { LString Folder = LGetSystemPath(LSP_USER_LINKS); if (!Folder) return false; #if defined(WINDOWS) LDirectory d; for (int b = d.First(Folder); b; b = d.Next()) { char *s = d.GetName(); if (s && stristr(s, ".lnk")) { char lnk[MAX_PATH_LEN]; if (d.Path(lnk, sizeof(lnk)) && LResolveShortcut(lnk, lnk, sizeof(lnk))) { Links.New() = lnk; } } } #elif defined(LINUX) char p[MAX_PATH_LEN]; if (!LMakePath(p, sizeof(p), Folder, "bookmarks")) { LgiTrace("%s:%i - Failed to make path '%s'\n", _FL, Folder.Get()); return false; } if (!LFileExists(p)) return false; auto Txt = LReadFile(p); if (!Txt) { LgiTrace("%s:%i - failed to read '%s'\n", _FL, p); return false; } auto lines = Txt.SplitDelimit("\r\n"); for (auto line: lines) { LUri u(line); if (u.sProtocol.Equals("file")) Links.New() = u.sPath; } #else LAssert(!"Not impl yet."); return false; #endif return true; } diff --git a/src/common/Lgi/LgiMain.cpp b/src/common/Lgi/LgiMain.cpp --- a/src/common/Lgi/LgiMain.cpp +++ b/src/common/Lgi/LgiMain.cpp @@ -1,320 +1,242 @@ /** \file \author Matthew Allen */ #if defined(_MSC_VER) && defined(_DEBUG) #include "crtdbg.h" #endif #include "lgi/common/Lgi.h" #ifdef LGI_SDL #include #endif /** \brief The main entry point of a Lgi program To hide the differences between the different OS's standard entry points for programs the file LgiMain.cpp implements a simple platform specific entry point and then calls LgiMain() to pass execution onto your program. A simple LgiMain() looks like this: \code int LgiMain(OsAppArguments &Args) { LApp *App = new LApp("application/x-MyProgram", Args); if (App && App->IsOk()) { App->AppWnd = new MyWindow; App->Run(); delete App; } return 0; } \endcode */ extern int LgiMain ( /// The arguments passed in from the OS. OsAppArguments &AppArgs ); bool _BuildCheck() { #ifdef _DEBUG const char *AppBuild = "Debug"; #else const char *AppBuild = "Release"; #endif char *LgiBuild = (char*) (LIsReleaseBuild() ? "Release" : "Debug"); if (_stricmp(AppBuild, LgiBuild)) { LgiTrace("Build check failed, app=%s lgi=%s\n", AppBuild, LgiBuild); return LgiMsg( NULL, "This application and it's libraries are mismatched:\n" "\n" " Application:\t%s\n" " Lgi:\t\t%s\n" "\n" "If you continue you may encounter crashes and other undesirable problems.\n" "Do you want to continue anyway?", "Warning", MB_YESNO, AppBuild, LgiBuild) == IDYES; } return true; } #ifdef WIN32 int #if _CONSOLE main(int args, char *arg[]) #else WINAPI CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) #endif { /* #if defined(_MSC_VER) && defined(_DEBUG) _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); // _CrtSetBreakAlloc(34058); #endif */ #ifdef __GTK_H__ { #ifdef _MSC_VER // Really? REALLY? Get your shit together GTK. _putenv("GDK_WIN32_USE_EXPERIMENTAL_OLE2_DND=1"); #endif #if _CONSOLE gtk_init(&args, &arg); #else gtk_init(&__argc, &__argv); #endif } #endif int Status = 0; #if !_CONSOLE && WINNATIVE _lgi_app_instance = hInstance; #endif if (_BuildCheck()) { char16 *CL = (char16*)GetCommandLineW(); #if WINNATIVE OsAppArguments AppArgs; #if !_CONSOLE AppArgs.hInstance = hInstance; AppArgs.nCmdShow = nCmdShow; #endif if (*CL == '\"') { AppArgs.lpCmdLine = StrchrW(CL+1, '\"'); } else { AppArgs.lpCmdLine = StrchrW(CL+1, ' '); } if (AppArgs.lpCmdLine) AppArgs.lpCmdLine++; #else LString::Array Mem; LArray Args; char16 *Ws = L" \t\r\n"; for (char16 *c = CL; *c; ) { while (*c && StrchrW(Ws, *c)) c++; if (!*c) { printf("Breaking on NULL at %i\n", (int)(c-CL)); break; } if (*c == '\"' || *c == '\'') { char16 delim = *c++; char16 *end = StrchrW(c, delim); if (end) { LString s(c, end-c); Mem.New() = s; Args.Add(s.Get()); c = end + 1; } else { LString s(c); Mem.New() = s; Args.Add(s.Get()); break; } } else { char16 *end = c; while (*end && !StrchrW(Ws, *end)) end++; if (end > c) Args.Add(WideToUtf8(c, end-c)); c = end + (*end != 0); } } OsAppArguments AppArgs(Args.Length(), (const char**) &Args[0]); #endif Status = LgiMain(AppArgs); } return Status; } #else -#if defined(LGI_CARBON) -pascal OSErr AppEventHandler(const AppleEvent *ae, AppleEvent *reply, SRefCon handlerRefcon) -{ - OSErr err = eventNotHandledErr; - - LgiTrace("AppEventHandler called.\n"); - - if (ae->descriptorType == typeAppleEvent) - { - uint32 Code; - Size Used = 0; - DescType typeCode = 0; - OSErr e = AEGetAttributePtr(ae, - keyEventIDAttr, - typeWildCard, - &typeCode, - &Code, - sizeof(Code), - &Used); - if (e) - { - LgiTrace("%s:%i - AEGetAttributePtr failed (%i).\n", _FL, e); - } - else if (Code == kAEGetURL) - { - char urlbuf[512]; - e = AEGetParamPtr(ae, keyDirectObject, typeUTF8Text, NULL, urlbuf, sizeof(urlbuf), &Used); - if (e) - LgiTrace("%s:%i - AEGetParamPtr failed (%i).\n", _FL, e); - else if (Used < sizeof(urlbuf)) - { - urlbuf[Used] = 0; - if (LAppInst && LAppInst->AppWnd) - { - LAppInst->AppWnd->PostEvent(M_URL, (LMessage::Param) new LString(urlbuf)); - } - else - { - LgiTrace("%s:%i - No AppWnd.\n", _FL); - err = eventInternalErr; - } - } - } - else - { - LgiTrace("%s:%i - Support for '%4.4s' not implement.\n", _FL, &Code); - err = eventParameterNotFoundErr; - } - } - - return err; -} -#endif - int main(int Args, const char **Arg) { int Status = 0; #ifdef __GTK_H__ - #if 0 && defined(_DEBUG) - - // This turns on fatal GKT warnings all the time... - // Useful for debugging. - LArray a; + #if 0 && defined(_DEBUG) - for (int i=0; i a; + + for (int i=0; iGetFont() && Parent->GetFont()->Handle() ? Parent->GetFont() : LSysFont; Txt = new LDisplayString(f, cName); } return 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()) + 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; + LItemColumn *Column = NULL; 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 && + 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; LNotification n(LNotifyItemColumnClicked); n.Int[0] = Col; SendNotify(n); } bool LItemContainer::GetColumnClickInfo(int &Col, LMouse &m) { if (ColClick < 0) { Col = ColumnAtX(m.x); return true; } else 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(); } } } +void LItemContainer::SetSortingMark(int ColIdx, bool Up) +{ + for (int i=0; iUpArrow(ColIdx == i && Up); + c->DownArrow(ColIdx == i && !Up); + } +} + 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) { d->SetName(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; +#define _(name) \ +bool LItemColumn::name() \ +{ \ + return d->name; \ +} \ +\ +void LItemColumn::name(bool b) \ +{ \ + d->name = b; \ + if (d->Parent) \ + d->Parent->Invalidate(&d->Parent->ColumnHeader); \ } - -void LItemColumn::Type(int i) -{ - d->cType = i; - if (d->Parent) - { - d->Parent->Invalidate(&d->Parent->ColumnHeader); - } -} - -int LItemColumn::Type() -{ - return d->cType; -} +COLUMN_FLAGS() +#undef _ 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) return; 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) + if (HasSort()) { 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) + if (HasSort()) { Mx += d->Parent->GetImageList()->TileX() + 4; } } else if (ValidStr(d->cName)) { auto Ds = d->GetDs(); if (!Ds || !Ds->GetFont()) { LAssert(0); return; } auto f = Ds->GetFont(); 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 = Ds->Y(); int ry = r.Y(); int y = r.y1 + ((ry - ty) >> 1); // d->Txt->_debug = true; Ds->Draw(pDC, r.x1 + Off + 3, y + Off, &r); - if (d->cMark) + if (HasSort()) { Mx += Ds->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) + if (UpArrow()) + { + 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); + } + else if (DownArrow()) { - 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; - } + 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); } } 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->GetDs(); 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/List.cpp b/src/common/Widgets/List.cpp --- a/src/common/Widgets/List.cpp +++ b/src/common/Widgets/List.cpp @@ -1,2722 +1,2721 @@ /*hdr ** FILE: LList.cpp ** AUTHOR: Matthew Allen ** DATE: 14/2/2000 ** DESCRIPTION: Lgi self-drawn listbox ** ** Copyright (C) 2000 Matthew Allen ** fret@memecode.com */ #include #include #include #include "lgi/common/Lgi.h" #include "lgi/common/SkinEngine.h" #include "lgi/common/List.h" #include "lgi/common/ScrollBar.h" #include "lgi/common/DisplayString.h" #include "lgi/common/LgiRes.h" #include "lgi/common/CssTools.h" // Debug defines #define DEBUG_EDIT_LABEL 1 // Number of pixels you have to move the mouse until a drag is initiated. #define DRAG_THRESHOLD 4 // Switches for various profiling code.. #define LList_POUR_PROFILE 1 #define LList_ONPAINT_PROFILE 0 // Options #define DOUBLE_BUFFER_PAINT 0 #define ForAllItems(Var) for (auto Var : Items) #define ForAllItemsReverse(Var) Iterator ItemIter(&Items); for (LListItem *Var = ItemIter.Last(); Var; Var = ItemIter.Prev()) #define VisibleItems() CompletelyVisible // (LastVisible - FirstVisible + 1) #define MaxScroll() MAX((int)Items.Length() - CompletelyVisible, 0) class LListPrivate { public: // Mode LListMode Mode; int Columns; int VisibleColumns; // This is a pointer to a flag, that gets set when the object // is deleted. Used to trap events deleting the window. If an // event handler deletes the current window we can't touch any // of the member variables anymore, so we need to know to quit/return // ASAP. bool *DeleteFlag; // If this is true the ctrl is selecting lots of things // and we only want to notify once. bool NoSelectEvent; // Drag'n'drop LPoint DragStart; int DragData; // Kayboard search uint64 KeyLast; char16 *KeyBuf; // Class LListPrivate() { DragData = 0; KeyBuf = 0; DeleteFlag = 0; Columns = 0; VisibleColumns = 0; Mode = LListDetails; NoSelectEvent = false; } ~LListPrivate() { if (DeleteFlag) *DeleteFlag = true; DeleteArray(KeyBuf); } }; class LListItemPrivate { public: bool Selected = false; bool Visible = true; int ListItem_Image = -1; List Cols; LArray Str; LArray Display; int16 LayoutColumn = -1; LListItemPrivate() { } ~LListItemPrivate() { Cols.DeleteObjects(); EmptyStrings(); EmptyDisplay(); } void EmptyStrings() { Str.DeleteArrays(); } void EmptyDisplay() { Display.DeleteObjects(); } }; //////////////////////////////////////////////////////////////////////////////////////////// LListItemColumn::LListItemColumn(LListItem *item, int col) { _Column = col; _Item = item; _Value = 0; _Item->d->Cols.Insert(this); } LList *LListItemColumn::GetList() { return _Item ? _Item->Parent : 0; } LItemContainer *LListItemColumn::GetContainer() { return GetList(); } LListT *LListItemColumn::GetAllItems() { return GetList() ? &GetList()->Items : 0; } void LListItemColumn::Value(int64 i) { if (i != _Value) { _Value = i; _Item->OnColumnNotify(_Column, _Value); } } LListItemColumn *LListItemColumn::GetItemCol(LListItem *i, int Col) { if (i) { for (auto c: i->d->Cols) { if (c->_Column == Col) { return c; } } } return 0; } //////////////////////////////////////////////////////////////////////////////////////////// // List item LListItem::LListItem(const char *initStr) { d = new LListItemPrivate; Pos.ZOff(-1, -1); if (initStr) SetText(initStr); } LListItem::~LListItem() { if (Parent) Parent->Remove(this); DeleteObj(d); } void LListItem::SetImage(int i) { d->ListItem_Image = i; } int LListItem::GetImage(int Flags) { return d->ListItem_Image; } LItemContainer *LListItem::GetContainer() { return Parent; } List *LListItem::GetItemCols() { return &d->Cols; } /* Calling this to store your data is optional. Just override the "GetText" function to return your own data to avoid duplication in memory. */ bool LListItem::SetText(const char *s, int i) { if (i < 0) return false; // Delete any existing column DeleteArray((char*&)d->Str[i]); DeleteObj(d->Display[i]); // Add new string in d->Str[i] = NewStr(s); if (Parent) Parent->SendNotify(LNotifyItemChange); return true; } // User can override this if they want to use their own data const char *LListItem::GetText(int i) { return d->Str[i]; } bool LListItem::Select() { return d->Selected; } LRect *LListItem::GetPos(int Col) { static LRect r; r = Pos; if (Parent->GetMode() == LListDetails) { if (Col >= 0) { LItemColumn *Column = 0; int Cx = Parent->GetImageList() ? 16 : 0; for (int c=0; cColumnAt(c); if (Column) { Cx += Column->Width(); } } Column = Parent->ColumnAt(Col); if (Column) { r.x1 = Cx; r.x2 = Cx + Column->Width() - 1; } } } else { r.Offset(16, 0); } return &r; } void LListItem::Select(bool b) { if (d->Selected != b) { d->Selected = b; Update(); if (Parent && d->Selected && !Parent->d->NoSelectEvent) { LArray Items; Items.Add(this); Parent->OnItemSelect(Items); } } } void LListItem::ScrollTo() { if (Parent) { if (Parent->GetMode() == LListDetails && Parent->VScroll) { ssize_t n = Parent->Items.IndexOf(this); if (n < Parent->FirstVisible) { Parent->VScroll->Value(n); Parent->Invalidate(&Parent->ItemsPos); } else if (n >= Parent->LastVisible) { Parent->VScroll->Value(n - (Parent->LastVisible - Parent->FirstVisible) + 1); Parent->Invalidate(&Parent->ItemsPos); } } else if (Parent->GetMode() == LListColumns && Parent->HScroll) { ssize_t n = Parent->Items.IndexOf(this); if (n < Parent->FirstVisible) { Parent->HScroll->Value(d->LayoutColumn); Parent->Invalidate(&Parent->ItemsPos); } else if (n >= Parent->LastVisible) { ssize_t Range = Parent->HScroll->Page(); Parent->HScroll->Value(d->LayoutColumn - Range); Parent->Invalidate(&Parent->ItemsPos); } } } } void LListItem::Update() { if (Parent) { if (Parent->Lock(_FL)) { d->EmptyDisplay(); LPoint Info; OnMeasure(&Info); LRect r = Pos; if (r.Valid()) { if (Info.y != r.Y()) { Pos.y2 = Pos.y1 + Info.y - 1; Parent->PourAll(); r.y1 = MIN(r.y1, Pos.y1); r.y2 = Parent->ItemsPos.y2; } Parent->Invalidate(&r); } Parent->Unlock(); } } else { d->EmptyDisplay(); } } void LListItem::OnMeasure(LPoint *Info) { if (!Info) return; if (Parent && Parent->GetMode() == LListDetails) { Info->x = 4 << 10; } else { auto s = GetDs(0); Info->x = 22 + (s ? s->X() : 0); } LFont *f = Parent ? Parent->GetFont() : LSysFont; Info->y = MAX(16, f->GetHeight() + 2); // the default height } bool LListItem::GridLines() { return (Parent) ? Parent->GridLines : false; } void LListItem::OnMouseClick(LMouse &m) { int Col = Parent ? Parent->ColumnAtX(m.x) : -1; for (auto h: d->Cols) { if (Col == h->GetColumn()) { h->OnMouseClick(m); } } } LDisplayString *LListItem::GetDs(int Col, int FitTo) { if (!d->Display[Col]) { LFont *f = GetFont(); if (!f && Parent) f = Parent->GetFont(); if (!f) f = LSysFont; const char *Text = d->Str[Col] ? d->Str[Col] : GetText(Col); LAssert((NativeInt)Text != 0xcdcdcdcd && (NativeInt)Text != 0xfdfdfdfd); d->Display[Col] = new LDisplayString(f, Text?Text:(char*)""); if (d->Display[Col] && FitTo > 0) { d->Display[Col]->TruncateWithDots(FitTo); } } return d->Display[Col]; } void LListItem::ClearDs(int Col) { if (Col >= 0) { DeleteObj(d->Display[Col]); } else { d->Display.DeleteObjects(); } } void LListItem::OnPaintColumn(LItem::ItemPaintCtx &Ctx, int i, LItemColumn *c) { LSurface *&pDC = Ctx.pDC; if (!pDC) return; LRect ng = Ctx; // non-grid area if (c && c->InDrag()) { pDC->Colour(DragColumnColour); pDC->Rectangle(&ng); } else { LColour Background = Ctx.Back; if (Parent->GetMode() == LListDetails && - (c && c->Mark()) && + (c && c->HasSort()) && !d->Selected) { Background = GdcMixColour(LColour(0, 24), Background, (double)1/32); } if (GridLines()) { ng.x2--; ng.y2--; } - if (!c || c->Type() == GIC_ASK_TEXT) + if (!c || !c->HasImage()) { - LDisplayString *Ds = GetDs(i, Ctx.X()); + auto Ds = GetDs(i, Ctx.X()); if (Ds) { Ds->GetFont()->TabSize(0); Ds->GetFont()->Transparent(false); Ds->GetFont()->Colour(Ctx.Fore, Background); switch (Ctx.Align.Type) { case LCss::AlignCenter: Ds->Draw(pDC, ng.x1+((ng.X()-Ds->X())/2), ng.y1+1, &ng); break; case LCss::AlignRight: Ds->Draw(pDC, ng.x2-Ds->X()-1, ng.y1+1, &ng); break; default: // Left or inherit Ds->Draw(pDC, ng.x1+1, ng.y1+1, &ng); break; } } else { pDC->Colour(Background); pDC->Rectangle(&ng); } } else { pDC->Colour(Background); pDC->Rectangle(&ng); - if ((c && c->Type() == GIC_ASK_IMAGE) && - Parent->GetImageList()) + if (Parent->GetImageList()) { int Img = GetImage(); if (Img >= 0) { int CenterY = Ctx.y1 + ((Ctx.Y() - Parent->GetImageList()->TileY()) >> 1); LAssert(CenterY >= 0); Parent->GetImageList()->Draw(pDC, Ctx.x1+1, CenterY, Img, Background); } } } if (GridLines()) { pDC->Colour(L_LOW); pDC->Line(Ctx.x1, Ctx.y2, Ctx.x2, Ctx.y2); pDC->Line(Ctx.x2, Ctx.y1, Ctx.x2, Ctx.y2); } } } void LListItem::OnPaint(LItem::ItemPaintCtx &Ctx) { if (!Parent || !d->Visible) return; int x = Ctx.x1; auto CtxX = Ctx.X(); LAutoPtr Prev; if (GetCss()) { Prev.Reset(new ItemPaintCtx(Ctx)); LCss::ColorDef Fill = GetCss()->Color(); if (Fill.Type == LCss::ColorRgb) Ctx.Fore.Set(Fill.Rgb32, 32); if (!Select()) { Fill = GetCss()->BackgroundColor(); if (Fill.Type == LCss::ColorRgb) Ctx.Back.Set(Fill.Rgb32, 32); } } // Icon? if (Parent->IconCol) { LItem::ItemPaintCtx IcoCtx = Ctx; IcoCtx.Set(x, Ctx.y1, x + Parent->IconCol->Width()-1, Ctx.y2); // draw icon OnPaintColumn(IcoCtx, -1, Parent->IconCol); x = IcoCtx.x2 + 1; } // draw columns auto It = d->Cols.begin(); LListItemColumn *h = *It; LItem::ItemPaintCtx ColCtx = Ctx; if (Parent->Columns.Length()) { for (int i=0; iColumns.Length(); i++) { LItemColumn *c = Parent->Columns[i]; if (Parent->GetMode() == LListColumns) ColCtx.Set(x, Ctx.y1, Ctx.x2, Ctx.y2); else ColCtx.Set(x, Ctx.y1, x + c->Width()-1, Ctx.y2); ColCtx.Align = c->TextAlign(); OnPaintColumn(ColCtx, i, c); if (h && i == h->GetColumn()) { h->OnPaintColumn(ColCtx, i, c); h = *(++It); } x = ColCtx.x2 + 1; if (Parent->GetMode() == LListColumns) break; } } else { // One fake column for the whole control: ColCtx.Set(x, Ctx.y1, x+CtxX-1, Ctx.y2); ColCtx.Align = LCss::AlignLeft; OnPaintColumn(ColCtx, 0, NULL); x = ColCtx.x2 + 1; } // after columns if (x <= Ctx.x2) { Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(x, Ctx.y1, Ctx.x2, Ctx.y2); } if (Prev) Ctx = *Prev; } ////////////////////////////////////////////////////////////////////////////// // List control LList::LList(int id, int x, int y, int cx, int cy, const char *name) : ResObject(Res_ListView) { d = new LListPrivate; SetId(id); Name(name); ItemsPos.ZOff(-1, -1); Buf = 0; GridLines = false; FirstVisible = -1; LastVisible = -1; EditLabels = false; MultiSelect(true); CompletelyVisible = 0; Keyboard = -1; Sunken(true); Name("LList"); #if WINNATIVE SetStyle(GetStyle() | WS_TABSTOP); SetDlgCode(DLGC_WANTARROWS); Cursor = 0; #endif SetTabStop(true); LRect r(x, y, x+cx, y+cy); SetPos(r); LResources::StyleElement(this); } LList::~LList() { DeleteObj(Buf); Empty(); EmptyColumns(); DeleteObj(d); } LListMode LList::GetMode() { return d->Mode; } void LList::SetMode(LListMode m) { if (d->Mode ^ m) { d->Mode = m; if (IsAttached()) { PourAll(); Invalidate(); } } } void LList::OnItemClick(LListItem *Item, LMouse &m) { if (Item) Item->OnMouseClick(m); } void LList::OnItemBeginDrag(LListItem *Item, LMouse &m) { if (Item) Item->OnBeginDrag(m); } void LList::OnItemSelect(LArray &It) { if (It.Length()) { Keyboard = (int)Items.IndexOf(It[0]); LAssert(Keyboard >= 0); LHashTbl, bool> Sel; for (int n=0; nOnSelect(); if (!MultiSelect()) Sel.Add(It[n], true); } if (!MultiSelect()) { // deselect all other items ForAllItems(i) { if (!Sel.Find(i)) { if (i->d->Selected) { /* i->d->Selected = false; i->Update(); */ i->Select(false); } } } } } // Notify selection change SendNotify(LNotifyItemSelect); } bool LItemContainer::DeleteColumn(LItemColumn *Col) { bool Status = false; if (Col && Lock(_FL)) { if (Columns.HasItem(Col)) { Columns.Delete(Col); DeleteObj(Col); UpdateAllItems(); SendNotify(LNotifyItemColumnsChanged); Status = true; } Unlock(); } return Status; } LMessage::Result LList::OnEvent(LMessage *Msg) { switch (Msg->Msg()) { #ifdef WIN32 case WM_VSCROLL: { if (VScroll) return VScroll->OnEvent(Msg); break; } #endif } return LItemContainer::OnEvent(Msg); } int LList::OnNotify(LViewI *Ctrl, LNotification n) { if ( (Ctrl->GetId() == IDC_VSCROLL && VScroll) || (Ctrl->GetId() == IDC_HSCROLL && HScroll) ) { if (n.Type == LNotifyScrollBarCreate) UpdateScrollBars(); Invalidate(&ItemsPos); } return LLayout::OnNotify(Ctrl, n); } LRect &LList::GetClientRect() { static LRect r; r = GetPos(); r.Offset(-r.x1, -r.y1); return r; } LListItem *LList::HitItem(int x, int y, int *Index) { int n=0; ForAllItems(i) { if ( ( // Is list mode we consider the item to have infinite width. // This helps with multi-selection when the cursor falls outside // the window's bounds but is still receiving mouse move messages // because of mouse capture. d->Mode == LListDetails && y >= i->Pos.y1 && y <= i->Pos.y2 ) || ( i->Pos.Overlap(x, y) ) ) { if (Index) *Index = n; return i; } n++; } return NULL; } void LList::ClearDs(int Col) { ForAllItems(i) { i->ClearDs(Col); } } void LList::KeyScroll(int iTo, int iFrom, bool SelectItems) { int Start = -1, End = -1, i = 0; { ForAllItems(n) { if (n->Select()) { if (Start < 0) { Start = i; } } else if (Start >= 0 && End < 0) { End = i - 1; } i++; } if (End < 0) End = i - 1; } if (Items.Length() == 0) return; iTo = limit(iTo, 0, (int)Items.Length()-1); iFrom = limit(iFrom, 0, (int)Items.Length()-1); LListItem *To = Items.ItemAt(iTo); LListItem *From = Items.ItemAt(iFrom); // int Inc = (iTo < iFrom) ? -1 : 1; if (To && From && iTo != iFrom) { // LListItem *Item = 0; if (SelectItems) { int OtherEnd = Keyboard == End ? Start : End; int Min = MIN(OtherEnd, iTo); int Max = MAX(OtherEnd, iTo); i = 0; d->NoSelectEvent = true; LArray Sel; ForAllItems(n) { bool s = i>=Min && i<=Max; n->Select(s); if (s) Sel.Add(n); i++; } d->NoSelectEvent = false; OnItemSelect(Sel); } else { Select(To); } To->ScrollTo(); Keyboard = iTo; } } bool LList::OnMouseWheel(double Lines) { if (VScroll) { int64 Old = VScroll->Value(); VScroll->Value(Old + (int)Lines); if (Old != VScroll->Value()) { Invalidate(&ItemsPos); } } if (HScroll) { int64 Old = HScroll->Value(); HScroll->Value(Old + (int)(Lines / 3)); if (Old != HScroll->Value()) { Invalidate(&ItemsPos); } } return true; } bool LList::OnKey(LKey &k) { bool Status = false; LListItem *Item = GetSelected(); if (Item) { Status = Item->OnKey(k); } if (k.vkey != LK_UP && k.vkey != LK_DOWN && k.CtrlCmd()) { switch (k.c16) { case 'A': case 'a': { if (k.Down()) SelectAll(); Status = true; break; } } } else { switch (k.vkey) { case LK_RETURN: { #if WINNATIVE if (!k.IsChar) #endif { if (k.Down()) SendNotify(LNotification(k)); } break; } case LK_BACKSPACE: case LK_DELETE: case LK_ESCAPE: { if (k.Down()) SendNotify(LNotification(k)); break; } case LK_UP: { // int i = Value(); #ifdef MAC if (k.Ctrl()) goto LList_PageUp; else if (k.System()) goto LList_Home; #endif if (k.Down()) KeyScroll(Keyboard-1, Keyboard, k.Shift()); Status = true; break; } case LK_DOWN: { #ifdef MAC if (k.Ctrl()) goto LList_PageDown; else if (k.System()) goto LList_End; #endif if (k.Down()) KeyScroll(Keyboard+1, Keyboard, k.Shift()); Status = true; break; } case LK_LEFT: { if (GetMode() == LListColumns) { if (k.Down()) { LListItem *Hit = GetSelected(); if (Hit) { LListItem *To = 0; int ToDist = 0x7fffffff; for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { LListItem *i = *It; if (!i->Pos.Valid()) break; if (i->Pos.x2 < Hit->Pos.x1) { int Dx = i->Pos.x1 - Hit->Pos.x1; int Dy = i->Pos.y1 - Hit->Pos.y1; int IDist = Dx * Dx + Dy * Dy; if (!To || IDist < ToDist) { To = i; ToDist = IDist; } } } if (!To && HScroll) { if (Hit->d->LayoutColumn == HScroll->Value() + 1) { // Seek back to the start of the column before the // first visible column for (auto it = Items.begin(FirstVisible); it.In(); it--) { LListItem *i = *it; if (i->d->LayoutColumn < HScroll->Value()) { it++; break; } } // Now find the entry at the right height } } if (To) { Select(0); To->Select(true); To->ScrollTo(); } } } Status = true; } break; } case LK_RIGHT: { if (GetMode() == LListColumns) { if (k.Down()) { LListItem *Hit = GetSelected(); if (Hit) { LListItem *To = 0; int ToDist = 0x7fffffff; for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { LListItem *i = *It; if (!i->Pos.Valid()) break; if (i->Pos.x1 > Hit->Pos.x2) { int Dx = i->Pos.x1 - Hit->Pos.x1; int Dy = i->Pos.y1 - Hit->Pos.y1; int IDist = Dx * Dx + Dy * Dy; if (!To || IDist < ToDist) { To = i; ToDist = IDist; } } } if (To) { Select(0); To->Select(true); To->ScrollTo(); } } } Status = true; } break; } case LK_PAGEUP: { #ifdef MAC LList_PageUp: #endif if (k.Down()) { int Vis = VisibleItems(); Vis = MAX(Vis, 0); KeyScroll(Keyboard-Vis, Keyboard, k.Shift()); } Status = true; break; } case LK_PAGEDOWN: { #ifdef MAC LList_PageDown: #endif if (k.Down()) { int Vis = VisibleItems(); Vis = MAX(Vis, 0); KeyScroll(Keyboard+Vis, Keyboard, k.Shift()); } Status = true; break; } case LK_END: { #ifdef MAC LList_End: #endif if (k.Down()) KeyScroll((int)Items.Length()-1, Keyboard, k.Shift()); Status = true; break; } case LK_HOME: { #ifdef MAC LList_Home: #endif if (k.Down()) KeyScroll(0, Keyboard, k.Shift()); Status = true; break; } #ifdef VK_APPS case VK_APPS: { if (k.Down()) { LListItem *s = GetSelected(); if (s) { LRect *r = &s->Pos; if (r) { LMouse m; LListItem *FirstVisible = ItemAt((VScroll) ? (int)VScroll->Value() : 0); m.x = 32 + ItemsPos.x1; m.y = r->y1 + (r->Y() >> 1) - (FirstVisible ? FirstVisible->Pos.y1 : 0) + ItemsPos.y1; m.Target = this; m.ViewCoords = true; m.Down(true); m.Right(true); OnMouseClick(m); } Status = true; } } break; } #endif default: { if ( !Status && k.IsChar && ( IsDigit(k.c16) || IsAlpha(k.c16) || strchr("_.-", k.c16) ) ) { if (k.Down()) { uint64 Now = LCurrentTime(); LStringPipe p; if (d->KeyBuf && Now < d->KeyLast + 1500) { p.Push(d->KeyBuf); } DeleteArray(d->KeyBuf); d->KeyLast = Now; p.Push(&k.c16, 1); d->KeyBuf = p.NewStrW(); if (d->KeyBuf) { char *c8 = WideToUtf8(d->KeyBuf); if (c8) { int Col = 0; bool Ascend = true; for (int i=0; iMark()) + auto c = Columns[i]; + if (c->HasSort()) { Col = i; - if (c->Mark() == GLI_MARK_UP_ARROW) + if (c->UpArrow()) { Ascend = false; } } } bool Selected = false; auto It = Ascend ? Items.begin() : Items.rbegin(); for (; It.In(); Ascend ? ++It : --It) { LListItem *i = *It; if (!Selected) { const char *t = i->GetText(Col); if (t && stricmp(t, c8) >= 0) { i->Select(true); i->ScrollTo(); Selected = true; } else { i->Select(false); } } else { i->Select(false); } } DeleteArray(c8); } } } Status = true; } break; } } } return Status; } LCursor LList::GetCursor(int x, int y) { LItemColumn *Resize, *Over; HitColumn(x, y, Resize, Over); if (Resize) return LCUR_SizeHor; return LCUR_Normal; } void LList::OnMouseClick(LMouse &m) { // m.Trace("LList::OnMouseClick"); if (Lock(_FL)) { if (m.Down()) { Focus(true); DragMode = DRAG_NONE; d->DragStart.x = m.x; d->DragStart.y = m.y; - if (ColumnHeaders && + if (ColumnHeaders() && ColumnHeader.Overlap(m.x, m.y)) { // Clicked on a column heading LItemColumn *Resize, *Over; int Index = HitColumn(m.x, m.y, Resize, Over); if (Resize) { if (m.Double()) { if (m.CtrlCmd()) { ResizeColumnsToContent(); } else { ColSizes Sizes; GetColumnSizes(Sizes); int AvailablePx = GetClient().X() - 5; if (VScroll) AvailablePx -= VScroll->X(); int ExpandPx = AvailablePx - (Sizes.FixedPx + Sizes.ResizePx); if (ExpandPx > 0) { int MaxPx = Resize->GetContentSize() + DEFAULT_COLUMN_SPACING; int AddPx = MIN(ExpandPx, MaxPx - Resize->Width()); if (AddPx > 0) { Resize->Width(Resize->Width() + AddPx); ClearDs(Index); Invalidate(); } } } } else { DragMode = RESIZE_COLUMN; d->DragData = (int)Columns.IndexOf(Resize); Capture(true); } } else { DragMode = CLICK_COLUMN; d->DragData = (int)Columns.IndexOf(Over); if (Over) { Over->Value(true); LRect r = Over->GetPos(); Invalidate(&r); Capture(true); } } } else if (ItemsPos.Overlap(m.x, m.y)) { // Clicked in the items area bool HandlerHung = false; int ItemIndex = -1; LListItem *Item = HitItem(m.x, m.y, &ItemIndex); // LViewI *Notify = Item ? (GetNotify()) ? GetNotify() : GetParent() : 0; d->DragData = ItemIndex; if (Item && Item->Select()) { // Click on selected item if (m.CtrlCmd()) { Item->Select(false); OnItemClick(Item, m); } else { // Could be drag'n'drop operation // Or just a select int64 StartHandler = LCurrentTime(); // this will get set if 'this' is deleted. bool DeleteFlag = false; // Setup the delete flag pointer d->DeleteFlag = &DeleteFlag; // Do the event... may delete 'this' object, or hang for a long time OnItemClick(Item, m); // If the object has been deleted... exit out of here NOW! if (DeleteFlag) { return; } // Shut down the delete flag pointer... it'll point to invalid stack soon. d->DeleteFlag = 0; // Check if the handler hung for a long time... uint64 Now = LCurrentTime(); HandlerHung = Now - StartHandler > 200; if (!HandlerHung && !m.Double() && !m.IsContextMenu()) { // Start d'n'd watcher pulse... SetPulse(100); Capture(true); DragMode = CLICK_ITEM; } if (!IsCapturing()) { // If capture failed then we reset the dragmode... DragMode = DRAG_NONE; } } } else { // Selection change if (m.Shift() && MultiSelect()) { int n = 0; int a = MIN(ItemIndex, Keyboard); int b = MAX(ItemIndex, Keyboard); LArray Sel; ForAllItems(i) { bool s = n >= a && n <= b; if (i->d->Selected ^ s) { i->d->Selected = s; i->Update(); } if (s) Sel.Add(i); n++; } OnItemSelect(Sel); if (Item) Item->Select(true); } else { bool PostSelect = false; bool SelectionChanged = false; // Temporaily turn off selection events... // and just send one at the end. // d->NoSelectEvent = true; ForAllItems(i) { if (Item == i) // clicked item { if (m.CtrlCmd()) { // Toggle selected state if (!i->Select()) { Keyboard = (int)Items.IndexOf(i); } i->Select(!i->Select()); SelectionChanged = true; } else { // Select this after we have delselected everything else PostSelect = true; } } else if (!m.CtrlCmd() || !MultiSelect()) { if (i->Select()) { i->Select(false); SelectionChanged = true; } } } if (PostSelect) { SelectionChanged |= Item->Select() == false; Item->Select(true); Keyboard = (int)Items.IndexOf(Item); } if (!m.CtrlCmd() && Items.Length() && !m.IsContextMenu()) { DragMode = SELECT_ITEMS; SetPulse(100); Capture(true); } if (SelectionChanged) { SendNotify(LNotifyItemSelect); } // d->NoSelectEvent = false; } OnItemClick(Item, m); } if (!HandlerHung) { if (m.IsContextMenu()) SendNotify(LNotification(m, LNotifyItemContextMenu)); else if (Item || m.Double()) SendNotify(LNotification(m)); else SendNotify(LNotification(m, LNotifyContainerClick)); } } } else // Up Click { switch (DragMode) { case CLICK_COLUMN: { if (d->DragData < 0) break; LItemColumn *c = Columns[d->DragData]; if (c) { c->Value(false); LRect cpos = c->GetPos(); Invalidate(&cpos); if (cpos.Overlap(m.x, m.y)) { OnColumnClick((int)Columns.IndexOf(c), m); } } else { OnColumnClick(-1, m); } break; } case CLICK_ITEM: { // This code allows the user to change a larger selection // down to a single item, by clicking on that item. This // can't be done on the down click because the user may also // be clicking the selected items to drag them somewhere and // if we de-selected all but the clicked item on the down // click they would never be able to drag and drop more than // one item. // // However we also do not want this to select items after the // contents of the list box have changed since the down click if (d->DragData >= Items.Length()) break; auto Item = Items.ItemAt(d->DragData); if (Item) { bool Change = false; LArray s; ForAllItems(i) { bool Sel = Item == i; if (Sel ^ i->Select()) { Change = true; i->Select(Sel); if (Sel) { s.Add(i); } } } if (Change) OnItemSelect(s); } break; } case DRAG_COLUMN: { // End column drag if (DragCol) { LRect DragPos = DragCol->GetPos(); LPoint p(DragPos.x1 + (DragPos.X()/2), 0); PointToView(p); int OldIndex = DragCol->GetIndex(); int Best = 100000000, NewIndex = OldIndex, i=0, delta; for (i=0; iGetPos().x1); if (delta < Best) { Best = delta; NewIndex = i - (i > OldIndex ? 1 : 0); } } delta = abs(p.x - Columns.Last()->GetPos().x2); if (delta < Best) { NewIndex = i; } LItemColumn *Col = DragCol->GetColumn(); if (OldIndex != NewIndex && OnColumnReindex(Col, OldIndex, NewIndex)) { Columns.SetFixedLength(false); Columns.Delete(Col, true); Columns.AddAt(OldIndex < NewIndex ? NewIndex-1 : NewIndex, Col); Columns.SetFixedLength(true); UpdateAllItems(); } DragCol->Quit(); DragCol = NULL; } Invalidate(); break; } } LListItem *Item = HitItem(m.x, m.y); if (Item) { OnItemClick(Item, m); } if (IsCapturing()) { Capture(false); } DragMode = DRAG_NONE; } Unlock(); } } void LList::OnPulse() { if (!Lock(_FL)) return; if (IsCapturing()) { LMouse m; bool HasMs = GetMouse(m); // m.Trace("LList::OnPulse"); if (HasMs && (m.y < 0 || m.y >= Y())) { switch (DragMode) { case SELECT_ITEMS: { int OverIndex = 0; LListItem *Over = 0; if (m.y < 0) { int Space = -m.y; int n = FirstVisible - 1; for (auto It = Items.begin(n); It != Items.end(); --It, n--) { LListItem *i = *It; LPoint Info; i->OnMeasure(&Info); if (Space > Info.y) { Space -= Info.y; } else { OverIndex = n; Over = i; break; } } if (!Over) { Over = Items[0]; OverIndex = 0; } } else if (m.y >= Y()) { int Space = m.y - Y(); int n = LastVisible + 1; for (auto It = Items.begin(n); It != Items.end(); ++It, n++) { LListItem *i = *It; LPoint Info; i->OnMeasure(&Info); if (Space > Info.y) { Space -= Info.y; } else { OverIndex = n; Over = i; break; } } if (!Over) { Over = *Items.rbegin(); OverIndex = (int)Items.Length()-1; } } int Min = MIN(d->DragData, OverIndex); int Max = MAX(d->DragData, OverIndex); int n = Min; for (auto It = Items.begin(Min); It != Items.end() && n <= Max; ++It, n++) { LListItem *i = *It; if (!i->Select()) i->Select(true); } if (Over) { Over->ScrollTo(); } break; } } } } else { DragMode = DRAG_NONE; SetPulse(); } Unlock(); } void LList::OnMouseMove(LMouse &m) { if (!Lock(_FL)) return; // m.Trace("LList::OnMouseMove"); 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; } case CLICK_COLUMN: { if (d->DragData < 0 || d->DragData >= Columns.Length()) break; LItemColumn *c = Columns[d->DragData]; if (c) { if (abs(m.x - d->DragStart.x) > DRAG_THRESHOLD || abs(m.y - d->DragStart.y) > DRAG_THRESHOLD) { OnColumnDrag(d->DragData, m); } else { bool Over = c->GetPos().Overlap(m.x, m.y); if (m.Down() && Over != c->Value()) { c->Value(Over); LRect r = c->GetPos(); Invalidate(&r); } } } break; } case SELECT_ITEMS: { int n=0; // bool Selected = m.y < ItemsPos.y1; if (IsCapturing()) { if (MultiSelect()) { int Over = -1; HitItem(m.x, m.y, &Over); if (m.y < ItemsPos.y1 && FirstVisible == 0) { Over = 0; } else { int n = FirstVisible; for (auto it = Items.begin(n); it != Items.end(); it++) { auto k = *it; if (!k->OnScreen()) break; if ((m.y >= k->Pos.y1) && (m.y <= k->Pos.y2)) { Over = n; break; } n++; } } if (Over >= 0) { n = 0; int Start = MIN(Over, d->DragData); int End = MAX(Over, d->DragData); ForAllItems(i) { i->Select(n >= Start && n <= End); n++; } } } else { ForAllItems(i) { i->Select(i->Pos.Overlap(m.x, m.y)); } } } break; } case CLICK_ITEM: { if (d->DragData >= Items.Length()) break; auto Cur = Items.ItemAt(d->DragData); if (Cur) { Cur->OnMouseMove(m); if (IsCapturing() && (abs(d->DragStart.x-m.x) > DRAG_THRESHOLD || abs(d->DragStart.y-m.y) > DRAG_THRESHOLD)) { Capture(false); OnItemBeginDrag(Cur, m); DragMode = DRAG_NONE; } } break; } default: { List s; if (GetSelection(s)) { for (auto c: s) { LMouse ms = m; ms.x -= c->Pos.x1; ms.y -= c->Pos.y1; c->OnMouseMove(ms); } } break; } } Unlock(); } int64 LList::Value() { int n=0; ForAllItems(i) { if (i->Select()) { return n; } n++; } return -1; } void LList::Value(int64 Index) { int n=0; ForAllItems(i) { if (n == Index) { i->Select(true); Keyboard = n; } else { i->Select(false); } n++; } } void LList::SelectAll() { if (Lock(_FL)) { ForAllItems(i) { i->d->Selected = true; } Unlock(); Invalidate(); } } bool LList::Select(LListItem *Obj) { bool Status = false; ForAllItems(i) { i->Select(Obj == i); if (Obj == i) Status = true; } return true; } LListItem *LList::GetSelected() { LListItem *n = 0; if (Lock(_FL)) { ForAllItems(i) { if (i->Select()) { n = i; break; } } Unlock(); } return n; } bool LList::GetUpdateRegion(LListItem *i, LRegion &r) { r.Empty(); if (d->Mode == LListDetails) { if (i->Pos.Valid()) { LRect u = i->Pos; u.y2 = ItemsPos.y2; r.Union(&u); return true; } } else if (d->Mode == LListColumns) { if (i->Pos.Valid()) { LRect u = i->Pos; u.y2 = ItemsPos.y2; r.Union(&u); u.x1 = u.x2 + 1; u.y1 = ItemsPos.y1; r.Union(&u); return true; } } return false; } bool LList::Insert(LListItem *i, int Index, bool Update) { List l; l.Insert(i); return Insert(l, Index, Update); } bool LList::Insert(List &l, int Index, bool Update) { bool Status = false; if (Lock(_FL)) { bool First = Items.Length() == 0; // Insert list of items for (auto i: l) { if (i->Parent != this) { i->Parent = this; i->Select(false); Items.Insert(i, Index); i->OnInsert(); if (Index >= 0) Index++; if (First) { First = false; Keyboard = 0; i->Select(true); } } } Status = true; Unlock(); if (Update) { // Update screen PourAll(); Invalidate(); // Notify SendNotify(LNotifyItemInsert); } } return Status; } bool LList::Delete(ssize_t Index) { return Delete(Items.ItemAt(Index)); } bool LList::Delete(LListItem *i) { bool Status = false; if (Lock(_FL)) { if (Remove(i)) { // Delete DeleteObj(i); Status = true; } Unlock(); } return Status; } bool LList::Remove(LListItem *i) { bool Status = false; if (Lock(_FL)) { if (i && i->GetList() == this) { LRegion Up; bool Visible = GetUpdateRegion(i, Up); bool Selected = i->Select(); int Index = (int)Items.IndexOf(i); int64 Pos = (VScroll) ? VScroll->Value() : 0; // Remove from list Items.Delete(i); i->OnRemove(); i->Parent = 0; UpdateScrollBars(); // Update screen if ((VScroll && VScroll->Value() != Pos) || Index < FirstVisible) { Invalidate(&ItemsPos); } else if (Visible) { Up.y2 = ItemsPos.y2; Invalidate(&Up); } // Notify LViewI *Note = GetNotify() ? GetNotify() : GetParent(); if (Note) { if (Selected) { LArray s; OnItemSelect(s); } LNotification n(LNotifyItemDelete); Note->OnNotify(this, n); } Status = true; } Unlock(); } return Status; } bool LList::HasItem(LListItem *Obj) { return Items.HasItem(Obj); } int LList::IndexOf(LListItem *Obj) { return (int)Items.IndexOf(Obj); } LListItem *LList::ItemAt(size_t Index) { return Index < Items.Length() ? Items.ItemAt(Index) : NULL; } void LList::ScrollToSelection() { if (VScroll) { int n=0; int Vis = VisibleItems(); ForAllItems(i) { if (i->Select()) { if (n < FirstVisible || n > LastVisible) { int k = n - (Vis/2); VScroll->Value(MAX(k, 0)); Invalidate(&ItemsPos); break; } } n++; } } } void LList::Empty() { if (Lock(_FL)) { ForAllItems(i) { LAssert(i->Parent == this); i->Parent = 0; DeleteObj(i); } Items.Empty(); FirstVisible = LastVisible = -1; DragMode = DRAG_NONE; if (VScroll) { VScroll->Value(0); VScroll->SetRange(0); } Invalidate(); DeleteArray(d->KeyBuf); Unlock(); } } void LList::RemoveAll() { if (Lock(_FL)) { if (Items.Length()) { LArray s; OnItemSelect(s); } for (auto i: Items) { i->OnRemove(); i->Parent = 0; } Items.Empty(); FirstVisible = LastVisible = -1; DragMode = DRAG_NONE; if (VScroll) { // these have to be in this order because // "SetLimits" can cause the VScroll object to // be deleted and becoming NULL VScroll->Value(0); VScroll->SetRange(0); } Invalidate(); DeleteArray(d->KeyBuf); Unlock(); } } void LList::OnPosChange() { LLayout::OnPosChange(); } void LList::UpdateScrollBars() { static bool Processing = false; if (!Processing && InThread()) { Processing = true; if (VScroll) { int Vis = VisibleItems(); int Max = MaxScroll(); if (VScroll->Value() > MAX(Max, 0)) { VScroll->Value(Max); } VScroll->SetPage(Vis); VScroll->SetRange(Items.Length()); } if (HScroll) { HScroll->SetPage(d->VisibleColumns); HScroll->SetRange(d->Columns); } Processing = false; } } void LList::PourAll() { #if LList_POUR_PROFILE LProfile Prof("PourAll()", 100); #endif // Layout all the elements LRect Client = GetClient(); LFont *Font = GetFont(); if (d->Mode == LListDetails) { - if (ColumnHeaders) + if (ColumnHeaders()) { ColumnHeader = Client; ColumnHeader.y2 = ColumnHeader.y1 + Font->GetHeight() + 4; ItemsPos = Client; ItemsPos.y1 = ColumnHeader.y2 + 1; } else { ItemsPos = Client; ColumnHeader.ZOff(-1, -1); } int n = 0; int y = ItemsPos.y1; int Max = MaxScroll(); FirstVisible = (VScroll) ? (int)VScroll->Value() : 0; if (FirstVisible > Max) FirstVisible = Max; LastVisible = 0x7FFFFFFF; CompletelyVisible = 0; bool SomeHidden = false; // Process visible flag ForAllItems(i) { auto css = i->GetCss(); i->d->Visible = !css || css->Display() != LCss::DispNone; } #if LList_POUR_PROFILE Prof.Add("List items"); #endif ForAllItems(i) { if (!i->d->Visible) { i->Pos.Set(-1, -1, -2, -2); SomeHidden = true; continue; // Don't increment 'n' } if (n < FirstVisible || n > LastVisible) { i->Pos.Set(-1, -1, -2, -2); SomeHidden = true; } else { LPoint Info; i->OnMeasure(&Info); if (i->Pos.Valid() && Info.y != i->Pos.Y()) { // This detects changes in item height and invalidates the items below this one. LRect in(0, y+Info.y, X()-1, Y()-1); Invalidate(&in); } i->Pos.Set(ItemsPos.x1, y, ItemsPos.x2, y+Info.y-1); y = y+Info.y; if (i->Pos.y2 > ItemsPos.y2) { LastVisible = n; SomeHidden = true; } else { CompletelyVisible++; } } n++; } if (LastVisible >= Items.Length()) { LastVisible = (int)Items.Length() - 1; } SetScrollBars(false, SomeHidden); UpdateScrollBars(); } else if (d->Mode == LListColumns) { ColumnHeader.ZOff(-1, -1); ItemsPos = Client; FirstVisible = 0; int CurX = 0; int CurY = 0; int MaxX = 16; LArray Col; d->Columns = 1; d->VisibleColumns = 0; int64 ScrollX = HScroll ? HScroll->Value() : 0; int64 OffsetY = HScroll ? 0 : LScrollBar::GetScrollSize(); FirstVisible = -1; int n = 0; #if LList_POUR_PROFILE Prof.Add("List cols"); #endif ForAllItems(i) { LPoint Info; i->OnMeasure(&Info); if (d->Columns <= ScrollX || CurX > ItemsPos.X()) { i->Pos.ZOff(-1, -1); i->d->LayoutColumn = d->Columns; if (ItemsPos.y1 + CurY + Info.y > ItemsPos.y2 - OffsetY) { CurY = 0; d->Columns++; if (d->Columns > ScrollX && CurX < ItemsPos.X()) { goto FlowItem; } } } else { FlowItem: if (ItemsPos.y1 + CurY + Info.y > ItemsPos.y2 - OffsetY) { // wrap to next column for (int n=0; nPos.x2 = CurX + MaxX - 1; } Col.Length(0); CurX += MaxX; CurY = 0; d->Columns++; if (CurX < ItemsPos.X()) { d->VisibleColumns++; } } if (FirstVisible < 0) FirstVisible = n; LastVisible = n; i->d->LayoutColumn = d->Columns; i->Pos.ZOff(Info.x-1, Info.y-1); i->Pos.Offset(ItemsPos.x1 + CurX, ItemsPos.y1 + CurY); Col[Col.Length()] = i; MaxX = MAX(MaxX, Info.x); CompletelyVisible++; } CurY += Info.y; n++; } d->VisibleColumns = MAX(1, d->VisibleColumns); // pour remaining items... for (n=0; nPos.x2 = CurX + MaxX - 1; } Col.Length(0); if (CurX + MaxX < ItemsPos.X()) { d->VisibleColumns++; } // printf("%u - ScrollX=%i VisCol=%i Cols=%i\n", (uint32)LCurrentTime(), ScrollX, d->VisibleColumns, d->Columns); SetScrollBars(d->VisibleColumns < d->Columns, false); UpdateScrollBars(); } } static LColour Tint(LColour back, double amt) { bool Darken = back.GetGray() >= 128; LColour Mixer = Darken ? LColour::Black : LColour::White; return back.Mix(Mixer, (float)(1.0f - amt)); } void LList::OnPaint(LSurface *pDC) { #if LList_ONPAINT_PROFILE int Start = LCurrentTime(), t1, t2, t3, t4, t5; #endif if (!Lock(_FL)) return; LCssTools Tools(this); LColour DisabledTint(L_MED); LColour Workspace(L_WORKSPACE); LColour NonFocusBack(L_NON_FOCUS_SEL_BACK); LColour Fore = Enabled() ? Tools.GetFore() : Tools.GetFore().Mix(DisabledTint); LColour Back = Tools.GetBack(&Workspace, 0); double NonFocusBackAmt = (double)NonFocusBack.GetGray() / Workspace.GetGray(); if (!Enabled()) Back = Back.Mix(DisabledTint); LColour SelFore(Focus() ? L_FOCUS_SEL_FORE : L_NON_FOCUS_SEL_FORE); LColour SelBack(Focus() ? L_FOCUS_SEL_BACK : (Enabled() ? Tint(Back, NonFocusBackAmt) : DisabledTint)); PourAll(); // printf("ListPaint SelFore=%s SelBack=%s Back=%s %f NonFocusBack=%s\n", SelFore.GetStr(), SelBack.GetStr(), Back.GetStr(), NonFocusBackAmt, NonFocusBack.GetStr()); #if LList_ONPAINT_PROFILE t1 = LCurrentTime(); #endif // Check icon column status then draw if (AskImage() && !IconCol) { IconCol.Reset(new LItemColumn(this, 0, 18)); if (IconCol) { IconCol->Resizable(false); - IconCol->Type(GIC_ASK_IMAGE); + IconCol->HasImage(true); } } else if (!AskImage()) IconCol.Reset(); PaintColumnHeadings(pDC); #if LList_ONPAINT_PROFILE t2 = LCurrentTime(); #endif // Draw items if (!Buf) Buf = new LMemDC; LRect r = ItemsPos; int n = FirstVisible; int LastY = r.y1; LCss::ColorDef Fill; int LastSelected = -1; LItem::ItemPaintCtx Ctx; Ctx.pDC = pDC; LRegion Rgn(ItemsPos); if (Items.Length()) { for (auto It = Items.begin(n); It != Items.end(); ++It, n++) { LListItem *i = *It; if (i->Pos.Valid()) { // Setup painting colours in the context if (LastSelected ^ (int)i->Select()) { if ((LastSelected = i->Select())) { Ctx.Fore = SelFore; Ctx.Back = SelBack; } else { Ctx.Fore = Fore; Ctx.Back = Back; } } // tell the item what colour to use #if DOUBLE_BUFFER_PAINT if (Buf->X() < i->Pos.X() || Buf->Y() < i->Pos.Y()) { Buf->Create(i->Pos.X(), i->Pos.Y(), GdcD->GetBits()); } Ctx = i->Pos; Ctx.r.Offset(-Ctx.r.x1, -Ctx.r.y1); i->OnPaint(Ctx); pDC->Blt(i->Pos.x1, i->Pos.y1, Buf, &Ctx.r); #else (LRect&)Ctx = i->Pos; i->OnPaint(Ctx); #endif Rgn.Subtract(&i->Pos); LastY = i->Pos.y2 + 1; } } } pDC->Colour(Back); for (LRect *w=Rgn.First(); w; w=Rgn.Next()) { pDC->Rectangle(w); } Unlock(); #if LList_ONPAINT_PROFILE int64 End = LCurrentTime(); printf("LList::OnPaint() pour=%i headers=%i items=%i\n", (int) (t1-Start), (int) (t2-t1), (int) (End-t2)); #endif } void LList::OnFocus(bool b) { LListItem *s = GetSelected(); if (Items.Length()) { if (!s) { s = Items[0]; if (s) s->Select(true); } for (auto It = Items.begin(FirstVisible); It != Items.end(); ++It) { auto i = *It; if (i->Pos.Valid() && i->d->Selected) { Invalidate(&i->Pos); } } } LLayout::OnFocus(b); if (!b && IsCapturing()) { Capture(false); } } void LList::UpdateAllItems() { if (Lock(_FL)) { bool needsRepour = false; ForAllItems(i) { auto css = i->GetCss(); bool vis = !css || css->Display() != LCss::DispNone; if (i->d->Visible != vis) needsRepour = true; i->d->EmptyDisplay(); } Unlock(); if (needsRepour) PourAll(); Invalidate(); } } int LList::GetContentSize(int Index) { int Max = 0; for (auto It = Items.begin(); It.In(); It++) { LListItem *i = *It; LDisplayString *s = i->d->Display[Index]; LDisplayString *Mem = 0; // If no cached string, create it for the list item if (!s || s->IsTruncated()) { LFont *f = i->GetFont(); if (!f) f = GetFont(); if (!f) f = LSysFont; const char *Text = i->d->Str[Index] ? i->d->Str[Index] : i->GetText(Index); if (s && s->IsTruncated()) { s = Mem = new LDisplayString(f, Text?Text:(char*)""); } else { s = i->d->Display[Index] = new LDisplayString(f, Text?Text:(char*)""); } } // Measure it if (s) { Max = MAX(Max, s->X()); } DeleteObj(Mem); } // Measure the heading too LItemColumn *Col = Columns[Index]; LFont *f = GetFont(); LAssert(f != 0); if (f) { LDisplayString h(f, Col->Name()); - int Hx = h.X() + (Col->Mark() ? 10 : 0); + int Hx = h.X() + (Col->HasSort() ? 10 : 0); Max = MAX(Max, Hx); } return Max; } diff --git a/src/common/Widgets/TimePopup.cpp b/src/common/Widgets/TimePopup.cpp --- a/src/common/Widgets/TimePopup.cpp +++ b/src/common/Widgets/TimePopup.cpp @@ -1,360 +1,360 @@ /// \file /// \author Matthew Allen #include #include "lgi/common/Lgi.h" #include "lgi/common/Popup.h" #include "lgi/common/MonthView.h" #include "lgi/common/List.h" #include "lgi/common/Edit.h" #include "lgi/common/DateTimeCtrls.h" /// This class is the list of times in a popup, it is used by LTimeDropDown LTimeDropDown::LTimeDropDown() : LDropDown(-1, 0, 0, 10, 10, 0), ResObject(Res_Custom) { SetPopup(Drop = new LTimePopup(this)); } int LTimeDropDown::OnNotify(LViewI *Ctrl, LNotification n) { LViewI *DateSrc = GetNotify(); if (Ctrl == (LViewI*)Drop && DateSrc) { auto a = Drop->GetTime(); LDateTime ts; const char *str = DateSrc->Name(); ts.Set(str); ts.SetTime(a); DateSrc->Name(ts.Get()); return true; } return LDropDown::OnNotify(Ctrl, n); } void LTimeDropDown::OnChildrenChanged(LViewI *Wnd, bool Attaching) { if (Wnd == (LViewI*)Drop && !Attaching) { Drop = NULL; } } bool LTimeDropDown::OnLayout(LViewLayoutInfo &Inf) { if (!Inf.Width.Max) { Inf.Width.Min = Inf.Width.Max = 20; } else if (!Inf.Height.Max) { Inf.Height.Min = Inf.Height.Max = LSysFont->GetHeight() + 6; } else return false; return true; } void LTimeDropDown::SetDate(char *d) { LViewI *n = GetNotify(); if (n && d) { LDateTime New; New.SetNow(); const char *Old = n->Name(); if (ValidStr(Old)) { New.Set(Old); } else { Old = n->Name(); if (ValidStr(Old)) { New.Set(Old); } } New.SetTime(d); char Buf[256]; New.Get(Buf, sizeof(Buf)); n->Name(Buf); LViewI *Nn = n->GetNotify() ? n->GetNotify() : n->GetParent(); if (Nn) Nn->OnNotify(n, LNotifyValueChanged); } } void LTimeDropDown::OnMouseClick(LMouse &m) { if (m.Down()) { if (Drop) { // Set it's time from our current value LViewI *n = GetNotify(); if (n) { LDateTime New; const char *Old = n->Name(); if (Old && New.Set(Old)) { Drop->SetTime(&New); } } } } LDropDown::OnMouseClick(m); } class TimeList : public LList { LView *Owner; public: bool Key, Mouse; TimeList(LView *owner) : LList(100, 1, 1, 50, 50, "TimeList") { Owner = owner; Key = Mouse = false; } bool OnKey(LKey &k) { bool b = false; if (k.vkey == LK_UP || k.vkey == LK_DOWN || k.vkey == LK_ESCAPE || k.vkey == LK_RETURN) { Key = true; LList::OnKey(k); b = true; Key = false; } if (!b && Owner) b = Owner->OnKey(k); return b; } void OnMouseClick(LMouse &m) { Mouse = true; LList::OnMouseClick(m); Mouse = false; } }; LTimePopup::LTimePopup(LView *owner) : LPopup(owner) { SetParent(owner); SetNotify(owner); Owner = owner; Ignore = true; Children.Insert(Times = new TimeList(owner)); if (Times) { Times->Sunken(false); - Times->ShowColumnHeader(false); + Times->ColumnHeaders(false); Times->AddColumn(""); Times->MultiSelect(false); // Build the list of times... LDateTime Dt; for (int t=0; t<24; t++) { int Time; // Be sensitive to the 24-hr setting... if (Dt.GetFormat() & GDTF_24HOUR) { Time = t; } else { Time = t % 12 > 0 ? t % 12 : 12; } char s[256]; LListItem *i = new LListItem; if (i) { if (Dt.GetFormat() & GDTF_24HOUR) { sprintf_s(s, sizeof(s), "%i:00", Time); } else { sprintf_s(s, sizeof(s), "%i:00 %c", Time, t < 12 ? 'a' : 'p'); } i->SetText(s); Times->Insert(i); } i = new LListItem; if (i) { if (Dt.GetFormat() & GDTF_24HOUR) { sprintf_s(s, sizeof(s), "%i:30", Time); } else { sprintf_s(s, sizeof(s), "%i:30 %c", Time, t < 12 ? 'a' : 'p'); } i->SetText(s); Times->Insert(i); } } } LDateTime Now; if (owner) Now.SetTime(owner->Name()); else Now.SetNow(); SetTime(&Now); LRect r(0, 0, 100, 10*16 + 1); SetPos(r); } LTimePopup::~LTimePopup() { if (Owner) { Owner->OnChildrenChanged(this, false); Owner->Invalidate(); } } void LTimePopup::OnCreate() { Times->Attach(this); } void LTimePopup::OnPaint(LSurface *pDC) { // 1px black border LRect r = GetClient(); r.Offset(-r.x1, -r.y1); pDC->Colour(L_TEXT); pDC->Box(&r); r.Inset(1, 1); // Client if (Times) { Ignore = true; Times->SetPos(r); Times->Focus(true); Ignore = false; } } int LTimePopup::OnNotify(LViewI *c, LNotification n) { if (c->GetId() == 100 && !Ignore) { if (n.Type == LNotifyItemSelect || n.Type == LNotifyReturnKey) { LListItem *Sel = Times->GetSelected(); if (Sel) { const char *t = Sel->GetText(0); if (t) { LViewI *v = GetNotify(); if (v) { v->Name(t); v->OnNotify(this, LNotifyValueChanged); if (Times->Mouse || n.Type == LNotifyReturnKey) { v->Focus(true); Visible(false); } } } } } else if (n.Type == LNotifyEscapeKey) { LViewI *v = GetNotify(); if (v) v->Focus(true); Visible(false); } } return 0; } LString LTimePopup::GetTime() { if (Times) { LListItem *s = Times->GetSelected(); if (s) return s->GetText(0); } return LString(); } void LTimePopup::SetTime(LDateTime *t) { if (!t || !Times) return; for (auto i : *Times) { const char *s = i->GetText(0); if (!s) continue; LDateTime p; p.SetTime(s); if (p.Hours() != t->Hours()) continue; if ( (p.Minutes() < 30 && t->Minutes() < 30) || (p.Minutes() >= 30 && t->Minutes() >= 30) ) { Ignore = true; i->Select(true); i->ScrollTo(); Ignore = false; break; } } } class LTimePopupFactory : public LViewFactory { LView *NewView(const char *Class, LRect *Pos, const char *Text) { if (Class && _stricmp(Class, "LTimeDropDown") == 0) { return new LTimeDropDown; } return 0; } } TimePopupFactory; 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,2234 +1,2234 @@ #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 ForAll(Items) for (auto c : Items) struct LTreeLocker { LTree *t = NULL; bool status = false; LTreeLocker(LTree *tree, const char *file, int line) : t(tree) { if (t) status = t->Lock(file, line); } ~LTreeLocker() { if (status && t) t->Unlock(); } }; #define TREELOCK(ptr) LTreeLocker _lock(ptr, _FL); ////////////////////////////////////////////////////////////////////////////// // Private class definitions for binary compatibility class LTreePrivate { public: // Private data int LineFlags[4] = {}; bool LayoutDirty = true; LPoint Limit; LPoint LastClick; LPoint DragStart; int DragData = 0; LAutoPtr IconCache; bool InPour = false; int64 DropSelectTime = 0; int8 IconTextGap = 0; int LastLayoutPx = -1; LMouse *CurrentClick = NULL; LTreeItem *ScrollTo = NULL; // Visual style LTree::ThumbStyle Btns = LTree::TreeTriangle; bool JoiningLines = false; // Pointers into items... be careful to clear when deleting items... LTreeItem *LastHit = NULL; List Selection; LTreeItem *DropTarget = NULL; }; class LTreeItemPrivate { LArray Ds; LArray ColPx; public: LTreeItem *Item; LRect Pos; LRect Thumb; LRect Text; LRect Icon; bool Open = false; bool Selected = false; bool Visible = false; bool Last = false; int Depth = 0; LTreeItemPrivate(LTreeItem *it) { Item = it; Pos.ZOff(-1, -1); 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; auto txt = Item->GetText(Col); if (txt) { 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(const char *initStr) { d = new LTreeItemPrivate(this); if (initStr) SetText(initStr); } LTreeItem::~LTreeItem() { if (Tree) { if (Tree->d->DropTarget == this) Tree->d->DropTarget = NULL; if (Tree->d->LastHit == this) Tree->d->LastHit = NULL; if (Tree->IsCapturing()) Tree->Capture(false); } int y = 0; LTree *t = NULL; 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 NULL; } 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()) { LgiTrace("%s:%i - Invalid pos: %s, ColumnPx=%i\n", _FL, 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) { TREELOCK(Tree); 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) { // Draw the text in the context area: LFont *f = ds->GetFont(); f->Colour(Ctx.Fore, Ctx.TxtBack); ds->Draw(Ctx.pDC, Ctx.x1 + 2, Ctx.y1 + 1, &Ctx); } else { // No string, fill the space with background Ctx.pDC->Colour(Ctx.Back); Ctx.pDC->Rectangle(&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; + 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); } // Internal tree methods List *LTree::GetSelLst() { return &d->Selection; } void LTree::_Update(LRect *r, bool Now) { TREELOCK(this) 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(this) 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(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_ClearDs(Col); } LPoint LTree::_ScrollPos() { TREELOCK(this) 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(this) 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(this) 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(this) 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(this) Item->OnSelect(); SendNotify(LNotifyItemSelect); } void LTree::OnItemExpand(LTreeItem *Item, bool Expand) { TREELOCK(this) if (Item) Item->OnExpand(Expand); } LTreeItem *LTree::GetAdjacent(LTreeItem *i, bool Down) { TREELOCK(this) 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 = NULL; 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(this) 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(this) if (VScroll) VScroll->Value(VScroll->Value() + (int)Lines); return true; } void LTree::OnMouseClick(LMouse &m) { TREELOCK(this) d->CurrentClick = &m; if (m.Down()) { DragMode = DRAG_NONE; - if (ColumnHeaders && + 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(this) 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(this) if (Columns.Length() == 0 && d->LastLayoutPx != GetClient().X()) d->LayoutDirty = true; LLayout::OnPosChange(); _UpdateScrollBars(); } void LTree::OnPaint(LSurface *pDC) { TREELOCK(this) LCssTools Tools(this); #if 0 // coverage testing... pDC->Colour(LColour(255, 0, 255)); pDC->Rectangle(); #endif rItems = GetClient(); LFont *f = GetFont(); - if (ShowColumnHeader()) + if (ColumnHeaders()) { 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()); if (d->IconCache.Reset(new LMemDC) && 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(this) if (n.Type == 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(this) LTreeItem *NewObj = LTreeNode::Insert(Obj, Pos); if (NewObj) NewObj->_SetTreePtr(this); return NewObj; } bool LTree::HasItem(LTreeItem *Obj, bool Recurse) { TREELOCK(this) if (!Obj) return false; return LTreeNode::HasItem(Obj, Recurse); } bool LTree::Remove(LTreeItem *Obj) { TREELOCK(this) bool Status = false; if (Obj && Obj->Tree == this) { Obj->Remove(); Status = true; } return Status; } void LTree::RemoveAll() { TREELOCK(this) List::I it = Items.begin(); for (LTreeItem *i=*it; i; i=*++it) i->_Remove(); Invalidate(); } void LTree::Empty() { TREELOCK(this) LTreeItem *i; while ((i = Items[0])) Delete(i); } bool LTree::Delete(LTreeItem *Obj) { bool Status = false; TREELOCK(this) if (Obj) { LTreeItem *i; while ((i = Obj->Items[0])) { Delete(i); } Obj->Remove(); DeleteObj(Obj); Status = true; } return Status; } void LTree::OnPulse() { TREELOCK(this) 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(this) 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(this) LItemColumn *Resize = NULL, *Over = NULL; HitColumn(x, y, Resize, Over); return (Resize) ? LCUR_SizeHor : LCUR_Normal; } void LTree::OnDragEnter() { TREELOCK(this) InsideDragOp(true); SetPulse(120); } void LTree::OnDragExit() { TREELOCK(this) InsideDragOp(false); SetPulse(); SelectDropTarget(0); } void LTree::SelectDropTarget(LTreeItem *Item) { TREELOCK(this) 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(this) 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(this) return d->Selection[0]; } bool LTree::ForAllItems(std::function Callback) { TREELOCK(this) return ForEach(Callback) > 0; } void LTree::OnItemClick(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK(this) Item->OnMouseClick(m); if (!m.Ctrl() && !m.Shift()) SendNotify(LNotification(m)); } void LTree::OnItemBeginDrag(LTreeItem *Item, LMouse &m) { if (!Item) return; TREELOCK(this) Item->OnBeginDrag(m); } void LTree::OnFocus(bool b) { TREELOCK(this) // 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(this) d->LayoutDirty = true; LTreeItemUpdateAll(this); } void LTree::SetVisualStyle(ThumbStyle Btns, bool JoiningLines) { TREELOCK(this) d->Btns = Btns; d->JoiningLines = JoiningLines; Invalidate(); } diff --git a/utils/SlogViewer/src/SlogViewerMain.cpp b/utils/SlogViewer/src/SlogViewerMain.cpp --- a/utils/SlogViewer/src/SlogViewerMain.cpp +++ b/utils/SlogViewer/src/SlogViewerMain.cpp @@ -1,556 +1,556 @@ #define _CRT_SECURE_NO_WARNINGS #include "lgi/common/Lgi.h" #include "lgi/common/OptionsFile.h" #include "lgi/common/DocApp.h" #include "lgi/common/Box.h" #include "lgi/common/List.h" #include "lgi/common/TextView3.h" #include "lgi/common/TabView.h" #include "lgi/common/StructuredLog.h" #include "lgi/common/StatusBar.h" #include "resdefs.h" ////////////////////////////////////////////////////////////////// const char *AppName = "SlogViewer"; enum DisplayMode { DisplayHex, DisplayEscaped }; enum Ctrls { IDC_BOX = 100, IDC_LOG, IDC_HEX, IDC_ESCAPED, IDC_TABS, IDC_STATUS, }; struct Context { LList *log = NULL; LTabView *tabs = NULL; LTextView3 *hex = NULL; LTextView3 *escaped = NULL; }; class Entry : public LListItem { Context *c; struct Value { LVariantType type; LArray data; LString name; }; LArray values; LString cache; public: Entry(Context *ctx) : c(ctx) { } void add(LVariantType type, size_t sz, void *ptr, const char *name) { auto &v = values.New(); v.type = type; v.data.Add((uint8_t*)ptr, sz); v.name = name; } const char *GetText(int i) { if (!cache) { LStringPipe p; for (auto &v: values) { switch (v.type) { case GV_INT32: case GV_INT64: { if (v.data.Length() == 4) p.Print(" %i", *(int*)v.data.AddressOf()); else if (v.data.Length() == 8) p.Print(" " LPrintfInt64, *(int64_t*)v.data.AddressOf()); else LAssert(!"Unknown int size."); break; } case GV_STRING: { p.Print(" %.*s", (int)MIN(32,v.data.Length()), v.data.AddressOf()); if (v.data.Length() >= 32) p.Print("..."); break; } case GV_WSTRING: { p.Print(" %.*S", (int)MIN(32,v.data.Length()), v.data.AddressOf()); if (v.data.Length() >= 32) p.Print("..."); break; } case GV_CUSTOM: { p.Print(" Object:%s", v.name.Get()); break; } case GV_VOID_PTR: { // p.Print("/Obj"); break; } case GV_BINARY: { p.Print(" (Bin)"); break; } default: { LAssert(!"Unknown type."); break; } } if (p.GetSize() > 64) break; } cache = p.NewLStr(); } return cache; } template void PrintEscaped(LStringPipe &p, T *s, T *e) { for (auto c = s; c < e; c++) { switch (*c) { case '\\': p.Print("\\\\"); break; case '\t': p.Print("\\t"); break; case '\r': p.Print("\\r"); break; case '\n': p.Print("\\n\n"); break; case 0x07: p.Print("\\b"); break; case 0x1b: p.Print("\\e"); break; default: if (*c < ' ' || *c >= 128) { LString hex; hex.Printf("\\x%x", typename std::make_unsigned::type(*c)); p.Write(hex); } else { char ch = (char)*c; p.Write(&ch, 1); } break; } } } LString IntToString(Value &v, bool showHex) { LString s; if (v.data.Length() == 1) { auto i = (uint8_t*) v.data.AddressOf(); if (showHex) s.Printf("%i 0x%x", *i, *i); else s.Printf("%i", *i); } else if (v.data.Length() == 2) { auto i = (uint16_t*) v.data.AddressOf(); if (showHex) s.Printf("%i 0x%x", *i, *i); else s.Printf("%i", *i); } else if (v.data.Length() == 4) { auto i = (uint32_t*) v.data.AddressOf(); if (showHex) s.Printf("%i 0x%x", *i, *i); else s.Printf("%i", *i); } else if (v.data.Length() == 8) { auto i = (int64_t*) v.data.AddressOf(); if (showHex) s.Printf(LPrintfInt64 " 0x" LPrintfHex64, *i, *i); else s.Printf(LPrintfInt64, *i); } else LAssert(!"Impl me."); return s; } void Select(bool b) override { LListItem::Select(b); auto mode = (DisplayMode)c->tabs->Value(); auto ctrl = mode ? c->escaped : c->hex; if (!b) return; LStringPipe p; LString Obj; int Idx = 0; bool shorthand = false; for (auto &v: values) { auto sType = LVariant::TypeToString(v.type); switch (v.type) { case GV_CUSTOM: { shorthand = v.name.Equals("LRect") || v.name.Equals("LColour"); if (shorthand) p.Print("object %s {", v.name.Get()); else p.Print("object %s {\n\n", v.name.Get()); Obj = v.name; Idx = 0; continue; } case GV_VOID_PTR: { if (shorthand) p.Print(" }\n"); else p.Print("}\n"); Obj.Empty(); shorthand = false; continue; } case GV_STRING: { if (Obj == "LConsole") { auto s = (char*)v.data.AddressOf(); auto e = s + v.data.Length(); p.Print("[%i]=", Idx++); PrintEscaped(p, s, e); p.Print("\n"); continue; } break; } } if (shorthand) { switch (v.type) { case GV_INT32: case GV_INT64: p.Print(" %s=%s", v.name.Get(), IntToString(v, false).Get()); break; case GV_STRING: p.Print(" %s=%.*s", v.name.Get(), (int)v.data.Length(), v.data.AddressOf()); break; default: p.Print(" %s=#impl(%s)", v.name.Get(), LVariant::TypeToString(v.type)); break; } } else { p.Print("%s, %i bytes%s%s: ", sType, (int)v.data.Length(), v.name ? ", " : "", v.name ? v.name.Get() : ""); switch (v.type) { case GV_INT32: case GV_INT64: { p.Print("%s\n", IntToString(v, true).Get()); break; } case GV_BINARY: case GV_STRING: { p.Print("\n"); if (mode == DisplayHex) { char line[300]; int ch = 0; const int rowSize = 16; const int colHex = 10; const int colAscii = colHex + (rowSize * 3) + 2; const int colEnd = colAscii + rowSize; for (size_t addr = 0; addr < v.data.Length() ; addr += rowSize) { ZeroObj(line); sprintf(line, "%8.8x", (int)addr); auto rowBytes = MIN(v.data.Length() - addr, rowSize); LAssert(rowBytes <= rowSize); auto rowPtr = v.data.AddressOf(addr); for (int i=0; i= ' ' && rowPtr[i] < 128 ? rowPtr[i] : '.'; } for (int i=0; i s && e[-1] != '\n') p.Write("\n"); } break; } case GV_WSTRING: { p.Print("\n"); if (mode == DisplayHex) { char line[300]; const int rowSize = 16; const int colHex = 10; const int colAscii = colHex + (rowSize / 2 * 5) + 2; const int colEnd = colAscii + rowSize; for (size_t addr = 0; addr < v.data.Length() ; addr += rowSize) { ZeroObj(line); sprintf(line, "%8.8x", (int)addr); auto rowBytes = MIN(v.data.Length() - addr, rowSize); auto rowWords = rowBytes / sizeof(char16); LAssert(rowBytes <= rowSize); auto rowPtr = (char16*) v.data.AddressOf(addr); for (int i=0; i= ' ' && rowPtr[i] < 128 ? rowPtr[i] : '.'; } for (int i=0; i s && e[-1] != '\n') p.Write("\n"); } break; } default: { LAssert(!"Impl me."); break; } } p.Print("\n"); } } ctrl->Name(p.NewLStr()); } }; class ReaderThread : public LThread { Context *Ctx; LString FileName; LList *Lst = NULL; Progress *Prog = NULL; public: ReaderThread(Context *ctx, const char *filename, LList *lst, Progress *prog) : LThread("ReaderThread") { Ctx = ctx; FileName = filename; Lst = lst; Prog = prog; Run(); } ~ReaderThread() { Prog->Cancel(); WaitForExit(10000); } int Main() { LStructuredLog file(FileName, false); LAutoPtr cur; List items; while (file.Read([this, &cur, &items](auto type, auto size, auto ptr, auto name) { if (type == LStructuredIo::EndRow) { items.Insert(cur.Release()); if (items.Length() > 100) { Lst->Insert(items); items.Empty(); } return; } if (!cur && !cur.Reset(new Entry(Ctx))) return; cur->add(type, size, ptr, name); }, Prog)) ; Lst->Insert(items); return 0; } }; class App : public LDocApp, public Context { LBox *box = NULL; LAutoPtr Reader; LStatusBar *Status = NULL; LProgressStatus *Prog = NULL; public: App() : LDocApp(AppName) { Name(AppName); LRect r(0, 0, 1100, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); if (_Create()) { _LoadMenu(); AddView(Status = new LStatusBar(IDC_STATUS)); Status->AppendPane("Some text"); Status->AppendPane(Prog = new LProgressStatus()); Prog->GetCss(true)->Width("200px"); Prog->GetCss()->TextAlign(LCss::AlignRight); AddView(box = new LBox(IDC_BOX)); box->AddView(log = new LList(IDC_LOG, 0, 0, 200, 200)); log->GetCss(true)->Width("40%"); - log->ShowColumnHeader(false); + log->ColumnHeaders(false); log->AddColumn("Items", 1000); box->AddView(tabs = new LTabView(IDC_TABS)); auto tab = tabs->Append("Hex"); tab->Append(hex = new LTextView3(IDC_HEX)); hex->Sunken(true); hex->SetPourLargest(true); tab = tabs->Append("Escaped"); tab->Append(escaped = new LTextView3(IDC_ESCAPED)); escaped->Sunken(true); escaped->SetPourLargest(true); AttachChildren(); Visible(true); } } void OnReceiveFiles(LArray &Files) { if (Files.Length()) OpenFile(Files[0], false, NULL); } void OpenFile(const char *FileName, bool ReadOnly, std::function Callback) { auto ok = Reader.Reset(new ReaderThread(this, FileName, log, Prog)); if (Callback) Callback(ok); } void SaveFile(const char *FileName, std::function Callback) { if (Callback) Callback(FileName, false); } int OnNotify(LViewI *Ctrl, LNotification n) { if (Ctrl->GetId() == IDC_TABS) { auto item = log->GetSelected(); if (item) item->Select(true); } return 0; } }; ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { LApp a(AppArgs, AppName); if (a.IsOk()) { a.AppWnd = new App; a.Run(); } return 0; }