Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/MSDIAL5/MsdialGuiApp/Model/Core/MethodModelBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using CompMs.App.Msdial.Model.DataObj;
using CompMs.App.Msdial.Model.Service;
using CompMs.App.Msdial.ViewModel.Service;
using CompMs.Common.Enum;
using CompMs.CommonMVVM;
using CompMs.MsdialCore.Parameter;
Expand Down Expand Up @@ -75,7 +74,7 @@ public Task LoadAnalysisFileAsync(AnalysisFileBeanModel analysisFile, Cancellati
/// Opening by hand checks the disk again; the automatic first open uses what the dataset found a moment before,
/// so that a folder that is gone is not checked once for every sample in it.
/// </summary>
private ErrorMessageBoxRequest? CannotOpenReason(AnalysisFileBeanModel file, bool checkAgain) {
private MessageRequest? CannotOpenReason(AnalysisFileBeanModel file, bool checkAgain) {
if (BrowsingWithoutRawData == BrowsingWithoutRawData.NotSupported) {
// Imaging: opening a file selects its image, and the project only opened because the raw data are there.
return null;
Expand Down
38 changes: 18 additions & 20 deletions src/MSDIAL5/MsdialGuiApp/Model/Core/ProjectModel.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using CompMs.App.Msdial.Common;
using CompMs.App.Msdial.Model.Service;
using CompMs.App.Msdial.Model.Setting;
using CompMs.App.Msdial.ViewModel.Service;
using CompMs.CommonMVVM;
using CompMs.Graphics.UI.Message;
using CompMs.MsdialCore.DataObj;
using CompMs.MsdialCore.Parameter;
using CompMs.MsdialCore.Parser;
using CompMs.MsdialIntegrate.Parser;
using Microsoft.Win32;
using Reactive.Bindings.Notifiers;
using System;
using System.Collections.Concurrent;
Expand Down Expand Up @@ -137,14 +136,14 @@ private static async Task<ProjectModel> LoadMdprojectAsync(string projectPath, I
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
// Reading the datasets is data work only, and a loaded library can be hundreds of MB: it runs off the UI thread,
// which keeps pumping meanwhile (a minute without pumping is a ContextSwitchDeadlock under the debugger).
// The picker and the messages go to the dispatcher themselves, and the models are built after the await,
// back on the UI thread.
// The picker and the messages are published on the broker, whose view takes them to the dispatcher,
// and the models are built after the await, back on the UI thread.
var projectDataStorage = await Task.Run(() => ReadDatasetsAsync(
streamManager,
deserializer,
path => new DirectoryTreeStreamManager(path),
projectDir,
LocateDatasetFileAsync,
(parameter, missingPath) => LocateDatasetFileAsync(broker, parameter, missingPath),
broker));
System.Diagnostics.Debug.WriteLine($"Read the datasets of {projectPath} in {stopwatch.Elapsed.TotalSeconds:F1} s.");
if (projectDataStorage == null) {
Expand Down Expand Up @@ -198,11 +197,11 @@ internal static async Task<ProjectDataStorage> ReadDatasetsAsync(
missingFiles[parameter] = notFound.FileName;
}
Interlocked.Increment(ref unloadedDatasets);
broker.Publish(new ErrorMessageBoxRequest
broker.Publish(new MessageRequest
{
Caption = "Dataset not loaded",
Content = MessageHelper.DatasetNotLoaded(parameter.ProjectFileName, error),
Icon = MessageBoxImage.Error,
Severity = MessageSeverity.Error,
});
}).ConfigureAwait(false);
if (projectDataStorage is not null && projectDataStorage.Storages.Count == 0 && unloadedDatasets > 0) {
Expand All @@ -212,21 +211,20 @@ internal static async Task<ProjectDataStorage> ReadDatasetsAsync(
return projectDataStorage!;
}

private static async Task<string?> LocateDatasetFileAsync(ProjectBaseParameter parameter, string? missingPath) {
/// <summary>
/// Asks the view, through the broker, to pick the dataset file or a copy of it, and returns the file picked, or null when none was.
/// It is asked from the thread that reads the datasets: the view shows the picker on the UI thread, and the answer is there once Publish returns.
/// </summary>
internal static Task<string?> LocateDatasetFileAsync(IMessageBroker broker, ProjectBaseParameter parameter, string? missingPath) {
string? result = null;
await Application.Current.Dispatcher.InvokeAsync(() =>
var request = new OpenFileRequest(path => result = path)
{
var newofd = new OpenFileDialog
{
Filter = "Dataset file(.mddata)|*.mddata|All(*)|*",
Title = MessageHelper.LocateDatasetFileTitle(parameter.ProjectFileName, missingPath),
RestoreDirectory = true
};
if (newofd.ShowDialog() == true) {
result = newofd.FileName;
}
});
return result;
Filter = "Dataset file(.mddata)|*.mddata|All(*)|*",
Title = MessageHelper.LocateDatasetFileTitle(parameter.ProjectFileName, missingPath),
RestoreDirectory = true,
};
broker.Publish(request);
return Task.FromResult(result);
}

private static async Task<ProjectModel> LoadMddatasetAsync(string mddata, IMessageBroker broker) {
Expand Down
4 changes: 2 additions & 2 deletions src/MSDIAL5/MsdialGuiApp/Model/Export/ExportFailure.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using CompMs.App.Msdial.Model.Loader;
using CompMs.App.Msdial.ViewModel.Service;
using CompMs.App.Msdial.Model.Service;
using Reactive.Bindings.Notifiers;
using System;
using System.IO;
Expand All @@ -17,7 +17,7 @@ namespace CompMs.App.Msdial.Model.Export
internal static class ExportFailure
{
public static void Report(IMessageBroker broker, Exception error) {
broker.Publish(new ErrorMessageBoxRequest
broker.Publish(new MessageRequest
{
Caption = "Export failed",
Content = Describe(error),
Expand Down
35 changes: 35 additions & 0 deletions src/MSDIAL5/MsdialGuiApp/Model/Service/MessageRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace CompMs.App.Msdial.Model.Service
{
/// <summary>
/// How serious a <see cref="MessageRequest"/> is; the view shows it as the icon of the message.
/// </summary>
internal enum MessageSeverity
{
Information,
Warning,
Error,
}

/// <summary>
/// A message that a model publishes on the message broker for the view to show, and, when it asks Yes or No, the answer.
/// It carries no UI type, so a model can say something without referring to the view or the view models.
/// </summary>
internal sealed class MessageRequest
{
public string Caption { get; set; } = "Error";

public string Content { get; set; } = string.Empty;

public MessageSeverity Severity { get; set; } = MessageSeverity.Error;

/// <summary>
/// true to ask Yes or No; otherwise the message only has an OK button.
/// </summary>
public bool AsksYesNo { get; set; }

/// <summary>
/// true when the user answered Yes (or OK); null while nobody has answered.
/// </summary>
public bool? Result { get; set; }
}
}
72 changes: 31 additions & 41 deletions src/MSDIAL5/MsdialGuiApp/Model/Service/RawDataGuard.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
using CompMs.App.Msdial.Model.Core;
using CompMs.App.Msdial.Model.DataObj;
using CompMs.App.Msdial.ViewModel.Service;
using Reactive.Bindings.Notifiers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;

namespace CompMs.App.Msdial.Model.Service
{
/// <summary>
/// What MS-DIAL says when the raw data files of a project are missing: the prompt shown when a dataset opens,
/// why a sample or a command is refused, and the titles of the panels that stay empty.
/// Every message here names a path or an exception, so each goes through <see cref="ErrorMessageBoxRequest"/>,
/// a message box that fits them.
/// Every message here names a path or an exception, so each goes through <see cref="MessageRequest"/>,
/// which the view shows as a message box that fits them.
/// </summary>
internal static class RawDataGuard
{
Expand All @@ -39,10 +37,10 @@ public static bool ConfirmOpen(IMessageBroker broker, BrowsingWithoutRawData mod
}
var counts = $"Raw data are not available for {missing.Count} of {sampleCount} samples in this dataset";
var list = "Raw data files not found:\n" + FormatPaths(missing.Select(file => file.AnalysisFilePath).ToList());
ErrorMessageBoxRequest request;
MessageRequest request;
switch (mode) {
case BrowsingWithoutRawData.Supported:
request = new ErrorMessageBoxRequest
request = new MessageRequest
{
Caption = RawDataNotFoundCaption,
Content = $"{counts}, so only the information saved in the project will be drawn for them.\n\n"
Expand All @@ -52,38 +50,37 @@ public static bool ConfirmOpen(IMessageBroker broker, BrowsingWithoutRawData mod
+ "the export of centroid spectra, and reprocessing.\n\n"
+ $"{list}\n\n"
+ "Continue?",
ButtonType = MessageBoxButton.YesNo,
Icon = MessageBoxImage.Warning,
AsksYesNo = true,
Severity = MessageSeverity.Warning,
};
break;
case BrowsingWithoutRawData.AlignmentsOnly:
request = new ErrorMessageBoxRequest
request = new MessageRequest
{
Caption = RawDataNotFoundCaption,
Content = $"{counts}, so only the information saved in the project will be drawn.\n\n"
+ "Alignment results can be browsed. For this type of project the peak spot viewer reads the raw data, "
+ "so these samples cannot be opened in it, and reprocessing is not available.\n\n"
+ $"{list}\n\n"
+ "Continue?",
ButtonType = MessageBoxButton.YesNo,
Icon = MessageBoxImage.Warning,
AsksYesNo = true,
Severity = MessageSeverity.Warning,
};
break;
default:
broker.Publish(new ErrorMessageBoxRequest
broker.Publish(new MessageRequest
{
Caption = RawDataNotFoundCaption,
Content = $"{counts}.\n\n"
+ "Imaging projects draw their ion images from the raw data, so this project cannot be opened without them.\n\n"
+ $"{list}\n\n"
+ "Put the files back at these paths and open the project again.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Error,
Severity = MessageSeverity.Error,
});
return false;
}
broker.Publish(request);
return request.Result == MessageBoxResult.Yes;
return request.Result == true;
}

/// <summary>
Expand All @@ -94,14 +91,13 @@ public static bool Ensure(bool isRawDataAvailable, AnalysisFileBeanModel file, I
if (isRawDataAvailable) {
return true;
}
broker.Publish(new ErrorMessageBoxRequest
broker.Publish(new MessageRequest
{
Caption = RawDataNotAvailableCaption,
Content = $"{feature} needs the raw data, and the raw data file of {file.AnalysisFileName} was not found:\n"
+ $" {file.AnalysisFilePath}\n\n"
+ "Put the file back at this path, then open another sample and this one again to reload it.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Information,
Severity = MessageSeverity.Information,
});
return false;
}
Expand All @@ -116,77 +112,71 @@ public static bool EnsureAll(IEnumerable<AnalysisFileBeanModel> files, IMessageB
if (missing.Count == 0) {
return true;
}
broker.Publish(new ErrorMessageBoxRequest
broker.Publish(new MessageRequest
{
Caption = RawDataNotAvailableCaption,
Content = $"{feature} cannot run because the raw data files of {missing.Count} of {all.Count} samples were not found. "
+ $"{feature} reads the raw data of every sample again.\n\n"
+ "Raw data files not found:\n"
+ $"{FormatPaths(missing.Select(file => file.AnalysisFilePath).ToList())}\n\n"
+ "Put the files back at these paths to run it. The saved results can still be browsed.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Information,
Severity = MessageSeverity.Information,
});
return false;
}

public static ErrorMessageBoxRequest SampleNeedsRawData(AnalysisFileBeanModel file) {
return new ErrorMessageBoxRequest
public static MessageRequest SampleNeedsRawData(AnalysisFileBeanModel file) {
return new MessageRequest
{
Caption = RawDataNotAvailableCaption,
Content = $"{file.AnalysisFileName} cannot be opened in the peak spot viewer: for this type of project the viewer reads the raw data, "
+ "and the raw data file was not found:\n"
+ $" {file.AnalysisFilePath}\n\n"
+ "Alignment results can still be browsed.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Information,
Severity = MessageSeverity.Information,
};
}

public static ErrorMessageBoxRequest PeakListMissing(AnalysisFileBeanModel file) {
return new ErrorMessageBoxRequest
public static MessageRequest PeakListMissing(AnalysisFileBeanModel file) {
return new MessageRequest
{
Caption = "Saved results not found",
Content = $"{file.AnalysisFileName} cannot be opened in the peak spot viewer because its saved peak list was not found:\n"
+ $" {file.PeakListFilePath}",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Information,
Severity = MessageSeverity.Information,
};
}

public static ErrorMessageBoxRequest SampleOpenFailed(AnalysisFileBeanModel file, Exception error) {
return new ErrorMessageBoxRequest
public static MessageRequest SampleOpenFailed(AnalysisFileBeanModel file, Exception error) {
return new MessageRequest
{
Caption = "Sample could not be opened",
Content = $"{file.AnalysisFileName} could not be opened in the peak spot viewer.\n\n"
+ $"{error.GetType().Name}: {error.Message}\n\n"
+ "The project is still open. You can select another sample or an alignment result.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Error,
Severity = MessageSeverity.Error,
};
}

public static ErrorMessageBoxRequest AlignmentOpenFailed(AlignmentFileBeanModel file, Exception error) {
return new ErrorMessageBoxRequest
public static MessageRequest AlignmentOpenFailed(AlignmentFileBeanModel file, Exception error) {
return new MessageRequest
{
Caption = "Alignment result could not be opened",
Content = $"{file.FileName} could not be opened in the alignment viewer.\n\n"
+ $"{error.GetType().Name}: {error.Message}\n\n"
+ "The project is still open. You can select a sample or another alignment result.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Error,
Severity = MessageSeverity.Error,
};
}

public static ErrorMessageBoxRequest MsfinderWithoutMs1(AnalysisFileBeanModel file) {
return new ErrorMessageBoxRequest
public static MessageRequest MsfinderWithoutMs1(AnalysisFileBeanModel file) {
return new MessageRequest
{
Caption = RawDataNotAvailableCaption,
Content = $"The raw data file of {file.AnalysisFileName} was not found, so its MS1 isotope pattern cannot be read. "
+ "MS-FINDER receives the MS/MS spectrum only, and its formula ranking may differ from a run with the raw data.\n\n"
+ "Sending the same feature from the alignment result includes the isotope pattern saved with the alignment spot.",
ButtonType = MessageBoxButton.OK,
Icon = MessageBoxImage.Information,
Severity = MessageSeverity.Information,
};
}

Expand Down
29 changes: 21 additions & 8 deletions src/MSDIAL5/MsdialGuiApp/View/Core/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ public MainWindow() {
.Subscribe(OpenFileDialog);
broker.ToObservable<ErrorMessageBoxRequest>()
.Subscribe(ShowErrorConfirmationMessage);
broker.ToObservable<MessageRequest>()
.Subscribe(ShowMessage);
broker.ToObservable<RiDictionarySettingViewModel>()
.Subscribe(ShowRiDictionarySettingDialog);
broker.ToObservable<AlignedChromatogramModificationViewModelLegacy>()
Expand Down Expand Up @@ -341,14 +343,17 @@ private void GetSaveFilePath(SaveFileNameRequest request) {
}

private void OpenFileDialog(OpenFileRequest request) {
var ofd = new OpenFileDialog() {
Title = request.Title,
Filter = request.Filter,
};
Dispatcher.Invoke(() => {
var ofd = new OpenFileDialog()
{
Title = request.Title,
Filter = request.Filter,
};

if (ofd.ShowDialog(this) == true) {
request.Run(ofd.FileName);
}
if (ofd.ShowDialog(this) == true) {
request.Run(ofd.FileName);
}
});
}

private void SelectFolderPath(SelectFolderRequest request) {
Expand All @@ -363,10 +368,18 @@ private void SelectFolderPath(SelectFolderRequest request) {
}

private void ShowErrorConfirmationMessage(ErrorMessageBoxRequest request) {
Dispatcher.Invoke(() => {
var result = MessageBox.Show(request.Content, request.Caption, request.ButtonType, MessageBoxImage.Error);
request.Result = result;
});
}

private void ShowMessage(MessageRequest request) {
Dispatcher.Invoke(() => {
// The wait cursor is left to the operations that set it: the message box draws its own arrow,
// and an operation that finishes while the box is open clears the cursor itself.
request.Result = MessageBox.Show(request.Content, request.Caption, request.ButtonType, request.Icon);
var result = MessageBox.Show(request.Content, request.Caption, MessageRequestBox.ButtonsOf(request), MessageRequestBox.IconOf(request));
request.Result = MessageRequestBox.IsAccepted(result);
});
}

Expand Down
Loading
Loading