using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core.Commands; using Tango.FSE.Common; using Tango.FSE.Common.Diagnostics; using Tango.FSE.Common.FileAssociation; using Tango.FSE.Common.Notifications; using Tango.FSE.Diagnostics.Project; using Tango.FSE.Diagnostics.Views; using Tango.PMR.Diagnostics; namespace Tango.FSE.Diagnostics.ViewModels { public class DiagnosticsViewVM : FSEViewModelWithModuleSettings { public class WidgetType { public String Name { get; set; } public Type Type { get; set; } public override string ToString() { return Name; } } private bool _isLoaded; private string _factoryPojectFile; private string _customProjectFile; private List _copiedWidget; private const string PROJECT_EXTENSION = ".tdp"; private const string FACTORY_PROJECT_NAME = "diagnostics" + PROJECT_EXTENSION; #region Properties private bool _isLoadingProject; public bool IsLoadingProject { get { return _isLoadingProject; } set { _isLoadingProject = value; RaisePropertyChangedAuto(); } } private DiagnosticsProject _project; public DiagnosticsProject Project { get { return _project; } set { _project = value; RaisePropertyChangedAuto(); } } private ObservableCollection _tabs; public ObservableCollection Tabs { get { return _tabs; } set { _tabs = value; RaisePropertyChangedAuto(); } } private DiagnosticsTabViewVM _selectedTab; public DiagnosticsTabViewVM SelectedTab { get { return _selectedTab; } set { _selectedTab = value; RaisePropertyChangedAuto(); OnSelectedTabChanged(); } } public List AvailableWidgetTypes { get; set; } private WidgetType _selectedWidgetType; public WidgetType SelectedWidgetType { get { return _selectedWidgetType; } set { _selectedWidgetType = value; RaisePropertyChangedAuto(); } } private bool _editMode; public bool EditMode { get { return _editMode; } set { _editMode = value; RaisePropertyChangedAuto(); OnEditModeChanged(); } } private bool _creationMode; public bool CreationMode { get { return _creationMode; } set { _creationMode = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(NotInCreationOrPasteMode)); InvalidateRelayCommands(); } } private bool _pasteMode; public bool PasteMode { get { return _pasteMode; } set { _pasteMode = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(NotInCreationOrPasteMode)); InvalidateRelayCommands(); } } public bool NotInCreationOrPasteMode { get { return !CreationMode && !PasteMode; } } private bool _showGridLines; public bool ShowGridLines { get { return _showGridLines; } set { _showGridLines = value; RaisePropertyChangedAuto(); Tabs.ToList().ForEach(x => x.ShowGridLines = value); } } #endregion #region Commands public RelayCommand NewProjectCommand { get; set; } public RelayCommand OpenProjectCommand { get; set; } public RelayCommand SaveProjectCommand { get; set; } public RelayCommand SaveAsProjectCommand { get; set; } public RelayCommand CutSelectedWidgetsCommand { get; set; } public RelayCommand CopySelectedWidgetsCommand { get; set; } public RelayCommand PasteWidgetsCommand { get; set; } public RelayCommand DeleteSelectedWidgetsCommand { get; set; } public RelayCommand AddNewTabCommand { get; set; } public RelayCommand RemoveTabCommand { get; set; } public RelayCommand AddWidgetCommand { get; set; } public RelayCommand SelectionCommand { get; set; } public RelayCommand AbortCreationCommand { get; set; } public RelayCommand DeselectWidgetsCommand { get; set; } public RelayCommand ResetGridCommand { get; set; } public RelayCommand ResetToFactoryProjectCommand { get; set; } public RelayCommand MoveSelectedWidgetsRightCommand { get; set; } public RelayCommand MoveSelectedWidgetsLeftCommand { get; set; } public RelayCommand MoveSelectedWidgetsDownCommand { get; set; } public RelayCommand MoveSelectedWidgetsUpCommand { get; set; } public RelayCommand ScaleSelectedWidgetsRightCommand { get; set; } public RelayCommand ScaleSelectedWidgetsLeftCommand { get; set; } public RelayCommand ScaleSelectedWidgetsDownCommand { get; set; } public RelayCommand ScaleSelectedWidgetsUpCommand { get; set; } #endregion #region Constructors public DiagnosticsViewVM() { Tabs = new ObservableCollection(); Project = new DiagnosticsProject(); AvailableWidgetTypes = new List(); _copiedWidget = new List(); OpenProjectCommand = new RelayCommand(OpenProject, () => EditMode); SaveProjectCommand = new RelayCommand(SaveProject, () => EditMode); SaveAsProjectCommand = new RelayCommand(SaveAsProject, () => EditMode); NewProjectCommand = new RelayCommand(CreateNewProject, () => EditMode); AddNewTabCommand = new RelayCommand(AddNewTab, () => EditMode); RemoveTabCommand = new RelayCommand(RemoveTab, () => EditMode); AddWidgetCommand = new RelayCommand(StartWidgetCreation, () => EditMode && SelectedTab != null); SelectionCommand = new RelayCommand(OnWidgetCreation, () => EditMode && ((CreationMode && SelectedWidgetType != null) || PasteMode) && SelectedTab != null); AbortCreationCommand = new RelayCommand(AbortWidgetCreation, () => EditMode && (CreationMode || PasteMode)); DeselectWidgetsCommand = new RelayCommand(DeselectAllWidgets, () => EditMode); CopySelectedWidgetsCommand = new RelayCommand(CopySelectedWidgets, () => EditMode && SelectedTab != null && SelectedTab.Tab.Widgets.Any(x => x.IsSelected)); PasteWidgetsCommand = new RelayCommand(StartPasteWidgets, () => EditMode && !PasteMode && !CreationMode && _copiedWidget.Count > 0); ResetGridCommand = new RelayCommand(ResetSelectedTabGrid, () => EditMode && SelectedTab != null); DeleteSelectedWidgetsCommand = new RelayCommand(DeleteSelectedWidgets, () => EditMode && SelectedTab != null && SelectedTab.Tab.Widgets.Any(x => x.IsSelected)); CutSelectedWidgetsCommand = new RelayCommand(CutSelectedWidgets, () => EditMode && SelectedTab != null && SelectedTab.Tab.Widgets.Any(x => x.IsSelected)); ResetToFactoryProjectCommand = new RelayCommand(ResetToFactoryProject, () => EditMode); MoveSelectedWidgetsRightCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Right), () => EditMode); MoveSelectedWidgetsLeftCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Left), () => EditMode); MoveSelectedWidgetsUpCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Top), () => EditMode); MoveSelectedWidgetsDownCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Bottom), () => EditMode); ScaleSelectedWidgetsRightCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Right), () => EditMode); ScaleSelectedWidgetsLeftCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Left), () => EditMode); ScaleSelectedWidgetsUpCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Top), () => EditMode); ScaleSelectedWidgetsDownCommand = new RelayCommand(() => MoveSelectedWidgets(Dock.Bottom), () => EditMode); InitAvailableWidgetTypes(); } #endregion #region Widget Management private void InitAvailableWidgetTypes() { var types = this.GetType().Assembly.GetTypes().Where(x => typeof(DiagnosticsWidget).IsAssignableFrom(x) && !x.IsAbstract).ToList(); foreach (var type in types.OrderBy(x => x.Name).ToList()) { var att = type.GetCustomAttribute(); AvailableWidgetTypes.Add(new WidgetType() { Name = att != null ? att.Description : type.Name.Replace("Widget", ""), Type = type }); } } private void StartWidgetCreation(WidgetType widgetType) { if (SelectedTab == null || PasteMode) return; DeselectAllWidgets(); SelectedWidgetType = widgetType; CreationMode = true; } private void AbortWidgetCreation() { CreationMode = false; PasteMode = false; SelectedWidgetType = null; } private async void OnWidgetCreation(Rect rect) { if (CreationMode) { CreationMode = false; if (SelectedTab == null || SelectedWidgetType == null) return; DiagnosticsWidget widget = Activator.CreateInstance(SelectedWidgetType.Type) as DiagnosticsWidget; widget.Column = (int)rect.X; widget.Row = (int)rect.Y; widget.ColumnSpan = (int)rect.Width; widget.RowSpan = (int)rect.Height; await AddWidget(SelectedTab, widget); SelectedWidgetType = null; } else if (PasteMode && _copiedWidget.Count > 0) { PasteMode = false; if (SelectedTab == null) return; var startLeft = _copiedWidget.Min(x => x.Column); var startTop = _copiedWidget.Min(x => x.Row); var offsetX = rect.Left - startLeft; var offsetY = rect.Top - startTop; foreach (var widget in _copiedWidget.Select(x => x.Clone()).ToList()) { widget.Column += (int)offsetX; widget.Row += (int)offsetY; await AddWidget(SelectedTab, widget); } } } private async Task AddWidget(DiagnosticsTabViewVM tab, DiagnosticsWidget widget) { widget.IsVisible = tab.IsVisible; widget.EditMode = EditMode; await widget.Init(); tab.Tab.Widgets.Add(widget); } private Rect? GetFirstAvailableTabSpace(DiagnosticsProjectTab tab, int columnSpan, int rowSpan) { int column = 0; int row = 0; List board = tab.Widgets.Select(x => new Rect(x.Column, x.Row, Math.Max(x.ColumnSpan, 1), Math.Max(x.RowSpan, 1))).ToList(); bool found = false; //Search for first available space by intersection. for (int rowIndex = 0; rowIndex < tab.Rows.Count; rowIndex++) { for (int columnIndex = 0; columnIndex < tab.Columns.Count; columnIndex++) { Rect rect = new Rect(columnIndex, rowIndex, columnSpan, rowSpan); if (board.Any(x => { var intersect = Rect.Intersect(x, rect); return (intersect.Width > 0 && intersect.Height > 0); })) { continue; } column = columnIndex; row = rowIndex; found = true; break; } if (found) break; } if (found) { return new Rect(column, row, columnSpan, rowSpan); } return null; } private void DisposeWidgets(IEnumerable widgets) { foreach (var widget in widgets.ToList()) { try { widget.Dispose(); } catch (Exception ex) { LogManager.Log(ex, $"Error disposing widget {widget.DisplayName}, {widget.ID}."); } } } private void DeleteSelectedWidgets() { if (SelectedTab != null && SelectedTab.Tab.Widgets.Count(x => x.IsSelected) > 0) { var selectedWidgets = SelectedTab.Tab.Widgets.Where(x => x.IsSelected).ToList(); foreach (var widget in selectedWidgets) { SelectedTab.Tab.Widgets.Remove(widget); widget.Dispose(); } } } private void CutSelectedWidgets() { CopySelectedWidgets(); DeleteSelectedWidgets(); } private void MoveSelectedWidgets(Dock direction) { if (SelectedTab != null) { var selectedWidgets = SelectedTab.Tab.Widgets.Where(x => x.IsSelected).ToList(); if (Keyboard.IsKeyDown(Key.LeftCtrl)) { switch (direction) { case Dock.Right: selectedWidgets.ForEach(x => x.ColumnSpan += 1); break; case Dock.Left: selectedWidgets.ForEach(x => x.ColumnSpan -= 1); break; case Dock.Top: selectedWidgets.ForEach(x => x.RowSpan -= 1); break; case Dock.Bottom: selectedWidgets.ForEach(x => x.RowSpan += 1); break; } } else { switch (direction) { case Dock.Right: selectedWidgets.ForEach(x => x.Column += 1); break; case Dock.Left: selectedWidgets.ForEach(x => x.Column -= 1); break; case Dock.Top: selectedWidgets.ForEach(x => x.Row -= 1); break; case Dock.Bottom: selectedWidgets.ForEach(x => x.Row += 1); break; } } } } #endregion #region Override Methods public override void OnApplicationStarted() { base.OnApplicationStarted(); DiagnosticsProvider.FrameReceived += DiagnosticsProvider_FrameReceived; FileAssociationProvider.RegisterFileAssociationHandler("diagnostics", HandleDiagnosticsFileAssociation); } public async override void OnApplicationReady() { base.OnApplicationReady(); if (!_isLoaded) { _factoryPojectFile = Path.Combine(ApplicationManager.StartPath, FACTORY_PROJECT_NAME); if (CurrentUser.HasPermission(Permissions.FSE_EditDiagnosticsProject) && ModuleSettings.CustomProjectPath != null && File.Exists(ModuleSettings.CustomProjectPath)) { await LoadProject(ModuleSettings.CustomProjectPath); } else { await LoadFactoryProject(); } } } public override void OnApplicationShuttingDown() { base.OnApplicationShuttingDown(); SaveUserSettings(); } public override void OnNavigatedTo() { base.OnNavigatedTo(); Tabs.ToList().ForEach(x => x.IsVisible = IsVisible); } public override void OnNavigatedFrom() { base.OnNavigatedFrom(); Tabs.ToList().ForEach(x => x.IsVisible = IsVisible); } #endregion #region Diagnostics Frame Received private void DiagnosticsProvider_FrameReceived(object sender, DiagnosticsFrameReceivedEventArgs e) { PopulateDiagnosticsData(e.Frame); } #endregion #region Project Management private async void ResetToFactoryProject() { if (await NotificationProvider.ShowWarningQuestion("Are you sure you want to discard the current project and open the factory project?")) { await LoadFactoryProject(); _customProjectFile = null; ModuleSettings.CustomProjectPath = null; ModuleSettings.Save(); } } private async void OpenProject() { var result = await StorageProvider.OpenFile("Open diagnostics project", $"Diagnostics Projects|*{PROJECT_EXTENSION}"); if (result.Confirmed) { try { await LoadProject(result.SelectedItem, true); _customProjectFile = result.SelectedItem; ModuleSettings.CustomProjectPath = _customProjectFile; ModuleSettings.Save(); await NotificationProvider.ShowSuccess("Diagnostics project loaded successfully.\nThis project will be loaded automatically the next time you open the application.\nTo cancel this behavior, use the 'Reset To Factory' option in the file menu."); } catch (Exception ex) { await NotificationProvider.ShowError($"Error opening diagnostics project.\n{ex.FlattenMessage()}"); } } } private Task LoadFactoryProject() { return LoadProject(_factoryPojectFile); } private async Task LoadProject(String filePath, bool throwException = false) { try { IsLoadingProject = true; _isLoaded = false; if (Project != null) { DisposeWidgets(Project.FlattenWidgets()); } Project = DiagnosticsProject.FromFile(filePath); await Services.TechComponentsService.Preload(); foreach (var widget in Project.Tabs.SelectMany(x => x.Widgets)) { try { if (widget is DiagnosticsConfigurableWidget) { await Task.Factory.StartNew(() => { DiagnosticsUserSettingsManager.Default.Settings.ApplyWidgetSettings(widget as DiagnosticsConfigurableWidget); }); } await widget.Init(); } catch (Exception ex) { LogManager.Log(ex, $"Error initializing diagnostics widget with ID '{widget.ID}'."); NotificationProvider.PushErrorReportingSnackbar(ex, "Diagnostics Module Error", "Error initializing diagnostics widget."); } } Tabs = new ObservableCollection(); foreach (var tab in Project.Tabs) { Tabs.Add(new DiagnosticsTabViewVM() { Tab = tab, IsVisible = IsVisible }); } SelectedTab = Tabs.FirstOrDefault(); EditMode = EditMode; _isLoaded = true; } catch (Exception ex) { NotificationProvider.PushErrorReportingSnackbar(ex, "Diagnostics Module Error", "Error initializing diagnostics module."); if (throwException) { throw ex; } } finally { IsLoadingProject = false; } } private async void SaveAsProject() { var result = await StorageProvider.SaveFile("Save diagnostics project", $"Diagnostics Projects|*{PROJECT_EXTENSION}", FACTORY_PROJECT_NAME, PROJECT_EXTENSION); if (result.Confirmed) { SaveProject(result.SelectedItem); } } private void SaveProject() { if (_customProjectFile != null) { SaveProject(_customProjectFile); } else { SaveAsProject(); } } private void SaveProject(String filePath, bool throwException = false) { try { Project.ToFile(filePath); SaveUserSettings(); _customProjectFile = filePath; ModuleSettings.CustomProjectPath = _customProjectFile; ModuleSettings.Save(); NotificationProvider.PushSnackbarItem(MessageType.Success, "Diagnostics Project Saved", true, "Diagnostics project saved successfully and will be loaded automatically on next application startup.", TimeSpan.FromSeconds(3), null, () => { StorageProvider.ShowInExplorer(filePath); }); } catch (Exception ex) { LogManager.Log(ex, "Error saving diagnostics project."); NotificationProvider.ShowError($"Error saving diagnostics project.\n{ex.FlattenMessage()}"); if (throwException) { throw ex; } } } private async void CreateNewProject() { if (!await NotificationProvider.ShowWarningQuestion("Are you sure you want to create a new project?")) return; if (Project != null) { DisposeWidgets(Project.FlattenWidgets()); } Project = new DiagnosticsProject(); Project.Tabs.Add(DiagnosticsProjectTab.CreateNew("untitled", 12, 12)); Tabs = new ObservableCollection(); foreach (var tab in Project.Tabs) { Tabs.Add(new DiagnosticsTabViewVM() { Tab = tab }); } SelectedTab = Tabs.FirstOrDefault(); } private void SaveUserSettings() { if (EditMode) return; try { foreach (var widget in Project.FlattenWidgets()) { try { if (widget is DiagnosticsConfigurableWidget) { DiagnosticsUserSettingsManager.Default.Settings.SetWidgetSettings(widget as DiagnosticsConfigurableWidget); } } catch (Exception ex) { LogManager.Log(ex, $"Error saving widget user settings for widget '{widget.DisplayName}'."); } } //If you are going to work with different projects in the future you need to remote this. //Because it will erase any settings other than for the current project. DiagnosticsUserSettingsManager.Default.ClearGhostRecords(Project.FlattenWidgets().OfType().ToList()); DiagnosticsUserSettingsManager.Default.Save(); } catch (Exception ex) { LogManager.Log(ex, "Error saving diagnostics user settings collection."); } } #endregion #region Tab Management private void ResetSelectedTabGrid() { if (SelectedTab != null) { foreach (var column in SelectedTab.Tab.Columns) { column.Width = new GridLength(1, GridUnitType.Star); } foreach (var row in SelectedTab.Tab.Rows) { row.Height = new GridLength(1, GridUnitType.Star); } } } private void OnSelectedTabChanged() { } private void AddNewTab() { var tab = DiagnosticsProjectTab.CreateNew("untitled", 12, 12); var tabVM = new DiagnosticsTabViewVM() { Tab = tab }; Project.Tabs.Add(tab); Tabs.Add(tabVM); tabVM.ShowGridLines = ShowGridLines; tabVM.EditMode = EditMode; SelectedTab = tabVM; SelectedTab.IsVisible = IsVisible; } private async void RemoveTab(DiagnosticsTabViewVM tabVM) { if (await NotificationProvider.ShowWarningQuestion("Are you sure you want to remove this tab?")) { var index = Tabs.IndexOf(tabVM); Tabs.Remove(tabVM); Project.Tabs.Remove(tabVM.Tab); DisposeWidgets(tabVM.Tab.Widgets); if (Tabs.Count > index) { SelectedTab = Tabs[index]; } else { SelectedTab = null; } } } private void DeselectAllWidgets() { Tabs.ToList().ForEach(x => { x.SelectedWidget = null; x.IsWidgetSettingsOpened = false; }); } #endregion #region Populate Diagnostics Data private void PopulateDiagnosticsData(DiagnosticsFrame frame) { if (_isLoaded) { foreach (var tab in Tabs.ToList()) { tab.PopulateDiagnosticsData(frame.ToPackage()); } } } #endregion #region Properties Change private void OnEditModeChanged() { Tabs.ToList().ForEach(x => x.EditMode = EditMode); InvalidateRelayCommands(); } #endregion #region Cut / Copy /Paste private void CopySelectedWidgets() { if (SelectedTab != null) { _copiedWidget.Clear(); var selectedWidgets = SelectedTab.Tab.Widgets.Where(x => x.IsSelected).ToList(); _copiedWidget.AddRange(selectedWidgets.Select(x => x.Clone())); InvalidateRelayCommands(); NotificationProvider.PushSnackbarItem(MessageType.Info, "Diagnostics Widgets Copied", true, $"{_copiedWidget.Count} diagnostics widgets copied.", TimeSpan.FromSeconds(1.5)); } } private void StartPasteWidgets() { if (SelectedTab == null || CreationMode) return; DeselectAllWidgets(); PasteMode = true; } #endregion #region File Association private async void HandleDiagnosticsFileAssociation(FileAssociationPackage package) { if (!CurrentUser.HasPermission(Permissions.FSE_EditDiagnosticsProject)) { await NotificationProvider.ShowError("Current user profile does not allow loading custom diagnostics projects."); return; } if (File.Exists(package.File)) { try { LogManager.Log("Opening diagnostics project from file association..."); await NavigationManager.NavigateTo(true, nameof(DiagnosticsView)); using (NotificationProvider.PushTaskItem("Loading diagnostics project...")) { await Task.Delay(2000); await LoadProject(package.File); } } catch (Exception ex) { LogManager.Log(ex, "Error occurred while trying to handle the diagnostics file association."); } } } #endregion } }