aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Modules/Tango.MachineStudio.DB/ViewModels/MultiComboVM.cs
blob: d15ac6e11c2747307cdd61b871e9b45c7d938079 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.SharedUI;

namespace Tango.MachineStudio.DB.ViewModels
{
    public class MultiComboVM<T> : EntityViewModel<T>
    {
        private Action _onSelectionChanged;

        public event EventHandler SelectionChanged;

        public MultiComboVM(T entity) : base(entity)
        {

        }

        public MultiComboVM(T entity, Action onSelectionChanged) : this(entity)
        {
            _onSelectionChanged = onSelectionChanged;
        }

        private bool _isSelected;

        public bool IsSelected
        {
            get { return _isSelected; }
            set { _isSelected = value; RaisePropertyChangedAuto(); OnSelectionChanged(); }
        }

        protected virtual void OnSelectionChanged()
        {
            if (_onSelectionChanged != null)
            {
                _onSelectionChanged();
            }

            SelectionChanged?.Invoke(this, new EventArgs());
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.BL;
using Tango.BL.Entities;
using Tango.PPC.Common;
using Tango.PPC.Jobs.Messages;
using System.Data.Entity;
using Tango.Core.Commands;
using System.Windows;
using Tango.Touch.Controls;
using System.Windows.Media;
using Tango.DragAndDrop;
using System.ComponentModel;
using System.Windows.Data;
using Tango.PPC.Jobs.Dialogs;
using Tango.PPC.Jobs.Views;
using Tango.BL.Catalogs;
using System.Runtime.InteropServices;
using System.Threading;
using Tango.BL.ColorConversion;
using Tango.SharedUI.Helpers;
using Tango.PPC.Common.Navigation;
using Tango.PPC.Jobs.NavigationObjects;
using Tango.PPC.Jobs.ViewContracts;
using System.Collections.ObjectModel;
using Tango.PPC.Common.Models;
using Tango.Logging;
using Tango.PPC.Common.Messages;
using Tango.BL.Builders;

namespace Tango.PPC.Jobs.ViewModels
{
    /// <summary>
    /// Represents the selected job view model.
    /// </summary>
    /// <seealso cref="Tango.PPC.Common.PPCViewModel" />
    public class JobViewVM : PPCViewModel<IJobView>, INavigationObjectReceiver<JobNavigationObject>
    {
        private ObservablesContext _db;
        private bool _can_navigate_back;
        private Thread _check_gamut_thread;
        private Job _job_to_load;
        private JobNavigationIntent _job_to_load_intent;
        private static Dictionary<String, List<FineTuneItem>> _jobs_fine_tune_items;

        #region Properties

        private Job _job;
        /// <summary>
        /// Gets or sets the selected job.
        /// </summary>
        public Job Job
        {
            get { return _job; }
            set { _job = value; RaisePropertyChangedAuto(); }
        }

        private ICollectionView _segmentsCollectionView;
        /// <summary>
        /// Gets or sets the job segments collection view.
        /// </summary>
        public ICollectionView SegmentsCollectionView
        {
            get { return _segmentsCollectionView; }
            set
            {
                _segmentsCollectionView = value;
                RaisePropertyChangedAuto();
            }
        }

        private List<ColorSpace> _colorSpaces;
        /// <summary>
        /// Gets or sets the available color spaces.
        /// </summary>
        public List<ColorSpace> ColorSpaces
        {
            get { return _colorSpaces; }
            set { _colorSpaces = value; RaisePropertyChangedAuto(); }
        }

        private List<Rml> _rmls;
        /// <summary>
        /// Gets or sets the available RMLS.
        /// </summary>
        public List<Rml> Rmls
        {
            get { return _rmls; }
            set { _rmls = value; RaisePropertyChangedAuto(); }
        }

        private List<SpoolType> _spoolTypes;
        /// <summary>
        /// Gets or sets the available spool types.
        /// </summary>
        public List<SpoolType> SpoolTypes
        {
            get { return _spoolTypes; }
            set { _spoolTypes = value; RaisePropertyChangedAuto(); }
        }

        private List<Customer> _customers;
        /// <summary>
        /// Gets or sets the available customers.
        /// </summary>
        public List<Customer> Customers
        {
            get { return _customers; }
            set { _customers = value; RaisePropertyChangedAuto(); }
        }

        private String _customersFilter;
        /// <summary>
        /// Gets or sets the customers filter.
        /// </summary>
        public String CustomersFilter
        {
            get { return _customersFilter; }
            set { _customersFilter = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the customers automatic complete provider.
        /// </summary>
        public AutoCompleteProvider<Customer> CustomersAutoCompleteProvider { get; set; }

        private ObservableCollection<FineTuneItem> _fineTuneItems;
        /// <summary>
        /// Gets or sets the fine tune items.
        /// </summary>
        public ObservableCollection<FineTuneItem> FineTuneItems
        {
            get { return _fineTuneItems; }
            set { _fineTuneItems = value; RaisePropertyChangedAuto(); }
        }

        private ObservableCollection<FineTuneItem> _approvalFineTuneItems;
        /// <summary>
        /// Gets or sets the fine tune items.
        /// </summary>
        public ObservableCollection<FineTuneItem> ApprovalFineTuneItems
        {
            get { return _approvalFineTuneItems; }
            set { _approvalFineTuneItems = value; RaisePropertyChangedAuto(); }
        }

        private bool _isFineTuneExpanded;
        /// <summary>
        /// Gets or sets a value indicating whether the fine tuning region is expanded.
        /// </summary>
        public bool IsFineTuneExpanded
        {
            get { return _isFineTuneExpanded; }
            set
            {
                _isFineTuneExpanded = value;
                RaisePropertyChangedAuto();

                if (_isFineTuneExpanded)
                {
                    SyncFineTuneItemsToBrushStops();
                }
            }
        }

        private bool _isJobDetailsExpanded;
        /// <summary>
        /// Gets or sets a value indicating whether the job details area is expanded.
        /// </summary>
        public bool IsJobDetailsExpanded
        {
            get { return _isJobDetailsExpanded; }
            set { _isJobDetailsExpanded = value; RaisePropertyChangedAuto(); }
        }

        private List<ColorCatalog> _twineCatalogItems;
        /// <summary>
        /// Gets or sets the twine catalog items.
        /// </summary>
        public List<ColorCatalog> TwineCatalogItems
        {
            get { return _twineCatalogItems; }
            set { _twineCatalogItems = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the twine catalog automatic complete provider.
        /// </summary>
        public IAutoCompleteProvider TwineCatalogAutoCompleteProvider { get; set; }

        #endregion

        #region Commands

        /// <summary>
        /// Gets or sets the add solid segment command.
        /// </summary>
        public RelayCommand AddSolidSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the add gradient segment command.
        /// </summary>
        public RelayCommand AddGradientSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the add brush stop command.
        /// </summary>
        public RelayCommand<Segment> AddBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the segment dropped command.
        /// </summary>
        public RelayCommand<DropEventArgs> SegmentDroppedCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove segment command.
        /// </summary>
        public RelayCommand<Segment> RemoveSegmentCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove brush stop command.
        /// </summary>
        public RelayCommand<BrushStop> RemoveBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove job command.
        /// </summary>
        public RelayCommand RemoveJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the save job command.
        /// </summary>
        public RelayCommand SaveJobCommand { get; set; }

        /// <summary>
        /// Gets or sets the replace brush stop command.
        /// </summary>
        public RelayCommand<BrushStop> ReplaceBrushStopCommand { get; set; }

        /// <summary>
        /// Gets or sets the twine catalog field tap command.
        /// </summary>
        public RelayCommand<BrushStop> OpenTwineCatalogCommand { get; set; }

        /// <summary>
        /// Gets or sets the increase decrease samples to dye command.
        /// </summary>
        public RelayCommand<String> IncreaseDecreaseSamplesToDyeCommand { get; set; }

        /// <summary>
        /// Gets or sets the start sample dye command.
        /// </summary>
        public RelayCommand StartSampleDyeCommand { get; set; }

        /// <summary>
        /// Gets or sets the dye command.
        /// </summary>
        public RelayCommand DyeCommand { get; set; }

        /// <summary>
        /// Gets or sets the approve sample command.
        /// </summary>
        public RelayCommand ApproveSampleCommand { get; set; }

        /// <summary>
        /// Gets or sets the repeat sample dye command.
        /// </summary>
        public RelayCommand RepeatSampleDyeCommand { get; set; }

        /// <summary>
        /// Gets or sets another sample command.
        /// </summary>
        public RelayCommand AnotherSampleCommand { get; set; }

        /// <summary>
        /// Gets or sets the invoke fine tuning palette command.
        /// </summary>
        public RelayCommand<FineTuneItem> InvokeFineTuningPaletteCommand { get; set; }

        /// <summary>
        /// Gets or sets the reset fine tuning command.
        /// </summary>
        public RelayCommand ResetFineTuningCommand { get; set; }

        /// <summary>
        /// Gets or sets the start fine tuning command.
        /// </summary>
        public RelayCommand StartFineTuningCommand { get; set; }

        /// <summary>
        /// Gets or sets the approve fine tuning command.
        /// </summary>
        public RelayCommand ApproveFineTuningCommand { get; set; }

        /// <summary>
        /// Gets or sets the repeat fine tuning command.
        /// </summary>
        public RelayCommand RepeatFineTuningCommand { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes the <see cref="JobViewVM"/> class.
        /// </summary>
        static JobViewVM()
        {
            _jobs_fine_tune_items = new Dictionary<string, List<FineTuneItem>>();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="JobViewVM"/> class.
        /// </summary>
        public JobViewVM()
        {
            RegisterForMessage<JobSelectedMessage>(HandleJobSelectedMessage);

            FineTuneItems = new ObservableCollection<FineTuneItem>();
            ApprovalFineTuneItems = new ObservableCollection<FineTuneItem>();
            TwineCatalogItems = new List<ColorCatalog>();

            CustomersAutoCompleteProvider = new AutoCompleteProvider<Customer>((customer, filter) =>
            {
                return customer.Name.ToLower().StartsWith(filter != null ? filter.ToLower() : String.Empty);
            });


            TwineCatalogAutoCompleteProvider = new AutoCompleteProvider<ColorCatalog>((color, filter) =>
            {
                return !String.IsNullOrWhiteSpace(filter) && color.Name.ToLower().StartsWith(filter.ToLower());
            });

            //Initialize Commands
            AddSolidSegmentCommand = new RelayCommand(() => AddSolidSegment());
            AddBrushStopCommand = new RelayCommand<Segment>(AddBrushStop);
            AddGradientSegmentCommand = new RelayCommand(() => AddGradientSegment());
            SegmentDroppedCommand = new RelayCommand<DropEventArgs>((e) =>
            {
                DragAndDropSegment(
                    (e.Draggable as FrameworkElement).DataContext as Segment,
                    (e.Droppable as FrameworkElement).DataContext as Segment);
            });

            RemoveSegmentCommand = new RelayCommand<Segment>(RemoveSegment);
            RemoveBrushStopCommand = new RelayCommand<BrushStop>(RemoveBrushStop);
            RemoveJobCommand = new RelayCommand(RemoveJob);
            SaveJobCommand = new RelayCommand(() => SaveJob());
            ReplaceBrushStopCommand = new RelayCommand<BrushStop>(InvokeColorAdjustmentForBrushStop);
            IncreaseDecreaseSamplesToDyeCommand = new RelayCommand<string>((x) =>
            {
                if (x == "+")
                {
                    Job.SampleUnitsOrMeters++;
                }
                else
                {
                    Job.SampleUnitsOrMeters--;
                }
            });

            _check_gamut_thread = new Thread(CheckGamutThreadMethod);
            _check_gamut_thread.IsBackground = true;

            StartSampleDyeCommand = new RelayCommand(StartSampleDye);
            DyeCommand = new RelayCommand(StartJob, CanStartJob);

            ApproveSampleCommand = new RelayCommand(ApproveSampleDye);
            RepeatSampleDyeCommand = new RelayCommand(RepeatSampleDye);
            AnotherSampleCommand = new RelayCommand(DyeAnotherSample);
            InvokeFineTuningPaletteCommand = new RelayCommand<FineTuneItem>(InvokeFineTuningPalette);
            ResetFineTuningCommand = new RelayCommand(ResetFineTuning);
            StartFineTuningCommand = new RelayCommand(StartFineTuning, () => FineTuneItems.Any(x => x.IsSelected));
            RepeatFineTuningCommand = new RelayCommand(RepeatFineTuning);
            ApproveFineTuningCommand = new RelayCommand(ApproveFineTuning);
            OpenTwineCatalogCommand = new RelayCommand<BrushStop>(OpenTwineCatalog);
        }

        #endregion

        #region Job Management

        /// <summary>
        /// Loads the job.
        /// </summary>
        private async void LoadJob()
        {
            try
            {
                if (!(_job_to_load == null || (_job_to_load != null && Job != null && _job_to_load.Guid == Job.Guid)))
                {
                    LogManager.Log($"Loading selected job '{_job_to_load.Name}'...");

                    NotificationProvider.SetGlobalBusyMessage("Loading job details...");

                    _can_navigate_back = false;

                    _db = ObservablesContext.CreateDefault();

                    Job = await new JobBuilder(_db).Set(_job_to_load.Guid)
                        .WithConfiguration()
                        .WithRML()
                        .WithUser()
                        .WithSegments()
                        .WithBrushStops()
                        .BuildAsync();

                    Job.ValidateOnPropertyChanged = true;

                    LogManager.Log("Loading RMLS...");
                    Rmls = (await new RmlsCollectionBuilder(_db).Set().WithActiveParametersGroup().WithCAT(Job.MachineGuid).WithCCT().WithLiquidFactors().BuildAsync()).ToList();
                    LogManager.Log("Loading Color Spaces...");
                    ColorSpaces = await _db.ColorSpaces.ToListAsync();
                    LogManager.Log("Loading Spool Types...");
                    SpoolTypes = await _db.SpoolTypes.ToListAsync();
                    LogManager.Log("Loading Customers...");
                    Customers = await _db.Customers.Where(x => x.OrganizationGuid == MachineProvider.Machine.OrganizationGuid).ToListAsync();
                    TwineCatalogItems = await _db.ColorCatalogs.Where(x => x.ColorSpace.Code == (int)BL.Enumerations.ColorSpaces.Twine).OrderBy(x => x.Name).ToListAsync();

                    if (!_check_gamut_thread.IsAlive)
                    {
                        _check_gamut_thread.Start();
                    }

                    SegmentsCollectionView = CollectionViewSource.GetDefaultView(Job.Segments);
                    SegmentsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Segment.SegmentIndex), ListSortDirection.Ascending));

                    InvokeUIOnIdle(() =>
                    {
                        NotificationProvider.ReleaseGlobalBusyMessage();
                    });

                    _job_to_load = null;
                }

                if (!_jobs_fine_tune_items.ContainsKey(Job.Guid) && Job.JobFineTuningStatus == BL.Enumerations.FineTuningStatuses.PendingApproval)
                {
                    Job.JobFineTuningStatus = BL.Enumerations.FineTuningStatuses.Unspecified;
                }

                if (_job_to_load_intent == JobNavigationIntent.NewJob)
                {
                    IsJobDetailsExpanded = true;
                }

                LogManager.Log($"Job editing state = '{Job.JobEditingState}'.");

                if (Job.JobEditingState == BL.Enumerations.EditingStates.SampleDye && Job.JobSampleDyeStatus == BL.Enumerations.SampleDyeStatuses.PendingApproval)
                {
                    LogManager.Log("Directing view to display sample dye region.");
                    View.DisplaySampleDye();
                }
                else if (Job.JobEditingState == BL.Enumerations.EditingStates.FineTuning && Job.JobFineTuningStatus == BL.Enumerations.FineTuningStatuses.PendingApproval)
                {
                    LogManager.Log("Directing view to display fine tuning region.");
                    View.DisplayFineTuning();
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error loading job '{_job_to_load.Name}'");
                await NotificationProvider.ShowError("An error occurred while trying to load the selected job.");
            }
        }

        /// <summary>
        /// Saves the job.
        /// </summary>
        private async void SaveJob(bool displayNotification = true)
        {
            try
            {
                if (Job.Validate(_db))
                {
                    LogManager.Log("Saving job...");

                    await _db.SaveChangesAsync();
                    RaiseMessage(new JobSavedMessage() { Job = Job });

                    if (displayNotification)
                    {
                        await NotificationProvider.ShowInfo(String.Format("Job '{0}' saved successfully.", Job.Name));
                    }
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error saving job '{Job.Name}'.");
                await NotificationProvider.ShowError("An error occurred while trying to save the job.");
            }
        }

        /// <summary>
        /// Removes the job.
        /// </summary>
        private async void RemoveJob()
        {
            try
            {
                LogManager.Log("Removing job...");

                if (await NotificationProvider.ShowQuestion("Are you sure you want to delete the this job?"))
                {
                    await Job.DeleteCascadeAsync(_db);
                    RaiseMessage(new JobRemovedMessage() { Job = Job });
                    _can_navigate_back = true;
                    await NavigationManager.NavigateBack();
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error removing job '{Job.Name}'.");
                await NotificationProvider.ShowError("An error occurred while trying to remove the job.");
            }
        }

        /// <summary>
        /// Starts the job.
        /// </summary>
        private void StartJob()
        {
            try
            {
                LogManager.Log("Start job command pressed. Starting job and navigating to job progress view...");
                PrintingManager.Print(Job, _db);
                NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Could not start the current job.");
                NotificationProvider.ShowError($"Cannot start job.\n{ex.Message}.");
            }
        }

        /// <summary>
        /// Determines whether this instance [can start job].
        /// </summary>
        private bool CanStartJob()
        {
            return
                Job != null &&
                !Job.Segments.SelectMany(x => x.BrushStops).ToList().Exists(x => x.IsOutOfGamut);
        }

        #endregion

        #region Segments Management

        /// <summary>
        /// Adds a new solid segment.
        /// </summary>
        private Segment AddSolidSegment()
        {
            try
            {
                LogManager.Log("Adding new solid segment...");
                return Job.AddSolidSegment(MachineProvider.Machine.DefaultSegmentLength > 0 ? MachineProvider.Machine.DefaultSegmentLength : 10);
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Could not add a new solid segment.");
                NotificationProvider.ShowError("An error occurred while trying to add a new segment.");
                return null;
            }
        }

        /// <summary>
        /// Adds a new gradient segment.
        /// </summary>
        private Segment AddGradientSegment()
        {
            try
            {
                LogManager.Log("Adding new gradient segment...");
                return Job.AddGradientSegment(MachineProvider.Machine.DefaultSegmentLength > 0 ? MachineProvider.Machine.DefaultSegmentLength : 10);
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Could not add a new gradient segment.");
                NotificationProvider.ShowError("An error occurred while trying to add a new segment.");
                return null;
            }
        }

        /// <summary>
        /// Called when a segment has been dragged and dropped into another segment.
        /// </summary>
        /// <param name="draggedJob">The dragged job.</param>
        /// <param name="droppedJob">The dropped job.</param>
        private void DragAndDropSegment(Segment draggedSegment, Segment droppedSegment)
        {
            LogManager.Log($"Segment Drag & Drop '{draggedSegment.SegmentIndex}' => '{droppedSegment.SegmentIndex}'.");

            if (draggedSegment.SegmentIndex > droppedSegment.SegmentIndex)
            {
                draggedSegment.SegmentIndex = droppedSegment.SegmentIndex - 1;
            }
            else
            {
                draggedSegment.SegmentIndex = droppedSegment.SegmentIndex + 1;
            }

            int index = 1;

            foreach (var segment in Job.Segments.OrderBy(x => x.SegmentIndex))
            {
                segment.SegmentIndex = index++;
            }

            SegmentsCollectionView.Refresh();
        }

        /// <summary>
        /// Removes the segment.
        /// </summary>
        /// <param name="segment">The segment.</param>
        private async void RemoveSegment(Segment segment)
        {
            try
            {
                if (await NotificationProvider.ShowQuestion("Are you sure you want to remove the selected segment?"))
                {
                    LogManager.Log($"Removing job segment {segment.SegmentIndex}");

                    segment.BrushStops.ToList().ForEach(x =>
                    {
                        _db.BrushStops.Remove(x);
                    });
                    _db.Segments.Remove(segment);
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Could not remove segment.");
                await NotificationProvider.ShowError("An error occurred while trying to add a new segment.");
            }
        }

        #endregion

        #region Brush Stops Management

        /// <summary>
        /// Adds a new brush stop to the specified segment.
        /// </summary>
        /// <param name="segment">The segment.</param>
        private void AddBrushStop(Segment segment)
        {
            LogManager.Log($"Adding new brush stop to segment {segment.SegmentIndex}.");
            segment.AddBrushStop();
        }

        /// <summary>
        /// Removes the brush stop.
        /// </summary>
        /// <param name="brushStop">The brush stop.</param>
        private void RemoveBrushStop(BrushStop brushStop)
        {
            if (brushStop.Segment.BrushStops.Count > 2)
            {
                LogManager.Log($"removing brush stop {brushStop.StopIndex} from segment {brushStop.Segment.SegmentIndex}.");
                _db.BrushStops.Remove(brushStop);
            }
            else
            {
                NotificationProvider.ShowInfo("Gradient segments must contain at least two colors.");
            }
        }

        /// <summary>
        /// Invokes the color adjustment for the specified brush stop.
        /// </summary>
        /// <param name="brushStop">The brush stop.</param>
        private async void InvokeColorAdjustmentForBrushStop(BrushStop brushStop)
        {
            try
            {
                LogManager.Log($"Invoking triplet color adjustment dialog for brush stop {brushStop.StopIndex} at segment {brushStop.Segment.SegmentIndex}.");

                LogManager.Log("Retrieving color conversion suggestions for brush stop...");
                var conversionOutput = TangoColorConverter.GetSuggestions(brushStop);

                BasicColorCorrectionViewVM vm = null;

                vm = await NotificationProvider.ShowDialog<BasicColorCorrectionViewVM>(new BasicColorCorrectionViewVM()
                {
                    InvalidBrushStop = brushStop,
                    Suggestions = TangoColorConverter.CreateTrippletSuggestions(conversionOutput),
                });

                if (vm.Result == BasicColorCorrectionViewVM.ColorCorrectionDialogResult.MoreOptions)
                {
                    LogManager.Log("Invoking hive color conversion dialog...");
                    vm = await NotificationProvider.ShowDialog<AdvancedColorCorrectionViewVM>(new AdvancedColorCorrectionViewVM()
                    {
                        InvalidBrushStop = brushStop,
                        Suggestions = TangoColorConverter.CreateHiveSuggestions(conversionOutput),
                    });
                }

                if (vm.Result == BasicColorCorrectionViewVM.ColorCorrectionDialogResult.Confirmed)
                {
                    LogManager.Log($"Color suggestion selected: {vm.SelectedSuggestion.Color.ToString()}.");
                    brushStop.Color = vm.SelectedSuggestion.Color;
                    brushStop.Corrected = true;
                    brushStop.IsOutOfGamut = false;
                    brushStop.OutOfGamutChecked = true;
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error while invoking color adjustment dialog.");
                await NotificationProvider.ShowError("An error occurred while trying to convert the selected color.");
            }
        }

        /// <summary>
        /// Called when the brush stop field value has been changed (This called from the view!).
        /// </summary>
        /// <param name="brushStop">The brush stop.</param>
        public void OnBrushStopFieldValueChanged(BrushStop brushStop)
        {
            brushStop.Corrected = false;
            brushStop.OutOfGamutChecked = false;
        }

        /// <summary>
        /// Opens the twine catalog for the specified brush stop.
        /// </summary>
        /// <param name="stop">The stop.</param>
        private async void OpenTwineCatalog(BrushStop stop)
        {
            var catalogItem = await NavigationManager.NavigateForResult<JobsModule, TwineCatalogView, CatalogItem, BrushStop>(stop, true);

            if (catalogItem != null)
            {
                stop.ColorCatalog = TwineCatalogItems.SingleOrDefault(x => x.Guid == catalogItem.Entity.Guid);
            }
        }

        #endregion

        #region Job Selection Message

        /// <summary>
        /// Handles the job selected message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void HandleJobSelectedMessage(JobSelectedMessage message)
        {
            _job_to_load = message.Job;
        }

        #endregion

        #region Sample Dye

        /// <summary>
        /// Starts a sample dye.
        /// </summary>
        private void StartSampleDye()
        {
            try
            {
                LogManager.Log("Sample dye command pressed...");

                PrintingManager.PrintSample(Job, _db);

                NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error executing sample dye for job {Job.Name}.");
                NotificationProvider.ShowError("An error occurred while trying to execute the sample dye.");
            }
        }

        /// <summary>
        /// Sets the job status back to not approved.
        /// </summary>
        private void RepeatSampleDye()
        {
            LogManager.Log("Repeat sample dye command pressed...");
            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobSampleDyeStatus = BL.Enumerations.SampleDyeStatuses.Unspecified;
        }

        /// <summary>
        /// Approves the sample dye.
        /// </summary>
        private void ApproveSampleDye()
        {
            LogManager.Log("Approve sample dye command pressed...");

            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobSampleDyeStatus = BL.Enumerations.SampleDyeStatuses.Approved;

            Job.SampleDyeApproveDate = DateTime.UtcNow;
            SaveJob(false);
        }

        /// <summary>
        /// Dyes another sample.
        /// </summary>
        private void DyeAnotherSample()
        {
            LogManager.Log("Dye another sample dye command pressed...");

            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobSampleDyeStatus = BL.Enumerations.SampleDyeStatuses.Unspecified;
        }

        #endregion

        #region Fine Tuning

        /// <summary>
        /// Synchronizes the fine tune items to brush stops.
        /// </summary>
        private void SyncFineTuneItemsToBrushStops()
        {
            try
            {
                if (Job != null)
                {
                    if (_jobs_fine_tune_items.ContainsKey(Job.Guid))
                    {
                        FineTuneItems = _jobs_fine_tune_items[Job.Guid].ToObservableCollection();
                    }
                    else
                    {
                        FineTuneItems.Clear();

                        foreach (var stop in Job.Segments.SelectMany(x => x.BrushStops).DistinctBy(x => x.Color))
                        {
                            FineTuneItem item = new FineTuneItem(TangoColorConverter.GetSuggestions(stop));
                            item.BrushStops = Job.Segments.SelectMany(x => x.BrushStops).Where(x => x.Color == stop.Color).ToList();
                            item.SelectedSuggestion = item.Suggestions[item.Suggestions.Count / 2];
                            item.SelectedChanged += () => StartFineTuningCommand.RaiseCanExecuteChanged();
                            FineTuneItems.Add(item);
                        }

                        _jobs_fine_tune_items[Job.Guid] = FineTuneItems.ToList();
                    }

                    ApprovalFineTuneItems = FineTuneItems.Where(x => x.IsSelected).ToObservableCollection();

                    StartFineTuningCommand.RaiseCanExecuteChanged();
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error while trying to synchronize fine tuning items with brush stops.");
            }
        }

        /// <summary>
        /// Invokes the fine tuning palette dialog.
        /// </summary>
        /// <param name="fineTuneItem">The fine tune item.</param>
        private async void InvokeFineTuningPalette(FineTuneItem fineTuneItem)
        {
            LogManager.Log("Invoke fine tuning palette command pressed...");

            try
            {
                FineTuningPaletteViewVM vm = new FineTuningPaletteViewVM(fineTuneItem, Job);
                await NotificationProvider.ShowDialog(vm);

                if (vm.DialogResult)
                {
                    fineTuneItem.Suggestions = vm.Suggestions;
                    fineTuneItem.SelectedSuggestion = vm.SelectedSuggestion;
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error invoking the fine tunning palette");
                await NotificationProvider.ShowError("An error occurred while trying to display the fine tunning palette.");
            }
        }

        /// <summary>
        /// Resets the fine tuning.
        /// </summary>
        private void ResetFineTuning()
        {
            SyncFineTuneItemsToBrushStops();
        }

        /// <summary>
        /// Starts the fine tuning.
        /// </summary>
        private void StartFineTuning()
        {
            try
            {
                LogManager.Log("Start fine tunning job command pressed...");

                _jobs_fine_tune_items[Job.Guid] = FineTuneItems.ToList();

                PrintingManager.PrintFineTuning(Job, _db, FineTuneItems);

                NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error executing fine tuning job.");
                NotificationProvider.ShowError("An error occurred while trying to start the fine tuning job.");
            }
        }

        /// <summary>
        /// Approves the fine tuning.
        /// </summary>
        private void ApproveFineTuning()
        {
            LogManager.Log("Approve fine tuning command pressed.");

            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobFineTuningStatus = BL.Enumerations.FineTuningStatuses.Approved;

            foreach (var item in ApprovalFineTuneItems)
            {
                foreach (var stop in item.BrushStops)
                {
                    stop.Color = item.SelectedSuggestion.Color;
                }
            }


            Job.FineTuningApproveDate = DateTime.UtcNow;
            SaveJob(false);

            if (_jobs_fine_tune_items.ContainsKey(Job.Guid))
            {
                _jobs_fine_tune_items.Remove(Job.Guid);
            }

            SyncFineTuneItemsToBrushStops();
        }

        /// <summary>
        /// Repeats the fine tuning.
        /// </summary>
        private void RepeatFineTuning()
        {
            LogManager.Log("Repeat fine tuning command pressed.");

            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobFineTuningStatus = BL.Enumerations.FineTuningStatuses.Unspecified;
        }

        #endregion

        #region Out Of Gamut Check Thread

        /// <summary>
        /// Iterates over all brush stops and checks for out of gamut.
        /// </summary>
        private void CheckGamutThreadMethod()
        {
            while (true)
            {
                Thread.Sleep(500);

                if (Job != null && IsVisible && (Job.ColorSpace != null && (Job.ColorSpace.Code == BL.Enumerations.ColorSpaces.RGB.ToInt32() || Job.ColorSpace.Code == BL.Enumerations.ColorSpaces.LAB.ToInt32())))
                {
                    var brushStops = Job.Segments.SelectMany(x => x.BrushStops).Where(x => !x.Corrected && !x.OutOfGamutChecked).ToList();

                    foreach (var stop in brushStops)
                    {
                        try
                        {
                            stop.IsOutOfGamut = TangoColorConverter.IsOutOfGamut(stop);
                            stop.OutOfGamutChecked = true;
                        }
                        catch
                        {
                            LogManager.Log($"Out of gamut check failed for brush stop {stop.StopIndex} at segment {stop.Segment.SegmentIndex}.", LogCategory.Warning);
                        }
                    }

                    if (brushStops.Count > 0)
                    {
                        InvokeUI(() =>
                        {
                            DyeCommand.RaiseCanExecuteChanged();
                        });
                    }
                }
            }
        }

        #endregion

        #region IPPC ViewModel Overrides

        /// <summary>
        /// Called when the application has been started.
        /// </summary>
        public override void OnApplicationStarted()
        {
            base.OnApplicationStarted();
        }

        /// <summary>
        /// Called when the navigation system has navigated to this VM view.
        /// </summary>
        public override void OnNavigatedTo()
        {
            base.OnNavigatedTo();
            LoadJob();
        }

        /// <summary>
        /// Called when the navigation system has navigated from this VM view.
        /// </summary>
        public override void OnNavigatedFrom()
        {
            base.OnNavigatedFrom();
            _job_to_load_intent = JobNavigationIntent.Default;
        }

        /// <summary>
        /// Called before the navigation system navigates back from this object.
        /// Return false to abort the navigation.
        /// </summary>
        /// <returns></returns>
        public async override Task<bool> OnNavigateBackRequest()
        {
            bool result = true;

            if (!_can_navigate_back)
            {
                if (await NotificationProvider.ShowQuestion("Are you sure you want to exit this job?"))
                {
                    Job = null;
                    SegmentsCollectionView = null;
                }
                else
                {
                    result = false;
                }
            }

            return result;
        }

        #endregion

        #region INavigationObjectReceiver

        public void OnNavigatedToWithObject(JobNavigationObject e)
        {
            _job_to_load_intent = e.Intent;
            _job_to_load = e.Job;
        }

        #endregion
    }
}