aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/PPC/Tango.PPC.UI/Notifications/NotificationItems/UpdateAvailableNotificationItem.cs
blob: 9e336f276aace6ec4d84d9bf563bb311b806d421 (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
45
46
47
48
49
50
51
52
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Tango.PPC.Common.Notifications;

namespace Tango.PPC.UI.Notifications.NotificationItems
{
    /// <summary>
    /// Represents a simple text message notification item which can be inserted into the application notifications panel.
    /// </summary>
    /// <seealso cref="Tango.PPC.Common.Notifications.NotificationItem" />
    public class UpdateAvailableNotificationItem : NotificationItem
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UpdateAvailableNotificationItem"/> class.
        /// </summary>
        public UpdateAvailableNotificationItem()
        {
            CanClose = true;
        }

        private String _version;
        /// <summary>
        /// Gets or sets the message.
        /// </summary>
        public String Version
        {
            get { return _version; }
            set { _version = value; RaisePropertyChangedAuto(); }
        }

        private bool _isDatabaseUpdate;
        /// <summary>
        /// Gets or sets a value indicating whether this instance is database update.
        /// </summary>
        public bool IsDatabaseUpdate        
        {
            get { return _isDatabaseUpdate; }
            set { _isDatabaseUpdate = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the view type.
        /// </summary>
        public override Type ViewType
        {
            get { return typeof(UpdateAvailableNotificationItemView); }
        }
    }
}
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.Jobs.Models;

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();
                }
            }
        }


        #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> TwineCatalogFieldTapCommand { 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>();

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

            //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);
            TwineCatalogFieldTapCommand = new RelayCommand<BrushStop>(InvokeTwineCatalogForBrushStop);
            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);
        }

        #endregion

        #region Job Management

        /// <summary>
        /// Loads the job.
        /// </summary>
        private async void LoadJob()
        {
            if (!(_job_to_load == null || (_job_to_load != null && Job != null && _job_to_load.Guid == Job.Guid)))
            {
                NotificationProvider.SetGlobalBusyMessage("Loading job details...");

                _can_navigate_back = false;
                base.OnNavigatedTo();

                _db = ObservablesContext.CreateDefault();
                var a = _db.Jobs.ToList();
                Job = await _db.Jobs.SingleOrDefaultAsync(x => x.Guid == _job_to_load.Guid);
                Job.ValidateOnPropertyChanged = true;
                Rmls = await _db.Rmls.ToListAsync();
                ColorSpaces = await _db.ColorSpaces.ToListAsync();
                SpoolTypes = await _db.SpoolTypes.ToListAsync();
                Customers = await _db.Customers.Where(x => x.OrganizationGuid == MachineProvider.Machine.OrganizationGuid).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.JobEditingState == BL.Enumerations.EditingStates.SampleDye && Job.JobSampleDyeStatus == BL.Enumerations.SampleDyeStatuses.PendingApproval)
            {
                View.DisplaySampleDye();
            }
            else if (Job.JobEditingState == BL.Enumerations.EditingStates.FineTuning && Job.JobFineTuningStatus == BL.Enumerations.FineTuningStatuses.PendingApproval)
            {
                View.DisplayFineTuning();
            }
        }

        /// <summary>
        /// Saves the job.
        /// </summary>
        private async void SaveJob(bool displayNotification = true)
        {
            if (Job.Validate(_db))
            {
                await _db.SaveChangesAsync();
                RaiseMessage(new JobSavedMessage() { Job = Job });

                if (displayNotification)
                {
                    await NotificationProvider.ShowInfo(String.Format("Job '{0}' saved successfully.", Job.Name));
                }
            }
        }

        /// <summary>
        /// Removes the job.
        /// </summary>
        private async void RemoveJob()
        {
            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();
            }
        }

        /// <summary>
        /// Starts the job.
        /// </summary>
        private void StartJob()
        {
            MachineProvider.MachineOperator.Print(Job);
            NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
        }

        /// <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()
        {
            return Job.AddSolidSegment(MachineProvider.Machine.DefaultSegmentLength > 0 ? MachineProvider.Machine.DefaultSegmentLength : 10);
        }

        /// <summary>
        /// Adds a new gradient segment.
        /// </summary>
        private Segment AddGradientSegment()
        {
            return Job.AddGradientSegment(MachineProvider.Machine.DefaultSegmentLength > 0 ? MachineProvider.Machine.DefaultSegmentLength : 10);
        }

        /// <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)
        {
            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)
        {
            if (await NotificationProvider.ShowQuestion("Are you sure you want to remove the selected segment?"))
            {
                segment.BrushStops.ToList().ForEach(x =>
                {
                    _db.BrushStops.Remove(x);
                });
                _db.Segments.Remove(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)
        {
            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)
            {
                _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)
        {
            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)
            {
                vm = await NotificationProvider.ShowDialog<AdvancedColorCorrectionViewVM>(new AdvancedColorCorrectionViewVM()
                {
                    InvalidBrushStop = brushStop,
                    Suggestions = TangoColorConverter.CreateHiveSuggestions(conversionOutput),
                });
            }

            if (vm.Result == BasicColorCorrectionViewVM.ColorCorrectionDialogResult.Confirmed)
            {
                brushStop.Color = vm.SelectedSuggestion.Color;
            }
        }

        private async void InvokeTwineCatalogForBrushStop(BrushStop brushStop)
        {
            _can_navigate_back = true;
            var catalogItem = await NavigationManager.NavigateForResult<JobsModule, TwineCatalogView, CatalogItem, BrushStop>(brushStop);
            _can_navigate_back = false;

            if (catalogItem != null)
            {
                brushStop.ColorCatalog = _db.ColorCatalogs.Single(x => x.Guid == catalogItem.Entity.Guid);
                brushStop.Color = catalogItem.Color;
            }
        }

        public void OnBrushStopFieldValueChanged(BrushStop brushStop)
        {
            brushStop.Corrected = false;
            brushStop.OutOfGamutChecked = false;
        }

        #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()
        {
            Job sampleDyeJob = Job.Clone();
            sampleDyeJob.Guid = Job.Guid;
            sampleDyeJob.Designation = BL.Enumerations.JobDesignations.SampleDye;
            sampleDyeJob.Name = Job.Name + " (sample)";

            if (Job.JobType == BL.Enumerations.JobTypes.Embroidery)
            {
                sampleDyeJob.NumberOfUnits = Job.SampleUnitsOrMeters;
            }
            else
            {
                sampleDyeJob.NumberOfUnits = 1;

                foreach (var segment in sampleDyeJob.Segments)
                {
                    segment.Length = Job.SampleUnitsOrMeters;
                }
            }

            SaveJob(false);

            var thisJob = Job;
            var thisContext = _db;

            MachineProvider.MachineOperator.Print(sampleDyeJob).Completed += async (x, e) =>
            {
                thisJob.JobStatus = BL.Enumerations.JobStatuses.PendingApproval;

                if (Job != null && Job.Guid == thisJob.Guid)
                {
                    thisJob.JobEditingState = BL.Enumerations.EditingStates.SampleDye;
                    thisJob.JobSampleDyeStatus = BL.Enumerations.SampleDyeStatuses.PendingApproval;
                    await thisContext.SaveChangesAsync();
                }
            };

            NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
        }

        /// <summary>
        /// Sets the job status back to not approved.
        /// </summary>
        private void RepeatSampleDye()
        {
            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobSampleDyeStatus = BL.Enumerations.SampleDyeStatuses.Unspecified;
        }

        /// <summary>
        /// Approves the sample dye.
        /// </summary>
        private void ApproveSampleDye()
        {
            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()
        {
            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()
        {
            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();
            }
        }

        private async void InvokeFineTuningPalette(FineTuneItem fineTuneItem)
        {
            FineTuningPaletteViewVM vm = new FineTuningPaletteViewVM(fineTuneItem, Job);
            await NotificationProvider.ShowDialog(vm);

            if (vm.DialogResult)
            {
                fineTuneItem.Suggestions = vm.Suggestions;
                fineTuneItem.SelectedSuggestion = vm.SelectedSuggestion;
            }
        }

        private void ResetFineTuning()
        {
            SyncFineTuneItemsToBrushStops();
        }

        private void StartFineTuning()
        {
            try
            {
                Job fineTuneJob = Job.Clone();
                fineTuneJob.NumberOfUnits = 1;
                fineTuneJob.Designation = BL.Enumerations.JobDesignations.FineTuning;
                fineTuneJob.Guid = Job.Guid;
                fineTuneJob.Name = Job.Name + " (fine tuning)";
                fineTuneJob.Segments.Clear();

                foreach (var suggestion in FineTuneItems.Where(x => x.IsSelected).SelectMany(x => x.Suggestions))
                {
                    var segment = fineTuneJob.AddSolidSegment(suggestion.Color);
                }

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

                var thisJob = Job;
                var thisContext = _db;

                MachineProvider.MachineOperator.Print(fineTuneJob).Completed += async (x, e) =>
                {
                    thisJob.JobEditingState = BL.Enumerations.EditingStates.FineTuning;
                    thisJob.JobFineTuningStatus = BL.Enumerations.FineTuningStatuses.PendingApproval;
                    await thisContext.SaveChangesAsync();
                };

                NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex);
                NotificationProvider.ShowError(ex.Message);
            }
        }

        private void ApproveFineTuning()
        {
            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();
        }

        private void RepeatFineTuning()
        {
            Job.JobEditingState = BL.Enumerations.EditingStates.Default;
            Job.JobFineTuningStatus = BL.Enumerations.FineTuningStatuses.Unspecified;
        }

        #endregion

        #region Out Of Gamut Check Thread

        private void CheckGamutThreadMethod()
        {
            while (true)
            {
                Thread.Sleep(500);

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

                    foreach (var stop in brushStops)
                    {
                        stop.IsOutOfGamut = TangoColorConverter.IsOutOfGamut(stop);
                        stop.OutOfGamutChecked = true;
                    }

                    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()
        {
            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
    }
}