From fb3fa4dac766b015a72668b26fe8f3cf4a456908 Mon Sep 17 00:00:00 2001 From: Daniel Grunwald Date: Mon, 8 Feb 2010 23:10:38 +0000 Subject: [PATCH] Rewritten IProgressMonitor: - allow nested progress monitors - use .NET 4 cancellation framework git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5483 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Frontend/AddIn/Src/ProfilerRunner.cs | 15 +- .../Refactoring/ResourceRefactoringService.cs | 28 +- .../SearchAndReplace/Project/Engine/Search.cs | 2 +- .../Project/Engine/SearchReplaceManager.cs | 2 +- .../Project/ICSharpCode.SharpDevelop.csproj | 1 + .../Gui/Components/StatusBar/SdStatusBar.cs | 87 +++-- .../Src/Gui/Dialogs/AsynchronousWaitDialog.cs | 366 ++++++++++-------- .../Base/Project/Src/Gui/IProgressMonitor.cs | 124 +++--- .../Base/Project/Src/Gui/ProgressCollector.cs | 335 ++++++++++++++++ .../Base/Project/Src/Project/BuildEngine.cs | 152 +++----- .../Project/Converter/LanguageConverter.cs | 29 +- .../Project/Src/Project/IBuildFeedbackSink.cs | 7 + .../Src/Project/ProjectLoadInformation.cs | 18 +- .../Project/Src/Project/Solution/Solution.cs | 22 +- .../ParserService/LoadSolutionProjects.cs | 79 ++-- .../ParserService/ParseProjectContent.cs | 16 +- .../ProjectBinding/ProjectBindingService.cs | 12 +- .../CompileModifiedProjectsOnly.cs | 4 + .../RefactoringService/RefactoringService.cs | 37 +- .../Services/StatusBar/StatusBarService.cs | 134 +++---- 20 files changed, 893 insertions(+), 577 deletions(-) create mode 100644 src/Main/Base/Project/Src/Gui/ProgressCollector.cs diff --git a/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs b/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs index 0c6eb726b7..b59070b27c 100644 --- a/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs +++ b/src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs @@ -80,24 +80,27 @@ namespace ICSharpCode.Profiler.AddIn void FinishSession() { - using (AsynchronousWaitDialog dlg = AsynchronousWaitDialog.ShowWaitDialog(StringParser.Parse("${res:AddIns.Profiler.Messages.PreparingForAnalysis}"), true)) { - try { + try { + using (AsynchronousWaitDialog dlg = AsynchronousWaitDialog.ShowWaitDialog(StringParser.Parse("${res:AddIns.Profiler.Messages.PreparingForAnalysis}"), true)) { profiler.Dispose(); WorkbenchSingleton.SafeThreadAsyncCall(() => { controlWindow.AllowClose = true; this.controlWindow.Close(); }); if (database != null) { - database.WriteTo(writer, progress => !dlg.IsCancelled); + database.WriteTo(writer, progress => { + dlg.Progress = progress; + return !dlg.CancellationToken.IsCancellationRequested; + }); writer.Close(); database.Close(); } else { writer.Close(); } - if (!dlg.IsCancelled) + if (!dlg.CancellationToken.IsCancellationRequested) OnRunFinished(EventArgs.Empty); - } catch (Exception ex) { - Debug.Print(ex.ToString()); } + } catch (Exception ex) { + MessageService.ShowException(ex); } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs index c920df325f..10da853f87 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs @@ -73,12 +73,14 @@ namespace Hornung.ResourceToolkit.Refactoring ICollection files = GetPossibleFiles(scope); if (monitor != null) { - monitor.BeginTask("${res:SharpDevelop.Refactoring.FindingReferences}", files.Count, true); + monitor.TaskName = StringParser.Parse("${res:SharpDevelop.Refactoring.FindingReferences}"); } - + double workDone = 0; foreach (string fileName in files) { - - if (monitor != null && monitor.IsCancelled) { + if (monitor != null) + monitor.Progress = workDone / files.Count; + workDone += 1; + if (monitor != null && monitor.CancellationToken.IsCancellationRequested) { return null; } @@ -91,13 +93,11 @@ namespace Hornung.ResourceToolkit.Refactoring } catch (FileNotFoundException) { } if (doc == null) { - if (monitor != null) ++monitor.WorkDone; continue; } string fileContent = doc.Text; if (String.IsNullOrEmpty(fileContent)) { - if (monitor != null) ++monitor.WorkDone; continue; } @@ -139,15 +139,12 @@ namespace Hornung.ResourceToolkit.Refactoring } } - - if (monitor != null) ++monitor.WorkDone; } LoggingService.Info("ResourceToolkit: FindReferences finished in "+(DateTime.UtcNow - startTime).TotalSeconds.ToString(System.Globalization.CultureInfo.CurrentCulture)+"s"); } finally { NRefactoryAstCacheService.DisableCache(); - if (monitor != null) monitor.Done(); } return references; @@ -210,10 +207,6 @@ namespace Hornung.ResourceToolkit.Refactoring return null; } - if (monitor != null) { - monitor.BeginTask(null, 0, false); - } - List unused = new List(); // Get a list of all referenced resource files. @@ -257,8 +250,6 @@ namespace Hornung.ResourceToolkit.Refactoring } } - if (monitor != null) monitor.Done(); - return unused.AsReadOnly(); } @@ -302,10 +293,6 @@ namespace Hornung.ResourceToolkit.Refactoring return; } - if (monitor != null) { - monitor.BeginTask(null, 0, false); - } - try { // rename definition (if present) if (rrr.ResourceFileContent.ContainsKey(rrr.Key)) { @@ -319,7 +306,6 @@ namespace Hornung.ResourceToolkit.Refactoring if (monitor != null) monitor.ShowingDialog = true; MessageService.ShowWarningFormatted("${res:Hornung.ResourceToolkit.ErrorProcessingResourceFile}" + Environment.NewLine + ex.Message, rrr.ResourceFileContent.FileName); if (monitor != null) monitor.ShowingDialog = false; - if (monitor != null) monitor.Done(); // Do not rename the references when renaming the definition failed. return; } @@ -340,8 +326,6 @@ namespace Hornung.ResourceToolkit.Refactoring if (monitor != null) monitor.ShowingDialog = false; } } - - if (monitor != null) monitor.Done(); } // ******************************************************************************************************************************** diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs b/src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs index 9237638178..8a0f794d6c 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs @@ -89,7 +89,7 @@ namespace SearchAndReplace Debug.Assert(documentIterator != null); Debug.Assert(textIteratorBuilder != null); - if (monitor != null && monitor.IsCancelled) + if (monitor != null && monitor.CancellationToken.IsCancellationRequested) return null; if (info != null && textIterator != null && documentIterator.CurrentFileName != null) { diff --git a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs index 34bc6d6b73..3887226965 100644 --- a/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs +++ b/src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs @@ -352,7 +352,7 @@ namespace SearchAndReplace static void ShowNotFoundMessage(IProgressMonitor monitor) { - if (monitor != null && monitor.IsCancelled) + if (monitor != null && monitor.CancellationToken.IsCancellationRequested) return; if (monitor != null) monitor.ShowingDialog = true; MessageBox.Show(WorkbenchSingleton.MainWin32Window, diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj index 37d9a2b86e..3a11296a11 100644 --- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj +++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj @@ -206,6 +206,7 @@ + diff --git a/src/Main/Base/Project/Src/Gui/Components/StatusBar/SdStatusBar.cs b/src/Main/Base/Project/Src/Gui/Components/StatusBar/SdStatusBar.cs index 24c19597f0..3e5edd9a27 100644 --- a/src/Main/Base/Project/Src/Gui/Components/StatusBar/SdStatusBar.cs +++ b/src/Main/Base/Project/Src/Gui/Components/StatusBar/SdStatusBar.cs @@ -10,6 +10,7 @@ using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; +using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using ICSharpCode.Core; @@ -42,6 +43,10 @@ namespace ICSharpCode.SharpDevelop.Gui { cursorStatusBarPanel.Width = 150; modeStatusBarPanel.Width = 25; + + statusProgressBar.Minimum = 0; + statusProgressBar.Maximum = 1; + statusProgressBarItem.Visibility = Visibility.Hidden; statusProgressBarItem.Width = 100; statusProgressBarItem.Content = statusProgressBar; @@ -103,52 +108,56 @@ namespace ICSharpCode.SharpDevelop.Gui bool statusProgressBarIsVisible; string currentTaskName; + OperationStatus currentStatus; + SolidColorBrush progressForegroundBrush; - public void DisplayProgress(string taskName, int workDone, int totalWork) + public void DisplayProgress(string taskName, double workDone, OperationStatus status) { - if (taskName == null) - taskName = ""; - if (totalWork < 0) - totalWork = 0; - if (workDone < 0) - workDone = 0; - if (workDone > totalWork) - workDone = totalWork; + if (!statusProgressBarIsVisible) { + statusProgressBarItem.Visibility = Visibility.Visible; + statusProgressBarIsVisible = true; + } - WorkbenchSingleton.SafeThreadAsyncCall( - delegate { - if (!statusProgressBarIsVisible) { - statusProgressBarItem.Visibility = Visibility.Visible; - statusProgressBarIsVisible = true; - } - - if (totalWork == 0) { - statusProgressBar.IsIndeterminate = true; - } else { - statusProgressBar.IsIndeterminate = false; - if (statusProgressBar.Maximum != totalWork) { - if (statusProgressBar.Value > totalWork) - statusProgressBar.Value = 0; - statusProgressBar.Maximum = totalWork; - } - statusProgressBar.Value = workDone; - } - - if (currentTaskName != taskName) { - currentTaskName = taskName; - jobNamePanel.Content = StringParser.Parse(taskName); - } - }); + if (double.IsNaN(workDone)) { + statusProgressBar.IsIndeterminate = true; + status = OperationStatus.Normal; // indeterminate doesn't support foreground color + } else { + statusProgressBar.IsIndeterminate = false; + statusProgressBar.Value = workDone; + } + + if (status != currentStatus) { + if (progressForegroundBrush == null) { + SolidColorBrush defaultForeground = statusProgressBar.Foreground as SolidColorBrush; + progressForegroundBrush = new SolidColorBrush(defaultForeground != null ? defaultForeground.Color : Colors.Blue); + } + + if (status == OperationStatus.Error) { + statusProgressBar.Foreground = progressForegroundBrush; + progressForegroundBrush.BeginAnimation(SolidColorBrush.ColorProperty, new ColorAnimation( + Colors.Red, new Duration(TimeSpan.FromSeconds(0.6)), FillBehavior.HoldEnd)); + } else if (status == OperationStatus.Warning) { + statusProgressBar.Foreground = progressForegroundBrush; + progressForegroundBrush.BeginAnimation(SolidColorBrush.ColorProperty, new ColorAnimation( + Colors.YellowGreen, new Duration(TimeSpan.FromSeconds(0.6)), FillBehavior.HoldEnd)); + } else { + statusProgressBar.ClearValue(ProgressBar.ForegroundProperty); + progressForegroundBrush = null; + } + currentStatus = status; + } + + if (currentTaskName != taskName) { + currentTaskName = taskName; + jobNamePanel.Content = taskName; + } } public void HideProgress() { - WorkbenchSingleton.SafeThreadAsyncCall( - delegate { - statusProgressBarIsVisible = false; - statusProgressBarItem.Visibility = Visibility.Collapsed; - jobNamePanel.Content = currentTaskName = ""; - }); + statusProgressBarIsVisible = false; + statusProgressBarItem.Visibility = Visibility.Collapsed; + jobNamePanel.Content = currentTaskName = ""; } } } diff --git a/src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs b/src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs index 7f8d9be8c4..14cfb7fcdb 100644 --- a/src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs +++ b/src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs @@ -6,20 +6,30 @@ // using System; +using System.ComponentModel; using System.Drawing; using System.Threading; using System.Windows.Forms; -using ICSharpCode.SharpDevelop.Gui; + using ICSharpCode.Core; +using ICSharpCode.SharpDevelop.Gui; namespace ICSharpCode.SharpDevelop.Gui { internal sealed partial class AsynchronousWaitDialogForm { - internal AsynchronousWaitDialogForm() + internal AsynchronousWaitDialogForm(bool allowCancel) { InitializeComponent(); cancelButton.Text = ResourceService.GetString("Global.CancelButtonText"); + + if (allowCancel) { + cancelButton.Visible = true; + progressBar.Width = cancelButton.Left - 8 - progressBar.Left; + } else { + cancelButton.Visible = false; + progressBar.Width = cancelButton.Right - progressBar.Left; + } } } @@ -30,27 +40,26 @@ namespace ICSharpCode.SharpDevelop.Gui /// long_running_action(); /// } /// or: - /// using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) { + /// using (IProgressMonitor monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) { /// long_running_action(monitor); /// } /// - public sealed class AsynchronousWaitDialog : IProgressMonitor, IDisposable + public sealed class AsynchronousWaitDialog : IProgressMonitor { /// /// Delay until the wait dialog becomes visible, in ms. /// public const int ShowWaitDialogDelay = 500; - readonly object lockObject = new object(); - bool disposed; + readonly string titleName; + readonly string defaultTaskName; + readonly CancellationTokenSource cancellation; + readonly ProgressCollector collector; + + readonly SynchronizationHelper synchronizationHelper = new SynchronizationHelper(); AsynchronousWaitDialogForm dlg; - volatile int totalWork; - volatile string titleName; - volatile string taskName; - volatile int workDone; - volatile bool cancelled; - volatile bool allowCancel; + #region Constructors /// /// Shows a wait dialog. /// @@ -59,11 +68,7 @@ namespace ICSharpCode.SharpDevelop.Gui /// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object public static AsynchronousWaitDialog ShowWaitDialog(string titleName) { - if (titleName == null) - throw new ArgumentNullException("titleName"); - AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, false); - h.Start(); - return h; + return ShowWaitDialog(titleName, null, false); } /// @@ -74,20 +79,109 @@ namespace ICSharpCode.SharpDevelop.Gui /// AsynchronousWaitDialog object - you can use it to access the wait dialog's properties. /// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object public static AsynchronousWaitDialog ShowWaitDialog(string titleName, bool allowCancel) + { + return ShowWaitDialog(titleName, null, allowCancel); + } + + /// + /// Shows a wait dialog that does not support cancelling. + /// + /// Title of the wait dialog + /// Specifies whether a cancel button should be shown. + /// The default description text, if no named task is active. + /// AsynchronousWaitDialog object - you can use it to access the wait dialog's properties. + /// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object + public static AsynchronousWaitDialog ShowWaitDialog(string titleName, string defaultTaskName, bool allowCancel) { if (titleName == null) throw new ArgumentNullException("titleName"); - AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, allowCancel); + AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, allowCancel); h.Start(); return h; } - private AsynchronousWaitDialog(string titleName, bool allowCancel) + /// + /// Shows a wait dialog that does supports cancelling. + /// + /// Title of the wait dialog + /// The default description text, if no named task is active. + /// The action to run within the wait dialog + public static void RunInCancellableWaitDialog(string titleName, string defaultTaskName, Action action) + { + if (titleName == null) + throw new ArgumentNullException("titleName"); + using (AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, true)) { + h.Start(); + try { + action(h); + } catch (OperationCanceledException ex) { + // consume OperationCanceledException + if (ex.CancellationToken != h.CancellationToken) + throw; + } + } + } + + private AsynchronousWaitDialog(string titleName, string defaultTaskName, bool allowCancel) + { + this.titleName = StringParser.Parse(titleName); + this.defaultTaskName = StringParser.Parse(defaultTaskName ?? "${res:Global.PleaseWait}"); + if (allowCancel) + this.cancellation = new CancellationTokenSource(); + this.collector = new ProgressCollector(synchronizationHelper, allowCancel ? cancellation.Token : CancellationToken.None); + } + #endregion + + #region SynchronizationHelper + // this class works around the issue that we don't initially have an ISynchronizeInvoke implementation + // for the target thread, we only create it after ShowWaitDialogDelay. + sealed class SynchronizationHelper : ISynchronizeInvoke { - this.titleName = titleName; - Done(); // set default values for titleName - this.allowCancel = allowCancel; + volatile ISynchronizeInvoke targetSynchronizeInvoke; + + public bool InvokeRequired { + get { + ISynchronizeInvoke si = targetSynchronizeInvoke; + return si != null && si.InvokeRequired; + } + } + + public IAsyncResult BeginInvoke(Delegate method, object[] args) + { + ISynchronizeInvoke si = targetSynchronizeInvoke; + if (si != null) + return si.BeginInvoke(method, args); + else { + // When target is not available, invoke method on current thread, but use a lock + // to ensure we don't run multiple methods concurrently. + lock (this) { + method.DynamicInvoke(args); + return null; + } + // yes this is morally questionable - maybe it would be better to enqueue all invocations and run them later? + // ProgressCollector would have to avoid enqueuing stuff multiple times for all kinds of updates + // (currently it does this only with updates to the Progress property) + } + } + + public void SetTarget(ISynchronizeInvoke targetSynchronizeInvoke) + { + lock (this) { + this.targetSynchronizeInvoke = targetSynchronizeInvoke; + } + } + + public object EndInvoke(IAsyncResult result) + { + throw new NotSupportedException(); + } + + public object Invoke(Delegate method, object[] args) + { + throw new NotSupportedException(); + } } + #endregion #region Start waiting thread /// @@ -106,19 +200,29 @@ namespace ICSharpCode.SharpDevelop.Gui void Run() { Thread.Sleep(ShowWaitDialogDelay); - bool isShowingDialog; - lock (lockObject) { - if (disposed) - return; - dlg = new AsynchronousWaitDialogForm(); - dlg.Text = StringParser.Parse(titleName); - dlg.cancelButton.Click += CancelButtonClick; - UpdateTask(); - dlg.CreateControl(); - IntPtr h = dlg.Handle; // force handle creation - isShowingDialog = showingDialog; + if (collector.ProgressMonitorIsDisposed) + return; + + dlg = new AsynchronousWaitDialogForm(cancellation != null); + dlg.Text = titleName; + dlg.cancelButton.Click += CancelButtonClick; + dlg.CreateControl(); + IntPtr h = dlg.Handle; // force handle creation + + // ensure events occur on this thread, then register event handlers + synchronizationHelper.SetTarget(dlg); + collector.ProgressMonitorDisposed += progress_ProgressMonitorDisposed; + collector.PropertyChanged += progress_PropertyChanged; + + // check IsDisposed once again (we might have missed an event while we initialized the dialog): + if (collector.ProgressMonitorIsDisposed) { + dlg.Dispose(); + return; } - if (isShowingDialog) { + + progress_PropertyChanged(null, new System.ComponentModel.PropertyChangedEventArgs("TaskName")); + + if (collector.ShowingDialog) { Application.Run(); } else { Application.Run(dlg); @@ -129,166 +233,92 @@ namespace ICSharpCode.SharpDevelop.Gui /// /// Closes the wait dialog. /// - public void Dispose() - { - lock (lockObject) { - if (disposed) return; - disposed = true; - if (dlg != null) { - dlg.BeginInvoke(new MethodInvoker(DisposeInvoked)); - } - } - } - - void DisposeInvoked() + void progress_ProgressMonitorDisposed(object sender, EventArgs e) { dlg.Dispose(); Application.ExitThread(); } - public int WorkDone { - get { - return workDone; - } - set { - if (workDone != value) { - lock (lockObject) { - workDone = value; - if (dlg != null && disposed == false) { - dlg.BeginInvoke(new MethodInvoker(UpdateProgress)); - } - } - } - } - } + bool reshowTimerRunning = false; - public string TaskName { - get { - lock (lockObject) { - return taskName; + void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // show/hide dialog as required by ShowingDialog + if (dlg.Visible == collector.ShowingDialog) { + if (collector.ShowingDialog) { + dlg.Hide(); + } else if (!reshowTimerRunning) { + reshowTimerRunning = true; + var timer = new System.Windows.Forms.Timer(); + timer.Interval = 100; + timer.Tick += delegate { + timer.Dispose(); + reshowTimerRunning = false; + if (!collector.ShowingDialog) + dlg.Show(); + }; + timer.Start(); } } - set { - if (taskName != value) { - lock (lockObject) { - taskName = value; - if (dlg != null && disposed == false) { - dlg.BeginInvoke(new MethodInvoker(UpdateTask)); - } - } - } + + // update task name and progress + if (e.PropertyName == "TaskName") + dlg.taskLabel.Text = collector.TaskName ?? defaultTaskName; + if (double.IsNaN(collector.Progress)) { + dlg.progressBar.Style = ProgressBarStyle.Marquee; + } else { + dlg.progressBar.Style = ProgressBarStyle.Continuous; + dlg.progressBar.Value = Math.Max(0, Math.Min(100, (int)(collector.Progress * 100))); } } - /// - /// Begins a new task with the specified name and total amount of work. - /// - /// Name of the task. Use null to display "please wait..." message - /// Total amount of work in work units. Use 0 for unknown amount of work. - /// Specifies whether the task can be cancelled. - public void BeginTask(string name, int totalWork, bool allowCancel) + void CancelButtonClick(object sender, EventArgs e) { - if (name == null) - name = "${res:Global.PleaseWait}"; - if (totalWork < 0) - totalWork = 0; - - lock (lockObject) { - this.allowCancel = allowCancel; - this.totalWork = totalWork; - this.taskName = name; - if (dlg != null && disposed == false) { - dlg.BeginInvoke(new MethodInvoker(UpdateTask)); - } - } + dlg.cancelButton.Enabled = false; + if (cancellation != null) + cancellation.Cancel(); } - /// - /// Resets the task to a generic "please wait" with marquee progress bar. - /// - public void Done() - { - workDone = 0; - BeginTask(null, 0, false); + #region IProgressMonitor interface impl (forwards to progress.ProgressMonitor) + /// + public string TaskName { + get { return collector.ProgressMonitor.TaskName; } + set { collector.ProgressMonitor.TaskName = value; } } - void UpdateTask() - { - int totalWork = this.totalWork; - - dlg.taskLabel.Text = StringParser.Parse(taskName); - if (allowCancel) { - dlg.cancelButton.Visible = true; - dlg.progressBar.Width = dlg.cancelButton.Left - 8 - dlg.progressBar.Left; - } else { - dlg.cancelButton.Visible = false; - dlg.progressBar.Width = dlg.cancelButton.Right - dlg.progressBar.Left; - } - - if (totalWork > 0) { - if (dlg.progressBar.Value > totalWork) { - dlg.progressBar.Value = 0; - } - dlg.progressBar.Maximum = totalWork + 1; - dlg.progressBar.Style = ProgressBarStyle.Continuous; - } else { - dlg.progressBar.Style = ProgressBarStyle.Marquee; - } - UpdateProgress(); + /// + public double Progress { + get { return collector.ProgressMonitor.Progress; } + set { collector.ProgressMonitor.Progress = value; } } - void UpdateProgress() - { - int workDone = this.workDone; - if (workDone < 0) workDone = 0; - if (workDone > dlg.progressBar.Maximum) - workDone = dlg.progressBar.Maximum; - dlg.progressBar.Value = workDone; + /// + public bool ShowingDialog { + get { return collector.ProgressMonitor.ShowingDialog; } + set { collector.ProgressMonitor.ShowingDialog = value; } } - bool showingDialog; + /// + public OperationStatus Status { + get { return collector.ProgressMonitor.Status; } + set { collector.ProgressMonitor.Status = value; } + } - public bool ShowingDialog { - get { return showingDialog; } - set { - if (showingDialog != value) { - lock (lockObject) { - showingDialog = value; - if (dlg != null && disposed == false) { - if (value) { - dlg.BeginInvoke(new MethodInvoker(dlg.Hide)); - } else { - dlg.BeginInvoke(new MethodInvoker(delegate { - Thread.Sleep(100); - if (showingDialog) { - dlg.Show(); - } - })); - } - } - } - } - } + /// + public CancellationToken CancellationToken { + get { return collector.ProgressMonitor.CancellationToken; } } - void CancelButtonClick(object sender, EventArgs e) + /// + public IProgressMonitor CreateSubTask(double workAmount) { - dlg.cancelButton.Enabled = false; - if (!cancelled) { - cancelled = true; - EventHandler eh = Cancelled; - if (eh != null) { - eh(this, e); - } - } + return collector.ProgressMonitor.CreateSubTask(workAmount); } - public bool IsCancelled { - get { - return cancelled; - } + public void Dispose() + { + collector.ProgressMonitor.Dispose(); } - - public event EventHandler Cancelled; + #endregion } } diff --git a/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs b/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs index 17ca0518bd..edc2569410 100644 --- a/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs +++ b/src/Main/Base/Project/Src/Gui/IProgressMonitor.cs @@ -6,67 +6,88 @@ // using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; namespace ICSharpCode.SharpDevelop.Gui { /// - /// This is a basic interface to a "progress bar" type of - /// control. + /// Represents a target where an active task reports progress to. /// - public interface IProgressMonitor + /// + /// This interface is not thread-safe, but it can be used to report progress from background threads which will be + /// automatically sent to the correct GUI thread. + /// Using a progress monitor from multiple threads is possible if the user synchronizes the access. + /// + public interface IProgressMonitor : IDisposable { /// - /// Begins a new task with the specified name and total amount of work. + /// Gets/Sets the amount of work already done within this task. + /// Always uses a scale from 0 to 1 local to this progress monitor. /// - /// Name of the task. Use null to display a default message - /// Total amount of work in work units. Use 0 for unknown amount of work. - /// Specifies whether the task can be cancelled. - void BeginTask(string name, int totalWork, bool allowCancel); + double Progress { get; set; } /// - /// Gets/Sets the amount of work already done + /// Creates a nested task. /// - int WorkDone { - get; - set; - } + /// The amount of work this sub-task performs in relation to the work of this task. + /// That means, this parameter is used as a scaling factor for work performed within the subtask. + /// A new progress monitor representing the sub-task. + /// Multiple child progress monitors can be used at once; even concurrently on multiple threads. + IProgressMonitor CreateSubTask(double workAmount); /// - /// Marks the current task as Done. + /// Gets/Sets the name to show while the task is active. /// - void Done(); - - /// - /// Gets/Sets the current task name. - /// - string TaskName { - get; - set; - } + string TaskName { get; set; } /// /// Gets/sets if the task current shows a modal dialog. Set this property to true to make progress /// dialogs windows temporarily invisible while your modal dialog is showing. /// - bool ShowingDialog { + bool ShowingDialog { // TODO: get rid of this. Don't mix calculations and UI! get; set; } /// - /// Gets whether the user has cancelled the operation. + /// Gets the cancellation token. /// - bool IsCancelled { - get; - } + CancellationToken CancellationToken { get; } /// - /// Occurs when the user cancels the operation. - /// This event could be raised on any thread. + /// Gets/Sets the operation status. + /// + /// Note: the status of the whole operation is the most severe status of all nested monitors. + /// The more severe value persists even if the child monitor gets disposed. /// - event EventHandler Cancelled; + OperationStatus Status { get; set; } } + /// + /// Represents the status of a operation with progress monitor. + /// + public enum OperationStatus : byte + { + /// + /// Everything is normal. + /// + Normal, + /// + /// There was at least one warning. + /// + Warning, + /// + /// There was at least one error. + /// + Error + } + + /// + /// Adapter IDomProgressMonitor -> IProgressMonitor + /// public sealed class DomProgressMonitor : Dom.IDomProgressMonitor { IProgressMonitor monitor; @@ -92,41 +113,30 @@ namespace ICSharpCode.SharpDevelop.Gui } } - internal class DummyProgressMonitor : IProgressMonitor + /// + /// Dummy progress monitor implementation that does not report the progress anywhere. + /// + public sealed class DummyProgressMonitor : IProgressMonitor { - int workDone; - string taskName; - bool showingDialog; + public string TaskName { get; set; } - public int WorkDone { - get { return workDone; } - set { workDone = value; } - } + public bool ShowingDialog { get; set; } - public string TaskName { - get { return taskName; } - set { taskName = value; } - } + public OperationStatus Status { get; set; } - public void BeginTask(string name, int totalWork, bool allowCancel) - { - taskName = name; - workDone = 0; + public CancellationToken CancellationToken { + get { return CancellationToken.None; } } - public void Done() - { - } + public double Progress { get; set; } - public bool IsCancelled { - get { return false; } + public IProgressMonitor CreateSubTask(double workAmount) + { + return new DummyProgressMonitor(); } - public bool ShowingDialog { - get { return showingDialog; } - set { showingDialog = value; } + public void Dispose() + { } - - public event EventHandler Cancelled { add { } remove { } } } } diff --git a/src/Main/Base/Project/Src/Gui/ProgressCollector.cs b/src/Main/Base/Project/Src/Gui/ProgressCollector.cs new file mode 100644 index 0000000000..deb13ee3bc --- /dev/null +++ b/src/Main/Base/Project/Src/Gui/ProgressCollector.cs @@ -0,0 +1,335 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace ICSharpCode.SharpDevelop.Gui +{ + /// + /// Collects progress using nested IProgressMonitors and provides it to a different thread using events. + /// + public sealed class ProgressCollector : INotifyPropertyChanged + { + readonly ISynchronizeInvoke eventThread; + readonly CancellationToken cancellationToken; + readonly MonitorImpl root; + readonly LinkedList namedMonitors = new LinkedList(); + readonly object updateLock = new object(); + + string taskName; + double progress; + OperationStatus status; + bool showingDialog; + bool rootMonitorIsDisposed; + + public ProgressCollector(ISynchronizeInvoke eventThread, CancellationToken cancellationToken) + { + if (eventThread == null) + throw new ArgumentNullException("eventThread"); + this.eventThread = eventThread; + this.cancellationToken = cancellationToken; + this.root = new MonitorImpl(this, null, 1); + } + + public event EventHandler ProgressMonitorDisposed; + public event PropertyChangedEventHandler PropertyChanged; + + void OnPropertyChanged(string propertyName) + { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public double Progress { + get { return progress; } + private set { + Debug.Assert(!eventThread.InvokeRequired); + if (progress != value) { + progress = value; + // Defensive programming: parallel processes like the build could change properites even + // after the monitor is disposed (they shouldn't do that, but it could happen), + // and we don't want to confuse consumers like the status bar by + // raising events from disposed monitors. + if (!rootMonitorIsDisposed) + OnPropertyChanged("Progress"); + } + } + } + + public bool ShowingDialog { + get { return showingDialog; } + set { + Debug.Assert(!eventThread.InvokeRequired); + if (showingDialog != value) { + showingDialog = value; + if (!rootMonitorIsDisposed) + OnPropertyChanged("ShowingDialog"); + } + } + } + + public string TaskName { + get { return taskName; } + private set { + Debug.Assert(!eventThread.InvokeRequired); + if (taskName != value) { + taskName = value; + if (!rootMonitorIsDisposed) + OnPropertyChanged("TaskName"); + } + } + } + + public OperationStatus Status { + get { return status; } + private set { + Debug.Assert(!eventThread.InvokeRequired); + if (status != value) { + status = value; + if (!rootMonitorIsDisposed) + OnPropertyChanged("Status"); + } + } + } + + public IProgressMonitor ProgressMonitor { + get { return root; } + } + + /// + /// Gets whether the root progress monitor was disposed. + /// + public bool ProgressMonitorIsDisposed { + get { return rootMonitorIsDisposed; } + } + + bool hasUpdateScheduled; + double storedNewProgress = -1; + OperationStatus storedNewStatus; + + void SetProgress(double newProgress) + { + // this method is always called within a lock(updateLock) block, so we don't + // have to worry about thread safety when accessing hasUpdateScheduled and storedNewProgress + + storedNewProgress = newProgress; + ScheduleUpdate(); + } + + void ScheduleUpdate() + { + // This test ensures that only 1 update is scheduled at a single point in time. If updates + // come in faster than the GUI can process them, we'll skip some and directly update to the newest value. + if (!hasUpdateScheduled) { + hasUpdateScheduled = true; + eventThread.BeginInvoke( + (Action)delegate { + lock (updateLock) { + this.Progress = storedNewProgress; + this.Status = storedNewStatus; + hasUpdateScheduled = false; + } + }, + null + ); + } + } + + void SetStatus(OperationStatus newStatus) + { + // this method is always called within a lock(updateLock) block, so we don't + // have to worry about thread safety when accessing hasUpdateScheduled and storedNewStatus + + storedNewStatus = newStatus; + ScheduleUpdate(); + } + + void SetShowingDialog(bool newValue) + { + eventThread.BeginInvoke( + (Action)delegate { this.ShowingDialog = newValue; }, + null + ); + } + + void OnRootMonitorDisposed() + { + eventThread.BeginInvoke( + (Action)delegate { + if (rootMonitorIsDisposed) // ignore double dispose + return; + rootMonitorIsDisposed = true; + if (ProgressMonitorDisposed != null) { + ProgressMonitorDisposed(this, EventArgs.Empty); + } + }, + null); + } + + void SetTaskName(string newName) + { + eventThread.BeginInvoke( + (Action)delegate { this.TaskName = newName; }, + null); + } + + LinkedListNode RegisterNamedMonitor(string name) + { + lock (namedMonitors) { + LinkedListNode newEntry = namedMonitors.AddLast(name); + if (namedMonitors.First == newEntry) { + SetTaskName(name); + } + return newEntry; + } + } + + void UnregisterNamedMonitor(LinkedListNode nameEntry) + { + lock (namedMonitors) { + bool wasFirst = namedMonitors.First == nameEntry; + namedMonitors.Remove(nameEntry); + if (wasFirst) + SetTaskName(namedMonitors.First != null ? namedMonitors.First.Value : null); + } + } + + void ChangeName(LinkedListNode nameEntry, string newName) + { + lock (namedMonitors) { + if (namedMonitors.First == nameEntry) + SetTaskName(newName); + nameEntry.Value = newName; + } + } + + sealed class MonitorImpl : IProgressMonitor + { + readonly ProgressCollector collector; + readonly MonitorImpl parent; + readonly double scaleFactor; + LinkedListNode nameEntry; + double currentProgress; + OperationStatus localStatus, currentStatus; + int childrenWithWarnings, childrenWithErrors; + + public MonitorImpl(ProgressCollector collector, MonitorImpl parent, double scaleFactor) + { + this.collector = collector; + this.parent = parent; + this.scaleFactor = scaleFactor; + } + + public bool ShowingDialog { + get { return collector.ShowingDialog; } + set { collector.SetShowingDialog(value); } + } + + public string TaskName { + get { + if (nameEntry != null) + return nameEntry.Value; + else + return null; + } + set { + if (nameEntry != null) { + if (value == null) { + collector.UnregisterNamedMonitor(nameEntry); + nameEntry = null; + } else { + if (nameEntry.Value != value) + collector.ChangeName(nameEntry, value); + } + } else { + if (value != null) + nameEntry = collector.RegisterNamedMonitor(value); + } + } + } + + public CancellationToken CancellationToken { + get { return collector.cancellationToken; } + } + + public double Progress { + get { return currentProgress; } + set { + lock (collector.updateLock) { + UpdateProgress(value); + } + } + } + + void UpdateProgress(double progress) + { + if (parent != null) + parent.UpdateProgress(parent.currentProgress + (progress - this.currentProgress) * scaleFactor); + else + collector.SetProgress(progress); + this.currentProgress = progress; + } + + public OperationStatus Status { + get { return localStatus; } + set { + if (localStatus != value) { + localStatus = value; + lock (collector.updateLock) { + UpdateStatus(); + } + } + } + } + + void UpdateStatus() + { + OperationStatus oldStatus = currentStatus; + if (childrenWithErrors > 0) + currentStatus = OperationStatus.Error; + else if (childrenWithWarnings > 0 && localStatus != OperationStatus.Error) + currentStatus = OperationStatus.Warning; + else + currentStatus = localStatus; + if (oldStatus != currentStatus) { + if (parent != null) { + if (oldStatus == OperationStatus.Warning) + parent.childrenWithWarnings--; + else if (oldStatus == OperationStatus.Error) + parent.childrenWithErrors--; + + if (currentStatus == OperationStatus.Warning) + parent.childrenWithWarnings++; + else if (currentStatus == OperationStatus.Error) + parent.childrenWithErrors++; + + parent.UpdateStatus(); + } else { + collector.SetStatus(currentStatus); + } + } + } + + public IProgressMonitor CreateSubTask(double workAmount) + { + return new MonitorImpl(collector, this, workAmount); + } + + public void Dispose() + { + this.TaskName = null; + if (parent == null) + collector.OnRootMonitorDisposed(); + } + } + } +} diff --git a/src/Main/Base/Project/Src/Project/BuildEngine.cs b/src/Main/Base/Project/Src/Project/BuildEngine.cs index b8c5e0a044..a572e7b6bb 100644 --- a/src/Main/Base/Project/Src/Project/BuildEngine.cs +++ b/src/Main/Base/Project/Src/Project/BuildEngine.cs @@ -8,10 +8,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Threading; using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Gui; -using System.Text; namespace ICSharpCode.SharpDevelop.Project { @@ -25,7 +26,7 @@ namespace ICSharpCode.SharpDevelop.Project public sealed class BuildEngine { #region Building in the SharpDevelop GUI - static CancellableProgressMonitor guiBuildProgressMonitor; + static CancellationTokenSource guiBuildCancellation; static IAnalyticsMonitorTrackedFeature guiBuildTrackedFeature; /// @@ -41,7 +42,7 @@ namespace ICSharpCode.SharpDevelop.Project if (options == null) throw new ArgumentNullException("options"); WorkbenchSingleton.AssertMainThread(); - if (guiBuildProgressMonitor != null) { + if (guiBuildCancellation != null) { BuildResults results = new BuildResults(); StatusBarService.ShowErrorMessage(Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning")); results.Add(new BuildError(null, Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning"))); @@ -50,13 +51,13 @@ namespace ICSharpCode.SharpDevelop.Project options.Callback(results); } } else { - guiBuildProgressMonitor = new CancellableProgressMonitor(StatusBarService.CreateProgressMonitor()); + guiBuildCancellation = new CancellationTokenSource(); + IProgressMonitor progressMonitor = StatusBarService.CreateProgressMonitor(guiBuildCancellation.Token); guiBuildTrackedFeature = AnalyticsMonitorService.TrackFeature("Build"); StatusBarService.SetMessage(StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb}...")); ProjectService.RaiseEventBuildStarted(new BuildEventArgs(project, options)); StartBuild(project, options, - new MessageViewSink(TaskService.BuildMessageViewCategory), - guiBuildProgressMonitor); + new MessageViewSink(TaskService.BuildMessageViewCategory, progressMonitor)); } } @@ -66,7 +67,7 @@ namespace ICSharpCode.SharpDevelop.Project public static bool IsGuiBuildRunning { get { WorkbenchSingleton.AssertMainThread(); - return guiBuildProgressMonitor != null; + return guiBuildCancellation != null; } } @@ -77,8 +78,8 @@ namespace ICSharpCode.SharpDevelop.Project public static void CancelGuiBuild() { WorkbenchSingleton.AssertMainThread(); - if (guiBuildProgressMonitor != null) { - guiBuildProgressMonitor.Cancel(); + if (guiBuildCancellation != null) { + guiBuildCancellation.Cancel(); } } @@ -88,10 +89,16 @@ namespace ICSharpCode.SharpDevelop.Project sealed class MessageViewSink : IBuildFeedbackSink { Gui.MessageViewCategory messageView; + Gui.IProgressMonitor progressMonitor; - public MessageViewSink(ICSharpCode.SharpDevelop.Gui.MessageViewCategory messageView) + public MessageViewSink(MessageViewCategory messageView, Gui.IProgressMonitor progressMonitor) { this.messageView = messageView; + this.progressMonitor = progressMonitor; + } + + public IProgressMonitor ProgressMonitor { + get { return progressMonitor; } } public void ReportError(BuildError error) @@ -116,7 +123,7 @@ namespace ICSharpCode.SharpDevelop.Project { WorkbenchSingleton.SafeThreadAsyncCall( delegate { - guiBuildProgressMonitor = null; + guiBuildCancellation = null; if (guiBuildTrackedFeature != null) { guiBuildTrackedFeature.EndTracking(); guiBuildTrackedFeature = null; @@ -140,75 +147,6 @@ namespace ICSharpCode.SharpDevelop.Project }); } } - - sealed class CancellableProgressMonitor : IProgressMonitor - { - IProgressMonitor baseProgressMonitor; - - public CancellableProgressMonitor(IProgressMonitor baseProgressMonitor) - { - this.baseProgressMonitor = baseProgressMonitor; - } - - readonly object lockObject = new object(); - bool isCancelAllowed; - bool isCancelled; - - public bool IsCancelAllowed { - get { return isCancelAllowed; } - } - - public bool IsCancelled { - get { return isCancelled; } - } - - public void Cancel() - { - EventHandler eh = null; - lock (lockObject) { - if (isCancelAllowed && !isCancelled) { - ICSharpCode.Core.LoggingService.Info("Cancel build"); - isCancelled = true; - eh = Cancelled; - } - } - if (eh != null) - eh(this, EventArgs.Empty); - } - - public event EventHandler Cancelled; - - public void BeginTask(string name, int totalWork, bool allowCancel) - { - baseProgressMonitor.BeginTask(name, totalWork, allowCancel); - lock (lockObject) { - isCancelAllowed = allowCancel; - } - } - - public void Done() - { - lock (lockObject) { - isCancelAllowed = false; - } - baseProgressMonitor.Done(); - } - - public int WorkDone { - get { return baseProgressMonitor.WorkDone; } - set { baseProgressMonitor.WorkDone = value; } - } - - public string TaskName { - get { return baseProgressMonitor.TaskName; } - set { baseProgressMonitor.TaskName = value; } - } - - public bool ShowingDialog { - get { return baseProgressMonitor.ShowingDialog; } - set { baseProgressMonitor.ShowingDialog = value; } - } - } #endregion #region StartBuild @@ -220,8 +158,9 @@ namespace ICSharpCode.SharpDevelop.Project /// The build feedback sink that receives the build output. /// The output is nearly sent "as it comes in": sometimes output must wait because the BuildEngine /// will ensure that output from two projects building in parallel isn't interleaved. - /// The progress monitor that receives build progress. - public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink, IProgressMonitor progressMonitor) + /// The progress monitor that receives build progress. The monitor will be disposed + /// when the build completes. + public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink) { if (project == null) throw new ArgumentNullException("solution"); @@ -240,7 +179,7 @@ namespace ICSharpCode.SharpDevelop.Project BuildEngine engine = new BuildEngine(options, project); engine.buildStart = DateTime.Now; engine.combinedBuildFeedbackSink = realtimeBuildFeedbackSink; - engine.progressMonitor = progressMonitor; + engine.progressMonitor = realtimeBuildFeedbackSink.ProgressMonitor; try { engine.rootNode = engine.CreateBuildGraph(project); } catch (CyclicDependencyException ex) { @@ -257,10 +196,7 @@ namespace ICSharpCode.SharpDevelop.Project if (engine.workersToStart < 1) engine.workersToStart = 1; - if (progressMonitor != null) { - progressMonitor.Cancelled += engine.BuildCancelled; - progressMonitor.BeginTask("", engine.nodeDict.Count, true); - } + engine.cancellationRegistration = engine.progressMonitor.CancellationToken.Register(engine.BuildCancelled); engine.ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildStarted}"); engine.StartBuildProjects(); @@ -292,6 +228,9 @@ namespace ICSharpCode.SharpDevelop.Project /// The list of nodes that directly depend on this node internal List dependentOnThis = new List(); + /// Progress monitor just for this node, used only while the node is being built + internal IProgressMonitor perNodeProgressMonitor; + int totalDependentOnThisCount = -1; /// Gets the number of nodes that depend on this node @@ -336,6 +275,12 @@ namespace ICSharpCode.SharpDevelop.Project public void ReportError(BuildError error) { + if (error.IsWarning) { + if (perNodeProgressMonitor.Status != OperationStatus.Error) + perNodeProgressMonitor.Status = OperationStatus.Warning; + } else { + perNodeProgressMonitor.Status = OperationStatus.Error; + } engine.ReportError(this, error); } @@ -348,6 +293,15 @@ namespace ICSharpCode.SharpDevelop.Project { engine.OnBuildFinished(this, success); } + + IProgressMonitor IBuildFeedbackSink.ProgressMonitor { + get { + // property should be accessed only while build is running and progress monitor available + if (perNodeProgressMonitor == null) + throw new InvalidOperationException(); + return perNodeProgressMonitor; + } + } } #endregion @@ -355,6 +309,7 @@ namespace ICSharpCode.SharpDevelop.Project readonly Dictionary nodeDict = new Dictionary(); readonly BuildOptions options; IProgressMonitor progressMonitor; + CancellationTokenRegistration cancellationRegistration; BuildNode rootNode; readonly IBuildable rootProject; readonly BuildResults results = new BuildResults(); @@ -461,6 +416,7 @@ namespace ICSharpCode.SharpDevelop.Project } BuildNode node = projectsReadyForBuildStart[projectToStartIndex]; projectsReadyForBuildStart.RemoveAt(projectToStartIndex); + node.perNodeProgressMonitor = progressMonitor.CreateSubTask(1.0 / nodeDict.Count); node.buildStarted = true; bool hasDependencyErrors = false; @@ -513,7 +469,9 @@ namespace ICSharpCode.SharpDevelop.Project void OnBuildFinished(BuildNode node, bool success) { - ICSharpCode.Core.LoggingService.Info("Finished building " + node.project.Name +", success=" + success); + ICSharpCode.Core.LoggingService.Info("Finished building " + node.project.Name + ", success=" + success); + node.perNodeProgressMonitor.Progress = 1; + node.perNodeProgressMonitor.Dispose(); lock (this) { if (node.buildFinished) { throw new InvalidOperationException("This node already finished building, do not call IBuildFeedbackSink.Done() multiple times!"); @@ -524,9 +482,6 @@ namespace ICSharpCode.SharpDevelop.Project projectsCurrentlyBuilding.Remove(node); results.AddBuiltProject(node.project); - if (progressMonitor != null) { - progressMonitor.WorkDone += 1; - } foreach (BuildNode n in node.dependentOnThis) { n.outstandingDependencies--; @@ -571,9 +526,8 @@ namespace ICSharpCode.SharpDevelop.Project ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildFinished}" + buildTime); } - if (progressMonitor != null) { - progressMonitor.Done(); - } + cancellationRegistration.Dispose(); + progressMonitor.Dispose(); ReportDone(); } @@ -603,7 +557,7 @@ namespace ICSharpCode.SharpDevelop.Project #region Cancel build bool buildIsCancelled; - void BuildCancelled(object sender, EventArgs e) + void BuildCancelled() { lock (this) { if (buildIsDone) @@ -692,11 +646,9 @@ namespace ICSharpCode.SharpDevelop.Project void UpdateProgressTaskName() { lock (this) { - if (progressMonitor != null) { - progressMonitor.TaskName = "${res:MainWindow.CompilerMessages.BuildVerb} " - + string.Join(", ", projectsCurrentlyBuilding.Select(n => n.project.Name)) - + "..."; - } + progressMonitor.TaskName = StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb} ") + + string.Join(", ", projectsCurrentlyBuilding.Select(n => n.project.Name)) + + "..."; } } #endregion diff --git a/src/Main/Base/Project/Src/Project/Converter/LanguageConverter.cs b/src/Main/Base/Project/Src/Project/Converter/LanguageConverter.cs index 3e95698184..4c102d23b7 100644 --- a/src/Main/Base/Project/Src/Project/Converter/LanguageConverter.cs +++ b/src/Main/Base/Project/Src/Project/Converter/LanguageConverter.cs @@ -55,7 +55,7 @@ namespace ICSharpCode.SharpDevelop.Project.Converter } } - protected virtual int GetRequiredWork(ProjectItem item) + protected virtual double GetRequiredWork(ProjectItem item) { if (item.ItemType == ItemType.Compile) { return 50; @@ -121,13 +121,11 @@ namespace ICSharpCode.SharpDevelop.Project.Converter throw new ArgumentNullException("targetProjectItems"); ICollection sourceItems = sourceProject.Items; - int totalWork = 0; + double totalWork = 0; foreach (ProjectItem item in sourceItems) { totalWork += GetRequiredWork(item); } - monitor.BeginTask("Converting", totalWork, true); - int workDone = 0; foreach (ProjectItem item in sourceItems) { FileProjectItem fileItem = item as FileProjectItem; if (fileItem != null && FileUtility.IsBaseDirectory(sourceProject.Directory, fileItem.FileName)) { @@ -148,13 +146,9 @@ namespace ICSharpCode.SharpDevelop.Project.Converter } else { targetProjectItems.AddProjectItem(item.CloneFor(targetProject)); } - if (monitor.IsCancelled) { - return; - } - workDone += GetRequiredWork(item); - monitor.WorkDone = workDone; + monitor.CancellationToken.ThrowIfCancellationRequested(); + monitor.Progress += GetRequiredWork(item) / totalWork; } - monitor.Done(); } protected StringBuilder conversionLog; @@ -178,16 +172,23 @@ namespace ICSharpCode.SharpDevelop.Project.Converter conversionLog.Append(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.TargetDirectory")).Append(": "); conversionLog.AppendLine(targetProjectDirectory); + try { + PerformConversion(translatedTitle, sourceProject, targetProjectDirectory); + } catch (OperationCanceledException) { + // ignore + } + } + + void PerformConversion(string translatedTitle, MSBuildBasedProject sourceProject, string targetProjectDirectory) + { IProject targetProject; - using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog(translatedTitle)) { + using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog(translatedTitle, "Converting", true)) { Directory.CreateDirectory(targetProjectDirectory); targetProject = CreateProject(targetProjectDirectory, sourceProject); CopyProperties(sourceProject, targetProject); conversionLog.AppendLine(); CopyItems(sourceProject, targetProject, monitor); - if (monitor.IsCancelled) { - return; - } + monitor.CancellationToken.ThrowIfCancellationRequested(); conversionLog.AppendLine(); AfterConversion(targetProject); conversionLog.AppendLine(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.ConversionComplete")); diff --git a/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs b/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs index 3b32909983..0608a5db0a 100644 --- a/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs +++ b/src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs @@ -15,6 +15,13 @@ namespace ICSharpCode.SharpDevelop.Project /// public interface IBuildFeedbackSink { + /// + /// Gets the progress monitor associated with this build. + /// Does not return null. + /// This member is thread-safe. + /// + Gui.IProgressMonitor ProgressMonitor { get; } + /// /// Reports an build error by adding it to the error list. /// This member is thread-safe. diff --git a/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs b/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs index 7e50a39c45..57f20a1769 100644 --- a/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs +++ b/src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs @@ -11,7 +11,7 @@ using System; namespace ICSharpCode.SharpDevelop.Project { /// - /// Description of ProjectLoadInformation. + /// Parameter object for loading an existing project. /// public class ProjectLoadInformation { @@ -21,7 +21,21 @@ namespace ICSharpCode.SharpDevelop.Project public string ProjectName { get; private set; } public string TypeGuid { get; set; } internal string Guid { get; set; } - public Gui.IProgressMonitor ProgressMonitor { get; set; } + + Gui.IProgressMonitor progressMonitor = new Gui.DummyProgressMonitor(); + + /// + /// Gets/Sets the progress monitor used during the load. + /// This property never returns null. + /// + public Gui.IProgressMonitor ProgressMonitor { + get { return progressMonitor; } + set { + if (value == null) + throw new ArgumentNullException(); + progressMonitor = value; + } + } public ProjectLoadInformation(Solution parentSolution, string fileName, string projectName) { diff --git a/src/Main/Base/Project/Src/Project/Solution/Solution.cs b/src/Main/Base/Project/Src/Project/Solution/Solution.cs index fcc8c2869a..7ee2317875 100644 --- a/src/Main/Base/Project/Src/Project/Solution/Solution.cs +++ b/src/Main/Base/Project/Src/Project/Solution/Solution.cs @@ -511,6 +511,9 @@ namespace ICSharpCode.SharpDevelop.Project static ProjectSection SetupSolutionLoadSolutionProjects(Solution newSolution, StreamReader sr, IProgressMonitor progressMonitor) { + if (progressMonitor == null) + throw new ArgumentNullException("progressMonitor"); + string solutionDirectory = Path.GetDirectoryName(newSolution.FileName); ProjectSection nestedProjectsSection = null; @@ -542,15 +545,10 @@ namespace ICSharpCode.SharpDevelop.Project ProjectLoadInformation loadInfo = new ProjectLoadInformation(newSolution, location, title); loadInfo.TypeGuid = projectGuid; loadInfo.Guid = guid; -// loadInfo.ProgressMonitor = progressMonitor; -// IProject newProject = ProjectBindingService.LoadProject(loadInfo); -// newProject.IdGuid = guid; projectsToLoad.Add(loadInfo); IList currentProjectSections = new List(); ReadProjectSections(sr, currentProjectSections); readProjectSections.Add(currentProjectSections); -// newSolution.AddFolder(newProject); - } match = match.NextMatch(); } else { @@ -578,10 +576,16 @@ namespace ICSharpCode.SharpDevelop.Project loadInfo.Platform = AbstractProject.GetPlatformNameFromKey(projectConfig.Location); loadInfo.ProgressMonitor = progressMonitor; - IProject newProject = ProjectBindingService.LoadProject(loadInfo); - newProject.IdGuid = loadInfo.Guid; - newProject.ProjectSections.AddRange(projectSections); - newSolution.AddFolder(newProject); + progressMonitor.Progress = (double)i / projectsToLoad.Count; + progressMonitor.TaskName = "Loading " + loadInfo.ProjectName; + + using (IProgressMonitor nestedProgressMonitor = progressMonitor.CreateSubTask(1.0 / projectsToLoad.Count)) { + loadInfo.ProgressMonitor = nestedProgressMonitor; + IProject newProject = ProjectBindingService.LoadProject(loadInfo); + newProject.IdGuid = loadInfo.Guid; + newProject.ProjectSections.AddRange(projectSections); + newSolution.AddFolder(newProject); + } } return nestedProjectsSection; } diff --git a/src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs b/src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs index 7a8108a0d8..f56665d4ba 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs @@ -77,8 +77,8 @@ namespace ICSharpCode.SharpDevelop if (!reParse1.Contains(pc)) { LoggingService.Debug("Enqueue for reinitializing references: " + project); reParse1.Add(pc); - jobs.AddJob(new JobTask(ct => ReInitializeReferences(pc, ct), - "Loading references for " + project.Name + "...", + jobs.AddJob(new JobTask(pm => ReInitializeReferences(pc, pm), + GetLoadReferenceTaskTitle(project.Name), 10 )); } @@ -89,8 +89,8 @@ namespace ICSharpCode.SharpDevelop if (!reParse2.Contains(pc)) { LoggingService.Debug("Enqueue for reparsing code: " + project); reParse2.Add(pc); - jobs.AddJob(new JobTask(ct => ReparseCode(pc, ct), - "${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + project.Name + "...", + jobs.AddJob(new JobTask(pm => ReparseCode(pc, pm), + GetParseTaskTitle(project.Name), pc.GetInitializationWorkAmount() )); } @@ -100,20 +100,20 @@ namespace ICSharpCode.SharpDevelop } } - static void ReInitializeReferences(ParseProjectContent pc, CancellationToken ct) + static void ReInitializeReferences(ParseProjectContent pc, IProgressMonitor progressMonitor) { lock (reParse1) { reParse1.Remove(pc); } - pc.ReInitialize1(ct); + pc.ReInitialize1(progressMonitor); } - static void ReparseCode(ParseProjectContent pc, CancellationToken ct) + static void ReparseCode(ParseProjectContent pc, IProgressMonitor progressMonitor) { lock (reParse2) { reParse2.Remove(pc); } - pc.ReInitialize2(ct); + pc.ReInitialize2(progressMonitor); } #endregion @@ -132,26 +132,36 @@ namespace ICSharpCode.SharpDevelop for (int i = 0; i < createdContents.Count; i++) { ParseProjectContent pc = createdContents[i]; jobs.AddJob(new JobTask(pc.Initialize1, - "Loading references for " + pc.ProjectName + "...", + GetLoadReferenceTaskTitle(pc.ProjectName), 10)); } for (int i = 0; i < createdContents.Count; i++) { ParseProjectContent pc = createdContents[i]; jobs.AddJob(new JobTask(pc.Initialize2, - "${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + pc.ProjectName + "...", + GetParseTaskTitle(pc.ProjectName), pc.GetInitializationWorkAmount())); } jobs.AddJob(new JobTask(ct => RaiseThreadEnded(openedSolution), "", 0)); jobs.StartRunningIfRequired(); } + static string GetLoadReferenceTaskTitle(string projectName) + { + return "Loading references for " + projectName + "..."; + } + + static string GetParseTaskTitle(string projectName) + { + return StringParser.Parse("${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} ") + projectName + "..."; + } + internal static void InitNewProject(ParseProjectContent pc) { jobs.AddJob(new JobTask(pc.Initialize1, - "Loading references for " + pc.ProjectName + "...", + GetLoadReferenceTaskTitle(pc.ProjectName), 10)); jobs.AddJob(new JobTask(pc.Initialize2, - "${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + pc.ProjectName + "...", + GetParseTaskTitle(pc.ProjectName), pc.GetInitializationWorkAmount())); jobs.StartRunningIfRequired(); } @@ -171,11 +181,11 @@ namespace ICSharpCode.SharpDevelop { readonly object lockObj = new object(); readonly Queue actions = new Queue(); - readonly IProgressMonitor progressMonitor = StatusBarService.CreateProgressMonitor(); CancellationTokenSource cancellationSource = new CancellationTokenSource(); + IProgressMonitor progressMonitor; bool threadIsRunning; - int totalWork; - int workDone; + double totalWork; + double workDone; public void AddJob(JobTask task) { @@ -193,8 +203,8 @@ namespace ICSharpCode.SharpDevelop if (!this.threadIsRunning && this.actions.Count > 0) { this.threadIsRunning = true; - progressMonitor.BeginTask(this.actions.Peek().name, totalWork, false); - progressMonitor.WorkDone = 0; + progressMonitor = StatusBarService.CreateProgressMonitor(cancellationSource.Token); + progressMonitor.TaskName = this.actions.Peek().name; Thread thread = new Thread(new ThreadStart(RunThread)); thread.Name = "LoadSolutionProjects"; @@ -208,26 +218,29 @@ namespace ICSharpCode.SharpDevelop void RunThread() { while (true) { - CancellationToken token; JobTask task; - int totalWork, workDone; + // copy fields from this class into local variables to ensure thread-safety + // with concurrent Clear() calls + double totalWork, workDone; + IProgressMonitor progressMonitor; lock (lockObj) { if (actions.Count == 0) { this.threadIsRunning = false; - progressMonitor.Done(); + this.progressMonitor.Dispose(); + this.progressMonitor = null; return; } task = this.actions.Dequeue(); - token = this.cancellationSource.Token; totalWork = this.totalWork; workDone = this.workDone; + progressMonitor = this.progressMonitor; } - if (task.cost > 0) { - progressMonitor.BeginTask(task.name, totalWork, false); - progressMonitor.WorkDone = workDone; - } + progressMonitor.Progress = workDone / totalWork; + progressMonitor.TaskName = task.name; try { - task.Run(token); + using (IProgressMonitor subTask = progressMonitor.CreateSubTask(task.cost / totalWork)) { + task.Run(subTask); + } lock (lockObj) { this.workDone += task.cost; } @@ -245,6 +258,10 @@ namespace ICSharpCode.SharpDevelop cancellationSource.Cancel(); actions.Clear(); cancellationSource = new CancellationTokenSource(); + if (progressMonitor != null) { + progressMonitor.Dispose(); + progressMonitor = null; + } this.totalWork = 0; this.workDone = 0; } @@ -253,11 +270,11 @@ namespace ICSharpCode.SharpDevelop sealed class JobTask { - readonly Action action; + readonly Action action; internal readonly string name; - internal readonly int cost; + internal readonly double cost; - public JobTask(Action action, string name, int cost) + public JobTask(Action action, string name, double cost) { if (action == null) throw new ArgumentNullException("action"); @@ -266,9 +283,9 @@ namespace ICSharpCode.SharpDevelop this.cost = cost; } - public void Run(CancellationToken token) + public void Run(IProgressMonitor progressMonitor) { - action(token); + action(progressMonitor); } } } diff --git a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs index 1654bedd85..4c36b88ce9 100644 --- a/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs +++ b/src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs @@ -54,7 +54,7 @@ namespace ICSharpCode.SharpDevelop return string.Format("[{0}: {1}]", GetType().Name, project.Name); } - internal void Initialize1(CancellationToken token) + internal void Initialize1(IProgressMonitor progressMonitor) { ICollection items = project.Items; ProjectService.ProjectItemAdded += OnProjectItemAdded; @@ -65,7 +65,7 @@ namespace ICSharpCode.SharpDevelop project.ResolveAssemblyReferences(); foreach (ProjectItem item in items) { if (!initializing) return; // abort initialization - token.ThrowIfCancellationRequested(); + progressMonitor.CancellationToken.ThrowIfCancellationRequested(); if (ItemType.ReferenceItemTypes.Contains(item.ItemType)) { ReferenceProjectItem reference = item as ReferenceProjectItem; if (reference != null) { @@ -79,7 +79,7 @@ namespace ICSharpCode.SharpDevelop OnReferencedContentsChanged(EventArgs.Empty); } - internal void ReInitialize1(CancellationToken token) + internal void ReInitialize1(IProgressMonitor progressMonitor) { lock (ReferencedContents) { ReferencedContents.Clear(); @@ -90,7 +90,7 @@ namespace ICSharpCode.SharpDevelop ProjectService.ProjectItemRemoved -= OnProjectItemRemoved; initializing = true; try { - Initialize1(token); + Initialize1(progressMonitor); } finally { initializing = false; } @@ -236,14 +236,14 @@ namespace ICSharpCode.SharpDevelop return project.Items.Count; } - internal void ReInitialize2(CancellationToken token) + internal void ReInitialize2(IProgressMonitor progressMonitor) { if (initializing) return; initializing = true; - Initialize2(token); + Initialize2(progressMonitor); } - internal void Initialize2(CancellationToken token) + internal void Initialize2(IProgressMonitor progressMonitor) { if (!initializing) return; //int progressStart = progressMonitor.WorkDone; @@ -262,7 +262,7 @@ namespace ICSharpCode.SharpDevelop ParseableFileContentFinder finder = new ParseableFileContentFinder(); var fileContents = - from p in project.Items.AsParallel().WithCancellation(token) + from p in project.Items.AsParallel().WithCancellation(progressMonitor.CancellationToken) where !ItemType.NonFileItemTypes.Contains(p.ItemType) && !String.IsNullOrEmpty(p.FileName) select finder.Create(p); fileContents.ForAll( diff --git a/src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs b/src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs index 5cfcfc1189..9e2376e6bd 100644 --- a/src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs +++ b/src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs @@ -97,9 +97,7 @@ namespace ICSharpCode.SharpDevelop string title = loadInformation.ProjectName; IProgressMonitor progressMonitor = loadInformation.ProgressMonitor; - if (progressMonitor != null) { - progressMonitor.BeginTask("Loading " + title, 0, false); - } + progressMonitor.CancellationToken.ThrowIfCancellationRequested(); IProject newProject; if (!File.Exists(location)) { @@ -112,16 +110,16 @@ namespace ICSharpCode.SharpDevelop newProject = binding.LoadProject(loadInformation); } catch (ProjectLoadException ex) { LoggingService.Warn("Project load error", ex); - if (progressMonitor != null) progressMonitor.ShowingDialog = true; + progressMonitor.ShowingDialog = true; newProject = new UnknownProject(location, title, ex.Message, true); newProject.TypeGuid = loadInformation.TypeGuid; - if (progressMonitor != null) progressMonitor.ShowingDialog = false; + progressMonitor.ShowingDialog = false; } catch (UnauthorizedAccessException ex) { LoggingService.Warn("Project load error", ex); - if (progressMonitor != null) progressMonitor.ShowingDialog = true; + progressMonitor.ShowingDialog = true; newProject = new UnknownProject(location, title, ex.Message, true); newProject.TypeGuid = loadInformation.TypeGuid; - if (progressMonitor != null) progressMonitor.ShowingDialog = false; + progressMonitor.ShowingDialog = false; } } else { string ext = Path.GetExtension(location); diff --git a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs index f765174023..82bdd44847 100644 --- a/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs +++ b/src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs @@ -297,6 +297,10 @@ namespace ICSharpCode.SharpDevelop.Project this.currentPass = currentPass; } + public Gui.IProgressMonitor ProgressMonitor { + get { return sink.ProgressMonitor; } + } + public void ReportError(BuildError error) { sink.ReportError(error); diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs index 7b7e26ed12..938d418644 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs @@ -154,32 +154,25 @@ namespace ICSharpCode.SharpDevelop.Refactoring } ParseableFileContentFinder finder = new ParseableFileContentFinder(); List references = new List(); - try { + + if (progressMonitor != null) + progressMonitor.TaskName = StringParser.Parse("${res:SharpDevelop.Refactoring.FindingReferences}"); + + foreach (ProjectItem item in files) { + var entry = finder.Create(item); + if (progressMonitor != null) { - progressMonitor.BeginTask("${res:SharpDevelop.Refactoring.FindingReferences}", files.Count, true); - } - int index = 0; - foreach (ProjectItem item in files) { - var entry = finder.Create(item); - - if (progressMonitor != null) { - progressMonitor.WorkDone = index; - if (progressMonitor.IsCancelled) { - return null; - } - } - - ITextBuffer content = entry.GetContent(); - if (content != null) { - AddReferences(references, ownerClass, member, isLocal, entry.FileName, content.Text); - } - index++; + progressMonitor.Progress += 1.0 / files.Count; + if (progressMonitor.CancellationToken.IsCancellationRequested) + return null; } - } finally { - if (progressMonitor != null) { - progressMonitor.Done(); + + ITextBuffer content = entry.GetContent(); + if (content != null) { + AddReferences(references, ownerClass, member, isLocal, entry.FileName, content.Text); } } + return references; } diff --git a/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs b/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs index a9c84a7cf6..c5ef669c20 100644 --- a/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs +++ b/src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs @@ -7,10 +7,13 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Threading; using System.Windows; using System.Windows.Media.Imaging; + using ICSharpCode.Core; using ICSharpCode.SharpDevelop.Gui; @@ -97,107 +100,58 @@ namespace ICSharpCode.SharpDevelop Visible = PropertyService.Get("ICSharpCode.SharpDevelop.Gui.StatusBarVisible", true); } - public static void Update() + #region Progress Monitor + static Stack waitingProgresses = new Stack(); + static ProgressCollector currentProgress; + + public static IProgressMonitor CreateProgressMonitor(CancellationToken cancellationToken) { - System.Diagnostics.Debug.Assert(statusBar != null); - /* statusBar.Panels.Clear(); - statusBar.Controls.Clear(); - - foreach (StatusBarContributionItem item in Items) { - if (item.Control != null) { - statusBar.Controls.Add(item.Control); - } else if (item.Panel != null) { - statusBar.Panels.Add(item.Panel); - } else { - throw new ApplicationException("StatusBarContributionItem " + item.ItemID + " has no Control or Panel defined."); - } - }*/ + ProgressCollector progress = new ProgressCollector(WorkbenchSingleton.Workbench.SynchronizingObject, cancellationToken); + AddProgress(progress); + return progress.ProgressMonitor; } - #region Progress Monitor - static HashSet activeProgressMonitors = new HashSet(); - static StatusBarProgressMonitor currentProgressMonitor; - - public static IProgressMonitor CreateProgressMonitor() + public static void AddProgress(ProgressCollector progress) { + if (progress == null) + throw new ArgumentNullException("progress"); System.Diagnostics.Debug.Assert(statusBar != null); - return new StatusBarProgressMonitor(); + WorkbenchSingleton.AssertMainThread(); + if (currentProgress != null) { + currentProgress.ProgressMonitorDisposed -= progress_ProgressMonitorDisposed; + currentProgress.PropertyChanged -= progress_PropertyChanged; + } + waitingProgresses.Push(currentProgress); // push even if currentProgress==null + SetActiveProgress(progress); } - sealed class StatusBarProgressMonitor : IProgressMonitor + static void SetActiveProgress(ProgressCollector progress) { - int workDone, totalWork; - - public int WorkDone { - get { return workDone; } - set { - if (workDone == value) - return; - workDone = value; - lock (activeProgressMonitors) { - if (currentProgressMonitor == this) { - UpdateDisplay(); - } - } - } - } - - void UpdateDisplay() - { - statusBar.DisplayProgress(taskName, workDone, totalWork); - } - - string taskName; - - public string TaskName { - get { return taskName; } - set { - if (taskName == value) - return; - taskName = value; - lock (activeProgressMonitors) { - if (currentProgressMonitor == this) { - UpdateDisplay(); - } - } - } - } - - public bool ShowingDialog { get; set; } - - public bool IsCancelled { - get { return false; } + WorkbenchSingleton.AssertMainThread(); + currentProgress = progress; + if (progress == null) { + statusBar.HideProgress(); + return; } - public void BeginTask(string name, int totalWork, bool allowCancel) - { - lock (activeProgressMonitors) { - activeProgressMonitors.Add(this); - currentProgressMonitor = this; - this.taskName = name; - this.workDone = 0; - this.totalWork = totalWork; - UpdateDisplay(); - } + progress.ProgressMonitorDisposed += progress_ProgressMonitorDisposed; + if (progress.ProgressMonitorIsDisposed) { + progress_ProgressMonitorDisposed(progress, null); + return; } - - public void Done() - { - lock (activeProgressMonitors) { - activeProgressMonitors.Remove(this); - if (currentProgressMonitor == this) { - if (activeProgressMonitors.Count > 0) { - currentProgressMonitor = activeProgressMonitors.First(); - currentProgressMonitor.UpdateDisplay(); - } else { - currentProgressMonitor = null; - statusBar.HideProgress(); - } - } - } - } - - public event EventHandler Cancelled { add { } remove { } } + progress.PropertyChanged += progress_PropertyChanged; + } + + static void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + Debug.Assert(sender == currentProgress); + statusBar.DisplayProgress(currentProgress.TaskName, currentProgress.Progress, currentProgress.Status); + } + + static void progress_ProgressMonitorDisposed(object sender, EventArgs e) + { + Debug.Assert(sender == currentProgress); + SetActiveProgress(waitingProgresses.Pop()); // stack is never empty: we push null as first element } #endregion }