aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/MachineStudio/Tango.MachineStudio.UI/Views/LoadingView.xaml.cs
blob: 15eba3cf870495b95046b79d1756b856f1dcfe84 (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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace Tango.MachineStudio.UI.Views
{
    /// <summary>
    /// Interaction logic for LoadingView.xaml
    /// </summary>
    public partial class LoadingView : UserControl
    {
        public LoadingView()
        {
            InitializeComponent();
        }
    }
}
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 System.Runtime.InteropServices;
using System.Threading;
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;
using Tango.PPC.Jobs.AppButtons;
using Tango.Core.Threading;
using System.Diagnostics;
using System.Runtime.ExceptionServices;
using Tango.Explorer;
using Tango.PPC.Storage;
using System.IO;
using Tango.ColorConversion;
using Tango.Integration.Operation;

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;
        private StartPrintingButton _start_printing_btn;
        private ActionTimer _volumeConversionTimer;
        private IColorConverter _converter;
        private string _current_job_string;
        private bool startingJob = false;

        #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<ColorCatalogsItem> _catalogItems;
        /// <summary>
        /// Gets or sets the twine catalog items.
        /// </summary>
        public List<ColorCatalogsItem> CatalogItems
        {
            get { return _catalogItems; }
            set { _catalogItems = value; RaisePropertyChangedAuto(); }
        }

        private ColorCatalogsItem _selectedCatalogItem;
        /// <summary>
        /// Gets or sets the selected catalog item.
        /// </summary>
        public ColorCatalogsItem SelectedCatalogItem
        {
            get { return _selectedCatalogItem; }
            set { _selectedCatalogItem = value; RaisePropertyChangedAuto(); }
        }

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

        private ColorCatalog _selectedCatalog;
        /// <summary>
        /// Gets or sets the selected catalog.
        /// </summary>
        public ColorCatalog SelectedCatalog
        {
            get { return _selectedCatalog; }
            set { _selectedCatalog = value; RaisePropertyChangedAuto(); }
        }

        #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> OpenCatalogCommand { 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; }

        /// <summary>
        /// Gets or sets the export embroidery command.
        /// </summary>
        public RelayCommand ExportEmbroideryCommand { 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()
        {
            _converter = new DefaultColorConverter();

            _volumeConversionTimer = new ActionTimer(TimeSpan.FromMilliseconds(50));

            RegisterForMessage<JobSelectedMessage>(HandleJobSelectedMessage);

            FineTuneItems = new ObservableCollection<FineTuneItem>();
            ApprovalFineTuneItems = new ObservableCollection<FineTuneItem>();
            CatalogItems = new List<ColorCatalogsItem>();

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


            CatalogAutoCompleteProvider = new AutoCompleteProvider<ColorCatalogsItem>((item, filter) =>
            {
                return !String.IsNullOrWhiteSpace(filter) && item.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, CanStartJob);
            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(true));
            StartFineTuningCommand = new RelayCommand(StartFineTuning, () => FineTuneItems.Any(x => x.IsSelected) && CanStartJob());
            RepeatFineTuningCommand = new RelayCommand(RepeatFineTuning);
            ApproveFineTuningCommand = new RelayCommand(ApproveFineTuning);
            OpenCatalogCommand = new RelayCommand<BrushStop>(OpenCatalog);
            ExportEmbroideryCommand = new RelayCommand(ExportEmbroidery);
        }

        #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)))
                {
                    View.ScrollToTop();

                    LogManager.Log($"Loading selected job '{_job_to_load.Name}'...");

                    //NotificationProvider.SetGlobalBusyMessage("Loading job details...");
                    IsFree = false;

                    _can_navigate_back = false;

                    if (_db != null)
                    {
                        Rmls.ForEach(x => x.Cct = null);
                        Rmls = null;
                        _db.Dispose();
                        GC.Collect();
                    }

                    _db = ObservablesContext.CreateDefault();

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

                    Job.RmlChanged -= OnRmlChanged;
                    Job.RmlChanged += OnRmlChanged;
                    Job.NameChanged -= Job_NameChanged;
                    Job.NameChanged += Job_NameChanged;

                    Job.ValidateOnPropertyChanged = true;

                    LogManager.Log("Loading RMLS...");
                    Rmls = (await new RmlsCollectionBuilder(_db).SetAll().WithActiveParametersGroup().WithCAT(Job.MachineGuid).WithCCT().WithLiquidFactors().WithSpools().ForHeadType(MachineProvider.Machine.MachineHeadType).ForSite(MachineProvider.Machine.SiteGuid).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();

                    if (Job.ColorSpace.Space == BL.Enumerations.ColorSpaces.Catalog)
                    {
                        SelectedCatalog = await new CatalogBuilder(_db).Set(Job.ColorCatalogGuid).WithGroups().WithItems().BuildAsync();

                        if (SelectedCatalog != null)
                        {
                            CatalogItems = SelectedCatalog.ColorCatalogsGroups.SelectMany(x => x.ColorCatalogsItems).OrderBy(x => x.ItemIndex).ToList();
                        }
                        else
                        {
                            await NotificationProvider.ShowError("The selected color catalog for this job could not be found.\nCannot load job.");
                            Job = null;
                            _can_navigate_back = true;
                            await NavigationManager.NavigateBack();
                            return;
                        }
                    }

                    foreach (var segment in Job.Segments)
                    {
                        SetSegmentLiquidVolumesIfVolume(segment);
                    }

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

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

                    ResetFineTuning();

                    _job_to_load = null;

                    _current_job_string = Job.ToJobFileWhenLoaded().ToString();
                }

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

                ValidateBrushStops();

                DyeCommand.RaiseCanExecuteChanged();
                StartSampleDyeCommand.RaiseCanExecuteChanged();
                StartFineTuningCommand.RaiseCanExecuteChanged();
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error loading job '{(_job_to_load != null ? _job_to_load.Name : "null")}'");
                await NotificationProvider.ShowError("An error occurred while trying to load the selected job.");
                _can_navigate_back = true;
                await NavigationManager.NavigateBack();
            }
            finally
            {
                InvokeUI(() =>
                {
                    IsFree = true;
                    //NotificationProvider.ReleaseGlobalBusyMessage();
                });
            }
        }

        private void Job_NameChanged(object sender, string e)
        {
            DyeCommand.RaiseCanExecuteChanged();
        }

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

                    if (!String.IsNullOrWhiteSpace(CustomersFilter))
                    {
                        if (!Customers.Exists(x => x.Name == CustomersFilter))
                        {
                            var newCustomer = new Customer()
                            {
                                OrganizationGuid = MachineProvider.Machine.OrganizationGuid,
                                Name = CustomersFilter,
                            };

                            _db.Customers.Add(newCustomer);

                            Job.Customer = newCustomer;
                        }
                    }

                    Job.LastUpdated = DateTime.UtcNow;
                    Job.IsSynchronized = false;
                    Job.JobStatus = BL.Enumerations.JobStatuses.Draft;
                    Job.MarkModified(_db);
                    await _db.SaveChangesAsync();
                    _current_job_string = Job.ToJobFileWhenLoaded().ToString();

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

                    RaiseMessage(new JobSavedMessage() { Job = Job });
                }
                else
                {
                    await NotificationProvider.ShowError($"Error saving job. {Job.ValidationErrors.FirstOrDefault()}");
                }
            }
            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 async void StartJob()
        {
            if (startingJob) return;

            try
            {
                Debug.WriteLine("Job Starting...");
                startingJob = true;
                LogManager.Log("Start job command pressed. Starting job and navigating to job progress view...");
                var handler = await PrintingManager.Print(Job, _db);
                await NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
                startingJob = false;
            }
            catch (InsufficientLiquidQuantityException)
            {
                //Ignore..
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Could not start the current job.");
                await NotificationProvider.ShowError($"{ex.Message}.");
            }
            finally
            {
                startingJob = false;
            }
        }

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

        #endregion

        #region RML Changed

        protected virtual void OnRmlChanged(object sender, Rml rml)
        {
            //There is no need for that.

            ////Replace all brush stops twine color catalogs.
            //foreach (var stop in Job.Segments.SelectMany(x => x.BrushStops).Where(x => x.ColorCatalog != null && x.BrushColorSpace == BL.Enumerations.ColorSpaces.Twine).ToList())
            //{
            //    var replacement = CatalogItems.SingleOrDefault(x => x.Name == stop.ColorCatalog.Name);
            //    stop.ColorCatalog = replacement;
            //}

            ////Replace all brush stops coats color catalogs.
            //foreach (var stop in Job.Segments.SelectMany(x => x.BrushStops).Where(x => x.ColorCatalog != null && x.BrushColorSpace == BL.Enumerations.ColorSpaces.Coats).ToList())
            //{
            //    var replacement = CoatsCatalogItems.SingleOrDefault(x => x.Name == stop.ColorCatalog.Name);
            //    stop.ColorCatalog = replacement;
            //}

            ResetFineTuning();
        }

        #endregion

        #region Segments Management

        /// <summary>
        /// Adds a new solid segment.
        /// </summary>
        private Segment AddSolidSegment()
        {
            try
            {
                LogManager.Log("Adding new solid segment...");
                var s = Job.AddSolidSegment(Settings.DefaultSegmentLength > 0 ? Settings.DefaultSegmentLength : 10);
                SetSegmentLiquidVolumesIfVolume(s);
                return s;
            }
            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...");
                var s = Job.AddGradientSegment(Settings.DefaultSegmentLength > 0 ? Settings.DefaultSegmentLength : 10);
                SetSegmentLiquidVolumesIfVolume(s);
                return s;
            }
            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;
            }

            ArrangeSegmentsIndices();
        }

        /// <summary>
        /// Removes the segment.
        /// </summary>
        /// <param name="segment">The segment.</param>
        private async void RemoveSegment(Segment segment)
        {
            if (Job.Segments.Count > 1)
            {
                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);

                        ArrangeSegmentsIndices();

                        DyeCommand.RaiseCanExecuteChanged();
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Could not remove segment.");
                    await NotificationProvider.ShowError("An error occurred while trying to add a new segment.");
                }
            }
            else
            {
                await NotificationProvider.ShowInfo("A job must contain at least one color segment.");
            }
        }

        /// <summary>
        /// Sets the segment liquid volumes.
        /// </summary>
        /// <param name="segment">The segment.</param>
        private void SetSegmentLiquidVolumesIfVolume(Segment segment)
        {
            if (Job.ColorSpace.Code == BL.Enumerations.ColorSpaces.Volume.ToInt32())
            {
                foreach (var stop in segment.BrushStops)
                {
                    stop.SetLiquidVolumes(Job.Machine.Configuration, Job.Rml, Job.Rml.GetActiveProcessGroup().ProcessParametersTables.FirstOrDefault());

                    var lub = stop.LiquidVolumes.FirstOrDefault(x => x.IdsPack.LiquidType.Code == (int)BL.Enumerations.LiquidTypes.Lubricant);
                    if (lub != null)
                    {
                        lub.Volume = 100;
                    }
                }
            }
        }

        private void ArrangeSegmentsIndices()
        {
            int index = 1;

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

            SegmentsCollectionView.Refresh();
        }

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

        /// <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}.");
                var segment = brushStop.Segment;
                _db.BrushStops.Remove(brushStop);
                ArrangeBrushStopsIndices(segment);
            }
            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...");
                PMR.ColorLab.ConversionOutput conversionOutput = null;

                if (brushStop.IsOutOfGamut)
                {
                    conversionOutput = _converter.Convert(brushStop, false);
                }

                BasicColorCorrectionViewVM vm = null;
                List<ColorConversionSuggestion> suggestions = null;

                if (brushStop.IsOutOfGamut)
                {
                    vm = await NotificationProvider.ShowDialog<BasicColorCorrectionViewVM>(new BasicColorCorrectionViewVM()
                    {
                        InvalidBrushStop = brushStop,
                        Suggestions = new List<ColorConversionSuggestion>() { new ColorConversionSuggestion(conversionOutput.SingleCoordinates, 0, 0) },
                    });
                }

                if (vm == null || vm.Result == BasicColorCorrectionViewVM.ColorCorrectionDialogResult.MoreOptions)
                {
                    NotificationProvider.SetGlobalBusyMessage("Generating color hive...");

                    await Task.Factory.StartNew(() =>
                    {
                        conversionOutput = _converter.Convert(brushStop, true);

                        suggestions = conversionOutput.CreateHiveSuggestions();

                        if (vm == null)
                        {
                            var center = suggestions.GetCenterSuggestion();
                            center.Coordinates.Red = brushStop.Red;
                            center.Coordinates.Green = brushStop.Green;
                            center.Coordinates.Blue = brushStop.Blue;

                            center.Coordinates.L = brushStop.L;
                            center.Coordinates.A = brushStop.A;
                            center.Coordinates.B = brushStop.B;
                        }
                    });

                    NotificationProvider.ReleaseGlobalBusyMessage();
                    LogManager.Log("Invoking hive color conversion dialog...");
                    vm = await NotificationProvider.ShowDialog<AdvancedColorCorrectionViewVM>(new AdvancedColorCorrectionViewVM()
                    {
                        InvalidBrushStop = brushStop,
                        Suggestions = suggestions,
                        IsOutOfGamut = brushStop.IsOutOfGamut,
                    });
                }

                if (vm.Result == BasicColorCorrectionViewVM.ColorCorrectionDialogResult.Confirmed)
                {
                    LogManager.Log($"Color suggestion selected: {vm.SelectedSuggestion.Color.ToString()}.");

                    if (brushStop.BrushColorSpace == BL.Enumerations.ColorSpaces.RGB)
                    {
                        brushStop.Red = vm.SelectedSuggestion.Coordinates.Red;
                        brushStop.Green = vm.SelectedSuggestion.Coordinates.Green;
                        brushStop.Blue = vm.SelectedSuggestion.Coordinates.Blue;
                    }
                    else if (brushStop.BrushColorSpace == BL.Enumerations.ColorSpaces.LAB)
                    {
                        brushStop.L = vm.SelectedSuggestion.Coordinates.L;
                        brushStop.A = vm.SelectedSuggestion.Coordinates.A;
                        brushStop.B = vm.SelectedSuggestion.Coordinates.B;
                    }
                    else if (brushStop.BrushColorSpace == BL.Enumerations.ColorSpaces.Volume)
                    {
                        if (vm.SelectedSuggestion != suggestions.GetCenterSuggestion())
                        {
                            vm.SelectedSuggestion.ApplyOnBrushStop(brushStop);
                        }
                    }

                    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.");
            }
            finally
            {
                NotificationProvider.ReleaseGlobalBusyMessage();
                DyeCommand.RaiseCanExecuteChanged();
            }
        }

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

                if (stop.BrushColorSpace == BL.Enumerations.ColorSpaces.Volume)
                {
                    _volumeConversionTimer.ResetReplace(() =>
                    {
                        try
                        {
                            var output = _converter.Convert(stop, false);

                            stop.Red = output.SingleCoordinates.Red;
                            stop.Green = output.SingleCoordinates.Green;
                            stop.Blue = output.SingleCoordinates.Blue;
                            stop.Corrected = true;
                            stop.IsOutOfGamut = false;
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, "An error occurred while trying to get volume => RGB from conversion engine.");
                        }
                    });
                }
            }
        }

        /// <summary>
        /// Opens the twine catalog for the specified brush stop.
        /// </summary>
        /// <param name="stop">The stop.</param>
        private async void OpenCatalog(BrushStop stop)
        {
            var catalogItem = await NavigationManager.NavigateForResult<JobsModule, TwineCatalogView, ColorCatalogsItem, TwineCatalogNavigationObject>(new TwineCatalogNavigationObject()
            {
                SelectedItem = stop.ColorCatalogsItem,
                Catalog = SelectedCatalog
            }, true);

            if (catalogItem != null)
            {
                stop.ColorCatalogsItem = catalogItem;
            }
        }

        private void ArrangeBrushStopsIndices(Segment segment)
        {
            for (int i = 0; i < segment.BrushStops.Count; i++)
            {
                segment.BrushStops[i].StopIndex = i + 1;
            }
        }

        private bool ValidateBrushStops()
        {
            return Job.Segments.SelectMany(x => x.BrushStops).ToList().All(x => x.Validate(_db));
        }

        #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 async void StartSampleDye()
        {
            try
            {
                LogManager.Log("Sample dye command pressed...");

                await PrintingManager.PrintSample(Job, _db);

                await NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error executing sample dye for job {Job.Name}.");
                await NotificationProvider.ShowError(ex.Message);
            }
        }

        /// <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 async void SyncFineTuneItemsToBrushStops(bool displayBusy = false)
        {
            try
            {
                if (Job != null)
                {
                    if (_jobs_fine_tune_items.ContainsKey(Job.Guid))
                    {
                        FineTuneItems = _jobs_fine_tune_items[Job.Guid].ToObservableCollection();
                    }
                    else
                    {
                        if (displayBusy)
                        {
                            NotificationProvider.SetGlobalBusyMessage("Generating suggestions...");
                        }

                        FineTuneItems.Clear();

                        foreach (var stop in Job.Segments.SelectMany(x => x.BrushStops).Where(x => !x.IsTransparent).Where(x => x.ColorSpace.Space == BL.Enumerations.ColorSpaces.RGB || x.ColorSpace.Space == BL.Enumerations.ColorSpaces.LAB).DistinctBy(x => x.Color))
                        {
                            var conversionoutput = await _converter.ConvertAsync(stop, true);
                            FineTuneItem item = new FineTuneItem(conversionoutput);
                            item.BrushStop = stop;
                            item.BrushStops = Job.Segments.SelectMany(x => x.BrushStops).Where(x => x.Color == stop.Color).ToList();
                            item.SelectedSuggestion = item.Suggestions.GetCenterSuggestion();
                            item.SelectedChanged += () => StartFineTuningCommand.RaiseCanExecuteChanged();
                            FineTuneItems.Add(item);
                        }

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

                        NotificationProvider.ReleaseGlobalBusyMessage();
                    }

                    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.");
            }
            finally
            {
                NotificationProvider.ReleaseGlobalBusyMessage();
            }
        }

        /// <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.");
            }
            finally
            {
                NotificationProvider.ReleaseGlobalBusyMessage();
            }
        }

        /// <summary>
        /// Resets the fine tuning.
        /// </summary>
        private void ResetFineTuning(bool displayBusy = false)
        {
            if (Job != null && _jobs_fine_tune_items.ContainsKey(Job.Guid))
            {
                _jobs_fine_tune_items.Remove(Job.Guid);
            }

            SyncFineTuneItemsToBrushStops(displayBusy);
        }

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

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

                await PrintingManager.PrintFineTuning(Job, _db, FineTuneItems);

                await NavigationManager.NavigateTo<JobsModule>(nameof(JobProgressView));
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error executing fine tuning job.");
                await NotificationProvider.ShowError(ex.Message);
            }
        }

        /// <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>
        [HandleProcessCorruptedStateExceptions]
        private void CheckGamutThreadMethod()
        {
            while (true)
            {
                Thread.Sleep(500);

                if (Job != null && Job.Rml.Cct != 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 = _converter.IsOutOfGamut(stop);
                            stop.OutOfGamutChecked = true;
                        }
                        catch (AccessViolationException)
                        {
                            LogManager.Log($"Out of gamut check failed for brush stop {stop.StopIndex} at segment {stop.Segment.SegmentIndex}.", LogCategory.Warning);
                            continue;
                        }
                        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();
                            StartSampleDyeCommand.RaiseCanExecuteChanged();
                            StartFineTuningCommand.RaiseCanExecuteChanged();
                        });
                    }
                }
            }
        }

        #endregion

        #region Export Embroidery

        private async void ExportEmbroidery()
        {
            try
            {
                if (!StorageProvider.IsConnected)
                {
                    await NotificationProvider.ShowError("No storage device connected.");
                    return;
                }

                var result = await NavigationManager.
                  NavigateForResult<StorageModule,
                  Storage.Views.MainView, ExplorerFileItem,
                  Storage.Models.StorageNavigationRequest>(
                  new Storage.Models.StorageNavigationRequest()
                  {
                      Intent = Storage.Models.StorageNavigationIntent.SaveFile,
                      DefaultFileName = Job.Name + Path.GetExtension(Job.EmbroideryFileName),
                      Filter = Path.GetExtension(Job.EmbroideryFileName),
                      Title = "Export Embroidery File",
                  });

                if (result != null)
                {
                    File.WriteAllBytes(Path.HasExtension(result.Path) ? result.Path : result.Path + Path.GetExtension(Job.EmbroideryFileName), Job.EmbroideryFileData);
                    await NotificationProvider.ShowSuccess("Embroidery file exported successfully.");
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, "Error exporting embroidery file.");
                await NotificationProvider.ShowError("An error occurred while trying to save the selected embroidery file.");
            }
        }

        #endregion

        #region IPPC ViewModel Overrides

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

            MachineProvider.MachineOperator.PrintingEnded += MachineOperator_PrintingEnded;
        }

        private void MachineOperator_PrintingEnded(object sender, Integration.Operation.PrintingEventArgs e)
        {
            if (IsVisible)
            {
                _start_printing_btn.Push();
            }
        }

        /// <summary>
        /// Called when the navigation system has navigated to this VM view.
        /// </summary>
        public override void OnNavigatedTo()
        {
            if (!MachineProvider.MachineOperator.IsPrinting)
            {
                _start_printing_btn.Push();
            }

            base.OnNavigatedTo();
            LoadJob();
        }

        /// <summary>
        /// Called when the navigation system has navigated from this VM view.
        /// </summary>
        public override void OnNavigatedFrom()
        {
            _start_printing_btn.Pop();

            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)
            {
                bool jobChainged = false;

                if (Job != null)
                {
                    string job_string = Job.ToJobFileWhenLoaded().ToString();
                    jobChainged = job_string != _current_job_string;
                }

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

            return result;
        }

        public override void OnApplicationReady()
        {
            base.OnApplicationReady();

            _start_printing_btn = new StartPrintingButton(DyeCommand, MachineProvider.MachineOperator);
        }
        #endregion

        #region INavigationObjectReceiver

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

        #endregion
    }
}