diff --git a/Lvc/resources/Lvc.lr8 b/Lvc/resources/Lvc.lr8
--- a/Lvc/resources/Lvc.lr8
+++ b/Lvc/resources/Lvc.lr8
@@ -1,325 +1,333 @@
+
+
+
+
+
+
+
+
diff --git a/Lvc/resources/resdefs.h b/Lvc/resources/resdefs.h
--- a/Lvc/resources/resdefs.h
+++ b/Lvc/resources/resdefs.h
@@ -1,68 +1,74 @@
// Generated by LgiRes
// This file generated by LgiRes
#define IDC_COMMIT_AND_PUSH 3
#define IDC_BRANCH 11
#define IDC_BRANCH_DROPDOWN 14
#define IDC_MSG 16
#define IDD_OPTIONS 22
#define IDC_OPTIONS_TABLE 23
#define IDC_SVN 25
#define IDC_SVN_BROWSE 26
#define IDC_GIT 28
#define IDC_GIT_BROWSE 29
#define IDC_HG 32
#define IDC_CVS 33
#define IDC_HG_BROWSE 36
#define IDC_CVS_BROWSE 37
#define IDC_UNTRACKED 38
#define IDC_PULL_ALL 39
#define IDC_STATUS 40
#define IDC_UPDATE 41
#define IDC_HEADS 45
#define IDC_BUILD_FIX 47
#define IDD_REMOTE_FOLDER 61
#define IDC_62 62
#define IDC_HOSTS 64
#define IDC_65 65
#define IDC_HOSTNAME 67
#define IDC_USER 68
#define IDC_PASS 69
#define IDC_DELETE 70
#define IDC_REMOTE_PATH 74
#define IDC_SVN_LIMIT 79
#define IDC_GIT_LIMIT 80
#define IDC_HG_LIMIT 81
#define IDC_CVS_LIMIT 82
+#define IDS_CREATE_NEW_BRANCH 84
+#define IDS_ERR_NO_IMPL_FOR_TYPE 85
+#define IDS_ERR_MERGE_FAILED 86
+#define IDS_ERR_NO_VCS_FOUND 87
+#define IDS_ERR_ADD_FAILED 88
+#define IDS_ERR_REVERT_FAILED 89
#define IDC_TABLE 500
#define IDC_COMMIT 501
#define IDC_PULL 502
#define IDC_PUSH 503
#define IDC_COMMIT_TABLE 504
#define IDM_PATCH_VIEWER 505
#define IDM_MENU_506 506
#define IDD_COMMIT 507
#define IDD_TOOLBAR 508
#define IDC_OPEN 509
#define IDM_FILE 510
#define IDM_OPTIONS 511
#define IDM_HELP 512
#define IDM_ABOUT 513
#define IDM_VIEW_MENU 514
#define IDM_EDIT_MENU 515
#define IDM_FIND 516
#define IDM_REFRESH 517
#define IDM_UNTRACKED 518
#define IDM_MENU_519 519
#define IDM_EXIT 520
#define IDM_REPO 521
#define IDM_PULL 522
#define IDM_PUSH 523
#define IDM_STATUS 524
#define IDM_MENU_525 525
#define IDM_UPDATE_SUBS 526
#define IDM_OPEN_LOCAL 527
#define IDM_OPEN_REMOTE 528
#define IDM_MENU_529 529
#define IDM_OPEN_DIFF 530
diff --git a/Lvc/src/VcFolder.cpp b/Lvc/src/VcFolder.cpp
--- a/Lvc/src/VcFolder.cpp
+++ b/Lvc/src/VcFolder.cpp
@@ -1,4545 +1,4575 @@
#include "Lvc.h"
#include "lgi/common/Combo.h"
#include "lgi/common/ClipBoard.h"
#include "lgi/common/Json.h"
#include "lgi/common/ProgressDlg.h"
#include "resdefs.h"
#ifndef CALL_MEMBER_FN
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
#endif
#define MAX_AUTO_RESIZE_ITEMS 2000
#define PROFILE_FN 0
#if PROFILE_FN
#define PROF(s) Prof.Add(s)
#else
#define PROF(s)
#endif
class TmpFile : public LFile
{
int Status;
LString Hint;
public:
TmpFile(const char *hint = NULL)
{
Status = 0;
if (hint)
Hint = hint;
else
Hint = "_lvc";
}
LFile &Create()
{
LFile::Path p(LSP_TEMP);
p += Hint;
do
{
char s[256];
sprintf_s(s, sizeof(s), "../%s%i.tmp", Hint.Get(), LRand());
p += s;
}
while (p.Exists());
Status = LFile::Open(p.GetFull(), O_READWRITE);
return *this;
}
};
bool TerminalAt(LString Path)
{
#if defined(MAC)
return LExecute("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal", Path);
#elif defined(WINDOWS)
TCHAR w[MAX_PATH_LEN];
auto r = GetWindowsDirectory(w, CountOf(w));
if (r > 0)
{
LFile::Path p = LString(w);
p += "system32\\cmd.exe";
FileDev->SetCurrentFolder(Path);
return LExecute(p);
}
#elif defined(LINUX)
LExecute("gnome-terminal", NULL, Path);
#endif
return false;
}
int Ver2Int(LString v)
{
auto p = v.Split(".");
int i = 0;
for (auto s : p)
{
auto Int = s.Int();
if (Int < 256)
{
i <<= 8;
i |= (uint8_t)Int;
}
else
{
LAssert(0);
return 0;
}
}
return i;
}
int ToolVersion[VcMax] = {0};
#define DEBUG_READER_THREAD 0
#if DEBUG_READER_THREAD
#define LOG_READER(...) printf(__VA_ARGS__)
#else
#define LOG_READER(...)
#endif
ReaderThread::ReaderThread(VersionCtrl vcs, LAutoPtr p, LStream *out) : LThread("ReaderThread")
{
Vcs = vcs;
Process = p;
Out = out;
Result = -1;
FilterCount = 0;
// We don't start this thread immediately... because the number of threads is scaled to the system
// resources, particularly CPU cores.
}
ReaderThread::~ReaderThread()
{
Out = NULL;
while (!IsExited())
LSleep(1);
}
const char *HgFilter = "We\'re removing Mercurial support";
const char *CvsKill = "No such file or directory";
int ReaderThread::OnLine(char *s, ssize_t len)
{
switch (Vcs)
{
case VcHg:
{
if (strnistr(s, HgFilter, len))
FilterCount = 4;
if (FilterCount > 0)
{
FilterCount--;
return 0;
}
else if (LString(s, len).Strip().Equals("remote:"))
{
return 0;
}
break;
}
case VcCvs:
{
if (strnistr(s, CvsKill, len))
return -1;
break;
}
default:
break;
}
return 1;
}
bool ReaderThread::OnData(char *Buf, ssize_t &r)
{
LOG_READER("OnData %i\n", (int)r);
#if 1
char *Start = Buf;
for (char *c = Buf; c < Buf + r;)
{
bool nl = *c == '\n';
c++;
if (nl)
{
int Result = OnLine(Start, c - Start);
if (Result < 0)
{
// Kill process and exit thread.
Process->Kill();
return false;
}
if (Result == 0)
{
ssize_t LineLen = c - Start;
ssize_t NextLine = c - Buf;
ssize_t Remain = r - NextLine;
if (Remain > 0)
memmove(Start, Buf + NextLine, Remain);
r -= LineLen;
c = Start;
}
else Start = c;
}
}
#endif
Out->Write(Buf, r);
return true;
}
int ReaderThread::Main()
{
bool b = Process->Start(true, false);
if (!b)
{
LString s("Process->Start failed.\n");
Out->Write(s.Get(), s.Length(), ErrSubProcessFailed);
return ErrSubProcessFailed;
}
char Buf[1024];
ssize_t r;
LOG_READER("%s:%i - starting reader loop, pid=%i\n", _FL, Process->Handle());
while (Process->IsRunning())
{
if (Out)
{
LOG_READER("%s:%i - starting read.\n", _FL);
r = Process->Read(Buf, sizeof(Buf));
LOG_READER("%s:%i - read=%i.\n", _FL, (int)r);
if (r > 0)
{
if (!OnData(Buf, r))
return -1;
}
}
else
{
Process->Kill();
return -1;
break;
}
}
LOG_READER("%s:%i - process loop done.\n", _FL);
if (Out)
{
while ((r = Process->Read(Buf, sizeof(Buf))) > 0)
OnData(Buf, r);
}
LOG_READER("%s:%i - loop done.\n", _FL);
Result = (int) Process->GetExitValue();
#if _DEBUG
if (Result)
printf("%s:%i - Process err: %i 0x%x\n", _FL, Result, Result);
#endif
return Result;
}
/////////////////////////////////////////////////////////////////////////////////////////////
int VcFolder::CmdMaxThreads = 0;
int VcFolder::CmdActiveThreads = 0;
void VcFolder::Init(AppPriv *priv)
{
if (!CmdMaxThreads)
CmdMaxThreads = LAppInst->GetCpuCount();
d = priv;
IsCommit = false;
IsLogging = false;
IsUpdate = false;
IsFilesCmd = false;
CommitListDirty = false;
IsUpdatingCounts = false;
IsBranches = StatusNone;
IsIdent = StatusNone;
Unpushed = Unpulled = -1;
Type = VcNone;
CmdErrors = 0;
CurrentCommitIdx = -1;
Expanded(false);
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
LAssert(d != NULL);
}
VcFolder::VcFolder(AppPriv *priv, const char *uri)
{
Init(priv);
Uri.Set(uri);
GetType();
}
VcFolder::VcFolder(AppPriv *priv, LXmlTag *t)
{
Init(priv);
Serialize(t, false);
}
VcFolder::~VcFolder()
{
Log.DeleteObjects();
}
VersionCtrl VcFolder::GetType()
{
if (Type == VcNone)
Type = d->DetectVcs(this);
return Type;
}
const char *VcFolder::LocalPath()
{
if (!Uri.IsProtocol("file") || Uri.sPath.IsEmpty())
{
LAssert(!"Shouldn't call this if not a file path.");
return NULL;
}
auto c = Uri.sPath.Get();
#ifdef WINDOWS
if (*c == '/')
c++;
#endif
return c;
}
const char *VcFolder::GetText(int Col)
{
switch (Col)
{
case 0:
{
if (Uri.IsFile())
Cache = LocalPath();
else
Cache.Printf("%s%s", Uri.sHost.Get(), Uri.sPath.Get());
if (Cmds.Length())
Cache += " (...)";
return Cache;
}
case 1:
{
CountCache.Printf("%i/%i", Unpulled, Unpushed);
CountCache = CountCache.Replace("-1", "--");
return CountCache;
}
}
return NULL;
}
bool VcFolder::Serialize(LXmlTag *t, bool Write)
{
if (Write)
t->SetContent(Uri.ToString());
else
{
LString s = t->GetContent();
bool isUri = s.Find("://") >= 0;
if (isUri)
Uri.Set(s);
else
Uri.SetFile(s);
}
return true;
}
LXmlTag *VcFolder::Save()
{
LXmlTag *t = new LXmlTag(OPT_Folder);
if (t)
Serialize(t, true);
return t;
}
const char *VcFolder::GetVcName()
{
const char *Def = NULL;
switch (GetType())
{
case VcGit:
Def = "git";
break;
case VcSvn:
Def = "svn";
break;
case VcHg:
Def = "hg";
break;
case VcCvs:
Def = "cvs";
break;
default:
break;
}
if (!VcCmd)
{
LString Opt;
Opt.Printf("%s-path", Def);
LVariant v;
if (d->Opts.GetValue(Opt, v))
VcCmd = v.Str();
}
if (!VcCmd)
VcCmd = Def;
return VcCmd;
}
bool VcFolder::RunCmd(const char *Args, LoggingType Logging, std::function Callback)
{
Result Ret;
Ret.Code = -1;
const char *Exe = GetVcName();
if (!Exe || CmdErrors > 2)
return false;
if (Uri.IsFile())
{
new ProcessCallback(Exe,
Args,
LocalPath(),
Logging == LogNone ? d->Log : NULL,
GetTree()->GetWindow(),
Callback);
}
else
{
LAssert(!"Impl me.");
return false;
}
return true;
}
bool VcFolder::StartCmd(const char *Args, ParseFn Parser, ParseParams *Params, LoggingType Logging)
{
const char *Exe = GetVcName();
if (!Exe)
return false;
if (CmdErrors > 2)
return false;
if (Uri.IsFile())
{
if (d->Log && Logging != LogSilo)
d->Log->Print("%s %s\n", Exe, Args);
LAutoPtr Process(new LSubProcess(Exe, Args));
if (!Process)
return false;
Process->SetInitFolder(Params && Params->AltInitPath ? Params->AltInitPath.Get() : LocalPath());
#ifdef MAC
// Mac GUI apps don't share the terminal path, so this overrides that and make it work
auto Path = LGetPath();
if (Path.Length())
{
LString Tmp = LString(LGI_PATH_SEPARATOR).Join(Path);
Process->SetEnvironment("PATH", Tmp);
}
#endif
LString::Array Ctx;
Ctx.SetFixedLength(false);
Ctx.Add(LocalPath());
Ctx.Add(Exe);
Ctx.Add(Args);
LAutoPtr c(new Cmd(Ctx, Logging, d->Log));
if (!c)
return false;
c->PostOp = Parser;
c->Params.Reset(Params);
c->Rd.Reset(new ReaderThread(GetType(), Process, c));
Cmds.Add(c.Release());
}
else
{
auto c = d->GetConnection(Uri.ToString());
if (!c)
return false;
if (!c->Command(this, Exe, Args, Parser, Params))
return false;
}
Update();
return true;
}
int LogDateCmp(LListItem *a, LListItem *b, NativeInt Data)
{
VcCommit *A = dynamic_cast(a);
VcCommit *B = dynamic_cast(b);
if ((A != NULL) ^ (B != NULL))
{
// This handles keeping the "working folder" list item at the top
return (A != NULL) - (B != NULL);
}
// Sort the by date from most recent to least
return -A->GetTs().Compare(&B->GetTs());
}
void VcFolder::AddGitName(LString Hash, LString Name)
{
LString Existing = GitNames.Find(Hash);
if (Existing)
GitNames.Add(Hash, Existing + "," + Name);
else
GitNames.Add(Hash, Name);
}
LString VcFolder::GetGitNames(LString Hash)
{
LString Short = Hash(0, 11);
return GitNames.Find(Short);
}
bool VcFolder::ParseBranches(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
LString::Array a = s.SplitDelimit("\r\n");
for (auto &l: a)
{
LString::Array c = l.SplitDelimit(" \t");
if (c[0].Equals("*"))
{
CurrentBranch = c[1];
AddGitName(c[2], CurrentBranch);
Branches.Add(CurrentBranch, new VcBranch(CurrentBranch, c[2]));
}
else
{
AddGitName(c[1], c[0]);
Branches.Add(c[0], new VcBranch(c[0], c[1]));
}
}
break;
}
case VcHg:
{
auto a = s.SplitDelimit("\r\n");
Branches.DeleteObjects();
for (auto b: a)
{
if (!CurrentBranch)
CurrentBranch = b;
Branches.Add(b, new VcBranch(b));
}
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
default:
{
break;
}
}
IsBranches = Result ? StatusError : StatusNone;
OnBranchesChange();
return false;
}
void VcFolder::GetRemoteUrl(std::function Callback)
{
LAutoPtr p(new ParseParams);
p->Callback = Callback;
switch (GetType())
{
case VcGit:
{
StartCmd("config --get remote.origin.url", NULL, p.Release());
break;
}
case VcSvn:
{
StartCmd("info --show-item=url", NULL, p.Release());
break;
}
case VcHg:
{
StartCmd("paths default", NULL, p.Release());
break;
}
default:
break;
}
}
void VcFolder::OnBranchesChange()
{
auto *w = d->Tree->GetWindow();
if (!w || !LTreeItem::Select())
return;
if (Branches.Length())
{
// Set the colours up
LString Default;
for (auto b: Branches)
{
if (!stricmp(b.key, "default") ||
!stricmp(b.key, "trunk"))
Default = b.key;
/*
else
printf("Other=%s\n", b.key);
*/
}
int Idx = 1;
for (auto b: Branches)
{
if (!b.value->Colour.IsValid())
{
if (Default && !stricmp(b.key, Default))
b.value->Colour = GetPaletteColour(0);
else
b.value->Colour = GetPaletteColour(Idx++);
}
}
}
DropDownBtn *dd;
if (w->GetViewById(IDC_BRANCH_DROPDOWN, dd))
{
LString::Array a;
for (auto b: Branches)
a.Add(b.key);
dd->SetList(IDC_BRANCH, a);
}
LViewI *b;
if (Branches.Length() > 0 &&
w->GetViewById(IDC_BRANCH, b))
{
if (CurrentBranch)
{
b->Name(CurrentBranch);
}
else
{
auto it = Branches.begin();
if (it != Branches.end())
b->Name((*it).key);
}
}
}
void VcFolder::DefaultFields()
{
if (Fields.Length() == 0)
{
switch (GetType())
{
case VcHg:
{
Fields.Add(LGraph);
Fields.Add(LIndex);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
case VcGit:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LBranch);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
default:
{
Fields.Add(LGraph);
Fields.Add(LRevision);
Fields.Add(LAuthor);
Fields.Add(LTimeStamp);
Fields.Add(LMessageTxt);
break;
}
}
}
}
void VcFolder::UpdateColumns()
{
d->Commits->EmptyColumns();
for (auto c: Fields)
{
switch (c)
{
case LGraph: d->Commits->AddColumn("---", 60); break;
case LIndex: d->Commits->AddColumn("Index", 60); break;
case LBranch: d->Commits->AddColumn("Branch", 60); break;
case LRevision: d->Commits->AddColumn("Revision", 60); break;
case LAuthor: d->Commits->AddColumn("Author", 240); break;
case LTimeStamp: d->Commits->AddColumn("Date", 130); break;
case LMessageTxt: d->Commits->AddColumn("Message", 400); break;
default: LAssert(0); break;
}
}
}
void VcFolder::FilterCurrentFiles()
{
LArray All;
d->Files->GetAll(All);
// Update the display property
for (auto i: All)
{
auto fn = i->GetText(COL_FILENAME);
bool vis = !d->FileFilter || Stristr(fn, d->FileFilter.Get());
i->GetCss(true)->Display(vis ? LCss::DispBlock : LCss::DispNone);
// LgiTrace("Filter '%s' by '%s' = %i\n", fn, d->FileFilter.Get(), vis);
}
d->Files->Sort(0);
d->Files->UpdateAllItems();
d->Files->ResizeColumnsToContent();
}
void VcFolder::Select(bool b)
{
#if PROFILE_FN
LProfile Prof("Select");
#endif
if (!b)
{
auto *w = d->Tree->GetWindow();
w->SetCtrlName(IDC_BRANCH, NULL);
}
PROF("Parent.Select");
LTreeItem::Select(b);
if (b)
{
if (Uri.IsFile() && !LDirExists(LocalPath()))
return;
PROF("DefaultFields");
DefaultFields();
PROF("Type Change");
if (GetType() != d->PrevType)
{
d->PrevType = GetType();
UpdateColumns();
}
PROF("UpdateCommitList");
if ((Log.Length() == 0 || CommitListDirty) && !IsLogging)
{
switch (GetType())
{
case VcGit:
{
LVariant Limit;
d->Opts.GetValue("git-limit", Limit);
LString cmd = "rev-list --all --header --timestamp --author-date-order", s;
if (Limit.CastInt32() > 0)
{
s.Printf(" -n %i", Limit.CastInt32());
cmd += s;
}
IsLogging = StartCmd(cmd, &VcFolder::ParseRevList);
break;
}
case VcSvn:
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
if (CommitListDirty)
{
IsLogging = StartCmd("up", &VcFolder::ParsePull, new ParseParams("log"));
break;
}
LString s;
if (Limit.CastInt32() > 0)
s.Printf("log --limit %i", Limit.CastInt32());
else
s = "log";
IsLogging = StartCmd(s, &VcFolder::ParseLog);
break;
}
case VcHg:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
StartCmd("resolve -l", &VcFolder::ParseResolveList);
break;
}
case VcPending:
{
break;
}
default:
{
IsLogging = StartCmd("log", &VcFolder::ParseLog);
break;
}
}
CommitListDirty = false;
}
PROF("GetBranches");
if (GetBranches())
OnBranchesChange();
if (d->CurFolder != this)
{
PROF("RemoveAll");
d->CurFolder = this;
d->Commits->RemoveAll();
}
PROF("Uncommit");
if (!Uncommit)
Uncommit.Reset(new UncommitedItem(d));
d->Commits->Insert(Uncommit, 0);
PROF("Log Loop");
int64 CurRev = Atoi(CurrentCommit.Get());
List Ls;
for (auto l: Log)
{
if (CurrentCommit &&
l->GetRev())
{
switch (GetType())
{
case VcSvn:
{
int64 LogRev = Atoi(l->GetRev());
if (CurRev >= 0 && CurRev >= LogRev)
{
CurRev = -1;
l->SetCurrent(true);
}
else
{
l->SetCurrent(false);
}
break;
}
default:
l->SetCurrent(!_stricmp(CurrentCommit, l->GetRev()));
break;
}
}
bool Add = !d->CommitFilter;
if (d->CommitFilter)
{
const char *s = l->GetRev();
if (s && strstr(s, d->CommitFilter) != NULL)
Add = true;
s = l->GetAuthor();
if (s && stristr(s, d->CommitFilter) != NULL)
Add = true;
s = l->GetMsg();
if (s && stristr(s, d->CommitFilter) != NULL)
Add = true;
}
LList *CurOwner = l->GetList();
if (Add ^ (CurOwner != NULL))
{
if (Add)
Ls.Insert(l);
else
d->Commits->Remove(l);
}
}
PROF("Ls Ins");
d->Commits->Insert(Ls);
if (d->Resort >= 0)
{
PROF("Resort");
d->Commits->Sort(LstCmp, d->Resort);
d->Resort = -1;
}
PROF("ColSizing");
if (d->Commits->Length() > MAX_AUTO_RESIZE_ITEMS)
{
int i = 0;
if (GetType() == VcHg && d->Commits->GetColumns() >= 7)
{
d->Commits->ColumnAt(i++)->Width(60); // LGraph
d->Commits->ColumnAt(i++)->Width(40); // LIndex
d->Commits->ColumnAt(i++)->Width(100); // LRevision
d->Commits->ColumnAt(i++)->Width(60); // LBranch
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
else if (d->Commits->GetColumns() >= 5)
{
d->Commits->ColumnAt(i++)->Width(40); // LGraph
d->Commits->ColumnAt(i++)->Width(270); // LRevision
d->Commits->ColumnAt(i++)->Width(240); // LAuthor
d->Commits->ColumnAt(i++)->Width(130); // LTimeStamp
d->Commits->ColumnAt(i++)->Width(400); // LMessage
}
}
else d->Commits->ResizeColumnsToContent();
PROF("UpdateAll");
d->Commits->UpdateAllItems();
PROF("GetCur");
GetCurrentRevision();
}
}
int CommitRevCmp(VcCommit **a, VcCommit **b)
{
int64 arev = Atoi((*a)->GetRev());
int64 brev = Atoi((*b)->GetRev());
int64 diff = (int64)brev - arev;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitIndexCmp(VcCommit **a, VcCommit **b)
{
auto ai = (*a)->GetIndex();
auto bi = (*b)->GetIndex();
auto diff = (int64)bi - ai;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
int CommitDateCmp(VcCommit **a, VcCommit **b)
{
uint64 ats, bts;
(*a)->GetTs().Get(ats);
(*b)->GetTs().Get(bts);
int64 diff = (int64)bts - ats;
if (diff < 0) return -1;
return (diff > 0) ? 1 : 0;
}
void VcFolder::GetCurrentRevision(ParseParams *Params)
{
if (CurrentCommit || IsIdent != StatusNone)
return;
switch (GetType())
{
case VcGit:
if (StartCmd("rev-parse HEAD", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcSvn:
if (StartCmd("info", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcHg:
if (StartCmd("id -i -n", &VcFolder::ParseInfo, Params))
IsIdent = StatusActive;
break;
case VcCvs:
break;
default:
break;
}
}
bool VcFolder::GetBranches(ParseParams *Params)
{
if (Branches.Length() > 0 || IsBranches != StatusNone)
return true;
switch (GetType())
{
case VcGit:
if (StartCmd("-P branch -a -v", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
break;
case VcSvn:
Branches.Add("trunk", new VcBranch("trunk"));
OnBranchesChange();
break;
case VcHg:
if (StartCmd("branch", &VcFolder::ParseBranches, Params))
IsBranches = StatusActive;
break;
case VcCvs:
break;
default:
break;
}
return false;
}
bool VcFolder::ParseRevList(int Result, LString s, ParseParams *Params)
{
Log.DeleteObjects();
int Errors = 0;
switch (GetType())
{
case VcGit:
{
LString::Array Commits;
Commits.SetFixedLength(false);
// Split on the NULL chars...
char *c = s.Get();
char *e = c + s.Length();
while (c < e)
{
char *nul = c;
while (nul < e && *nul) nul++;
if (nul <= c) break;
Commits.New().Set(c, nul-c);
if (nul >= e) break;
c = nul + 1;
}
for (auto Commit: Commits)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->GitParse(Commit, true))
{
Log.Add(Rev.Release());
}
else
{
LAssert(!"Parse failed.");
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, Commit.Get());
Errors++;
}
}
LinkParents();
break;
}
default:
LAssert(!"Impl me.");
break;
}
IsLogging = false;
return Errors == 0;
}
LString VcFolder::GetFilePart(const char *uri)
{
LUri u(uri);
LString File = u.IsFile() ?
u.DecodeStr(u.LocalPath()) :
u.sPath(Uri.sPath.Length(), -1).LStrip("/");
return File;
}
void VcFolder::LogFile(const char *uri)
{
LString Args;
switch (GetType())
{
case VcSvn:
case VcHg:
case VcGit:
{
LString File = GetFilePart(uri);
ParseParams *Params = new ParseParams(uri);
Args.Printf("log \"%s\"", File.Get());
IsLogging = StartCmd(Args, &VcFolder::ParseLog, Params, LogNormal);
break;
}
default:
LAssert(!"Impl me.");
break;
}
}
VcLeaf *VcFolder::FindLeaf(const char *Path, bool OpenTree)
{
VcLeaf *r = NULL;
if (OpenTree)
DoExpand();
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
bool VcFolder::ParseLog(int Result, LString s, ParseParams *Params)
{
LHashTbl, VcCommit*> Map;
for (auto pc: Log)
Map.Add(pc->GetRev(), pc);
int Skipped = 0, Errors = 0;
VcLeaf *File = Params ? FindLeaf(Params->Str, true) : NULL;
LArray *Out = File ? &File->Log : &Log;
if (File)
{
for (auto Leaf = File; Leaf; Leaf = dynamic_cast(Leaf->GetParent()))
Leaf->OnExpand(true);
File->Select(true);
File->ScrollTo();
}
switch (GetType())
{
case VcGit:
{
LString::Array c;
c.SetFixedLength(false);
char *prev = s.Get();
for (char *i = s.Get(); *i; )
{
if (!strnicmp(i, "commit ", 7))
{
if (i > prev)
{
c.New().Set(prev, i - prev);
// LgiTrace("commit=%i\n", (int)(i - prev));
}
prev = i;
}
while (*i)
{
if (*i++ == '\n')
break;
}
}
for (unsigned i=0; i Rev(new VcCommit(d, this));
if (Rev->GitParse(c[i], false))
{
if (!Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else
{
LgiTrace("%s:%i - Failed:\n%s\n\n", _FL, c[i].Get());
Errors++;
}
}
Out->Sort(CommitDateCmp);
break;
}
case VcSvn:
{
LString::Array c = s.Split("------------------------------------------------------------------------");
for (unsigned i=0; i Rev(new VcCommit(d, this));
LString Raw = c[i].Strip();
if (Rev->SvnParse(Raw))
{
if (File || !Map.Find(Rev->GetRev()))
Out->Add(Rev.Release());
else
Skipped++;
}
else if (Raw)
{
OnCmdError(Raw, "ParseLog Failed");
Errors++;
}
}
Out->Sort(CommitRevCmp);
break;
}
case VcHg:
{
LString::Array c = s.Split("\n\n");
LHashTbl, VcCommit*> Idx;
for (auto &Commit: c)
{
LAutoPtr Rev(new VcCommit(d, this));
if (Rev->HgParse(Commit))
{
auto Existing = File ? NULL : Map.Find(Rev->GetRev());
if (!Existing)
Out->Add(Existing = Rev.Release());
if (Existing->GetIndex() >= 0)
Idx.Add(Existing->GetIndex(), Existing);
}
}
if (!File)
{
// Patch all the trivial parents...
for (auto c: Log)
{
if (c->GetParents()->Length() > 0)
continue;
auto CIdx = c->GetIndex();
if (CIdx <= 0)
continue;
auto Par = Idx.Find(CIdx - 1);
if (Par)
c->GetParents()->Add(Par->GetRev());
}
}
Out->Sort(CommitIndexCmp);
if (!File)
LinkParents();
d->Resort = 1;
break;
}
case VcCvs:
{
if (Result)
{
OnCmdError(s, "Cvs command failed.");
break;
}
LHashTbl, VcCommit*> Map;
LString::Array c = s.Split("=============================================================================");
for (auto &Commit: c)
{
if (Commit.Strip().Length())
{
LString Head, File;
LString::Array Versions = Commit.Split("----------------------------");
LString::Array Lines = Versions[0].SplitDelimit("\r\n");
for (auto &Line: Lines)
{
LString::Array p = Line.Split(":", 1);
if (p.Length() == 2)
{
// LgiTrace("Line: %s\n", Line->Get());
LString Var = p[0].Strip().Lower();
LString Val = p[1].Strip();
if (Var.Equals("branch"))
{
if (Val.Length())
Branches.Add(Val, new VcBranch(Val));
}
else if (Var.Equals("head"))
{
Head = Val;
}
else if (Var.Equals("rcs file"))
{
LString::Array f = Val.SplitDelimit(",");
File = f.First();
}
}
}
// LgiTrace("%s\n", Commit->Get());
for (unsigned i=1; i= 3)
{
LString Ver = Lines[0].Split(" ").Last();
LString::Array a = Lines[1].SplitDelimit(";");
LString Date = a[0].Split(":", 1).Last().Strip();
LString Author = a[1].Split(":", 1).Last().Strip();
LString Id = a[2].Split(":", 1).Last().Strip();
LString Msg = Lines[2];
LDateTime Dt;
if (Dt.Parse(Date))
{
uint64 Ts;
if (Dt.Get(Ts))
{
VcCommit *Cc = Map.Find(Ts);
if (!Cc)
{
Map.Add(Ts, Cc = new VcCommit(d, this));
Out->Add(Cc);
Cc->CvsParse(Dt, Author, Msg);
}
Cc->Files.Add(File.Get());
}
else LAssert(!"NO ts for date.");
}
else LAssert(!"Date parsing failed.");
}
}
}
}
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (File)
File->ShowLog();
// LgiTrace("%s:%i - ParseLog: Skip=%i, Error=%i\n", _FL, Skipped, Errors);
IsLogging = false;
return !Result;
}
void VcFolder::LinkParents()
{
#if PROFILE_FN
LProfile Prof("LinkParents");
#endif
LHashTbl,VcCommit*> Map;
// Index all the commits
int i = 0;
for (auto c:Log)
{
c->Idx = i++;
c->NodeIdx = -1;
Map.Add(c->GetRev(), c);
}
// Create all the edges...
PROF("Create edges.");
for (auto c:Log)
{
auto *Par = c->GetParents();
for (auto &pRev : *Par)
{
auto *p = Map.Find(pRev);
if (p)
new VcEdge(p, c);
#if 0
else
return;
#endif
}
}
// Map the edges to positions
PROF("Map edges.");
typedef LArray EdgeArr;
LArray Active;
for (auto c:Log)
{
for (unsigned i=0; c->NodeIdx<0 && iParent == c)
{
c->NodeIdx = i;
break;
}
}
}
// Add starting edges to active set
for (auto e:c->Edges)
{
if (e->Child == c)
{
if (c->NodeIdx < 0)
c->NodeIdx = (int)Active.Length();
e->Idx = c->NodeIdx;
c->Pos.Add(e, e->Idx);
Active[e->Idx].Add(e);
}
}
// Now for all active edges... assign positions
for (unsigned i=0; iLength(); n++)
{
LAssert(Active.PtrCheck(Edges));
VcEdge *e = (*Edges)[n];
if (c == e->Child || c == e->Parent)
{
LAssert(c->NodeIdx >= 0);
c->Pos.Add(e, c->NodeIdx);
}
else
{
// May need to untangle edges with different parents here
bool Diff = false;
for (auto edge: *Edges)
{
if (edge != e &&
edge->Child != c &&
edge->Parent != e->Parent)
{
Diff = true;
break;
}
}
if (Diff)
{
int NewIndex = -1;
// Look through existing indexes for a parent match
for (unsigned ii=0; iiParent?
bool Match = true;
for (auto ee: Active[ii])
{
if (ee->Parent != e->Parent)
{
Match = false;
break;
}
}
if (Match)
NewIndex = ii;
}
if (NewIndex < 0)
// Create new index for this parent
NewIndex = (int)Active.Length();
Edges->Delete(e);
auto &NewEdges = Active[NewIndex];
NewEdges.Add(e);
Edges = &Active[i]; // The 'Add' above can invalidate the object 'Edges' refers to
e->Idx = NewIndex;
c->Pos.Add(e, NewIndex);
n--;
}
else
{
LAssert(e->Idx == i);
c->Pos.Add(e, i);
}
}
}
}
// Process terminating edges
for (auto e: c->Edges)
{
if (e->Parent == c)
{
if (e->Idx < 0)
{
// This happens with out of order commits..?
continue;
}
int i = e->Idx;
if (c->NodeIdx < 0)
c->NodeIdx = i;
if (Active[i].HasItem(e))
Active[i].Delete(e);
else
LgiTrace("%s:%i - Warning: Active doesn't have 'e'.\n", _FL);
}
}
// Collapse any empty active columns
for (unsigned i=0; iIdx > 0);
edge->Idx--;
c->Pos.Add(edge, edge->Idx);
}
}
i--;
}
}
}
// Find all the "heads", i.e. a commit without any children
PROF("Find heads.");
LCombo *Heads;
if (d->Wnd()->GetViewById(IDC_HEADS, Heads))
{
Heads->Empty();
for (auto c:Log)
{
bool Has = false;
for (auto e:c->Edges)
{
if (e->Parent == c)
{
Has = true;
break;
}
}
if (!Has)
Heads->Insert(c->GetRev());
}
Heads->SendNotify(LNotifyTableLayoutRefresh);
}
}
VcFile *AppPriv::FindFile(const char *Path)
{
if (!Path)
return NULL;
LArray files;
if (Files->GetAll(files))
{
LString p = Path;
p = p.Replace(DIR_STR, "/");
for (auto f : files)
{
auto Fn = f->GetFileName();
if (p.Equals(Fn))
return f;
}
}
return NULL;
}
VcFile *VcFolder::FindFile(const char *Path)
{
return d->FindFile(Path);
}
void VcFolder::OnCmdError(LString Output, const char *Msg)
{
if (!CmdErrors)
{
if (Output.Length())
d->Log->Write(Output, Output.Length());
auto vc_name = GetVcName();
if (vc_name)
{
LString::Array a = GetProgramsInPath(GetVcName());
d->Log->Print("'%s' executables in the path:\n", GetVcName());
for (auto Bin : a)
d->Log->Print(" %s\n", Bin.Get());
}
else if (Msg)
{
d->Log->Print("%s\n", Msg);
}
}
CmdErrors++;
d->Tabs->Value(1);
GetCss(true)->Color(LColour::Red);
}
void VcFolder::ClearError()
{
GetCss(true)->Color(LCss::ColorInherit);
}
bool VcFolder::ParseInfo(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
{
auto p = s.Strip().SplitDelimit();
CurrentCommit = p[0].Strip(" \t\r\n+");
if (p.Length() > 1)
CurrentCommitIdx = p[1].Int();
else
CurrentCommitIdx = -1;
if (Params && Params->Str.Equals("CountToTip"))
CountToTip();
break;
}
case VcSvn:
{
if (s.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old");
break;
}
LString::Array c = s.Split("\n");
for (unsigned i=0; iIsWorking = true;
ParseStatus(Result, s, Params);
break;
}
case VcCvs:
{
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
if (Untracked)
{
auto Lines = s.SplitDelimit("\n");
for (auto Ln: Lines)
{
auto p = Ln.SplitDelimit(" \t", 1);
if (p.Length() > 1)
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
// else fall thru
}
default:
{
ParseDiffs(s, LString(), true);
break;
}
}
FilterCurrentFiles();
d->Files->ResizeColumnsToContent();
if (GetType() == VcSvn)
{
Unpushed = d->Files->Length() > 0 ? 1 : 0;
Update();
}
return false;
}
void VcFolder::DiffRange(const char *FromRev, const char *ToRev)
{
if (!FromRev || !ToRev)
return;
switch (GetType())
{
case VcSvn:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("diff -r%s:%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcGit:
{
ParseParams *p = new ParseParams;
p->IsWorking = false;
p->Str = LString(FromRev) + ":" + ToRev;
LString a;
a.Printf("-P diff %s..%s", FromRev, ToRev);
StartCmd(a, &VcFolder::ParseDiff, p);
break;
}
case VcCvs:
case VcHg:
default:
LAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiff(int Result, LString s, ParseParams *Params)
{
if (Params)
ParseDiffs(s, Params->Str, Params->IsWorking);
else
ParseDiffs(s, LString(), true);
return false;
}
void VcFolder::Diff(VcFile *file)
{
auto Fn = file->GetFileName();
if (!Fn ||
!Stricmp(Fn, ".") ||
!Stricmp(Fn, ".."))
return;
const char *Prefix = "";
switch (GetType())
{
case VcGit:
Prefix = "-P ";
// fall through
case VcHg:
{
LString a;
auto rev = file->GetRevision();
if (rev)
a.Printf("%sdiff %s \"%s\"", Prefix, rev, Fn);
else
a.Printf("%sdiff \"%s\"", Prefix, Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcSvn:
{
LString a;
if (file->GetRevision())
a.Printf("diff -r %s \"%s\"", file->GetRevision(), Fn);
else
a.Printf("diff \"%s\"", Fn);
StartCmd(a, &VcFolder::ParseDiff);
break;
}
case VcCvs:
break;
default:
LAssert(!"Impl me.");
break;
}
}
bool VcFolder::ParseDiffs(LString s, LString Rev, bool IsWorking)
{
LAssert(IsWorking || Rev.Get() != NULL);
switch (GetType())
{
case VcGit:
{
LString::Array a = s.Split("\n");
LString Diff;
VcFile *f = NULL;
for (unsigned i=0; iSetDiff(Diff);
Diff.Empty();
auto Bits = a[i].SplitDelimit();
LString Fn, State = "M";
if (Bits[1].Equals("--cc"))
{
Fn = Bits.Last();
State = "C";
}
else
Fn = Bits.Last()(2,-1);
// LgiTrace("%s\n", a[i].Get());
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(State, COL_STATE);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
else if (!_strnicmp(Ln, "new file", 8))
{
if (f)
f->SetText("A", COL_STATE);
}
else if (!_strnicmp(Ln, "deleted file", 12))
{
if (f)
f->SetText("D", COL_STATE);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcHg:
{
LString Sep("\n");
LString::Array a = s.Split(Sep);
LString::Array Diffs;
VcFile *f = NULL;
List Files;
LProgressDlg Prog(GetTree(), 1000);
Prog.SetDescription("Reading diff lines...");
Prog.SetRange(a.Length());
// Prog.SetYieldTime(300);
for (unsigned i=0; iSetDiff(Sep.Join(Diffs));
Diffs.Empty();
auto MainParts = a[i].Split(" -r ");
auto FileParts = MainParts.Last().Split(" ",1);
LString Fn = FileParts.Last();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
// f->SetText(Status, COL_STATE);
Files.Insert(f);
}
else if (!_strnicmp(Ln, "index", 5) ||
!_strnicmp(Ln, "commit", 6) ||
!_strnicmp(Ln, "Author:", 7) ||
!_strnicmp(Ln, "Date:", 5) ||
!_strnicmp(Ln, "+++", 3) ||
!_strnicmp(Ln, "---", 3))
{
// Ignore
}
else
{
Diffs.Add(a[i]);
}
Prog.Value(i);
if (Prog.IsCancelled())
break;
}
if (f && Diffs.Length())
{
f->SetDiff(Sep.Join(Diffs));
Diffs.Empty();
}
d->Files->Insert(Files);
break;
}
case VcSvn:
{
LString::Array a = s.Replace("\r").Split("\n");
LString Diff;
VcFile *f = NULL;
bool InPreamble = false;
bool InDiff = false;
for (unsigned i=0; iSetDiff(Diff);
f->Select(false);
}
Diff.Empty();
InDiff = false;
InPreamble = false;
LString Fn = a[i].Split(":", 1).Last().Strip();
f = FindFile(Fn);
if (!f)
f = new VcFile(d, this, Rev, IsWorking);
f->SetText(Fn.Replace("\\","/"), COL_FILENAME);
f->SetText("M", COL_STATE);
f->GetStatus();
d->Files->Insert(f);
}
else if (!_strnicmp(Ln, "------", 6))
{
InPreamble = !InPreamble;
}
else if (!_strnicmp(Ln, "======", 6))
{
InPreamble = false;
InDiff = true;
}
else if (InDiff)
{
if (!strncmp(Ln, "--- ", 4) ||
!strncmp(Ln, "+++ ", 4))
{
}
else
{
if (Diff) Diff += "\n";
Diff += a[i];
}
}
}
if (f && Diff)
{
f->SetDiff(Diff);
Diff.Empty();
}
break;
}
case VcCvs:
{
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
FilterCurrentFiles();
return true;
}
bool VcFolder::ParseFiles(int Result, LString s, ParseParams *Params)
{
d->ClearFiles();
ParseDiffs(s, Params->Str, false);
IsFilesCmd = false;
FilterCurrentFiles();
return false;
}
void VcFolder::OnSshCmd(SshParams *p)
{
if (!p || !p->f)
{
LAssert(!"Param error.");
return;
}
LString s = p->Output;
int Result = p->ExitCode;
if (Result == ErrSubProcessFailed)
{
CmdErrors++;
}
else if (p->Parser)
{
bool Reselect = CALL_MEMBER_FN(*this, p->Parser)(Result, s, p->Params);
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
}
if (p->Params &&
p->Params->Callback)
{
p->Params->Callback(s);
}
}
void VcFolder::OnPulse()
{
bool Reselect = false, CmdsChanged = false;
static bool Processing = false;
if (!Processing)
{
Processing = true; // Lock out processing, if it puts up a dialog or something...
// bad things happen if we try and re-process something.
// printf("Cmds.Len=%i\n", (int)Cmds.Length());
for (unsigned i=0; iRd->GetState()=%i\n", c->Rd->GetState());
if (c->Rd->GetState() == LThread::THREAD_INIT)
{
if (CmdActiveThreads < CmdMaxThreads)
{
c->Rd->Run();
CmdActiveThreads++;
// LgiTrace("CmdActiveThreads++ = %i\n", CmdActiveThreads);
}
// else printf("Too many active threads.");
}
else if (c->Rd->IsExited())
{
CmdActiveThreads--;
// LgiTrace("CmdActiveThreads-- = %i\n", CmdActiveThreads);
LString s = c->GetBuf();
int Result = c->Rd->ExitCode();
if (Result == ErrSubProcessFailed)
{
if (!CmdErrors)
d->Log->Print("Error: Can't run '%s'\n", GetVcName());
CmdErrors++;
}
else if (c->PostOp)
{
if (s.Length() == 18 &&
s.Equals("GSUBPROCESS_ERROR\n"))
{
OnCmdError(s, "Sub process failed.");
}
else
{
Reselect |= CALL_MEMBER_FN(*this, c->PostOp)(Result, s, c->Params);
}
}
if (c->Params &&
c->Params->Callback)
{
c->Params->Callback(s);
}
Cmds.DeleteAt(i--, true);
delete c;
CmdsChanged = true;
}
// else printf("Not exited.\n");
}
Processing = false;
}
if (Reselect)
{
if (LTreeItem::Select())
Select(true);
}
if (CmdsChanged)
{
Update();
}
if (CmdErrors)
{
d->Tabs->Value(1);
CmdErrors = false;
}
}
void VcFolder::OnRemove()
{
LXmlTag *t = d->Opts.LockTag(OPT_Folders, _FL);
if (t)
{
Uncommit.Reset();
if (LTreeItem::Select())
{
d->Files->Empty();
d->Commits->RemoveAll();
}
bool Found = false;
auto u = Uri.ToString();
for (auto c: t->Children)
{
if (!c->IsTag(OPT_Folder))
printf("%s:%i - Wrong tag: %s, %s\n", _FL, c->GetTag(), OPT_Folder);
else if (!c->GetContent())
printf("%s:%i - No content.\n", _FL);
else
{
auto Content = c->GetContent();
if (!_stricmp(Content, u))
{
c->RemoveTag();
delete c;
Found = true;
break;
}
}
}
LAssert(Found);
d->Opts.Unlock();
}
}
void VcFolder::Empty()
{
Type = VcNone;
IsCommit = false;
IsLogging = false;
IsUpdate = false;
IsFilesCmd = false;
CommitListDirty = false;
IsUpdatingCounts = false;
IsBranches = StatusNone;
IsIdent = StatusNone;
Unpushed = Unpulled = -1;
CmdErrors = 0;
CurrentCommitIdx = -1;
CurrentCommit.Empty();
RepoUrl.Empty();
VcCmd.Empty();
Uncommit.Reset();
Log.DeleteObjects();
d->Commits->Empty();
d->Files->Empty();
if (!Uri.IsFile())
GetCss(true)->Color(LColour::Blue);
}
void VcFolder::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Browse To", IDM_BROWSE_FOLDER, Uri.IsFile());
s.AppendItem(
#ifdef WINDOWS
"Command Prompt At",
#else
"Terminal At",
#endif
IDM_TERMINAL, Uri.IsFile());
s.AppendItem("Clean", IDM_CLEAN);
s.AppendSeparator();
s.AppendItem("Pull", IDM_PULL);
s.AppendItem("Status", IDM_STATUS);
s.AppendItem("Push", IDM_PUSH);
s.AppendItem("Update Subs", IDM_UPDATE_SUBS, GetType() == VcGit);
s.AppendSeparator();
s.AppendItem("Remove", IDM_REMOVE);
s.AppendItem("Remote URL", IDM_REMOTE_URL);
if (!Uri.IsFile())
{
s.AppendSeparator();
s.AppendItem("Edit Location", IDM_EDIT);
}
int Cmd = s.Float(GetTree(), m);
switch (Cmd)
{
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(LocalPath());
break;
}
case IDM_TERMINAL:
{
TerminalAt(LocalPath());
break;
}
case IDM_CLEAN:
{
Clean();
break;
}
case IDM_PULL:
{
Pull();
break;
}
case IDM_STATUS:
{
FolderStatus();
break;
}
case IDM_PUSH:
{
Push();
break;
}
case IDM_UPDATE_SUBS:
{
UpdateSubs();
break;
}
case IDM_REMOVE:
{
OnRemove();
delete this;
break;
}
case IDM_EDIT:
{
auto Dlg = new LInput(GetTree(), Uri.ToString(), "URI:", "Remote Folder Location");
Dlg->DoModal([this, Dlg](auto dlg, auto ctrlId)
{
if (ctrlId)
{
Uri.Set(Dlg->GetStr());
Empty();
Select(true);
}
delete dlg;
});
break;
}
case IDM_REMOTE_URL:
{
GetRemoteUrl([this](auto str)
{
LString Url = str.Strip();
if (Url)
{
auto a = new LAlert(GetTree(), "Remote Url", Url, "Copy", "Ok");
a->DoModal([this, Url](auto dlg, auto code)
{
if (code == 1)
{
LClipBoard c(GetTree());
c.Text(Url);
}
delete dlg;
});
}
});
break;
}
default:
break;
}
}
}
void VcFolder::OnUpdate(const char *Rev)
{
if (!Rev)
return;
if (!IsUpdate)
{
LString Args;
NewRev = Rev;
switch (GetType())
{
case VcGit:
Args.Printf("checkout %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
case VcSvn:
Args.Printf("up -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
case VcHg:
Args.Printf("update -r %s", Rev);
IsUpdate = StartCmd(Args, &VcFolder::ParseUpdate, NULL, LogNormal);
break;
default:
{
LAssert(!"Impl me.");
break;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////
int FolderCompare(LTreeItem *a, LTreeItem *b, NativeInt UserData)
{
VcLeaf *A = dynamic_cast(a);
VcLeaf *B = dynamic_cast(b);
if (!A || !B)
return 0;
return A->Compare(B);
}
struct SshFindEntry
{
LString Flags, Name, User, Group;
uint64_t Size;
LDateTime Modified, Access;
SshFindEntry &operator =(const LString &s)
{
auto p = s.SplitDelimit("/");
if (p.Length() == 7)
{
Flags = p[0];
Group = p[1];
User = p[2];
Access.Set((uint64_t) p[3].Int());
Modified.Set((uint64_t) p[4].Int());
Size = p[5].Int();
Name = p[6];
}
return *this;
}
bool IsDir() { return Flags(0) == 'd'; }
bool IsHidden() { return Name(0) == '.'; }
const char *GetName() { return Name; }
static int Compare(SshFindEntry *a, SshFindEntry *b) { return Stricmp(a->Name.Get(), b->Name.Get()); }
};
bool VcFolder::ParseRemoteFind(int Result, LString s, ParseParams *Params)
{
if (!Params || !s)
return false;
auto Parent = Params->Leaf ? static_cast(Params->Leaf) : static_cast(this);
LUri u(Params->Str);
auto Lines = s.SplitDelimit("\r\n");
LArray Entries;
for (size_t i=1; iStr, Dir.GetName(), true);
}
}
else if (!Dir.IsHidden())
{
char *Ext = LGetExtension(Dir.GetName());
if (!Ext) continue;
if (!stricmp(Ext, "c") ||
!stricmp(Ext, "cpp") ||
!stricmp(Ext, "h"))
{
LUri Path = u;
Path += Dir.GetName();
new VcLeaf(this, Parent, Params->Str, Dir.GetName(), false);
}
}
}
return false;
}
void VcFolder::ReadDir(LTreeItem *Parent, const char *ReadUri)
{
LUri u(ReadUri);
if (u.IsFile())
{
// Read child items
LDirectory Dir;
for (int b = Dir.First(u.LocalPath()); b; b = Dir.Next())
{
auto name = Dir.GetName();
if (Dir.IsHidden())
continue;
LUri Path = u;
Path += name;
new VcLeaf(this, Parent, u.ToString(), name, Dir.IsDir());
}
}
else
{
auto c = d->GetConnection(ReadUri);
if (!c)
return;
LString Path = u.sPath(Uri.sPath.Length(), -1).LStrip("/");
LString Args;
Args.Printf("\"%s\" -maxdepth 1 -printf \"%%M/%%g/%%u/%%A@/%%T@/%%s/%%P\n\"", Path ? Path.Get() : ".");
auto *Params = new ParseParams(ReadUri);
Params->Leaf = dynamic_cast(Parent);
c->Command(this, "find", Args, &VcFolder::ParseRemoteFind, Params);
return;
}
Parent->Sort(FolderCompare);
}
void VcFolder::OnVcsType(LString errorMsg)
{
if (!d)
{
LAssert(!"No priv instance");
return;
}
auto c = d->GetConnection(Uri.ToString(), false);
if (c)
{
auto NewType = c->Types.Find(Uri.sPath);
if (NewType && NewType != Type)
{
if (NewType == VcError)
{
OnCmdError(LString(), errorMsg);
}
else
{
Type = NewType;
ClearError();
Update();
if (LTreeItem::Select())
Select(true);
}
}
}
}
void VcFolder::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
ReadDir(this, Uri.ToString());
}
}
void VcFolder::OnExpand(bool b)
{
if (b)
DoExpand();
}
void VcFolder::ListCommit(VcCommit *c)
{
if (!IsFilesCmd)
{
LString Args;
switch (GetType())
{
case VcGit:
Args.Printf("-P show %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcSvn:
Args.Printf("log --verbose --diff -r %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
case VcCvs:
{
d->ClearFiles();
for (unsigned i=0; iFiles.Length(); i++)
{
VcFile *f = new VcFile(d, this, c->GetRev(), false);
if (f)
{
f->SetText(c->Files[i], COL_FILENAME);
d->Files->Insert(f);
}
}
FilterCurrentFiles();
break;
}
case VcHg:
{
Args.Printf("diff --change %s", c->GetRev());
IsFilesCmd = StartCmd(Args, &VcFolder::ParseFiles, new ParseParams(c->GetRev()));
break;
}
default:
LAssert(!"Impl me.");
break;
}
if (IsFilesCmd)
d->ClearFiles();
}
}
LString ConvertUPlus(LString s)
{
LArray c;
LUtf8Ptr p(s);
int32 ch;
while ((ch = p))
{
if (ch == '{')
{
auto n = p.GetPtr();
if (n[1] == 'U' &&
n[2] == '+')
{
// Convert unicode code point
p += 3;
ch = (int32)htoi(p.GetPtr());
c.Add(ch);
while ((ch = p) != '}')
p++;
}
else c.Add(ch);
}
else c.Add(ch);
p++;
}
c.Add(0);
#ifdef LINUX
return LString((char16*)c.AddressOf());
#else
return LString(c.AddressOf());
#endif
}
bool VcFolder::ParseStatus(int Result, LString s, ParseParams *Params)
{
bool ShowUntracked = d->Wnd()->GetCtrlValue(IDC_UNTRACKED) != 0;
bool IsWorking = Params ? Params->IsWorking : false;
List Ins;
switch (GetType())
{
case VcCvs:
{
LHashTbl,VcFile*> Map;
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
Map.Add(f->GetText(COL_FILENAME), f);
}
#if 0
LFile Tmp("C:\\tmp\\output.txt", O_WRITE);
Tmp.Write(s);
Tmp.Close();
#endif
LString::Array a = s.Split("===================================================================");
for (auto i : a)
{
LString::Array Lines = i.SplitDelimit("\r\n");
if (Lines.Length() == 0)
continue;
LString f = Lines[0].Strip();
if (f.Find("File:") == 0)
{
LString::Array Parts = f.SplitDelimit("\t");
LString File = Parts[0].Split(": ").Last().Strip();
LString Status = Parts[1].Split(": ").Last();
LString WorkingRev;
for (auto l : Lines)
{
LString::Array p = l.Strip().Split(":", 1);
if (p.Length() > 1 &&
p[0].Strip().Equals("Working revision"))
{
WorkingRev = p[1].Strip();
}
}
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, WorkingRev, IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText(Status, COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
else if (f(0) == '?' &&
ShowUntracked)
{
LString File = f(2, -1);
VcFile *f = Map.Find(File);
if (!f)
{
if ((f = new VcFile(d, this, LString(), IsWorking)))
Ins.Insert(f);
}
if (f)
{
f->SetText("?", COL_STATE);
f->SetText(File, COL_FILENAME);
f->Update();
}
}
}
for (auto i: *d->Files)
{
VcFile *f = dynamic_cast(i);
if (f)
{
if (f->GetStatus() == VcFile::SUnknown)
f->SetStatus(VcFile::SUntracked);
}
}
break;
}
case VcGit:
{
LString::Array Lines = s.SplitDelimit("\r\n");
int Fmt = ToolVersion[VcGit] >= Ver2Int("2.8.0") ? 2 : 1;
for (auto Ln : Lines)
{
char Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("usage: git") >= 0)
{
// It's probably complaining about the --porcelain=2 parameter
OnCmdError(s, "Args error");
}
else if (Type != '?')
{
VcFile *f = NULL;
if (Fmt == 2)
{
LString::Array p = Ln.SplitDelimit(" ", 8);
if (p.Length() < 7)
d->Log->Print("%s:%i - Error: not enough tokens: '%s'\n", _FL, Ln.Get());
else
{
f = new VcFile(d, this, p[6], IsWorking);
f->SetText(p[1].Strip("."), COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
}
}
else if (Fmt == 1)
{
LString::Array p = Ln.SplitDelimit(" ");
f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(p.Last(), COL_FILENAME);
}
if (f)
Ins.Insert(f);
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
case VcHg:
case VcSvn:
{
if (s.Find("failed to import") >= 0)
{
OnCmdError(s, "Tool error.");
return false;
}
LString::Array Lines = s.SplitDelimit("\r\n");
for (auto Ln : Lines)
{
char Type = Ln(0);
if (Ln.Lower().Find("error:") >= 0)
{
}
else if (Ln.Find("client is too old") >= 0)
{
OnCmdError(s, "Client too old.");
return false;
}
else if (Strchr(" \t", Type) ||
Ln.Find("Summary of conflicts") >= 0)
{
// Ignore
}
else if (Type != '?')
{
LString::Array p = Ln.SplitDelimit(" ", 1);
if (p.Length() == 2)
{
LString File;
if (GetType() == VcSvn)
File = ConvertUPlus(p.Last());
else
File = p.Last();
if (GetType() == VcSvn &&
File.Find("+ ") == 0)
{
File = File(5, -1);
}
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText(p[0], COL_STATE);
f->SetText(File.Replace("\\","/"), COL_FILENAME);
f->GetStatus();
Ins.Insert(f);
}
else LAssert(!"What happen?");
}
else if (ShowUntracked)
{
VcFile *f = new VcFile(d, this, LString(), IsWorking);
f->SetText("?", COL_STATE);
f->SetText(Ln(2,-1), COL_FILENAME);
Ins.Insert(f);
}
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
if ((Unpushed = Ins.Length() > 0))
{
if (CmdErrors == 0)
GetCss(true)->Color(LColour(255, 128, 0));
}
else if (Unpulled == 0)
{
GetCss(true)->Color(LCss::ColorInherit);
}
Update();
if (LTreeItem::Select())
{
d->Files->Insert(Ins);
FilterCurrentFiles();
}
else
{
Ins.DeleteObjects();
}
if (Params && Params->Leaf)
Params->Leaf->AfterBrowse();
return false; // Don't refresh list
}
// Clone/checkout any sub-repositries.
bool VcFolder::UpdateSubs()
{
LString Arg;
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
Arg = "submodule update --init";
break;
}
return StartCmd(Arg, &VcFolder::ParseUpdateSubs, NULL, LogNormal);
}
bool VcFolder::ParseUpdateSubs(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
default:
case VcSvn:
case VcHg:
case VcCvs:
return false;
case VcGit:
break;
}
return false;
}
void VcFolder::FolderStatus(const char *uri, VcLeaf *Notify)
{
LUri Uri(uri);
if (Uri.IsFile() && Uri.sPath)
{
LFile::Path p(Uri.sPath(1,-1));
if (!p.IsFolder())
{
LAssert(!"Needs to be a folder.");
return;
}
}
if (LTreeItem::Select())
d->ClearFiles();
LString Arg;
switch (GetType())
{
case VcSvn:
case VcHg:
Arg = "status";
break;
case VcCvs:
Arg = "status -l";
break;
case VcGit:
if (!ToolVersion[VcGit])
LAssert(!"Where is the version?");
// What version did =2 become available? It's definitely not in v2.5.4
// Not in v2.7.4 either...
if (ToolVersion[VcGit] >= Ver2Int("2.8.0"))
Arg = "-P status --porcelain=2";
else
Arg = "-P status --porcelain";
break;
default:
return;
}
ParseParams *p = new ParseParams;
if (uri && Notify)
{
p->AltInitPath = uri;
p->Leaf = Notify;
}
else
{
p->IsWorking = true;
}
StartCmd(Arg, &VcFolder::ParseStatus, p);
switch (GetType())
{
case VcHg:
CountToTip();
break;
default:
break;
}
}
void VcFolder::CountToTip()
{
// if (Path.Equals("C:\\Users\\matthew\\Code\\Lgi\\trunk"))
{
// LgiTrace("%s: CountToTip, br=%s, idx=%i\n", Path.Get(), CurrentBranch.Get(), (int)CurrentCommitIdx);
if (!CurrentBranch)
GetBranches(new ParseParams("CountToTip"));
else if (CurrentCommitIdx < 0)
GetCurrentRevision(new ParseParams("CountToTip"));
else
{
LString Arg;
Arg.Printf("id -n -r %s", CurrentBranch.Get());
StartCmd(Arg, &VcFolder::ParseCountToTip);
}
}
}
bool VcFolder::ParseCountToTip(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
if (CurrentCommitIdx >= 0)
{
auto p = s.Strip();
auto idx = p.Int();
if (idx >= CurrentCommitIdx)
{
Unpulled = (int) (idx - CurrentCommitIdx);
Update();
}
}
break;
default:
break;
}
return false;
}
void VcFolder::ListWorkingFolder()
{
d->ClearFiles();
bool Untracked = d->IsMenuChecked(IDM_UNTRACKED);
LString Arg;
switch (GetType())
{
case VcCvs:
if (Untracked)
Arg = "-qn update";
else
Arg = "-q diff --brief";
break;
case VcSvn:
Arg = "status";
break;
case VcGit:
// Fucking git won't honour their own docs.
StartCmd("-P diff --diff-filter=AD --cached", &VcFolder::ParseWorking);
Arg = "-P diff --diff-filter=CMRTU";
break;
case VcHg:
Arg = "status -mard";
break;
default:
return;
}
StartCmd(Arg, &VcFolder::ParseWorking);
}
void VcFolder::GitAdd()
{
if (!PostAdd)
return;
LString Args;
if (PostAdd->Files.Length() == 0)
{
LString m(PostAdd->Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -m \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, PostAdd->Param, LogNormal);
PostAdd.Reset();
}
else
{
LString Last = PostAdd->Files.Last();
Args.Printf("add \"%s\"", Last.Replace("\"", "\\\"").Replace("/", DIR_STR).Get());
PostAdd->Files.PopLast();
StartCmd(Args, &VcFolder::ParseGitAdd, NULL, LogNormal);
}
}
bool VcFolder::ParseGitAdd(int Result, LString s, ParseParams *Params)
{
GitAdd();
return false;
}
bool VcFolder::ParseCommit(int Result, LString s, ParseParams *Params)
{
if (LTreeItem::Select())
Select(true);
CommitListDirty = Result == 0;
CurrentCommit.Empty();
IsCommit = false;
if (Result)
{
switch (GetType())
{
case VcGit:
{
if (s.Find("Please tell me who you are") >= 0)
{
auto i = new LInput(GetTree(), "", "Git user name:", AppName);
i->DoModal([this, i](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.name \"%s\"", i->GetStr().Get());
StartCmd(Args);
auto inp = new LInput(GetTree(), "", "Git user email:", AppName);
i->DoModal([this, inp](auto dlg, auto ctrlId)
{
if (ctrlId)
{
LString Args;
Args.Printf("config --global user.email \"%s\"", inp->GetStr().Get());
StartCmd(Args);
}
delete dlg;
});
}
delete dlg;
});
}
break;
}
default:
break;
}
return false;
}
if (Result == 0 && LTreeItem::Select())
{
d->ClearFiles();
auto *w = d->Diff ? d->Diff->GetWindow() : NULL;
if (w)
w->SetCtrlName(IDC_MSG, NULL);
}
switch (GetType())
{
case VcGit:
{
Unpushed++;
CommitListDirty = true;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
break;
}
case VcSvn:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcHg:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
if (Params && Params->Str.Find("Push") >= 0)
Push();
else
GetCss(true)->Color(LColour::Green);
}
break;
}
case VcCvs:
{
CurrentCommit.Empty();
CommitListDirty = true;
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (!Result)
{
Unpushed = 0;
Update();
GetCss(true)->Color(LColour::Green);
}
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
return true;
}
void VcFolder::Commit(const char *Msg, const char *Branch, bool AndPush)
{
LArray Add;
bool Partial = false;
for (auto fp: *d->Files)
{
VcFile *f = dynamic_cast(fp);
if (f)
{
int c = f->Checked();
if (c > 0)
Add.Add(f);
else
Partial = true;
}
}
if (CurrentBranch && Branch &&
!CurrentBranch.Equals(Branch))
{
int Response = LgiMsg(GetTree(), "Do you want to start a new branch?", AppName, MB_YESNO);
if (Response != IDYES)
return;
LJson j;
j.Set("Command", "commit");
j.Set("Msg", Msg);
j.Set("AndPush", (int64_t)AndPush);
StartBranch(Branch, j.GetJson());
return;
}
if (!IsCommit)
{
LString Args;
ParseParams *Param = AndPush ? new ParseParams("Push") : NULL;
switch (GetType())
{
case VcGit:
{
if (Add.Length() == 0)
{
break;
}
else if (Partial)
{
if (PostAdd.Reset(new GitCommit))
{
PostAdd->Files.SetFixedLength(false);
for (auto f : Add)
PostAdd->Files.Add(f->GetFileName());
PostAdd->Msg = Msg;
PostAdd->Branch = Branch;
PostAdd->Param = Param;
GitAdd();
}
}
else
{
LString m(Msg);
m = m.Replace("\"", "\\\"");
Args.Printf("commit -am \"%s\"", m.Get());
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
}
break;
}
case VcSvn:
{
LString::Array a;
a.New().Printf("commit -m \"%s\"", Msg);
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcHg:
{
LString::Array a;
LString CommitMsg = Msg;
TmpFile Tmp;
if (CommitMsg.Find("\n") >= 0)
{
Tmp.Create().Write(Msg);
a.New().Printf("commit -l \"%s\"", Tmp.GetName());
}
else
{
a.New().Printf("commit -m \"%s\"", Msg);
}
if (Partial)
{
for (auto pf: Add)
{
LString s = pf->GetFileName();
if (s.Find(" ") >= 0)
a.New().Printf("\"%s\"", s.Get());
else
a.New() = s;
}
}
Args = LString(" ").Join(a);
IsCommit = StartCmd(Args, &VcFolder::ParseCommit, Param, LogNormal);
if (d->Tabs && IsCommit)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
break;
}
case VcCvs:
{
LString a;
a.Printf("commit -m \"%s\"", Msg);
IsCommit = StartCmd(a, &VcFolder::ParseCommit, NULL, LogNormal);
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
}
}
bool VcFolder::ParseStartBranch(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
if (Result == 0 && Params && Params->Str)
{
LJson j(Params->Str);
auto cmd = j.Get("Command");
if (cmd.Equals("commit"))
{
auto Msg = j.Get("Msg");
auto AndPush = j.Get("AndPush").Int();
if (Msg)
{
Commit(Msg, NULL, AndPush > 0);
}
}
}
break;
}
default:
{
OnCmdError(LString(), "No commit impl for type.");
break;
}
}
return true;
}
void VcFolder::StartBranch(const char *BranchName, const char *OnCreated)
{
if (!BranchName)
return;
switch (GetType())
{
case VcHg:
{
LString a;
a.Printf("branch \"%s\"", BranchName);
StartCmd(a, &VcFolder::ParseStartBranch, OnCreated ? new ParseParams(OnCreated) : NULL);
break;
}
default:
{
- OnCmdError(LString(), "No commit impl for type.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
}
void VcFolder::Push(bool NewBranchOk)
{
LString Args;
bool Working = false;
switch (GetType())
{
case VcHg:
{
auto args = NewBranchOk ? "push --new-branch" : "push";
Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcGit:
{
- Working = StartCmd("push", &VcFolder::ParsePush, NULL, LogNormal);
+ LString args;
+ if (NewBranchOk)
+ {
+ if (CurrentBranch)
+ {
+ args.Printf("push --set-upstream origin %s", CurrentBranch.Get());
+ }
+ else
+ {
+ OnCmdError(LString(), "Don't have the current branch?");
+ return;
+ }
+ }
+ else
+ {
+ args = "push";
+ }
+
+ Working = StartCmd(args, &VcFolder::ParsePush, NULL, LogNormal);
break;
}
case VcSvn:
{
// Nothing to do here.. the commit pushed the data already
break;
}
default:
{
OnCmdError(LString(), "No push impl for type.");
break;
}
}
if (d->Tabs && Working)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePush(int Result, LString s, ParseParams *Params)
{
bool Status = false;
if (Result)
{
- if (GetType() == VcHg)
+ bool needsNewBranchPerm = false;
+
+ switch (GetType())
{
- if (s.Find("push creates new remote branches") > 0)
+ case VcHg:
+ {
+ needsNewBranchPerm = s.Find("push creates new remote branches") >= 0;
+ break;
+ }
+ case VcGit:
{
- if (LgiMsg(GetTree(), "Push will create a new remote branch. Is that ok?", AppName, MB_YESNO) == IDYES)
- {
- Push(true);
- return false;
- }
+ needsNewBranchPerm = s.Find("The current branch") >= 0 &&
+ s.Find("has no upstream branch") >= 0;
+ break;
}
}
+
+ if (needsNewBranchPerm &&
+ LgiMsg(GetTree(), LLoadString(IDS_CREATE_NEW_BRANCH), AppName, MB_YESNO) == IDYES)
+ {
+ Push(true);
+ return false;
+ }
OnCmdError(s, "Push failed.");
}
else
{
switch (GetType())
{
case VcGit:
break;
case VcSvn:
break;
default:
break;
}
Unpushed = 0;
GetCss(true)->Color(LColour::Green);
Update();
Status = true;
}
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
return Status; // no reselect
}
void VcFolder::Pull(int AndUpdate, LoggingType Logging)
{
bool Status = false;
if (AndUpdate < 0)
AndUpdate = GetTree()->GetWindow()->GetCtrlValue(IDC_UPDATE) != 0;
switch (GetType())
{
case VcNone:
return;
case VcHg:
Status = StartCmd(AndUpdate ? "pull -u" : "pull", &VcFolder::ParsePull, NULL, Logging);
break;
case VcGit:
Status = StartCmd(AndUpdate ? "pull" : "fetch", &VcFolder::ParsePull, NULL, Logging);
break;
case VcSvn:
Status = StartCmd("up", &VcFolder::ParsePull, NULL, Logging);
break;
default:
- OnCmdError(LString(), "No pull impl for type.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
if (d->Tabs && Status)
{
d->Tabs->Value(1);
GetTree()->SendNotify((LNotifyType)LvcCommandStart);
}
}
bool VcFolder::ParsePull(int Result, LString s, ParseParams *Params)
{
GetTree()->SendNotify((LNotifyType)LvcCommandEnd);
if (Result)
{
OnCmdError(s, "Pull failed.");
return false;
}
else ClearError();
switch (GetType())
{
case VcGit:
{
// Git does a merge by default, so the current commit changes...
CurrentCommit.Empty();
break;
}
case VcHg:
{
CurrentCommit.Empty();
auto Lines = s.SplitDelimit("\n");
bool HasUpdates = false;
for (auto Ln: Lines)
{
if (Ln.Find("files updated") < 0)
continue;
auto Parts = Ln.Split(",");
for (auto p: Parts)
{
auto n = p.Strip().Split(" ", 1);
if (n.Length() == 2)
{
if (n[0].Int() > 0)
HasUpdates = true;
}
}
}
if (HasUpdates)
GetCss(true)->Color(LColour::Green);
else
GetCss(true)->Color(LCss::ColorInherit);
break;
}
case VcSvn:
{
// Svn also does a merge by default and can update our current position...
CurrentCommit.Empty();
LString::Array a = s.SplitDelimit("\r\n");
for (auto &Ln: a)
{
if (Ln.Find("At revision") >= 0)
{
LString::Array p = Ln.SplitDelimit(" .");
CurrentCommit = p.Last();
break;
}
else if (Ln.Find("svn cleanup") >= 0)
{
OnCmdError(s, "Needs cleanup");
break;
}
}
if (Params && Params->Str.Equals("log"))
{
LVariant Limit;
d->Opts.GetValue("svn-limit", Limit);
LString Args;
if (Limit.CastInt32() > 0)
Args.Printf("log --limit %i", Limit.CastInt32());
else
Args = "log";
IsLogging = StartCmd(Args, &VcFolder::ParseLog);
return false;
}
break;
}
default:
break;
}
CommitListDirty = true;
return true; // Yes - reselect and update
}
void VcFolder::MergeToLocal(LString Rev)
{
switch (GetType())
{
case VcGit:
{
LString Args;
Args.Printf("merge -m \"Merge with %s\" %s", Rev.Get(), Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
case VcHg:
{
LString Args;
Args.Printf("merge -r %s", Rev.Get());
StartCmd(Args, &VcFolder::ParseMerge, NULL, LogNormal);
break;
}
default:
- LgiMsg(GetTree(), "Not implemented.", AppName);
+ LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseMerge(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
case VcHg:
if (Result == 0)
CommitListDirty = true;
else
- OnCmdError(s, "Merge failed.");
+ OnCmdError(s, LLoadString(IDS_ERR_MERGE_FAILED));
break;
default:
LAssert(!"Impl me.");
break;
}
return true;
}
void VcFolder::Refresh()
{
CommitListDirty = true;
CurrentCommit.Empty();
GitNames.Empty();
Branches.DeleteObjects();
if (Uncommit && Uncommit->LListItem::Select())
Uncommit->Select(true);
Select(true);
}
void VcFolder::Clean()
{
switch (GetType())
{
case VcSvn:
StartCmd("cleanup", &VcFolder::ParseClean, NULL, LogNormal);
break;
default:
- LgiMsg(GetTree(), "Not implemented.", AppName);
+ LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
bool VcFolder::ParseClean(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcSvn:
if (Result == 0)
GetCss(true)->Color(LCss::ColorInherit);
break;
default:
LAssert(!"Impl me.");
break;
}
return false;
}
LColour VcFolder::BranchColour(const char *Name)
{
if (!Name)
return GetPaletteColour(0);
auto b = Branches.Find(Name);
if (!b) // Must be a new one?
{
int i = 1;
for (auto b: Branches)
{
auto &v = b.value;
if (!v->Colour.IsValid())
{
if (v->Default)
v->Colour = GetPaletteColour(0);
else
v->Colour = GetPaletteColour(i++);
}
}
Branches.Add(Name, b = new VcBranch(Name));
b->Colour = GetPaletteColour((int)Branches.Length());
}
return b ? b->Colour : GetPaletteColour(0);
}
void VcFolder::CurrentRev(std::function Callback)
{
LString Cmd;
Cmd.Printf("id -i");
RunCmd(Cmd, LogNormal, [Callback](auto r)
{
if (r.Code == 0)
Callback(r.Out.Strip());
});
}
bool VcFolder::RenameBranch(LString NewName, LArray &Revs)
{
switch (GetType())
{
case VcHg:
{
// Update to the ancestor of the commits
LHashTbl,int> Refs(0, -1);
for (auto c: Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) < 0)
Refs.Add(p, 0);
if (Refs.Find(c->GetRev()) >= 0)
Refs.Add(c->GetRev(), 1);
}
LString::Array Ans;
for (auto i:Refs)
{
if (i.value == 0)
Ans.Add(i.key);
}
LArray Ancestors = d->GetRevs(Ans);
if (Ans.Length() != 1)
{
// We should only have one ancestor
LString s, m;
s.Printf("Wrong number of ancestors: " LPrintfInt64 ".\n", Ans.Length());
for (auto i: Ancestors)
{
m.Printf("\t%s\n", i->GetRev());
s += m;
}
LgiMsg(GetTree(), s, AppName, MB_OK);
break;
}
LArray Top;
for (auto c:Revs)
{
for (auto p:*c->GetParents())
if (Refs.Find(p) == 0)
Top.Add(c);
}
if (Top.Length() != 1)
{
d->Log->Print("Error: Can't find top most commit. (%s:%i)\n", _FL);
return false;
}
// Create the new branch...
auto First = Ancestors.First();
LString Cmd;
Cmd.Printf("update -r " LPrintfInt64, First->GetIndex());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
Cmd.Printf("branch \"%s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
// Commit it to get a revision point to rebase to
Cmd.Printf("commit -m \"Branch: %s\"", NewName.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, &Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CurrentRev([this, &Cmd, NewName, &Top](auto BranchNode)
{
// Rebase the old tree to this point
Cmd.Printf("rebase -s %s -d %s", Top.First()->GetRev(), BranchNode.Get());
RunCmd(Cmd, LogNormal, [this, &Cmd, NewName, Top](auto r)
{
if (r.Code)
{
d->Log->Print("Error: Cmd '%s' failed. (%s:%i)\n", Cmd.Get(), _FL);
return;
}
CommitListDirty = true;
d->Log->Print("Finished rename.\n", _FL);
});
});
});
});
});
break;
}
default:
{
- LgiMsg(GetTree(), "Not impl for this VCS.", AppName);
+ LgiMsg(GetTree(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE), AppName);
break;
}
}
return true;
}
void VcFolder::GetVersion()
{
auto t = GetType();
switch (t)
{
case VcGit:
case VcSvn:
case VcHg:
case VcCvs:
StartCmd("--version", &VcFolder::ParseVersion, NULL, LogNormal);
break;
case VcPending:
break;
default:
- OnCmdError(LString(), "No version control found.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_VCS_FOUND));
break;
}
}
bool VcFolder::ParseVersion(int Result, LString s, ParseParams *Params)
{
if (Result)
return false;
auto p = s.SplitDelimit();
switch (GetType())
{
case VcGit:
{
if (p.Length() > 2)
{
ToolVersion[GetType()] = Ver2Int(p[2]);
printf("Git version: %s\n", p[2].Get());
}
else
LAssert(0);
break;
}
case VcSvn:
{
if (p.Length() > 2)
{
ToolVersion[GetType()] = Ver2Int(p[2]);
printf("Svn version: %s\n", p[2].Get());
}
else
LAssert(0);
break;
}
case VcHg:
{
if (p.Length() >= 5)
{
auto Ver = p[4].Strip("()");
ToolVersion[GetType()] = Ver2Int(Ver);
printf("Hg version: %s\n", Ver.Get());
}
break;
}
case VcCvs:
{
#ifdef _DEBUG
for (auto i : p)
printf("i='%s'\n", i.Get());
#endif
if (p.Length() > 1)
{
auto Ver = p[2];
ToolVersion[GetType()] = Ver2Int(Ver);
printf("Cvs version: %s\n", Ver.Get());
}
break;
}
default:
break;
}
return false;
}
bool VcFolder::ParseAddFile(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcCvs:
{
if (Result)
{
d->Tabs->Value(1);
- OnCmdError(s, "Add file failed");
+ OnCmdError(s, LLoadString(IDS_ERR_ADD_FAILED));
}
else ClearError();
break;
}
default:
break;
}
return false;
}
bool VcFolder::AddFile(const char *Path, bool AsBinary)
{
if (!Path)
return false;
switch (GetType())
{
case VcCvs:
{
auto p = LString(Path).RSplit(DIR_STR, 1);
ParseParams *params = NULL;
if (p.Length() >= 2)
{
if ((params = new ParseParams))
params->AltInitPath = p[0];
}
LString a;
a.Printf("add%s \"%s\"", AsBinary ? " -kb" : "", p.Length() > 1 ? p.Last().Get() : Path);
return StartCmd(a, &VcFolder::ParseAddFile, params);
break;
}
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseRevert(int Result, LString s, ParseParams *Params)
{
if (GetType() == VcSvn)
{
if (s.Find("Skipped ") >= 0)
Result = 1; // Stupid svn... *sigh*
}
if (Result)
{
- OnCmdError(s, "Error reverting changes.");
+ OnCmdError(s, LLoadString(IDS_ERR_REVERT_FAILED));
}
ListWorkingFolder();
return false;
}
bool VcFolder::Revert(LString::Array &Uris, const char *Revision)
{
if (Uris.Length() == 0)
return false;
switch (GetType())
{
case VcGit:
{
LStringPipe p;
p.Print("checkout");
for (auto u: Uris)
{
auto Path = GetFilePart(u);
p.Print(" \"%s\"", Path.Get());
}
auto a = p.NewLStr();
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
case VcHg:
case VcSvn:
{
LStringPipe p;
if (Revision)
p.Print("up -r %s", Revision);
else
p.Print("revert");
for (auto u: Uris)
{
auto Path = GetFilePart(u);
p.Print(" \"%s\"", Path.Get());
}
auto a = p.NewLStr();
return StartCmd(a, &VcFolder::ParseRevert);
break;
}
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseResolveList(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcHg:
{
auto lines = s.Replace("\r").Split("\n");
for (auto &ln: lines)
{
auto p = ln.Split(" ", 1);
if (p.Length() == 2)
{
if (p[0].Equals("U"))
{
auto f = new VcFile(d, this, LString(), true);
f->SetText(p[0], COL_STATE);
f->SetText(p[1], COL_FILENAME);
f->GetStatus();
d->Files->Insert(f);
}
}
}
break;
}
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::ParseResolve(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
break;
}
case VcHg:
{
d->Log->Print("Resolve: %s\n", s.Get());
break;
}
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::Resolve(const char *Path, LvcResolve Type)
{
if (!Path)
return false;
switch (GetType())
{
case VcGit:
{
LString a;
a.Printf("add \"%s\"", Path);
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
}
case VcHg:
{
LString a;
auto local = GetFilePart(Path);
switch (Type)
{
case ResolveMark:
a.Printf("resolve -m \"%s\"", local.Get());
break;
case ResolveUnmark:
a.Printf("resolve -u \"%s\"", local.Get());
break;
case ResolveLocal:
a.Printf("resolve -t internal:local \"%s\"", local.Get());
break;
case ResolveIncoming:
a.Printf("resolve -t internal:other \"%s\"", local.Get());
break;
default:
break;
}
if (a)
return StartCmd(a, &VcFolder::ParseResolve, new ParseParams(Path));
break;
}
case VcSvn:
case VcCvs:
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return false;
}
bool VcFolder::ParseBlame(int Result, LString s, ParseParams *Params)
{
new BlameUi(d, GetType(), s);
return false;
}
bool VcFolder::Blame(const char *Path)
{
if (!Path)
return false;
LUri u(Path);
switch (GetType())
{
case VcGit:
{
LString a;
a.Printf("-P blame \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcHg:
{
LString a;
a.Printf("annotate -un \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
case VcSvn:
{
LString a;
a.Printf("blame \"%s\"", u.sPath.Get());
return StartCmd(a, &VcFolder::ParseBlame);
break;
}
default:
{
- LAssert(!"Impl me.");
+ OnCmdError(LString(), LLoadString(IDS_ERR_NO_IMPL_FOR_TYPE));
break;
}
}
return true;
}
bool VcFolder::SaveFileAs(const char *Path, const char *Revision)
{
if (!Path || !Revision)
return false;
return true;
}
bool VcFolder::ParseSaveAs(int Result, LString s, ParseParams *Params)
{
return false;
}
bool VcFolder::ParseCounts(int Result, LString s, ParseParams *Params)
{
switch (GetType())
{
case VcGit:
{
Unpushed = (int) s.Strip().Split("\n").Length();
break;
}
case VcSvn:
{
int64 ServerRev = 0;
bool HasUpdate = false;
LString::Array c = s.Split("\n");
for (unsigned i=0; i 1 && a[0].Equals("Status"))
ServerRev = a.Last().Int();
else if (a[0].Equals("*"))
HasUpdate = true;
}
if (ServerRev > 0 && HasUpdate)
{
int64 CurRev = CurrentCommit.Int();
Unpulled = (int) (ServerRev - CurRev);
}
else Unpulled = 0;
Update();
break;
}
default:
{
LAssert(!"Impl me.");
break;
}
}
IsUpdatingCounts = false;
Update();
return false; // No re-select
}
void VcFolder::SetEol(const char *Path, int Type)
{
if (!Path) return;
switch (Type)
{
case IDM_EOL_LF:
{
ConvertEol(Path, false);
break;
}
case IDM_EOL_CRLF:
{
ConvertEol(Path, true);
break;
}
case IDM_EOL_AUTO:
{
#ifdef WINDOWS
ConvertEol(Path, true);
#else
ConvertEol(Path, false);
#endif
break;
}
}
}
void VcFolder::UncommitedItem::Select(bool b)
{
LListItem::Select(b);
if (b)
{
LTreeItem *i = d->Tree->Selection();
VcFolder *f = dynamic_cast(i);
if (f)
f->ListWorkingFolder();
if (d->Msg)
{
d->Msg->Name(NULL);
auto *w = d->Msg->GetWindow();
if (w)
{
w->SetCtrlEnabled(IDC_COMMIT, true);
w->SetCtrlEnabled(IDC_COMMIT_AND_PUSH, true);
}
}
}
}
void VcFolder::UncommitedItem::OnPaint(LItem::ItemPaintCtx &Ctx)
{
LFont *f = GetList()->GetFont();
f->Transparent(false);
f->Colour(Ctx.Fore, Ctx.Back);
LDisplayString ds(f, "(working folder)");
ds.Draw(Ctx.pDC, Ctx.x1 + ((Ctx.X() - ds.X()) / 2), Ctx.y1 + ((Ctx.Y() - ds.Y()) / 2), &Ctx);
}
//////////////////////////////////////////////////////////////////////////////////////////
VcLeaf::VcLeaf(VcFolder *parent, LTreeItem *Item, LString uri, LString leaf, bool folder)
{
Parent = parent;
d = Parent->GetPriv();
LAssert(uri.Find("://") >= 0); // Is URI
Uri.Set(uri);
LAssert(Uri);
Leaf = leaf;
Folder = folder;
Tmp = NULL;
Item->Insert(this);
if (Folder)
{
Insert(Tmp = new LTreeItem);
Tmp->SetText("Loading...");
}
}
VcLeaf::~VcLeaf()
{
Log.DeleteObjects();
}
LString VcLeaf::Full()
{
LUri u = Uri;
u += Leaf;
return u.ToString();
}
void VcLeaf::OnBrowse()
{
auto full = Full();
LList *Files = d->Files;
Files->Empty();
LDirectory Dir;
for (int b = Dir.First(full); b; b = Dir.Next())
{
if (Dir.IsDir())
continue;
VcFile *f = new VcFile(d, Parent, LString(), true);
if (f)
{
f->SetUri(LString("file://") + full);
f->SetText(Dir.GetName(), COL_FILENAME);
Files->Insert(f);
}
}
Files->ResizeColumnsToContent();
if (Folder)
Parent->FolderStatus(full, this);
}
void VcLeaf::AfterBrowse()
{
}
VcLeaf *VcLeaf::FindLeaf(const char *Path, bool OpenTree)
{
if (!Stricmp(Path, Full().Get()))
return this;
if (OpenTree)
DoExpand();
VcLeaf *r = NULL;
for (auto n = GetChild(); !r && n; n = n->GetNext())
{
auto l = dynamic_cast(n);
if (l)
r = l->FindLeaf(Path, OpenTree);
}
return r;
}
void VcLeaf::DoExpand()
{
if (Tmp)
{
Tmp->Remove();
DeleteObj(Tmp);
Parent->ReadDir(this, Full());
}
}
void VcLeaf::OnExpand(bool b)
{
if (b)
DoExpand();
}
const char *VcLeaf::GetText(int Col)
{
if (Col == 0)
return Leaf;
return NULL;
}
int VcLeaf::GetImage(int Flags)
{
return Folder ? IcoFolder : IcoFile;
}
int VcLeaf::Compare(VcLeaf *b)
{
// Sort folders to the top...
if (Folder ^ b->Folder)
return (int)b->Folder - (int)Folder;
// Then alphabetical
return Stricmp(Leaf.Get(), b->Leaf.Get());
}
bool VcLeaf::Select()
{
return LTreeItem::Select();
}
void VcLeaf::Select(bool b)
{
LTreeItem::Select(b);
if (b)
{
d->Commits->RemoveAll();
OnBrowse();
ShowLog();
}
}
void VcLeaf::ShowLog()
{
if (Log.Length())
{
d->Commits->RemoveAll();
Parent->DefaultFields();
Parent->UpdateColumns();
for (auto i: Log)
d->Commits->Insert(i);
}
}
void VcLeaf::OnMouseClick(LMouse &m)
{
if (m.IsContextMenu())
{
LSubMenu s;
s.AppendItem("Log", IDM_LOG);
s.AppendItem("Blame", IDM_BLAME, !Folder);
s.AppendSeparator();
s.AppendItem("Browse To", IDM_BROWSE_FOLDER);
s.AppendItem("Terminal At", IDM_TERMINAL);
int Cmd = s.Float(GetTree(), m - _ScrollPos());
switch (Cmd)
{
case IDM_LOG:
{
Parent->LogFile(Full());
break;
}
case IDM_BLAME:
{
Parent->Blame(Full());
break;
}
case IDM_BROWSE_FOLDER:
{
LBrowseToFile(Full());
break;
}
case IDM_TERMINAL:
{
TerminalAt(Full());
break;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
ProcessCallback::ProcessCallback(LString exe,
LString args,
LString localPath,
LTextLog *log,
LView *view,
std::function callback) :
Log(log),
View(view),
Callback(callback),
LThread("ProcessCallback.Thread"),
LSubProcess(exe, args)
{
SetInitFolder(localPath);
if (Log)
Log->Print("%s %s\n", exe.Get(), args.Get());
Run();
}
int ProcessCallback::Main()
{
if (!Start())
{
Ret.Out.Printf("Process failed with %i", GetErrorCode());
Callback(Ret);
}
else
{
while (IsRunning())
{
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
}
auto Rd = Read();
if (Rd.Length())
{
Ret.Out += Rd;
if (Log)
Log->Write(Rd.Get(), Rd.Length());
}
Ret.Code = GetExitValue();
}
View->PostEvent(M_HANDLE_CALLBACK, (LMessage::Param)this);
return 0;
}
void ProcessCallback::OnComplete() // Called in the GUI thread...
{
Callback(Ret);
}
diff --git a/ResourceEditor/src/LgiResApp.cpp b/ResourceEditor/src/LgiResApp.cpp
--- a/ResourceEditor/src/LgiResApp.cpp
+++ b/ResourceEditor/src/LgiResApp.cpp
@@ -1,4725 +1,4725 @@
/*
** FILE: LgiRes.cpp
** AUTHOR: Matthew Allen
** DATE: 3/8/99
** DESCRIPTION: Lgi Resource Editor
**
** Copyright (C) 1999, Matthew Allen
** fret@memecode.com
*/
#include
#include "LgiResEdit.h"
#include "LgiRes_Dialog.h"
#include "LgiRes_Menu.h"
#include "lgi/common/About.h"
#include "lgi/common/TextLabel.h"
#include "lgi/common/Edit.h"
#include "lgi/common/CheckBox.h"
#include "lgi/common/ProgressDlg.h"
#include "lgi/common/TextView3.h"
#include "lgi/common/Token.h"
#include "lgi/common/DataDlg.h"
#include "lgi/common/Button.h"
#include "lgi/common/Menu.h"
#include "lgi/common/StatusBar.h"
#include "resdefs.h"
char AppName[] = "Lgi Resource Editor";
char HelpFile[] = "Help.html";
char OptionsFileName[] = "Options.r";
char TranslationStrMagic[] = "LgiRes.String";
#define VIEW_PULSE_RATE 100
#ifndef DIALOG_X
#define DIALOG_X 1.56
#define DIALOG_Y 1.85
#define CTRL_X 1.50
#define CTRL_Y 1.64
#endif
enum Ctrls
{
IDC_HBOX = 100,
IDC_VBOX,
};
const char *TypeNames[] = {
"",
"Css",
"Dialog",
"String",
"Menu",
0};
//////////////////////////////////////////////////////////////////////////////
ResFileFormat GetFormat(const char *File)
{
ResFileFormat Format = Lr8File;
char *Ext = LGetExtension(File);
if (Ext)
{
if (stricmp(Ext, "lr") == 0) Format = CodepageFile;
else if (stricmp(Ext, "xml") == 0) Format = XmlFile;
}
return Format;
}
char *EncodeXml(const char *Str, int Len)
{
char *Ret = 0;
if (Str)
{
LStringPipe p;
const char *s = Str;
for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); )
{
switch (*e)
{
case '<':
{
p.Push(s, e-s);
p.Push("<");
s = ++e;
break;
}
case '>':
{
p.Push(s, e-s);
p.Push(">");
s = ++e;
break;
}
case '&':
{
p.Push(s, e-s);
p.Push("&");
s = ++e;
break;
}
case '\\':
{
if (e[1] == 'n')
{
// Newline
p.Push(s, e-s);
p.Push("\n");
s = (e += 2);
break;
}
// fall thru
}
case '\'':
case '\"':
case '/':
{
// Convert to entity
p.Push(s, e-s);
char b[32];
snprintf(b, sizeof(b), "%i;", *e);
p.Push(b);
s = ++e;
break;
}
default:
{
// Regular character
e++;
break;
}
}
}
p.Push(s);
Ret = p.NewStr();
}
return Ret;
}
char *DecodeXml(const char *Str, int Len)
{
if (Str)
{
LStringPipe p;
const char *s = Str;
for (const char *e = Str; e && *e && (Len < 0 || ((e-Str) < Len)); )
{
switch (*e)
{
case '&':
{
// Store string up to here
p.Push(s, e-s);
e++;
if (*e == '#')
{
// Numerical
e++;
if (*e == 'x' || *e == 'X')
{
// Hex
e++;
char16 c = htoi(e);
char *c8 = WideToUtf8(&c, 1);
if (c8)
{
p.Push(c8);
DeleteArray(c8);
}
}
else if (isdigit(*e))
{
// Decimal
char16 c = atoi(e);
char *c8 = WideToUtf8(&c, 1);
if (c8)
{
p.Push(c8);
DeleteArray(c8);
}
}
else
{
LAssert(0);
}
while (*e && *e != ';') e++;
}
else if (isalpha(*e))
{
// named entity
const char *Name = e;
while (*e && *e != ';') e++;
auto Len = e - Name;
if (Len == 3 && strnicmp(Name, "amp", Len) == 0)
{
p.Push("&");
}
else if (Len == 2 && strnicmp(Name, "gt", Len) == 0)
{
p.Push(">");
}
else if (Len == 2 && strnicmp(Name, "lt", Len) == 0)
{
p.Push("<");
}
else
{
// Unsupported entity
LAssert(0);
}
}
else
{
LAssert(0);
while (*e && *e != ';') e++;
}
s = ++e;
break;
}
case '\n':
{
p.Push(s, e-s);
p.Push("\\n");
s = ++e;
break;
}
default:
{
e++;
break;
}
}
}
p.Push(s);
return p.NewStr();
}
return 0;
}
//////////////////////////////////////////////////////////////////////////////
Resource::Resource(AppWnd *w, int t, bool enabled)
{
AppWindow = w;
ResType = t;
Item = 0;
SysObject = false;
LAssert(AppWindow);
}
Resource::~Resource()
{
AppWindow->OnResourceDelete(this);
if (Item)
{
Item->Obj = 0;
DeleteObj(Item);
}
}
bool Resource::IsSelected()
{
return Item?Item->Select():false;
}
bool Resource::Attach(LViewI *Parent)
{
LView *w = Wnd();
if (w)
{
return w->Attach(Parent);
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
ResFolder::ResFolder(AppWnd *w, int t, bool enabled) :
Resource(w, t, enabled)
{
Wnd()->Name("");
Wnd()->Enabled(enabled);
}
//////////////////////////////////////////////////////////////////////////////
ObjTreeItem::ObjTreeItem(Resource *Object)
{
if ((Obj = Object))
{
Obj->Item = this;
if (dynamic_cast(Object))
SetImage(ICON_FOLDER);
else
{
int t = Object->Type();
switch (t)
{
case TYPE_CSS: SetImage(ICON_CSS); break;
case TYPE_DIALOG: SetImage(ICON_DIALOG); break;
case TYPE_STRING: SetImage(ICON_STRING); break;
case TYPE_MENU: SetImage(ICON_MENU); break;
}
}
}
}
ObjTreeItem::~ObjTreeItem()
{
if (Obj)
{
Obj->Item = 0;
DeleteObj(Obj);
}
}
const char *ObjTreeItem::GetText(int i)
{
if (Obj)
{
int Type = Obj->Type();
if (Type > 0)
return Obj->Wnd()->Name();
else
return TypeNames[-Type];
}
return "#NO_OBJ";
}
void ObjTreeItem::OnSelect()
{
if (Obj)
{
Obj->App()->OnResourceSelect(Obj);
}
}
void ObjTreeItem::OnMouseClick(LMouse &m)
{
if (!Obj) return;
if (m.IsContextMenu())
{
Tree->Select(this);
LSubMenu RClick;
if (Obj->Wnd()->Enabled())
{
if (Obj->Type() > 0)
{
// Resource
RClick.AppendItem("Delete", IDM_DELETE, !Obj->SystemObject());
RClick.AppendItem("Rename", IDM_RENAME, !Obj->SystemObject());
}
else
{
// Folder
RClick.AppendItem("New", IDM_NEW, true);
RClick.AppendSeparator();
auto Insert = RClick.AppendSub("Import from...");
if (Insert)
{
Insert->AppendItem("Lgi File", IDM_IMPORT, true);
Insert->AppendItem("Win32 Resource Script", IDM_IMPORT_WIN32, false);
}
}
// Custom entries
if (!Obj->SystemObject())
{
Obj->OnRightClick(&RClick);
}
}
else
{
RClick.AppendItem("Not implemented", 0, false);
}
if (Tree->GetMouse(m, true))
{
int Cmd = 0;
switch (Cmd = RClick.Float(Tree, m.x, m.y))
{
case IDM_NEW:
{
SerialiseContext Ctx;
Obj->App()->NewObject(Ctx, 0, -Obj->Type());
break;
}
case IDM_DELETE:
{
Obj->App()->SetDirty(true, [this](auto ok)
{
if (ok)
Obj->App()->DelObject(Obj);
});
break;
}
case IDM_RENAME:
{
auto Dlg = new LInput(Tree, GetText(), "Enter the name for the object", "Object Name");
- Dlg->DoModal([&](auto dlg, auto id)
+ Dlg->DoModal([this, Dlg](auto dlg, auto id)
{
if (id)
{
Obj->Wnd()->Name(Dlg->GetStr());
Update();
Obj->App()->SetDirty(true, NULL);
}
delete dlg;
});
break;
}
case IDM_IMPORT:
{
auto Select = new LFileSelect(Obj->App());
Select->Type("Text", "*.txt");
Select->Open([&](auto dlg, auto status)
{
if (status)
{
LFile F;
if (F.Open(dlg->Name(), O_READ))
{
SerialiseContext Ctx;
Resource *Res = Obj->App()->NewObject(Ctx, 0, -Obj->Type());
if (Res)
{
// TODO
// Res->Read();
}
}
else
{
LgiMsg(Obj->App(), "Couldn't open file for reading.");
}
}
delete dlg;
});
break;
}
case IDM_IMPORT_WIN32:
{
/*
List l;
if (ImportWin32Dialogs(l, MainWnd))
{
for (ResDialog *r = l.First(); r; r = l.Next())
{
Obj->App()->InsertObject(TYPE_DIALOG, r);
}
}
*/
break;
}
default:
{
Obj->OnCommand(Cmd);
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////////
FieldView::FieldView(AppWnd *app) : Fields(NextId, true)
{
NextId = 100;
App = app;
Source = 0;
Ignore = true;
SetTabStop(true);
Sunken(true);
#ifdef WIN32
SetExStyle(GetExStyle() | WS_EX_CONTROLPARENT);
#endif
}
FieldView::~FieldView()
{
}
void FieldView::Serialize(bool Write)
{
if (!Source)
return;
Ignore = !Write;
Fields.SetMode(Write ? FieldTree::UiToObj : FieldTree::ObjToUi);
Fields.SetView(this);
Source->Serialize(Fields);
/*
for (DataDlgField *f=Fields.First(); f; f=Fields.Next())
{
LViewI *v;
if (GetViewById(f->GetCtrl(), v))
{
switch (f->GetType())
{
case DATA_STR:
{
if (Write) // Ctrl -> Options
{
char *s = v->Name();
Options->Set(f->GetOption(), s);
}
else // Options -> Ctrl
{
char *s = 0;
Options->Get(f->GetOption(), s);
v->Name(s?s:(char*)"");
}
break;
}
case DATA_BOOL:
case DATA_INT:
{
if (Write) // Ctrl -> Options
{
char *s = v->Name();
if (s && (s = strchr(s, '\'')))
{
s++;
char *e = strchr(s, '\'');
int i = 0;
if (e - s == 4)
{
memcpy(&i, s, 4);
i = LgiSwap32(i);
Options->Set(f->GetOption(), i);
}
}
else
{
int i = v->Value();
Options->Set(f->GetOption(), i);
}
}
else // Options -> Ctrl
{
int i = 0;
Options->Get(f->GetOption(), i);
if (i != -1 && (i & 0xff000000) != 0)
{
char m[8];
i = LgiSwap32(i);
sprintf(m, "'%04.4s'", &i);
v->Name(m);
}
else
{
v->Value(i);
}
}
break;
}
case DATA_FLOAT:
case DATA_PASSWORD:
case DATA_STR_SYSTEM:
default:
{
LAssert(0);
break;
}
}
}
else LAssert(0);
}
*/
Ignore = false;
}
class TextViewEdit : public LTextView3
{
public:
bool Multiline;
TextViewEdit( int Id,
int x,
int y,
int cx,
int cy,
LFontType *FontInfo = 0) :
LTextView3(Id, x, y, cx, cy, FontInfo)
{
Multiline = false;
#ifdef WIN32
SetDlgCode(DLGC_WANTARROWS | DLGC_WANTCHARS);
#endif
}
bool OnKey(LKey &k)
{
if (!Multiline && (k.c16 == '\t' || k.c16 == LK_RETURN))
{
return false;
}
return LTextView3::OnKey(k);
}
};
class Hr : public LView
{
public:
Hr(int x1, int y, int x2)
{
LRect r(x1, y, x2, y+1);
SetPos(r);
}
void OnPaint(LSurface *pDC)
{
LRect c = GetClient();
LThinBorder(pDC, c, DefaultSunkenEdge);
}
bool OnLayout(LViewLayoutInfo &Inf)
{
if (Inf.Width.Min)
Inf.Height.Min = Inf.Height.Max = 2;
else
Inf.Width.Min = Inf.Width.Max = -1;
return true;
}
};
void FieldView::OnDelete(FieldSource *s)
{
if (Source != NULL && Source == s)
{
// Clear fields
Source->_FieldView = 0;
Fields.Empty();
// remove all children
LViewI *c;
while ((c = Children[0]))
{
c->Detach();
DeleteObj(c);
}
Source = NULL;
}
}
void FieldView::OnSelect(FieldSource *s)
{
Ignore = true;
OnDelete(Source);
if (Source)
{
// Clear fields
Source->_FieldView = 0;
Fields.Empty();
// remove all children
LViewI *c;
while ((c = Children[0]))
{
c->Detach();
DeleteObj(c);
}
Source = 0;
}
if (s)
{
// Add new fields
Source = s;
Source->_FieldView = AddDispatch();
if (Source->GetFields(Fields))
{
LFontType Sys;
Sys.GetSystemFont("System");
LTableLayout *t = new LTableLayout(IDC_TABLE);
int Row = 0;
LLayoutCell *Cell;
LArray a;
Fields.GetAll(a);
for (int i=0; iLength(); n++, Row++)
{
FieldTree::Field *c = (*b)[n];
switch (c->Type)
{
case DATA_STR:
case DATA_FLOAT:
case DATA_INT:
case DATA_FILENAME:
{
Cell = t->GetCell(0, Row);
Cell->VerticalAlign(LCss::VerticalMiddle);
Cell->Add(new LTextLabel(-1, 0, 0, -1, -1, c->Label));
TextViewEdit *Tv;
Cell = t->GetCell(1, Row, true, c->Type == DATA_FILENAME ? 1 : 2);
Cell->Add(Tv = new TextViewEdit(c->Id, 0, 0, 100, 20, &Sys));
if (Tv)
{
Tv->Multiline = c->Multiline;
Tv->GetCss(true)->Height(LCss::Len(LCss::LenPx, c->Multiline ? LSysFont->GetHeight() * 8 : LSysFont->GetHeight() + 8));
Tv->SetWrapType(TEXTED_WRAP_NONE);
Tv->Sunken(true);
}
if (c->Type == DATA_FILENAME)
{
Cell = t->GetCell(2, Row);
Cell->Add(new LButton(-c->Id, 0, 0, 21, 21, "..."));
}
break;
}
case DATA_BOOL:
{
Cell = t->GetCell(1, Row, true, 2);
Cell->Add(new LCheckBox(c->Id, 0, 0, -1, -1, c->Label));
break;
}
default:
LAssert(!"Impl me.");
break;
}
}
if (i < a.Length() - 1)
{
Cell = t->GetCell(0, Row++, true, 3);
Cell->Add(new Hr(0, 0, X()-1));
}
}
AddView(t);
OnPosChange();
AttachChildren();
Invalidate();
}
Serialize(false);
Ignore = false;
}
}
void FieldView::OnPosChange()
{
LRect c = GetClient();
c.Inset(6, 6);
LViewI *v;
if (GetViewById(IDC_TABLE, v))
v->SetPos(c);
}
LMessage::Result FieldView::OnEvent(LMessage *m)
{
switch (m->Msg())
{
case M_OBJECT_CHANGED:
{
FieldSource *Src = (FieldSource*)m->A();
if (Src == Source)
{
Fields.SetMode(FieldTree::ObjToUi);
Fields.SetView(this);
Serialize(false);
}
else LAssert(0);
break;
}
}
return LLayout::OnEvent(m);
}
int FieldView::OnNotify(LViewI *Ctrl, LNotification n)
{
if (!Ignore)
{
LTextView3 *Tv = dynamic_cast(Ctrl);
if (Tv && n.Type == LNotifyCursorChanged)
{
return 0;
}
LArray a;
Fields.GetAll(a);
for (int i=0; iLength(); n++)
{
FieldTree::Field *c = (*b)[n];
if (c->Id == Ctrl->GetId())
{
// Write the value back to the objects
Fields.SetMode(FieldTree::UiToObj);
Fields.SetView(this);
Source->Serialize(Fields);
return 0;
}
else if (c->Id == -Ctrl->GetId())
{
auto s = new LFileSelect(this);
s->Open([&](auto dlg, auto status)
{
if (status)
{
auto File = App->GetCurFile();
if (File)
{
LFile::Path p = File;
p--;
auto Rel = LMakeRelativePath(p, dlg->Name());
if (Rel)
SetCtrlName(c->Id, Rel);
else
SetCtrlName(c->Id, dlg->Name());
}
else SetCtrlName(c->Id, dlg->Name());
Fields.SetMode(FieldTree::UiToObj);
Fields.SetView(this);
Source->Serialize(Fields);
}
delete dlg;
});
}
}
}
}
return 0;
}
void FieldView::OnPaint(LSurface *pDC)
{
pDC->Colour(L_MED);
pDC->Rectangle();
}
//////////////////////////////////////////////////////////////////////////////
ObjContainer::ObjContainer(AppWnd *w) :
LTree(100, 0, 0, 100, 100, "LgiResObjTree")
{
Window = w;
Sunken(true);
Insert(Style = new ObjTreeItem( new ResFolder(Window, -TYPE_CSS)));
Insert(Dialogs = new ObjTreeItem( new ResFolder(Window, -TYPE_DIALOG)));
Insert(Strings = new ObjTreeItem( new ResFolder(Window, -TYPE_STRING)));
Insert(Menus = new ObjTreeItem( new ResFolder(Window, -TYPE_MENU)));
const char *IconFile = "_icons.gif";
auto f = LFindFile(IconFile);
LAssert(f);
if (f)
{
Images = LLoadImageList(f, 16, 16);
LAssert(Images);
if (Images)
SetImageList(Images, false);
else
LgiTrace("%s:%i - failed to load '%s'\n", _FL, IconFile);
}
}
ObjContainer::~ObjContainer()
{
DeleteObj(Images);
}
bool ObjContainer::AppendChildren(ObjTreeItem *Res, List &Lst)
{
bool Status = true;
if (Res)
{
LTreeItem *Item = Res->GetChild();
while (Item)
{
ObjTreeItem *i = dynamic_cast(Item);
if (i) Lst.Insert(i->GetObj());
else Status = false;
Item = Item->GetNext();
}
}
return Status;
}
Resource *ObjContainer::CurrentResource()
{
ObjTreeItem *Item = dynamic_cast(Selection());
if (!Item)
return NULL;
return Item->GetObj();
}
bool ObjContainer::ListObjects(List &Lst)
{
bool Status = AppendChildren(Style, Lst);
Status &= AppendChildren(Dialogs, Lst);
Status &= AppendChildren(Strings, Lst);
Status &= AppendChildren(Menus, Lst);
return Status;
}
//////////////////////////////////////////////////////////////////////////////
#ifdef WIN32
int Icon = IDI_ICON1;
#else
const char *Icon = "icon64.png";
#endif
AppWnd::AppWnd() :
LDocApp(AppName, Icon)
{
ShowLanguages.Add("en", true);
SetQuitOnClose(true);
if (_Create())
{
LVariant Langs;
if (GetOptions()->GetValue(OPT_ShowLanguages, Langs))
{
ShowLanguages.Empty();
LToken L(Langs.Str(), ",");
for (int i=0; iEmpty();
_Destroy();
}
void AppWnd::OnCreate()
{
if (_LoadMenu("IDM_MENU"))
{
if (_FileMenu)
{
int n = 6;
_FileMenu->AppendSeparator(n++);
_FileMenu->AppendItem("Import Win32 Script", IDM_IMPORT_WIN32, true, n++);
_FileMenu->AppendItem("Import LgiRes Language", IDM_IMPORT_LANG, true, n++);
_FileMenu->AppendItem("Compare To File...", IDM_COMPARE, true, n++);
_FileMenu->AppendSeparator(n++);
_FileMenu->AppendItem("Properties", IDM_PROPERTIES, true, n++);
}
ViewMenu = Menu->FindSubMenu(IDM_VIEW);
LAssert(ViewMenu);
}
else LgiTrace("%s:%i - _LoadMenu failed.\n", _FL);
Status = 0;
StatusInfo[0] = StatusInfo[1] = 0;
HBox = new LBox(IDC_HBOX);
if (HBox)
{
HBox->GetCss(true)->Padding("5px");
VBox = new LBox(IDC_VBOX, true);
if (VBox)
{
HBox->AddView(VBox);
VBox->AddView(Objs = new ObjContainer(this));
if (Objs)
{
Objs->AskImage(true);
Objs->AskText(true);
}
VBox->AddView(Fields = new FieldView(this));
VBox->Value(200);
}
HBox->Value(240);
HBox->Attach(this);
}
DropTarget(true);
LString Open;
if (LAppInst->GetOption("o", Open))
LoadLgi(Open);
}
void AppWnd::OnLanguagesChange(LLanguageId Lang, bool Add, bool Update)
{
bool Change = false;
if (Lang)
{
// Update the list....
bool Has = false;
for (int i=0; iId, Lang) == 0)
{
Has = true;
if (!Add)
{
Languages.DeleteAt(i);
Change = true;
}
break;
}
}
if (Add && !Has)
{
Change = true;
Languages.Add(LFindLang(Lang));
}
}
// Update the menu...
if (ViewMenu && (Change || Update))
{
// Remove existing language menu items
while (ViewMenu->RemoveItem(2));
// Add new ones
int n = 0;
for (int i=0; iAppendItem(Lang->Name, IDM_LANG_BASE + n, true);
if (Item)
{
if (CurLang == i)
{
Item->Checked(true);
}
}
}
}
}
}
bool AppWnd::ShowLang(LLanguageId Lang)
{
return ShowLanguages.Find(Lang) != 0;
}
void AppWnd::ShowLang(LLanguageId Lang, bool Show)
{
// Apply change
if (Show)
{
OnLanguagesChange(Lang, true);
ShowLanguages.Add(Lang, true);
}
else
{
ShowLanguages.Delete(Lang);
}
// Store the setting for next time
LStringPipe p;
// const char *L;
// for (bool i = ShowLanguages.First(&L); i; i = ShowLanguages.Next(&L))
for (auto i : ShowLanguages)
{
if (p.GetSize()) p.Push(",");
p.Push(i.key);
}
char *Langs = p.NewStr();
if (Langs)
{
LVariant v;
GetOptions()->SetValue(OPT_ShowLanguages, v = Langs);
DeleteArray(Langs);
}
// Update everything
List res;
if (ListObjects(res))
{
for (auto r: res)
{
r->OnShowLanguages();
}
}
}
LLanguage *AppWnd::GetCurLang()
{
if (CurLang >= 0 && CurLang < Languages.Length())
return Languages[CurLang];
return LFindLang("en");
}
void AppWnd::SetCurLang(LLanguage *L)
{
for (int i=0; iId == L->Id)
{
// Set new current
CurLang = i;
// Update everything
List res;
if (ListObjects(res))
{
for (auto r: res)
{
r->OnShowLanguages();
}
}
break;
}
}
}
LArray *AppWnd::GetLanguages()
{
return &Languages;
}
class Test : public LView
{
COLOUR c;
public:
Test(COLOUR col, int x1, int y1, int x2, int y2)
{
c = col;
LRect r(x1, y1, x2, y2);
SetPos(r);
_BorderSize = 1;
Sunken(true);
}
void OnPaint(LSurface *pDC)
{
pDC->Colour(c, 24);
pDC->Rectangle();
}
};
LMessage::Result AppWnd::OnEvent(LMessage *m)
{
switch (m->Msg())
{
case M_CHANGE:
{
LAutoPtr note((LNotification*)m->B());
return OnNotify((LViewI*) m->A(), *note);
}
case M_DESCRIBE:
{
char *Text = (char*) m->A();
if (Text)
{
SetStatusText(Text, STATUS_NORMAL);
}
break;
}
}
return LDocApp::OnEvent(m);
}
void _CountGroup(ResStringGroup *Grp, int &Words, int &Multi)
{
for (auto s: *Grp->GetStrs())
{
if (s->Items.Length() > 1)
{
Multi++;
char *e = s->Get("en");
if (e)
{
LToken t(e, " ");
Words += t.Length();
}
}
}
}
int AppWnd::OnCommand(int Cmd, int Event, OsView Handle)
{
SerialiseContext Ctx;
switch (Cmd)
{
case IDM_SHOW_LANG:
{
auto Dlg = new ShowLanguagesDlg(this);
Dlg->DoModal([](auto dlg, auto ctrlId)
{
delete dlg;
});
break;
}
case IDM_NEW_CSS:
{
NewObject(Ctx, 0, TYPE_CSS);
break;
}
case IDM_NEW_DIALOG:
{
NewObject(Ctx, 0, TYPE_DIALOG);
break;
}
case IDM_NEW_STRING_GRP:
{
NewObject(Ctx, 0, TYPE_STRING);
break;
}
case IDM_NEW_MENU:
{
NewObject(Ctx, 0, TYPE_MENU);
break;
}
case IDM_CLOSE:
{
Empty();
break;
}
case IDM_IMPORT_WIN32:
{
LoadWin32();
break;
}
case IDM_IMPORT_LANG:
{
ImportLang();
break;
}
case IDM_COMPARE:
{
Compare();
break;
}
case IDM_PROPERTIES:
{
List l;
if (Objs->ListObjects(l))
{
int Dialogs = 0;
int Strings = 0;
int Menus = 0;
int Words = 0;
int MultiLingual = 0;
for (auto r: l)
{
switch (r->Type())
{
case TYPE_DIALOG:
{
Dialogs++;
break;
}
case TYPE_STRING:
{
ResStringGroup *Grp = dynamic_cast(r);
if (Grp)
{
Strings += Grp->GetStrs()->Length();
_CountGroup(Grp, Words, MultiLingual);
}
break;
}
case TYPE_MENU:
{
Menus++;
ResMenu *Menu = dynamic_cast(r);
if (Menu)
{
if (Menu->Group)
{
Strings += Menu->Group->GetStrs()->Length();
_CountGroup(Menu->Group, Words, MultiLingual);
}
}
break;
}
}
}
LgiMsg( this,
"This file contains:\n"
"\n"
" Dialogs: %i\n"
" Menus: %i\n"
" Strings: %i\n"
" Multi-lingual: %i\n"
" Words: %i",
AppName,
MB_OK,
Dialogs,
Menus,
Strings,
MultiLingual,
Words);
}
break;
}
case IDM_EXIT:
{
LCloseApp();
break;
}
case IDM_FIND:
{
auto s = new Search(this);
s->DoModal([&](auto dlg, auto id)
{
if (id)
new Results(this, s);
delete dlg;
});
break;
}
case IDM_NEXT:
{
LgiMsg(this, "Not implemented :(", AppName);
break;
}
case IDM_CUT:
{
auto Focus = LAppInst->GetFocus();
if (Focus)
{
Focus->PostEvent(M_CUT);
}
else
{
Resource *r = Objs->CurrentResource();
if (r)
r->Copy(true);
}
break;
}
case IDM_COPY:
{
auto Focus = LAppInst->GetFocus();
if (Focus)
{
Focus->PostEvent(M_COPY);
}
else
{
Resource *r = Objs->CurrentResource();
if (r)
r->Copy(false);
}
break;
}
case IDM_PASTE:
{
auto Focus = LAppInst->GetFocus();
if (Focus)
{
Focus->PostEvent(M_PASTE);
}
else
{
Resource *r = Objs->CurrentResource();
if (r)
r->Paste();
}
break;
}
case IDM_TABLELAYOUT_TEST:
{
OpenTableLayoutTest(this);
break;
}
case IDM_HELP:
{
char ExeName[MAX_PATH_LEN];
sprintf_s(ExeName, sizeof(ExeName), "%s", LGetExePath().Get());
while (strchr(ExeName, DIR_CHAR) && strlen(ExeName) > 3)
{
char p[256];
LMakePath(p, sizeof(p), ExeName, "index.html");
if (!LFileExists(p))
{
LMakePath(p, sizeof(p), ExeName, "help");
LMakePath(p, sizeof(p), p, "index.html");
}
if (LFileExists(p))
{
LExecute(HelpFile, NULL, ExeName);
break;
}
LTrimDir(ExeName);
}
break;
}
case IDM_SHOW_SHORTCUTS:
{
if (!ShortCuts)
{
ShortCuts = new ShortCutView(this);
}
if (ShortCuts)
{
auto res = Objs->CurrentResource();
if (res)
ShortCuts->OnResource(res);
}
break;
}
case IDM_ABOUT:
{
LAbout Dlg( this,
AppName,
APP_VER,
"\nLgi Resource Editor (lr8 files).",
"icon64.png",
"http://www.memecode.com/lgi/res",
"fret@memecode.com");
break;
}
default:
{
int Idx = Cmd - IDM_LANG_BASE;
if (Idx >= 0 && Idx < Languages.Length())
{
// Deselect the old lang
auto Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0;
if (Item)
{
Item->Checked(false);
}
// Set the current
CurLang = Idx;
// Set the new lang's menu item
Item = ViewMenu ? ViewMenu->ItemAt(CurLang + 2) : 0;
if (Item)
{
Item->Checked(true);
}
// Update everything
List res;
if (ListObjects(res))
{
for (auto r: res)
{
r->OnShowLanguages();
}
}
}
break;
}
}
return LDocApp::OnCommand(Cmd, Event, Handle);
}
int AppWnd::OnNotify(LViewI *Ctrl, LNotification n)
{
switch (Ctrl->GetId())
{
default:
{
break;
}
}
return 0;
}
void AppWnd::FindStrings(List &Strs, char *Define, int *CtrlId)
{
if (Objs)
{
List l;
if (Objs->ListObjects(l))
{
for (auto r: l)
{
StringList *s = r->GetStrs();
if (s)
{
for (auto Str: *s)
{
if (Define && ValidStr(Str->GetDefine()))
{
if (strcmp(Define, Str->GetDefine()) == 0)
{
Strs.Insert(Str);
continue;
}
}
if (CtrlId)
{
if (*CtrlId == Str->GetId())
{
Strs.Insert(Str);
continue;
}
}
}
}
}
}
}
}
int AppWnd::GetUniqueCtrlId()
{
int Max = 0;
if (Objs)
{
List l;
if (Objs->ListObjects(l))
{
LHashTbl, int> t;
for (auto r: l)
{
StringList *sl = r->GetStrs();
if (sl)
{
for (auto s: *sl)
{
if (s->GetId() > 0 &&
!t.Find(s->GetId()))
{
t.Add(s->GetId(), s->GetId());
}
Max = MAX(s->GetId(), Max);
}
}
}
int i = 500;
while (true)
{
if (t.Find(i))
{
i++;
}
else
{
return i;
}
}
}
}
return Max + 1;
}
int AppWnd::GetUniqueStrRef(int Start)
{
if (!Objs)
return -1;
List l;
if (!Objs->ListObjects(l))
return -1;
LHashTbl, ResString*> Map;
LArray Dupes;
for (auto r: l)
{
ResStringGroup *Grp = r->GetStringGroup();
if (Grp)
{
List::I it = Grp->GetStrs()->begin();
for (ResString *s = *it; s; s = *++it)
{
if (s->GetRef())
{
ResString *Existing = Map.Find(s->GetRef());
if (Existing)
{
// These get their ref's reset to a unique value as a side
// effect of this function...
Dupes.Add(s);
}
else
{
Map.Add(s->GetRef(), s);
}
}
else
{
// auto Idx = Grp->GetStrs()->IndexOf(s);
LAssert(!"No string ref?");
}
}
}
}
for (int i=Start; true; i++)
{
if (!Map.Find(i))
{
if (Dupes.Length())
{
ResString *s = Dupes[0];
Dupes.DeleteAt(0);
s->SetRef(i);
SetDirty(true, NULL);
}
else
{
return i;
}
}
}
return -1;
}
ResString *AppWnd::GetStrFromRef(int Ref)
{
ResString *Str = 0;
if (Objs)
{
List l;
if (Objs->ListObjects(l))
{
for (auto r: l)
{
ResStringGroup *Grp = dynamic_cast(r);
if (Grp)
{
if ((Str = Grp->FindRef(Ref)))
break;
}
}
}
}
return Str;
}
ResStringGroup *AppWnd::GetDialogSymbols()
{
if (Objs)
{
List l;
if (Objs->ListObjects(l))
{
for (auto r: l)
{
ResStringGroup *Grp = dynamic_cast(r);
if (Grp)
{
auto ObjName = Grp->Wnd()->Name();
if (ObjName && stricmp(ObjName, StrDialogSymbols) == 0)
{
return Grp;
}
}
}
}
}
return NULL;
}
void AppWnd::OnReceiveFiles(LArray &Files)
{
auto f = Files.Length() ? Files[0] : 0;
if (f)
{
_OpenFile(f, false, NULL);
}
}
void AppWnd::SetStatusText(char *Text, int Pane)
{
if (Pane >= 0 && Pane < STATUS_MAX && StatusInfo[Pane])
{
StatusInfo[Pane]->Name(Text);
}
}
Resource *AppWnd::NewObject(SerialiseContext ctx, LXmlTag *load, int Type, bool Select)
{
Resource *r = 0;
ObjTreeItem *Dir = 0;
switch (Type)
{
case TYPE_CSS:
{
r = new ResCss(this);
Dir = Objs->Style;
break;
}
case TYPE_DIALOG:
{
r = new ResDialog(this);
Dir = Objs->Dialogs;
break;
}
case TYPE_STRING:
{
r = new ResStringGroup(this);
Dir = Objs->Strings;
break;
}
case TYPE_MENU:
{
r = new ResMenu(this);
Dir = Objs->Menus;
break;
}
}
if (r)
{
ObjTreeItem *Item = new ObjTreeItem(r);
if (Item)
{
Dir->Insert(Item);
Dir->Update();
Dir->Expanded(true);
if (Select)
{
Objs->Select(Item);
}
}
r->Create(load, &ctx);
if (Item)
{
Item->Update();
}
SetDirty(true, NULL);
}
return r;
}
bool AppWnd::InsertObject(int Type, Resource *r, bool Select)
{
bool Status = false;
if (r)
{
ObjTreeItem *Dir = 0;
switch (Type)
{
case TYPE_CSS:
{
Dir = Objs->Style;
break;
}
case TYPE_DIALOG:
{
Dir = Objs->Dialogs;
break;
}
case TYPE_STRING:
{
Dir = Objs->Strings;
break;
}
case TYPE_MENU:
{
Dir = Objs->Menus;
break;
}
}
if (Dir)
{
ObjTreeItem *Item = new ObjTreeItem(r);
if (Item)
{
const char *Name = Item->GetText();
r->Item = Item;
Dir->Insert(Item, (Name && Name[0] == '_') ? 0 : -1);
Dir->Update();
Dir->Expanded(true);
if (Select)
{
Objs->Select(Item);
}
Status = true;
}
}
}
return Status;
}
void AppWnd::DelObject(Resource *r)
{
OnResourceSelect(0);
DeleteObj(r);
}
ObjTreeItem *GetTreeItem(LTreeItem *ti, Resource *r)
{
for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext())
{
ObjTreeItem *o = dynamic_cast(i);
if (o)
{
if (o->GetObj() == r) return o;
}
o = GetTreeItem(i, r);
if (o) return o;
}
return 0;
}
ObjTreeItem *GetTreeItem(LTree *ti, Resource *r)
{
for (LTreeItem *i=ti->GetChild(); i; i=i->GetNext())
{
ObjTreeItem *o = dynamic_cast(i);
if (o)
{
if (o->GetObj() == r) return o;
}
o = GetTreeItem(i, r);
if (o) return o;
}
return 0;
}
void AppWnd::GotoObject(ResString *s,
ResStringGroup *g,
ResDialog *d,
ResMenuItem *m,
ResDialogCtrl *c)
{
if (s)
{
Resource *Res = 0;
if (g)
{
Res = g;
}
else if (d)
{
Res = d;
}
else if (m)
{
Res = m->GetMenu();
}
if (Res)
{
ObjTreeItem *ti = GetTreeItem(Objs, Res);
if (ti)
{
ti->Select(true);
if (g)
{
s->GetList()->Select(0);
s->ScrollTo();
s->Select(true);
}
else if (d)
{
d->SelectCtrl(c);
}
else if (m)
{
for (LTreeItem *i=m; i; i=i->GetParent())
{
i->Expanded(true);
}
m->Select(true);
m->ScrollTo();
}
}
else
{
printf("%s:%i - couldn't find resources tree item\n", _FL);
}
}
}
}
bool AppWnd::ListObjects(List &Lst)
{
if (Objs)
{
return Objs->ListObjects(Lst);
}
return false;
}
void AppWnd::OnObjChange(FieldSource *r)
{
if (Fields)
{
Fields->Serialize(false);
SetDirty(true, NULL);
}
}
void AppWnd::OnObjSelect(FieldSource *r)
{
if (Fields)
Fields->OnSelect(r);
}
void AppWnd::OnObjDelete(FieldSource *r)
{
if (Fields)
{
Fields->OnDelete(r);
}
}
void AppWnd::OnResourceDelete(Resource *r)
{
auto v = GetShortCutView();
if (v)
v->OnResource(NULL);
}
void AppWnd::OnResourceSelect(Resource *r)
{
if (LastRes)
{
OnObjSelect(NULL);
if (ContentView)
{
ContentView->Detach();
DeleteObj(ContentView);
}
LastRes = NULL;
}
if (r)
{
ContentView = r->CreateUI();
if (ContentView)
{
if (HBox)
ContentView->Attach(HBox);
LastRes = r;
}
auto v = GetShortCutView();
if (v)
v->OnResource(r);
}
}
char *TagName(LXmlTag *t)
{
static char Buf[1024];
LArray Tags;
for (; t; t = t->Parent)
{
Tags.AddAt(0, t);
}
Buf[0] = 0;
for (int i=0; iGetTag());
}
return Buf;
}
class ResCompare : public LWindow, public LResourceLoad
{
LList *Lst;
public:
ResCompare(const char *File1, const char *File2)
{
Lst = 0;
LRect p;
LAutoString n;
if (LoadFromResource(IDD_COMPARE, this, &p, &n))
{
SetPos(p);
Name(n);
MoveToCenter();
GetViewById(IDC_DIFFS, Lst);
if (Attach(0))
{
Visible(true);
AttachChildren();
LXmlTag *t1 = new LXmlTag;
LXmlTag *t2 = new LXmlTag;
if (t1 && File1)
{
LFile f;
if (f.Open(File1, O_READ))
{
LXmlTree x(GXT_NO_ENTITIES);
if (!x.Read(t1, &f, 0))
{
DeleteObj(t1);
}
}
else
{
DeleteObj(t1);
}
}
if (t2 && File2)
{
LFile f;
if (f.Open(File2, O_READ))
{
LXmlTree x(GXT_NO_ENTITIES);
if (!x.Read(t2, &f, 0))
{
DeleteObj(t2);
}
}
else
{
DeleteObj(t2);
}
}
if (Lst && t1 && t2)
{
Lst->Enabled(false);
Compare(t1, t2);
Lst->Enabled(true);
}
DeleteObj(t1);
DeleteObj(t2);
}
}
}
void Compare(LXmlTag *t1, LXmlTag *t2)
{
char s[1024];
if (stricmp(t1->GetTag(), t2->GetTag()) != 0)
{
snprintf(s, sizeof(s), "Different Tag: '%s' <-> '%s'", t1->GetTag(), t2->GetTag());
LListItem *i = new LListItem;
if (i)
{
i->SetText(s);
i->SetText(TagName(t1), 1);
Lst->Insert(i);
}
}
LHashTbl,LXmlAttr*> a;
for (int i=0; iAttr.Length(); i++)
{
LXmlAttr *a1 = &t1->Attr[i];
a.Add(a1->GetName(), a1);
}
for (int n=0; nAttr.Length(); n++)
{
LXmlAttr *a2 = &t2->Attr[n];
LXmlAttr *a1 = (LXmlAttr*) a.Find(a2->GetName());
if (a1)
{
if (strcmp(a1->GetValue(), a2->GetValue()) != 0)
{
snprintf(s, sizeof(s), "Different Attr Value: '%s' <-> '%s'", a1->GetValue(), a2->GetValue());
LListItem *i = new LListItem;
if (i)
{
i->SetText(s);
snprintf(s, sizeof(s), "%s.%s", TagName(t1), a1->GetName());
i->SetText(s, 1);
Lst->Insert(i);
}
}
a.Delete(a2->GetName());
}
else
{
snprintf(s, sizeof(s), "[Right] Missing Attr: '%s' = '%s'", a2->GetName(), a2->GetValue());
LListItem *i = new LListItem;
if (i)
{
i->SetText(s);
i->SetText(TagName(t1), 1);
Lst->Insert(i);
}
}
}
// char *Key;
// for (void *v = a.First(&Key); v; v = a.Next(&Key))
for (auto v : a)
{
LXmlAttr *a1 = v.value;
snprintf(s, sizeof(s), "[Left] Missing Attr: '%s' = '%s'", a1->GetName(), a1->GetValue());
LListItem *i = new LListItem;
if (i)
{
i->SetText(s);
i->SetText(TagName(t1), 1);
Lst->Insert(i);
}
}
if (t1->IsTag("string-group"))
{
LArray r1, r2;
for (auto t: t1->Children)
{
char *Ref;
if ((Ref = t->GetAttr("ref")))
{
int r = atoi(Ref);
if (r)
{
r1[r] = t;
}
}
}
for (auto t: t2->Children)
{
char *Ref;
if ((Ref = t->GetAttr("ref")))
{
int r = atoi(Ref);
if (r)
{
r2[r] = t;
}
}
}
auto Max = MAX(r1.Length(), r2.Length());
for (int i = 0; iGetAttr("ref"), r1[i]->GetAttr("Define"));
LListItem *n = new LListItem;
if (n)
{
n->SetText(s);
n->SetText(TagName(r1[i]), 1);
Lst->Insert(n);
}
}
else if (r2[i])
{
snprintf(s, sizeof(s), "[Left] Missing String: Ref=%s, Def=%s", r2[i]->GetAttr("ref"), r2[i]->GetAttr("Define"));
LListItem *n = new LListItem;
if (n)
{
n->SetText(s);
n->SetText(TagName(r2[i]), 1);
Lst->Insert(n);
}
}
}
}
else
{
LXmlTag *c1 = t1->Children[0];
LXmlTag *c2 = t2->Children[0];
while (c1 && c2)
{
Compare(c1, c2);
c1 = t1->Children[0];
c2 = t2->Children[0];
}
}
}
void OnPosChange()
{
LRect c = GetClient();
if (Lst)
{
c.Inset(7, 7);
Lst->SetPos(c);
}
}
};
void AppWnd::Compare()
{
auto s = new LFileSelect(this);
s->Type("Lgi Resource", "*.lr8");
s->Open([&](auto dlg, auto status)
{
if (status)
new ResCompare(GetCurFile(), dlg->Name());
delete dlg;
});
}
void AppWnd::ImportLang()
{
// open dialog
auto Select = new LFileSelect(this);
Select->Type("Lgi Resources", "*.lr8;*.xml");
Select->Open([&](auto dlg, auto status)
{
if (status)
{
LFile F;
if (F.Open(dlg->Name(), O_READ))
{
SerialiseContext Ctx;
Ctx.Format = GetFormat(dlg->Name());
// convert file to Xml objects
LXmlTag *Root = new LXmlTag;
if (Root)
{
LXmlTree Tree(GXT_NO_ENTITIES);
if (Tree.Read(Root, &F, 0))
{
List Menus;
List Groups;
for (auto t: Root->Children)
{
if (t->IsTag("menu"))
{
ResMenu *Menu = new ResMenu(this);
if (Menu && Menu->Read(t, Ctx))
{
Menus.Insert(Menu);
}
else break;
}
else if (t->IsTag("string-group"))
{
ResStringGroup *g = new ResStringGroup(this);
if (g && g->Read(t, Ctx))
{
Groups.Insert(g);
}
else break;
}
}
Ctx.PostLoad(this);
bool HasData = false;
for (auto g: Groups)
{
g->SetLanguages();
if (g->GetStrs()->Length() > 0 &&
g->GetLanguages() > 0)
{
HasData = true;
}
}
if (HasData)
{
List Langs;
for (auto g: Groups)
{
for (int i=0; iGetLanguages(); i++)
{
LLanguage *Lang = g->GetLanguage(i);
if (Lang)
{
bool Has = false;
for (auto l: Langs)
{
if (stricmp((char*)l, (char*)Lang) == 0)
{
Has = true;
break;
}
}
if (!Has)
{
Langs.Insert(Lang);
}
}
}
}
auto Dlg = new LangDlg(this, Langs);
Dlg->DoModal([&](auto dlg, auto id)
{
if (id == IDOK && Dlg->Lang)
{
LStringPipe Errors;
int Matches = 0;
int NotFound = 0;
int Imported = 0;
int Different = 0;
for (auto g: Groups)
{
List::I Strings = g->GetStrs()->begin();
for (ResString *s=*Strings; s; s=*++Strings)
{
ResString *d = GetStrFromRef(s->GetRef());
if (d)
{
Matches++;
char *Str = s->Get(Dlg->Lang->Id);
char *Dst = d->Get(Dlg->Lang->Id);
if
(
(
Str &&
Dst &&
strcmp(Dst, Str) != 0
)
||
(
(Str != 0) ^
(Dst != 0)
)
)
{
Different++;
d->Set(Str, Dlg->Lang->Id);
Imported++;
}
}
else
{
NotFound++;
char e[256];
snprintf(e, sizeof(e), "String ref=%i (%s)\n", s->GetRef(), s->GetDefine());
Errors.Push(e);
}
}
}
List Lst;
if (ListObjects(Lst))
{
for (auto m: Menus)
{
// find matching menu in our list
ResMenu *Match = 0;
for (auto r: Lst)
{
ResMenu *n = dynamic_cast(r);
if (n && stricmp(n->Name(), m->Name()) == 0)
{
Match = n;
break;
}
}
if (Match)
{
// match strings
List *Src = m->GetStrs();
List *Dst = Match->GetStrs();
for (auto s: *Src)
{
bool FoundRef = false;
for (auto d: *Dst)
{
if (s->GetRef() == d->GetRef())
{
FoundRef = true;
char *Str = s->Get(Dlg->Lang->Id);
if (Str)
{
char *Dst = d->Get(Dlg->Lang->Id);
if (!Dst || strcmp(Dst, Str))
{
Different++;
}
d->Set(Str, Dlg->Lang->Id);
Imported++;
}
break;
}
}
if (!FoundRef)
{
NotFound++;
char e[256];
snprintf(e, sizeof(e), "MenuString ref=%i (%s)\n", s->GetRef(), s->GetDefine());
Errors.Push(e);
}
}
Match->SetLanguages();
}
}
for (auto r: Lst)
{
ResStringGroup *StrRes = dynamic_cast(r);
if (StrRes)
{
StrRes->SetLanguages();
}
}
}
char *ErrorStr = Errors.NewStr();
LgiMsg( this,
"Imported: %i\n"
"Matched: %i\n"
"Not matched: %i\n"
"Different: %i\n"
"Total: %i\n"
"\n"
"Import complete.\n"
"\n%s",
AppName,
MB_OK,
Imported,
Matches,
NotFound,
Different,
Matches + NotFound,
ErrorStr?ErrorStr:(char*)"");
}
delete dlg;
});
}
else
{
LgiMsg(this, "No language information to import", AppName, MB_OK);
}
// Groups.DeleteObjects();
// Menus.DeleteObjects();
}
else
{
LgiMsg(this, "Failed to parse XML from file.\nError: %s", AppName, MB_OK, Tree.GetErrorMsg());
}
DeleteObj(Root);
}
}
}
delete dlg;
});
}
bool AppWnd::Empty()
{
// Delete any existing objects
List l;
if (ListObjects(l))
{
for (auto It = l.begin(); It != l.end(); )
{
auto r = *It;
if (r->SystemObject())
l.Delete(It);
else
It++;
}
for (auto r: l)
{
DelObject(r);
}
}
return true;
}
bool AppWnd::OpenFile(const char *FileName, bool Ro)
{
if (stristr(FileName, ".lr8") ||
stristr(FileName, ".xml"))
{
return LoadLgi(FileName);
}
else if (stristr(FileName, ".rc"))
{
LoadWin32(FileName);
return true;
}
return false;
}
void AppWnd::SaveFile(const char *FileName, std::function Callback)
{
if (stristr(FileName, ".lr8") ||
stristr(FileName, ".xml"))
{
auto r = SaveLgi(FileName);
if (Callback)
Callback(FileName, r);
return;
}
if (Callback)
Callback(FileName, false);
}
void AppWnd::GetFileTypes(LFileSelect *Dlg, bool Write)
{
Dlg->Type("Lgi Resources", "*.lr8;*.xml");
if (!Write)
{
Dlg->Type("All Files", LGI_ALL_FILES);
}
}
// Lgi load/save
bool AppWnd::TestLgi(bool Quite)
{
bool Status = true;
List l;
if (ListObjects(l))
{
ErrorCollection Errors;
for (auto r: l)
{
Status &= r->Test(&Errors);
}
if (Errors.StrErr.Length() > 0)
{
LStringPipe Sample;
for (int i=0; iGetRef(),
s->GetDefine(),
Errors.StrErr[i].Msg.Get());
}
char *Sam = Sample.NewStr();
LgiMsg(this, "%i strings have errors.\n\n%s", AppName, MB_OK, Errors.StrErr.Length(), Sam);
DeleteArray(Sam);
}
else if (!Quite)
{
LgiMsg(this, "Object are all ok.", AppName);
}
}
return Status;
}
#define PROFILE_LOAD 0
#if PROFILE_LOAD
#define PROF(s) prof.Add(s)
#else
#define PROF(s)
#endif
bool AppWnd::LoadLgi(const char *FileName)
{
#if PROFILE_LOAD
LProfile prof("LoadLgi");
#endif
Empty();
if (!FileName)
return false;
PROF("fOpen");
LFile f;
if (!f.Open(FileName, O_READ))
return false;
PROF("prog");
LAutoPtr Progress(new LProgressDlg(this));
Progress->SetDescription("Initializing...");
Progress->SetType("Tags");
LAutoPtr Root(new LXmlTag);
if (!Root)
return false;
// convert file to Xml objects
LXmlTree Xml(0);
Progress->SetDescription("Lexing...");
PROF("xml.read");
if (!Xml.Read(Root, &f, 0))
{
LgiMsg(this, "Xml read failed: %s", AppName, MB_OK, Xml.GetErrorMsg());
return false;
}
Progress->SetRange(Root->Children.Length());
PROF("xml to objs");
// convert Xml list into objects
SerialiseContext Ctx;
for (auto t: Root->Children)
{
Progress->Value(Root->Children.IndexOf(t));
int RType = 0;
if (t->IsTag("dialog"))
RType = TYPE_DIALOG;
else if (t->IsTag("string-group"))
RType = TYPE_STRING;
else if (t->IsTag("menu"))
RType = TYPE_MENU;
else if (t->IsTag("style"))
RType = TYPE_CSS;
else
LAssert(!"Unexpected tag");
if (RType > 0)
NewObject(Ctx, t, RType, false);
}
PROF("postload");
Ctx.PostLoad(this);
PROF("sort");
SortDialogs();
PROF("test");
TestLgi();
PROF("scan langs");
// Scan for languages and update the view lang menu
Languages.Length(0);
LHashTbl, LLanguage*> Langs;
if (!ViewMenu)
return false;
PROF("remove menu items");
// Remove existing language menu items
while (ViewMenu->RemoveItem(1));
ViewMenu->AppendSeparator();
PROF("enum objs");
// Enumerate all languages
List res;
if (ListObjects(res))
{
for (auto r: res)
{
ResStringGroup *Sg = r->IsStringGroup();
if (Sg)
{
for (int i=0; iGetLanguages(); i++)
{
LLanguage *Lang = Sg->GetLanguage(i);
if (Lang)
{
Langs.Add(Lang->Id, Lang);
}
}
}
}
}
PROF("update langs");
// Update languages array
int n = 0;
for (auto i : Langs)
{
Languages.Add(i.value);
auto Item = ViewMenu->AppendItem(i.value->Name, IDM_LANG_BASE + n, true);
if (Item && i.value->IsEnglish())
{
Item->Checked(true);
CurLang = n;
}
n++;
}
PROF("none menu");
if (Languages.Length() == 0)
ViewMenu->AppendItem("(none)", -1, false);
return true;
}
void SerialiseContext::PostLoad(AppWnd *App)
{
for (int i=0; iGetUniqueCtrlId();
s->SetId(Id);
Log.Print("Repaired CtrlId of string ref %i to %i\n", s->GetRef(), Id);
}
LAutoString a(Log.NewStr());
if (ValidStr(a))
{
LgiMsg(App, "%s", "Load Warnings", MB_OK, a.Get());
}
}
int DialogNameCompare(ResDialog *a, ResDialog *b, NativeInt Data)
{
const char *A = (a)?a->Name():0;
const char *B = (b)?b->Name():0;
if (A && B) return stricmp(A, B);
return -1;
}
void AppWnd::SortDialogs()
{
List Lst;
if (ListObjects(Lst))
{
List Dlgs;
for (auto r: Lst)
{
ResDialog *Dlg = dynamic_cast(r);
if (Dlg)
{
Dlgs.Insert(Dlg);
Dlg->Item->Remove();
}
}
Dlgs.Sort(DialogNameCompare);
for (auto d: Dlgs)
{
Objs->Dialogs->Insert(d->Item);
}
}
}
class ResTreeNode
{
public:
char *Str;
ResTreeNode *a, *b;
ResTreeNode(char *s)
{
a = b = 0;
Str = s;
}
~ResTreeNode()
{
DeleteArray(Str);
DeleteObj(a);
DeleteObj(b);
}
void Enum(List &l)
{
if (a)
{
a->Enum(l);
}
if (Str)
{
l.Insert(Str);
}
if (b)
{
b->Enum(l);
}
}
bool Add(char *s)
{
int Comp = (Str && s) ? stricmp(Str, s) : -1;
if (Comp == 0)
{
return false;
}
if (Comp < 0)
{
if (a)
{
return a->Add(s);
}
else
{
a = new ResTreeNode(s);
}
}
if (Comp > 0)
{
if (b)
{
return b->Add(s);
}
else
{
b = new ResTreeNode(s);
}
}
return true;
}
};
class ResTree {
ResTreeNode *Root;
public:
ResTree()
{
Root = 0;
}
~ResTree()
{
DeleteObj(Root);
}
bool Add(char *s)
{
if (s)
{
if (!Root)
{
Root = new ResTreeNode(NewStr(s));
return true;
}
else
{
return Root->Add(NewStr(s));
}
}
return false;
}
void Enum(List &l)
{
if (Root)
{
Root->Enum(l);
}
}
};
const char *HeaderStr = "// This file generated by LgiRes\r\n\r\n";
struct DefinePair
{
char *Name;
int Value;
};
int PairCmp(DefinePair *a, DefinePair *b)
{
return a->Value - b->Value;
}
bool AppWnd::WriteDefines(LStream &Defs)
{
bool Status = false;
ResTree Tree;
// Empty file
Defs.Write(HeaderStr, strlen(HeaderStr));
// make a unique list of #define's
List Lst;
if (ListObjects(Lst))
{
LHashTbl,int> Def;
LHashTbl,char*> Ident;
for (auto r: Lst)
{
List *StrList = r->GetStrs();
if (StrList)
{
Status = true;
List::I sl = StrList->begin();
for (ResString *s = *sl; s; s = *++sl)
{
if (ValidStr(s->GetDefine()))
{
if (stricmp(s->GetDefine(), "IDOK") == 0)
{
s->SetId(IDOK);
}
else if (stricmp(s->GetDefine(), "IDCANCEL") == 0)
{
s->SetId(IDCANCEL);
}
else if (stricmp(s->GetDefine(), "IDC_STATIC") == 0)
{
s->SetId(-1);
}
else if (stricmp(s->GetDefine(), "-1") == 0)
{
s->SetDefine(0);
}
else
{
// Remove dupe ID's
char IdStr[32];
snprintf(IdStr, sizeof(IdStr), "%i", s->GetId());
char *Define;
if ((Define = Ident.Find(IdStr)))
{
if (strcmp(Define, s->GetDefine()))
{
List n;
FindStrings(n, s->GetDefine());
int NewId = GetUniqueCtrlId();
for (auto Ns: n)
{
Ns->SetId(NewId);
}
}
}
else
{
Ident.Add(IdStr, s->GetDefine());
}
// Make all define's the same
int CtrlId;
if ((CtrlId = Def.Find(s->GetDefine())))
{
// Already there...
s->SetId(CtrlId);
}
else
{
// Add...
LAssert(s->GetId());
if (s->GetId())
Def.Add(s->GetDefine(), s->GetId());
}
}
}
}
}
}
// write the list out
LArray Pairs;
// char *s = 0;
// for (int i = Def.First(&s); i; i = Def.Next(&s))
for (auto i : Def)
{
if (ValidStr(i.key) &&
stricmp(i.key, "IDOK") != 0 &&
stricmp(i.key, "IDCANCEL") != 0 &&
stricmp(i.key, "IDC_STATIC") != 0 &&
stricmp(i.key, "-1") != 0)
{
DefinePair &p = Pairs.New();
p.Name = i.key;
p.Value = i.value;
}
}
Pairs.Sort(PairCmp);
for (int n=0; n=' ' && (uint8_t)(c) <= 127)
if (IsPrintable(s[0]) &&
IsPrintable(s[1]) &&
IsPrintable(s[2]) &&
IsPrintable(s[3]))
{
#ifndef __BIG_ENDIAN__
int32 i = LgiSwap32(p.Value);
memcpy(s, &i, 4);
#endif
Defs.Print("#define %s%s'%04.4s'\r\n", p.Name, Tab, s);
}
else
Defs.Print("#define %s%s%i\r\n", p.Name, Tab, p.Value);
}
}
return Status;
}
bool AppWnd::SaveLgi(const char *FileName)
{
bool Status = false;
if (!TestLgi())
{
if (LgiMsg(this, "Do you want to save the file with errors?", AppName, MB_YESNO) == IDNO)
return false;
}
// Rename the existing file to 'xxxxxx.bak'
if (LFileExists(FileName))
{
char Bak[MAX_PATH_LEN];
strcpy_s(Bak, sizeof(Bak), FileName);
char *e = LGetExtension(Bak);
if (e)
{
strcpy(e, "bak");
if (LFileExists(Bak))
FileDev->Delete(Bak, false);
FileDev->Move(FileName, Bak);
}
}
// Save the file to xml
if (FileName)
{
LFile f;
LFile::Path DefsName = FileName;
DefsName += "../resdefs.h";
LStringPipe Defs;
if (f.Open(FileName, O_WRITE))
{
SerialiseContext Ctx;
f.SetSize(0);
Defs.SetSize(0);
Defs.Print("// Generated by LgiRes\r\n\r\n");
List l;
if (ListObjects(l))
{
// Remove all duplicate symbol Id's from the dialogs
for (auto r: l)
{
ResDialog *Dlg = dynamic_cast(r);
if (Dlg)
Dlg->CleanSymbols();
}
// write defines
WriteDefines(Defs);
LXmlTag Root("resources");
// Write all string lists out first so that when we load objects
// back in again the strings will already be loaded and can
// be referenced
for (auto r: l)
{
if (r->Type() == TYPE_STRING)
{
LXmlTag *c = new LXmlTag;
if (c && r->Write(c, Ctx))
{
Root.InsertTag(c);
}
else
{
LAssert(0);
DeleteObj(c);
}
}
}
// now write the rest of the objects out
for (auto r: l)
{
if (r->Type() != TYPE_STRING)
{
LXmlTag *c = new LXmlTag;
if (c && r->Write(c, Ctx))
{
Root.InsertTag(c);
}
else
{
r->Write(c, Ctx);
LAssert(0);
DeleteObj(c);
}
}
}
// Set the offset type.
//
// Older versions of LgiRes stored the dialog's controls at a fixed
// offset (3,17) from where they should've been. That was fixed, but
// to differentiate between the 2 systems, we store a tag at the
// root element.
Root.SetAttr("Offset", 1);
LXmlTree Tree(GXT_NO_ENTITIES);
Status = Tree.Write(&Root, &f);
if (Status)
{
// Also write the header... but only if it's changed...
auto DefsContent = Defs.NewLStr();
LAutoString OldDefsContent(LReadTextFile(DefsName));
if (Strcmp(DefsContent.Get(), OldDefsContent.Get()))
{
LFile DefsFile;
if (!DefsFile.Open(DefsName, O_WRITE))
goto FileErrorMsg;
DefsFile.SetSize(0);
DefsFile.Write(DefsContent);
}
}
}
}
else
{
FileErrorMsg:
LgiMsg(this,
"Couldn't open these files for output:\n"
"\t%s\n"
"\t%s\n"
"\n"
"Maybe they are read only or locked by another application.",
AppName,
MB_OK,
FileName,
DefsName.GetFull().Get());
}
}
return Status;
}
// Win32 load/save
#define ADJUST_CTRLS_X 2
#define ADJUST_CTRLS_Y 12
#define IMP_MODE_SEARCH 0
#define IMP_MODE_DIALOG 1
#define IMP_MODE_DLG_CTRLS 2
#define IMP_MODE_STRINGS 3
#define IMP_MODE_MENU 4
class ImportDefine
{
public:
char *Name;
char *Value;
ImportDefine()
{
Name = Value = 0;
}
ImportDefine(char *Line)
{
Name = Value = 0;
if (Line && *Line == '#')
{
Line++;
if (strnicmp(Line, "define", 6) == 0)
{
Line += 6;
Line = LSkipDelim(Line);
char *Start = Line;
const char *WhiteSpace = " \r\n\t";
while (*Line && !strchr(WhiteSpace, *Line))
{
Line++;
}
Name = NewStr(Start, Line-Start);
Line = LSkipDelim(Line);
Start = Line;
while (*Line && !strchr(WhiteSpace, *Line))
{
Line++;
}
if (Start != Line)
{
Value = NewStr(Start, Line-Start);
}
}
}
}
~ImportDefine()
{
DeleteArray(Name);
DeleteArray(Value);
}
};
class DefineList : public List
{
int NestLevel;
public:
bool Defined;
List IncludeDirs;
DefineList()
{
Defined = true;
NestLevel = 0;
}
~DefineList()
{
for (auto i: *this)
{
DeleteObj(i);
}
for (auto c: IncludeDirs)
{
DeleteArray(c);
}
}
void DefineSymbol(const char *Name, const char *Value = 0)
{
ImportDefine *Def = new ImportDefine;
if (Def)
{
Def->Name = NewStr(Name);
if (Value) Def->Value = NewStr(Value);
Insert(Def);
}
}
ImportDefine *GetDefine(char *Name)
{
if (Name)
{
for (auto i: *this)
{
if (i->Name && stricmp(i->Name, Name) == 0)
{
return i;
}
}
}
return NULL;
}
void ProcessLine(char *Line)
{
if (NestLevel > 16)
{
return;
}
if (Line && *Line == '#')
{
Line++;
LToken T(Line);
if (T.Length() > 0)
{
if (stricmp(T[0], "define") == 0) // #define
{
ImportDefine *Def = new ImportDefine(Line-1);
if (Def)
{
if (Def->Name)
{
Insert(Def);
}
else
{
DeleteObj(Def);
}
}
}
else if (stricmp(T[0], "include") == 0) // #include
{
NestLevel++;
LFile F;
if (T.Length() > 1)
{
for (auto IncPath: IncludeDirs)
{
char FullPath[256];
strcpy(FullPath, IncPath);
if (FullPath[strlen(FullPath)-1] != DIR_CHAR)
{
strcat(FullPath, DIR_STR);
}
strcat(FullPath, T[1]);
if (F.Open(FullPath, O_READ))
{
char Line[1024];
while (!F.Eof())
{
F.ReadStr(Line, sizeof(Line));
char *p = LSkipDelim(Line);
if (*p == '#')
{
ProcessLine(p);
}
}
break;
}
}
}
NestLevel--;
}
else if (stricmp(T[0], "if") == 0) // #if
{
}
else if (stricmp(T[0], "ifdef") == 0) // #if
{
if (T.Length() > 1)
{
Defined = GetDefine(T[1]) != 0;
}
}
else if (stricmp(T[0], "endif") == 0) // #endif
{
Defined = true;
}
else if (stricmp(T[0], "pragma") == 0)
{
ImportDefine *Def = new ImportDefine;
if (Def)
{
char *Str = Line + 7;
char *First = strchr(Str, '(');
char *Second = (First) ? strchr(First+1, ')') : 0;
if (First && Second)
{
Insert(Def);
Def->Name = NewStr(Str, First-Str);
First++;
Def->Value = NewStr(First, Second-First);
}
else
{
DeleteObj(Def);
}
}
}
else if (stricmp(T[0], "undef") == 0)
{
}
}
}
// else it's not for us anyway
}
};
void TokLine(LArray &T, char *Line)
{
if (Line)
{
// Exclude comments
for (int k=0; Line[k]; k++)
{
if (Line[k] == '/' && Line[k+1] == '/')
{
Line[k] = 0;
break;
}
}
// Break into tokens
for (const char *s = Line; s && *s; )
{
while (*s && strchr(" \t", *s)) s++;
char *t = LTokStr(s);
if (t)
T.Add(t);
else break;
}
}
}
void AppWnd::LoadWin32(const char *FileName)
{
bool Status = false;
LHashTbl,bool> CtrlNames;
CtrlNames.Add("LTEXT", true);
CtrlNames.Add("EDITTEXT", true);
CtrlNames.Add("COMBOBOX", true);
CtrlNames.Add("SCROLLBAR", true);
CtrlNames.Add("GROUPBOX", true);
CtrlNames.Add("PUSHBUTTON", true);
CtrlNames.Add("DEFPUSHBUTTON", true);
CtrlNames.Add("CONTROL", true);
CtrlNames.Add("ICON", true);
CtrlNames.Add("LISTBOX", true);
Empty();
auto Load = [&](const char *FileName)
{
if (!FileName)
return;
LProgressDlg Progress(this);
Progress.SetDescription("Initializing...");
Progress.SetType("K");
Progress.SetScale(1.0/1024.0);
char *FileTxt = LReadTextFile(FileName);
if (FileTxt)
{
LToken Lines(FileTxt, "\r\n");
DeleteArray(FileTxt);
DefineList Defines;
ResStringGroup *String = new ResStringGroup(this);
int Mode = IMP_MODE_SEARCH;
// Language
char *Language = 0;
LLanguageId LanguageId = 0;
// Dialogs
List DlLList;
CtrlDlg *Dlg = 0;
// Menus
ResDialog *Dialog = 0;
ResMenu *Menu = 0;
List Menus;
ResMenuItem *MenuItem[32];
int MenuLevel = 0;
bool MenuNewLang = false;
int MenuNextItem = 0;
// Include defines
char IncPath[256];
strcpy(IncPath, FileName);
LTrimDir(IncPath);
Defines.IncludeDirs.Insert(NewStr(IncPath));
Defines.DefineSymbol("_WIN32");
Defines.DefineSymbol("IDC_STATIC", "-1");
DoEvery Ticker(200);
Progress.SetDescription("Reading resources...");
Progress.SetRange(Lines.Length());
if (String)
{
InsertObject(TYPE_STRING, String, false);
}
for (int CurLine = 0; CurLine < Lines.Length(); CurLine++)
{
if (Ticker.DoNow())
{
Progress.Value(CurLine);
}
// Skip white space
char *Line = Lines[CurLine];
char *p = LSkipDelim(Line);
Defines.ProcessLine(Line);
// Tokenize
LArray T;
TokLine(T, Line);
// Process line
if (Defines.Defined)
{
switch (Mode)
{
case IMP_MODE_SEARCH:
{
DeleteObj(Dialog);
Dlg = 0;
if (*p != '#')
{
if (T.Length() > 1 &&
(stricmp(T[1], "DIALOG") == 0 ||
stricmp(T[1], "DIALOGEX") == 0))
{
Mode = IMP_MODE_DIALOG;
Dialog = new ResDialog(this);
if (Dialog)
{
Dialog->Create(NULL, NULL);
Dialog->Name(T[0]);
auto It = Dialog->IterateViews();
Dlg = dynamic_cast(It[0]);
if (Dlg)
{
int Pos[4] = {0, 0, 0, 0};
int i = 0;
for (; iResDialogCtrl::SetPos(r);
Dlg->GetStr()->SetDefine(T[0]);
ImportDefine *Def = Defines.GetDefine(T[0]);
if (Def)
{
Dlg->GetStr()->SetId(atoi(Def->Value));
}
}
}
break;
}
if (T.Length() > 1 &&
stricmp(T[1], "MENU") == 0)
{
ZeroObj(MenuItem);
Mode = IMP_MODE_MENU;
Menu = 0;
// Check for preexisting menu in another language
MenuNewLang = false;
for (auto m: Menus)
{
if (stricmp(m->Name(), T[0]) == 0)
{
MenuNewLang = true;
Menu = m;
break;
}
}
// If it doesn't preexist then create it
if (!Menu)
{
Menu = new ResMenu(this);
if (Menu)
{
Menus.Insert(Menu);
Menu->Create(NULL, NULL);
Menu->Name(T[0]);
}
}
break;
}
if (T.Length() > 0 &&
stricmp(T[0], "STRINGTABLE") == 0)
{
Mode = IMP_MODE_STRINGS;
if (String)
{
String->Name("_Win32 Imports_");
}
break;
}
if (T.Length() > 2 &&
stricmp(T[0], "LANGUAGE") == 0)
{
LanguageId = 0;
DeleteArray(Language);
char *Language = NewStr(T[1]);
if (Language)
{
LLanguage *Info = LFindLang(0, Language);
if (Info)
{
LanguageId = Info->Id;
ResDialog::AddLanguage(Info->Id);
}
}
break;
}
}
break;
}
case IMP_MODE_DIALOG:
{
if (T.Length() > 0 && Dlg)
{
if (stricmp(T[0], "CAPTION") == 0)
{
char *Caption = T[1];
if (Caption)
{
Dlg->GetStr()->Set(Caption, LanguageId);
}
}
else if (stricmp(T[0], "BEGIN") == 0)
{
Mode = IMP_MODE_DLG_CTRLS;
}
}
break;
}
case IMP_MODE_DLG_CTRLS:
{
char *Type = T[0];
if (!Type) break;
if (stricmp(Type, "end") != 0)
{
// Add wrapped content to the token array.
char *Next = Lines[CurLine+1];
if (Next)
{
Next = LSkipDelim(Next);
char *NextTok = LTokStr((const char*&)Next);
if (NextTok)
{
if (stricmp(NextTok, "END") != 0 &&
!CtrlNames.Find(NextTok))
{
TokLine(T, Lines[++CurLine]);
}
DeleteArray(NextTok);
}
}
}
// Process controls
if (stricmp(Type, "LTEXT") == 0)
{
if (T.Length() >= 7)
{
CtrlText *Ctrl = new CtrlText(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->Set(T[1], LanguageId);
Ctrl->GetStr()->SetDefine(T[2]);
ImportDefine *Def = Defines.GetDefine(T[2]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y);
r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "EDITTEXT") == 0)
{
if (T.Length() >= 7)
{
CtrlEditbox *Ctrl = new CtrlEditbox(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->SetDefine(T[1]);
ImportDefine *Def = Defines.GetDefine(T[1]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y);
r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "COMBOBOX") == 0)
{
if (T.Length() >= 6)
{
CtrlComboBox *Ctrl = new CtrlComboBox(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->SetDefine(T[1]);
ImportDefine *Def = Defines.GetDefine(T[1]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y);
r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "SCROLLBAR") == 0)
{
if (T.Length() == 6)
{
CtrlScrollBar *Ctrl = new CtrlScrollBar(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->SetDefine(T[1]);
ImportDefine *Def = Defines.GetDefine(T[1]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y);
r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "GROUPBOX") == 0)
{
if (T.Length() >= 7)
{
CtrlGroup *Ctrl = new CtrlGroup(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->Set(T[1], LanguageId);
Ctrl->GetStr()->SetDefine(T[2]);
ImportDefine *Def = Defines.GetDefine(T[2]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y);
r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "PUSHBUTTON") == 0 ||
stricmp(Type, "DEFPUSHBUTTON") == 0)
{
if (T.Length() >= 7)
{
CtrlButton *Ctrl = new CtrlButton(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->Set(T[1], LanguageId);
Ctrl->GetStr()->SetDefine(T[2]);
ImportDefine *Def = Defines.GetDefine(T[2]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y);
r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "CONTROL") == 0)
{
if (T.Length() >= 7)
{
char *Caption = T[1];
char *Id = T[2];
char *Type = T[3];
bool Checkbox = false;
bool Radio = false;
bool Done = false;
// loop through styles
int i;
for (i=4; !Done && iSetPos(r);
if (Caption) Ctrl->GetStr()->Set(Caption, LanguageId);
if (Id) Ctrl->GetStr()->SetDefine(Id);
ImportDefine *Def = Defines.GetDefine(Id);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
Dlg->AddView(Ctrl->View());
}
}
}
}
else if (stricmp(Type, "ICON") == 0)
{
if (T.Length() >= 7)
{
CtrlBitmap *Ctrl = new CtrlBitmap(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->SetDefine(T[1]);
ImportDefine *Def = Defines.GetDefine(T[1]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[5])*CTRL_X, atoi(T[6])*CTRL_Y);
r.Offset(atoi(T[3])*CTRL_X+ADJUST_CTRLS_X, atoi(T[4])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl->View());
}
}
}
else if (stricmp(Type, "LISTBOX") == 0)
{
if (T.Length() >= 7)
{
CtrlList *Ctrl = new CtrlList(Dialog, 0);
if (Ctrl)
{
Ctrl->GetStr()->SetDefine(T[1]);
ImportDefine *Def = Defines.GetDefine(T[1]);
if (Def)
{
Ctrl->GetStr()->SetId(atoi(Def->Value));
}
LRect r;
r.ZOff(atoi(T[4])*CTRL_X, atoi(T[5])*CTRL_Y);
r.Offset(atoi(T[2])*CTRL_X+ADJUST_CTRLS_X, atoi(T[3])*CTRL_Y+ADJUST_CTRLS_Y);
Ctrl->ResDialogCtrl::SetPos(r);
Dlg->AddView(Ctrl);
}
}
}
else if (stricmp(Type, "END") == 0)
{
// search for an existing dialog resource in
// another language
ResDialog *Match = 0;
CtrlDlg *MatchObj = 0;
for (auto d: DlLList)
{
auto It = d->IterateViews();
LViewI *Wnd = It[0];
if (Wnd)
{
CtrlDlg *Obj = dynamic_cast(Wnd);
if (Obj)
{
if (Obj->GetStr()->GetId() == Dlg->GetStr()->GetId())
{
MatchObj = Obj;
Match = d;
break;
}
}
}
}
if (Match)
{
// Merge the controls from "Dlg" to "MatchObj"
List Old;
List New;
Dlg->ListChildren(New);
MatchObj->ListChildren(Old);
// add the language strings for the caption
// without clobbering the languages already
// present
for (auto s: Dlg->GetStr()->Items)
{
if (!MatchObj->GetStr()->Get(s->GetLang()))
{
MatchObj->GetStr()->Set(s->GetStr(), s->GetLang());
}
}
for (auto c: New)
{
ResDialogCtrl *MatchCtrl = 0;
// try matching by Id
{
for (auto Mc: Old)
{
if (Mc->GetStr()->GetId() == c->GetStr()->GetId() &&
Mc->GetStr()->GetId() > 0)
{
MatchCtrl = Mc;
break;
}
}
}
// ok no Id match, match by location and type
if (!MatchCtrl)
{
List Overlapping;
for (auto Mc: Old)
{
LRect a = Mc->View()->GetPos();
LRect b = c->View()->GetPos();
LRect c = a;
c.Bound(&b);
if (c.Valid())
{
int Sa = a.X() * a.Y();
int Sb = b.X() * b.Y();
int Sc = c.X() * c.Y();
int Total = Sa + Sb - Sc;
double Amount = (double) Sc / (double) Total;
if (Amount > 0.5)
{
// mostly similar in size
Overlapping.Insert(Mc);
}
}
}
if (Overlapping.Length() == 1)
{
MatchCtrl = Overlapping[0];
}
}
if (MatchCtrl)
{
// woohoo we are cool
for (auto s: c->GetStr()->Items)
{
MatchCtrl->GetStr()->Set(s->GetStr(), s->GetLang());
}
}
}
// Delete the duplicate
OnObjSelect(0);
DeleteObj(Dialog);
}
else
{
// Insert the dialog
InsertObject(TYPE_DIALOG, Dialog, false);
DlLList.Insert(Dialog);
}
Dialog = 0;
Dlg = 0;
Status = true;
Mode = IMP_MODE_SEARCH;
}
break;
}
case IMP_MODE_STRINGS:
{
if (stricmp(T[0], "BEGIN") == 0)
{
}
else if (stricmp(T[0], "END") == 0)
{
Status = true;
Mode = IMP_MODE_SEARCH;
}
else
{
if (T.Length() > 1)
{
ResString *Str = String->FindName(T[0]);
if (!Str)
{
Str = String->CreateStr();
if (Str)
{
ImportDefine *Def = Defines.GetDefine(T[0]);
if (Def)
{
Str->SetId(atoi(Def->Value));
}
Str->SetDefine(T[0]);
Str->UnDuplicate();
}
}
if (Str)
{
// get the language
LLanguage *Lang = LFindLang(Language);
LLanguageId SLang = (Lang) ? Lang->Id : (char*)"en";
StrLang *s = 0;
// look for language present in string object
for (auto ss: Str->Items)
{
if (*ss == SLang)
{
s = ss;
break;
}
}
// if not present then add it
if (!s)
{
s = new StrLang;
if (s)
{
Str->Items.Insert(s);
s->SetLang(SLang);
}
}
// set the value
if (s)
{
s->SetStr(T[1]);
}
}
}
}
break;
}
case IMP_MODE_MENU:
{
if (T.Length() >= 1)
{
if (stricmp(T[0], "BEGIN") == 0)
{
MenuLevel++;
}
else if (stricmp(T[0], "END") == 0)
{
MenuLevel--;
if (MenuLevel == 0)
{
Status = true;
Mode = IMP_MODE_SEARCH;
Menu->SetLanguages();
if (!MenuNewLang)
{
InsertObject(TYPE_MENU, Menu, false);
}
Menu = 0;
}
}
else
{
ResMenuItem *i = 0;
char *Text = T[1];
if (Text)
{
if (stricmp(T[0], "POPUP") == 0)
{
if (MenuNewLang)
{
LTreeItem *Ri = 0;
if (MenuItem[MenuLevel])
{
Ri = MenuItem[MenuLevel]->GetNext();
}
else
{
if (MenuLevel == 1)
{
Ri = Menu->ItemAt(0);
}
else if (MenuItem[MenuLevel-1])
{
Ri = MenuItem[MenuLevel-1]->GetChild();
}
}
if (Ri)
{
// Seek up to the next submenu
while (!Ri->GetChild())
{
Ri = Ri->GetNext();
}
i = dynamic_cast(Ri);
// char *si = i->Str.Get("en");
if (i)
{
MenuItem[MenuLevel] = i;
}
}
}
else
{
MenuItem[MenuLevel] = i = new ResMenuItem(Menu);
}
if (i)
{
LLanguage *Lang = LFindLang(Language);
i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en");
}
MenuNextItem = 0;
}
else if (stricmp(T[0], "MENUITEM") == 0)
{
if (MenuNewLang)
{
if (MenuItem[MenuLevel-1] && T.Length() > 2)
{
ImportDefine *id = Defines.GetDefine(T[2]);
if (id)
{
int Id = atoi(id->Value);
int n = 0;
for (LTreeItem *o = MenuItem[MenuLevel-1]->GetChild(); o; o = o->GetNext(), n++)
{
ResMenuItem *Res = dynamic_cast(o);
if (Res && Res->GetStr()->GetId() == Id)
{
i = Res;
break;
}
}
}
}
MenuNextItem++;
}
else
{
i = new ResMenuItem(Menu);
}
if (i)
{
if (stricmp(Text, "SEPARATOR") == 0)
{
// Set separator
i->Separator(true);
}
else
{
if (!MenuNewLang)
{
// Set Id
i->GetStr()->SetDefine(T[2]);
if (i->GetStr()->GetDefine())
{
ImportDefine *id = Defines.GetDefine(i->GetStr()->GetDefine());
if (id)
{
i->GetStr()->SetId(atoi(id->Value));
i->GetStr()->UnDuplicate();
}
}
}
// Set Text
LLanguage *Lang = LFindLang(Language);
i->GetStr()->Set(Text, (Lang) ? Lang->Id : (char*)"en");
}
}
}
}
if (i && !MenuNewLang)
{
if (MenuLevel == 1)
{
Menu->Insert(i);
}
else if (MenuItem[MenuLevel-1])
{
MenuItem[MenuLevel-1]->Insert(i);
}
}
}
}
break;
}
}
}
T.DeleteArrays();
}
DeleteObj(Dialog);
if (String->Length() > 0)
{
String->SetLanguages();
}
}
Invalidate();
};
if (FileName)
Load(FileName);
else
{
auto Select = new LFileSelect(this);
Select->Type("Win32 Resource Script", "*.rc");
Select->Open([&](auto dlg, auto status)
{
if (status)
Load(dlg->Name());
delete dlg;
});
}
}
bool AppWnd::SaveWin32()
{
return false;
}
/////////////////////////////////////////////////////////////////////////
ResFrame::ResFrame(Resource *child)
{
Child = child;
Name("ResFrame");
}
ResFrame::~ResFrame()
{
if (Child)
{
Child->App()->OnObjSelect(NULL);
Child->Wnd()->Detach();
}
}
void ResFrame::OnFocus(bool b)
{
Child->Wnd()->Invalidate();
}
bool ResFrame::Attach(LViewI *p)
{
bool Status = LLayout::Attach(p);
if (Status && Child)
{
Child->Attach(this);
Child->Wnd()->Visible(true);
}
return Status;
}
bool ResFrame::Pour(LRegion &r)
{
LRect *Best = FindLargest(r);
if (Best)
{
SetPos(*Best);
return true;
}
return false;
}
bool ResFrame::OnKey(LKey &k)
{
bool Status = false;
if (k.Down() && Child)
{
switch (k.c16)
{
case LK_DELETE:
{
if (k.Shift())
{
Child->Copy(true);
}
else
{
Child->Delete();
}
Status = true;
break;
}
case 'x':
case 'X':
{
if (k.Ctrl())
{
Child->Copy(true);
Status = true;
}
break;
}
case 'c':
case 'C':
{
if (k.Ctrl())
{
Child->Copy();
Status = true;
}
break;
}
case LK_INSERT:
{
if (k.Ctrl())
{
Child->Copy();
}
else if (k.Shift())
{
Child->Paste();
}
Status = true;
break;
}
case 'v':
case 'V':
{
if (k.Ctrl())
{
Child->Paste();
Status = true;
}
break;
}
}
}
return Child->Wnd()->OnKey(k) || Status;
}
void ResFrame::OnPaint(LSurface *pDC)
{
// Draw nice frame
LRect r(0, 0, X()-1, Y()-1);
LThinBorder(pDC, r, DefaultRaisedEdge);
pDC->Colour(L_MED);
LFlatBorder(pDC, r, 4);
LWideBorder(pDC, r, DefaultSunkenEdge);
// Set the child to the client area
Child->Wnd()->SetPos(r);
// Draw the dialog & controls
LView::OnPaint(pDC);
}
////////////////////////////////////////////////////////////////////
LgiFunc char *_LgiGenLangLookup();
#include "lgi/common/AutoPtr.h"
#include "lgi/common/Variant.h"
#include "lgi/common/Css.h"
#include "lgi/common/TableLayout.h"
class Foo : public LLayoutCell
{
public:
Foo()
{
TextAlign(AlignLeft);
}
bool Add(LView *v) { return false; }
bool Remove(LView *v) { return false; }
};
//////////////////////////////////////////////////////////////////////
ShortCutView::ShortCutView(AppWnd *app)
{
App = app;
LRect r(0, 0, 500, 600);
SetPos(r);
MoveSameScreen(App);
Name("Dialog Shortcuts");
if (Attach(0))
{
Lst = new LList(100, 0, 0, 100, 100);
Lst->Attach(this);
Lst->SetPourLargest(true);
Lst->AddColumn("Key", 50);
Lst->AddColumn("Ref", 80);
Lst->AddColumn("CtrlId", 80);
Lst->AddColumn("Name", 150);
Visible(true);
}
}
ShortCutView::~ShortCutView()
{
App->OnCloseView(this);
}
enum ShortCutCol
{
ColKey,
ColRefId,
ColCtrlId,
ColName
};
void FindMenuKeys(LList *out, ResMenu *menu)
{
if (!out || !menu)
return;
LArray items;
menu->EnumItems(items);
for (auto i: items)
{
auto sc = i->Shortcut();
if (sc)
{
auto item = new LListItem(sc);
LString ref, ctrl;
ref.Printf("%i", i->GetStr()->GetRef());
ctrl.Printf("%i", i->GetStr()->GetId());
item->SetText(ref, ColRefId);
item->SetText(ctrl, ColCtrlId);
item->SetText(i->GetStr()->Get(), ColName);
item->_UserPtr = i;
out->Insert(item);
}
}
}
void FindShortCuts(LList *Out, LViewI *In)
{
for (auto c: In->IterateViews())
{
auto rdc = dynamic_cast(c);
if (!rdc || !rdc->GetStr())
continue;
auto n = rdc->GetStr()->Get();
if (n)
{
char *a = strchr(n, '&');
if (a && a[1] != '&')
{
LListItem *li = new LListItem;
LString s(++a, 1);
LString ref, ctrl;
ref.Printf("%i", rdc->GetStr()->GetRef());
ctrl.Printf("%i", rdc->GetStr()->GetId());
li->SetText(s.Upper(), ColKey);
li->SetText(ref, ColRefId);
li->SetText(ctrl, ColCtrlId);
li->SetText(rdc->GetClass(), ColName);
li->_UserPtr = rdc;
Out->Insert(li);
}
}
FindShortCuts(Out, c);
}
}
int ShortCutView::OnNotify(LViewI *Ctrl, LNotification n)
{
if (Ctrl->GetId() == Lst->GetId())
{
switch (n.Type)
{
case LNotifyItemClick:
{
auto li = Lst->GetSelected();
if (!li)
break;
LString s = li->GetText(1);
ResObject *c = (ResObject*) li->_UserPtr;
if (!c)
break;
if (auto ctrl = dynamic_cast(c))
App->GotoObject(ctrl->GetStr(), NULL, ctrl->GetDlg(), NULL, ctrl);
else if (auto mi = dynamic_cast(c))
App->GotoObject(mi->GetStr(), NULL, NULL, mi, NULL);
else
LAssert(!"Impl me.");
break;
}
default:
break;
}
}
return LWindow::OnNotify(Ctrl, n);
}
void ShortCutView::OnResource(Resource *r)
{
Lst->Empty();
if (!r)
return;
if (auto dlg = dynamic_cast(r))
FindShortCuts(Lst, dlg);
else if (auto menu = dynamic_cast(r))
FindMenuKeys(Lst, menu);
Lst->Sort(0);
Lst->ResizeColumnsToContent();
}
ShortCutView *AppWnd::GetShortCutView()
{
return ShortCuts;
}
void AppWnd::OnCloseView(ShortCutView *v)
{
if (v == ShortCuts)
ShortCuts = NULL;
}
//////////////////////////////////////////////////////////////////////
void TestFunc()
{
/*
Foo c;
uint32 *p = (uint32*)&c;
p++;
p = (uint32*)(*p);
void *method = (void*)p[2];
for (int i=0; i<16; i++)
{
printf("[%i]=%p\n", i, p[i]);
}
c.OnChange(LCss::PropBackground);
*/
}
int LgiMain(OsAppArguments &AppArgs)
{
LApp a(AppArgs, "LgiRes");
if (a.IsOk())
{
if ((a.AppWnd = new AppWnd))
{
TestFunc();
a.AppWnd->Visible(true);
a.Run();
}
}
return 0;
}
diff --git a/src/common/Net/Net.cpp b/src/common/Net/Net.cpp
--- a/src/common/Net/Net.cpp
+++ b/src/common/Net/Net.cpp
@@ -1,2126 +1,2128 @@
/*hdr
** FILE: INet.cpp
** AUTHOR: Matthew Allen
** DATE: 28/5/98
** DESCRIPTION: Internet sockets
**
** Copyright (C) 1998, Matthew Allen
** fret@memecode.com
*/
#define _WINSOCK_DEPRECATED_NO_WARNINGS 1
#if defined(LINUX)
#include
#include
#include
#include
#elif defined(MAC)
#include
#include
#include
#include
#include
#endif
#include
#include "lgi/common/File.h"
#include "lgi/common/Net.h"
#include "lgi/common/LgiString.h"
#include "lgi/common/LgiCommon.h"
#include "LgiOsClasses.h"
#include "lgi/common/RegKey.h"
#define USE_BSD_SOCKETS 1
#define DEBUG_CONNECT 0
#define ETIMEOUT 400
#define PROTO_UDP 0x100
#define PROTO_BROADCAST 0x200
#if defined WIN32
#include
#include
#include
#include
typedef HOSTENT HostEnt;
typedef int socklen_t;
typedef unsigned long in_addr_t;
typedef BOOL option_t;
#define LPrintSock "%I64x"
#define MSG_NOSIGNAL 0
#ifndef EWOULDBLOCK
#define EWOULDBLOCK WSAEWOULDBLOCK
#endif
#ifndef EISCONN
#define EISCONN WSAEISCONN
#endif
#define OsAddr S_un.S_addr
#elif defined POSIX
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SOCKET_ERROR -1
#define LPrintSock "%x"
typedef hostent HostEnt;
typedef int option_t;
#define OsAddr s_addr
#endif
///////////////////////////////////////////////////////////////////////////////
#ifdef WIN32
static bool SocketsOpen = false;
#endif
bool StartNetworkStack()
{
#ifdef WIN32
#ifndef WINSOCK_VERSION
#define WINSOCK_VERSION MAKEWORD(2,2)
#endif
if (!SocketsOpen)
{
// Start sockets
WSADATA WsaData;
int Result = WSAStartup(WINSOCK_VERSION, &WsaData);
if (!Result)
{
if (WsaData.wVersion == WINSOCK_VERSION)
{
SocketsOpen = Result == 0;
}
else
{
WSACleanup();
return false;
}
}
}
#endif
return true;
}
void StopNetworkStack()
{
#ifdef WIN32
if (SocketsOpen)
{
WSACleanup();
SocketsOpen = false;
}
#endif
}
#ifdef WIN32
#include "..\..\Win\INet\MibAccess.h"
#include
#pragma comment(lib, "iphlpapi.lib")
#endif
bool LSocket::EnumInterfaces(LArray &Out)
{
bool Status = false;
StartNetworkStack();
#ifdef WIN32
#if 0
PMIB_IF_TABLE2 Tbl;
SecureZeroMemory(&Tbl, sizeof(Tbl));
auto r = GetIfTable2(&Tbl);
for (size_t i=0; iNumEntries; i++)
{
auto &e = Tbl->Table[i];
WCHAR w[256] = {0};
r = ConvertInterfaceLuidToNameW(&e.InterfaceLuid, w, 256);
MIB_UNICASTIPADDRESS_ROW Addr;
InitializeUnicastIpAddressEntry(&Addr);
Addr.Address.si_family = AF_INET;
Addr.InterfaceLuid = e.InterfaceLuid;
Addr.InterfaceIndex = e.InterfaceIndex;
r = GetUnicastIpAddressEntry(&Addr);
if (r == NO_ERROR)
{
int asd=0;
}
}
FreeMibTable(Tbl);
#else
MibII m;
m.Init();
MibInterface Intf[16] = {0};
int Count = m.GetInterfaces(Intf, CountOf(Intf));
if (Count)
{
for (int i=0; iifa_next)
{
if (a->ifa_addr &&
a->ifa_addr->sa_family == AF_INET)
{
sockaddr_in *in = (sockaddr_in*)a->ifa_addr;
sockaddr_in *mask = (sockaddr_in*)a->ifa_netmask;
auto &Intf = Out.New();
Intf.Ip4 = ntohl(in->sin_addr.s_addr);
Intf.Netmask4 = ntohl(mask->sin_addr.s_addr);
Intf.Name = a->ifa_name;
Status = true;
}
}
freeifaddrs(addrs);
}
#endif
return Status;
}
////////////////////////////////////////////////////////////////////////////////////////////////
class LSocketImplPrivate : public LCancel
{
public:
// Data
int Blocking : 1;
int NoDelay : 1;
int Udp : 1;
int Broadcast : 1;
int LogType = NET_LOG_NONE;
LString LogFile;
int Timeout = -1;
OsSocket Socket = INVALID_SOCKET;
int LastError = 0;
LCancel *Cancel = NULL;
LString ErrStr;
LSocketImplPrivate()
{
Cancel = this;
Blocking = true;
NoDelay = false;
Udp = false;
Broadcast = false;
}
~LSocketImplPrivate()
{
}
bool Select(int TimeoutMs, bool Read)
{
// Assign to local var to avoid a thread changing it
// on us between the validity check and the select.
// Which is important because a socket value of -1
// (ie invalid) will crash the FD_SET macro.
OsSocket s = Socket;
if (ValidSocket(s) && !Cancel->IsCancelled())
{
struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000};
fd_set set;
FD_ZERO(&set);
FD_SET(s, &set);
int ret = select( (int)s+1,
Read ? &set : NULL,
!Read ? &set : NULL,
NULL,
TimeoutMs >= 0 ? &t : NULL);
if (ret > 0 && FD_ISSET(s, &set))
{
return true;
}
}
return false;
}
// This is the timing granularity of the SelectWithCancel loop. Ie the
// number of milliseconds between checking the Cancel object.
constexpr static int CancelCheckMs = 50;
bool SelectWithCancel(int TimeoutMs, bool Read)
{
if (!Cancel)
// Regular select where we just wait the whole timeout...
return Select(TimeoutMs, false);
// Because select can't check out 'Cancel' value during the waiting we run the select
// call in a much smaller timeout and a loop so that we can respond to Cancel being set
// in a timely manner.
auto Now = LCurrentTime();
auto End = Now + TimeoutMs;
do
{
// Do the cancel check...
if (Cancel->IsCancelled())
break;
// How many ms to wait?
Now = LCurrentTime();
auto Remain = MIN(CancelCheckMs, (int)(End - Now));
if (Remain <= 0)
break;
if (Select(Remain, Read))
return true;
}
while (Now < End);
return false;
}
};
LSocket::LSocket(LStreamI *logger, void *unused_param)
{
StartNetworkStack();
BytesWritten = 0;
BytesRead = 0;
d = new LSocketImplPrivate;
}
LSocket::~LSocket()
{
Close();
DeleteObj(d);
}
bool LSocket::IsOK()
{
return
#ifndef __llvm__
this != 0 &&
#endif
d != 0;
}
LCancel *LSocket::GetCancel()
{
return d->Cancel;
}
void LSocket::SetCancel(LCancel *c)
{
d->Cancel = c;
}
void LSocket::OnDisconnect()
{
}
OsSocket LSocket::ReleaseHandle()
{
auto h = d->Socket;
d->Socket = INVALID_SOCKET;
return h;
}
OsSocket LSocket::Handle(OsSocket Set)
{
if (Set != INVALID_SOCKET)
{
d->Socket = Set;
}
return d->Socket;
}
bool LSocket::IsOpen()
{
if (ValidSocket(d->Socket) && !d->Cancel->IsCancelled())
{
return true;
}
return false;
}
int LSocket::GetTimeout()
{
return d->Timeout;
}
void LSocket::SetTimeout(int ms)
{
d->Timeout = ms;
}
bool LSocket::IsReadable(int TimeoutMs)
{
// Assign to local var to avoid a thread changing it
// on us between the validity check and the select.
// Which is important because a socket value of -1
// (ie invalid) will crash the FD_SET macro.
OsSocket s = d->Socket;
if (ValidSocket(s) && !d->Cancel->IsCancelled())
{
#ifdef LINUX
// Because Linux doesn't return from select() when the socket is
// closed elsewhere we have to do something different... damn Linux,
// why can't you just like do the right thing?
struct pollfd fds;
fds.fd = s;
fds.events = POLLIN | POLLRDHUP | POLLERR;
fds.revents = 0;
int r = poll(&fds, 1, TimeoutMs);
if (r > 0)
{
return fds.revents != 0;
}
else if (r < 0)
{
Error();
}
#else
struct timeval t = {TimeoutMs / 1000, (TimeoutMs % 1000) * 1000};
fd_set r;
FD_ZERO(&r);
FD_SET(s, &r);
int v = select((int)s+1, &r, 0, 0, &t);
if (v > 0 && FD_ISSET(s, &r))
{
return true;
}
else if (v < 0)
{
Error();
}
#endif
}
else
LgiTrace("%s:%i - Not a valid socket.\n", _FL);
return false;
}
bool LSocket::IsWritable(int TimeoutMs)
{
return d->SelectWithCancel(TimeoutMs, false);
}
bool LSocket::CanAccept(int TimeoutMs)
{
return IsReadable(TimeoutMs);
}
bool LSocket::IsBlocking()
{
return d->Blocking != 0;
}
void LSocket::IsBlocking(bool block)
{
if (d->Blocking ^ block)
{
d->Blocking = block;
#if defined WIN32
ulong NonBlocking = !block;
ioctlsocket(d->Socket, FIONBIO, &NonBlocking);
#elif defined POSIX
fcntl(d->Socket, F_SETFL, d->Blocking ? 0 : O_NONBLOCK);
#else
#error Impl me.
#endif
}
}
bool LSocket::IsDelayed()
{
return !d->NoDelay;
}
void LSocket::IsDelayed(bool Delay)
{
bool NoDelay = !Delay;
if (d->NoDelay ^ NoDelay)
{
d->NoDelay = NoDelay;
option_t i = d->NoDelay != 0;
setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (const char*)&i, sizeof(i));
}
}
int LSocket::GetLocalPort()
{
struct sockaddr_in addr;
socklen_t size;
ZeroObj(addr);
size = sizeof(addr);
if ((getsockname(Handle(), (sockaddr*)&addr, &size)) >= 0)
{
return ntohs(addr.sin_port);
}
return 0;
}
bool LSocket::GetLocalIp(char *IpAddr)
{
if (IpAddr)
{
struct sockaddr_in addr;
socklen_t size;
size = sizeof(addr);
if ((getsockname(Handle(), (sockaddr*)&addr, &size)) < 0)
return false;
if (addr.sin_addr.s_addr == INADDR_ANY)
return false;
uchar *a = (uchar*)&addr.sin_addr.s_addr;
sprintf_s( IpAddr,
16,
"%i.%i.%i.%i",
a[0],
a[1],
a[2],
a[3]);
return true;
}
return false;
}
bool LSocket::GetRemoteIp(uint32_t *IpAddr)
{
if (IpAddr)
{
struct sockaddr_in a;
socklen_t addrlen = sizeof(a);
if (!getpeername(Handle(), (sockaddr*)&a, &addrlen))
{
*IpAddr = ntohl(a.sin_addr.s_addr);
return true;
}
}
return false;
}
bool LSocket::GetRemoteIp(char *IpAddr)
{
if (!IpAddr)
return false;
uint32_t Ip = 0;
if (!GetRemoteIp(&Ip))
return false;
sprintf_s( IpAddr,
16,
"%u.%u.%u.%u",
(Ip >> 24) & 0xff,
(Ip >> 16) & 0xff,
(Ip >> 8) & 0xff,
(Ip) & 0xff);
return false;
}
int LSocket::GetRemotePort()
{
struct sockaddr_in a;
socklen_t addrlen = sizeof(a);
if (!getpeername(Handle(), (sockaddr*)&a, &addrlen))
{
return a.sin_port;
}
return 0;
}
int LSocket::Open(const char *HostAddr, int Port)
{
int Status = -1;
Close();
if (HostAddr)
{
BytesWritten = 0;
BytesRead = 0;
sockaddr_in RemoteAddr;
HostEnt *Host = 0;
in_addr_t IpAddress = 0;
ZeroObj(RemoteAddr);
#ifdef WIN32
d->Socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 0, WSA_FLAG_OVERLAPPED);
#else
d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
if (ValidSocket(d->Socket))
{
LArray Buf(512);
#if !defined(MAC)
option_t i;
socklen_t sz = sizeof(i);
int r = getsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char*)&i, &sz);
if (d->NoDelay ^ i)
{
i = d->NoDelay != 0;
setsockopt(d->Socket, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i));
}
#endif
if (IsDigit(*HostAddr) && strchr(HostAddr, '.'))
{
// Ip address
// IpAddress = inet_addr(HostAddr);
if (!inet_pton(AF_INET, HostAddr, &IpAddress))
{
Error();
return 0;
}
/* This seems complete unnecessary? -fret Dec 2018
#if defined(WIN32)
Host = c((const char*) &IpAddress, 4, AF_INET);
if (!Host)
Error();
#else
Host = gethostbyaddr
(
#ifdef MAC
HostAddr,
#else
&IpAddress,
#endif
4,
AF_INET
);
#endif
*/
}
else
{
// Name address
#ifdef LINUX
Host = new HostEnt;
if (Host)
{
memset(Host, 0, sizeof(*Host));
HostEnt *Result = 0;
int Err = 0;
int Ret;
while
(
(
!GetCancel()
||
!GetCancel()->IsCancelled()
)
&&
(
Ret
=
gethostbyname_r(HostAddr,
Host,
&Buf[0], Buf.Length(),
&Result,
&Err)
)
==
ERANGE
)
{
Buf.Length(Buf.Length() << 1);
}
if (Ret)
{
auto ErrStr = LErrorCodeToString(Err);
printf("%s:%i - gethostbyname_r('%s') returned %i, %i, %s\n",
_FL, HostAddr, Ret, Err, ErrStr.Get());
DeleteObj(Host);
}
}
#if DEBUG_CONNECT
printf("%s:%i - Host=%p\n", __FILE__, __LINE__, Host);
#endif
#else
Host = gethostbyname(HostAddr);
#endif
if (!Host)
{
Error();
Close();
return false;
}
}
if (1)
{
RemoteAddr.sin_family = AF_INET;
RemoteAddr.sin_port = htons(Port);
if (Host)
{
if (Host->h_addr_list && Host->h_addr_list[0])
{
memcpy(&RemoteAddr.sin_addr, Host->h_addr_list[0], sizeof(in_addr) );
}
else return false;
}
else
{
memcpy(&RemoteAddr.sin_addr, &IpAddress, sizeof(IpAddress) );
}
#ifdef WIN32
if (d->Timeout < 0)
{
// Do blocking connect
Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in));
}
else
#endif
{
#define CONNECT_LOGGING 0
// Setup the connect
bool Block = IsBlocking();
if (Block)
{
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Setting non blocking\n", d->Socket);
#endif
IsBlocking(false);
}
// Do initial connect to kick things off..
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Doing initial connect to %s:%i\n", d->Socket, HostAddr, Port);
#endif
Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in));
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Initial connect=%i Block=%i\n", d->Socket, Status, Block);
#endif
// Wait for the connect to finish?
if (Status && Block)
{
Error(Host);
#ifdef WIN32
// yeah I know... wtf? (http://itamarst.org/writings/win32sockets.html)
#define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == WSAEINVAL || d->LastError == WSAEWOULDBLOCK)
#else
#define IsWouldBlock() (d->LastError == EWOULDBLOCK || d->LastError == EINPROGRESS)
#endif
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - IsWouldBlock()=%i d->LastError=%i\n", d->Socket, IsWouldBlock(), d->LastError);
#endif
int64 End = LCurrentTime() + (d->Timeout > 0 ? d->Timeout : 30000);
while ( !d->Cancel->IsCancelled() &&
ValidSocket(d->Socket) &&
IsWouldBlock())
{
int64 Remaining = End - LCurrentTime();
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Remaining " LPrintfInt64 "\n", d->Socket, Remaining);
#endif
if (Remaining < 0)
{
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Leaving loop\n", d->Socket);
#endif
break;
}
if (IsWritable((int)MIN(Remaining, 1000)))
{
// Should be ready to connect now...
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Secondary connect...\n", d->Socket);
#endif
Status = connect(d->Socket, (sockaddr*) &RemoteAddr, sizeof(sockaddr_in));
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Secondary connect=%i\n", d->Socket, Status);
#endif
if (Status != 0)
{
Error(Host);
if (d->LastError == EISCONN
#ifdef WIN32
|| d->LastError == WSAEISCONN // OMG windows, really?
#endif
)
{
Status = 0;
}
else
{
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Connect=%i Err=%i\n", d->Socket, Status, d->LastError);
#endif
if (IsWouldBlock())
continue;
}
break;
}
else
{
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Connected...\n", d->Socket);
#endif
break;
}
}
else
{
#if CONNECT_LOGGING
LgiTrace(LPrintSock " - Timout...\n", d->Socket);
#endif
}
}
}
if (Block)
IsBlocking(true);
}
if (!Status)
{
char Info[256];
sprintf_s(Info,
sizeof(Info),
"[INET] Socket Connect: %s [%i.%i.%i.%i], port: %i",
HostAddr,
(RemoteAddr.sin_addr.s_addr) & 0xFF,
(RemoteAddr.sin_addr.s_addr >> 8) & 0xFF,
(RemoteAddr.sin_addr.s_addr >> 16) & 0xFF,
(RemoteAddr.sin_addr.s_addr >> 24) & 0xFF,
Port);
OnInformation(Info);
}
}
if (Status)
{
#ifdef WIN32
closesocket(d->Socket);
#else
close(d->Socket);
#endif
d->Socket = INVALID_SOCKET;
}
}
else
{
Error();
}
}
return Status == 0;
}
bool LSocket::Bind(int Port, bool reuseAddr)
{
if (!ValidSocket(d->Socket))
{
OnError(0, "Attempt to use invalid socket to bind.");
return false;
}
if (reuseAddr)
{
int so_reuseaddr = 1;
if (setsockopt(Handle(), SOL_SOCKET, SO_REUSEADDR, (const char *)&so_reuseaddr, sizeof so_reuseaddr))
OnError(0, "Attempt to set SO_REUSEADDR failed.");
// This might not be fatal... so continue on.
}
sockaddr_in add;
add.sin_family = AF_INET;
add.sin_addr.s_addr = htonl(INADDR_ANY);
add.sin_port = htons(Port);
int ret = bind(Handle(), (sockaddr*)&add, sizeof(add));
if (ret)
{
Error();
}
return ret == 0;
}
bool LSocket::Listen(int Port)
{
Close();
d->Socket = socket(AF_INET, SOCK_STREAM, 0);
if (d->Socket >= 0)
{
BytesWritten = 0;
BytesRead = 0;
sockaddr Addr;
sockaddr_in *a = (sockaddr_in*) &Addr;
ZeroObj(Addr);
a->sin_family = AF_INET;
a->sin_port = htons(Port);
a->sin_addr.OsAddr = INADDR_ANY;
if (bind(d->Socket, &Addr, sizeof(Addr)) >= 0)
{
if (listen(d->Socket, SOMAXCONN) != SOCKET_ERROR)
{
return true;
}
else
{
Error();
}
}
else
{
Error();
}
}
else
{
Error();
}
return false;
}
bool LSocket::Accept(LSocketI *c)
{
if (!c)
{
LAssert(0);
return false;
}
OsSocket NewSocket = INVALID_SOCKET;
sockaddr Address;
/*
int Length = sizeof(Address);
NewSocket = accept(d->Socket, &Address, &Length);
*/
// int Loop = 0;
socklen_t Length = sizeof(Address);
uint64 Start = LCurrentTime();
while ( !d->Cancel->IsCancelled() &&
ValidSocket(d->Socket))
{
if (IsReadable(100))
{
NewSocket = accept(d->Socket, &Address, &Length);
break;
}
else if (d->Timeout > 0)
{
uint64 Now = LCurrentTime();
if (Now - Start >= d->Timeout)
{
LString s;
s.Printf("Accept timeout after %.1f seconds.", ((double)(Now-Start)) / 1000.0);
OnInformation(s);
return false;
}
}
}
if (!ValidSocket(NewSocket))
return false;
return ValidSocket(c->Handle(NewSocket));
}
int LSocket::Close()
{
if (ValidSocket(d->Socket))
{
#if defined WIN32
closesocket(d->Socket);
#else
close(d->Socket);
#endif
d->Socket = INVALID_SOCKET;
OnDisconnect();
}
return true;
}
void LSocket::Log(const char *Msg, ssize_t Ret, const char *Buf, ssize_t Len)
{
if (d->LogFile)
{
LFile f;
if (f.Open(d->LogFile, O_WRITE))
{
f.Seek(f.GetSize(), SEEK_SET);
switch (d->LogType)
{
case NET_LOG_HEX_DUMP:
{
char s[256];
f.Write(s, sprintf_s(s, sizeof(s), "%s = %i\r\n", Msg, (int)Ret));
for (int i=0; iSocket) || !Data || d->Cancel->IsCancelled())
return -1;
int Status = 0;
if (d->Timeout < 0 || IsWritable(d->Timeout))
{
Status = (int)send
(
d->Socket,
(char*)Data,
(int) Len,
Flags
#ifndef MAC
| MSG_NOSIGNAL
#endif
);
}
if (Status < 0)
Error();
else if (Status == 0)
OnDisconnect();
else
{
if (Status < Len)
{
// Just in case it's a string lets be safe
((char*)Data)[Status] = 0;
}
BytesWritten += Status;
OnWrite((char*)Data, Status);
}
return Status;
}
ssize_t LSocket::Read(void *Data, ssize_t Len, int Flags)
{
if (!ValidSocket(d->Socket) || !Data || d->Cancel->IsCancelled())
return -1;
ssize_t Status = -1;
if (d->Timeout < 0 || IsReadable(d->Timeout))
{
Status = recv(d->Socket, (char*)Data, (int) Len, Flags
#ifdef MSG_NOSIGNAL
| MSG_NOSIGNAL
#endif
);
}
Log("Read", (int)Status, (char*)Data, Status>0 ? Status : 0);
if (Status < 0)
Error();
else if (Status == 0)
OnDisconnect();
else
{
if (Status < Len)
{
// Just in case it's a string lets be safe
((char*)Data)[Status] = 0;
}
BytesRead += Status;
OnRead((char*)Data, (int)Status);
}
return (int)Status;
}
void LSocket::OnError(int ErrorCode, const char *ErrorDescription)
{
d->ErrStr.Printf("Error(%i): %s", ErrorCode, ErrorDescription);
}
const char *LSocket::GetErrorString()
{
return d->ErrStr;
}
int LSocket::Error(void *Param)
{
// Get the most recent error.
if (!(d->LastError =
#ifdef WIN32
WSAGetLastError()
#else
errno
#endif
))
return 0;
// These are not really errors...
if (d->LastError == EWOULDBLOCK ||
d->LastError == EISCONN)
return 0;
static class ErrorMsg {
public:
int Code;
const char *Msg;
}
ErrorCodes[] =
{
{0, "Socket disconnected."},
#if defined WIN32
{WSAEACCES, "Permission denied."},
{WSAEADDRINUSE, "Address already in use."},
{WSAEADDRNOTAVAIL, "Cannot assign requested address."},
{WSAEAFNOSUPPORT, "Address family not supported by protocol family."},
{WSAEALREADY, "Operation already in progress."},
{WSAECONNABORTED, "Software caused connection abort."},
{WSAECONNREFUSED, "Connection refused."},
{WSAECONNRESET, "Connection reset by peer."},
{WSAEDESTADDRREQ, "Destination address required."},
{WSAEFAULT, "Bad address."},
{WSAEHOSTDOWN, "Host is down."},
{WSAEHOSTUNREACH, "No route to host."},
{WSAEINPROGRESS, "Operation now in progress."},
{WSAEINTR, "Interrupted function call."},
{WSAEINVAL, "Invalid argument."},
{WSAEISCONN, "Socket is already connected."},
{WSAEMFILE, "Too many open files."},
{WSAEMSGSIZE, "Message too long."},
{WSAENETDOWN, "Network is down."},
{WSAENETRESET, "Network dropped connection on reset."},
{WSAENETUNREACH, "Network is unreachable."},
{WSAENOBUFS, "No buffer space available."},
{WSAENOPROTOOPT, "Bad protocol option."},
{WSAENOTCONN, "Socket is not connected."},
{WSAENOTSOCK, "Socket operation on non-socket."},
{WSAEOPNOTSUPP, "Operation not supported."},
{WSAEPFNOSUPPORT, "Protocol family not supported."},
{WSAEPROCLIM, "Too many processes."},
{WSAEPROTONOSUPPORT,"Protocol not supported."},
{WSAEPROTOTYPE, "Protocol wrong type for socket."},
{WSAESHUTDOWN, "Cannot send after socket shutdown."},
{WSAESOCKTNOSUPPORT,"Socket type not supported."},
{WSAETIMEDOUT, "Connection timed out."},
{WSAEWOULDBLOCK, "Operation would block."},
{WSAHOST_NOT_FOUND, "Host not found."},
{WSANOTINITIALISED, "Successful WSAStartup not yet performed."},
{WSANO_DATA, "Valid name, no data record of requested type."},
{WSANO_RECOVERY, "This is a non-recoverable error."},
{WSASYSNOTREADY, "Network subsystem is unavailable."},
{WSATRY_AGAIN, "Non-authoritative host not found."},
{WSAVERNOTSUPPORTED,"WINSOCK.DLL version out of range."},
{WSAEDISCON, "Graceful shutdown in progress."},
#else
{EACCES, "Permission denied."},
{EADDRINUSE, "Address already in use."},
{EADDRNOTAVAIL, "Cannot assign requested address."},
{EAFNOSUPPORT, "Address family not supported by protocol family."},
{EALREADY, "Operation already in progress."},
{ECONNABORTED, "Software caused connection abort."},
{ECONNREFUSED, "Connection refused."},
{ECONNRESET, "Connection reset by peer."},
{EFAULT, "Bad address."},
{EHOSTUNREACH, "No route to host."},
{EINPROGRESS, "Operation now in progress."},
{EINTR, "Interrupted function call."},
{EINVAL, "Invalid argument."},
{EISCONN, "Socket is already connected."},
{EMFILE, "Too many open files."},
{EMSGSIZE, "Message too long."},
{ENETDOWN, "Network is down."},
{ENETRESET, "Network dropped connection on reset."},
{ENETUNREACH, "Network is unreachable."},
{ENOBUFS, "No buffer space available."},
{ENOPROTOOPT, "Bad protocol option."},
{ENOTCONN, "Socket is not connected."},
{ENOTSOCK, "Socket operation on non-socket."},
{EOPNOTSUPP, "Operation not supported."},
{EPFNOSUPPORT, "Protocol family not supported."},
{EPROTONOSUPPORT, "Protocol not supported."},
{EPROTOTYPE, "Protocol wrong type for socket."},
{ESHUTDOWN, "Cannot send after socket shutdown."},
{ETIMEDOUT, "Connection timed out."},
{EWOULDBLOCK, "Resource temporarily unavailable."},
{HOST_NOT_FOUND, "Host not found."},
{NO_DATA, "Valid name, no data record of requested type."},
{NO_RECOVERY, "This is a non-recoverable error."},
{TRY_AGAIN, "Non-authoritative host not found."},
{ETIMEOUT, "Operation timed out."},
{EDESTADDRREQ, "Destination address required."},
{EHOSTDOWN, "Host is down."},
#ifndef HAIKU
{ESOCKTNOSUPPORT, "Socket type not supported."},
#endif
#endif
{-1, 0}
};
ErrorMsg *Error = ErrorCodes;
while (Error->Code >= 0 && Error->Code != d->LastError)
{
Error++;
}
if (d->LastError == 10060 && Param)
{
HostEnt *He = (HostEnt*)Param;
char s[256];
sprintf_s(s, sizeof(s), "%s (gethostbyname returned '%s')", Error->Msg, He->h_name);
OnError(d->LastError, s);
}
else
#if defined(MAC)
if (d->LastError != 36)
#endif
{
OnError(d->LastError, (Error->Code >= 0) ? Error->Msg : "");
}
switch (d->LastError)
{
case 0:
#ifdef WIN32
case 183: // I think this is a XP 'disconnect' ???
case WSAECONNABORTED:
case WSAECONNRESET:
case WSAENETRESET:
#else
case ECONNABORTED:
case ECONNRESET:
case ENETRESET:
#endif
{
Close();
break;
}
}
return d->LastError;
}
bool LSocket::GetUdp()
{
return d->Udp != 0;
}
void LSocket::SetUdp(bool isUdp)
{
if (d->Udp ^ isUdp)
{
d->Udp = isUdp;
if (!ValidSocket(d->Socket))
{
if (d->Udp)
d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
else
d->Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
}
if (d->Broadcast)
{
option_t enabled = d->Broadcast != 0;
setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled));
}
}
}
void LSocket::SetBroadcast(bool isBroadcast)
{
d->Broadcast = isBroadcast;
}
bool LSocket::AddMulticastMember(uint32_t MulticastIp, uint32_t LocalInterface)
{
if (!MulticastIp)
return false;
struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr = htonl(MulticastIp); // your multicast address
mreq.imr_interface.s_addr = htonl(LocalInterface); // your incoming interface IP
int r = setsockopt(Handle(), IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*) &mreq, sizeof(mreq));
if (!r)
{
LgiTrace("AddMulticastMember(%s, %s)\n", LIpToStr(MulticastIp).Get(), LIpToStr(LocalInterface).Get());
return true;
}
Error();
return false;
}
bool LSocket::SetMulticastInterface(uint32_t Interface)
{
if (!Interface)
return false;
struct sockaddr_in addr;
addr.sin_addr.s_addr = Interface;
auto r = setsockopt(Handle(), IPPROTO_IP, IP_MULTICAST_IF, (const char*) &addr, sizeof(addr));
if (!r)
return true;
Error();
return false;
}
bool LSocket::CreateUdpSocket()
{
if (!ValidSocket(d->Socket))
{
d->Socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ValidSocket(d->Socket))
{
if (d->Broadcast)
{
option_t enabled = d->Broadcast != 0;
auto r = setsockopt(Handle(), SOL_SOCKET, SO_BROADCAST, (char*)&enabled, sizeof(enabled));
if (r)
Error();
}
}
}
return ValidSocket(d->Socket);
}
int LSocket::ReadUdp(void *Buffer, int Size, int Flags, uint32_t *Ip, uint16_t *Port)
{
if (!Buffer || Size < 0)
return -1;
CreateUdpSocket();
sockaddr_in a;
socklen_t AddrSize = sizeof(a);
ZeroObj(a);
a.sin_family = AF_INET;
if (Port)
a.sin_port = htons(*Port);
#if defined(WINDOWS)
a.sin_addr.S_un.S_addr = INADDR_ANY;
#else
a.sin_addr.s_addr = INADDR_ANY;
#endif
auto b = recvfrom(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, &AddrSize);
if (b > 0)
{
OnRead((char*)Buffer, (int)b);
if (Ip)
*Ip = ntohl(a.sin_addr.OsAddr);
if (Port)
*Port = ntohs(a.sin_port);
}
return (int)b;
}
int LSocket::WriteUdp(void *Buffer, int Size, int Flags, uint32_t Ip, uint16_t Port)
{
if (!Buffer || Size < 0)
return -1;
CreateUdpSocket();
sockaddr_in a;
ZeroObj(a);
a.sin_family = AF_INET;
a.sin_port = htons(Port);
a.sin_addr.OsAddr = htonl(Ip);
ssize_t b = sendto(d->Socket, (char*)Buffer, Size, Flags, (sockaddr*)&a, sizeof(a));
if (b > 0)
{
OnWrite((char*)Buffer, (int)b);
}
else
{
printf("%s:%i - sendto failed with %i.\n", _FL, errno);
}
return (int)b;
}
//////////////////////////////////////////////////////////////////////////////
bool HaveNetConnection()
{
bool Status = false;
// Check for dial up connection
#if defined WIN32
typedef DWORD (__stdcall *RasEnumConnections_Proc)(LPRASCONN lprasconn, LPDWORD lpcb, LPDWORD lpcConnections);
typedef DWORD (__stdcall *RasGetConnectStatus_Proc)(HRASCONN hrasconn, LPRASCONNSTATUS lprasconnstatus);
HMODULE hRas = (HMODULE) LoadLibraryA("rasapi32.dll");
if (hRas)
{
RASCONN Con[10];
DWORD Connections = 0;
DWORD Bytes = sizeof(Con);
ZeroObj(Con);
Con[0].dwSize = sizeof(Con[0]);
RasEnumConnections_Proc pRasEnumConnections = (RasEnumConnections_Proc) GetProcAddress(hRas, "RasEnumConnectionsA");
RasGetConnectStatus_Proc pRasGetConnectStatus = (RasGetConnectStatus_Proc) GetProcAddress(hRas, "RasGetConnectStatusA");
if (pRasEnumConnections &&
pRasGetConnectStatus)
{
pRasEnumConnections(Con, &Bytes, &Connections);
for (unsigned i=0; i alloc,
std::function setLength)
{
const char *e = s;
static const char *WhiteSpace = " \r\t\n";
// Look for the end of the string
while (*e)
{
if (*e == '\n')
{
if (!strchr(" \t", e[1]))
{
break;
}
}
e++;
}
// Trim whitespace off each end of the string
while (s < e && strchr(WhiteSpace, *s))
s++;
while (e > s && strchr(" \r\t\n", e[-1]))
e--;
// Calc the length
auto Str = alloc(e - s);
if (Str)
{
auto In = s;
auto Out = Str;
while (In < e)
{
if (*In == '\r')
{
}
else if (*In == 9)
{
*Out++ = ' ';
}
else
{
*Out++ = *In;
}
In++;
}
setLength(Out - Str);
}
}
template
T *SeekNextLine(T *s, T *End)
{
if (!s)
return s;
for (; *s && *s != '\n' && (!End || s < End); s++)
;
if (*s == '\n' && (!End || s < End))
s++;
return s;
}
// Search through headers for a field
char *InetGetHeaderField( // Returns an allocated string or NULL on failure
const char *Headers, // Pointer to all the headers
const char *Field, // The field your after
ssize_t Len) // Maximum len to run, or -1 for NULL terminated
{
if (Headers && Field)
{
// for all lines
const char *End = Len < 0 ? 0 : Headers + Len;
size_t FldLen = strlen(Field);
for (const char *s = Headers;
*s && (!End || s < End);
s = SeekNextLine(s, End))
{
if (*s != 9 &&
_strnicmp(s, Field, FldLen) == 0)
{
// found a match
s += FldLen;
if (*s == ':')
{
s++;
while (*s)
{
if (strchr(" \t\r", *s))
{
s++;
}
else if (*s == '\n')
{
if (strchr(" \r\n\t", s[1]))
s += 2;
else
break;
}
else break;
}
char *value = NULL;
InetGetField(s,
[&value](auto sz)
{
return value = new char[sz + 1];
},
[&value](auto sz)
{
value[sz] = 0;
});
return value;
}
}
}
}
return NULL;
}
LString LGetHeaderField(LString Headers, const char *Field)
{
if (!Headers || !Field)
return LString();
// for all lines
auto End = Headers.Get() + Headers.Length();
auto FldLen = Strlen(Field);
for (auto s = Headers.Get();
s < End;
s = SeekNextLine(s, End))
{
+ if (!*s)
+ break;
if (*s != '\t' &&
Strnicmp(s, Field, FldLen) == 0 &&
s[FldLen] == ':')
{
// found a match
s += FldLen + 1;
while (*s && s < End)
{
if (strchr(" \t\r", *s))
{
s++;
}
else if (*s == '\n')
{
if (strchr(" \r\n\t", s[1]))
s += 2;
else
break;
}
else break;
}
LString value;
InetGetField(s,
[&value](auto sz)
{
value.Length(sz);
return value.Get();
},
[&value](auto sz)
{
value.Length(sz);
});
return value;
}
}
return LString();
}
void InetGetSubField_Impl( const char *s,
const char *Field,
std::function output)
{
if (!s || !Field)
return;
s = strchr(s, ';');
if (!s)
return;
s++;
char *Status = NULL;
auto FieldLen = strlen(Field);
auto White = " \t\r\n";
while (*s)
{
// Skip leading whitespace
while (*s && (strchr(White, *s) || *s == ';')) s++;
// Parse field name
if (IsAlpha((uint8_t)*s))
{
const char *f = s;
while (*s && (IsAlpha(*s) || *s == '-')) s++;
bool HasField = ((s-f) == FieldLen) && (_strnicmp(Field, f, FieldLen) == 0);
while (*s && strchr(White, *s)) s++;
if (*s == '=')
{
s++;
while (*s && strchr(White, *s)) s++;
if (*s && strchr("\'\"", *s))
{
// Quote Delimited Field
char d = *s++;
char *e = strchr((char*)s, d);
if (e)
{
if (HasField)
{
output(s, e - s);
break;
}
s = e + 1;
}
else break;
}
else
{
// Space Delimited Field
const char *e = s;
while (*e && !strchr(White, *e) && *e != ';') e++;
if (HasField)
{
output(s, e - s);
break;
}
s = e;
}
}
else break;
}
else break;
}
}
char *InetGetSubField(const char *HeaderValue, const char *Field)
{
char *result = NULL;
InetGetSubField_Impl(HeaderValue, Field,
[&result](auto str, auto sz)
{
result = NewStr(str, sz);
});
return result;
}
LString LGetSubField(LString HeaderValue, const char *Field)
{
LString result;
InetGetSubField_Impl(HeaderValue, Field,
[&result](auto str, auto sz)
{
result.Set(str, sz);
});
return result;
}
LString LIpToStr(uint32_t ip)
{
LString s;
s.Printf("%i.%i.%i.%i",
(ip>>24)&0xff,
(ip>>16)&0xff,
(ip>>8)&0xff,
(ip)&0xff);
return s;
}
uint32_t LIpToInt(LString str)
{
auto p = str.Split(".");
if (p.Length() != 4)
return 0;
uint32_t ip = 0;
for (auto &s : p)
{
ip <<= 8;
auto n = s.Int();
if (n > 255)
{
LAssert(0);
return 0;
}
ip |= (uint8_t)s.Int();
}
return ip;
}
uint32_t LHostnameToIp(const char *Host)
{
if (!Host)
return 0;
// struct addrinfo hints = {};
struct addrinfo *res = NULL;
getaddrinfo(Host, NULL, NULL, &res);
if (!res)
return 0;
uint32_t ip = 0;
if (res->ai_addr)
{
auto fam = res->ai_addr->sa_family;
if (fam == AF_INET)
{
auto a = (sockaddr_in*)res->ai_addr;
ip = ntohl(a->sin_addr.s_addr);
}
}
freeaddrinfo(res);
return ip;
}
bool WhatsMyIp(LAutoString &Ip)
{
bool Status = false;
StartNetworkStack();
#if defined WIN32
char hostname[256];
HostEnt *e = NULL;
if (gethostname(hostname, sizeof(hostname)) != SOCKET_ERROR)
{
e = gethostbyname(hostname);
}
if (e)
{
int Which = 0;
for (; e->h_addr_list[Which]; Which++);
Which--;
char IpAddr[32];
sprintf_s(IpAddr, sizeof(IpAddr),
"%i.%i.%i.%i",
(uchar)e->h_addr_list[Which][0],
(uchar)e->h_addr_list[Which][1],
(uchar)e->h_addr_list[Which][2],
(uchar)e->h_addr_list[Which][3]);
Status = Ip.Reset(NewStr(IpAddr));
}
#endif
return Status;
}
//////////////////////////////////////////////////////////////////////
#define SOCKS5_VER 5
#define SOCKS5_CMD_CONNECT 1
#define SOCKS5_CMD_BIND 2
#define SOCKS5_CMD_ASSOCIATE 3
#define SOCKS5_ADDR_IPV4 1
#define SOCKS5_ADDR_DOMAIN 3
#define SOCKS5_ADDR_IPV6 4
#define SOCKS5_AUTH_NONE 0
#define SOCKS5_AUTH_GSSAPI 1
#define SOCKS5_AUTH_USER_PASS 2
LSocks5Socket::LSocks5Socket()
{
Socks5Connected = false;
}
void LSocks5Socket::SetProxy(const LSocks5Socket *s)
{
Proxy.Reset(s ? NewStr(s->Proxy) : 0);
Port = s ? s->Port : 0;
UserName.Reset(s ? NewStr(s->UserName) : 0);
Password.Reset(s ? NewStr(s->Password) : 0);
}
void LSocks5Socket::SetProxy(char *proxy, int port, char *username, char *password)
{
Proxy.Reset(NewStr(proxy));
Port = port;
UserName.Reset(NewStr(username));
Password.Reset(NewStr(password));
}
int LSocks5Socket::Open(const char *HostAddr, int port)
{
bool Status = false;
if (HostAddr)
{
char Msg[256];
sprintf_s(Msg, sizeof(Msg), "[SOCKS5] Connecting to proxy server '%s'", HostAddr);
OnInformation(Msg);
Status = LSocket::Open(Proxy, Port) != 0;
if (Status)
{
char Buf[1024];
Buf[0] = SOCKS5_VER;
Buf[1] = 2; // methods
Buf[2] = SOCKS5_AUTH_NONE;
Buf[3] = SOCKS5_AUTH_USER_PASS;
// No idea how to implement this.
// AuthReq[3] = SOCKS5_AUTH_GSSAPI;
OnInformation("[SOCKS5] Connected, Requesting authentication type.");
LSocket::Write(Buf, 4, 0);
if (LSocket::Read(Buf, 2, 0) == 2)
{
if (Buf[0] == SOCKS5_VER)
{
bool Authenticated = false;
switch (Buf[1])
{
case SOCKS5_AUTH_NONE:
{
Authenticated = true;
OnInformation("[SOCKS5] No authentication needed.");
break;
}
case SOCKS5_AUTH_USER_PASS:
{
OnInformation("[SOCKS5] User/Pass authentication needed.");
if (UserName && Password)
{
char *b = Buf;
*b++ = 1; // ver of sub-negotiation ??
size_t NameLen = strlen(UserName);
LAssert(NameLen < 0x80);
*b++ = (char)NameLen;
b += sprintf_s(b, NameLen+1, "%s", UserName.Get());
size_t PassLen = strlen(Password);
LAssert(PassLen < 0x80);
*b++ = (char)PassLen;
b += sprintf_s(b, PassLen+1, "%s", Password.Get());
LSocket::Write(Buf, (int)(3 + NameLen + PassLen));
if (LSocket::Read(Buf, 2, 0) == 2)
{
Authenticated = (Buf[0] == 1 && Buf[1] == 0);
}
if (!Authenticated)
{
OnInformation("[SOCKS5] User/Pass authentication failed.");
}
}
break;
}
}
if (Authenticated)
{
OnInformation("[SOCKS5] Authentication successful.");
int HostPort = htons(port);
// Header
char *b = Buf;
*b++ = SOCKS5_VER;
*b++ = SOCKS5_CMD_CONNECT;
*b++ = 0; // reserved
long IpAddr = inet_addr(HostAddr);
if (IpAddr != -1)
{
// Ip
*b++ = SOCKS5_ADDR_IPV4;
memcpy(b, &IpAddr, 4);
b += 4;
}
else
{
// Domain Name
*b++ = SOCKS5_ADDR_DOMAIN;
size_t Len = strlen(HostAddr);
LAssert(Len < 0x80);
*b++ = (char)Len;
strcpy_s(b, Buf+sizeof(Buf)-b, HostAddr);
b += Len;
}
// Port
memcpy(b, &HostPort, 2);
b += 2;
LSocket::Write(Buf, (int)(b - Buf), 0);
if (LSocket::Read(Buf, 10, 0) == 10)
{
if (Buf[0] == SOCKS5_VER)
{
switch (Buf[1])
{
case 0:
Socks5Connected = true;
OnInformation("[SOCKS5] Connected!");
break;
case 1:
OnInformation("[SOCKS5] General SOCKS server failure");
break;
case 2:
OnInformation("[SOCKS5] Connection not allowed by ruleset");
break;
case 3:
OnInformation("[SOCKS5] Network unreachable");
break;
case 4:
OnInformation("[SOCKS5] Host unreachable");
break;
case 5:
OnInformation("[SOCKS5] Connection refused");
break;
case 6:
OnInformation("[SOCKS5] TTL expired");
break;
case 7:
OnInformation("[SOCKS5] Command not supported");
break;
case 8:
OnInformation("[SOCKS5] Address type not supported");
break;
default:
OnInformation("[SOCKS5] Unknown SOCKS server failure");
break;
}
}
else
{
OnInformation("[SOCKS5] Wrong socks version.");
}
}
else
{
OnInformation("[SOCKS5] Connection request read failed.");
}
}
else
{
OnInformation("[SOCKS5] Not authenticated.");
}
}
else
{
OnInformation("[SOCKS5] Wrong socks version.");
}
}
else
{
OnInformation("[SOCKS5] Authentication type read failed.");
}
Status = Socks5Connected;
if (!Status)
{
LSocket::Close();
OnInformation("[SOCKS5] Failure: Disconnecting.");
}
}
}
return Status;
}