Browse Source

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
pull/1/head
Daniel Grunwald 16 years ago
parent
commit
fb3fa4dac7
  1. 13
      src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs
  2. 28
      src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs
  3. 2
      src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs
  4. 2
      src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs
  5. 1
      src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
  6. 55
      src/Main/Base/Project/Src/Gui/Components/StatusBar/SdStatusBar.cs
  7. 356
      src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs
  8. 120
      src/Main/Base/Project/Src/Gui/IProgressMonitor.cs
  9. 335
      src/Main/Base/Project/Src/Gui/ProgressCollector.cs
  10. 146
      src/Main/Base/Project/Src/Project/BuildEngine.cs
  11. 29
      src/Main/Base/Project/Src/Project/Converter/LanguageConverter.cs
  12. 7
      src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs
  13. 18
      src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs
  14. 14
      src/Main/Base/Project/Src/Project/Solution/Solution.cs
  15. 79
      src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs
  16. 16
      src/Main/Base/Project/Src/Services/ParserService/ParseProjectContent.cs
  17. 12
      src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs
  18. 4
      src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs
  19. 21
      src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs
  20. 116
      src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs

13
src/AddIns/Misc/Profiler/Frontend/AddIn/Src/ProfilerRunner.cs

@ -80,24 +80,27 @@ namespace ICSharpCode.Profiler.AddIn
void FinishSession() 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(); profiler.Dispose();
WorkbenchSingleton.SafeThreadAsyncCall(() => { controlWindow.AllowClose = true; this.controlWindow.Close(); }); WorkbenchSingleton.SafeThreadAsyncCall(() => { controlWindow.AllowClose = true; this.controlWindow.Close(); });
if (database != null) { if (database != null) {
database.WriteTo(writer, progress => !dlg.IsCancelled); database.WriteTo(writer, progress => {
dlg.Progress = progress;
return !dlg.CancellationToken.IsCancellationRequested;
});
writer.Close(); writer.Close();
database.Close(); database.Close();
} else { } else {
writer.Close(); writer.Close();
} }
if (!dlg.IsCancelled) if (!dlg.CancellationToken.IsCancellationRequested)
OnRunFinished(EventArgs.Empty); OnRunFinished(EventArgs.Empty);
} catch (Exception ex) {
Debug.Print(ex.ToString());
} }
} catch (Exception ex) {
MessageService.ShowException(ex);
} }
} }

28
src/AddIns/Misc/ResourceToolkit/Project/Src/Refactoring/ResourceRefactoringService.cs

@ -73,12 +73,14 @@ namespace Hornung.ResourceToolkit.Refactoring
ICollection<string> files = GetPossibleFiles(scope); ICollection<string> files = GetPossibleFiles(scope);
if (monitor != null) { 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) { foreach (string fileName in files) {
if (monitor != null)
if (monitor != null && monitor.IsCancelled) { monitor.Progress = workDone / files.Count;
workDone += 1;
if (monitor != null && monitor.CancellationToken.IsCancellationRequested) {
return null; return null;
} }
@ -91,13 +93,11 @@ namespace Hornung.ResourceToolkit.Refactoring
} catch (FileNotFoundException) { } catch (FileNotFoundException) {
} }
if (doc == null) { if (doc == null) {
if (monitor != null) ++monitor.WorkDone;
continue; continue;
} }
string fileContent = doc.Text; string fileContent = doc.Text;
if (String.IsNullOrEmpty(fileContent)) { if (String.IsNullOrEmpty(fileContent)) {
if (monitor != null) ++monitor.WorkDone;
continue; 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"); LoggingService.Info("ResourceToolkit: FindReferences finished in "+(DateTime.UtcNow - startTime).TotalSeconds.ToString(System.Globalization.CultureInfo.CurrentCulture)+"s");
} finally { } finally {
NRefactoryAstCacheService.DisableCache(); NRefactoryAstCacheService.DisableCache();
if (monitor != null) monitor.Done();
} }
return references; return references;
@ -210,10 +207,6 @@ namespace Hornung.ResourceToolkit.Refactoring
return null; return null;
} }
if (monitor != null) {
monitor.BeginTask(null, 0, false);
}
List<ResourceItem> unused = new List<ResourceItem>(); List<ResourceItem> unused = new List<ResourceItem>();
// Get a list of all referenced resource files. // Get a list of all referenced resource files.
@ -257,8 +250,6 @@ namespace Hornung.ResourceToolkit.Refactoring
} }
} }
if (monitor != null) monitor.Done();
return unused.AsReadOnly(); return unused.AsReadOnly();
} }
@ -302,10 +293,6 @@ namespace Hornung.ResourceToolkit.Refactoring
return; return;
} }
if (monitor != null) {
monitor.BeginTask(null, 0, false);
}
try { try {
// rename definition (if present) // rename definition (if present)
if (rrr.ResourceFileContent.ContainsKey(rrr.Key)) { if (rrr.ResourceFileContent.ContainsKey(rrr.Key)) {
@ -319,7 +306,6 @@ namespace Hornung.ResourceToolkit.Refactoring
if (monitor != null) monitor.ShowingDialog = true; if (monitor != null) monitor.ShowingDialog = true;
MessageService.ShowWarningFormatted("${res:Hornung.ResourceToolkit.ErrorProcessingResourceFile}" + Environment.NewLine + ex.Message, rrr.ResourceFileContent.FileName); MessageService.ShowWarningFormatted("${res:Hornung.ResourceToolkit.ErrorProcessingResourceFile}" + Environment.NewLine + ex.Message, rrr.ResourceFileContent.FileName);
if (monitor != null) monitor.ShowingDialog = false; if (monitor != null) monitor.ShowingDialog = false;
if (monitor != null) monitor.Done();
// Do not rename the references when renaming the definition failed. // Do not rename the references when renaming the definition failed.
return; return;
} }
@ -340,8 +326,6 @@ namespace Hornung.ResourceToolkit.Refactoring
if (monitor != null) monitor.ShowingDialog = false; if (monitor != null) monitor.ShowingDialog = false;
} }
} }
if (monitor != null) monitor.Done();
} }
// ******************************************************************************************************************************** // ********************************************************************************************************************************

2
src/AddIns/Misc/SearchAndReplace/Project/Engine/Search.cs

@ -89,7 +89,7 @@ namespace SearchAndReplace
Debug.Assert(documentIterator != null); Debug.Assert(documentIterator != null);
Debug.Assert(textIteratorBuilder != null); Debug.Assert(textIteratorBuilder != null);
if (monitor != null && monitor.IsCancelled) if (monitor != null && monitor.CancellationToken.IsCancellationRequested)
return null; return null;
if (info != null && textIterator != null && documentIterator.CurrentFileName != null) { if (info != null && textIterator != null && documentIterator.CurrentFileName != null) {

2
src/AddIns/Misc/SearchAndReplace/Project/Engine/SearchReplaceManager.cs

@ -352,7 +352,7 @@ namespace SearchAndReplace
static void ShowNotFoundMessage(IProgressMonitor monitor) static void ShowNotFoundMessage(IProgressMonitor monitor)
{ {
if (monitor != null && monitor.IsCancelled) if (monitor != null && monitor.CancellationToken.IsCancellationRequested)
return; return;
if (monitor != null) monitor.ShowingDialog = true; if (monitor != null) monitor.ShowingDialog = true;
MessageBox.Show(WorkbenchSingleton.MainWin32Window, MessageBox.Show(WorkbenchSingleton.MainWin32Window,

1
src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj

@ -206,6 +206,7 @@
<Compile Include="Src\Gui\Pads\ProjectBrowser\TreeNodes\DirectoryNodeFactory.cs" /> <Compile Include="Src\Gui\Pads\ProjectBrowser\TreeNodes\DirectoryNodeFactory.cs" />
<Compile Include="Src\Gui\Pads\TaskList\TaskListPadCommands.cs" /> <Compile Include="Src\Gui\Pads\TaskList\TaskListPadCommands.cs" />
<Compile Include="Src\Gui\Pads\ToolsPad.cs" /> <Compile Include="Src\Gui\Pads\ToolsPad.cs" />
<Compile Include="Src\Gui\ProgressCollector.cs" />
<Compile Include="Src\Gui\Workbench\FullScreenEnabledWindow.cs" /> <Compile Include="Src\Gui\Workbench\FullScreenEnabledWindow.cs" />
<Compile Include="Src\Gui\Workbench\Layouts\AvalonDockLayout.cs" /> <Compile Include="Src\Gui\Workbench\Layouts\AvalonDockLayout.cs" />
<Compile Include="Src\Gui\Workbench\Layouts\AvalonPadContent.cs" /> <Compile Include="Src\Gui\Workbench\Layouts\AvalonPadContent.cs" />

55
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;
using System.Windows.Controls.Primitives; using System.Windows.Controls.Primitives;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using ICSharpCode.Core; using ICSharpCode.Core;
@ -42,6 +43,10 @@ namespace ICSharpCode.SharpDevelop.Gui
{ {
cursorStatusBarPanel.Width = 150; cursorStatusBarPanel.Width = 150;
modeStatusBarPanel.Width = 25; modeStatusBarPanel.Width = 25;
statusProgressBar.Minimum = 0;
statusProgressBar.Maximum = 1;
statusProgressBarItem.Visibility = Visibility.Hidden; statusProgressBarItem.Visibility = Visibility.Hidden;
statusProgressBarItem.Width = 100; statusProgressBarItem.Width = 100;
statusProgressBarItem.Content = statusProgressBar; statusProgressBarItem.Content = statusProgressBar;
@ -103,52 +108,56 @@ namespace ICSharpCode.SharpDevelop.Gui
bool statusProgressBarIsVisible; bool statusProgressBarIsVisible;
string currentTaskName; 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;
WorkbenchSingleton.SafeThreadAsyncCall(
delegate {
if (!statusProgressBarIsVisible) { if (!statusProgressBarIsVisible) {
statusProgressBarItem.Visibility = Visibility.Visible; statusProgressBarItem.Visibility = Visibility.Visible;
statusProgressBarIsVisible = true; statusProgressBarIsVisible = true;
} }
if (totalWork == 0) { if (double.IsNaN(workDone)) {
statusProgressBar.IsIndeterminate = true; statusProgressBar.IsIndeterminate = true;
status = OperationStatus.Normal; // indeterminate doesn't support foreground color
} else { } else {
statusProgressBar.IsIndeterminate = false; statusProgressBar.IsIndeterminate = false;
if (statusProgressBar.Maximum != totalWork) {
if (statusProgressBar.Value > totalWork)
statusProgressBar.Value = 0;
statusProgressBar.Maximum = totalWork;
}
statusProgressBar.Value = workDone; 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) { if (currentTaskName != taskName) {
currentTaskName = taskName; currentTaskName = taskName;
jobNamePanel.Content = StringParser.Parse(taskName); jobNamePanel.Content = taskName;
} }
});
} }
public void HideProgress() public void HideProgress()
{ {
WorkbenchSingleton.SafeThreadAsyncCall(
delegate {
statusProgressBarIsVisible = false; statusProgressBarIsVisible = false;
statusProgressBarItem.Visibility = Visibility.Collapsed; statusProgressBarItem.Visibility = Visibility.Collapsed;
jobNamePanel.Content = currentTaskName = ""; jobNamePanel.Content = currentTaskName = "";
});
} }
} }
} }

356
src/Main/Base/Project/Src/Gui/Dialogs/AsynchronousWaitDialog.cs

@ -6,20 +6,30 @@
// </file> // </file>
using System; using System;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Threading; using System.Threading;
using System.Windows.Forms; using System.Windows.Forms;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.SharpDevelop.Gui namespace ICSharpCode.SharpDevelop.Gui
{ {
internal sealed partial class AsynchronousWaitDialogForm internal sealed partial class AsynchronousWaitDialogForm
{ {
internal AsynchronousWaitDialogForm() internal AsynchronousWaitDialogForm(bool allowCancel)
{ {
InitializeComponent(); InitializeComponent();
cancelButton.Text = ResourceService.GetString("Global.CancelButtonText"); 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(); /// long_running_action();
/// } /// }
/// or: /// or:
/// using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) { /// using (IProgressMonitor monitor = AsynchronousWaitDialog.ShowWaitDialog("title")) {
/// long_running_action(monitor); /// long_running_action(monitor);
/// } /// }
/// </summary> /// </summary>
public sealed class AsynchronousWaitDialog : IProgressMonitor, IDisposable public sealed class AsynchronousWaitDialog : IProgressMonitor
{ {
/// <summary> /// <summary>
/// Delay until the wait dialog becomes visible, in ms. /// Delay until the wait dialog becomes visible, in ms.
/// </summary> /// </summary>
public const int ShowWaitDialogDelay = 500; public const int ShowWaitDialogDelay = 500;
readonly object lockObject = new object(); readonly string titleName;
bool disposed; readonly string defaultTaskName;
readonly CancellationTokenSource cancellation;
readonly ProgressCollector collector;
readonly SynchronizationHelper synchronizationHelper = new SynchronizationHelper();
AsynchronousWaitDialogForm dlg; AsynchronousWaitDialogForm dlg;
volatile int totalWork;
volatile string titleName;
volatile string taskName;
volatile int workDone;
volatile bool cancelled;
volatile bool allowCancel;
#region Constructors
/// <summary> /// <summary>
/// Shows a wait dialog. /// Shows a wait dialog.
/// </summary> /// </summary>
@ -59,11 +68,7 @@ namespace ICSharpCode.SharpDevelop.Gui
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns> /// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName) public static AsynchronousWaitDialog ShowWaitDialog(string titleName)
{ {
if (titleName == null) return ShowWaitDialog(titleName, null, false);
throw new ArgumentNullException("titleName");
AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, false);
h.Start();
return h;
} }
/// <summary> /// <summary>
@ -74,20 +79,109 @@ namespace ICSharpCode.SharpDevelop.Gui
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties. /// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns> /// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, bool allowCancel) public static AsynchronousWaitDialog ShowWaitDialog(string titleName, bool allowCancel)
{
return ShowWaitDialog(titleName, null, allowCancel);
}
/// <summary>
/// Shows a wait dialog that does not support cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="allowCancel">Specifies whether a cancel button should be shown.</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <returns>AsynchronousWaitDialog object - you can use it to access the wait dialog's properties.
/// To close the wait dialog, call Dispose() on the AsynchronousWaitDialog object</returns>
public static AsynchronousWaitDialog ShowWaitDialog(string titleName, string defaultTaskName, bool allowCancel)
{ {
if (titleName == null) if (titleName == null)
throw new ArgumentNullException("titleName"); throw new ArgumentNullException("titleName");
AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, allowCancel); AsynchronousWaitDialog h = new AsynchronousWaitDialog(titleName, defaultTaskName, allowCancel);
h.Start(); h.Start();
return h; return h;
} }
private AsynchronousWaitDialog(string titleName, bool allowCancel) /// <summary>
/// Shows a wait dialog that does supports cancelling.
/// </summary>
/// <param name="titleName">Title of the wait dialog</param>
/// <param name="defaultTaskName">The default description text, if no named task is active.</param>
/// <param name="action">The action to run within the wait dialog</param>
public static void RunInCancellableWaitDialog(string titleName, string defaultTaskName, Action<AsynchronousWaitDialog> 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 = titleName; this.titleName = StringParser.Parse(titleName);
Done(); // set default values for titleName this.defaultTaskName = StringParser.Parse(defaultTaskName ?? "${res:Global.PleaseWait}");
this.allowCancel = allowCancel; 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
{
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 #region Start waiting thread
/// <summary> /// <summary>
@ -106,19 +200,29 @@ namespace ICSharpCode.SharpDevelop.Gui
void Run() void Run()
{ {
Thread.Sleep(ShowWaitDialogDelay); Thread.Sleep(ShowWaitDialogDelay);
bool isShowingDialog; if (collector.ProgressMonitorIsDisposed)
lock (lockObject) {
if (disposed)
return; return;
dlg = new AsynchronousWaitDialogForm();
dlg.Text = StringParser.Parse(titleName); dlg = new AsynchronousWaitDialogForm(cancellation != null);
dlg.Text = titleName;
dlg.cancelButton.Click += CancelButtonClick; dlg.cancelButton.Click += CancelButtonClick;
UpdateTask();
dlg.CreateControl(); dlg.CreateControl();
IntPtr h = dlg.Handle; // force handle creation IntPtr h = dlg.Handle; // force handle creation
isShowingDialog = showingDialog;
// 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(); Application.Run();
} else { } else {
Application.Run(dlg); Application.Run(dlg);
@ -129,166 +233,92 @@ namespace ICSharpCode.SharpDevelop.Gui
/// <summary> /// <summary>
/// Closes the wait dialog. /// Closes the wait dialog.
/// </summary> /// </summary>
public void Dispose() void progress_ProgressMonitorDisposed(object sender, EventArgs e)
{
lock (lockObject) {
if (disposed) return;
disposed = true;
if (dlg != null) {
dlg.BeginInvoke(new MethodInvoker(DisposeInvoked));
}
}
}
void DisposeInvoked()
{ {
dlg.Dispose(); dlg.Dispose();
Application.ExitThread(); Application.ExitThread();
} }
public int WorkDone { bool reshowTimerRunning = false;
get {
return workDone;
}
set {
if (workDone != value) {
lock (lockObject) {
workDone = value;
if (dlg != null && disposed == false) {
dlg.BeginInvoke(new MethodInvoker(UpdateProgress));
}
}
}
}
}
public string TaskName {
get {
lock (lockObject) {
return taskName;
}
}
set {
if (taskName != value) {
lock (lockObject) {
taskName = value;
if (dlg != null && disposed == false) {
dlg.BeginInvoke(new MethodInvoker(UpdateTask));
}
}
}
}
}
/// <summary> void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
/// Begins a new task with the specified name and total amount of work.
/// </summary>
/// <param name="name">Name of the task. Use null to display "please wait..." message</param>
/// <param name="totalWork">Total amount of work in work units. Use 0 for unknown amount of work.</param>
/// <param name="allowCancel">Specifies whether the task can be cancelled.</param>
public void BeginTask(string name, int totalWork, bool allowCancel)
{ {
if (name == null) // show/hide dialog as required by ShowingDialog
name = "${res:Global.PleaseWait}"; if (dlg.Visible == collector.ShowingDialog) {
if (totalWork < 0) if (collector.ShowingDialog) {
totalWork = 0; dlg.Hide();
} else if (!reshowTimerRunning) {
lock (lockObject) { reshowTimerRunning = true;
this.allowCancel = allowCancel; var timer = new System.Windows.Forms.Timer();
this.totalWork = totalWork; timer.Interval = 100;
this.taskName = name; timer.Tick += delegate {
if (dlg != null && disposed == false) { timer.Dispose();
dlg.BeginInvoke(new MethodInvoker(UpdateTask)); reshowTimerRunning = false;
} if (!collector.ShowingDialog)
} dlg.Show();
};
timer.Start();
} }
/// <summary>
/// Resets the task to a generic "please wait" with marquee progress bar.
/// </summary>
public void Done()
{
workDone = 0;
BeginTask(null, 0, false);
} }
void UpdateTask() // update task name and progress
{ if (e.PropertyName == "TaskName")
int totalWork = this.totalWork; dlg.taskLabel.Text = collector.TaskName ?? defaultTaskName;
if (double.IsNaN(collector.Progress)) {
dlg.taskLabel.Text = StringParser.Parse(taskName); dlg.progressBar.Style = ProgressBarStyle.Marquee;
if (allowCancel) {
dlg.cancelButton.Visible = true;
dlg.progressBar.Width = dlg.cancelButton.Left - 8 - dlg.progressBar.Left;
} else { } 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; dlg.progressBar.Style = ProgressBarStyle.Continuous;
} else { dlg.progressBar.Value = Math.Max(0, Math.Min(100, (int)(collector.Progress * 100)));
dlg.progressBar.Style = ProgressBarStyle.Marquee;
} }
UpdateProgress();
} }
void UpdateProgress() void CancelButtonClick(object sender, EventArgs e)
{ {
int workDone = this.workDone; dlg.cancelButton.Enabled = false;
if (workDone < 0) workDone = 0; if (cancellation != null)
if (workDone > dlg.progressBar.Maximum) cancellation.Cancel();
workDone = dlg.progressBar.Maximum;
dlg.progressBar.Value = workDone;
} }
bool showingDialog; #region IProgressMonitor interface impl (forwards to progress.ProgressMonitor)
/// <inheritdoc/>
public bool ShowingDialog { public string TaskName {
get { return showingDialog; } get { return collector.ProgressMonitor.TaskName; }
set { set { collector.ProgressMonitor.TaskName = value; }
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();
}
}));
}
} }
/// <inheritdoc/>
public double Progress {
get { return collector.ProgressMonitor.Progress; }
set { collector.ProgressMonitor.Progress = value; }
} }
/// <inheritdoc/>
public bool ShowingDialog {
get { return collector.ProgressMonitor.ShowingDialog; }
set { collector.ProgressMonitor.ShowingDialog = value; }
} }
/// <inheritdoc/>
public OperationStatus Status {
get { return collector.ProgressMonitor.Status; }
set { collector.ProgressMonitor.Status = value; }
} }
/// <inheritdoc/>
public CancellationToken CancellationToken {
get { return collector.ProgressMonitor.CancellationToken; }
} }
void CancelButtonClick(object sender, EventArgs e) /// <inheritdoc/>
public IProgressMonitor CreateSubTask(double workAmount)
{ {
dlg.cancelButton.Enabled = false; return collector.ProgressMonitor.CreateSubTask(workAmount);
if (!cancelled) {
cancelled = true;
EventHandler eh = Cancelled;
if (eh != null) {
eh(this, e);
}
}
} }
public bool IsCancelled { public void Dispose()
get { {
return cancelled; collector.ProgressMonitor.Dispose();
}
} }
#endregion
public event EventHandler Cancelled;
} }
} }

120
src/Main/Base/Project/Src/Gui/IProgressMonitor.cs

@ -6,67 +6,88 @@
// </file> // </file>
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
namespace ICSharpCode.SharpDevelop.Gui namespace ICSharpCode.SharpDevelop.Gui
{ {
/// <summary> /// <summary>
/// This is a basic interface to a "progress bar" type of /// Represents a target where an active task reports progress to.
/// control.
/// </summary> /// </summary>
public interface IProgressMonitor /// <remarks>
/// 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.
/// </remarks>
public interface IProgressMonitor : IDisposable
{ {
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="name">Name of the task. Use null to display a default message</param> double Progress { get; set; }
/// <param name="totalWork">Total amount of work in work units. Use 0 for unknown amount of work.</param>
/// <param name="allowCancel">Specifies whether the task can be cancelled.</param>
void BeginTask(string name, int totalWork, bool allowCancel);
/// <summary> /// <summary>
/// Gets/Sets the amount of work already done /// Creates a nested task.
/// </summary> /// </summary>
int WorkDone { /// <param name="workAmount">The amount of work this sub-task performs in relation to the work of this task.
get; /// That means, this parameter is used as a scaling factor for work performed within the subtask.</param>
set; /// <returns>A new progress monitor representing the sub-task.
} /// Multiple child progress monitors can be used at once; even concurrently on multiple threads.</returns>
IProgressMonitor CreateSubTask(double workAmount);
/// <summary> /// <summary>
/// Marks the current task as Done. /// Gets/Sets the name to show while the task is active.
/// </summary> /// </summary>
void Done(); string TaskName { get; set; }
/// <summary> /// <summary>
/// Gets/Sets the current task name. /// 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.
/// </summary> /// </summary>
string TaskName { bool ShowingDialog { // TODO: get rid of this. Don't mix calculations and UI!
get; get;
set; set;
} }
/// <summary> /// <summary>
/// Gets/sets if the task current shows a modal dialog. Set this property to true to make progress /// Gets the cancellation token.
/// dialogs windows temporarily invisible while your modal dialog is showing.
/// </summary> /// </summary>
bool ShowingDialog { CancellationToken CancellationToken { get; }
get;
set;
}
/// <summary> /// <summary>
/// Gets whether the user has cancelled the operation. /// 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.
/// </summary> /// </summary>
bool IsCancelled { OperationStatus Status { get; set; }
get;
} }
/// <summary> /// <summary>
/// Occurs when the user cancels the operation. /// Represents the status of a operation with progress monitor.
/// This event could be raised on any thread. /// </summary>
public enum OperationStatus : byte
{
/// <summary>
/// Everything is normal.
/// </summary>
Normal,
/// <summary>
/// There was at least one warning.
/// </summary> /// </summary>
event EventHandler Cancelled; Warning,
/// <summary>
/// There was at least one error.
/// </summary>
Error
} }
/// <summary>
/// Adapter IDomProgressMonitor -> IProgressMonitor
/// </summary>
public sealed class DomProgressMonitor : Dom.IDomProgressMonitor public sealed class DomProgressMonitor : Dom.IDomProgressMonitor
{ {
IProgressMonitor monitor; IProgressMonitor monitor;
@ -92,41 +113,30 @@ namespace ICSharpCode.SharpDevelop.Gui
} }
} }
internal class DummyProgressMonitor : IProgressMonitor /// <summary>
/// Dummy progress monitor implementation that does not report the progress anywhere.
/// </summary>
public sealed class DummyProgressMonitor : IProgressMonitor
{ {
int workDone; public string TaskName { get; set; }
string taskName;
bool showingDialog;
public int WorkDone { public bool ShowingDialog { get; set; }
get { return workDone; }
set { workDone = value; }
}
public string TaskName { public OperationStatus Status { get; set; }
get { return taskName; }
set { taskName = value; }
}
public void BeginTask(string name, int totalWork, bool allowCancel) public CancellationToken CancellationToken {
{ get { return CancellationToken.None; }
taskName = name;
workDone = 0;
} }
public void Done() public double Progress { get; set; }
{
}
public bool IsCancelled { public IProgressMonitor CreateSubTask(double workAmount)
get { return false; } {
return new DummyProgressMonitor();
} }
public bool ShowingDialog { public void Dispose()
get { return showingDialog; } {
set { showingDialog = value; }
} }
public event EventHandler Cancelled { add { } remove { } }
} }
} }

335
src/Main/Base/Project/Src/Gui/ProgressCollector.cs

@ -0,0 +1,335 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
namespace ICSharpCode.SharpDevelop.Gui
{
/// <summary>
/// Collects progress using nested IProgressMonitors and provides it to a different thread using events.
/// </summary>
public sealed class ProgressCollector : INotifyPropertyChanged
{
readonly ISynchronizeInvoke eventThread;
readonly CancellationToken cancellationToken;
readonly MonitorImpl root;
readonly LinkedList<string> namedMonitors = new LinkedList<string>();
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; }
}
/// <summary>
/// Gets whether the root progress monitor was disposed.
/// </summary>
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<string> RegisterNamedMonitor(string name)
{
lock (namedMonitors) {
LinkedListNode<string> newEntry = namedMonitors.AddLast(name);
if (namedMonitors.First == newEntry) {
SetTaskName(name);
}
return newEntry;
}
}
void UnregisterNamedMonitor(LinkedListNode<string> nameEntry)
{
lock (namedMonitors) {
bool wasFirst = namedMonitors.First == nameEntry;
namedMonitors.Remove(nameEntry);
if (wasFirst)
SetTaskName(namedMonitors.First != null ? namedMonitors.First.Value : null);
}
}
void ChangeName(LinkedListNode<string> 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<string> 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();
}
}
}
}

146
src/Main/Base/Project/Src/Project/BuildEngine.cs

@ -8,10 +8,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
using System.Text;
namespace ICSharpCode.SharpDevelop.Project namespace ICSharpCode.SharpDevelop.Project
{ {
@ -25,7 +26,7 @@ namespace ICSharpCode.SharpDevelop.Project
public sealed class BuildEngine public sealed class BuildEngine
{ {
#region Building in the SharpDevelop GUI #region Building in the SharpDevelop GUI
static CancellableProgressMonitor guiBuildProgressMonitor; static CancellationTokenSource guiBuildCancellation;
static IAnalyticsMonitorTrackedFeature guiBuildTrackedFeature; static IAnalyticsMonitorTrackedFeature guiBuildTrackedFeature;
/// <summary> /// <summary>
@ -41,7 +42,7 @@ namespace ICSharpCode.SharpDevelop.Project
if (options == null) if (options == null)
throw new ArgumentNullException("options"); throw new ArgumentNullException("options");
WorkbenchSingleton.AssertMainThread(); WorkbenchSingleton.AssertMainThread();
if (guiBuildProgressMonitor != null) { if (guiBuildCancellation != null) {
BuildResults results = new BuildResults(); BuildResults results = new BuildResults();
StatusBarService.ShowErrorMessage(Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning")); StatusBarService.ShowErrorMessage(Core.ResourceService.GetString("MainWindow.CompilerMessages.MSBuildAlreadyRunning"));
results.Add(new BuildError(null, 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); options.Callback(results);
} }
} else { } else {
guiBuildProgressMonitor = new CancellableProgressMonitor(StatusBarService.CreateProgressMonitor()); guiBuildCancellation = new CancellationTokenSource();
IProgressMonitor progressMonitor = StatusBarService.CreateProgressMonitor(guiBuildCancellation.Token);
guiBuildTrackedFeature = AnalyticsMonitorService.TrackFeature("Build"); guiBuildTrackedFeature = AnalyticsMonitorService.TrackFeature("Build");
StatusBarService.SetMessage(StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb}...")); StatusBarService.SetMessage(StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb}..."));
ProjectService.RaiseEventBuildStarted(new BuildEventArgs(project, options)); ProjectService.RaiseEventBuildStarted(new BuildEventArgs(project, options));
StartBuild(project, options, StartBuild(project, options,
new MessageViewSink(TaskService.BuildMessageViewCategory), new MessageViewSink(TaskService.BuildMessageViewCategory, progressMonitor));
guiBuildProgressMonitor);
} }
} }
@ -66,7 +67,7 @@ namespace ICSharpCode.SharpDevelop.Project
public static bool IsGuiBuildRunning { public static bool IsGuiBuildRunning {
get { get {
WorkbenchSingleton.AssertMainThread(); WorkbenchSingleton.AssertMainThread();
return guiBuildProgressMonitor != null; return guiBuildCancellation != null;
} }
} }
@ -77,8 +78,8 @@ namespace ICSharpCode.SharpDevelop.Project
public static void CancelGuiBuild() public static void CancelGuiBuild()
{ {
WorkbenchSingleton.AssertMainThread(); WorkbenchSingleton.AssertMainThread();
if (guiBuildProgressMonitor != null) { if (guiBuildCancellation != null) {
guiBuildProgressMonitor.Cancel(); guiBuildCancellation.Cancel();
} }
} }
@ -88,10 +89,16 @@ namespace ICSharpCode.SharpDevelop.Project
sealed class MessageViewSink : IBuildFeedbackSink sealed class MessageViewSink : IBuildFeedbackSink
{ {
Gui.MessageViewCategory messageView; Gui.MessageViewCategory messageView;
Gui.IProgressMonitor progressMonitor;
public MessageViewSink(ICSharpCode.SharpDevelop.Gui.MessageViewCategory messageView) public MessageViewSink(MessageViewCategory messageView, Gui.IProgressMonitor progressMonitor)
{ {
this.messageView = messageView; this.messageView = messageView;
this.progressMonitor = progressMonitor;
}
public IProgressMonitor ProgressMonitor {
get { return progressMonitor; }
} }
public void ReportError(BuildError error) public void ReportError(BuildError error)
@ -116,7 +123,7 @@ namespace ICSharpCode.SharpDevelop.Project
{ {
WorkbenchSingleton.SafeThreadAsyncCall( WorkbenchSingleton.SafeThreadAsyncCall(
delegate { delegate {
guiBuildProgressMonitor = null; guiBuildCancellation = null;
if (guiBuildTrackedFeature != null) { if (guiBuildTrackedFeature != null) {
guiBuildTrackedFeature.EndTracking(); guiBuildTrackedFeature.EndTracking();
guiBuildTrackedFeature = null; 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 #endregion
#region StartBuild #region StartBuild
@ -220,8 +158,9 @@ namespace ICSharpCode.SharpDevelop.Project
/// <param name="realtimeBuildFeedbackSink">The build feedback sink that receives the build output. /// <param name="realtimeBuildFeedbackSink">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 /// 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.</param> /// will ensure that output from two projects building in parallel isn't interleaved.</param>
/// <param name="progressMonitor">The progress monitor that receives build progress.</param> /// <param name="progressMonitor">The progress monitor that receives build progress. The monitor will be disposed
public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink, IProgressMonitor progressMonitor) /// when the build completes.</param>
public static void StartBuild(IBuildable project, BuildOptions options, IBuildFeedbackSink realtimeBuildFeedbackSink)
{ {
if (project == null) if (project == null)
throw new ArgumentNullException("solution"); throw new ArgumentNullException("solution");
@ -240,7 +179,7 @@ namespace ICSharpCode.SharpDevelop.Project
BuildEngine engine = new BuildEngine(options, project); BuildEngine engine = new BuildEngine(options, project);
engine.buildStart = DateTime.Now; engine.buildStart = DateTime.Now;
engine.combinedBuildFeedbackSink = realtimeBuildFeedbackSink; engine.combinedBuildFeedbackSink = realtimeBuildFeedbackSink;
engine.progressMonitor = progressMonitor; engine.progressMonitor = realtimeBuildFeedbackSink.ProgressMonitor;
try { try {
engine.rootNode = engine.CreateBuildGraph(project); engine.rootNode = engine.CreateBuildGraph(project);
} catch (CyclicDependencyException ex) { } catch (CyclicDependencyException ex) {
@ -257,10 +196,7 @@ namespace ICSharpCode.SharpDevelop.Project
if (engine.workersToStart < 1) if (engine.workersToStart < 1)
engine.workersToStart = 1; engine.workersToStart = 1;
if (progressMonitor != null) { engine.cancellationRegistration = engine.progressMonitor.CancellationToken.Register(engine.BuildCancelled);
progressMonitor.Cancelled += engine.BuildCancelled;
progressMonitor.BeginTask("", engine.nodeDict.Count, true);
}
engine.ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildStarted}"); engine.ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildStarted}");
engine.StartBuildProjects(); engine.StartBuildProjects();
@ -292,6 +228,9 @@ namespace ICSharpCode.SharpDevelop.Project
/// <summary>The list of nodes that directly depend on this node</summary> /// <summary>The list of nodes that directly depend on this node</summary>
internal List<BuildNode> dependentOnThis = new List<BuildNode>(); internal List<BuildNode> dependentOnThis = new List<BuildNode>();
/// <summary>Progress monitor just for this node, used only while the node is being built</summary>
internal IProgressMonitor perNodeProgressMonitor;
int totalDependentOnThisCount = -1; int totalDependentOnThisCount = -1;
/// <summary>Gets the number of nodes that depend on this node /// <summary>Gets the number of nodes that depend on this node
@ -336,6 +275,12 @@ namespace ICSharpCode.SharpDevelop.Project
public void ReportError(BuildError error) 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); engine.ReportError(this, error);
} }
@ -348,6 +293,15 @@ namespace ICSharpCode.SharpDevelop.Project
{ {
engine.OnBuildFinished(this, success); 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 #endregion
@ -355,6 +309,7 @@ namespace ICSharpCode.SharpDevelop.Project
readonly Dictionary<IBuildable, BuildNode> nodeDict = new Dictionary<IBuildable, BuildNode>(); readonly Dictionary<IBuildable, BuildNode> nodeDict = new Dictionary<IBuildable, BuildNode>();
readonly BuildOptions options; readonly BuildOptions options;
IProgressMonitor progressMonitor; IProgressMonitor progressMonitor;
CancellationTokenRegistration cancellationRegistration;
BuildNode rootNode; BuildNode rootNode;
readonly IBuildable rootProject; readonly IBuildable rootProject;
readonly BuildResults results = new BuildResults(); readonly BuildResults results = new BuildResults();
@ -461,6 +416,7 @@ namespace ICSharpCode.SharpDevelop.Project
} }
BuildNode node = projectsReadyForBuildStart[projectToStartIndex]; BuildNode node = projectsReadyForBuildStart[projectToStartIndex];
projectsReadyForBuildStart.RemoveAt(projectToStartIndex); projectsReadyForBuildStart.RemoveAt(projectToStartIndex);
node.perNodeProgressMonitor = progressMonitor.CreateSubTask(1.0 / nodeDict.Count);
node.buildStarted = true; node.buildStarted = true;
bool hasDependencyErrors = false; bool hasDependencyErrors = false;
@ -514,6 +470,8 @@ namespace ICSharpCode.SharpDevelop.Project
void OnBuildFinished(BuildNode node, bool success) 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) { lock (this) {
if (node.buildFinished) { if (node.buildFinished) {
throw new InvalidOperationException("This node already finished building, do not call IBuildFeedbackSink.Done() multiple times!"); 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); projectsCurrentlyBuilding.Remove(node);
results.AddBuiltProject(node.project); results.AddBuiltProject(node.project);
if (progressMonitor != null) {
progressMonitor.WorkDone += 1;
}
foreach (BuildNode n in node.dependentOnThis) { foreach (BuildNode n in node.dependentOnThis) {
n.outstandingDependencies--; n.outstandingDependencies--;
@ -571,9 +526,8 @@ namespace ICSharpCode.SharpDevelop.Project
ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildFinished}" + buildTime); ReportMessageInternal("${res:MainWindow.CompilerMessages.BuildFinished}" + buildTime);
} }
if (progressMonitor != null) { cancellationRegistration.Dispose();
progressMonitor.Done(); progressMonitor.Dispose();
}
ReportDone(); ReportDone();
} }
@ -603,7 +557,7 @@ namespace ICSharpCode.SharpDevelop.Project
#region Cancel build #region Cancel build
bool buildIsCancelled; bool buildIsCancelled;
void BuildCancelled(object sender, EventArgs e) void BuildCancelled()
{ {
lock (this) { lock (this) {
if (buildIsDone) if (buildIsDone)
@ -692,13 +646,11 @@ namespace ICSharpCode.SharpDevelop.Project
void UpdateProgressTaskName() void UpdateProgressTaskName()
{ {
lock (this) { lock (this) {
if (progressMonitor != null) { progressMonitor.TaskName = StringParser.Parse("${res:MainWindow.CompilerMessages.BuildVerb} ")
progressMonitor.TaskName = "${res:MainWindow.CompilerMessages.BuildVerb} "
+ string.Join(", ", projectsCurrentlyBuilding.Select(n => n.project.Name)) + string.Join(", ", projectsCurrentlyBuilding.Select(n => n.project.Name))
+ "..."; + "...";
} }
} }
}
#endregion #endregion
} }
} }

29
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) { if (item.ItemType == ItemType.Compile) {
return 50; return 50;
@ -121,13 +121,11 @@ namespace ICSharpCode.SharpDevelop.Project.Converter
throw new ArgumentNullException("targetProjectItems"); throw new ArgumentNullException("targetProjectItems");
ICollection<ProjectItem> sourceItems = sourceProject.Items; ICollection<ProjectItem> sourceItems = sourceProject.Items;
int totalWork = 0; double totalWork = 0;
foreach (ProjectItem item in sourceItems) { foreach (ProjectItem item in sourceItems) {
totalWork += GetRequiredWork(item); totalWork += GetRequiredWork(item);
} }
monitor.BeginTask("Converting", totalWork, true);
int workDone = 0;
foreach (ProjectItem item in sourceItems) { foreach (ProjectItem item in sourceItems) {
FileProjectItem fileItem = item as FileProjectItem; FileProjectItem fileItem = item as FileProjectItem;
if (fileItem != null && FileUtility.IsBaseDirectory(sourceProject.Directory, fileItem.FileName)) { if (fileItem != null && FileUtility.IsBaseDirectory(sourceProject.Directory, fileItem.FileName)) {
@ -148,13 +146,9 @@ namespace ICSharpCode.SharpDevelop.Project.Converter
} else { } else {
targetProjectItems.AddProjectItem(item.CloneFor(targetProject)); targetProjectItems.AddProjectItem(item.CloneFor(targetProject));
} }
if (monitor.IsCancelled) { monitor.CancellationToken.ThrowIfCancellationRequested();
return; monitor.Progress += GetRequiredWork(item) / totalWork;
}
workDone += GetRequiredWork(item);
monitor.WorkDone = workDone;
} }
monitor.Done();
} }
protected StringBuilder conversionLog; protected StringBuilder conversionLog;
@ -178,16 +172,23 @@ namespace ICSharpCode.SharpDevelop.Project.Converter
conversionLog.Append(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.TargetDirectory")).Append(": "); conversionLog.Append(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.TargetDirectory")).Append(": ");
conversionLog.AppendLine(targetProjectDirectory); conversionLog.AppendLine(targetProjectDirectory);
try {
PerformConversion(translatedTitle, sourceProject, targetProjectDirectory);
} catch (OperationCanceledException) {
// ignore
}
}
void PerformConversion(string translatedTitle, MSBuildBasedProject sourceProject, string targetProjectDirectory)
{
IProject targetProject; IProject targetProject;
using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog(translatedTitle)) { using (AsynchronousWaitDialog monitor = AsynchronousWaitDialog.ShowWaitDialog(translatedTitle, "Converting", true)) {
Directory.CreateDirectory(targetProjectDirectory); Directory.CreateDirectory(targetProjectDirectory);
targetProject = CreateProject(targetProjectDirectory, sourceProject); targetProject = CreateProject(targetProjectDirectory, sourceProject);
CopyProperties(sourceProject, targetProject); CopyProperties(sourceProject, targetProject);
conversionLog.AppendLine(); conversionLog.AppendLine();
CopyItems(sourceProject, targetProject, monitor); CopyItems(sourceProject, targetProject, monitor);
if (monitor.IsCancelled) { monitor.CancellationToken.ThrowIfCancellationRequested();
return;
}
conversionLog.AppendLine(); conversionLog.AppendLine();
AfterConversion(targetProject); AfterConversion(targetProject);
conversionLog.AppendLine(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.ConversionComplete")); conversionLog.AppendLine(ResourceService.GetString("ICSharpCode.SharpDevelop.Commands.Convert.ConversionComplete"));

7
src/Main/Base/Project/Src/Project/IBuildFeedbackSink.cs

@ -15,6 +15,13 @@ namespace ICSharpCode.SharpDevelop.Project
/// </summary> /// </summary>
public interface IBuildFeedbackSink public interface IBuildFeedbackSink
{ {
/// <summary>
/// Gets the progress monitor associated with this build.
/// Does not return null.
/// This member is thread-safe.
/// </summary>
Gui.IProgressMonitor ProgressMonitor { get; }
/// <summary> /// <summary>
/// Reports an build error by adding it to the error list. /// Reports an build error by adding it to the error list.
/// This member is thread-safe. /// This member is thread-safe.

18
src/Main/Base/Project/Src/Project/ProjectLoadInformation.cs

@ -11,7 +11,7 @@ using System;
namespace ICSharpCode.SharpDevelop.Project namespace ICSharpCode.SharpDevelop.Project
{ {
/// <summary> /// <summary>
/// Description of ProjectLoadInformation. /// Parameter object for loading an existing project.
/// </summary> /// </summary>
public class ProjectLoadInformation public class ProjectLoadInformation
{ {
@ -21,7 +21,21 @@ namespace ICSharpCode.SharpDevelop.Project
public string ProjectName { get; private set; } public string ProjectName { get; private set; }
public string TypeGuid { get; set; } public string TypeGuid { get; set; }
internal string Guid { get; set; } internal string Guid { get; set; }
public Gui.IProgressMonitor ProgressMonitor { get; set; }
Gui.IProgressMonitor progressMonitor = new Gui.DummyProgressMonitor();
/// <summary>
/// Gets/Sets the progress monitor used during the load.
/// This property never returns null.
/// </summary>
public Gui.IProgressMonitor ProgressMonitor {
get { return progressMonitor; }
set {
if (value == null)
throw new ArgumentNullException();
progressMonitor = value;
}
}
public ProjectLoadInformation(Solution parentSolution, string fileName, string projectName) public ProjectLoadInformation(Solution parentSolution, string fileName, string projectName)
{ {

14
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) static ProjectSection SetupSolutionLoadSolutionProjects(Solution newSolution, StreamReader sr, IProgressMonitor progressMonitor)
{ {
if (progressMonitor == null)
throw new ArgumentNullException("progressMonitor");
string solutionDirectory = Path.GetDirectoryName(newSolution.FileName); string solutionDirectory = Path.GetDirectoryName(newSolution.FileName);
ProjectSection nestedProjectsSection = null; ProjectSection nestedProjectsSection = null;
@ -542,15 +545,10 @@ namespace ICSharpCode.SharpDevelop.Project
ProjectLoadInformation loadInfo = new ProjectLoadInformation(newSolution, location, title); ProjectLoadInformation loadInfo = new ProjectLoadInformation(newSolution, location, title);
loadInfo.TypeGuid = projectGuid; loadInfo.TypeGuid = projectGuid;
loadInfo.Guid = guid; loadInfo.Guid = guid;
// loadInfo.ProgressMonitor = progressMonitor;
// IProject newProject = ProjectBindingService.LoadProject(loadInfo);
// newProject.IdGuid = guid;
projectsToLoad.Add(loadInfo); projectsToLoad.Add(loadInfo);
IList<ProjectSection> currentProjectSections = new List<ProjectSection>(); IList<ProjectSection> currentProjectSections = new List<ProjectSection>();
ReadProjectSections(sr, currentProjectSections); ReadProjectSections(sr, currentProjectSections);
readProjectSections.Add(currentProjectSections); readProjectSections.Add(currentProjectSections);
// newSolution.AddFolder(newProject);
} }
match = match.NextMatch(); match = match.NextMatch();
} else { } else {
@ -578,11 +576,17 @@ namespace ICSharpCode.SharpDevelop.Project
loadInfo.Platform = AbstractProject.GetPlatformNameFromKey(projectConfig.Location); loadInfo.Platform = AbstractProject.GetPlatformNameFromKey(projectConfig.Location);
loadInfo.ProgressMonitor = progressMonitor; loadInfo.ProgressMonitor = progressMonitor;
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); IProject newProject = ProjectBindingService.LoadProject(loadInfo);
newProject.IdGuid = loadInfo.Guid; newProject.IdGuid = loadInfo.Guid;
newProject.ProjectSections.AddRange(projectSections); newProject.ProjectSections.AddRange(projectSections);
newSolution.AddFolder(newProject); newSolution.AddFolder(newProject);
} }
}
return nestedProjectsSection; return nestedProjectsSection;
} }
#endregion #endregion

79
src/Main/Base/Project/Src/Services/ParserService/LoadSolutionProjects.cs

@ -77,8 +77,8 @@ namespace ICSharpCode.SharpDevelop
if (!reParse1.Contains(pc)) { if (!reParse1.Contains(pc)) {
LoggingService.Debug("Enqueue for reinitializing references: " + project); LoggingService.Debug("Enqueue for reinitializing references: " + project);
reParse1.Add(pc); reParse1.Add(pc);
jobs.AddJob(new JobTask(ct => ReInitializeReferences(pc, ct), jobs.AddJob(new JobTask(pm => ReInitializeReferences(pc, pm),
"Loading references for " + project.Name + "...", GetLoadReferenceTaskTitle(project.Name),
10 10
)); ));
} }
@ -89,8 +89,8 @@ namespace ICSharpCode.SharpDevelop
if (!reParse2.Contains(pc)) { if (!reParse2.Contains(pc)) {
LoggingService.Debug("Enqueue for reparsing code: " + project); LoggingService.Debug("Enqueue for reparsing code: " + project);
reParse2.Add(pc); reParse2.Add(pc);
jobs.AddJob(new JobTask(ct => ReparseCode(pc, ct), jobs.AddJob(new JobTask(pm => ReparseCode(pc, pm),
"${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + project.Name + "...", GetParseTaskTitle(project.Name),
pc.GetInitializationWorkAmount() 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) { lock (reParse1) {
reParse1.Remove(pc); 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) { lock (reParse2) {
reParse2.Remove(pc); reParse2.Remove(pc);
} }
pc.ReInitialize2(ct); pc.ReInitialize2(progressMonitor);
} }
#endregion #endregion
@ -132,26 +132,36 @@ namespace ICSharpCode.SharpDevelop
for (int i = 0; i < createdContents.Count; i++) { for (int i = 0; i < createdContents.Count; i++) {
ParseProjectContent pc = createdContents[i]; ParseProjectContent pc = createdContents[i];
jobs.AddJob(new JobTask(pc.Initialize1, jobs.AddJob(new JobTask(pc.Initialize1,
"Loading references for " + pc.ProjectName + "...", GetLoadReferenceTaskTitle(pc.ProjectName),
10)); 10));
} }
for (int i = 0; i < createdContents.Count; i++) { for (int i = 0; i < createdContents.Count; i++) {
ParseProjectContent pc = createdContents[i]; ParseProjectContent pc = createdContents[i];
jobs.AddJob(new JobTask(pc.Initialize2, jobs.AddJob(new JobTask(pc.Initialize2,
"${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + pc.ProjectName + "...", GetParseTaskTitle(pc.ProjectName),
pc.GetInitializationWorkAmount())); pc.GetInitializationWorkAmount()));
} }
jobs.AddJob(new JobTask(ct => RaiseThreadEnded(openedSolution), "", 0)); jobs.AddJob(new JobTask(ct => RaiseThreadEnded(openedSolution), "", 0));
jobs.StartRunningIfRequired(); 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) internal static void InitNewProject(ParseProjectContent pc)
{ {
jobs.AddJob(new JobTask(pc.Initialize1, jobs.AddJob(new JobTask(pc.Initialize1,
"Loading references for " + pc.ProjectName + "...", GetLoadReferenceTaskTitle(pc.ProjectName),
10)); 10));
jobs.AddJob(new JobTask(pc.Initialize2, jobs.AddJob(new JobTask(pc.Initialize2,
"${res:ICSharpCode.SharpDevelop.Internal.ParserService.Parsing} " + pc.ProjectName + "...", GetParseTaskTitle(pc.ProjectName),
pc.GetInitializationWorkAmount())); pc.GetInitializationWorkAmount()));
jobs.StartRunningIfRequired(); jobs.StartRunningIfRequired();
} }
@ -171,11 +181,11 @@ namespace ICSharpCode.SharpDevelop
{ {
readonly object lockObj = new object(); readonly object lockObj = new object();
readonly Queue<JobTask> actions = new Queue<JobTask>(); readonly Queue<JobTask> actions = new Queue<JobTask>();
readonly IProgressMonitor progressMonitor = StatusBarService.CreateProgressMonitor();
CancellationTokenSource cancellationSource = new CancellationTokenSource(); CancellationTokenSource cancellationSource = new CancellationTokenSource();
IProgressMonitor progressMonitor;
bool threadIsRunning; bool threadIsRunning;
int totalWork; double totalWork;
int workDone; double workDone;
public void AddJob(JobTask task) public void AddJob(JobTask task)
{ {
@ -193,8 +203,8 @@ namespace ICSharpCode.SharpDevelop
if (!this.threadIsRunning && this.actions.Count > 0) { if (!this.threadIsRunning && this.actions.Count > 0) {
this.threadIsRunning = true; this.threadIsRunning = true;
progressMonitor.BeginTask(this.actions.Peek().name, totalWork, false); progressMonitor = StatusBarService.CreateProgressMonitor(cancellationSource.Token);
progressMonitor.WorkDone = 0; progressMonitor.TaskName = this.actions.Peek().name;
Thread thread = new Thread(new ThreadStart(RunThread)); Thread thread = new Thread(new ThreadStart(RunThread));
thread.Name = "LoadSolutionProjects"; thread.Name = "LoadSolutionProjects";
@ -208,26 +218,29 @@ namespace ICSharpCode.SharpDevelop
void RunThread() void RunThread()
{ {
while (true) { while (true) {
CancellationToken token;
JobTask task; 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) { lock (lockObj) {
if (actions.Count == 0) { if (actions.Count == 0) {
this.threadIsRunning = false; this.threadIsRunning = false;
progressMonitor.Done(); this.progressMonitor.Dispose();
this.progressMonitor = null;
return; return;
} }
task = this.actions.Dequeue(); task = this.actions.Dequeue();
token = this.cancellationSource.Token;
totalWork = this.totalWork; totalWork = this.totalWork;
workDone = this.workDone; workDone = this.workDone;
progressMonitor = this.progressMonitor;
} }
if (task.cost > 0) { progressMonitor.Progress = workDone / totalWork;
progressMonitor.BeginTask(task.name, totalWork, false); progressMonitor.TaskName = task.name;
progressMonitor.WorkDone = workDone;
}
try { try {
task.Run(token); using (IProgressMonitor subTask = progressMonitor.CreateSubTask(task.cost / totalWork)) {
task.Run(subTask);
}
lock (lockObj) { lock (lockObj) {
this.workDone += task.cost; this.workDone += task.cost;
} }
@ -245,6 +258,10 @@ namespace ICSharpCode.SharpDevelop
cancellationSource.Cancel(); cancellationSource.Cancel();
actions.Clear(); actions.Clear();
cancellationSource = new CancellationTokenSource(); cancellationSource = new CancellationTokenSource();
if (progressMonitor != null) {
progressMonitor.Dispose();
progressMonitor = null;
}
this.totalWork = 0; this.totalWork = 0;
this.workDone = 0; this.workDone = 0;
} }
@ -253,11 +270,11 @@ namespace ICSharpCode.SharpDevelop
sealed class JobTask sealed class JobTask
{ {
readonly Action<CancellationToken> action; readonly Action<IProgressMonitor> action;
internal readonly string name; internal readonly string name;
internal readonly int cost; internal readonly double cost;
public JobTask(Action<CancellationToken> action, string name, int cost) public JobTask(Action<IProgressMonitor> action, string name, double cost)
{ {
if (action == null) if (action == null)
throw new ArgumentNullException("action"); throw new ArgumentNullException("action");
@ -266,9 +283,9 @@ namespace ICSharpCode.SharpDevelop
this.cost = cost; this.cost = cost;
} }
public void Run(CancellationToken token) public void Run(IProgressMonitor progressMonitor)
{ {
action(token); action(progressMonitor);
} }
} }
} }

16
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); return string.Format("[{0}: {1}]", GetType().Name, project.Name);
} }
internal void Initialize1(CancellationToken token) internal void Initialize1(IProgressMonitor progressMonitor)
{ {
ICollection<ProjectItem> items = project.Items; ICollection<ProjectItem> items = project.Items;
ProjectService.ProjectItemAdded += OnProjectItemAdded; ProjectService.ProjectItemAdded += OnProjectItemAdded;
@ -65,7 +65,7 @@ namespace ICSharpCode.SharpDevelop
project.ResolveAssemblyReferences(); project.ResolveAssemblyReferences();
foreach (ProjectItem item in items) { foreach (ProjectItem item in items) {
if (!initializing) return; // abort initialization if (!initializing) return; // abort initialization
token.ThrowIfCancellationRequested(); progressMonitor.CancellationToken.ThrowIfCancellationRequested();
if (ItemType.ReferenceItemTypes.Contains(item.ItemType)) { if (ItemType.ReferenceItemTypes.Contains(item.ItemType)) {
ReferenceProjectItem reference = item as ReferenceProjectItem; ReferenceProjectItem reference = item as ReferenceProjectItem;
if (reference != null) { if (reference != null) {
@ -79,7 +79,7 @@ namespace ICSharpCode.SharpDevelop
OnReferencedContentsChanged(EventArgs.Empty); OnReferencedContentsChanged(EventArgs.Empty);
} }
internal void ReInitialize1(CancellationToken token) internal void ReInitialize1(IProgressMonitor progressMonitor)
{ {
lock (ReferencedContents) { lock (ReferencedContents) {
ReferencedContents.Clear(); ReferencedContents.Clear();
@ -90,7 +90,7 @@ namespace ICSharpCode.SharpDevelop
ProjectService.ProjectItemRemoved -= OnProjectItemRemoved; ProjectService.ProjectItemRemoved -= OnProjectItemRemoved;
initializing = true; initializing = true;
try { try {
Initialize1(token); Initialize1(progressMonitor);
} finally { } finally {
initializing = false; initializing = false;
} }
@ -236,14 +236,14 @@ namespace ICSharpCode.SharpDevelop
return project.Items.Count; return project.Items.Count;
} }
internal void ReInitialize2(CancellationToken token) internal void ReInitialize2(IProgressMonitor progressMonitor)
{ {
if (initializing) return; if (initializing) return;
initializing = true; initializing = true;
Initialize2(token); Initialize2(progressMonitor);
} }
internal void Initialize2(CancellationToken token) internal void Initialize2(IProgressMonitor progressMonitor)
{ {
if (!initializing) return; if (!initializing) return;
//int progressStart = progressMonitor.WorkDone; //int progressStart = progressMonitor.WorkDone;
@ -262,7 +262,7 @@ namespace ICSharpCode.SharpDevelop
ParseableFileContentFinder finder = new ParseableFileContentFinder(); ParseableFileContentFinder finder = new ParseableFileContentFinder();
var fileContents = 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) where !ItemType.NonFileItemTypes.Contains(p.ItemType) && !String.IsNullOrEmpty(p.FileName)
select finder.Create(p); select finder.Create(p);
fileContents.ForAll( fileContents.ForAll(

12
src/Main/Base/Project/Src/Services/ProjectBinding/ProjectBindingService.cs

@ -97,9 +97,7 @@ namespace ICSharpCode.SharpDevelop
string title = loadInformation.ProjectName; string title = loadInformation.ProjectName;
IProgressMonitor progressMonitor = loadInformation.ProgressMonitor; IProgressMonitor progressMonitor = loadInformation.ProgressMonitor;
if (progressMonitor != null) { progressMonitor.CancellationToken.ThrowIfCancellationRequested();
progressMonitor.BeginTask("Loading " + title, 0, false);
}
IProject newProject; IProject newProject;
if (!File.Exists(location)) { if (!File.Exists(location)) {
@ -112,16 +110,16 @@ namespace ICSharpCode.SharpDevelop
newProject = binding.LoadProject(loadInformation); newProject = binding.LoadProject(loadInformation);
} catch (ProjectLoadException ex) { } catch (ProjectLoadException ex) {
LoggingService.Warn("Project load error", 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 = new UnknownProject(location, title, ex.Message, true);
newProject.TypeGuid = loadInformation.TypeGuid; newProject.TypeGuid = loadInformation.TypeGuid;
if (progressMonitor != null) progressMonitor.ShowingDialog = false; progressMonitor.ShowingDialog = false;
} catch (UnauthorizedAccessException ex) { } catch (UnauthorizedAccessException ex) {
LoggingService.Warn("Project load error", 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 = new UnknownProject(location, title, ex.Message, true);
newProject.TypeGuid = loadInformation.TypeGuid; newProject.TypeGuid = loadInformation.TypeGuid;
if (progressMonitor != null) progressMonitor.ShowingDialog = false; progressMonitor.ShowingDialog = false;
} }
} else { } else {
string ext = Path.GetExtension(location); string ext = Path.GetExtension(location);

4
src/Main/Base/Project/Src/Services/ProjectService/CompileModifiedProjectsOnly.cs

@ -297,6 +297,10 @@ namespace ICSharpCode.SharpDevelop.Project
this.currentPass = currentPass; this.currentPass = currentPass;
} }
public Gui.IProgressMonitor ProgressMonitor {
get { return sink.ProgressMonitor; }
}
public void ReportError(BuildError error) public void ReportError(BuildError error)
{ {
sink.ReportError(error); sink.ReportError(error);

21
src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs

@ -154,32 +154,25 @@ namespace ICSharpCode.SharpDevelop.Refactoring
} }
ParseableFileContentFinder finder = new ParseableFileContentFinder(); ParseableFileContentFinder finder = new ParseableFileContentFinder();
List<Reference> references = new List<Reference>(); List<Reference> references = new List<Reference>();
try {
if (progressMonitor != null) { if (progressMonitor != null)
progressMonitor.BeginTask("${res:SharpDevelop.Refactoring.FindingReferences}", files.Count, true); progressMonitor.TaskName = StringParser.Parse("${res:SharpDevelop.Refactoring.FindingReferences}");
}
int index = 0;
foreach (ProjectItem item in files) { foreach (ProjectItem item in files) {
var entry = finder.Create(item); var entry = finder.Create(item);
if (progressMonitor != null) { if (progressMonitor != null) {
progressMonitor.WorkDone = index; progressMonitor.Progress += 1.0 / files.Count;
if (progressMonitor.IsCancelled) { if (progressMonitor.CancellationToken.IsCancellationRequested)
return null; return null;
} }
}
ITextBuffer content = entry.GetContent(); ITextBuffer content = entry.GetContent();
if (content != null) { if (content != null) {
AddReferences(references, ownerClass, member, isLocal, entry.FileName, content.Text); AddReferences(references, ownerClass, member, isLocal, entry.FileName, content.Text);
} }
index++;
}
} finally {
if (progressMonitor != null) {
progressMonitor.Done();
}
} }
return references; return references;
} }

116
src/Main/Base/Project/Src/Services/StatusBar/StatusBarService.cs

@ -7,10 +7,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading;
using System.Windows; using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using ICSharpCode.Core; using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Gui; using ICSharpCode.SharpDevelop.Gui;
@ -97,107 +100,58 @@ namespace ICSharpCode.SharpDevelop
Visible = PropertyService.Get("ICSharpCode.SharpDevelop.Gui.StatusBarVisible", true); Visible = PropertyService.Get("ICSharpCode.SharpDevelop.Gui.StatusBarVisible", true);
} }
public static void Update()
{
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.");
}
}*/
}
#region Progress Monitor #region Progress Monitor
static HashSet<StatusBarProgressMonitor> activeProgressMonitors = new HashSet<StatusBarProgressMonitor>(); static Stack<ProgressCollector> waitingProgresses = new Stack<ProgressCollector>();
static StatusBarProgressMonitor currentProgressMonitor; static ProgressCollector currentProgress;
public static IProgressMonitor CreateProgressMonitor() public static IProgressMonitor CreateProgressMonitor(CancellationToken cancellationToken)
{ {
System.Diagnostics.Debug.Assert(statusBar != null); ProgressCollector progress = new ProgressCollector(WorkbenchSingleton.Workbench.SynchronizingObject, cancellationToken);
return new StatusBarProgressMonitor(); AddProgress(progress);
return progress.ProgressMonitor;
} }
sealed class StatusBarProgressMonitor : IProgressMonitor public static void AddProgress(ProgressCollector progress)
{ {
int workDone, totalWork; if (progress == null)
throw new ArgumentNullException("progress");
public int WorkDone { System.Diagnostics.Debug.Assert(statusBar != null);
get { return workDone; } WorkbenchSingleton.AssertMainThread();
set { if (currentProgress != null) {
if (workDone == value) currentProgress.ProgressMonitorDisposed -= progress_ProgressMonitorDisposed;
return; currentProgress.PropertyChanged -= progress_PropertyChanged;
workDone = value;
lock (activeProgressMonitors) {
if (currentProgressMonitor == this) {
UpdateDisplay();
}
}
} }
waitingProgresses.Push(currentProgress); // push even if currentProgress==null
SetActiveProgress(progress);
} }
void UpdateDisplay() static void SetActiveProgress(ProgressCollector progress)
{ {
statusBar.DisplayProgress(taskName, workDone, totalWork); WorkbenchSingleton.AssertMainThread();
currentProgress = progress;
if (progress == null) {
statusBar.HideProgress();
return;
} }
string taskName; progress.ProgressMonitorDisposed += progress_ProgressMonitorDisposed;
if (progress.ProgressMonitorIsDisposed) {
public string TaskName { progress_ProgressMonitorDisposed(progress, null);
get { return taskName; }
set {
if (taskName == value)
return; return;
taskName = value;
lock (activeProgressMonitors) {
if (currentProgressMonitor == this) {
UpdateDisplay();
}
}
} }
progress.PropertyChanged += progress_PropertyChanged;
} }
public bool ShowingDialog { get; set; } static void progress_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
public bool IsCancelled {
get { return false; }
}
public void BeginTask(string name, int totalWork, bool allowCancel)
{ {
lock (activeProgressMonitors) { Debug.Assert(sender == currentProgress);
activeProgressMonitors.Add(this); statusBar.DisplayProgress(currentProgress.TaskName, currentProgress.Progress, currentProgress.Status);
currentProgressMonitor = this;
this.taskName = name;
this.workDone = 0;
this.totalWork = totalWork;
UpdateDisplay();
}
} }
public void Done() static void progress_ProgressMonitorDisposed(object sender, EventArgs e)
{ {
lock (activeProgressMonitors) { Debug.Assert(sender == currentProgress);
activeProgressMonitors.Remove(this); SetActiveProgress(waitingProgresses.Pop()); // stack is never empty: we push null as first element
if (currentProgressMonitor == this) {
if (activeProgressMonitors.Count > 0) {
currentProgressMonitor = activeProgressMonitors.First();
currentProgressMonitor.UpdateDisplay();
} else {
currentProgressMonitor = null;
statusBar.HideProgress();
}
}
}
}
public event EventHandler Cancelled { add { } remove { } }
} }
#endregion #endregion
} }

Loading…
Cancel
Save