using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.Core;
using Tango.Core.Commands;
using Tango.Core.DI;
using Tango.MachineStudio.Common;
using Tango.MachineStudio.Common.Modules;
using Tango.MachineStudio.Common.Navigation;
using Tango.MachineStudio.Common.Threading;
using Tango.SharedUI.Controls;
namespace Tango.MachineStudio.UI.Navigation
{
/// <summary>
/// Represents the Machine Studio default <see cref="INavigationManager">Navigation Manager</see>.
/// </summary>
/// <seealso cref="Tango.MachineStudio.Common.Navigation.INavigationManager" />
public class DefaultNavigationManager : ExtendedObject, INavigationManager
{
private event Action<Object, Object> NavigationCycleCompleted;
private IDispatcherProvider _dispatcherProvider;
private IStudioModuleLoader _moduleLoader;
private Object _currentVM;
private String _lastFullPath;
private bool _preventHistory;
private bool _navigating_back;
private Stack<String> _navigationHistory;
/// <summary>
/// Gets the current view model.
/// </summary>
public StudioViewModel CurrentVM
{
get { return _currentVM as StudioViewModel; }
}
private IStudioModule _currentModule;
/// <summary>
/// Gets or sets the current module.
/// </summary>
public IStudioModule CurrentModule
{
get { return _currentModule; }
private set { _currentModule = value; RaisePropertyChangedAuto(); }
}
/// <summary>
/// Navigates to the previous view.
/// </summary>
public RelayCommand NavigateBackCommand { get; private set; }
/// <summary>
/// Navigates to the specified full path in command parameter.
/// </summary>
public RelayCommand<String> NavigateToCommand { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="DefaultNavigationManager"/> class.
/// </summary>
/// <param name="moduleLoader">The module loader.</param>
public DefaultNavigationManager(IStudioModuleLoader moduleLoader, IDispatcherProvider dispatcherProvider)
{
_navigationHistory = new Stack<String>();
_moduleLoader = moduleLoader;
NavigateToCommand = new RelayCommand<string>(async (x) => await NavigateTo(x));
NavigateBackCommand = new RelayCommand(async () => await NavigateBack());
_dispatcherProvider = dispatcherProvider;
}
/// <summary>
/// Navigates to the specified PPC view.
/// </summary>
/// <param name="view">The view.</param>
public Task<bool> NavigateTo(NavigationView view, bool pushToHistory = true)
{
LogManager.Log($"Navigating to: {view.ToString()}...");
_dispatcherProvider.Invoke(() =>
{
MainWindow.Instance.NavigationControl.NavigateTo(view.ToString());
});
return Task.FromResult(true);
}
/// <summary>
/// Navigates to the specified PPC view with the specified receive object.
/// </summary>
/// <param name="view">The view.</param>
/// <param name="obj"></param>
/// <param name="pushToHistory"></param>
/// <returns></returns>
public Task<bool> NavigateWithObject<TPass>(NavigationView view, TPass obj, bool pushToHistory = true)
{
LogManager.Log($"Navigating to: {view.ToString()}, with object {typeof(TPass).Name}...");
MainWindow.Instance.NavigationControl.NavigateTo(view.ToString());
INavigationObjectReceiver<TPass> receiver = MainWindow.Instance.NavigationControl.Elements.FirstOrDefault(x => (x.GetType().Name == view.ToString() || NavigationControl.GetNavigationName(x) == view.ToString()) && x.DataContext is INavigationObjectReceiver<TPass>).DataContext as INavigationObjectReceiver<TPass>;
if (receiver != null)
{
receiver.OnNavigatedToWithObject(obj);
}
return Task.FromResult(true);
}
/// <summary>
/// Navigates to the specified module.
/// </summary>
/// <typeparam name="T"></typeparam>
public Task<bool> NavigateTo<T>(bool pushToHistory = true) where T : IStudioModule
{
return NavigateTo(typeof(T));
}
/// <summary>
/// Navigates to the specified module using the view path (e.g MainView.JobsView).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewPath">The view path.</param>
public Task<bool> NavigateTo<T>(string viewPath, bool pushToHistory = true) where T : IStudioModule
{
return NavigateTo<T>(pushToHistory, viewPath.Split('.'));
}
/// <summary>
/// Navigates to the specified module using the view path (e.g MainView,JobsView).
/// This method makes it easy to do stuff like NavigateTo(nameof(MainView),nameof(JobsView));
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="viewPath">The view path.</param>
public Task<bool> NavigateTo<T>(bool pushToHistory = true, params String[] viewPath) where T : IStudioModule
{
return NavigateTo(typeof(T), pushToHistory, viewPath);
}
/// <summary>
/// Navigates to the specified module and view by full path (e.g Jobs.JobsView).
/// </summary>
/// <param name="fullPath">The full path.</param>
public async Task<bool> NavigateTo(String fullPath, bool pushToHistory = true)
{
String[] path = fullPath.Split('.');
var module = _moduleLoader.UserModules.SingleOrDefault(x => x.GetType().Name == path[0] || x.Name == path[0]);
if (path.Length == 1 && path[0] == CurrentModule.Name) return true;
LogManager.Log($"Navigating to: {fullPath}...");
var fromVM = _currentVM;
if (_currentVM != null && _currentVM is INavigationBlocker)
{
if (_navigating_back)
{
if (!await (_currentVM as INavigationBlocker).OnNavigateBackRequest())
{
return false;
}
}
else
{
if (!await (_currentVM as INavigationBlocker).OnNavigateOutRequest())
{
return false;
}
}
}
if (pushToHistory && _lastFullPath != null && !_preventHistory)
{
_navigationHistory.Push(_lastFullPath);
RaisePropertyChanged(nameof(CanNavigateBack));
}
_lastFullPath = fullPath;
MainWindow.Instance.NavigationControl.NavigateTo(NavigationView.MainView.ToString());
var navigationControl = MachineStudio.UI.Views.MainView.Instance.NavigationControl;
CurrentModule = module;
var moduleView = navigationControl.NavigateTo(module.Name);
_currentVM = moduleView.DataContext;
if (path.Length > 1)
{
var moduleNavigation = moduleView.FindChildOffline<NavigationControl>();
moduleNavigation.RegisterForLoadedOrNow(async (x, e) =>
{
foreach (var view in path.Skip(1))
{
await Task.Delay(100);
var v = moduleNavigation.NavigateTo(view);
if (v != null)
{
_currentVM = v.DataContext;
if (view != path.Last())
{
moduleNavigation = v.FindChildOffline<NavigationControl>();
}
}
else
{
throw LogManager.Log(new ArgumentNullException("Could not navigate to " + fullPath));
}
}
NavigationCycleCompleted?.Invoke(fromVM, _currentVM);
});
}
return true;
}
/// <summary>
/// Navigates for result.
/// </summary>
/// <typeparam name="TModule">The type of the module.</typeparam>
/// <typeparam name="TView">The type of the view.</typeparam>
/// <typeparam name="TResult">The type of the result.</typeparam>
/// <typeparam name="TObject">The type of the object.</typeparam>
/// <param name="obj">The object.</param>
/// <param name="pushToHistory">if set to <c>true</c> [push to history].</param>
/// <returns></returns>
public Task<TResult> NavigateForResult<TModule, TView, TResult, TObject>(TObject obj, bool pushToHistory = true)
where TModule : IStudioModule
{
TaskCompletionSource<TResult> source = new TaskCompletionSource<TResult>();
var fromVM = _currentVM;
Object toVM = null;
Action<Object, Object> handler = null;
handler = (from, to) =>
{
if (toVM == null)
{
toVM = to;
if (toVM is INavigationResultProvider<TResult, TObject>)
{
(toVM as INavigationResultProvider<TResult, TObject>).OnNavigationObjectReceived(obj);
}
}
else
{
if (to == fromVM && from == toVM)
{
if (from is INavigationResultProvider<TResult, TObject>)
{
source.SetResult((from as INavigationResultProvider<TResult, TObject>).GetNavigationResult());
}
}
NavigationCycleCompleted -= handler;
}
};
NavigationCycleCompleted += handler;
NavigateTo<TModule>(typeof(TView).Name, pushToHistory);
return source.Task;
}
/// <summary>
/// Navigates to the specified module and view with the specified object.
/// </summary>
/// <typeparam name="TModule">The type of the module.</typeparam>
/// <typeparam name="TView">The type of the view.</typeparam>
/// <typeparam name="TPass">The type of the pass.</typeparam>
/// <param name="obj">The object.</param>
/// <param name="pushToHistory">if set to <c>true</c> [push to history].</param>
/// <returns></returns>
public Task<bool> NavigateWithObject<TModule, TView, TPass>(TPass obj, bool pushToHistory = true) where TModule : IStudioModule
{
TaskCompletionSource<bool> source = new TaskCompletionSource<bool>();
Action<Object, Object> handler = null;
handler = (from, to) =>
{
if (to is INavigationObjectReceiver<TPass>)
{
(to as INavigationObjectReceiver<TPass>).OnNavigatedToWithObject(obj);
}
NavigationCycleCompleted -= handler;
};
NavigationCycleCompleted += handler;
NavigateTo<TModule>(typeof(TView).Name, pushToHistory);
return source.Task;
}
private Task<bool> NavigateTo(Type moduleType, bool pushToHistory = true, params String[] viewPath)
{
if (viewPath != null && viewPath.Length > 0)
{
return NavigateTo(moduleType.Name + "." + String.Join(".", viewPath), pushToHistory);
}
else
{
return NavigateTo(moduleType.Name, pushToHistory);
}
}
/// <summary>
/// Gets a value indicating whether the navigation system is able to navigate to the previous view.
/// </summary>
public bool CanNavigateBack
{
get { return _navigationHistory.Count > 0; }
}
/// <summary>
/// Navigates to the previous view if <see cref="P:Tango.PPC.Common.Navigation.INavigationManager.CanNavigateBack" /> is true.
/// </summary>
public async Task<bool> NavigateBack()
{
LogManager.Log("Navigating back...");
_navigating_back = true;
String first = _navigationHistory.Pop();
_preventHistory = true;
if (await NavigateTo(first))
{
RaisePropertyChanged(nameof(CanNavigateBack));
_preventHistory = false;
_navigating_back = false;
return true;
}
else
{
_navigationHistory.Push(first);
_preventHistory = false;
_navigating_back = false;
RaisePropertyChanged(nameof(CanNavigateBack));
return false;
}
}
/// <summary>
/// Clears the navigation back history.
/// </summary>
public void ClearHistory()
{
LogManager.Log("Navigation history cleared.");
_navigationHistory.Clear();
RaisePropertyChanged(nameof(CanNavigateBack));
}
/// <summary>
/// Clears the navigation back history except the specified view type.
/// </summary>
/// <typeparam name="T"></typeparam>
public void ClearHistoryExcept<T>()
{
LogManager.Log($"Navigation history cleared except for {typeof(T).Name}.");
var history_list = _navigationHistory.ToList();
history_list = history_list.Where(x => x.Contains(typeof(T).Name)).Distinct().ToList();
_navigationHistory.Clear();
foreach (var item in history_list)
{
_navigationHistory.Push(item);
}
RaisePropertyChanged(nameof(CanNavigateBack));
}
public void NavigateToModule<T>() where T : IStudioModule
{
var loader = TangoIOC.Default.GetInstance<IStudioModuleLoader>();
var module = loader.UserModules.SingleOrDefault(x => x.GetType() == typeof(T));
if (module != null)
{
TangoIOC.Default.GetInstance<ViewModels.MainViewVM>().StartModule(module);
}
}
}
}