/// <summary> /// Inserts items via drag-and-drop</summary> /// <param name="e">DragEventArgs containing drag and drop event data</param> public void Insert(DragEventArgs e) { IEnumerable <ITimelineObject> itemCopies; if (m_timelineControl.DragDropObjects != null) { itemCopies = m_timelineControl.DragDropObjects; m_timelineControl.DragDropObjects = null; } else { itemCopies = ConvertDrop(e); } Point clientPoint = m_timelineControl.PointToClient(new Point(e.X, e.Y)); PointF mouseLocation = clientPoint; ITimelineObject dropTarget = Pick(mouseLocation); List <TimelinePath> newSelection = new List <TimelinePath>(); foreach (ITimelineObject droppedItem in itemCopies) { Insert(droppedItem, dropTarget); newSelection.Add(new TimelinePath(droppedItem)); } Selection.SetRange(newSelection.AsIEnumerable <object>()); }
/// <summary> /// Performs custom actions on DragEnter events</summary> /// <param name="e">DragEventArgs containing event data</param> protected virtual void OnDragEnter(DragEventArgs e) { var converted = new List <ITimelineObject>(ConvertDrop(e)); m_timelineControl.DragDropObjects = converted; Selection.Clear(); foreach (IEvent draggedEvent in converted.AsIEnumerable <IEvent>()) { Selection.Add(new TimelinePath(draggedEvent)); } }
/// <summary> /// Performs a picking operation and returns enumeration of Nodes intersecting the region</summary> /// <param name="pickRegion">Hit test region</param> /// <typeparam name="T">Type of objects intersecting pick region</typeparam> /// <returns>Objects of type T intersecting the pick region</returns> /// <remarks>The default implementation only returns intersecting Nodes, but derived /// classes can override this method to return Edges or EdgeRoutes as well.</remarks> public virtual IEnumerable <T> Pick <T>(Region pickRegion) where T : class { List <TNode> pickedGraphNodes = new List <TNode>(); using (Graphics g = AdaptedControl.CreateGraphics()) { RectangleF pickRect = pickRegion.GetBounds(g); pickRect = GdiUtil.InverseTransform(m_transformAdapter.Transform, pickRect); foreach (TNode node in m_graph.Nodes) { RectangleF nodeBounds = m_renderer.GetBounds(node, g); if (nodeBounds.IntersectsWith(pickRect)) { pickedGraphNodes.Add(node); } } } return(pickedGraphNodes.AsIEnumerable <T>()); }
/// <summary> /// Performs custom actions on validation Ending events</summary> /// <param name="sender">Validation context</param> /// <param name="e">Event args</param> protected override void OnEnding(object sender, EventArgs e) { var referenceValidator = DomNode.Cast<ReferenceValidator>(); referenceValidator.Suspended = false; if (m_undoingOrRedoing) { foreach (var subgraph in m_subGraphs.Where(x => x.Dirty).OrderByDescending(s => s.Level)) { subgraph.Dirty = false; // just reset Dirty flag foreach (var wire in subgraph.Wires) { // reset pin target wire.InputPinTarget = null; wire.OutputPinTarget = null; } subgraph.UpdateGroupPinInfo(); subgraph.OnChanged(EventArgs.Empty); // but notify the change } foreach (var circuit in m_circuits) { foreach (var wire in circuit.Wires) { // reset pin target wire.InputPinTarget = null; wire.OutputPinTarget = null; } circuit.Dirty = false; } return; } var containersToCheck = new List<ICircuitContainer>(); containersToCheck.AddRange(m_subGraphs.Where(g => g.Dirty).AsIEnumerable<ICircuitContainer>()); containersToCheck.AddRange(m_circuits.Where(g => g.Dirty).AsIEnumerable<ICircuitContainer>()); while (m_subGraphs.Any(n => n.Dirty) || m_circuits.Any(n => n.Dirty)) { // inner subgraphs updated first foreach (var subgraph in m_subGraphs.OrderByDescending(s => s.Level)) { subgraph.IgnoreFanInOut = MovingCrossContainer; subgraph.Update(); if (subgraph.IgnoreFanInOut) { subgraph.IgnoreFanInOut = false; subgraph.Dirty = true; } } UpdateWires(containersToCheck); MovingCrossContainer = false; foreach (var circuit in m_circuits) { circuit.Update(); } } // update group pin connectivity and other info from bottom up, for display purpose only foreach (var group in containersToCheck.AsIEnumerable<Group>().OrderByDescending(s => s.Level)) { group.UpdateGroupPinInfo(); } foreach (var subgraph in m_nodesInserted.Keys) { IEnumerable<Element> nodes = m_nodesInserted[subgraph]; if (nodes.Any()) { var viewingContext = subgraph.Cast<ViewingContext>(); if (viewingContext.Control != null) { var subGraphPinAdapter = viewingContext.Control.As<GroupPinEditor>(); if (subGraphPinAdapter != null) subGraphPinAdapter.AdjustLayout(nodes, EmptyEnumerable<GroupPin>.Instance, new Point(0, 0)); } } } }
private IList <GroupPinData> GetGroupPinChainData(D2dCircuitRenderer <TElement, TWire, TPin> circuitRender, Point worldOffset, D2dGraphics g) { var connection = DomNode.Cast <Wire>(); var dataPoints = new List <GroupPinData>(); if (circuitRender != null) { var relativePath = new List <ICircuitGroupType <TElement, TWire, TPin> >(); // --- edge starts from the output pin var group = connection.OutputElement.As <ICircuitGroupType <TElement, TWire, TPin> >(); foreach (var groupPin in connection.OutputPinSinkChain) { var pt = circuitRender.GetPinPositionCenterY(group.Cast <TElement>(), groupPin.Index, false, g); pt.Offset(worldOffset); pt.Offset(circuitRender.WorldOffset(relativePath.AsIEnumerable <TElement>())); relativePath.Add(group); dataPoints.Add(new GroupPinData { Pos = pt, Group = group, GroupPin = groupPin }); if (!group.Expanded) { break; } group = groupPin.InternalElement.As <ICircuitGroupType <TElement, TWire, TPin> >(); } dataPoints.Reverse(); // upward if (relativePath.Any()) { var startGroup = relativePath.Last(); if (startGroup.Expanded) // the edge starts from an expanded group pin, need to draw the virtual link { // the first data starts from sub-node/pin var firstGroupPin = dataPoints[0].GroupPin; Point p0 = circuitRender.GetPinPositionCenterY(firstGroupPin.InternalElement.Cast <TElement>(), firstGroupPin.InternalPinIndex, false, g); p0.Offset(worldOffset); p0.Offset(circuitRender.WorldOffset(relativePath.AsIEnumerable <TElement>())); dataPoints.Insert(0, new GroupPinData { Pos = p0 }); } } else // edge starts from a non-expanded node { Point p0 = circuitRender.GetPinPositionCenterY(connection.OutputElement.Cast <TElement>(), connection.OutputPin.Index, false, g); p0.Offset(worldOffset); dataPoints.Add(new GroupPinData { Pos = p0 }); } int numInputPoints = dataPoints.Count; dataPoints[dataPoints.Count - 1].IsReal = true; // --- edge ends at the input pin relativePath.Clear(); group = connection.InputElement.As <ICircuitGroupType <TElement, TWire, TPin> >(); foreach (var groupPin in connection.InputPinSinkChain) { var pt = circuitRender.GetPinPositionCenterY(group.Cast <TElement>(), groupPin.Index, true, g); pt.Offset(worldOffset); pt.Offset(circuitRender.WorldOffset(relativePath.AsIEnumerable <TElement>())); relativePath.Add(group); dataPoints.Add(new GroupPinData { Pos = pt, Group = group, GroupPin = groupPin }); if (!group.Expanded) { break; } group = groupPin.InternalElement.As <ICircuitGroupType <TElement, TWire, TPin> >(); } if (relativePath.Any()) { var lastGroupPin = dataPoints[dataPoints.Count - 1].GroupPin; var lastGroup = dataPoints[dataPoints.Count - 1].Group; if (lastGroup.Expanded) // the edge ends at an expanded group pin, need to draw the virtual link to subnode { // the last data ends at sub-node/pin Point pn = circuitRender.GetPinPositionCenterY(lastGroupPin.InternalElement.Cast <TElement>(), lastGroupPin.InternalPinIndex, true, g); pn.Offset(worldOffset); pn.Offset(circuitRender.WorldOffset(relativePath.AsIEnumerable <TElement>())); dataPoints.Add(new GroupPinData { Pos = pn }); } } else // edge ends at a non-expanded node { Point pn = circuitRender.GetPinPositionCenterY(connection.InputElement.Cast <TElement>(), connection.InputPin.Index, true, g); pn.Offset(worldOffset); dataPoints.Add(new GroupPinData { Pos = pn }); } dataPoints[numInputPoints].IsReal = true; } return(dataPoints); }
/// <summary> /// Creates an array of property descriptors that are associated with the adapted DomNode's /// DomNodeType. No duplicates will be in the array (based on the property descriptor's Name /// property).</summary> /// <returns>Array of property descriptors</returns> protected override System.ComponentModel.PropertyDescriptor[] GetPropertyDescriptors() { // Initialize property desciptors with the ones from the base class // If this is not done, the new property descriptors would be used instead of // rather than in addition to the ones defined in the schema List<System.ComponentModel.PropertyDescriptor> descriptors = new List<System.ComponentModel.PropertyDescriptor>(base.GetPropertyDescriptors()); // Add ITransformable properties: // Translation, Rotation, Scale, RotatePivot, ScalePivot (if supported by this object) ITransformable node = this.Cast<ITransformable>(); TransformationTypes transformType = node.TransformationType; NumericTupleEditor tupleEditor = new NumericTupleEditor(typeof(float), new string[] { "x", "y", "z" }); NumericTupleEditor rotationTupleEditor = new NumericTupleEditor(typeof(float), new string[] { "x", "y", "z" }); rotationTupleEditor.ScaleFactor = 360 / (2 * Math.PI); // Radians to Degrees string category = "Transform".Localize(); // Check for transform types if ((transformType & TransformationTypes.Translation) != 0) descriptors.Add( new AttributePropertyDescriptor( "Translation", Schema.transformObjectType.translateAttribute, category, "Translation of Game Object along X, Y, and Z axes".Localize(), false, tupleEditor)); if ((transformType & TransformationTypes.Rotation) != 0) descriptors.Add(new AttributePropertyDescriptor( "Rotation".Localize(), Schema.transformObjectType.rotateAttribute, category, "Origin of Rotation transform relative to Game Object Translation".Localize(), false, rotationTupleEditor)); if ((transformType & TransformationTypes.Scale) != 0) { if ((transformType & TransformationTypes.UniformScale) == 0) descriptors.Add( new AttributePropertyDescriptor( "Scale".Localize(), Schema.transformObjectType.scaleAttribute, category, "Scale of Game Object along X, Y, and Z axes".Localize(), false, tupleEditor)); else descriptors.Add( new AttributePropertyDescriptor( "Uniform Scale".Localize(), Schema.transformObjectType.scaleAttribute, category, "Scale of Game Object uniformly along X, Y, and Z axes".Localize(), false, new UniformArrayEditor<Single>())); } if ((transformType & TransformationTypes.Pivot) != 0) descriptors.Add( new AttributePropertyDescriptor( "Pivot".Localize(), Schema.transformObjectType.pivotAttribute, category, "Origin of Rotation and scale transform relative to Game Object Translation".Localize(), false, tupleEditor)); // remove hidden properties HashSet<string> hiddenProps = (HashSet<string>)this.DomNode.Type.GetTag(SchemaLoader.HiddenProperties); if (hiddenProps != null) { List<PropertyDescriptor> removeList = new List<PropertyDescriptor>(); foreach (AttributePropertyDescriptor propdescr in descriptors.AsIEnumerable<AttributePropertyDescriptor>()) { if (hiddenProps.Contains(propdescr.AttributeInfo.Name)) { removeList.Add(propdescr); } } foreach (PropertyDescriptor propDescr in removeList) descriptors.Remove(propDescr); } return descriptors.ToArray(); }
/// <summary> /// Creates an array of property descriptors that are associated with the adapted DomNode's /// DomNodeType. No duplicates will be in the array (based on the property descriptor's Name /// property).</summary> /// <returns>Array of property descriptors</returns> protected override System.ComponentModel.PropertyDescriptor[] GetPropertyDescriptors() { // Initialize property desciptors with the ones from the base class // If this is not done, the new property descriptors would be used instead of // rather than in addition to the ones defined in the schema List <System.ComponentModel.PropertyDescriptor> descriptors = new List <System.ComponentModel.PropertyDescriptor>(base.GetPropertyDescriptors()); // Add ITransformable properties: // Translation, Rotation, Scale, RotatePivot, ScalePivot (if supported by this object) ITransformable node = this.Cast <ITransformable>(); TransformationTypes transformType = node.TransformationType; NumericTupleEditor tupleEditor = new NumericTupleEditor(typeof(float), new string[] { "x", "y", "z" }); NumericTupleEditor rotationTupleEditor = new NumericTupleEditor(typeof(float), new string[] { "x", "y", "z" }); rotationTupleEditor.ScaleFactor = 360 / (2 * Math.PI); // Radians to Degrees FloatArrayConverter converter = new FloatArrayConverter(); FloatArrayConverter rotationConverter = new FloatArrayConverter(); rotationConverter.ScaleFactor = 360 / (2 * Math.PI); // Radians to Degrees string category = "Transform".Localize(); // Check for transform types if ((transformType & TransformationTypes.Translation) != 0) { descriptors.Add( new AttributePropertyDescriptor( "Translation", Schema.gameObjectType.translateAttribute, category, "Translation of Game Object along X, Y, and Z axes".Localize(), false, tupleEditor, converter)); } if ((transformType & TransformationTypes.Rotation) != 0) { descriptors.Add(new AttributePropertyDescriptor( "Rotation".Localize(), Schema.gameObjectType.rotateAttribute, category, "Origin of Rotation transform relative to Game Object Translation".Localize(), false, rotationTupleEditor, rotationConverter)); } if ((transformType & TransformationTypes.Scale) != 0) { if ((transformType & TransformationTypes.UniformScale) == 0) { descriptors.Add( new AttributePropertyDescriptor( "Scale".Localize(), Schema.gameObjectType.scaleAttribute, category, "Scale of Game Object along X, Y, and Z axes".Localize(), false, tupleEditor, converter)); } else { descriptors.Add( new AttributePropertyDescriptor( "Scale".Localize(), Schema.gameObjectType.scaleAttribute, category, "Scale of Game Object along X, Y, and Z axes".Localize(), false, new UniformArrayEditor <Single>(), new UniformFloatArrayConverter())); } } if ((transformType & TransformationTypes.Pivot) != 0) { descriptors.Add( new AttributePropertyDescriptor( "Pivot".Localize(), Schema.gameObjectType.pivotAttribute, category, "Origin of Rotation and scale transform relative to Game Object Translation".Localize(), false, tupleEditor, converter)); } // remove hidden properties HashSet <string> hiddenProps = (HashSet <string>) this.DomNode.Type.GetTag(SchemaLoader.HiddenProperties); if (hiddenProps != null) { List <PropertyDescriptor> removeList = new List <PropertyDescriptor>(); foreach (AttributePropertyDescriptor propdescr in descriptors.AsIEnumerable <AttributePropertyDescriptor>()) { if (hiddenProps.Contains(propdescr.AttributeInfo.Name)) { removeList.Add(propdescr); } } foreach (PropertyDescriptor propDescr in removeList) { descriptors.Remove(propDescr); } } return(descriptors.ToArray()); }
/// <summary> /// Inserts the data object into the context. /// Generic insert via IInstancingContext. Called from, for example, the standard paste command.</summary> /// <param name="insertingObject">Data to insert; e.g., System.Windows.Forms.IDataObject</param> public void Insert(object insertingObject) { IDataObject dataObject = (IDataObject)insertingObject; // use current document control to center the elements object[] items = dataObject.GetData(typeof(object[])) as object[]; if (items == null) { return; } IEnumerable <DomNode> sourceDomNodes = PathsToDomNodes(items); DomNode[] nodeCopies = DomNode.Copy(sourceDomNodes); var itemSources = new List <ITimelineObject>(sourceDomNodes.AsIEnumerable <ITimelineObject>()); var itemCopies = new List <ITimelineObject>(nodeCopies.AsIEnumerable <ITimelineObject>()); TimelineDocument document = DomNode.Cast <TimelineDocument>(); D2dTimelineControl timelineControl = document.TimelineControl; Rectangle clientRect = m_timelineControl.VisibleClientRectangle; Point clientPoint = new Point( clientRect.Left + clientRect.Width / 2, clientRect.Top + clientRect.Height / 2); CenterEvents(itemCopies.AsIEnumerable <IEvent>(), clientPoint, timelineControl.Transform); var sourceTargetPairs = new List <Tuple <ITimelineObject, ITimelineObject> >(itemSources.Count); for (int i = 0; i < itemSources.Count; i++) { sourceTargetPairs.Add(new Tuple <ITimelineObject, ITimelineObject>(itemSources[i], itemCopies[i])); } // Prepare the mapping of source objects to their tracks. These may be known already (from // a previous Copy operation), but we can't be sure, so let's augment m_copyObjToTrack if // possible. if (m_copyObjToTrack == null) { m_copyObjToTrack = new Dictionary <ITimelineObject, ITrack>(); } foreach (var source in itemSources) { IGroup group; ITrack track; GetTrackAndGroup(source, out track, out group); if (track != null) { m_copyObjToTrack[source] = track; } } // Guess where the user wants to paste. Priority: // 1. The timeline control's target (currently selected) track. ITrack targetTrack = timelineControl.TargetTrack != null ? (ITrack)m_timelineControl.TargetTrack.Last : null; // But only if it's visible. if (targetTrack != null) { if (!IsTrackVisible(targetTrack)) { targetTrack = null; } } // 2. The track in the center of the view. if (targetTrack == null) { ITimelineObject centerObject = Pick(clientPoint); IGroup centerGroup; GetTrackAndGroup(centerObject, out targetTrack, out centerGroup); } // 3. The first visible track if (targetTrack == null) { foreach (IGroup group in Timeline.Groups) { foreach (ITrack track in group.Tracks) { if (IsTrackVisible(track)) { targetTrack = track; break; } } if (targetTrack != null) { break; } } } Dictionary <ITimelineObject, ITrack> copiesToTracks = CreateTrackMappings( m_timelineDocument.Timeline, sourceTargetPairs, targetTrack, m_copyObjToTrack); foreach (ITimelineObject item in itemCopies) { // Not all items will have a track. The item could be a group, for example. ITrack dropTarget; copiesToTracks.TryGetValue(item, out dropTarget); Insert(item, dropTarget); } List <TimelinePath> newSelection = new List <TimelinePath>(); foreach (ITimelineObject copy in itemCopies) { // Would need a way to get the path of ITimelineReference objects.... if (TimelineControl.GetOwningTimeline(copy) != Timeline) { throw new NotImplementedException("We haven't implemented the ability to insert timeline objects into a sub-document"); } newSelection.Add(new TimelinePath(copy)); } Selection.SetRange(newSelection.AsIEnumerable <object>()); }
/// <summary> /// Performs custom actions on validation Ending events</summary> /// <param name="sender">Validation context</param> /// <param name="e">Event args</param> protected override void OnEnding(object sender, EventArgs e) { var referenceValidator = DomNode.As <ReferenceValidator>(); if (referenceValidator != null) { referenceValidator.Suspended = false; } if (m_undoingOrRedoing) { foreach (var subgraph in m_subGraphs.Where(x => x.Dirty).OrderByDescending(s => s.Level)) { subgraph.Dirty = false; // just reset Dirty flag foreach (var wire in subgraph.Wires) { // reset pin target wire.InputPinTarget = null; wire.OutputPinTarget = null; } subgraph.UpdateGroupPinInfo(); subgraph.OnChanged(EventArgs.Empty); // but notify the change } foreach (var circuit in m_circuits) { foreach (var wire in circuit.Wires) { // reset pin target wire.InputPinTarget = null; wire.OutputPinTarget = null; } circuit.Dirty = false; } return; } var containersToCheck = new List <ICircuitContainer>(); containersToCheck.AddRange(m_subGraphs.Where(g => g.Dirty).AsIEnumerable <ICircuitContainer>()); containersToCheck.AddRange(m_circuits.Where(g => g.Dirty).AsIEnumerable <ICircuitContainer>()); while (m_subGraphs.Any(n => n.Dirty) || m_circuits.Any(n => n.Dirty)) { // inner subgraphs updated first foreach (var subgraph in m_subGraphs.OrderByDescending(s => s.Level)) { subgraph.IgnoreFanInOut = MovingCrossContainer; subgraph.Update(); if (subgraph.IgnoreFanInOut) { subgraph.IgnoreFanInOut = false; subgraph.Dirty = true; } } UpdateWires(containersToCheck); MovingCrossContainer = false; foreach (var circuit in m_circuits) { circuit.Update(); } } // update group pin connectivity and other info from bottom up, for display purpose only foreach (var group in containersToCheck.AsIEnumerable <Group>().OrderByDescending(s => s.Level)) { group.UpdateGroupPinInfo(); } foreach (var subgraph in m_nodesInserted.Keys) { IEnumerable <Element> nodes = m_nodesInserted[subgraph]; if (nodes.Any()) { var viewingContext = subgraph.Cast <ViewingContext>(); if (viewingContext.Control != null) { var subGraphPinAdapter = viewingContext.Control.As <GroupPinEditor>(); if (subGraphPinAdapter != null) { subGraphPinAdapter.AdjustLayout(nodes, EmptyEnumerable <GroupPin> .Instance, new Point(0, 0)); } } } } }
/// <summary> /// Groups the specified GameObjects</summary> /// <param name="gobs">GameObjects to be grouped</param> /// <remarks>Creates a new GameObjectGroup and moves all /// the GameObjects into it.</remarks> public ITransformableGroup Group(IEnumerable <object> gobs) { // extra check. if (!CanGroup(gobs)) { return(null); } IGameObjectFolder root = null; var gameObjects = new List <ITransformable>(); foreach (var gameObject in gobs) { ITransformable trans = gameObject.As <ITransformable>(); if (trans == null) { continue; } var node = gameObject.As <DomNode>(); if (node == null) { continue; } gameObjects.Add(trans); var root1 = GetObjectFolder(node); if (root != null && root != root1) { return(null); } root = root1; } // sort from shallowest in the tree to deepest. Then remove any nodes that // are already children of other nodes in the list gameObjects.Sort( (lhs, rhs) => { return(lhs.As <DomNode>().Lineage.Count().CompareTo(rhs.As <DomNode>().Lineage.Count())); }); // awkward iteration here... Maybe there's a better way to traverse this list..? for (int c = 0; c < gameObjects.Count;) { var n = gameObjects[c].As <DomNode>(); bool remove = false; for (int c2 = 0; c2 < c; ++c2) { if (n.IsDescendantOf(gameObjects[c2].As <DomNode>())) { remove = true; break; } } if (remove) { gameObjects.RemoveAt(c); } else { ++c; } } // finally, we must have at least 2 valid objects to perform the grouping operation if (gameObjects.Count < 2) { return(null); } AABB groupBox = new AABB(); foreach (var gameObject in gameObjects.AsIEnumerable <IBoundable>()) { groupBox.Extend(gameObject.BoundingBox); } var group = root.CreateGroup(); if (group == null) { return(null); } group.As <DomNode>().InitializeExtensions(); // try to add the group to the parent of the shallowest item var groupParent = gameObjects[0].As <DomNode>().Parent.AsAll <IHierarchical>(); bool addedToGroupParent = false; foreach (var h in groupParent) { if (h.AddChild(group)) { addedToGroupParent = true; break; } } if (!addedToGroupParent) { return(null); } // arrange the transform for the group so that it's origin is in the center of the objects var groupParentTransform = new Matrix4F(); if (groupParent.Is <ITransformable>()) { groupParentTransform = groupParent.As <ITransformable>().LocalToWorld; } Matrix4F invgroupParentTransform = new Matrix4F(); invgroupParentTransform.Invert(groupParentTransform); group.Transform = Matrix4F.Multiply(new Matrix4F(groupBox.Center), invgroupParentTransform); Matrix4F invWorld = new Matrix4F(); invWorld.Invert(group.Transform); // now try to actually add the objects to the group var hier = group.AsAll <IHierarchical>(); foreach (var gameObject in gameObjects) { Matrix4F oldWorld = gameObject.LocalToWorld; bool addedToGroup = false; foreach (var h in hier) { if (h.AddChild(gameObject)) { addedToGroup = true; break; } } if (addedToGroup) { gameObject.Transform = Matrix4F.Multiply(oldWorld, invWorld); } } return(group); }