Browse Source
- allow nested progress monitors - use .NET 4 cancellation framework git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5483 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61pull/1/head
20 changed files with 893 additions and 577 deletions
@ -0,0 +1,335 @@
@@ -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(); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue