456 lines
16 KiB
C#
456 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Forms;
|
|
using System.Windows.Input;
|
|
using PettingZoo.Core.Connection;
|
|
using PettingZoo.Core.ExportImport;
|
|
using PettingZoo.UI.Connection;
|
|
using PettingZoo.UI.Subscribe;
|
|
using PettingZoo.UI.Tab;
|
|
using PettingZoo.UI.Tab.Subscriber;
|
|
using PettingZoo.UI.Tab.Undocked;
|
|
using PettingZoo.WPF.ProgressWindow;
|
|
using PettingZoo.WPF.ViewModel;
|
|
using Serilog;
|
|
using Application = System.Windows.Application;
|
|
using MessageBox = System.Windows.MessageBox;
|
|
|
|
namespace PettingZoo.UI.Main
|
|
{
|
|
public enum ConnectionStatusType
|
|
{
|
|
Connecting,
|
|
Ok,
|
|
Error
|
|
}
|
|
|
|
|
|
public class MainWindowViewModel : BaseViewModel, IAsyncDisposable, ITabHost
|
|
{
|
|
private readonly ILogger logger;
|
|
private readonly IConnectionFactory connectionFactory;
|
|
private readonly IConnectionDialog connectionDialog;
|
|
private readonly ISubscribeDialog subscribeDialog;
|
|
private readonly ITabContainer tabContainer;
|
|
private readonly ITabHostProvider tabHostProvider;
|
|
private readonly ITabFactory tabFactory;
|
|
private readonly IExportImportFormatProvider exportImportFormatProvider;
|
|
|
|
private SubscribeDialogParams? subscribeDialogParams;
|
|
private IConnection? connection;
|
|
private string connectionStatus;
|
|
private ITab? activeTab;
|
|
private readonly Dictionary<ITab, Window> undockedTabs = new();
|
|
|
|
private readonly DelegateCommand connectCommand;
|
|
private readonly DelegateCommand disconnectCommand;
|
|
private readonly DelegateCommand publishCommand;
|
|
private readonly DelegateCommand subscribeCommand;
|
|
private readonly DelegateCommand closeTabCommand;
|
|
private readonly DelegateCommand undockTabCommand;
|
|
private readonly DelegateCommand importCommand;
|
|
|
|
private ConnectionStatusType connectionStatusType;
|
|
|
|
public Window? TabHostWindow { get; set; }
|
|
|
|
|
|
public string ConnectionStatus
|
|
{
|
|
get => connectionStatus;
|
|
private set => SetField(ref connectionStatus, value);
|
|
}
|
|
|
|
|
|
public ConnectionStatusType ConnectionStatusType
|
|
{
|
|
get => connectionStatusType;
|
|
set => SetField(ref connectionStatusType, value, otherPropertiesChanged: new [] { nameof(ConnectionStatusOk), nameof(ConnectionStatusError), nameof(ConnectionStatusConnecting) });
|
|
}
|
|
|
|
public Visibility ConnectionStatusOk => ConnectionStatusType == ConnectionStatusType.Ok ? Visibility.Visible : Visibility.Collapsed;
|
|
public Visibility ConnectionStatusError => ConnectionStatusType == ConnectionStatusType.Error ? Visibility.Visible : Visibility.Collapsed;
|
|
public Visibility ConnectionStatusConnecting => ConnectionStatusType == ConnectionStatusType.Connecting ? Visibility.Visible : Visibility.Collapsed;
|
|
|
|
|
|
public ObservableCollection<ITab> Tabs { get; }
|
|
|
|
public ITab? ActiveTab
|
|
{
|
|
get => activeTab;
|
|
set
|
|
{
|
|
var currentTab = activeTab;
|
|
|
|
if (!SetField(ref activeTab, value, otherPropertiesChanged: new[] { nameof(ToolbarCommands), nameof(ToolbarCommandsSeparatorVisibility) }))
|
|
return;
|
|
|
|
(currentTab as ITabActivate)?.Deactivate();
|
|
(activeTab as ITabActivate)?.Activate();
|
|
}
|
|
}
|
|
|
|
public ICommand ConnectCommand => connectCommand;
|
|
public ICommand DisconnectCommand => disconnectCommand;
|
|
public ICommand PublishCommand => publishCommand;
|
|
public ICommand SubscribeCommand => subscribeCommand;
|
|
public ICommand CloseTabCommand => closeTabCommand;
|
|
public ICommand UndockTabCommand => undockTabCommand;
|
|
public ICommand ImportCommand => importCommand;
|
|
|
|
public IEnumerable<TabToolbarCommand> ToolbarCommands => ActiveTab is ITabToolbarCommands tabToolbarCommands
|
|
? tabToolbarCommands.ToolbarCommands
|
|
: Enumerable.Empty<TabToolbarCommand>();
|
|
|
|
public Visibility ToolbarCommandsSeparatorVisibility =>
|
|
ToolbarCommands.Any() ? Visibility.Visible : Visibility.Collapsed;
|
|
|
|
public Visibility NoTabsVisibility =>
|
|
Tabs.Count > 0 ? Visibility.Collapsed : Visibility.Visible;
|
|
|
|
|
|
public MainWindowViewModel(ILogger logger, IConnectionFactory connectionFactory, IConnectionDialog connectionDialog,
|
|
ISubscribeDialog subscribeDialog, ITabContainer tabContainer, ITabHostProvider tabHostProvider, ITabFactory tabFactory,
|
|
IExportImportFormatProvider exportImportFormatProvider)
|
|
{
|
|
tabHostProvider.SetInstance(this);
|
|
|
|
this.logger = logger;
|
|
this.connectionFactory = connectionFactory;
|
|
this.connectionDialog = connectionDialog;
|
|
this.subscribeDialog = subscribeDialog;
|
|
this.tabContainer = tabContainer;
|
|
this.tabHostProvider = tabHostProvider;
|
|
this.tabFactory = tabFactory;
|
|
this.exportImportFormatProvider = exportImportFormatProvider;
|
|
|
|
connectionStatus = GetConnectionStatus(null);
|
|
connectionStatusType = ConnectionStatusType.Error;
|
|
|
|
Tabs = new ObservableCollection<ITab>();
|
|
connectCommand = new DelegateCommand(ConnectExecute);
|
|
disconnectCommand = new DelegateCommand(DisconnectExecute, IsConnectedCanExecute);
|
|
publishCommand = new DelegateCommand(PublishExecute, IsConnectedCanExecute);
|
|
subscribeCommand = new DelegateCommand(SubscribeExecute, IsConnectedCanExecute);
|
|
closeTabCommand = new DelegateCommand(CloseTabExecute, HasActiveTabCanExecute);
|
|
undockTabCommand = new DelegateCommand(UndockTabExecute, HasActiveTabCanExecute);
|
|
importCommand = new DelegateCommand(ImportExecute);
|
|
}
|
|
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
GC.SuppressFinalize(this);
|
|
|
|
if (connection != null)
|
|
await connection.DisposeAsync();
|
|
}
|
|
|
|
|
|
private async void ConnectExecute()
|
|
{
|
|
var connectionSettings = await connectionDialog.Show();
|
|
if (connectionSettings == null)
|
|
return;
|
|
|
|
if (connection != null)
|
|
await connection.DisposeAsync();
|
|
|
|
connection = connectionFactory.CreateConnection(new ConnectionParams(
|
|
connectionSettings.Host, connectionSettings.VirtualHost, connectionSettings.Port,
|
|
connectionSettings.Username, connectionSettings.Password));
|
|
connection.StatusChanged += ConnectionStatusChanged;
|
|
|
|
if (connectionSettings.Subscribe)
|
|
{
|
|
var subscriber = connection.Subscribe(connectionSettings.Exchange, connectionSettings.RoutingKey);
|
|
tabFactory.CreateSubscriberTab(connection, subscriber);
|
|
}
|
|
|
|
ConnectionChanged();
|
|
}
|
|
|
|
|
|
private async void DisconnectExecute()
|
|
{
|
|
Tabs.Clear();
|
|
|
|
var capturedUndockedTabs = undockedTabs.ToList();
|
|
undockedTabs.Clear();
|
|
|
|
foreach (var undockedTab in capturedUndockedTabs)
|
|
undockedTab.Value.Close();
|
|
|
|
RaisePropertyChanged(nameof(NoTabsVisibility));
|
|
undockTabCommand.RaiseCanExecuteChanged();
|
|
|
|
if (connection != null)
|
|
{
|
|
await connection.DisposeAsync();
|
|
connection = null;
|
|
}
|
|
|
|
ConnectionStatus = GetConnectionStatus(null);
|
|
ConnectionStatusType = ConnectionStatusType.Error;
|
|
ConnectionChanged();
|
|
}
|
|
|
|
|
|
private void SubscribeExecute()
|
|
{
|
|
if (connection == null)
|
|
return;
|
|
|
|
var newParams = subscribeDialog.Show(subscribeDialogParams);
|
|
if (newParams == null)
|
|
return;
|
|
|
|
subscribeDialogParams = newParams;
|
|
|
|
var subscriber = connection.Subscribe(subscribeDialogParams.Exchange, subscribeDialogParams.RoutingKey);
|
|
tabFactory.CreateSubscriberTab(connection, subscriber);
|
|
}
|
|
|
|
|
|
private void PublishExecute()
|
|
{
|
|
if (connection == null)
|
|
return;
|
|
|
|
tabFactory.CreatePublisherTab(connection);
|
|
}
|
|
|
|
|
|
private bool IsConnectedCanExecute()
|
|
{
|
|
return connection != null;
|
|
}
|
|
|
|
|
|
private void CloseTabExecute()
|
|
{
|
|
var tab = RemoveActiveTab();
|
|
(tab as IDisposable)?.Dispose();
|
|
}
|
|
|
|
|
|
private void UndockTabExecute()
|
|
{
|
|
var tab = RemoveActiveTab();
|
|
if (tab == null)
|
|
return;
|
|
|
|
var tabHostWindow = UndockedTabHostWindow.Create(tabHostProvider, tab, tabContainer.TabWidth, tabContainer.TabHeight);
|
|
undockedTabs.Add(tab, tabHostWindow);
|
|
|
|
tabHostWindow.Show();
|
|
(tab as ITabHostWindowNotify)?.HostWindowChanged(tabHostWindow);
|
|
}
|
|
|
|
|
|
private void ImportExecute()
|
|
{
|
|
var formats = exportImportFormatProvider.ImportFormats.ToArray();
|
|
|
|
var dialog = new OpenFileDialog
|
|
{
|
|
Filter = string.Join('|', formats.Select(f => f.Filter))
|
|
};
|
|
|
|
if (dialog.ShowDialog() != DialogResult.OK)
|
|
return;
|
|
|
|
if (dialog.FilterIndex <= 0 || dialog.FilterIndex > formats.Length)
|
|
return;
|
|
|
|
var filename = dialog.FileName;
|
|
var format = formats[dialog.FilterIndex - 1];
|
|
|
|
var progressWindow = new ProgressWindow(MainWindowStrings.ImportProgressWindowTitle)
|
|
{
|
|
Owner = TabHostWindow,
|
|
WindowStartupLocation = WindowStartupLocation.CenterOwner
|
|
};
|
|
progressWindow.Show();
|
|
|
|
|
|
Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
IReadOnlyList<ReceivedMessageInfo> messages;
|
|
await using (var importFile =
|
|
new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
|
{
|
|
messages = await format.Import(
|
|
new StreamProgressDecorator(importFile, progressWindow).Stream,
|
|
progressWindow.CancellationToken);
|
|
}
|
|
|
|
if (progressWindow.CancellationToken.IsCancellationRequested)
|
|
return;
|
|
|
|
await Application.Current.Dispatcher.BeginInvoke(() =>
|
|
{
|
|
progressWindow.Close();
|
|
progressWindow = null;
|
|
|
|
tabFactory.CreateSubscriberTab(connection, new ImportSubscriber(filename, messages));
|
|
});
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
// User cancelled
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
logger.Error(e, "Error while importing messages");
|
|
|
|
await Application.Current.Dispatcher.BeginInvoke(() =>
|
|
{
|
|
progressWindow?.Close();
|
|
progressWindow = null;
|
|
|
|
MessageBox.Show(string.Format(SubscriberViewStrings.ExportError, e.Message),
|
|
SubscriberViewStrings.ExportResultTitle,
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
if (progressWindow != null)
|
|
await Application.Current.Dispatcher.BeginInvoke(() =>
|
|
{
|
|
progressWindow.Close();
|
|
});
|
|
}
|
|
}, CancellationToken.None);
|
|
}
|
|
|
|
|
|
private ITab? RemoveActiveTab()
|
|
{
|
|
if (ActiveTab == null)
|
|
return null;
|
|
|
|
var activeTabIndex = Tabs.IndexOf(ActiveTab);
|
|
if (activeTabIndex == -1)
|
|
return null;
|
|
|
|
var tab = Tabs[activeTabIndex];
|
|
Tabs.RemoveAt(activeTabIndex);
|
|
|
|
if (activeTabIndex == Tabs.Count)
|
|
activeTabIndex--;
|
|
|
|
ActiveTab = activeTabIndex >= 0 ? Tabs[activeTabIndex] : null;
|
|
closeTabCommand.RaiseCanExecuteChanged();
|
|
undockTabCommand.RaiseCanExecuteChanged();
|
|
RaisePropertyChanged(nameof(NoTabsVisibility));
|
|
|
|
return tab;
|
|
}
|
|
|
|
|
|
private bool HasActiveTabCanExecute()
|
|
{
|
|
return ActiveTab != null;
|
|
}
|
|
|
|
|
|
public void AddTab(ITab tab)
|
|
{
|
|
Tabs.Add(tab);
|
|
ActiveTab = tab;
|
|
|
|
(tab as ITabHostWindowNotify)?.HostWindowChanged(TabHostWindow);
|
|
|
|
closeTabCommand.RaiseCanExecuteChanged();
|
|
undockTabCommand.RaiseCanExecuteChanged();
|
|
RaisePropertyChanged(nameof(NoTabsVisibility));
|
|
}
|
|
|
|
|
|
public void ActivateTab(ITab tab)
|
|
{
|
|
if (undockedTabs.TryGetValue(tab, out var window))
|
|
window.Activate();
|
|
else if (Tabs.Contains(tab))
|
|
ActiveTab = tab;
|
|
}
|
|
|
|
|
|
public void DockTab(ITab tab)
|
|
{
|
|
if (undockedTabs.Remove(tab, out var tabHostWindow))
|
|
tabHostWindow.Close();
|
|
|
|
AddTab(tab);
|
|
ActiveTab = tab;
|
|
|
|
(tab as ITabHostWindowNotify)?.HostWindowChanged(TabHostWindow);
|
|
}
|
|
|
|
public void UndockedTabClosed(ITab tab)
|
|
{
|
|
undockedTabs.Remove(tab);
|
|
}
|
|
|
|
|
|
private void ConnectionChanged()
|
|
{
|
|
disconnectCommand.RaiseCanExecuteChanged();
|
|
subscribeCommand.RaiseCanExecuteChanged();
|
|
publishCommand.RaiseCanExecuteChanged();
|
|
}
|
|
|
|
private void ConnectionStatusChanged(object? sender, StatusChangedEventArgs args)
|
|
{
|
|
ConnectionStatus = GetConnectionStatus(args);
|
|
ConnectionStatusType = args.Status switch
|
|
{
|
|
Core.Connection.ConnectionStatus.Connected => ConnectionStatusType.Ok,
|
|
Core.Connection.ConnectionStatus.Connecting => ConnectionStatusType.Connecting,
|
|
_ => ConnectionStatusType.Error
|
|
};
|
|
}
|
|
|
|
|
|
|
|
private static string GetConnectionStatus(StatusChangedEventArgs? args)
|
|
{
|
|
return args?.Status switch
|
|
{
|
|
Core.Connection.ConnectionStatus.Connecting => string.Format(MainWindowStrings.StatusConnecting, args.Context),
|
|
Core.Connection.ConnectionStatus.Connected => string.Format(MainWindowStrings.StatusConnected, args.Context),
|
|
Core.Connection.ConnectionStatus.Error => string.Format(MainWindowStrings.StatusError, args.Context),
|
|
Core.Connection.ConnectionStatus.Disconnected => MainWindowStrings.StatusDisconnected,
|
|
_ => MainWindowStrings.StatusDisconnected
|
|
};
|
|
}
|
|
}
|
|
|
|
|
|
public class DesignTimeMainWindowViewModel : MainWindowViewModel
|
|
{
|
|
public DesignTimeMainWindowViewModel() : base(null!, null!, null!, null!, null!, new DesignTimeTabHostProvider(), null!, null!)
|
|
{
|
|
}
|
|
|
|
|
|
private class DesignTimeTabHostProvider : ITabHostProvider
|
|
{
|
|
public ITabHost Instance => null!;
|
|
|
|
public void SetInstance(ITabHost instance)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
} |