void AddSlotToTable(Slot slot)
 {
     slotTable[InternalGetSlotName(slot)] = new SlotData() { Slot = slot };
     foreach (var child in slot.Children)
     {
         AddSlotToTable(child);
     }
 }
        public Slot Clone()
        {
            var clone = new Slot
            {
                Name = this.Name,
                Length = this.Length,
                MinLength = this.MinLength,
                MaxLength = this.MaxLength,
                Orientation = this.Orientation,
            };

            foreach (var child in this.children)
            {
                clone.children.Add(child.Clone());
            }

            return clone;
        }
        Slot SplitSlot(Slot targetSlot, Dock dock)
        {
            Orientation orientation = (dock == Dock.Left || dock == Dock.Right) ? Orientation.Horizontal : Orientation.Vertical;
            Slot newSlot = new Slot() { Name = (this.nextSlotName++).ToString() };

            // Splitting a slot by simply dividing the target slot can create an undesirable "split tree" effect, where slots with the same orientation
            // are in a tree rather than an array.  This makes the splitters behave incorrectly because the side of the tree with (grand)children is sized as a
            // single unit and the change is proportionally distributed.  Slot children that have the same orientation should all be parented by the same node.
            //
            // To avoid a "split tree", there are 3 cases to handle in a slot split.
            //      1:  The slot HAS a parent slot with the same orientation as the requested split.  The new slot is inserted into the parent slot's children.
            //      2:  The slot IS a parent slot with the same orientation as the requested split.  The new slot is appended/prepended to this slot's children.
            //      3:  The slot gets replaced by a new parent slot with two children, one being the existing slot and the other being a newly created/returned slot.
            if (targetSlot != this.SlotDefinition && targetSlot.Parent.Orientation == orientation)
            {
                // This is case 1.  Insert the new slot into the parent, either before or after the target slot based on dock.
                double newSize = targetSlot.Parent.Children.Average(n => n.Length.Value);
                newSlot.Length = new GridLength(newSize, GridUnitType.Star);

                int index = targetSlot.Parent.Children.IndexOf(targetSlot);

                if (dock == Dock.Right || dock == Dock.Bottom)
                {
                    index += 1;
                }

                // Note, adding child slots to a parent automatically assigns the child's Parent property
                targetSlot.Parent.Children.Insert(index, newSlot);
            }
            else if (targetSlot.Children.Count > 0 && targetSlot.Orientation == orientation)
            {
                // This is case 2.  Insert the new slot into the target slot (at the beginning or end based on dock).
                double newSize = targetSlot.Children.Average(n => n.Length.Value);
                newSlot.Length = new GridLength(newSize, GridUnitType.Star);

                if (dock == Dock.Left || dock == Dock.Top)
                {
                    targetSlot.Children.Insert(0, newSlot);
                }
                else
                {
                    targetSlot.Children.Add(newSlot);
                }
            }
            else
            {
                // This is case 3.  Theoretically, we replace targetSlot with a new slot with two children, one being targetSlot,
                // and the other being the new slot created by the split.  In practice, though, we don't actually *replace* targetSlot
                // because it may be the root.  So what we *really* do is create two new slots: A (the new slot) and B (the new targetSlot).
                // Then rename targetSlot and move its children (if any) to slot B, and name slot B with targetSlot's old name.
                Slot newSlotB = new Slot { Name = targetSlot.Name, Orientation = targetSlot.Orientation };

                // Add the children from targetSlot to newSlotB first, which re-assigns Parent for each of them...
                foreach (var child in targetSlot.Children)
                {
                    newSlotB.Children.Add(child);
                }

                // And then remove them one-at-a-time (so the slot sees each one and disconnects.  .Clear does a reset)
                while (targetSlot.Children.Count > 0)
                {
                    targetSlot.Children.RemoveAt(targetSlot.Children.Count - 1);
                }

                // Rename targetSlot and set its orientation
                targetSlot.Name = (this.nextSlotName++).ToString();
                targetSlot.Orientation = orientation;

                // Add the new children to targetSlot
                if (dock == Dock.Left || dock == Dock.Top)
                {
                    targetSlot.Children.Add(newSlot);
                    targetSlot.Children.Add(newSlotB);
                }
                else
                {
                    targetSlot.Children.Add(newSlotB);
                    targetSlot.Children.Add(newSlot);
                }
            }

            return newSlot;
        }
        public static Slot LoadSlotFromState(XElement slotElement)
        {
            if (gridLengthConverter == null)
            {
                gridLengthConverter = TypeDescriptor.GetConverter(typeof(GridLength));
            }

            Slot slot = new Slot();

            slot.Name = slotElement.Attribute("Name").Value;
            slot.Length = (GridLength)gridLengthConverter.ConvertFromString(null, CultureInfo.InvariantCulture, slotElement.Attribute("Length").Value);
            slot.Orientation = (Orientation)Enum.Parse(typeof(Orientation), slotElement.Attribute("Orientation").Value);

            foreach (var child in slotElement.Elements("Slot"))
            {
                slot.Children.Add(LoadSlotFromState(child));
            }

            return slot;
        }
 public static XElement BuildSlotElement(Slot slot)
 {
     return new XElement("Slot",
         new XAttribute("Name", slot.Name),
         new XAttribute("Length", slot.Length),
         new XAttribute("Orientation", slot.Orientation),
         slot.Children.Select(c => BuildSlotElement(c)));
 }
        public ViewSource AddViewSource(IViewCreationCommand creator, Slot targetSlot, Dock? dock)
        {
            ViewSource source;

            if (creator == null)
            {
                return null;
            }

            if (this.ViewSources.Count == 0)
            {
                source = new ViewSource(this, this.nextViewId++, this.SlotDefinition.Name, creator);
                this.ViewSources.Add(source);

                var handler = this.PlaceholderModified;

                // Note that this is the only place where this event could fire (going from 0 to 1 sources)
                if (this.IsNewPlaceholder && handler != null)
                {
                    handler(this, EventArgs.Empty);
                }

                return source;
            }

            if (targetSlot == null)
            {
                return null;
            }

            Slot newSlot = targetSlot;

            if (dock.HasValue)
            {
                newSlot = SplitSlot(targetSlot, dock.Value);
            }

            source = new ViewSource(this, this.nextViewId++, newSlot.Name, creator);
            this.ViewSources.Add(source);

            RecomputeViewShortcutKeys();
            return source;
        }
        void UpdateHitState(Point pos)
        {
            LayoutControl targetLayout = this.CurrentLayout;
            bool ghostPositionSet = false;

            this.dockTargetSlot = null;
            this.dragGhostWindow.CancelReason = "To place the view, drag over an empty layout, an existing view, or a docking target.";

            if (targetLayout != null)
            {
                var posInLayout = Mouse.GetPosition(targetLayout);
                var rect = new Rect(0, 0, targetLayout.ActualWidth, targetLayout.ActualHeight);

                if (rect.Contains(posInLayout))
                {
                    this.dockTargetSlot = targetLayout.GetSlotUnderPoint(Mouse.GetPosition(targetLayout));
                }
                else
                {
                    targetLayout = null;
                }

                if (this.dockTargetSlot != null)
                {
                    if (this.creatorBeingDragged != null)
                    {
                        if (this.creatorBeingDragged.Category.DocumentFactoryName != null &&
                            targetLayout.LayoutInstance.LayoutDefinition.DocumentFactoryName != this.creatorBeingDragged.Category.DocumentFactoryName)
                        {
                            // The creator being dragged is affinitized with a particular document type which is not the same type as the active
                            // layout.  Disallow the drop.
                            this.dockTargetSlot = null;
                            this.dragGhostWindow.CancelReason = string.Format(CultureInfo.InvariantCulture, "This view can only be placed in a {0} layout.", this.creatorBeingDragged.Category.DisplayName);
                        }
                        else if (this.creatorBeingDragged.Command.IsSingleInstancePerLayout &&
                            (targetLayout.LayoutInstance.LayoutDefinition.ViewSources.Any(vs => vs.ViewCreator == this.creatorBeingDragged.Command)))
                        {
                            // This is a single-instance-per-layout view that already exists in the layout.  No dice.
                            this.dockTargetSlot = null;
                            this.dragGhostWindow.CancelReason = "Only one instance of this view type can exist in a layout.";
                        }
                        else if (this.creatorBeingDragged.Command.IsSingleInstance)
                        {
                            var existingSource = ToolsUIApplication.Instance.LayoutDefinitions.SelectMany(d => d.ViewSources).Where(vs => vs.ViewCreator == this.creatorBeingDragged.Command).FirstOrDefault();

                            if (existingSource != null)
                            {
                                // This is a single-instance view that already exists in a layout (perhaps not even this one).
                                this.dockTargetSlot = null;
                                this.dragGhostWindow.CancelReason = string.Format(CultureInfo.InvariantCulture,
                                    "This view already exists in the '{0}' layout.  Only one instance of this view type can be created.", existingSource.Parent.Header);
                            }
                        }
                    }
                }
            }

            if (this.dockTargetSlot != null)
            {
                this.layout = targetLayout;
                if (this.dockTargetWindow == null)
                {
                    this.dockTargetWindow = new ViewDropTargetWindow();
                }

                var rect = targetLayout.GetSlotScreenRect(this.dockTargetSlot);

                this.dockTargetWindow.Left = rect.X;
                this.dockTargetWindow.Top = rect.Y;
                this.dockTargetWindow.Width = rect.Width;
                this.dockTargetWindow.Height = rect.Height;
                this.dockTargetWindow.IsTabbedSpotVisible = true;
                this.dockTargetWindow.RootSlot = targetLayout.LayoutInstance.LayoutDefinition.SlotDefinition;
                this.dockTargetWindow.TargetSlot = this.dockTargetSlot;
                this.dockTargetWindow.AreDockSpotsVisible = targetLayout.LayoutInstance.LayoutDefinition.ViewSources.Count > 0;
                this.dockTargetWindow.Show();

                this.hitTestSpot = null;
                this.tabHitTesting = false;
                VisualTreeHelper.HitTest(this.dockTargetWindow, this.HitTestFilter, this.HitTestResult, new PointHitTestParameters(Mouse.GetPosition(this.dockTargetWindow)));
                if (this.hitTestSpot != null)
                {
                    var spot = this.hitTestSpot;

                    if (spot != null && spot.DestinationSlot != null)
                    {
                        rect = targetLayout.GetSlotScreenRect(spot.DestinationSlot);

                        this.dragGhostWindow.CancelMode = false;
                        this.dragGhostWindow.CancelReason = null;

                        if (spot.IsTabbed)
                        {
                            this.dragGhostWindow.Left = rect.X;
                            this.dragGhostWindow.Top = rect.Y;
                            this.dragGhostWindow.Width = rect.Width;
                            this.dragGhostWindow.Height = rect.Height;
                        }
                        else
                        {
                            switch (spot.Dock)
                            {
                                case Dock.Top:
                                    this.dragGhostWindow.Left = rect.X;
                                    this.dragGhostWindow.Top = rect.Y;
                                    this.dragGhostWindow.Width = rect.Width;
                                    this.dragGhostWindow.Height = rect.Height / 2;
                                    break;
                                case Dock.Bottom:
                                    this.dragGhostWindow.Width = rect.Width;
                                    this.dragGhostWindow.Height = rect.Height / 2;
                                    this.dragGhostWindow.Left = rect.X;
                                    this.dragGhostWindow.Top = rect.Y + this.dragGhostWindow.Height;
                                    break;
                                case Dock.Left:
                                    this.dragGhostWindow.Left = rect.X;
                                    this.dragGhostWindow.Top = rect.Y;
                                    this.dragGhostWindow.Width = rect.Width / 2;
                                    this.dragGhostWindow.Height = rect.Height;
                                    break;
                                case Dock.Right:
                                    this.dragGhostWindow.Width = rect.Width / 2;
                                    this.dragGhostWindow.Height = rect.Height;
                                    this.dragGhostWindow.Left = rect.X + this.dragGhostWindow.Width;
                                    this.dragGhostWindow.Top = rect.Y;
                                    break;
                            }
                        }

                        ghostPositionSet = true;
                    }
                }
                else
                {
                    // Didn't even hit the tabbed spot (the transparent rect covering the whole slot), so must be in
                    // the margin.  Tear down the dock target window.  (Won't flicker because it fades in...)
                    this.dockTargetWindow.Close();
                    this.dockTargetWindow = null;
                    this.dockTargetSlot = null;
                }
            }
            else
            {
                if (this.dockTargetWindow != null)
                {
                    this.dockTargetWindow.Close();
                    this.dockTargetWindow = null;
                }

                this.layout = null;

                // See if the mouse is over a layout tab.  If so, select it.
                this.tabHitTesting = true;
                this.hitTestTabItem = null;
                VisualTreeHelper.HitTest(this.tabControl, this.HitTestFilter, this.HitTestResult, new PointHitTestParameters(Mouse.GetPosition(this.tabControl)));

                if (this.hitTestTabItem != null)
                {
                    var layoutDef = this.hitTestTabItem.DataContext as LayoutDefinition;

                    if (layoutDef != null)
                    {
                        this.tabControl.SelectedItem = layoutDef;
                    }
                }
            }

            if (!ghostPositionSet)
            {
                this.dragGhostWindow.Width = 200;
                this.dragGhostWindow.Height = 200;
                this.dragGhostWindow.Left = pos.X;
                this.dragGhostWindow.Top = pos.Y;
                this.dragGhostWindow.CancelMode = true;
            }
        }
        public Rect GetSlotScreenRect(Slot slot)
        {
            if (this.slotPanel == null || slot == null)
            {
                return new Rect();
            }

            var upperLeft = this.slotPanel.PointToScreenIndependent(slot.UpperLeft);
            var lowerRight = this.slotPanel.PointToScreenIndependent(new Point(slot.UpperLeft.X + slot.ActualSize.Width, slot.UpperLeft.Y + slot.ActualSize.Height));

            return new Rect(upperLeft, lowerRight);
        }
        Slot SplitSlot(Slot targetSlot, Dock dock)
        {
            Orientation orientation = (dock == Dock.Left || dock == Dock.Right) ? Orientation.Horizontal : Orientation.Vertical;
            Slot        newSlot     = new Slot()
            {
                Name = (this.nextSlotName++).ToString()
            };

            // Splitting a slot by simply dividing the target slot can create an undesirable "split tree" effect, where slots with the same orientation
            // are in a tree rather than an array.  This makes the splitters behave incorrectly because the side of the tree with (grand)children is sized as a
            // single unit and the change is proportionally distributed.  Slot children that have the same orientation should all be parented by the same node.
            //
            // To avoid a "split tree", there are 3 cases to handle in a slot split.
            //      1:  The slot HAS a parent slot with the same orientation as the requested split.  The new slot is inserted into the parent slot's children.
            //      2:  The slot IS a parent slot with the same orientation as the requested split.  The new slot is appended/prepended to this slot's children.
            //      3:  The slot gets replaced by a new parent slot with two children, one being the existing slot and the other being a newly created/returned slot.
            if (targetSlot != this.SlotDefinition && targetSlot.Parent.Orientation == orientation)
            {
                // This is case 1.  Insert the new slot into the parent, either before or after the target slot based on dock.
                double newSize = targetSlot.Parent.Children.Average(n => n.Length.Value);
                newSlot.Length = new GridLength(newSize, GridUnitType.Star);

                int index = targetSlot.Parent.Children.IndexOf(targetSlot);

                if (dock == Dock.Right || dock == Dock.Bottom)
                {
                    index += 1;
                }

                // Note, adding child slots to a parent automatically assigns the child's Parent property
                targetSlot.Parent.Children.Insert(index, newSlot);
            }
            else if (targetSlot.Children.Count > 0 && targetSlot.Orientation == orientation)
            {
                // This is case 2.  Insert the new slot into the target slot (at the beginning or end based on dock).
                double newSize = targetSlot.Children.Average(n => n.Length.Value);
                newSlot.Length = new GridLength(newSize, GridUnitType.Star);

                if (dock == Dock.Left || dock == Dock.Top)
                {
                    targetSlot.Children.Insert(0, newSlot);
                }
                else
                {
                    targetSlot.Children.Add(newSlot);
                }
            }
            else
            {
                // This is case 3.  Theoretically, we replace targetSlot with a new slot with two children, one being targetSlot,
                // and the other being the new slot created by the split.  In practice, though, we don't actually *replace* targetSlot
                // because it may be the root.  So what we *really* do is create two new slots: A (the new slot) and B (the new targetSlot).
                // Then rename targetSlot and move its children (if any) to slot B, and name slot B with targetSlot's old name.
                Slot newSlotB = new Slot {
                    Name = targetSlot.Name, Orientation = targetSlot.Orientation
                };

                // Add the children from targetSlot to newSlotB first, which re-assigns Parent for each of them...
                foreach (var child in targetSlot.Children)
                {
                    newSlotB.Children.Add(child);
                }

                // And then remove them one-at-a-time (so the slot sees each one and disconnects.  .Clear does a reset)
                while (targetSlot.Children.Count > 0)
                {
                    targetSlot.Children.RemoveAt(targetSlot.Children.Count - 1);
                }

                // Rename targetSlot and set its orientation
                targetSlot.Name        = (this.nextSlotName++).ToString();
                targetSlot.Orientation = orientation;

                // Add the new children to targetSlot
                if (dock == Dock.Left || dock == Dock.Top)
                {
                    targetSlot.Children.Add(newSlot);
                    targetSlot.Children.Add(newSlotB);
                }
                else
                {
                    targetSlot.Children.Add(newSlotB);
                    targetSlot.Children.Add(newSlot);
                }
            }

            return(newSlot);
        }
 private static string InternalGetSlotName(Slot child)
 {
     return child.Name ?? "";
 }