diff --git a/Lvc/Src/Main.cpp b/Lvc/Src/Main.cpp --- a/Lvc/Src/Main.cpp +++ b/Lvc/Src/Main.cpp @@ -1,726 +1,734 @@ #include "Lgi.h" #include "../Resources/resdefs.h" #include "Lvc.h" #include "GTableLayout.h" #ifdef WINDOWS #include "../Resources/resource.h" #endif #include "GTextLog.h" #include "GButton.h" #include "GXmlTreeUi.h" ////////////////////////////////////////////////////////////////// const char *AppName = "Lvc"; VersionCtrl DetectVcs(const char *Path) { char p[MAX_PATH]; if (!Path) return VcNone; if (LgiMakePath(p, sizeof(p), Path, ".git") && DirExists(p)) return VcGit; if (LgiMakePath(p, sizeof(p), Path, ".svn") && DirExists(p)) return VcSvn; if (LgiMakePath(p, sizeof(p), Path, ".hg") && DirExists(p)) return VcHg; if (LgiMakePath(p, sizeof(p), Path, "CVS") && DirExists(p)) return VcCvs; return VcNone; } class DiffView : public GTextLog { public: DiffView(int id) : GTextLog(id) { } void PourStyle(size_t Start, ssize_t Length) { for (auto ln : GTextView3::Line) { if (!ln->c.IsValid()) { char16 *t = Text + ln->Start; if (*t == '+') { ln->c = GColour::Green; ln->Back.Rgb(245, 255, 245); } else if (*t == '-') { ln->c = GColour::Red; ln->Back.Rgb(255, 245, 245); } else if (*t == '@') { ln->c.Rgb(128, 128, 128); ln->Back.Rgb(235, 235, 235); } else ln->c.Set(LC_TEXT, 24); } } } }; class ToolBar : public GLayout, public GLgiRes { public: ToolBar() { GAutoString Name; GRect Pos; if (LoadFromResource(IDD_TOOLBAR, this, &Pos, &Name)) { OnPosChange(); } else LgiAssert(!"Missing toolbar resource"); } void OnCreate() { AttachChildren(); } void OnPosChange() { GRect Cli = GetClient(); GTableLayout *v; if (GetViewById(IDC_TABLE, v)) { v->SetPos(Cli); v->OnPosChange(); auto r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); - GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y()+3)); + + // printf("Used = %s\n", r.GetStr()); + GCss::Len NewSz(GCss::LenPx, (float)r.Y()+3); + auto OldSz = GetCss(true)->Height(); + if (OldSz != NewSz) + { + GetCss(true)->Height(NewSz); + SendNotify(GNotifyTableLayout_Refresh); + } } else LgiAssert(!"Missing table ctrl"); } void OnPaint(GSurface *pDC) { pDC->Colour(LC_MED, 24); pDC->Rectangle(); } }; class CommitCtrls : public GLayout, public GLgiRes { public: CommitCtrls() { GAutoString Name; GRect Pos; if (LoadFromResource(IDD_COMMIT, this, &Pos, &Name)) { GTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) { v->GetCss(true)->PaddingRight("8px"); GRect r = v->GetPos(); r.Offset(-r.x1, -r.y1); r.x2++; v->SetPos(r); v->OnPosChange(); r = v->GetUsedArea(); if (r.Y() <= 1) r.Set(0, 0, 30, 30); GetCss(true)->Height(GCss::Len(GCss::LenPx, (float)r.Y())); } else LgiAssert(!"Missing table ctrl"); } else LgiAssert(!"Missing toolbar resource"); } void OnPosChange() { GTableLayout *v; if (GetViewById(IDC_COMMIT_TABLE, v)) v->SetPos(GetClient()); } void OnCreate() { AttachChildren(); } }; GString::Array GetProgramsInPath(const char *Program) { GString::Array Bin; GString Prog = Program; #ifdef WINDOWS Prog += LGI_EXECUTABLE_EXT; #endif GString::Array a = LGetPath(); for (auto p : a) { GFile::Path c(p, Prog); if (c.Exists()) Bin.New() = c.GetFull(); } return Bin; } class OptionsDlg : public GDialog, public GXmlTreeUi { GOptionsFile &Opts; public: OptionsDlg(GViewI *Parent, GOptionsFile &opts) : Opts(opts) { SetParent(Parent); Map("svn-path", IDC_SVN, GV_STRING); Map("git-path", IDC_GIT, GV_STRING); Map("hg-path", IDC_HG, GV_STRING); Map("cvs-path", IDC_CVS, GV_STRING); if (LoadFromResource(IDD_OPTIONS)) { MoveSameScreen(Parent); Convert(&Opts, this, true); } } void Browse(int EditId) { GFileSelect s; s.Parent(this); if (s.Open()) { SetCtrlName(EditId, s.Name()); } } void BrowseFiles(GViewI *Ctrl, const char *Bin, int EditId) { GRect Pos = Ctrl->GetPos(); GdcPt2 Pt(Pos.x1, Pos.y2 + 1); PointToScreen(Pt); GSubMenu s; GString::Array Bins = GetProgramsInPath(Bin); for (unsigned i=0; i= 1000) { GString Bin = Bins[Cmd - 1000]; if (Bin) SetCtrlName(EditId, Bin); } break; } } } int OnNotify(GViewI *Ctrl, int Flags) { switch (Ctrl->GetId()) { case IDC_SVN_BROWSE: BrowseFiles(Ctrl, "svn", IDC_SVN); break; case IDC_GIT_BROWSE: BrowseFiles(Ctrl, "git", IDC_GIT); break; case IDC_HG_BROWSE: BrowseFiles(Ctrl, "hg", IDC_HG); break; case IDC_CVS_BROWSE: BrowseFiles(Ctrl, "cvs", IDC_CVS); break; case IDOK: Convert(&Opts, this, false); // fall case IDCANCEL: { EndModal(Ctrl->GetId() == IDOK); break; } } return GDialog::OnNotify(Ctrl, Flags); } }; class CommitList : public LList { public: CommitList(int id) : LList(id, 0, 0, 200, 200) { } void SelectRevisions(GString::Array &Revs) { VcCommit *Scroll = NULL; for (auto it = begin(); it != end(); it++) { VcCommit *item = dynamic_cast(*it); if (item) { for (auto r: Revs) { if (item->IsRev(r)) { if (!Scroll) Scroll = item; item->Select(true); } } } } if (Scroll) Scroll->ScrollTo(); } bool OnKey(GKey &k) { switch (k.c16) { case 'p': case 'P': { if (k.Down()) { GArray Sel; GetSelection(Sel); if (Sel.Length()) { auto p = Sel[0]->GetParents(); if (p->Length() == 0) break; for (auto c:Sel) c->Select(false); SelectRevisions(*p); } } return true; } } return LList::OnKey(k); } }; class App : public GWindow, public AppPriv { GAutoPtr ImgLst; public: App() { GString AppRev; AppRev.Printf("%s v%s", AppName, APP_VERSION); Name(AppRev); GRect r(0, 0, 1400, 800); SetPos(r); MoveToCenter(); SetQuitOnClose(true); Opts.SerializeFile(false); SerializeState(&Opts, "WndPos", true); #ifdef WINDOWS SetIcon(MAKEINTRESOURCEA(IDI_ICON1)); #else SetIcon("icon32.png"); #endif ImgLst.Reset(LgiLoadImageList("image-list.png", 16, 16)); if (Attach(0)) { if ((Menu = new GMenu)) { Menu->SetPrefAndAboutItems(IDM_OPTIONS, IDM_ABOUT); Menu->Attach(this); Menu->Load(this, "IDM_MENU"); } GBox *ToolsBox = new GBox(IDC_TOOLS_BOX, true, "ToolsBox"); GBox *FoldersBox = new GBox(IDC_FOLDERS_BOX, false, "FoldersBox"); GBox *CommitsBox = new GBox(IDC_COMMITS_BOX, true, "CommitsBox"); ToolBar *Tools = new ToolBar; ToolsBox->Attach(this); Tools->Attach(ToolsBox); FoldersBox->Attach(ToolsBox); Tree = new GTree(IDC_TREE, 0, 0, 200, 200); Tree->GetCss(true)->Width(GCss::Len("320px")); Tree->ShowColumnHeader(true); Tree->AddColumn("Folder", 250); Tree->AddColumn("Counts", 50); Tree->Attach(FoldersBox); Tree->SetImageList(ImgLst, false); CommitsBox->Attach(FoldersBox); Lst = new CommitList(IDC_LIST); Lst->Attach(CommitsBox); Lst->GetCss(true)->Height("40%"); Lst->AddColumn("---", 40); Lst->AddColumn("Commit", 270); Lst->AddColumn("Author", 240); Lst->AddColumn("Date", 130); Lst->AddColumn("Message", 400); GBox *FilesBox = new GBox(IDC_FILES_BOX, false); FilesBox->Attach(CommitsBox); Files = new LList(IDC_FILES, 0, 0, 200, 200); Files->GetCss(true)->Width("35%"); Files->Attach(FilesBox); Files->AddColumn("[ ]", 30); Files->AddColumn("State", 100); Files->AddColumn("Name", 400); GBox *MsgBox = new GBox(IDC_MSG_BOX, true); MsgBox->Attach(FilesBox); CommitCtrls *Commit = new CommitCtrls; Commit->Attach(MsgBox); Commit->GetCss(true)->Height("25%"); if (Commit->GetViewById(IDC_MSG, Msg)) { GTextView3 *Tv = dynamic_cast(Msg); if (Tv) { Tv->Sunken(true); Tv->SetWrapType(TEXTED_WRAP_NONE); } } else LgiAssert(!"No ctrl?"); Tabs = new GTabView(IDC_TAB_VIEW); Tabs->Attach(MsgBox); const char *Style = "Padding: 0px 8px 8px 0px"; Tabs->GetCss(true)->Parse(Style); GTabPage *p = Tabs->Append("Diff"); p->Append(Diff = new DiffView(IDC_TXT)); // Diff->Sunken(true); Diff->SetWrapType(TEXTED_WRAP_NONE); p = Tabs->Append("Log"); p->Append(Log = new GTextLog(IDC_LOG)); // Log->Sunken(true); Log->SetWrapType(TEXTED_WRAP_NONE); AttachChildren(); Visible(true); } GXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (!f) { Opts.CreateTag(OPT_Folders); f = Opts.LockTag(OPT_Folders, _FL); } if (f) { bool Req[VcMax] = {0}; for (GXmlTag *c = f->Children.First(); c; c = f->Children.Next()) { if (c->IsTag(OPT_Folder)) { auto f = new VcFolder(this, c); Tree->Insert(f); if (!Req[f->GetType()]) { Req[f->GetType()] = true; f->GetVersion(); } } } Opts.Unlock(); } SetPulse(200); } ~App() { SerializeState(&Opts, "WndPos", false); GXmlTag *f = Opts.LockTag(OPT_Folders, _FL); if (f) { f->EmptyChildren(); VcFolder *vcf = NULL; bool b; while ((b = Tree->Iterate(vcf))) f->InsertTag(vcf->Save()); Opts.Unlock(); } Opts.SerializeFile(true); } int OnCommand(int Cmd, int Event, OsView Wnd) { switch (Cmd) { case IDM_OPTIONS: { OptionsDlg Dlg(this, Opts); Dlg.DoModal(); break; } case IDM_FIND: { GInput i(this, "", "Search string:"); if (i.DoModal()) { GString::Array Revs; Revs.Add(i.GetStr()); CommitList *cl; if (GetViewById(IDC_LIST, cl)) cl->SelectRevisions(Revs); } break; } } return 0; } void OnPulse() { VcFolder *vcf = NULL; bool b; while ((b = Tree->Iterate(vcf))) vcf->OnPulse(); } void OpenFolder() { GFileSelect s; s.Parent(this); if (s.OpenFolder()) { if (DetectVcs(s.Name())) Tree->Insert(new VcFolder(this, s.Name())); else LgiMsg(this, "Folder not under version control.", AppName); } } int OnNotify(GViewI *c, int flag) { switch (c->GetId()) { case IDC_FILES: { switch (flag) { case GNotifyItem_ColumnClicked: { int Col = -1; GMouse m; if (Files->GetColumnClickInfo(Col, m)) { if (Col == 0) { // Select / deselect all checkboxes.. List n; if (Files->GetAll(n)) { bool Checked = false; for (VcFile *f = n.First(); f; f = n.Next()) Checked |= f->Checked() > 0; for (VcFile *f = n.First(); f; f = n.Next()) f->Checked(Checked ? 0 : 1); } } } break; } } break; } case IDC_OPEN: { OpenFolder(); break; } case IDC_TREE: { switch (flag) { case GNotifyContainer_Click: { GMouse m; c->GetMouse(m); if (m.Right()) { GSubMenu s; s.AppendItem("Add", IDM_ADD); int Cmd = s.Float(c->GetGView(), m); switch (Cmd) { case IDM_ADD: { OpenFolder(); break; } } } break; } case LvcCommandStart: { SetCtrlEnabled(IDC_PUSH, false); SetCtrlEnabled(IDC_PUSH_ALL, false); SetCtrlEnabled(IDC_PULL, false); SetCtrlEnabled(IDC_PULL_ALL, false); break; } case LvcCommandEnd: { SetCtrlEnabled(IDC_PUSH, true); SetCtrlEnabled(IDC_PUSH_ALL, true); SetCtrlEnabled(IDC_PULL, true); SetCtrlEnabled(IDC_PULL_ALL, true); break; } } break; } case IDC_CLEAR_FILTER: { SetCtrlName(IDC_FILTER, NULL); // Fall through } case IDC_FILTER: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Select(true); break; } case IDC_COMMIT_AND_PUSH: case IDC_COMMIT: { const char *Msg = GetCtrlName(IDC_MSG); if (ValidStr(Msg)) { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) { char *Branch = GetCtrlName(IDC_BRANCH); bool AndPush = c->GetId() == IDC_COMMIT_AND_PUSH; f->Commit(Msg, ValidStr(Branch) ? Branch : NULL, AndPush); } } else LgiMsg(this, "No message for commit.", AppName); break; } case IDC_PUSH: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Push(); break; } case IDC_PULL: { VcFolder *f = dynamic_cast(Tree->Selection()); if (f) f->Pull(); break; } case IDC_PULL_ALL: { GArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->Pull(LogSilo); } break; } case IDC_STATUS: { GArray Folders; Tree->GetAll(Folders); for (auto f : Folders) { f->FolderStatus(); } break; } case IDC_HEADS: { if (flag == GNotifyValueChanged) { - GString Rev = c->Name(); + auto Revs = GString(c->Name()).SplitDelimit(); CommitList *cl; if (GetViewById(IDC_LIST, cl)) - cl->SelectRevisions(Rev.SplitDelimit()); + cl->SelectRevisions(Revs); } break; } } return 0; } }; ////////////////////////////////////////////////////////////////// int LgiMain(OsAppArguments &AppArgs) { GApp a(AppArgs, AppName); if (a.IsOk()) { a.AppWnd = new App; a.Run(); } return 0; } diff --git a/include/common/GBox.h b/include/common/GBox.h --- a/include/common/GBox.h +++ b/include/common/GBox.h @@ -1,51 +1,52 @@ #ifndef _GBOX_H_ #define _GBOX_H_ #include "GCss.h" /// This is a vertical or horizontal layout box, similar to the /// old GSplitter control except it can handle any number of children class LgiClass GBox : public GView { struct GBoxPriv *d; protected: public: struct Spacer { GRect Pos; // Position on screen in view coords GColour Colour; // Colour of the spacer uint32_t SizePx; // Size in pixels }; GBox(int Id = -1, bool Vertical = false, const char *name = NULL); ~GBox(); const char *GetClass() { return "GBox"; } bool IsVertical(); void SetVertical(bool v); Spacer *GetSpacer(int i); GViewI *GetViewAt(int i); bool SetViewAt(uint32_t i, GViewI *v); int64 Value(); void Value(int64 i); void OnCreate(); void OnPaint(GSurface *pDC); void OnPosChange(); void OnMouseClick(GMouse &m); bool OnViewMouse(GView *v, GMouse &m); void OnMouseMove(GMouse &m); void OnChildrenChanged(GViewI *Wnd, bool Attaching); GMessage::Result OnEvent(GMessage *Msg); bool Pour(GRegion &r); LgiCursor GetCursor(int x, int y); bool OnLayout(GViewLayoutInfo &Inf); + int OnNotify(GViewI *Ctrl, int Flags); bool Serialize(GDom *Dom, const char *OptName, bool Write); bool SetSize(int ViewIndex, GCss::Len Size); }; -#endif \ No newline at end of file +#endif diff --git a/src/common/Widgets/GBox.cpp b/src/common/Widgets/GBox.cpp --- a/src/common/Widgets/GBox.cpp +++ b/src/common/Widgets/GBox.cpp @@ -1,741 +1,754 @@ #include "Lgi.h" #include "GBox.h" #include "GCssTools.h" #include "LgiRes.h" -#include "GPopup.h" +#include "GPopup.h" +#include "GNotifications.h" #define DEFAULT_SPACER_PX 5 // #define DEFAULT_SPACER_COLOUR24 LC_MED #define DEFAULT_MINIMUM_SIZE_PX 5 #define ACTIVE_SPACER_SIZE_PX 9 enum GBoxMessages { M_CHILDREN_CHANGED = M_USER + 0x2000 }; struct GBoxPriv { public: bool Vertical; GArray Spacers; GBox::Spacer *Dragging; GdcPt2 DragOffset; bool Dirty; GBoxPriv() { Dirty = false; Vertical = false; Dragging = NULL; } int GetBox(GRect &r) { return Vertical ? r.Y() : r.X(); } GBox::Spacer *HitTest(int x, int y) { for (int i=0; iUnregisterHook(this); DeleteObj(d); } bool GBox::IsVertical() { return d->Vertical; } void GBox::SetVertical(bool v) { if (d->Vertical != v) { d->Vertical = v; OnPosChange(); } } GBox::Spacer *GBox::GetSpacer(int idx) { if (Children.Length()) { while (d->Spacers.Length() < Children.Length() - 1) { Spacer &s = d->Spacers.New(); s.SizePx = DEFAULT_SPACER_PX; // s.Colour.c24(DEFAULT_SPACER_COLOUR24); } } return idx >= 0 && idx < d->Spacers.Length() ? &d->Spacers[idx] : NULL; } GViewI *GBox::GetViewAt(int i) { return Children[i]; } bool GBox::SetViewAt(uint32_t i, GViewI *v) { if (!v || i > Children.Length()) { return false; } if (v->GetParent()) v->Detach(); v->Visible(true); bool Status; if (i < Children.Length()) { // Remove existing view.. GViewI *existing = Children[i]; if (existing == v) return true; if (existing) existing->Detach(); Status = AddView(v, i); } else { Status = AddView(v); } if (Status) { AttachChildren(); } return Status; } void GBox::OnCreate() { AttachChildren(); OnPosChange(); GWindow *Wnd = GetWindow(); if (Wnd) Wnd->RegisterHook(this, GMouseEvents); } bool GBox::OnViewMouse(GView *v, GMouse &m) { // This hook allows the GBox to catch clicks nearby the splits even if the splits are too small // to grab normally. Consider the case of a split that is 1px wide. The active region needs to // be a little larger than that, however a normal click would go through to the child windows // on either side of the split rather than to the GBox. if (!m.IsMove() && m.Down()) { // Convert click to the local coordinates of this view GMouse Local = m; while (v && v != (GView*)this && v->GetParent()) { if (dynamic_cast(v)) return true; GRect p = v->GetPos(); Local.x += p.x1; Local.y += p.y1; GViewI *vi = v->GetParent(); v = vi ? vi->GetGView() : NULL; } if (v == (GView*)this) { // Is the click over our spacers? Spacer *s = d->HitTest(Local.x, Local.y); if (s) { // Pass the click to ourselves and prevent the normal view from getting it. OnMouseClick(Local); return false; } } } return true; } bool GBox::Pour(GRegion &r) { GRect *p = FindLargest(r); if (!p) return false; SetPos(*p); return true; } void GBox::OnPaint(GSurface *pDC) { if (d->Dirty) { d->Dirty = false; OnPosChange(); } GRect cli = GetClient(); GCssTools tools(GetCss(), GetFont()); cli = tools.PaintBorderAndPadding(pDC, cli); GColour cBack = StyleColour(GCss::PropBackgroundColor, GColour(LC_MED, 24)); size_t ChildViews = Children.Length(); if (ChildViews == 0) { pDC->Colour(cBack); pDC->Rectangle(&cli); } else { #if 0 // coverage check... pDC->Colour(GColour(255, 0, 255)); pDC->Rectangle(&cli); #endif GRegion Painted(cli); for (int i=0; iSpacers.Length(); i++) { Spacer &s = d->Spacers[i]; if (s.Colour.IsValid()) pDC->Colour(s.Colour); else pDC->Colour(cBack); pDC->Rectangle(&s.Pos); Painted.Subtract(&s.Pos); } for (auto c : Children) Painted.Subtract(&c->GetPos()); for (auto r = Painted.First(); r; r = Painted.Next()) { pDC->Colour(cBack); pDC->Rectangle(r); } } } struct BoxRange { int Min, Max; GCss::Len Size; GViewI *View; BoxRange() { Min = Max = 0; View = NULL; } }; void GBox::OnPosChange() { GCssTools tools(GetCss(), GetFont()); GRect client = GetClient(); if (!client.Valid()) return; GRect content = tools.ApplyBorder(client); content = tools.ApplyPadding(content); GetSpacer(0); GAutoPtr views(IterateViews()); int Cur = content.x1, Idx = 0; int AvailablePx = d->GetBox(content); if (AvailablePx <= 0) return; GArray Sizes; int SpacerPx = 0; int FixedPx = 0; int FixedChildren = 0; int PercentPx = 0; int PercentChildren = 0; float PercentCount = 0.0f; int AutoChildren = 0; // Do first pass over children and find their sizes for (GViewI *c = views->First(); c; c = views->Next(), Idx++) { GCss *css = c->GetCss(); BoxRange &box = Sizes.New(); box.View = c; // Get any available CSS size if (css) { if (IsVertical()) box.Size = css->Height(); else box.Size = css->Width(); } // Work out some min and max values if (box.Size.IsValid()) { if (box.Size.Type == GCss::LenPercent) { box.Max = box.Size.ToPx(AvailablePx, GetFont()); PercentPx += box.Max; PercentCount += box.Size.Value; PercentChildren++; } else if (box.Size.IsDynamic()) { AutoChildren++; } else { // Fixed children get first crack at the space box.Min = box.Max = box.Size.ToPx(AvailablePx, GetFont()); FixedPx += box.Min; FixedChildren++; } } else AutoChildren++; // Allocate area for spacers in the Fixed portion if (Idx < Children.Length() - 1) { Spacer &s = d->Spacers[Idx]; SpacerPx += s.SizePx; } } // Convert all the percentage sizes to px int RemainingPx = AvailablePx - SpacerPx - FixedPx; for (int i=0; i RemainingPx) { if (AutoChildren > 0 || PercentChildren > 1) { // Well... ah... we better leave _some_ space for them. int AutoPx = 16 * AutoChildren; float Ratio = ((float)RemainingPx - AutoPx) / PercentPx; int Px = (int) (box.Max * Ratio); box.Size.Type = GCss::LenPx; box.Size.Value = (float) Px; RemainingPx -= Px; } else { // We can just take all the space... box.Size.Type = GCss::LenPx; box.Size.Value = (float) RemainingPx; RemainingPx = 0; } } else { box.Size.Type = GCss::LenPx; box.Size.Value = (float) box.Max; RemainingPx -= box.Max; } } } // Convert auto children to px int AutoPx = AutoChildren > 0 ? RemainingPx / AutoChildren : 0; for (int i=0; i 1) { box.Size.Value = (float) AutoPx; RemainingPx -= AutoPx; } else { box.Size.Value = (float) RemainingPx; RemainingPx = 0; } AutoChildren--; } } for (int i=0; iVertical) { viewPos.y1 = Cur; viewPos.y2 = Cur + Px - 1; } else { viewPos.x1 = Cur; viewPos.x2 = Cur + Px - 1; } box.View->SetPos(viewPos); #ifdef WIN32 // This forces the update, otherwise the child's display lags till the // mouse is released *rolls eyes* box.View->Invalidate((GRect*)NULL, true); #endif Cur += Px; // Allocate area for spacer if (i < Sizes.Length() - 1) { Spacer &s = d->Spacers[i]; s.Pos = content; if (d->Vertical) { s.Pos.y1 = Cur; s.Pos.y2 = Cur + s.SizePx - 1; } else { s.Pos.x1 = Cur; s.Pos.x2 = Cur + s.SizePx - 1; } Cur += s.SizePx; } } } void GBox::OnMouseClick(GMouse &m) { if (m.Down()) { d->Dragging = d->HitTest(m.x, m.y); if (d->Dragging) { d->DragOffset.x = m.x - d->Dragging->Pos.x1; d->DragOffset.y = m.y - d->Dragging->Pos.y1; Capture(d->Dragging != NULL); } } else if (IsCapturing()) { Capture(false); d->Dragging = NULL; } } bool IsValidLen(GCss *c, GCss::PropType p) { if (!c || c->GetType(p) != GCss::TypeLen) return false; GCss::Len *l = (GCss::Len*)c->PropAddress(p); if (!l) return false; return l->IsValid(); } void GBox::OnMouseMove(GMouse &m) { if (!d->Dragging || !IsCapturing()) return; if (!m.Down()) { // Something else got the up click? Capture(false); return; } int DragIndex = (int) (d->Dragging - &d->Spacers[0]); if (DragIndex < 0 || DragIndex >= d->Spacers.Length()) { LgiAssert(0); return; } GViewI *Prev = Children[DragIndex]; if (!Prev) { LgiAssert(0); return; } GViewI *Next = DragIndex < Children.Length() ? Children[DragIndex+1] : NULL; GCssTools tools(GetCss(), GetFont()); GRect Content = tools.ApplyMargin(GetClient()); int ContentPx = d->GetBox(Content); GRect SplitPos = d->Dragging->Pos; GCss *PrevStyle = Prev->GetCss(); GCss::PropType Style = d->Vertical ? GCss::PropHeight : GCss::PropWidth; bool EditPrev = !Next || IsValidLen(PrevStyle, Style); GViewI *Edit = EditPrev ? Prev : Next; LgiAssert(Edit != NULL); GRect ViewPos = Edit->GetPos(); auto *EditCss = Edit->GetCss(true); if (d->Vertical) { // Work out the minimum height of the view GCss::Len MinHeight = EditCss->MinHeight(); int MinPx = MinHeight.IsValid() ? MinHeight.ToPx(ViewPos.Y(), Edit->GetFont()) : DEFAULT_MINIMUM_SIZE_PX; int Offset = m.y - d->DragOffset.y - SplitPos.y1; if (Offset) { // Slide up and down the Y axis // Limit to the min size GRect r = ViewPos; if (EditPrev) { r.y2 += Offset; if (r.Y() < MinPx) { int Diff = MinPx - r.Y(); Offset += Diff; r.y2 += Diff; } } else { r.y1 += Offset; if (r.Y() < MinPx) { int Diff = MinPx - r.Y(); Offset -= Diff; r.y1 -= Diff; } } if (Offset) { SplitPos.Offset(0, Offset); // Save the new height of the view GCss::Len Ht = EditCss->Height(); if (Ht.Type == GCss::LenPercent && ContentPx > 0) { Ht.Value = (float)r.Y() * 100 / ContentPx; } else { Ht.Type = GCss::LenPx; Ht.Value = (float)r.Y(); } EditCss->Height(Ht); } } } else { // Work out the minimum width of the view GCss::Len MinWidth = EditCss->MinWidth(); int MinPx = MinWidth.IsValid() ? MinWidth.ToPx(ViewPos.X(), Edit->GetFont()) : DEFAULT_MINIMUM_SIZE_PX; int Offset = m.x - d->DragOffset.x - SplitPos.x1; if (Offset) { // Slide along the X axis // Limit to the min size GRect r = ViewPos; if (EditPrev) { r.x2 += Offset; int rx = r.X(); if (r.X() < MinPx) { int Diff = MinPx - rx; Offset += Diff; r.x2 += Diff; } } else { r.x1 += Offset; int rx = r.X(); if (r.X() < MinPx) { int Diff = MinPx - rx; Offset -= Diff; r.x1 -= Diff; } } if (Offset) { SplitPos.Offset(Offset, 0); // Save the new height of the view GCss::Len Wid = EditCss->Width(); if (Wid.Type == GCss::LenPercent && ContentPx > 0) { Wid.Value = (float)r.X() * 100 / ContentPx; } else { Wid.Type = GCss::LenPx; Wid.Value = (float)r.X(); } EditCss->Width(Wid); } } } OnPosChange(); Invalidate((GRect*)NULL, true); } + +int GBox::OnNotify(GViewI *Ctrl, int Flags) +{ + if (Flags == GNotifyTableLayout_Refresh) + { + d->Dirty = true; + if (Handle()) + PostEvent(M_CHILDREN_CHANGED); + } + + return GView::OnNotify(Ctrl, Flags); +} void GBox::OnChildrenChanged(GViewI *Wnd, bool Attaching) { #if 0 LgiTrace("GBox(%s)::OnChildrenChanged(%s, %i)\n", Name(), Wnd ? Wnd->GetClass() : NULL, Attaching); for (int i=0; iGetClass(), Children[i]->Handle(), Children[i]->Visible()); #endif d->Dirty = true; if (Handle()) PostEvent(M_CHILDREN_CHANGED); } int64 GBox::Value() { GViewI *v = Children.First(); if (!v) return 0; GCss *css = v->GetCss(); if (!css) return 0; GCss::Len l = d->Vertical ? css->Height() : css->Width(); if (l.Type != GCss::LenPx) return 0; return (int64)l.Value; } void GBox::Value(int64 i) { GViewI *v = Children.First(); if (!v) return; GCss *css = v->GetCss(true); if (!css) return; if (d->Vertical) css->Height(GCss::Len(GCss::LenPx, (float)i)); else css->Width(GCss::Len(GCss::LenPx, (float)i)); OnPosChange(); } LgiCursor GBox::GetCursor(int x, int y) { Spacer *Over = d->HitTest(x, y); if (Over) return (d->Vertical) ? LCUR_SizeVer : LCUR_SizeHor; else return LCUR_Normal; } bool GBox::OnLayout(GViewLayoutInfo &Inf) { Inf.Width.Min = -1; Inf.Width.Max = -1; Inf.Height.Min = -1; Inf.Height.Max = -1; return true; } bool GBox::Serialize(GDom *Dom, const char *OptName, bool Write) { if (Write) { } else { } LgiAssert(0); return false; } bool GBox::SetSize(int ViewIndex, GCss::Len Size) { GViewI *v = Children[ViewIndex]; if (!v) return false; GCss *c = v->GetCss(true); if (!c) return false; c->Width(Size); return true; } GMessage::Result GBox::OnEvent(GMessage *Msg) { if (Msg->Msg() == M_CHILDREN_CHANGED) { if (d->Dirty) { d->Dirty = false; OnPosChange(); } } return GView::OnEvent(Msg); }