using System; using System.Collections; using System.Drawing; using System.Drawing.Drawing2D; namespace Netron.Diagramming.Core { /// /// This static class collects functions related to bundle selection /// public class Selection { public Selection(IController controller, IModel model) { this.mController = controller; this.mModel = model; } #region Events public event EventHandler OnNewSelection; #endregion #region Fields // ------------------------------------------------------------------ /// /// Specifies the way entities are selected. /// // ------------------------------------------------------------------ private SelectionTypes mSelectionType = SelectionTypes.Partial; // ------------------------------------------------------------------ /// /// The selected entities. /// // ------------------------------------------------------------------ private CollectionBase mSelection = new CollectionBase(); // ------------------------------------------------------------------ /// /// A pointer to the model. /// // ------------------------------------------------------------------ private IModel mModel; private IController mController; // ------------------------------------------------------------------ /// /// A pointer to a selected connector. /// // ------------------------------------------------------------------ private IConnector connector; #endregion #region Properties // ------------------------------------------------------------------ /// /// Gets or sets the connector selected by the user. /// /// The connector. // ------------------------------------------------------------------ public IConnector Connector { get { return connector; } set { connector = value; } } // ------------------------------------------------------------------ /// /// Gets the Model we're attached to. /// // ------------------------------------------------------------------ public IModel Model { get { return mModel; } } public IController Controller { get { return mController; } } // ------------------------------------------------------------------ /// /// Gets or sets the selected items. /// /// The selected items. // ------------------------------------------------------------------ public CollectionBase SelectedItems { get { return mSelection; } internal set { if (value == null || value.Count == 0) return; //clear the current selection Clear(); mSelection = value; foreach (IDiagramEntity entity in value) { if (entity.Group != null) entity.Group.IsSelected = true; else entity.IsSelected = true; } } } // ------------------------------------------------------------------ /// /// Gets the selected items but in flat form, i.e. the entities /// inside an are collected. /// // ------------------------------------------------------------------ public CollectionBase FlattenedSelectionItems { get { CollectionBase flatList = new CollectionBase(); foreach (IDiagramEntity entity in mSelection) { if (entity is IGroup) Utils.TraverseCollect(entity as IGroup, ref flatList); else flatList.Add(entity); } return flatList; } } #endregion #region Methods // ------------------------------------------------------------------ /// /// Creates a Bitmap from the selected entities. If no entities are /// selected, then 'null' is returned. /// /// Bitmap // ------------------------------------------------------------------ public Bitmap ToBitmap() { if (SelectedItems.Count <= 0) { return null; } Graphics g = mController.View.Graphics; g.Transform = mController.View.ViewMatrix; g.SmoothingMode = SmoothingMode.HighQuality; Bundle bundle = new Bundle(SelectedItems.Copy()); return ImageExporter.FromBundle(bundle, g); } public IConnector FindConnector(Predicate predicate) { IConnection con; IShape sh; foreach (IDiagramEntity entity in Model.Paintables) { if (typeof(IShape).IsInstanceOfType(entity)) { sh = entity as IShape; foreach (IConnector cn in sh.Connectors) { if (predicate(cn)) return cn; } } else if (typeof(IConnection).IsInstanceOfType(entity)) { con = entity as IConnection; if (predicate(con.From)) return con.From; if (predicate(con.To)) return con.To; } } return null; } /// /// Finds the first connector with the highest z-order under the given point /// /// public IConnector FindShapeConnector(Point surfacePoint) { IShape sh; foreach (IDiagramEntity entity in Model.Paintables) { if (typeof(IShape).IsInstanceOfType(entity)) { sh = entity as IShape; foreach (IConnector cn in sh.Connectors) { if (cn.Hit(surfacePoint)) return cn; } } } return null; } public IConnector FindConnectorAt(Point surfacePoint) { IConnection con; IShape sh; foreach (IDiagramEntity entity in Model.Paintables) { if (entity is IShape) { sh = entity as IShape; foreach (IConnector cn in sh.Connectors) { if (cn.Hit(surfacePoint)) return cn; } } else if (entity is IConnection) { con = entity as IConnection; if (con.From.Hit(surfacePoint)) return con.From; if (con.To.Hit(surfacePoint)) return con.To; } } return null; } /// /// Collects the shapes at the given (transformed surface) location. /// The shapes selected in this way are available /// /// The surface point. public void CollectEntitiesAt( Point surfacePoint, bool clearSelectionFirst) { if (surfacePoint == Point.Empty) return; if (mModel == null) return; // Only change the current selection if the mouse did not hit an // already selected element and the element is not a group. This // allows drilling down into group's children. if (mSelection.Count > 0) { foreach (IDiagramEntity entity in mSelection) { if ((entity.Hit(surfacePoint)) && ((entity is IGroup) == false)) { return; } } } // Clear the current selection only if we're supposed to. if (clearSelectionFirst) { Clear(); } IConnection con; IShape sh; //we use the paintables here rather than traversing the scene-graph because only //visible things can be collected //We traverse the paintables from top to bottom since the highest z-order //is at the top of the stack. for (int k = Model.Paintables.Count - 1; k >= 0; k--) { IDiagramEntity entity = Model.Paintables[k]; #region we give priority to the connector selection if (typeof(IConnection).IsInstanceOfType(entity)) { con = entity as IConnection; if (con.From.Hit(surfacePoint)) { connector = con.From; connector.IsSelected = true; this.RaiseOnNewSelection(); Invalidate(); return; } if (con.To.Hit(surfacePoint)) { connector = con.To; connector.IsSelected = true; this.RaiseOnNewSelection(); Invalidate(); return; } } else if (entity is IGroup) { //should I care about the connectors at this point...? } else if (entity is IShape) { sh = entity as IShape; foreach (IConnector cn in sh.Connectors) { //if there are connectors attached to the shape connector, the attached ones should be picked up and not the one of the shape if (cn.Hit(surfacePoint) && cn.AttachedConnectors.Count == 0) { connector = cn; connector.IsSelected = true; this.RaiseOnNewSelection(); Invalidate();//this will invalidate only the selected connector return; //we hit a connector and quit the selection. If the user intended to select the entity it had to be away from the connector! } } } #endregion #region no connector was hit, maybe the entity itself if (entity.Hit(surfacePoint)) { SelectEntity(entity, surfacePoint); break; } #endregion } RaiseOnNewSelection(); // Using a full invalidate is rather expensive, so we'll only // refresh the current selection. //Controller.View.Invalidate(); Invalidate(); } internal void SelectEntity(IDiagramEntity entity, Point surfacePoint) { // Groups are treated specially because we can drill-down // into the group. The process of drilling is the first // mouse hit will select the group. The second mouse hit // will select a child, if there's a child at that point. if (entity is IGroup) { if (entity.IsSelected == false) { entity.IsSelected = true; mSelection.Add(entity); } else { IGroup group = entity as IGroup; for (int j = group.Entities.Count - 1; j >= 0; j--) { IDiagramEntity child = group.Entities[j]; if (child.Hit(surfacePoint)) { // Repeat the process because what if this // child is too a group! SelectEntity(child, surfacePoint); group.IsSelected = false; if (mSelection.Contains(group)) { mSelection.Remove(group); } break; } } } } //else if (entity.Group != null) //{ // //entity.Group.IsSelected = true; // //mSelection.Add(entity.Group); //} else { entity.IsSelected = true; mSelection.Add(entity); } } private void RaiseOnNewSelection() { if (OnNewSelection != null) OnNewSelection(null, EventArgs.Empty); } /// /// Invalidates the current selection (either a connector or a set of entities). /// public void Invalidate() { if (connector != null) connector.Invalidate(); foreach (IDiagramEntity entity in mSelection) { entity.Invalidate(); } } /// /// Collects the entities inside the given rectangle. /// /// The surface rectangle. public void CollectEntitiesInside(Rectangle surfaceRectangle) { if (surfaceRectangle == Rectangle.Empty) return; this.Clear(); foreach (IDiagramEntity entity in Model.Paintables) { //if the entity is part of a group we have to look at the bigger picture if (mSelectionType == SelectionTypes.Inclusion) { if (entity.Group != null) { //the rectangle must contain the whole group if (surfaceRectangle.Contains(entity.Group.Rectangle)) { //add the group if not already present via another group member if (!mSelection.Contains(entity.Group)) mSelection.Add(entity.Group); continue; } } else { if (surfaceRectangle.Contains(entity.Rectangle)) { mSelection.Add(entity); entity.IsSelected = true; } } } else //the selection requires only partial overlap with the rectangle { if (entity.Group != null) { if (surfaceRectangle.IntersectsWith(entity.Group.Rectangle)) { if (!mSelection.Contains(entity.Group)) mSelection.Add(entity.Group); continue; } } else { if (surfaceRectangle.IntersectsWith(entity.Rectangle)) { if (!mSelection.Contains(entity))//it could be a group which got already selected by one of its children { mSelection.Add(entity); entity.IsSelected = true; } } } } } RaiseOnNewSelection(); } /// /// Clears the current selection /// public void Clear() { if (connector != null) { connector.IsSelected = false; connector = null; } if (mController == null || mModel == null) return; //deselect the current ones foreach (IDiagramEntity entity in SelectedItems) { entity.IsSelected = false; } //forget the current state mSelection.Clear(); if (Controller.View != null) Controller.View.HideTracker(); this.RaiseOnNewSelection(); } #endregion } }