You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
481 lines
12 KiB
481 lines
12 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <owner name="Daniel Grunwald" email="daniel@danielgrunwald.de"/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.IO; |
|
using ICSharpCode.SharpDevelop.Gui; |
|
using ICSharpCode.Core; |
|
|
|
namespace ICSharpCode.SharpDevelop |
|
{ |
|
/// <summary> |
|
/// Represents an opened file. |
|
/// </summary> |
|
public abstract class OpenedFile : ICanBeDirty |
|
{ |
|
protected IViewContent currentView; |
|
bool inLoadOperation; |
|
bool inSaveOperation; |
|
|
|
/// <summary> |
|
/// holds unsaved file content in memory when view containing the file was closed but no other view |
|
/// activated |
|
/// </summary> |
|
byte[] fileData; |
|
|
|
#region IsDirty |
|
bool isDirty; |
|
public event EventHandler IsDirtyChanged; |
|
|
|
/// <summary> |
|
/// Gets/sets if the file is has unsaved changes. |
|
/// </summary> |
|
public bool IsDirty { |
|
get { return isDirty;} |
|
set { |
|
if (isDirty != value) { |
|
isDirty = value; |
|
|
|
if (IsDirtyChanged != null) { |
|
IsDirtyChanged(this, EventArgs.Empty); |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Marks the file as dirty if it currently is not in a load operation. |
|
/// </summary> |
|
public virtual void MakeDirty() |
|
{ |
|
if (!inLoadOperation) { |
|
this.IsDirty = true; |
|
} |
|
} |
|
#endregion |
|
|
|
bool isUntitled; |
|
|
|
/// <summary> |
|
/// Gets if the file is untitled. Untitled files show a "Save as" dialog when they are saved. |
|
/// </summary> |
|
public bool IsUntitled { |
|
get { return isUntitled; } |
|
protected set { isUntitled = value; } |
|
} |
|
|
|
string fileName; |
|
|
|
/// <summary> |
|
/// Gets the name of the file. |
|
/// </summary> |
|
public string FileName { |
|
get { return fileName; } |
|
set { |
|
if (fileName == value) return; |
|
|
|
value = FileUtility.NormalizePath(value); |
|
|
|
if (fileName != value) { |
|
ChangeFileName(value); |
|
} |
|
} |
|
} |
|
|
|
protected virtual void ChangeFileName(string newValue) |
|
{ |
|
WorkbenchSingleton.AssertMainThread(); |
|
|
|
fileName = newValue; |
|
|
|
if (FileNameChanged != null) { |
|
FileNameChanged(this, EventArgs.Empty); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Occurs when the file name has changed. |
|
/// </summary> |
|
public event EventHandler FileNameChanged; |
|
|
|
public abstract event EventHandler FileClosed; |
|
|
|
/// <summary> |
|
/// Use this method to save the file to disk using a new name. |
|
/// </summary> |
|
public void SaveToDisk(string newFileName) |
|
{ |
|
this.FileName = newFileName; |
|
this.IsUntitled = false; |
|
SaveToDisk(); |
|
} |
|
|
|
public abstract void RegisterView(IViewContent view); |
|
public abstract void UnregisterView(IViewContent view); |
|
|
|
public virtual void CloseIfAllViewsClosed() |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Forces initialization of the specified view. |
|
/// </summary> |
|
public virtual void ForceInitializeView(IViewContent view) |
|
{ |
|
if (view == null) |
|
throw new ArgumentNullException("view"); |
|
|
|
if (currentView != view) { |
|
if (currentView == null) { |
|
SwitchedToView(view); |
|
} else { |
|
try { |
|
inLoadOperation = true; |
|
using (Stream sourceStream = OpenRead()) { |
|
view.Load(this, sourceStream); |
|
} |
|
} finally { |
|
inLoadOperation = false; |
|
} |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the list of view contents registered with this opened file. |
|
/// </summary> |
|
public abstract IList<IViewContent> RegisteredViewContents { |
|
get; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the view content that currently edits this file. |
|
/// If there are multiple view contents registered, this returns the view content that was last |
|
/// active. The property might return null even if view contents are registered if the last active |
|
/// content was closed. In that case, the file is stored in-memory and loaded when one of the |
|
/// registered view contents becomes active. |
|
/// </summary> |
|
public IViewContent CurrentView { |
|
get { return currentView; } |
|
} |
|
|
|
/// <summary> |
|
/// Opens the file for reading. |
|
/// </summary> |
|
public virtual Stream OpenRead() |
|
{ |
|
if (fileData != null) { |
|
return new MemoryStream(fileData, false); |
|
} else { |
|
return new FileStream(FileName, FileMode.Open, FileAccess.Read); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Sets the internally stored data to the specified byte array. |
|
/// This method should only be used when there is no current view or by the |
|
/// current view. |
|
/// </summary> |
|
/// <remarks> |
|
/// Use this method to specify the initial file content if you use a OpenedFile instance |
|
/// for a file that doesn't exist on disk but should be automatically created when a view |
|
/// with the file is saved, e.g. for .resx files created by the forms designer. |
|
/// </remarks> |
|
public virtual void SetData(byte[] fileData) |
|
{ |
|
if (fileData == null) |
|
throw new ArgumentNullException("fileData"); |
|
if (inLoadOperation) |
|
throw new InvalidOperationException("SetData cannot be used while loading"); |
|
if (inSaveOperation) |
|
throw new InvalidOperationException("SetData cannot be used while saving"); |
|
|
|
this.fileData = fileData; |
|
} |
|
|
|
/// <summary> |
|
/// Save the file to disk using the current name. |
|
/// </summary> |
|
public virtual void SaveToDisk() |
|
{ |
|
if (IsUntitled) |
|
throw new InvalidOperationException("Cannot save an untitled file to disk!"); |
|
|
|
LoggingService.Debug("Save " + FileName); |
|
bool safeSaving = FileService.SaveUsingTemporaryFile && File.Exists(FileName); |
|
string saveAs = safeSaving ? FileName + ".bak" : FileName; |
|
using (FileStream fs = new FileStream(saveAs, FileMode.Create, FileAccess.Write)) { |
|
if (currentView != null) { |
|
SaveCurrentViewToStream(fs); |
|
} else { |
|
fs.Write(fileData, 0, fileData.Length); |
|
} |
|
} |
|
if (safeSaving) { |
|
DateTime creationTime = File.GetCreationTimeUtc(FileName); |
|
File.Delete(FileName); |
|
try { |
|
File.Move(saveAs, FileName); |
|
} catch (UnauthorizedAccessException) { |
|
// sometime File.Move raise exception (TortoiseSVN, Anti-vir ?) |
|
// try again after short delay |
|
System.Threading.Thread.Sleep(250); |
|
File.Move(saveAs, FileName); |
|
} |
|
File.SetCreationTimeUtc(FileName, creationTime); |
|
} |
|
IsDirty = false; |
|
} |
|
|
|
// /// <summary> |
|
// /// Called before saving the current view. This event is raised both when saving to disk and to memory (for switching between views). |
|
// /// </summary> |
|
// public event EventHandler SavingCurrentView; |
|
// |
|
// /// <summary> |
|
// /// Called after saving the current view. This event is raised both when saving to disk and to memory (for switching between views). |
|
// /// </summary> |
|
// public event EventHandler SavedCurrentView; |
|
|
|
|
|
void SaveCurrentViewToStream(Stream stream) |
|
{ |
|
// if (SavingCurrentView != null) |
|
// SavingCurrentView(this, EventArgs.Empty); |
|
inSaveOperation = true; |
|
try { |
|
currentView.Save(this, stream); |
|
} finally { |
|
inSaveOperation = false; |
|
} |
|
// if (SavedCurrentView != null) |
|
// SavedCurrentView(this, EventArgs.Empty); |
|
} |
|
|
|
protected void SaveCurrentView() |
|
{ |
|
using (MemoryStream memoryStream = new MemoryStream()) { |
|
SaveCurrentViewToStream(memoryStream); |
|
fileData = memoryStream.ToArray(); |
|
} |
|
} |
|
|
|
|
|
protected void SwitchedToView(IViewContent newView) |
|
{ |
|
if (currentView != null) { |
|
if (newView.SupportsSwitchToThisWithoutSaveLoad(this, currentView) |
|
|| currentView.SupportsSwitchFromThisWithoutSaveLoad(this, newView)) |
|
{ |
|
// switch without Save/Load |
|
currentView.SwitchFromThisWithoutSaveLoad(this, newView); |
|
newView.SwitchToThisWithoutSaveLoad(this, currentView); |
|
|
|
currentView = newView; |
|
return; |
|
} |
|
} |
|
if (currentView != null) { |
|
SaveCurrentView(); |
|
} |
|
try { |
|
inLoadOperation = true; |
|
Properties memento = GetMemento(newView); |
|
using (Stream sourceStream = OpenRead()) { |
|
currentView = newView; |
|
fileData = null; |
|
newView.Load(this, sourceStream); |
|
} |
|
RestoreMemento(newView, memento); |
|
} finally { |
|
inLoadOperation = false; |
|
} |
|
} |
|
|
|
public virtual void ReloadFromDisk() |
|
{ |
|
var r = FileUtility.ObservedLoad(ReloadFromDiskInternal, FileName); |
|
if (r == FileOperationResult.Failed) { |
|
if (currentView != null && currentView.WorkbenchWindow != null) { |
|
currentView.WorkbenchWindow.CloseWindow(true); |
|
} |
|
} |
|
} |
|
|
|
void ReloadFromDiskInternal() |
|
{ |
|
fileData = null; |
|
if (currentView != null) { |
|
try { |
|
inLoadOperation = true; |
|
Properties memento = GetMemento(currentView); |
|
using (Stream sourceStream = OpenRead()) { |
|
currentView.Load(this, sourceStream); |
|
} |
|
IsDirty = false; |
|
RestoreMemento(currentView, memento); |
|
} finally { |
|
inLoadOperation = false; |
|
} |
|
} |
|
} |
|
|
|
static Properties GetMemento(IViewContent viewContent) |
|
{ |
|
IMementoCapable mementoCapable = viewContent as IMementoCapable; |
|
if (mementoCapable == null) { |
|
return null; |
|
} else { |
|
return mementoCapable.CreateMemento(); |
|
} |
|
} |
|
|
|
static void RestoreMemento(IViewContent viewContent, Properties memento) |
|
{ |
|
if (memento != null) { |
|
((IMementoCapable)viewContent).SetMemento(memento); |
|
} |
|
} |
|
} |
|
|
|
sealed class FileServiceOpenedFile : OpenedFile |
|
{ |
|
List<IViewContent> registeredViews = new List<IViewContent>(); |
|
FileChangeWatcher fileChangeWatcher; |
|
|
|
protected override void ChangeFileName(string newValue) |
|
{ |
|
FileService.OpenedFileFileNameChange(this, this.FileName, newValue); |
|
base.ChangeFileName(newValue); |
|
} |
|
|
|
internal FileServiceOpenedFile(string fileName) |
|
{ |
|
this.FileName = fileName; |
|
IsUntitled = false; |
|
fileChangeWatcher = new FileChangeWatcher(this); |
|
} |
|
|
|
internal FileServiceOpenedFile(byte[] fileData) |
|
{ |
|
this.FileName = null; |
|
SetData(fileData); |
|
IsUntitled = true; |
|
MakeDirty(); |
|
fileChangeWatcher = new FileChangeWatcher(this); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the list of view contents registered with this opened file. |
|
/// </summary> |
|
public override IList<IViewContent> RegisteredViewContents { |
|
get { return registeredViews.AsReadOnly(); } |
|
} |
|
|
|
public override void ForceInitializeView(IViewContent view) |
|
{ |
|
if (view == null) |
|
throw new ArgumentNullException("view"); |
|
if (!registeredViews.Contains(view)) |
|
throw new ArgumentException("registeredViews must contain view"); |
|
|
|
base.ForceInitializeView(view); |
|
} |
|
|
|
public override void RegisterView(IViewContent view) |
|
{ |
|
if (view == null) |
|
throw new ArgumentNullException("view"); |
|
if (registeredViews.Contains(view)) |
|
throw new ArgumentException("registeredViews already contains view"); |
|
|
|
registeredViews.Add(view); |
|
|
|
if (WorkbenchSingleton.Workbench != null) { |
|
WorkbenchSingleton.Workbench.ActiveViewContentChanged += WorkbenchActiveViewContentChanged; |
|
if (WorkbenchSingleton.Workbench.ActiveViewContent == view) { |
|
SwitchedToView(view); |
|
} |
|
} |
|
#if DEBUG |
|
view.Disposed += ViewDisposed; |
|
#endif |
|
} |
|
|
|
public override void UnregisterView(IViewContent view) |
|
{ |
|
if (view == null) |
|
throw new ArgumentNullException("view"); |
|
Debug.Assert(registeredViews.Contains(view)); |
|
|
|
if (WorkbenchSingleton.Workbench != null) { |
|
WorkbenchSingleton.Workbench.ActiveViewContentChanged -= WorkbenchActiveViewContentChanged; |
|
} |
|
#if DEBUG |
|
view.Disposed -= ViewDisposed; |
|
#endif |
|
|
|
registeredViews.Remove(view); |
|
if (registeredViews.Count > 0) { |
|
if (currentView == view) { |
|
SaveCurrentView(); |
|
currentView = null; |
|
} |
|
} else { |
|
// all views to the file were closed |
|
CloseIfAllViewsClosed(); |
|
} |
|
} |
|
|
|
public override void CloseIfAllViewsClosed() |
|
{ |
|
if (registeredViews.Count == 0) { |
|
FileService.OpenedFileClosed(this); |
|
|
|
FileClosed.RaiseEvent(this, EventArgs.Empty); |
|
|
|
if (fileChangeWatcher != null) { |
|
fileChangeWatcher.Dispose(); |
|
fileChangeWatcher = null; |
|
} |
|
} |
|
} |
|
|
|
#if DEBUG |
|
void ViewDisposed(object sender, EventArgs e) |
|
{ |
|
Debug.Fail("View was disposed while still registered with OpenedFile!"); |
|
} |
|
#endif |
|
|
|
void WorkbenchActiveViewContentChanged(object sender, EventArgs e) |
|
{ |
|
IViewContent newView = WorkbenchSingleton.Workbench.ActiveViewContent; |
|
|
|
if (!registeredViews.Contains(newView)) |
|
return; |
|
|
|
SwitchedToView(newView); |
|
} |
|
|
|
public override void SaveToDisk() |
|
{ |
|
try { |
|
if (fileChangeWatcher != null) |
|
fileChangeWatcher.Enabled = false; |
|
base.SaveToDisk(); |
|
} finally { |
|
if (fileChangeWatcher != null) |
|
fileChangeWatcher.Enabled = true; |
|
} |
|
} |
|
|
|
public override event EventHandler FileClosed; |
|
} |
|
}
|
|
|