using System; using System.IO; using System.Collections.Generic; using System.Text; using System.ComponentModel; using System.Windows.Forms; using System.Drawing; using System.Drawing.Printing; using System.Collections; namespace Netron.Diagramming.Core { /// /// Abstract base class for the various diagram control representations /// (WebForm and WinForm). /// [ToolboxItem(false)] public class DiagramControlBase : ScrollableControl, ISupportInitialize, IDiagramControl { #region Constants protected const string constGeneral = "General"; #endregion #region Events /// /// Occurs when the something got selected and the properties of it can/should be shown. /// [Category(constGeneral), Description("Occurs when the something got selected and the " + "properties of it can/should be shown."), Browsable(true)] public event EventHandler OnShowSelectionProperties; /// /// Occurs when the Properties have changed /// [Category(constGeneral), Description("Occurs when the properties have changed."), Browsable(true)] public event EventHandler OnShowDocumentProperties; /// /// Occurs when the properties of the canvas are exposed. /// public event EventHandler OnShowCanvasProperties; /// /// Occurs when an entity is added. /// This event usually is bubbled from one of the layers /// [Category(constGeneral), Description("Occurs when an entity is added."), Browsable(true)] public event EventHandler OnEntityAdded; /// /// Occurs when an entity is removed. /// This event usually is bubbled from one of the layers /// [Category(constGeneral), Description("Occurs when an entity is removed."), Browsable(true)] public event EventHandler OnEntityRemoved; /// /// Occurs on opening a file /// [Category(constGeneral), Description("Occurs when the control will open a file."), Browsable(true)] public event EventHandler OnOpeningDiagram; /// /// Occurs when a file was opened /// [Category(constGeneral), Description(" Occurs when a file was opened."), Browsable(true)] public event EventHandler OnDiagramOpened; /// /// Occurs when the history has changed in the undo/redo mechanism /// public event EventHandler OnHistoryChange; /// /// Occurs before saving a diagram /// [Category(constGeneral), Description("Occurs before saving a diagram."), Browsable(true)] public event EventHandler OnSavingDiagram; /// /// Occurs after saving a diagram /// [Category(constGeneral), Description("Occurs after saving a diagram."), Browsable(true)] public event EventHandler OnDiagramSaved; /// /// Occurs when the control resets the document internally, usually through the Ctrl+N hotkey. /// [Category(constGeneral), Description("Occurs when the control resets the document internally, usually through the Ctrl+N hotkey."), Browsable(true)] public event EventHandler OnNewDiagram; #region Event raisers /// /// Raises the OnShowCanvasProperties event. /// protected void RaiseOnShowCanvasProperties(SelectionEventArgs e) { EventHandler handler = OnShowCanvasProperties; if (handler != null) { handler(this, e); } } /// /// Raises the OnNewDiagram event. /// protected void RaiseOnNewDiagram() { EventHandler handler = OnNewDiagram; if (handler != null) { handler(this, EventArgs.Empty); } } /// /// Raises the OnHistory change. /// protected void RaiseOnHistoryChange(HistoryChangeEventArgs e) { EventHandler handler = OnHistoryChange; if (handler != null) { handler(this, e); } } /// /// Raises the event /// /// Properties event argument protected virtual void RaiseOnShowDocumentProperties(PropertiesEventArgs e) { EventHandler handler = OnShowDocumentProperties; if (handler != null) { handler(this, e); } } /// /// Raises the OnSavingDiagram event /// /// public void RaiseOnSavingDiagram(string filePath) { if (OnSavingDiagram != null) OnSavingDiagram(this, new FileEventArgs(new System.IO.FileInfo(filePath))); } /// /// Raises the OnShowSelectionProperties event. /// /// The instance containing the event data. protected virtual void RaiseOnShowSelectionProperties(SelectionEventArgs e) { EventHandler handler = OnShowSelectionProperties; if (handler != null) { handler(this, e); } } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void RaiseOnEntityAdded(EntityEventArgs e) { EventHandler handler = OnEntityAdded; if (handler != null) { handler(this, e); } } /// /// Raises the . /// /// The instance containing the event data. protected virtual void RaiseOnEntityRemoved(EntityEventArgs e) { EventHandler handler = OnEntityRemoved; if (handler != null) { handler(this, e); } } /// /// Raises the OnDiagramSaved event /// /// public void RaiseOnDiagramSaved(string filePath) { if (OnDiagramSaved != null) OnDiagramSaved(this, new FileEventArgs(new System.IO.FileInfo(filePath))); } /// /// Raises the OnOpeningDiagram event /// public void RaiseOnOpeningDiagram(string filePath) { if (OnOpeningDiagram != null) OnOpeningDiagram(this, new FileEventArgs(new System.IO.FileInfo(filePath))); } /// /// Raises the OnDiagramOpened event /// public void RaiseOnDiagramOpened(string filePath) { if (OnDiagramOpened != null) OnDiagramOpened(this, new FileEventArgs(new System.IO.FileInfo(filePath))); } #endregion #endregion #region Fields // ------------------------------------------------------------------ /// /// The page settings for printing the diagram. /// // ------------------------------------------------------------------ protected PageSettings mPageSettings; // ------------------------------------------------------------------ /// /// The filename to use when saving the diagram. /// // ------------------------------------------------------------------ protected string mFileName = ""; // ------------------------------------------------------------------ /// /// Collection of files that were opened. The collection can be /// saved to disk by calling 'SaveRecentFiles(string path)'. /// // ------------------------------------------------------------------ protected ArrayList myRecentFiles = new ArrayList(); // ------------------------------------------------------------------ /// /// the view /// // ------------------------------------------------------------------ protected IView mView; // ------------------------------------------------------------------ /// /// the controller /// // ------------------------------------------------------------------ protected IController mController; // ------------------------------------------------------------------ /// /// the Document field /// // ------------------------------------------------------------------ protected Document mDocument; protected bool mEnableAddConnection = true; #endregion #region Properties // ------------------------------------------------------------------ /// /// Gets or sets the page settings for the entire document. /// // ------------------------------------------------------------------ public PageSettings PageSettings { get { return mPageSettings; } set { mPageSettings = value; // PageSettings has the page size in hundreths of an inch and // the view requires mils (thousandths of an inch). mView.PageSize = new Size( mPageSettings.PaperSize.Width * 10, mPageSettings.PaperSize.Height * 10); mView.Landscape = mPageSettings.Landscape; } } // ------------------------------------------------------------------ /// /// Specifies if all shape's connectors are shown. /// // ------------------------------------------------------------------ public bool ShowConnectors { get { return this.mView.ShowConnectors; } set { this.mView.ShowConnectors = value; } } // ------------------------------------------------------------------ /// /// Gets or sets the filename to save the diagram as. /// // ------------------------------------------------------------------ public string FileName { get { return mFileName; } set { mFileName = value; } } /// /// Pans the diagram. /// /// The pan. [Browsable(false)] public Point Origin { get { return this.Controller.View.Origin; } set { this.Controller.View.Origin = value; } } /// /// Gets or sets the magnification/zoom. /// /// The magnification. [Browsable(false)] public SizeF Magnification { get { return this.Controller.View.Magnification; } set { this.Controller.View.Magnification = value; } } /// /// Gets the selected items. /// /// The selected items. [Browsable(false)] public CollectionBase SelectedItems { get { return this.Controller.Model.Selection.SelectedItems; } } /// /// Gets or sets whether to show the rulers. /// /// true to show the rulers; otherwise, false. [Browsable(true), Description("Gets or sets whether to show the rulers.")] public bool ShowRulers { get { return View.ShowRulers; } set { View.ShowRulers = value; } } /// /// Gets or sets whether a connection can be added. /// /// true if [enable add connection]; otherwise, false. [Browsable(true), Description("Gets or sets whether the user can add new connections to the diagram."), Category("Interactions")] public bool EnableAddConnection { get { return mEnableAddConnection; } set { mEnableAddConnection = value; } } #endregion #region Constructor /// /// Default constructor /// protected DiagramControlBase() { // Update the display setting. Graphics g = CreateGraphics(); Display.DpiX = g.DpiX; Display.DpiY = g.DpiY; //create the provider for all shapy diagram elements ShapeProvider shapeProvider = new ShapeProvider(); TypeDescriptor.AddProvider( shapeProvider, typeof(SimpleShapeBase)); TypeDescriptor.AddProvider( shapeProvider, typeof(ComplexShapeBase)); //the provider for connections ConnectionProvider connectionProvider = new ConnectionProvider(); TypeDescriptor.AddProvider( connectionProvider, typeof(ConnectionBase)); //scrolling stuff this.AutoScroll = true; this.HScroll = true; this.VScroll = true; mPageSettings = new PageSettings(); mPageSettings.Landscape = true; mPageSettings.PaperSize = new PaperSize("Letter", 850, 1100); } #endregion #region Properties /// /// Gets or sets the background image displayed in the control. /// /// /// An that represents the image to display in the background of the control. /// public override Image BackgroundImage { get { return base.BackgroundImage; } set { base.BackgroundImage = value; //TODO: change the backgroundtype } } //protected override void OnGiveFeedback(GiveFeedbackEventArgs gfbevent) //{ // base.OnGiveFeedback(gfbevent); // gfbevent.UseDefaultCursors = false; // Cursor.Current = CursorPallet.DropShape; //} /// /// /// [Browsable(true), Description("The background color of the canvas if the type is set to 'flat'"), Category("Appearance")] public new Color BackColor { get { return base.BackColor; } set { //communicate the change down to the model //shouldn't this be done via the controller? mDocument.Model.CurrentPage.Ambience.BackgroundColor = value; ArtPalette.DefaultPageBackgroundColor = value; base.BackColor = value; this.Invalidate(); } } /// /// Gets or sets the type of the background. /// /// The type of the background. [Browsable(true), Description("The background type"), Category("Appearance"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public CanvasBackgroundTypes BackgroundType { get { return mDocument.Model.CurrentPage.Ambience.BackgroundType; } set { mDocument.Model.CurrentPage.Ambience.BackgroundType = value; } } /// /// Gets or sets the view. /// /// The view. public IView View { get { return mView; } set { mView = value; } } /// /// Gets or sets the controller. /// /// The controller. public IController Controller { get { return mController; } set { AttachToController(value); } } // ------------------------------------------------------------------ /// /// Gets or sets the Document. You must call /// 'AttachToDocument(Document)' to apply a new document. /// // ------------------------------------------------------------------ public virtual Document Document { get { return mDocument; } set { mDocument = value; } } #endregion #region Methods // ------------------------------------------------------------------ /// /// Saves all recently opened files to the path specified. The files /// are saved to a simple text file, one filename per line. /// /// /// int: The max number of filenams to /// save. // ------------------------------------------------------------------ public virtual void SaveRecentlyOpenedFiles( string path, int maxFiles) { if (myRecentFiles.Count < 1) { return; } StreamWriter sw = File.CreateText(path); // Keep track of all files saved so we don't save duplicates. ArrayList savedFiles = new ArrayList(); string fileName; // Start at the last (i.e. most recent file opened) and save // until either 0 or maxFiles is reached. for (int i = myRecentFiles.Count - 1; i >= 0; i--) { if (maxFiles == 0) { break; } fileName = myRecentFiles[i].ToString(); if (File.Exists(fileName)) { if (savedFiles.Contains(fileName) == false) { sw.WriteLine(fileName); savedFiles.Add(fileName); maxFiles--; } } } sw.Flush(); sw.Close(); } // ------------------------------------------------------------------ /// /// Reads the list of recently opened filenames from the /// path specified. If the path doesn't exist or if there aren't /// any files to read, then 'null' is returned. /// /// string[] // ------------------------------------------------------------------ public virtual string[] ReadRecentlyOpenedFiles(string path) { if (File.Exists(path) == false) { return null; } StreamReader sr = new StreamReader(path); string text = sr.ReadToEnd(); string[] filenames = text.Split("\n".ToCharArray()); this.myRecentFiles.Clear(); string trimmedFileName; foreach (string filename in filenames) { trimmedFileName = filename.Trim("\r".ToCharArray()); if (File.Exists(trimmedFileName)) { this.myRecentFiles.Add(trimmedFileName); } } if (this.myRecentFiles.Count > 0) { return (string[])myRecentFiles.ToArray(typeof(string)); } else { return null; } } // ------------------------------------------------------------------ /// /// Sets the layout root. /// /// The shape. // ------------------------------------------------------------------ public virtual void SetLayoutRoot(IShape shape) { this.Controller.Model.LayoutRoot = shape; } // ------------------------------------------------------------------ /// /// Runs the activity. /// /// Name of the activity. // ------------------------------------------------------------------ public virtual void RunActivity(string activityName) { this.Controller.RunActivity(activityName); } // ------------------------------------------------------------------ /// /// Handles the OnEntityRemoved event of the Controller control. /// /// The source of the event. /// The /// instance /// containing the event data. // ------------------------------------------------------------------ protected virtual void HandleOnEntityRemoved( object sender, EntityEventArgs e) { RaiseOnEntityRemoved(e); } // ------------------------------------------------------------------ /// /// Handles the OnEntityAdded event of the Controller control. /// /// The source of the event. /// The /// instance /// containing the event data. // ------------------------------------------------------------------ protected virtual void HandleOnEntityAdded( object sender, EntityEventArgs e) { RaiseOnEntityAdded(e); } // ------------------------------------------------------------------ /// /// Handles the OnBackColorChange event of the View control. /// /// The source of the event. /// The /// instance /// containing the event data. // ------------------------------------------------------------------ protected void View_OnBackColorChange(object sender, ColorEventArgs e) { base.BackColor = e.Color; } // ------------------------------------------------------------------ /// /// Bubbles the OnHistoryChange event from the Controller to the surface /// /// The source of the event. /// The /// /// instance containing the event data. // ------------------------------------------------------------------ void mController_OnHistoryChange( object sender, HistoryChangeEventArgs e) { RaiseOnHistoryChange(e); } void Controller_OnShowSelectionProperties(object sender, SelectionEventArgs e) { RaiseOnShowSelectionProperties(e); } // ------------------------------------------------------------------ /// /// Attaches to the controller specified and registers for all /// required events to update this control from the controller. /// /// IController // ------------------------------------------------------------------ protected virtual void AttachToController(IController controller) { if (controller == null) throw new ArgumentNullException(); mController = controller; mController.OnHistoryChange += new EventHandler( mController_OnHistoryChange); mController.OnShowSelectionProperties += new EventHandler( Controller_OnShowSelectionProperties); mController.OnEntityAdded += new EventHandler( HandleOnEntityAdded); mController.OnEntityRemoved += new EventHandler( HandleOnEntityRemoved); } // ------------------------------------------------------------------ /// /// Resets the document and underlying model. /// // ------------------------------------------------------------------ public virtual void NewDocument() { // Lets see, we really only want to ask the user if they want to // save their changes if they don't have changes to save. // We can check the un/redo mananger to see if there are changes. if ((this.mController.UndoManager.CanRedo()) || (this.mController.UndoManager.CanUndo())) { DialogResult result = MessageBox.Show( "Save changes before clearing the current diagram?", "Confirm Clear", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); if (result == DialogResult.Yes) { this.Save(); } else if (result == DialogResult.Cancel) { return; } } Document = new Document(); this.mController.UndoManager.ClearUndoRedo(); AttachToDocument(Document); this.mFileName = ""; RaiseOnNewDiagram(); //this.Controller.Model.Clear(); } #region Printing // ------------------------------------------------------------------ /// /// Displays a PageSetupDialog so the user can specify how each /// page is printed. /// // ------------------------------------------------------------------ public void PageSetup() { PageSetupDialog dialog = new PageSetupDialog(); dialog.PageSettings = this.PageSettings; if (dialog.ShowDialog() == DialogResult.OK) { this.PageSettings = dialog.PageSettings; } } // ------------------------------------------------------------------ /// /// Prints all pages of the diagram. /// // ------------------------------------------------------------------ public void Print() { DiagramPrinter myPrinter = new DiagramPrinter( this.PageSettings, this, false); } // ------------------------------------------------------------------ /// /// Print previews all pages of the diagram. /// // ------------------------------------------------------------------ public void PrintPreview() { DiagramPrinter myPrinter = new DiagramPrinter( this.PageSettings, this, true); } #endregion #region IO // ------------------------------------------------------------------ /// /// If the current filename (FileName property) is empty, then a /// SaveFileDialog is displayed for the user to specify what to save /// the diagram as. Otherwise, the current filename is used to save /// the diagram. /// // ------------------------------------------------------------------ public virtual void Save() { try { if (this.mFileName == "") { SaveFileDialog dialog = new SaveFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { this.SaveAs(dialog.FileName); } } else { this.SaveAs(this.mFileName); } } catch (Exception ex) { MessageBox.Show("An error occured while saving the " + "diagram. See below for the message about the " + "error.\n\n" + "Message: " + ex.Message, "Save Diagram Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // ------------------------------------------------------------------ /// /// Displays a SaveFileDialog regardless if there's an existing /// filename so the user can save the diagram to a new location. /// // ------------------------------------------------------------------ public virtual void SaveAs() { SaveFileDialog dialog = new SaveFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { SaveAs(dialog.FileName); } } // ------------------------------------------------------------------ /// /// Saves the diagram to the given location. /// /// The path. // ------------------------------------------------------------------ public virtual void SaveAs(string path) { if (!Directory.Exists(Path.GetDirectoryName(path))) { throw new DirectoryNotFoundException( "Create the directory before saving the diagram to it."); } RaiseOnSavingDiagram(path); if (BinarySerializer.SaveAs(path, this) == true) { // Store the filename if the save was successful. this.mFileName = path; // Clear the undo/redo history. this.mController.UndoManager.ClearUndoRedo(); RaiseOnDiagramSaved(path); } } // ------------------------------------------------------------------ /// /// Displays an OpenFileDialog for the user to specify the diagram /// to open. /// // ------------------------------------------------------------------ public virtual void Open() { OpenFileDialog dialog = new OpenFileDialog(); if (dialog.ShowDialog() == DialogResult.OK) { this.Open(dialog.FileName); } } // ------------------------------------------------------------------ /// /// Opens the diagram from the specified path. /// /// The path. // ------------------------------------------------------------------ public virtual void Open(string path) { if (!Directory.Exists(Path.GetDirectoryName(path))) { throw new DirectoryNotFoundException( "Create the directory before saving the diagram to it."); } this.NewDocument();//makes it possible for the host to ask for // saving first. RaiseOnOpeningDiagram(path);//do it before opening the new diagram. if (BinarySerializer.Open(path, this) == true) { // Store the filename if successful. this.mFileName = path; // Add the filename to the list of previously opened files. this.myRecentFiles.Add(path); // Raise that a new diagram was opened. RaiseOnDiagramOpened(path); } //this.mFileName = mFileName; } #endregion // ------------------------------------------------------------------ /// /// Attachement to the document. /// /// The document. // ------------------------------------------------------------------ public virtual void AttachToDocument(Document document) { if (document == null) throw new ArgumentNullException(); this.mDocument = document; #region re-attach to the new model View.Model = Document.Model; Controller.Model = Document.Model; #endregion #region Update the ambience document.Model.SetCurrentPage(0); #endregion this.Controller.Model.Selection.Clear(); this.Invalidate(); } // ------------------------------------------------------------------ /// /// Activates a registered tool with the given name /// /// the name of a tool // ------------------------------------------------------------------ [System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Design", "CA1062:ValidateArgumentsOfPublicMethods")] public void ActivateTool(string toolName) { if (toolName == null || toolName.Trim().Length == 0) { throw new ArgumentNullException("The tool name cannot " + "be 'null' or empty."); } if (this.Controller == null) { throw new InconsistencyException( "The Controller of the surface is 'null', this is a " + "strong inconsistency in the MVC model."); } if (toolName.Trim().Length > 0) this.Controller.ActivateTool(toolName); } // ------------------------------------------------------------------ /// /// Starts the tool with the given name. /// /// the name of a registered tool // ------------------------------------------------------------------ public void LaunchTool(string toolName) { if (this.Controller == null) return; this.Controller.ActivateTool(toolName); } // ------------------------------------------------------------------ /// /// Overrides the base method to call the view which will paint the /// diagram on the given graphics surface. /// /// PaintEventArgs // ------------------------------------------------------------------ protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); mView.Paint(e.Graphics); } // ------------------------------------------------------------------ /// /// Paints the background of the control. /// /// A /// that /// contains information about the control to paint. // ------------------------------------------------------------------ protected override void OnPaintBackground(PaintEventArgs pevent) { base.OnPaintBackground(pevent); mView.PaintBackground(pevent.Graphics); } // ------------------------------------------------------------------ /// /// Undoes the last action that was recorded in the UndoManager. /// // ------------------------------------------------------------------ public void Undo() { this.Controller.Undo(); } // ------------------------------------------------------------------ /// /// Redoes the last action that was recorded in the UndoManager. /// // ------------------------------------------------------------------ public void Redo() { this.Controller.Redo(); } #endregion #region Explicit ISupportInitialize implementation void ISupportInitialize.BeginInit() { //here you can check the conformity of properties BeginInit(); } /// /// Signals the object that initialization is complete. /// void ISupportInitialize.EndInit() { EndInit(); } /// /// Signals the object that initialization is starting. /// protected virtual void BeginInit() { } /// /// Signals the object that initialization is complete. /// protected virtual void EndInit() { //necessary if the background type is gradient since the brush requires the size of the client rectangle mView.SetBackgroundType(BackgroundType); //the diagram control provider gives problems in design mode. If enable at design mode the //diagramming control becomes a container component rather than a form control because the essential //properties are disabled through the ControlProvider (the Location or ID for example). //Also, do not try to put this in the constructor, you need the ISupportInitialize to get a meaningful DesignMode value. if (!DesignMode) { ControlProvider controlProvider = new ControlProvider(); TypeDescriptor.AddProvider(controlProvider, typeof(DiagramControlBase)); } } #endregion } }