using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
// http://msdn.microsoft.com/en-us/library/ms171724.aspx
// http://msdn.microsoft.com/en-us/library/6hws6h2t.aspx
/*
* TODO:
*
* - UpdateParentTitle when DirtyState *changes*
* - I am still unhappy with that dirty handler
* - Recent files - Open, Save etc. add to recent files list
* - Recent files - settings
* - Recent files - build sub menu from it
* - Recent files - doc
* - Recent files - tests
* - Quit function for form close handler
* - Strings for FileDialog title
* - reset dirty for loaded documents after UI update?
* - file masks from doc type manager?? (or remove doc type manager?)
* - Document Managers for Serializable, XmlSerializable
* - replace "object or DocumentInfo" with "DocumentInfo", provide static DocumentInfo creator(s)
* - Localize strings
* - Parent window handle for message boxes
* - Implement "new Document dirty?"
* - "Shutdown" handler (similar to quit)
*
* - (?) suppress Close for "never modified" document?
* - (?) avoid frequent update of document title
*
* - (test!) automatic update of Form Title in all cases (except custom, SetDirty?!)
* - (test!) Implement event for ParentTitleChanged
*
* - (ok) "Dirty change" notification ==> Handler should call RefreshTitle instead
* - (ok) implement AskCloseUnmodified
* - (ok) AskClose flag ==> rename to AskCloseUnmodified
* - (ok) file dialog settings
*
* - (no) customizable parent document title - easier to implement using event
*
*/
/* Features not supported yet
*
* - Document type specific file filters
* - Save As Conversions (except by inspecting extension)
* - Ask for naming a new document?
* - support for managing document types
* - separation of overrideable functions and "UI Helper" function is not clear
* - storing the document manager selected with the document
* - flag "Keep file handle open while document is opened"? (to deny concurrent access)
* - Determining the current document for Save As etc. operations
* - Standard "List of documents" dialog
*
* - Title for the message boxes - would be nice to pass the "reason for the action"
*
* ------------------------
*
* "Close" Behavior:
* Closing an unsaved document:
* (Option: Ask "Save? Yes / No Cancel" | Save named documents silently?) - hmm, maybe later
* Option: "return to dialog when canceling save as"
* Closing a saved document:
* - "Close silently"
* - Ask for close"
*
* ------------------
*/
namespace PH.UI.FileSelect
{
/// <summary>WinForms component implementing standard file commands.</summary>
/// <remarks>
/// <para>The control can be added to a WinForms form, and adds a FileSelectCommand
/// extender property to menu items. This lets you assign menu items to commands
/// handled by the control. </para>
/// <para>
/// You also need to assign an <see cref="DocumentManager"/> that controls how to create,
/// load and save a document. There are several default implementations.
/// </para>
/// <para>
/// The following functionality is provided by the FileSelect component:
/// <list type="bullet">
/// <item>The commands New, Open, Close, Save, Save As and Save Copy As</item>
/// <item>Asking for file name when appropriate</item>
/// <item>Handling modified / unmodified document</item>
/// <item>Incremental default document title for new documents, e.g. Unknown-1, Unknown-2</item>
/// <item>Setting form title to include document title and modification marker</item>
/// </list>
/// Typically, you need to provide only the following:
/// <list type="bullet">
/// <item>specify a default document manager, or providing your own IDocumentManager implementation</item>
/// <item>hook up menu and toolbar items with FileSelect commands</item>
/// <item>Handle the OpenDocument and OpenNewDocument events to display the document</item>
/// <item>Handle the BeforeSaveDocument event to update the document from the user interface controls</item>
/// <item>Handle the CloseDocument event to update the user display</item>
/// <item>Signal that the document has been modified, or provide your own <see cref="IDirtyFlag"/> implementation</item>
/// </list>
/// </para>
/// </remarks>
[ProvideProperty("FileSelectCommand", typeof(object))]
public partial class FileSelect : Component, IExtenderProvider
{
public FileSelect() : this(null) {}
public FileSelect(IContainer container)
{
if (container != null)
container.Add(this);
InitializeComponent();
Commands = new CommandMap(this);
RecentFiles.FileListChanged += new EventHandler(RecentFiles_FileListChanged);
}
void RecentFiles_FileListChanged(object sender, EventArgs e)
{
List<string> filesList= new List<String>(RecentFiles.Files);
string[] files = filesList.ToArray();
foreach(IMenuItem imi in Commands.GetAllItemsFor(EFSCommand.RecentMore))
imi.UpdateFileList(files, files);
}
#region Configuration Properties
private bool m_singleFileMode = true;
/// <summary>Switch between Single- and Multi-file Mode.</summary>
/// <remarks>Currently, only single file mode is implemented.</remarks>
[DefaultValue(true)]
[Description("In single file mode, only a current document is supported. Otherwise, multiple documents are supported.")]
public bool SingleFileMode
{
get { return m_singleFileMode; }
// set { m_singleFileMode = value; }
}
private bool m_askCloseUnmodified = true;
/// <summary>When a document is closed, ask the user if it should be saved.</summary>
/// <remarks>When a document was modified, and the user closes the document, he will be asked
/// if he wants to save the document. The default is true. If this is set to false,
/// <see cref="AutoSaveOnClose"/> controls behavior.</remarks>
[DefaultValue(true)]
[Description("Ask if the user wants to save the current document before discarding changes.")]
public bool AskCloseUnmodified
{
get { return m_askCloseUnmodified; }
set { m_askCloseUnmodified = value; }
}
/// <summary>Control to hold the current document title.</summary>
/// <remarks>Typically, you select the form containing the document editor.
/// The current document title and a "modified" marker is appended to the title
/// of the control. By default, this is <c>null</c>, which disables this feature.</remarks>
Control m_parentControl = null;
[DefaultValue(null)]
public Control ParentControl
{
get { return m_parentControl; }
set { m_parentControl = value; }
}
private bool m_bUpdateParentTitle = true;
public bool UpdateParentTitle
{
get { return m_bUpdateParentTitle; }
set
{
if (value != m_bUpdateParentTitle)
{
m_bUpdateParentTitle = value;
if (m_baseParentTitle != null)
RefreshParentTitle(false);
}
}
}
private OpenFileDialog m_openFileDialog = new OpenFileDialog();
private SaveFileDialog m_saveFileDialog = new SaveFileDialog();
/// <summary>The dialog used in the default Open implementation.</summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public OpenFileDialog DialogOpen
{
get { return m_openFileDialog; }
}
/// <summary>The dialog used in the default Save implementations.</summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public SaveFileDialog DialogSave
{
get { return m_saveFileDialog; }
}
public class Strings : Component
{
string m_messageBoxTitle = "File";
string m_askSaveChanges = "Save changes to this document?";
string m_askCloseDocument = "Close document?";
[Localizable(true)]
[Description("Title for Message Boxes")]
[DefaultValue("File")]
public string MessageBoxTitle
{
get { return m_messageBoxTitle; }
set { m_messageBoxTitle = value; }
}
[Localizable(true)]
[Description("Message Box text: ask to save a modified file")]
[DefaultValue("Save changes to this document?")]
public string AskSaveChanges
{
get { return m_askSaveChanges; }
set { m_askSaveChanges = value; }
}
[Localizable(true)]
[Description("Message Box text: close unmodified document")]
[DefaultValue("Close document?")]
public string AskCloseDocument
{
get { return m_askCloseDocument; }
set { m_askCloseDocument = value; }
}
}
private Strings m_uiStrings = new Strings();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Strings UIStrings
{
get { return m_uiStrings; }
//set { m_uiStrings = value; }
}
private RecentFileList m_recentFiles = new RecentFileList();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public PH.UI.FileSelect.RecentFileList RecentFiles
{
get { return m_recentFiles; }
}
#endregion
#region Programming Interface
private IDocumentManager m_documentManager = null;
/// <summary>The document manager to create, load and save documents.</summary>
/// <remarks>...</remarks>
[Browsable(false)]
public IDocumentManager DocumentManager
{
get { return m_documentManager; }
set { m_documentManager = value; }
}
// TODO: multiple documents support
/// <summary>Additional information for the currently opened document.</summary>
private DocumentInfo m_currentDocumentInfo = null;
[Browsable(false)]
public DocumentInfo CurrentDocumentInfo
{
get { return m_currentDocumentInfo; }
}
/// <summary>The currently opened document.</summary>
[Browsable(false)]
public object CurrentDocument
{
get
{
if (CurrentDocumentInfo == null)
return null;
return CurrentDocumentInfo.Document;
}
}
internal void SetCurrentDocumentInfo(DocumentInfo docInfo)
{
// TODO: verify we can just "throw away", support multiple documents, etc.
m_currentDocumentInfo = docInfo;
// note: RefreshParentTitle doesn't get called here, since it might be followed by a later update
}
private string m_baseParentTitle = null;
public void RefreshParentTitle(bool sendEvent)
{
if (sendEvent)
OnTitleBarChanged();
if (m_parentControl == null)
return;
string title = null;
// restore previous title if it is known
if (!m_bUpdateParentTitle && m_baseParentTitle != null)
{
title = m_baseParentTitle;
}
else
{
// get base parent control title
if (m_baseParentTitle == null)
m_baseParentTitle = m_parentControl.Text;
if (CurrentDocumentInfo != null)
{
if (CurrentDocumentInfo.Path != null)
title = System.IO.Path.GetFileName(CurrentDocumentInfo.Path);
else
title = CurrentDocumentInfo.Title;
if (CurrentDocumentInfo.IsDirty)
title += " (*)";
if (m_baseParentTitle != String.Empty)
title = title + " - " + m_baseParentTitle;
}
else
title = m_baseParentTitle;
}
m_parentControl.Text = title;
}
public bool Quit(CloseReason closeReason)
{
if (closeReason == CloseReason.WindowsShutDown)
{
// TODO: provide custom handling
}
if (CurrentDocument != null) // TODO: multi document support
{
bool closed = CloseDocument(CurrentDocumentInfo);
if (!closed)
return false;
}
return true;
}
#endregion
#region Events and virtual functions
/// <summary>fires when a new document was created.</summary>
/// <remarks><para>You should handle this event by refreshing the user interface to reflect the new document contents.
/// Alternatively, a derived class can override <see cref="OnOpenedNewDocument"/>.</para>
/// <para>Usually, <c>OpenedNewDocument</c> and <see cref="OpenedDocument"/> can be handled by the same implementation.</para>
/// <para>The Event Handler receives an instance of <see cref="FileSelectEventArgs"/> as event object.</para>
/// </remarks>
public event EventHandler OpenedNewDocument;
protected virtual void OnOpenedNewDocument(DocumentInfo docInfo)
{
if (OpenedNewDocument != null)
OpenedNewDocument(this, new FileSelectEventArgs(EFSCommand.New, docInfo));
}
/// <summary>fires when a document was loaded.</summary>
/// <remarks><para>You should handle this event by refreshing the user interface to reflect the document contents.
/// Alternatively, a derived class can override <see cref="OnOpenedDocument"/>.</para>
/// <para>Usually, <see cref="OpenedNewDocument"/> and <c>OpenedDocument</c>> can be handled by the same implementation.</para>
/// <para>The Event Handler receives an instance of <see cref="FileSelectEventArgs"/> as event object.</para>
/// </remarks>
public event EventHandler OpenedDocument;
protected virtual void OnOpenedDocument(DocumentInfo docInfo)
{
if (OpenedDocument != null)
OpenedDocument(this, new FileSelectEventArgs(EFSCommand.Open, docInfo));
}
/// <summary>fires before a document is saved.</summary>
/// <remarks><para>You should handle this event by refreshing the document contents with the changes made in the user interface.
/// If the document is updated every time the user controls change, you need not handle this event.
/// Alternatively, a derived class can override <see cref="OnBeforeSaveDocument"/>.</para>
/// <para>The Event Handler receives an instance of <see cref="FileSelectEventArgs"/> as event object.</para>
/// </remarks>
public event EventHandler BeforeSaveDocument;
protected virtual bool OnBeforeSaveDocument(DocumentInfo docInfo, string savePath, bool resetDirty)
{
if (BeforeSaveDocument != null)
{
FileSelectEventArgs fse = new FileSelectEventArgs(EFSCommand.BeforeSave, docInfo, savePath, resetDirty);
BeforeSaveDocument(this, fse);
return fse.ContinueSave;
}
else
return true;
}
/// <summary>Fires after the document was saved.</summary>
/// <remarks>You normally need not handle this event.
/// Alternatively, a derived class can override <see cref="OnSavedDocument"/>.
/// </remarks>
public event EventHandler SavedDocument;
protected virtual void OnSavedDocument(EFSCommand cmd, DocumentInfo docInfo, string toPath, bool resetDirty)
{
if (SavedDocument != null)
SavedDocument(this, new FileSelectEventArgs(cmd, docInfo, toPath, resetDirty));
}
/// <summary>fires when the document was closed.</summary>
/// <remarks><para>You should handle this event by updating the user interface to indicate no document is open
/// Alternatively, a derived class can override <see cref="OnBeforeSaveDocument"/>.</para>
/// </remarks>
public event EventHandler ClosedDocument;
protected virtual void OnClosedDocument(DocumentInfo docInfo)
{
if (ClosedDocument != null)
ClosedDocument(this, new FileSelectEventArgs(EFSCommand.Close, docInfo));
}
public event EventHandler TitleBarChanged;
protected virtual void OnTitleBarChanged()
{
if (TitleBarChanged != null)
{
TitleBarChanged(this, new FileSelectEventArgs(EFSCommand.ParentTitleChanged, CurrentDocumentInfo));
}
}
#endregion
#region Implementation Command Map
/// <summary>utility class to implement <see cref="Commands"/> member.</summary>
/// <remarks>This class allows the caller to assign an <see cref="EFSCommand"/> to
/// one or more <see cref="IMenuItem"/>'s, which may e.g. be <c>ToolStripMenuItem</c>'s. </remarks>
public class CommandMap
{
public readonly FileSelect Owner;
public CommandMap(FileSelect owner)
{
Owner = owner;
m_events = new EventForwarderList(Owner);
}
/*
#region Event forwarder
private class EventForwarderList
{
/// <summary>Helepr class to wrap and forward MenuItem standard events</summary>
/// <remarks>FileSelect uses one instance per EFSCommand value, so that the forwarder can
/// attach the command Id t other forwarded event.</remarks>
public class EventForwarder
{
public readonly EFSCommand Command;
public readonly FileSelect Owner;
public EventForwarder(EFSCommand cmd, FileSelect owner)
{
Command = cmd;
Owner = owner;
}
public void ForwardEvent(object sender, EventArgs ea)
{
Owner.HandleMenuCommand(new MenuItemEventArgs(sender, ea, Command));
}
}
private EventForwarder[] m_efl;
public EventForwarderList(FileSelect owner)
{
m_efl = new EventForwarder[(int)EFSCommand._internal_Max];
for (int i = 0; i < (int)EFSCommand._internal_Max; ++i)
m_efl[i] = new EventForwarder((EFSCommand)i, owner);
}
public EventForwarder this[EFSCommand cmd]
{
get { return m_efl[(int)cmd]; }
}
}
private EventForwarderList m_events;
#endregion
*/
private Dictionary<IMenuItem, EFSCommand> m_map = new Dictionary<IMenuItem, EFSCommand>();
public EFSCommand this[IMenuItem item]
{
get
{
EFSCommand cmd;
if (m_map.TryGetValue(item, out cmd))
return cmd;
else
return EFSCommand.None;
}
set
{
EFSCommand prevCmd;
if (m_map.TryGetValue(item, out prevCmd))
item.Re;
if (value == EFSCommand.None)
m_map.Remove(item);
else
{
m_map[item] = value;
item.Click += m_events[value].ForwardEvent;
}
}
}
public IEnumerable<IMenuItem> GetAllItemsFor(EFSCommand cmd)
{
foreach (KeyValuePair<IMenuItem, EFSCommand> kvp in m_map)
{
if (kvp.Value == cmd)
yield return kvp.Key;
}
}
public IEnumerable<IMenuItem> GetAllItems()
{
return m_map.Keys;
}
}
/// <summary>Assigns menu items etc. to commands.</summary>
public CommandMap Commands;
#endregion
#region Utilities
/// <summary>List of commands available</summary>
public static EFSCommand[] EFSCommands = new EFSCommand[]
{
EFSCommand.New,
EFSCommand.NewMulti,
EFSCommand.Open,
EFSCommand.Close,
EFSCommand.Save,
EFSCommand.SaveAs,
EFSCommand.SaveCopyAs,
EFSCommand.Recent,
EFSCommand.RecentMore
};
internal virtual bool CloseSingleDocument()
{
if (!m_singleFileMode)
return true;
if (CurrentDocumentInfo == null)
return true;
return CloseDocument(CurrentDocumentInfo);
}
#endregion
#region Logic for handling commands
internal virtual bool SaveDocument(DocumentInfo docInfo, EFSCommand cmd)
{
// TODO: for multiple documents, use document manager of current document!
Debug.Assert(cmd == EFSCommand.Save || cmd == EFSCommand.SaveAs || cmd == EFSCommand.SaveCopyAs);
bool askForName = true;
string saveName = docInfo.Path;
if (cmd == EFSCommand.Save)
{
saveName = docInfo.Path;
askForName = String.IsNullOrEmpty(saveName);
}
else
{
// TODO: better default name for document?
}
if (askForName)
{
if (!UIAskForSaveName(cmd, ref saveName))
return false;
}
bool resetDirty = cmd == EFSCommand.Save || cmd == EFSCommand.SaveAs;
// call BeforeSafe handler
bool continueSave = OnBeforeSaveDocument(docInfo, saveName, resetDirty);
if (!continueSave)
return false;
// actually safe document
DocumentManager.SaveDocument(docInfo.Document, saveName, resetDirty);
// reset dirty flag using DirtyManager
if (resetDirty)
docInfo.IsDirty = false;
// update document path
if ((cmd == EFSCommand.Save && askForName) || cmd == EFSCommand.SaveAs)
docInfo.Path = saveName;
// update list of files
RecentFiles.Add(docInfo.Path);
// call after save handler
OnSavedDocument(cmd, docInfo, saveName, resetDirty);
if (resetDirty)
{
RefreshParentTitle(true);
Debug.Assert(!docInfo.IsDirty, "FileSelect: document still dirty after saving");
}
return true;
}
internal virtual bool CloseDocument(DocumentInfo doc)
{
Debug.Assert(doc != null);
// document dirty?
if (doc.IsDirty)
{
bool doSave;
//if (m_automaticSaveOnClose && doc.HasPath) // automatically save when path is known
// doSave = true;
//else
{
// TODO: get / build message
DialogResult result = UIAskSaveBeforeClose();
if (result == DialogResult.Cancel)
return false;
doSave = result != DialogResult.No;
}
if (doSave)
{
if (!SaveDocument(doc, EFSCommand.Save))
{
return false;
// TODO: add option to return to the "yes/no/cancel" dialog
// I've seen many people stuck there, so... maybe as a configuration setting
}
}
}
else // document not dirty
{
if (m_askCloseUnmodified)
{
bool doClose = UIAskCloseUnmodified();
if (!doClose)
return false;
}
}
// TODO: remove from multiple documents list
if (m_singleFileMode)
{
Debug.Assert(doc == CurrentDocumentInfo);
SetCurrentDocumentInfo(null);
}
OnClosedDocument(doc);
RefreshParentTitle(true);
return true;
}
internal virtual bool NewDocument()
{
if (!CloseSingleDocument())
return false;
RefreshParentTitle(true); // TODO: refresh only once (first refresh is to ensure valid state when new document is canceled or fails)
object newDocument = DocumentManager.NewDocument();
if (newDocument == null)
return false;
// check if doc mgr already provided a document info
DocumentInfo docInfo = newDocument as DocumentInfo;
if (docInfo == null) // - no? create it ourselves
{
docInfo = new DocumentInfo(newDocument, DocumentManager);
}
SetCurrentDocumentInfo(docInfo); // TODO: add to multi-doc-list
OnOpenedNewDocument(docInfo);
RefreshParentTitle(true);
return true;
}
internal virtual bool OpenDocument()
{
if (!CloseSingleDocument())
return false;
string path = String.Empty;
// TODO: better default path?
if (!UIAskForOpenName(ref path))
return false;
object newDocument = DocumentManager.LoadDocument(path);
if (newDocument == null)
return false;
// check if docmgr already returned a document info
DocumentInfo docInfo = newDocument as DocumentInfo;
if (docInfo == null) // create a new one otherwise
{
docInfo = new DocumentInfo(newDocument, DocumentManager);
docInfo.Path = path;
}
SetCurrentDocumentInfo(docInfo); // TODO: add to multi-list
RecentFiles.Add(docInfo.Path);
OnOpenedDocument(docInfo);
RefreshParentTitle(true);
return true;
}
internal virtual void HandleMenuCommand(MenuItemEventArgs ea)
{
switch (ea.Command)
{
case EFSCommand.New:
NewDocument();
break;
case EFSCommand.Close:
if (CurrentDocumentInfo != null)
CloseDocument(CurrentDocumentInfo);
break;
case EFSCommand.Save:
if (CurrentDocumentInfo != null)
{
SaveDocument(CurrentDocumentInfo, EFSCommand.Save);
}
break;
case EFSCommand.SaveAs:
if (CurrentDocumentInfo != null)
{
SaveDocument(CurrentDocumentInfo, EFSCommand.SaveAs);
}
break;
case EFSCommand.SaveCopyAs:
if (CurrentDocumentInfo != null)
{
SaveDocument(CurrentDocumentInfo, EFSCommand.SaveCopyAs);
}
break;
case EFSCommand.Open:
OpenDocument();
break;
// todo: implement commands
default:
Debug.Assert(false, "FileSelect - Unsupported command: " + ea.Command.ToString());
break;
}
}
#endregion
#region MenuItem Adapter for WinForms
private class WrapTSMI : IMenuItem
{
private EFSCommand m_command;
private FileSelect m_owner;
public WrapTSMI(FileSelect owner, EFSCommand command, ToolStripMenuItem item)
{
if (m_owner == null)
throw new NullReferenceException();
m_owner = owner;
m_command = command;
Item = item;
Item.Click += new EventHandler(Item_Click);
}
void Item_Click(object sender, EventArgs e)
{
if (Click == null)
return;
ToolStripItem item = sender as ToolStripItem;
if (item != null && item.Tag is OpenFileTag)
{
// TODO remove MenuItemEventArgs with three params for HandleMenuCommand
// TODO: add file name
m_owner.HandleMenuCommand(new MenuItemEventArgs(sender, e, EFSCommand.Recent));
}
else
{
m_owner.HandleMenuCommand(new MenuItemEventArgs(sender, e, m_command));
}
}
// TODO: wrappers for other menu types!?
public readonly ToolStripMenuItem Item;
#region IMenuItem Members
public bool Enabled
{
get { return Item.Enabled; }
set { Item.Enabled = value; }
}
public bool Contains(object o)
{
return o != null && Item == o;
}
private class OpenFileTag
{
public OpenFileTag(string fileName) { FileName = fileName; }
public readonly string FileName;
}
public void UpdateFileList(string[] files, string[] displayStrings)
{
ToolStrip parent = Item.GetCurrentParent();
int startIndex = parent.Items.IndexOf(Item);
int existing = 0; // number of existing elements
while (startIndex+existing < parent.Items.Count && (parent.Items[startIndex+existing].Tag is OpenFileTag))
++existing;
if (existing == 0)
existing = 1; // initially, the start item is not tagged.
// add or remove dummy items, as needed
for(int i=existing; i<files.Length; ++i)
parent.Items.Insert(startIndex+1, new ToolStripMenuItem());
for (int i = 0; i < existing - Math.Max(files.Length, 1); ++i)
parent.Items.RemoveAt(startIndex + 1);
bool showPlaceholders = files.Length != 0 || (Item.Site != null && Item.Site.DesignMode);
// update all items
for(int i=0; i<files.Length; ++i)
{
ToolStripItem it = parent.Items[startIndex + i];
it.Text = displayStrings[i];
it.Tag = new OpenFileTag(files[i]);
it.Click -= Item_Click; // remove handler if it was previously attached
it.Click += Item_Click;
}
// show/hide placeholder and follow-up separator
Item.Visible = showPlaceholders;
int nextIndex = startIndex + Math.Max(files.Length, 1);
if (nextIndex < parent.Items.Count &&
parent.Items[nextIndex] is ToolStripSeparator)
parent.Items[nextIndex].Visible = showPlaceholders;
}
#endregion
}
/// <summary>creates an IMenuItem for <see cref="Commands"/> from a <c>ToolStripMenuItem</c></summary>
public static IMenuItem Wrap(ToolStripMenuItem item) { return new WrapTSMI(item); }
#endregion
/// <summary>Sets the command associated with a <c>ToolStripMenuItem</c></summary>
/// <param name="tsmi"></param>
/// <param name="cmd"></param>
public void SetCommand(ToolStripMenuItem tsmi, EFSCommand cmd)
{
IMenuItem existing = GetMenuItemOf(tsmi);
if (existing != null)
Commands[existing] = cmd;
else
Commands[Wrap(tsmi)] = cmd;
}
#region UI overridables
protected virtual DialogResult UIAskSaveBeforeClose()
{
// TODO: use custom message string from base class
// TODO: include document info - title and/or path
return MessageBox.Show(
UIStrings.AskSaveChanges,
UIStrings.MessageBoxTitle,
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
}
protected virtual bool UIAskForSaveName(EFSCommand cmd, ref string name)
{
SaveFileDialog dlg = DialogSave;
dlg.FileName = name;
dlg.Title = cmd.ToString(); // TODO: need to use resources anyway
DialogResult result = dlg.ShowDialog();
if (result != DialogResult.OK)
return false;
name = dlg.FileName;
return true;
}
protected virtual bool UIAskForOpenName(ref string name)
{
OpenFileDialog dlg = DialogOpen;
dlg.FileName = name;
dlg.Title = EFSCommand.Open.ToString(); // TODO: need to use resources anyway
DialogResult result = dlg.ShowDialog();
if (result != DialogResult.OK)
return false;
name = dlg.FileName;
return true;
}
private bool UIAskCloseUnmodified()
{
return DialogResult.Yes == MessageBox.Show(
UIStrings.AskCloseDocument,
UIStrings.MessageBoxTitle, MessageBoxButtons.YesNo, MessageBoxIcon.Question);
}
#endregion
#region IExtenderProvider Members
#region IExtenderProvider and Getters / Setters
bool IExtenderProvider.CanExtend(object extendee)
{
if (extendee is ToolStripMenuItem)
return true;
return false;
}
private IMenuItem GetMenuItemOf(object o)
{
foreach (IMenuItem mi in Commands.GetAllItems())
{
if (mi.Contains(o))
return mi;
}
return null;
}
public void SetFileSelectCommand(object o, EFSCommand cmd)
{
ToolStripMenuItem tsi = o as ToolStripMenuItem;
if (tsi != null)
SetCommand(tsi, cmd);
}
public EFSCommand GetFileSelectCommand(object o)
{
IMenuItem mi = GetMenuItemOf(o);
if (mi != null)
return Commands[mi];
else
return EFSCommand.None;
}
#endregion
#endregion
}
/// <summary>Event Data for <see cref="FileSelect"/></summary>
public class FileSelectEventArgs : EventArgs
{
internal FileSelectEventArgs(EFSCommand command, DocumentInfo documentInfo, string path, bool resetDirty)
{
Command = command;
DocumentInfo = documentInfo;
Path = path;
ResetDirty = resetDirty;
}
internal FileSelectEventArgs(EFSCommand command, DocumentInfo documentInfo)
: this(command, documentInfo, null, false)
{ }
/// <summary>Type of the event.</summary>
public readonly EFSCommand Command;
/// <summary>DocumentInfo for the document that caused the event</summary>
public readonly DocumentInfo DocumentInfo;
/// <summary>Path, used in Save and Open events.</summary>
public readonly string Path;
/// <summary>Dirty flag will be reset, used with Save and SaveAs events.</summary>
public readonly bool ResetDirty;
bool m_bContinueSave = true;
/// <summary>Used in BeforeSaveEvent.</summary>
public bool ContinueSave
{
get { return m_bContinueSave; }
set
{
if (Command != EFSCommand.BeforeSave)
throw new InvalidOperationException("Setting BeforeSaveResult is only valid in BeforeSaveDocument handler.");
m_bContinueSave = value;
}
}
}
}