#include "Docking.h" namespace Upp { #define ALIGN_ASSERT(al) ASSERT(al >= 0 && al < 4) #define FRAME_MOVE_DIV 5 // Outside fraction of the highlight that the mouse must be in to trigger dockpane reordering #define VERSION 5 // Serialisation version /* * Public interface */ void DockWindow::State(int reason) { if (reason == Ctrl::OPEN) { if (!hideframe[0].GetParent()) DockLayout(); if (!init) { DockInit(); init = true; StopHighlight(false); } } TopWindow::State(reason); } bool DockWindow::Key(dword key, int count) { DoHotKeys(key); return TopWindow::Key(key, count); } void DockWindow::DoHotKeys(dword key) { if (!HasCloseButtons() || IsLocked()) return; for (int i = 0; i < dockers.GetCount(); i++) { if (dockers[i]->IsHotKey(key)) HideRestoreDocker(*dockers[i]); } } void DockWindow::Dock(int align, DockableCtrl& dc, int pos) { ALIGN_ASSERT(align); Register(dc); DockContainer(align, *GetReleasedContainer(dc), pos); } void DockWindow::Tabify(DockableCtrl& target, DockableCtrl& dc) { ASSERT(GetContainer(target)); Register(dc); DockAsTab(*GetContainer(target), dc); } void DockWindow::Float(DockableCtrl& dc, Point p) { if (dc.GetParent() && p.IsNullInstance()) p = GetScreenRect().TopLeft(); else Register(dc); FloatContainer(*GetReleasedContainer(dc), p); } void DockWindow::Float(DockableCtrl& dc, const char *title, Point p) { dc.Title(title); Float(dc, p); } void DockWindow::AutoHide(DockableCtrl& dc) { if (dc.IsAutoHide()) return; int align = GetDockAlign(dc); AutoHide(align == DOCK_NONE ? DOCK_TOP : align, dc); } void DockWindow::AutoHide(int align, DockableCtrl& dc) { ALIGN_ASSERT(align); Register(dc); DockCont *c = GetReleasedContainer(dc); c->StateAutoHide(*this); hideframe[align].AddCtrl(*c, dc.GetGroup()); } int DockWindow::FindDocker(const Ctrl *dc) { for (int i = 0; i < dockers.GetCount(); i++) if (dc == (Ctrl *) dockers[i]) return i; return -1; } DockableCtrl& DockWindow::Register(DockableCtrl& dc) { int ix = FindDocker(&dc); if (ix < 0) { ix = dockers.GetCount(); dockers.Add(&dc); dockerpos.Add();; } return *dockers[ix]; } void DockWindow::Deregister(const DockableCtrl& dc) { int ix = FindDocker(&dc); if (ix >= 0) { DockableCtrl &dc = *dockers[ix]; dockers.Remove(ix); Close(dc); dockerpos.Remove(ix); } for (int i = 0; i < ctrls.GetCount(); i++) { if (&dc == &ctrls[i]) { ctrls.Remove(i); break; } } } void DockWindow::Close(DockableCtrl& dc) { DockCont *c = GetContainer(dc); if (c && c->GetCount() > 1) { SaveDockerPos(dc); dc.Remove(); c->Layout(); return; } if (c) CloseContainer(*c); } void DockWindow::ActivateDockable(Ctrl& c) { int ix = FindDocker(&c); if (ix >= 0) Activate(*dockers[ix]); } void DockWindow::ActivateDockableChild(Ctrl& c) { Ctrl *p = c.GetParent(); int ix = -1; while (p && (ix = FindDocker(p)) < 0) p = p->GetParent(); if (ix >= 0) Activate(*dockers[ix]); } void DockWindow::Activate(DockableCtrl& dc) { if (dc.IsVisible() && dc.IsOpen()) return dc.TimedHighlight(200); DockCont *c = GetContainer(dc); if (!c) c = CreateContainer(dc); if (c->IsHidden()) RestoreDockerPos(dc); else if (c->IsAutoHide()) { for (int i = 0; i <= DOCK_BOTTOM; i++) if (hideframe[i].HasCtrl(*c)) hideframe[i].ShowAnimate(c); } else c->SetCursor(dc); } void DockWindow::SaveDockerPos(DockableCtrl& dc, PosInfo& pi) { // Get the container DockCont *cont = GetContainer(dc); if (!cont) { // Ctrl must be hidden pi = PosInfo(); return; } // Are we tabbed? pi.tabcont = (cont->GetCount() > 1) ? cont : NULL; // Find top DockCont in case of nesting DockCont *parent = GetContainer(*cont); while (parent) { cont = parent; parent = GetContainer(*cont); } // Save state pi.state = cont->GetDockState(); // determine context info StringStream s; switch (pi.state) { case DockCont::STATE_DOCKED: { int align = GetDockAlign(*cont); ALIGN_ASSERT(align); int ps = dockpane[align].GetChildIndex(cont); ASSERT(ps >= 0); Size sz = cont->GetSize(); s % align % ps % sz; break; } case DockCont::STATE_FLOATING: cont->SerializePlacement(s); break; case DockCont::STATE_AUTOHIDE: for (int i = 0; i < 4; i++) { int ix = hideframe[i].FindCtrl(*cont); if (ix >= 0) { s.Put(ix); break; } ASSERT(i != 3); // No alignment found! } break; default: return; } pi.data = s; } void DockWindow::SetDockerPosInfo(DockableCtrl& dc, const PosInfo& pi) { // Find PosInfo record for the ctrl int ix = FindDocker(&dc); if (ix < 0) return; dockerpos[ix] = pi; } void DockWindow::SaveDockerPos(DockableCtrl& dc) { // Find PosInfo record for the ctrl int ix = FindDocker(&dc); if (ix < 0) return; SaveDockerPos(dc, dockerpos[ix]); } void DockWindow::RestoreDockerPos(DockableCtrl& dc, bool savefirst) { // Find PosInfo record for the ctrl int ix = FindDocker(&dc); if (ix < 0) return; PosInfo pi = dockerpos[ix]; if (savefirst) SaveDockerPos(dc); if (pi.state == DockCont::STATE_NONE) { Float(dc); return; } // Read position based on state StringStream s(pi.data); switch (pi.state) { case DockCont::STATE_DOCKED: { int align, ps; Size sz; s % align % ps % sz; ALIGN_ASSERT(align); if (pi.tabcont && pi.tabcont->GetDockAlign() == align) DockAsTab(*~pi.tabcont, dc); else { DockCont *cont = GetReleasedContainer(dc); cont->StateDocked(*this); Dock0(align, *cont, ps, sz); } break; } case DockCont::STATE_FLOATING: { DockCont *cont = GetReleasedContainer(dc); cont->SerializePlacement(s); if (pi.tabcont && pi.tabcont->IsFloating()) DockAsTab(*~pi.tabcont, dc); else FloatContainer(*cont, Null, false); break; } case DockCont::STATE_AUTOHIDE: { AutoHide(s.Get(), dc); break; } default: NEVER(); } } void DockWindow::HideRestoreDocker(DockableCtrl& dc) { if (dc.IsHidden()) DockWindow::RestoreDockerPos(dc); else DockWindow::Close(dc); } void DockWindow::DockGroup(int align, String group, int pos) { ALIGN_ASSERT(align); bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group && IsDockAllowed(align, *dockers[i])) Dock(align, *dockers[i], pos); } void DockWindow::ForceDockGroup(int align, String group, int pos) { ALIGN_ASSERT(align); bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) Dock(align, *dockers[i], pos); } void DockWindow::FloatGroup(String group) { bool all = group == "All"; Point p = GetScreenRect().TopLeft(); Point inc(20, 20); for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) { p += inc; Float(*dockers[i], p); } } void DockWindow::AutoHideGroup(int align, String group) { ALIGN_ASSERT(align); bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) AutoHide(align, *dockers[i]); } void DockWindow::AutoHideGroup(String group) { bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) AutoHide(*dockers[i]); } void DockWindow::TabDockGroup(int align, String group, int pos) { if (DockCont *c = TabifyGroup(group)) { if (c->IsDockAllowed(align)) DockContainer(align, *c, pos); else FloatContainer(*c); } } void DockWindow::ForceTabDockGroup(int align, String group, int pos) { if (DockCont *c = TabifyGroup(group)) DockContainer(align, *c, pos); } void DockWindow::TabFloatGroup(String group) { if (DockCont *c = TabifyGroup(group)) FloatContainer(*c); } DockCont * DockWindow::TabifyGroup(String group) { DockCont *c = NULL; bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) { if (c) DockAsTab(*c, *dockers[i]); else c = GetReleasedContainer(*dockers[i]); } return c; } void DockWindow::CloseGroup(String group) { bool all = group == "All"; for (int i = 0; i < dockers.GetCount(); i++) if (all || dockers[i]->GetGroup() == group) Close(*dockers[i]); for (int i = 0; i < conts.GetCount(); i++) conts[i].Layout(); } /* * Container management */ DockCont * DockWindow::CreateContainer() { DockCont *dc = &conts.Add(); dc->StateNotDocked(this); SyncContainer(*dc); return dc; } DockCont * DockWindow::CreateContainer(DockableCtrl& dc) { DockCont *c = CreateContainer(); c->Add(dc); return c; } void DockWindow::DestroyContainer(DockCont& c) { for (int i = 0; i < conts.GetCount(); i++) if (&conts[i] == &c) return conts.Remove(i); } DockCont *DockWindow::GetReleasedContainer(DockableCtrl& dc) { DockCont *c = GetContainer(dc); if (c && c->GetCount() > 1) { SaveDockerPos(dc); dc.Remove(); c->RefreshLayout(); c = NULL; } if (c) Detach(*c); else c = CreateContainer(dc); return c; } void DockWindow::SyncContainer(DockCont& c) { // c.ToolWindow(childtoolwindows); c.Grouping(grouping); c.WindowButtons(menubtn, hidebtn, closebtn); c.SyncButtons(); c.Lock(IsLocked()); if (!tabalign) c.SyncTabs(TabBar::BOTTOM, tabtext); else { int align; if ((align = GetDockAlign(c)) == DOCK_NONE && c.IsAutoHide()) for (int i = 0; i < DOCK_BOTTOM; i++) if (hideframe[i].HasCtrl(c)) { align = i; break; } align = IsTB(align) ? TabBar::RIGHT : TabBar::BOTTOM; c.SyncTabs(align, tabtext); } } void DockWindow::SyncAll() { for (int i = 0; i < conts.GetCount(); i++) SyncContainer(conts[i]); } /* * Docking/Undocking helpers */ void DockWindow::Undock0(Ctrl& c, bool do_animatehl, int fsz, bool ishighlight) { int al = GetDockAlign(c); Ctrl *p = c.GetParent(); if (p != &dockpane[al]) { c.Remove(); if (p) { p->Layout(); p->RefreshLayout(); } } else { if (dockpane[al].GetFirstChild() == dockpane[al].GetLastChild()) fsz = 0; dockpane[al].Undock(c, do_animatehl, ishighlight); // TODO: fix nasty hack if (fsz >= 0) DoFrameSize(do_animatehl, al, fsz); } } void DockWindow::DoFrameSize(bool animate, int align, int targetsize) { if (!animate || !IsAnimatedFrames()) { dockframe[align].SetSize(targetsize); if (targetsize <= 0) dockframe[align].Hide(); } else { FrameAnim& a = frameanim[align]; if (a.inc) dockframe[align].SetSize(a.target); a.target = max(targetsize, 0); a.inc = (targetsize - dockframe[align].GetSize()) / dockpane[0].GetAnimMaxTicks(); if (!a.inc) a.inc = (targetsize > dockframe[align].GetSize()) ? 1 : -1; KillSetTimeCallback(-dockpane[0].GetAnimInterval(), THISBACK(FrameAnimateTick), TIMEID_ANIMATE); } } void DockWindow::FrameAnimateTick() { bool done = true; for (int i = 0; i < 4; i++) { FrameAnim& a = frameanim[i]; if (a.inc) { int sz = dockframe[i].GetSize() + a.inc; if (a.inc > 0 && sz >= a.target || a.inc < 0 && sz <= a.target) { sz = a.target; a.inc = 0; if (sz == 0) dockframe[i].Hide(); } else done = false; dockframe[i].SetSize(sz); } } if (done) KillTimeCallback(TIMEID_ANIMATE); } void DockWindow::Undock(DockCont& c) { if (!c.IsFloating() && !c.IsHidden()) { if (c.IsAutoHide()) { for (int i = 0; i < 4; i++) { int ix = hideframe[i].FindCtrl(c); if (ix >= 0) { hideframe[i].RemoveCtrl(ix); hideframe[i].RefreshFrame(); break; } } } else { int dock = GetDockAlign(c); if (dock >= 0 && dock < 4) { Ctrl *p = &c; while (p && p->GetParent() != &dockpane[dock]) p = p->GetParent(); ASSERT(p); bool v = !IsTB(dock); c.SyncUserSize(v, !v); } Undock0(c); } c.StateNotDocked(); } } void DockWindow::Unfloat(DockCont& c) { if (c.IsFloating()) { c.SyncUserSize(true, true); if (c.IsOpen() || c.IsPopUp()) c.Close(); c.StateNotDocked(); } } void DockWindow::DockAsTab(DockCont& target, DockableCtrl& dc) { DockCont *c = GetContainer(dc); if (c) { if (c->GetCount() == 1) CloseContainer(*c); else { Ctrl *c = dc.GetParent(); dc.Remove(); c->Layout(); } } target.Add(dc); } void DockWindow::Dock0(int align, Ctrl& c, int pos, bool do_animatehl, bool ishighlight) { Dock0(align, c, pos, CtrlBestSize(c, align), do_animatehl, ishighlight); } void DockWindow::Dock0(int align, Ctrl& c, int pos, Size sz, bool do_animatehl, bool ishighlight) { int fsz = IsTB(align) ? sz.cy : sz.cx; if (!dockframe[align].IsShown()) dockframe[align].Show(); if (fsz > dockframe[align].GetSize()) DoFrameSize(do_animatehl, align, fsz); dockpane[align].Dock(c, sz, pos, do_animatehl, ishighlight); } /* * Container docking */ void DockWindow::DockContainer(int align, DockCont& c, int pos) { Detach(c); c.StateDocked(*this); Dock0(align, c, pos); } void DockWindow::DockContainerAsTab(DockCont& target, DockCont& c, bool do_nested) { Detach(c); if (do_nested) { c.StateTabbed(*this); target << c; } else { target.AddFrom(c); DestroyContainer(c); } } void DockWindow::FloatContainer(DockCont& c, Point p, bool move) { ASSERT(IsOpen()); if (c.IsFloating()) return; Detach(c); c.StateFloating(*this); if (move) { Size best = CtrlBestSize(c, false); if (p.IsNullInstance()) p = GetScreenRect().CenterPoint() - best/2; c.SetClientRect(Rect(p, best)); } c.Open(this); } void DockWindow::FloatFromTab(DockCont& c, DockCont& tab) { Rect r = c.GetScreenRect(); tab.SetClientRect(r); tab.StateNotDocked(this); c.RefreshLayout(); tab.MoveBegin(); } void DockWindow::AutoHideContainer(int align, DockCont& c) { while (c.GetCount() && !c.IsAutoHide()) AutoHide(align, c.Get(0)); } void DockWindow::CloseContainer(DockCont& c) { c.SetAllDockerPos(); c.Clear(); Detach(c); DestroyContainer(c); } /* * Docking Interface helpers */ DockCont *DockWindow::GetMouseDockTarget() { Point mp = GetMousePos(); for (int i = 0; i < 4; i++) { if (dockpane[i].IsShown()) { Rect r = dockpane[i].GetScreenRect(); if (r.Contains(mp)) return dynamic_cast(dockpane[i].ChildFromPoint(mp -= r.TopLeft())); } } return NULL; } int DockWindow::GetDockAlign(const Ctrl& c) const { Ctrl *p = c.GetParent(); while (p && p->GetParent() != this) p = p->GetParent(); for (int i = 0; i < 4; i++) if (dockpane[i].GetParent() == p) return i; return DOCK_NONE; } int DockWindow::GetDockAlign(const Point& p) const { for (int i = 0; i < 4; i++) if (dockpane[i].IsVisible() && dockpane[i].GetScreenRect().Contains(p)) return i; return DOCK_NONE; } int DockWindow::GetAutoHideAlign(const DockCont& c) const { for (int i = 0; i < 4; i++) { if (hideframe[i].HasCtrl(c)) return i; } return DOCK_NONE; } Size DockWindow::CtrlBestSize(const Ctrl& c, int align, bool restrict) const { Size mn = c.GetMinSize(); Size mx = c.GetMaxSize(); Size std = c.GetStdSize(); if (restrict) { if (IsTB(align)) mx.cy = minmax(GetSize().cy/2, mn.cy, mx.cy); else mx.cx = minmax(GetSize().cx/2, mn.cx, mx.cx); } return minmax(std, mn, mx); } DockCont *DockWindow::FindDockTarget(DockCont& dc, int& dock) { Point p = GetMousePos(); Rect r = GetScreenView(); // IODO: This is not technically correct if the user has added additional frames. // Need to manually FrameAddSize for all frames up tolast dockpane DockCont *target = NULL; int align = DOCK_NONE; dock = DOCK_NONE; if (r.Contains(p)) { // Check for mouse near hidden dockpanes dock = GetPointAlign(p, r, true, true, true); if (dock != DOCK_NONE && dockpane[dock].IsVisible()) dock = DOCK_NONE; } else { // Check for mouse over a docked container target = GetMouseDockTarget(); if (target) { r = target->GetScreenRect(); dock = GetDockAlign(*target); align = GetPointAlign(p, r, IsTabbing(), IsTB(dock), !IsTB(dock)); } else return NULL; } if (dock != DOCK_NONE && (!dc.IsDockAllowed(dock) || IsPaneAnimating(dock) || IsFrameAnimating(dock)) || (dock == DOCK_NONE && !target)) { dock = DOCK_NONE; return NULL; } // Prepare for highlight if (target) { GetHighlightCtrl().bounds = GetAlignBounds(align, r, IsTabbing(), IsTB(dock), !IsTB(dock)); if (align == DOCK_NONE) dock = DOCK_NONE; // Tabbing // The following code handles the case of an insertion between two docked controls. In this case we must set // the highlight bounds to be a union of the bounds from each control. Very ugly. if (dock != DOCK_NONE) { Ctrl *c = IsTL(align) ? target->GetPrev() : target->GetNext(); if (c) { int opal = align > 1 ? align-2 : align+2; GetHighlightCtrl().bounds.Union(GetAlignBounds(opal, c->GetScreenRect(), IsTabbing())); } target = IsTL(align) ? target : dynamic_cast(target->GetNext()); } } else if (dock != DOCK_NONE) GetHighlightCtrl().bounds = GetAlignBounds(dock, r, true); return target; } Rect DockWindow::GetAlignBounds(int al, Rect r, bool center, bool allow_lr, bool allow_tb) { Size border = r.GetSize()/4; switch (al) { case DOCK_LEFT: r.right = r.left + (center ? border.cx : (GHalf_(r.left + r.right))); return r; case DOCK_TOP: r.bottom = r.top + (center ? border.cy : (GHalf_(r.top + r.bottom))); return r; case DOCK_RIGHT: r.left = r.right - (center ? border.cx : (GHalf_(r.left + r.right))); return r; case DOCK_BOTTOM: r.top = r.bottom - (center ? border.cy : (GHalf_(r.top + r.bottom))); return r; } if (allow_lr) r.DeflateHorz(border.cx); if (allow_tb) r.DeflateVert(border.cy); return r; } int DockWindow::GetPointAlign(const Point p, Rect r, bool center, bool allow_lr, bool allow_tb) { Size border = r.GetSize(); border.cx = allow_lr ? border.cx/4 : 0; border.cy = allow_tb ? border.cy/4 : 0; if (center && r.Deflated(border).Contains(p)) return DOCK_NONE; int q = GetQuad(p, r); int al = DOCK_NONE; if (q == 0 || q == 1) al = DOCK_TOP; else if (q == 2 || q == 3) al = DOCK_RIGHT; else if (q == 4 || q == 5) al = DOCK_BOTTOM; else if (q == 6 || q == 7) al = DOCK_LEFT; if (!allow_lr && (al == DOCK_LEFT || al == DOCK_RIGHT)) al = (q == 7 || q == 2) ? DOCK_TOP : DOCK_BOTTOM; else if (!allow_tb && (al == DOCK_TOP || al == DOCK_BOTTOM)) al = (q == 0 || q == 5) ? DOCK_LEFT : DOCK_RIGHT; return al; } int DockWindow::GetQuad(Point p, Rect r) /* Finds the quadrant in the rectangle for point p: ------- |\0|1/| |7\|/2| |-----| |6/|\3| |/5|4\| ------- */ { Point cp = r.CenterPoint(); p -= cp; cp -= r.TopLeft(); if (p.x <= 0) { // Left if (p.y <= 0) // Top-Left return ((p.y * cp.x)/cp.y > p.x) ? 7 : 0; else {// Bottom-Left p.x = -p.x; return ((p.y * cp.x)/cp.y > p.x) ? 5 : 6; } } else { // Right if (p.y <= 0) { // Top-Right p.x = -p.x; return ((p.y * cp.x)/cp.y > p.x) ? 2 : 1; } else // Bottom-Right return ((p.y * cp.x)/cp.y > p.x) ? 4 : 3; } } Rect DockWindow::GetFinalAnimRect(int align, Ctrl& c) { if (c.GetParent() != &dockpane[align]) // c is docked as a tab return c.GetScreenRect(); Rect r = dockpane[align].GetFinalAnimRect(c); r.Offset(dockpane[align].GetScreenRect().TopLeft()); if (IsFrameAnimating(align)) { switch (align) { case DOCK_LEFT: r.right = r.left + frameanim[align].target - 4; break; case DOCK_TOP: r.bottom = r.top + frameanim[align].target - 4; break; case DOCK_RIGHT: r.left = r.right - frameanim[align].target - 4; break; case DOCK_BOTTOM: r.top = r.bottom - frameanim[align].target - 4; break; } } return r; } // HighlightCtrl void DockWindow::HighlightCtrl::Paint(Draw& w) { if (buffer.IsEmpty()) ChPaint(w, GetSize(), *highlight); else w.DrawImage(0, 0, buffer); } void DockWindow::HighlightCtrl::SetHighlight(const Value& hl, bool _isnested, bool _cannest, Image bg) { highlight = &hl; img = bg; buffer.Clear(); cannest = _cannest; isnested = cannest && _isnested; CreateBuffer(); } void DockWindow::HighlightCtrl::SetNested(bool _isnested) { bool nest = cannest && _isnested; if (nest != isnested) { isnested = nest; CreateBuffer(); } } void DockWindow::HighlightCtrl::CreateBuffer() { if (!img.IsEmpty()) { Size sz = img.GetSize(); ImageDraw w(sz); w.DrawImage(0, 0, img); if (isnested) { Rect r = sz; Rect t = r; const TabCtrl::Style& s = TabBar::StyleDefault(); t.bottom -= s.tabheight + s.sel.top + 1; // Nasty bodge! See TabBar::GetStyleHeight r.top = t.bottom-1; r.right = max(min(150, r.GetWidth()/3), 20); r.left += 10; ChPaint(w, r, *highlight); ChPaint(w, t, *highlight); } else ChPaint(w, sz, *highlight); buffer = w; Refresh(); } } // Drag and Drop interface void DockWindow::StartHighlight(DockCont *dc) { int align = DOCK_NONE; DockCont *target = FindDockTarget(*dc, align); if (align != DOCK_NONE || target) { dc->SyncUserSize(true, true); Highlight(align, *dc, target); } else StopHighlight(IsAnimatedHighlight()); } void DockWindow::Highlight(int align, DockCont& cont, DockCont *target) { HighlightCtrl& hl = GetHighlightCtrl(); DockableCtrl& dc = cont.GetCurrent(); ASSERT(&hl != (Ctrl *)&cont); hl.SizeHint(cont.GetMinSize(), cont.GetMaxSize(), cont.GetStdSize()).Show(); prehighlightframepos = -1; if (align != DOCK_NONE) { if (NeedFrameReorder(align)) DoFrameReorder(align); // Do highlight hl.SetHighlight(dc.GetStyle().highlight[0], false, 0); hl.oldframesize = dockframe[align].GetSize(); int pos = target ? dockpane[align].GetChildIndex(target) : -1; Dock0(align, hl, pos, IsAnimatedHighlight(), true); } else if (target && IsTabbing()) { hl.Title(cont.GetTitle(true)).Icon(dc.GetIcon()); hl.SetHighlight(dc.GetStyle().highlight[1], CheckNesting(), cont.GetCount() > 1, target->GetHighlightImage()); if (false && frameorder) { align = GetDockAlign(*target); ALIGN_ASSERT(align); if (NeedFrameReorder(align)) DoFrameReorder(align); } DockAsTab(*target, hl); } } void DockWindow::StopHighlight(bool do_animatehl) { HighlightCtrl& hl = GetHighlightCtrl(); if (hl.GetParent()) { UndoFrameReorder(); Undock0(hl, do_animatehl, hl.oldframesize, true); hl.ClearHighlight(); } } void DockWindow::DoFrameReorder(int align) { // Reorder dockpanes int ix = FindFrame(dockframe[align]); if (ix != dockframepos) { prehighlightframepos = ix; RemoveFrame(ix); InsertFrame(dockframepos, dockframe[align]); } } void DockWindow::UndoFrameReorder() { if (IsReorderingFrames()) { CtrlFrame& c = GetFrame(dockframepos); RemoveFrame(dockframepos); InsertFrame(prehighlightframepos, c); prehighlightframepos = -1; } } bool DockWindow::NeedFrameReorder(int align) { // Determine if we need to re-order the dockpanes to set layout precedence if (!frameorder) return false; switch (align) { case DOCK_LEFT: return GetMousePos().x < (GetHighlightCtrl().bounds.left + GetHighlightCtrl().bounds.Width() / FRAME_MOVE_DIV); case DOCK_RIGHT: return GetMousePos().x > (GetHighlightCtrl().bounds.right - GetHighlightCtrl().bounds.Width() / FRAME_MOVE_DIV); case DOCK_TOP: return GetMousePos().y < (GetHighlightCtrl().bounds.top + GetHighlightCtrl().bounds.Height() / FRAME_MOVE_DIV); case DOCK_BOTTOM: return GetMousePos().y > (GetHighlightCtrl().bounds.bottom - GetHighlightCtrl().bounds.Height() / FRAME_MOVE_DIV); } return false; } void DockWindow::ContainerDragStart(DockCont& dc) { if (!dc.IsFloating()) { // Reposition if not under the mouse Detach(dc); dc.StateFloating(*this); Rect r = dc.GetScreenView(); r.SetSize(CtrlBestSize(dc, false)); dc.SyncUserSize(true, true); if (IsAnimatedHighlight() && dc.IsDocked() && dc.GetParent()) { Undock0(dc, true); dc.StateNotDocked(); } dc.PlaceClientRect(r); if(!dc.IsOpen()) dc.Open(this); dc.StartMouseDrag(); } } void DockWindow::ContainerDragMove(DockCont& dc) { HighlightCtrl& hl = GetHighlightCtrl(); Point p = GetMousePos(); if (hl.GetParent()) { if (!hl.bounds.Contains(p)) StopHighlight(IsAnimatedHighlight()); else { if (frameorder) { int align = GetDockAlign(hl); bool needed = (hl.GetParent() == &dockpane[align]) && NeedFrameReorder(align); if (!needed && IsReorderingFrames()) UndoFrameReorder(); else if (needed && !IsReorderingFrames()) DoFrameReorder(align); } hl.SetNested(CheckNesting()); } return KillTimeCallback(TIMEID_ANIMATE_DELAY); } animdelay ? KillSetTimeCallback(animdelay, THISBACK1(StartHighlight, &dc), TIMEID_ANIMATE_DELAY) : StartHighlight(&dc); } void DockWindow::ContainerDragEnd(DockCont& dc) { KillTimeCallback(TIMEID_ANIMATE_DELAY); if (!dc.IsFloating()) return; HighlightCtrl& hl = GetHighlightCtrl(); Ctrl *p = hl.GetParent(); int align = DOCK_NONE; if (p) for (int i = 0; i < 4; i++) if (p == &dockpane[i]) align = i; if (animatewnd && (p || align != DOCK_NONE)) dc.Animate(GetFinalAnimRect(align, hl), dockpane[0].GetAnimMaxTicks(), 5); if (align != DOCK_NONE) { Unfloat(dc); dc.StateDocked(*this); dockpane[align].Swap(hl, dc); dc.SetFocus(); } else if (DockCont *target = dynamic_cast(p)) { StopHighlight(false); DockContainerAsTab(*target, dc, CheckNesting() && dc.GetCount() > 1); } else StopHighlight(false); } /* * Misc */ void DockWindow::LockLayout(bool lock) { locked = lock; SyncAll(); } DockWindow& DockWindow::TabAutoAlign(bool al) { tabalign = al; SyncAll(); return *this; } DockWindow& DockWindow::TabShowText(bool text) { tabtext = text; SyncAll(); return *this; } void DockWindow::SetFrameSize(int align, int size) { ALIGN_ASSERT(align); dockframe[align].SetSize(size); } DockWindow& DockWindow::Animate(bool highlight, bool frame, bool window, int ticks, int interval) { animatehl = highlight; animatewnd = window; animatefrm = frame; ticks = max(ticks, 1); interval = max(interval, 1); for (int i = 0; i < 4; i++) dockpane[i].SetAnimateRate(ticks, interval); return *this; } DockWindow& DockWindow::Grouping(bool _grouping) { if (_grouping != grouping) { grouping = _grouping; SyncAll(); } return *this; } DockWindow& DockWindow::SetFrameOrder(int first, int second, int third, int fourth) { if (fourth != DOCK_NONE) DoFrameReorder(fourth); if (third != DOCK_NONE) DoFrameReorder(third); if (second != DOCK_NONE) DoFrameReorder(second); if (first != DOCK_NONE) DoFrameReorder(first); RefreshLayout(); return *this; } DockWindow& DockWindow::AllowDockAll() { return AllowDockLeft().AllowDockRight().AllowDockTop().AllowDockBottom(); } DockWindow& DockWindow::AllowDockNone() { return AllowDockLeft(false).AllowDockRight(false).AllowDockTop(false).AllowDockBottom(false); } bool DockWindow::IsDockAllowed(int align, DockableCtrl& dc) const { ALIGN_ASSERT(align); return dockable[align] && dc.IsDockAllowed(align); } DockWindow& DockWindow::AutoHide(bool v) { if (v != autohide) { autohide = v; SyncAll(); } return *this; } DockWindow& DockWindow::WindowButtons(bool menu, bool hide, bool close) { if (menu == menubtn && close == closebtn && hide == hidebtn) return *this;; menubtn = menu; closebtn = close; hidebtn = hide; SyncAll(); return *this; } void DockWindow::DockLayout(bool tb_precedence) { if (hideframe[0].GetParent()) for (int i = 0; i < 4; i++) { RemoveFrame(hideframe[i]); RemoveFrame(dockframe[i]); } int i = tb_precedence ? 0 : 1; // Top, Bottom, Left, Right AddFrame(hideframe[1-i]); AddFrame(hideframe[3-i]); AddFrame(hideframe[0+i]); AddFrame(hideframe[2+i]); dockframepos = GetFrameCount(); AddFrame(dockframe[1-i]); AddFrame(dockframe[3-i]); AddFrame(dockframe[0+i]); AddFrame(dockframe[2+i]); } DockableCtrl& DockWindow::Dockable(Ctrl& ctrl, WString title) { DockableCtrl& dc = Register(ctrls.Add().SizeHint(ctrl.GetMinSize())); if (!IsNull(title)) dc.Title(title); dc << ctrl.SizePos(); return dc; } void DockWindow::DockManager() { DockConfigDlg(*this).Run(); } void DockWindow::DockWindowMenu(Bar& bar) { if (IsGrouping()) menu.GroupListMenu(bar); else menu.WindowListMenu(bar, t_("All")); if (dockers.GetCount()) bar.Separator(); if (layouts.GetCount()) { bar.Add(t_("Layouts"), callback(&menu, &DockMenu::LayoutListMenu)); bar.Separator(); } bar.Add(t_("Manage Windows..."), THISBACK(DockManager)); } void DockWindow::SerializeWindow(Stream& s) { int version = VERSION; int v = version; s / v; SerializeLayout(s, true); s % tabbing % autohide % animatehl % nestedtabs % grouping % menubtn % closebtn % hidebtn % nesttoggle; if (version >= 4) s % locked; if (version >= 5) s % frameorder; if (s.IsLoading()) { SyncAll(); init = true; } } void DockWindow::ClearLayout() { // Close everything for (int i = 0; i < conts.GetCount(); i++) CloseContainer(conts[i]); for (int i = 0; i < 4; i++) { dockpane[i].Clear(); hideframe[i].Clear(); frameanim[i].inc = 0; } conts.Clear(); } void DockWindow::SerializeLayout(Stream& s, bool withsavedlayouts) { StopHighlight(false); int cnt = 0; int version = VERSION; s.Magic(); s % version; // Groups ArrayMap > groups; if (s.IsStoring()) for (int i = 0; i < dockers.GetCount(); i++) { String g = dockers[i]->GetGroup(); if (!g.IsEmpty()) { int ix = groups.Find(g); if (ix < 0) { groups.Add(dockers[i]->GetGroup(), Vector()); ix = groups.GetCount() - 1; } groups[ix].Add(i); } } s % groups; if (s.IsLoading()) { ClearLayout(); for (int i = 0; i < dockers.GetCount(); i++) dockers[i]->SetGroup(Null); for (int i = 0; i < groups.GetCount(); i++) { Vector& v = groups[i]; const String& g = groups.GetKey(i); for (int j = 0; j < v.GetCount(); j++) { int ix = v[j]; if (ix >= 0 && ix < dockers.GetCount()) dockers[ix]->SetGroup(g); } } } if (s.IsStoring()) { // Write frame order for (int i = 0; i < 4; i++) { int ps = max(FindFrame(dockframe[i]) - dockframepos, 0); s % ps; } // Write docked for (int i = 0; i < 4; i++) { DockPane& pane = dockpane[i]; int fsz = dockframe[i].IsShown() ? dockframe[i].GetSize() : 0; s / fsz % pane; DockCont *dc = dynamic_cast(pane.GetFirstChild()); for (int j = 0; dc && j < pane.GetCount(); j++) { s % *dc; dc = dynamic_cast(dc->GetNext()); } } cnt = 0; // Count Floating for (int i = 0; i < conts.GetCount(); i++) if (conts[i].IsFloating()) cnt++; // Write Floating s / cnt; for (int i = 0; i < conts.GetCount(); i++) { if (conts[i].IsFloating()) { conts[i].Serialize(s); conts[i].SerializePlacement(s, false); } } // Write Autohidden for (int i = 0; i < 4; i++) { cnt = hideframe[i].GetCount(); s / cnt; for (int j = 0; j < hideframe[i].GetCount(); j++) { int ix = FindDocker(&hideframe[i].GetCtrl(j)->Get(0)); if (ix >= 0) s / ix; } } } else { // Read frame order if (version >= 5) { int ps[4]; for (int i = 0; i < 4; i++) { s % ps[i]; SetFrame(dockframepos + i, InsetFrame()); } for (int i= 0; i < 4; i++) SetFrame(ps[i] + dockframepos, dockframe[i]); } // Read docked for (int i = 0; i < 4; i++) { DockPane& pane = dockpane[i]; dockframe[i].Hide(); int fsz; s / fsz % pane; for (int j = 0; j < pane.GetCount(); j++) { DockCont *dc = CreateContainer(); s % *dc; dc->StateDocked(*this); pane << *dc; } if (fsz && pane.GetCount()) { dockframe[i].SetSize(fsz); dockframe[i].Show(); } else dockframe[i].SetSize(0); } // Read floating s / cnt; for (int i = 0; i < cnt; i++) { DockCont *dc = CreateContainer(); dc->Serialize(s); FloatContainer(*dc); dc->SerializePlacement(s, false); } // Read Autohidden for (int i = 0; i < 4; i++) { s / cnt; for (int j = 0; j < cnt; j++) { int ix; s / ix; if (ix >= 0 && ix < dockers.GetCount()) AutoHide(i, *dockers[ix]); } } // Clear empty containers for (int i = conts.GetCount()-1; i >= 0; i--) { if (!conts.GetCount()) CloseContainer(conts[i]); } RefreshLayout(); } bool haslay = withsavedlayouts; s % haslay; if (withsavedlayouts && (s.IsStoring() || haslay)) s % layouts; s.Magic(); } void DockWindow::BackupLayout() { StringStream s; SerializeLayout(s); layoutbackup = s; } void DockWindow::RestoreLayout() { if (!layoutbackup.GetCount()) return; StringStream s(layoutbackup); SerializeLayout(s); } int DockWindow::SaveLayout(String name) { ASSERT(!name.IsEmpty()); StringStream s; SerializeLayout(s, false); int ix = layouts.FindAdd(name, String()); layouts[ix] = (String)s; return ix; } void DockWindow::LoadLayout(String name) { int ix = layouts.Find(name); if (ix >= 0) LoadLayout(ix); } void DockWindow::LoadLayout(int ix) { ASSERT(ix >= 0 && ix < layouts.GetCount()); StringStream s((String)layouts[ix]); SerializeLayout(s, false); RefreshLayout(); } void DockWindow::EnableFloating(bool enable) { for (int i = 0; i < conts.GetCount(); i++) if (conts[i].IsFloating()) conts[i].Enable(enable); } DockWindow::DockWindow() { menu.Set(*this); #ifdef PLATFORM_WIN32 childtoolwindows = true; #endif init= false; tabbing = true; nestedtabs = false; grouping = true; menubtn = true; nesttoggle = (K_CTRL | K_SHIFT); closebtn = true; hidebtn = true; locked = false; tabtext = true; tabalign = false; autohide = true; frameorder = true; childtoolwindows = false; showlockedhandles = false; for (int i = 0; i < 4; i++) { dockframe[i].Set(dockpane[i], 0, i); IsTB(i) ? dockpane[i].Horz() : dockpane[i].Vert(); hideframe[i].SetAlign(i); dockframe[i].Hide(); } AllowDockAll().Animate().AnimateDelay(30); } // PopUpDockWindow #define POPUP_SPACING 0 void PopUpDockWindow::ContainerDragStart(DockCont& dc) { DockWindow::ContainerDragStart(dc); last_target = NULL; last_popup = NULL; } void PopUpDockWindow::ContainerDragMove(DockCont& dc) { int align = DOCK_NONE; // Is the highlight the same as last time? (Quick escape) if (last_popup && last_popup->IsPopUp() && last_popup->GetRect().Contains(GetMousePos())) return; DockCont *target = GetMouseDockTarget(); int dock = DOCK_NONE; if (target) { dock = GetDockAlign(*target); if (!dc.IsDockAllowed(dock)) target = NULL; } bool target_changed = (target != last_target) && !GetHighlightCtrl().GetParent() && (!target || !IsPaneAnimating(dock)); // Hide show inner popups as necessary if (!target && last_target != NULL) HidePopUps(true, false); else if (target_changed) ShowInnerPopUps(dc, target); ShowOuterPopUps(dc); last_target = target; // Get potential alignment align = PopUpHighlight(inner, 5); if (align == DOCK_NONE) { target = NULL; last_target = NULL; align = PopUpHighlight(outer, 4); } else if (align == 4) align = DOCK_NONE; else if (target) { target = IsTL(align) ? target : dynamic_cast(target->GetNext()); align = dock; } // Do highlight if (align != DOCK_NONE || target) { if (align == DOCK_NONE) StopHighlight(false); dc.SyncUserSize(true, true); Highlight(align, dc, target); } else { PopUpHighlight(hide, 4); StopHighlight(IsAnimatedHighlight()); last_popup = NULL; } } void PopUpDockWindow::ContainerDragEnd(DockCont& dc) { int align = DOCK_NONE; if (IsAutoHide() && showhide) align = PopUpHighlight(hide, 4); HidePopUps(true, true); if (align != DOCK_NONE) AutoHideContainer(align, dc); else DockWindow::ContainerDragEnd(dc); last_target = NULL; last_popup = NULL; } int PopUpDockWindow::PopUpHighlight(PopUpButton *pb, int cnt) { int ix = -1; Point p = GetMousePos(); for (int i = 0; i < cnt; i++) { if (pb[i].IsPopUp() && pb[i].GetRect().Contains(p)) { if (!pb[i].hlight) { pb[i].hlight = &(style->highlight); pb[i].Refresh(); } ix = i; last_popup = &pb[i]; } else if (pb[i].hlight) { pb[i].hlight = NULL; pb[i].Refresh(); } } return ix; } void PopUpDockWindow::ShowOuterPopUps(DockCont& dc) { Rect wrect = GetScreenRect(); Point cp = wrect.CenterPoint(); Size psz(style->outersize, style->outersize); Rect prect = Rect(psz); psz /= 2; wrect.Deflate(12, 12); if (dc.IsDockAllowed(DOCK_LEFT)) ShowPopUp(outer[DOCK_LEFT], prect.Offseted(wrect.left + POPUP_SPACING, cp.y - psz.cy)); if (dc.IsDockAllowed(DOCK_TOP)) ShowPopUp(outer[DOCK_TOP], prect.Offseted(cp.x - psz.cx, wrect.top + POPUP_SPACING)); if (dc.IsDockAllowed(DOCK_RIGHT)) ShowPopUp(outer[DOCK_RIGHT], prect.Offseted(wrect.right - POPUP_SPACING - psz.cx*2, cp.y - psz.cy)); if (dc.IsDockAllowed(DOCK_BOTTOM)) ShowPopUp(outer[DOCK_BOTTOM], prect.Offseted(cp.x - psz.cx, wrect.bottom - POPUP_SPACING - psz.cy*2)); if (IsAutoHide() && showhide) { ShowPopUp(hide[DOCK_LEFT], prect.Offseted(wrect.left + POPUP_SPACING + style->outersize, cp.y - psz.cy)); ShowPopUp(hide[DOCK_TOP], prect.Offseted(cp.x - psz.cx, wrect.top + POPUP_SPACING + style->outersize)); ShowPopUp(hide[DOCK_RIGHT], prect.Offseted(wrect.right - POPUP_SPACING - psz.cx*2 - style->outersize, cp.y - psz.cy)); ShowPopUp(hide[DOCK_BOTTOM], prect.Offseted(cp.x - psz.cx, wrect.bottom - POPUP_SPACING - psz.cy*2 - style->outersize)); } } void PopUpDockWindow::ShowInnerPopUps(DockCont& dc, DockCont *target) { ASSERT(target); Rect wrect = GetScreenRect(); Size psz(style->innersize, style->innersize); Rect prect = Rect(psz); Point cp; psz /= 2; cp = target->GetScreenRect().CenterPoint(); int d = psz.cy * 5; cp.x = minmax(cp.x, wrect.left + d, wrect.right - d); cp.y = minmax(cp.y, wrect.top + d, wrect.bottom - d); int align = GetDockAlign(*target); if (IsTB(align)) { // Left/right docking allowed ShowPopUp(inner[DOCK_LEFT], prect.Offseted(cp.x - psz.cx*3, cp.y - psz.cy)); ShowPopUp(inner[DOCK_RIGHT], prect.Offseted(cp.x + psz.cx, cp.y - psz.cy)); } else { inner[DOCK_LEFT].Close(); inner[DOCK_RIGHT].Close(); } if (!IsTB(align)) { // Top/bottom docking allowed ShowPopUp(inner[DOCK_TOP], prect.Offseted(cp.x - psz.cx, cp.y - psz.cy*3)); ShowPopUp(inner[DOCK_BOTTOM], prect.Offseted(cp.x - psz.cx, cp.y + psz.cy)); } else { inner[DOCK_TOP].Close(); inner[DOCK_BOTTOM].Close(); } if (IsTabbing()) ShowPopUp(inner[4], prect.Offseted(cp.x-psz.cx, cp.y-psz.cy)); } void PopUpDockWindow::ShowPopUp(PopUpButton& pb, const Rect& r) { pb.SetRect(r); if (!pb.IsPopUp()) pb.PopUp(this, false, true, true); } void PopUpDockWindow::HidePopUps(bool _inner, bool _outer) { if (_inner) for (int i = 0; i < 5; i++) inner[i].Close(); if (_outer) { for (int i = 0; i < 4; i++) outer[i].Close(); for (int i = 0; i < 4; i++) hide[i].Close(); } } PopUpDockWindow& PopUpDockWindow::SetStyle(const Style& s) { style = &s; for (int i = 0; i < 5; i++) inner[i].icon = &s.inner[i]; for (int i = 0; i < 4; i++) outer[i].icon = &s.outer[i]; for (int i = 0; i < 4; i++) hide[i].icon = &s.hide[i]; return *this; } PopUpDockWindow::PopUpDockWindow() : showhide(true) { SetStyle(StyleDefault()); AnimateDelay(0); } CH_STYLE(PopUpDockWindow, Style, StyleDefault) { inner[0] = DockingImg::DockLeft(); inner[1] = DockingImg::DockTop(); inner[2] = DockingImg::DockRight(); inner[3] = DockingImg::DockBottom(); inner[4] = DockingImg::DockTab(); outer[0] = DockingImg::DockLeft(); outer[1] = DockingImg::DockTop(); outer[2] = DockingImg::DockRight(); outer[3] = DockingImg::DockBottom(); hide[0] = DockingImg::HideLeft(); hide[1] = DockingImg::HideTop(); hide[2] = DockingImg::HideRight(); hide[3] = DockingImg::HideBottom(); highlight = DockingImg::DockHL(); innersize = 25; outersize = 25; } }