using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Data; using Tango.BL; using Tango.BL.Entities; using Tango.BL.Enumerations; using Tango.Core.Commands; using Tango.Core.DI; using Tango.DragAndDrop; using Tango.PPC.Common; using Tango.PPC.Common.Messages; using Tango.PPC.Jobs.Dialogs; using Tango.PPC.Jobs.Messages; using Tango.PPC.Jobs.Views; using System.Data.Entity; using Tango.BL.Builders; using Tango.PPC.Jobs.NavigationObjects; using Tango.PPC.Storage; using Tango.Explorer; using System.IO; using Google.Protobuf; using Tango.PMR.Exports; using Tango.Settings; using Tango.Integration.ExternalBridge; using System.Windows.Media; using Tango.PMR.TCC; using Tango.Pulse; using System.Windows.Media.Imaging; using Tango.Touch.Components; using Tango.PPC.Jobs.ViewContracts; using Tango.Core.ExtensionMethods; namespace Tango.PPC.Jobs.ViewModels { /// /// Represents the jobs list view model. /// /// public class JobsViewVM : PPCViewModel { private ObservablesContext _db; //Holds the db context for the job list. private ObservableCollection _catalogs; //Holds the available color catalogs. public enum JobsCategory { Draft, History } #region Properties private ObservableCollection _jobs; /// /// Gets or sets the collection of jobs. /// public ObservableCollection Jobs { get { return _jobs; } set { _jobs = value; RaisePropertyChangedAuto(); } } private ICollectionView _draftJobsCollectionView; /// /// Gets or sets the jobs collection view. /// public ICollectionView DraftJobsCollectionView { get { return _draftJobsCollectionView; } set { _draftJobsCollectionView = value; RaisePropertyChangedAuto(); } } private ICollectionView _historyJobsCollectionView; /// /// Gets or sets the jobs collection view. /// public ICollectionView HistoryJobsCollectionView { get { return _historyJobsCollectionView; } set { _historyJobsCollectionView = value; RaisePropertyChangedAuto(); } } private ObservableCollection _selectedJobs; /// /// Gets or sets the selected jobs. /// public ObservableCollection SelectedJobs { get { return _selectedJobs; } set { _selectedJobs = value; RaisePropertyChangedAuto(); } } private bool _isLoadingJobs; /// /// Gets or sets a value indicating whether this instance is loading jobs. /// public bool IsLoadingJobs { get { return _isLoadingJobs; } set { _isLoadingJobs = value; RaisePropertyChangedAuto(); } } private bool _isMultiSelecting; /// /// Gets or sets a value indicating whether this the jobs list is in multi select mode. /// public bool IsMultiSelecting { get { return _isMultiSelecting; } set { _isMultiSelecting = value; RaisePropertyChangedAuto(); } } private int _selectedCategoryIndex; /// /// Gets or sets the index of the selected category. /// public int SelectedCategoryIndex { get { return _selectedCategoryIndex; } set { _selectedCategoryIndex = value; RaisePropertyChangedAuto(); RaisePropertyChanged(nameof(SelectedCategory)); } } /// /// Gets or sets the selected category. /// public JobsCategory SelectedCategory { get { return (JobsCategory)SelectedCategoryIndex; } set { if (SelectedCategoryIndex != value.ToInt32()) { SelectedCategoryIndex = value.ToInt32(); Filter = null; } } } private String _filter; /// /// Gets or sets the search filter. /// public String Filter { get { return _filter; } set { _filter = value; RaisePropertyChangedAuto(); OnFilterChanged(); } } private ICollectionFilter _collectionFilter; public ICollectionFilter CollectionFilter { get { return _collectionFilter; } set { _collectionFilter = value; RaisePropertyChangedAuto(); } } #endregion #region Commands /// /// Gets or sets the job selected command. /// public RelayCommand JobSelectedCommand { get; set; } /// /// Gets or sets the job drag and drop command. /// public RelayCommand JobDragedDroppedCommand { get; set; } /// /// Gets or sets the clear selection command. /// public RelayCommand ClearSelectionCommand { get; set; } /// /// Gets or sets the add job command. /// public RelayCommand AddJobCommand { get; set; } /// /// Gets or sets the delete jobs command. /// public RelayCommand DeleteJobsCommand { get; set; } /// /// Gets or sets the clone jobs command. /// public RelayCommand CloneJobsCommand { get; set; } /// /// Gets or sets the export job command. /// public RelayCommand ExportJobCommand { get; set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// public JobsViewVM() { Jobs = new ObservableCollection(); SelectedJobs = new ObservableCollection(); JobSelectedCommand = new RelayCommand((x) => SelectJob(x as Job)); JobDragedDroppedCommand = new RelayCommand((e) => { Job draggedJob = e.Draggable.DataContext as Job; Job droppedJob = e.Droppable.DataContext as Job; DragAndDropJob(draggedJob, droppedJob); }); ClearSelectionCommand = new RelayCommand(ClearSelection); AddJobCommand = new RelayCommand(() => AddNewJob()); DeleteJobsCommand = new RelayCommand(() => DeleteJobs(SelectedJobs)); CloneJobsCommand = new RelayCommand(() => CloneJobs(SelectedJobs)); ExportJobCommand = new RelayCommand(ExportJob); RegisterForMessage(HandleJobRemovedMessage); RegisterForMessage(HandleJobSavedMessage); RegisterForMessage((x) => Filter = null); CollectionFilter = new DefaultCollectionFilter(); CollectionFilter.RegisterFilter(item => { var job = item as Job; if (job != null) { if (String.IsNullOrEmpty(Filter)) { return true; } else { return (job.Name.ToLower().StartsWith(Filter) || (job.Customer != null && job.Customer.Name.ToLower().StartsWith(Filter))); } } else { return true; } }); } #endregion #region Drag & Drop /// /// Called when a job has been dragged and dropped into another job. /// /// The dragged job. /// The dropped job. private void DragAndDropJob(Job draggedJob, Job droppedJob) { LogManager.Log($"Job Drag & Drop '{draggedJob.Name}' => '{droppedJob.Name}'."); if (draggedJob.JobIndex > droppedJob.JobIndex) { draggedJob.JobIndex = droppedJob.JobIndex - 1; } else { draggedJob.JobIndex = droppedJob.JobIndex + 1; } int index = 1; foreach (var job in Jobs.OrderBy(x => x.JobIndex)) { job.JobIndex = index++; } DraftJobsCollectionView.Refresh(); } #endregion #region Job Selection & Loading /// /// Selects the job. /// /// The job. public async void SelectJob(Job job, bool directlyToEdit = false) { if (!ApplicationManager.IsInTechnicianMode && job.ColorSpace != null && job.ColorSpace.Code == ColorSpaces.Volume.ToInt32()) { await NotificationProvider.ShowError("The selected job is supported only in technician mode."); return; } LogManager.Log($"Job '{job.Name}' selected."); RaiseMessage(new JobSelectedMessage() { Job = job, Context = _db }); if (!directlyToEdit && MachineProvider.MachineOperator.Status == Integration.Operation.MachineStatuses.ReadyToDye) { await NavigationManager.NavigateWithObject(new JobSummeryNavigationObject() { Context = _db, Job = job, }); } else { await NavigationManager.NavigateTo(nameof(JobView)); } } /// /// Loads the jobs from database. /// public void LoadJobs(Action onCompleted = null) { try { LogManager.Log("Loading machine jobs..."); Task.Factory.StartNew(() => { IsLoadingJobs = true; Thread.Sleep(500); _db = ObservablesContext.CreateDefault(); var jobs = new JobsCollectionBuilder(_db).Set(x => x.MachineGuid == MachineProvider.Machine.Guid).WithSegments().WithBrushStops().WithCustomer().WithColorSpace().Build(); InvokeUI(() => { Jobs = jobs; DraftJobsCollectionView = new ListCollectionView(Jobs); DraftJobsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Job.LastUpdated), ListSortDirection.Descending)); DraftJobsCollectionView.Filter = new Predicate(x => { var job = x as Job; return job.JobStatus == JobStatuses.Draft; }); HistoryJobsCollectionView = new ListCollectionView(Jobs); HistoryJobsCollectionView.SortDescriptions.Add(new SortDescription(nameof(Job.LastUpdated), ListSortDirection.Descending)); HistoryJobsCollectionView.Filter = new Predicate(x => { var job = x as Job; return job.JobStatus != JobStatuses.Draft; }); IsLoadingJobs = false; LogManager.Log("Machine jobs loaded!"); onCompleted?.Invoke(); }); }); } catch (Exception ex) { LogManager.Log(ex); NotificationProvider.ShowError("An error occurred while trying to load the machine jobs."); } } /// /// Clears the job selection. /// public void ClearSelection() { SelectedJobs.Clear(); IsMultiSelecting = false; } /// /// Adds a new job. /// private async void AddNewJob(Color? colorProfile = null, TwnFile twnFile = null) { try { LogManager.Log("Adding new job..."); var settings = SettingsManager.Default.GetOrCreate(); var machine = MachineProvider.Machine; JobCreationViewVM vm = new JobCreationViewVM( machine.SupportedJobTypes.Count > 0 ? machine.SupportedJobTypes : Enum.GetValues(typeof(JobTypes)).Cast().ToList(), machine.SupportedColorSpaces.Count > 0 ? machine.SupportedColorSpaces : Enum.GetValues(typeof(ColorSpaces)).Cast().Where(x => x.IsUserSpace() || (ApplicationManager.IsInTechnicianMode && x == ColorSpaces.Volume)).ToList() ); CatalogSelectionViewVM catalogVM = new CatalogSelectionViewVM(_catalogs.ToList(), _catalogs.ToList().SingleOrDefault(x => x.Guid == settings.LastSelectedCatalogGuid)); if (settings.LastJobType != null) { vm.SelectedJobType = settings.LastJobType.Value; } else { vm.SelectedJobType = machine.SupportedJobTypes.FirstOrDefault(); } if (settings.LastJobColorSpace != null) { vm.SelectedColorSpace = settings.LastJobColorSpace.Value.IsUserSpace() ? settings.LastJobColorSpace.Value : ColorSpaces.Catalog; } else { var space = machine.SupportedColorSpaces.FirstOrDefault(); vm.SelectedColorSpace = space.IsUserSpace() ? space : ColorSpaces.Catalog; } if (colorProfile != null || twnFile != null) { vm.SupportedColorSpaces = new List() { ColorSpaces.RGB }; vm.SelectedColorSpace = vm.SupportedColorSpaces.First(); } if (twnFile != null) { vm.SupportedJobTypes = new List() { JobTypes.Embroidery }; vm.SelectedJobType = vm.SupportedJobTypes.First(); } if (twnFile == null) { if (machine.SupportedJobTypes.Count != 1 || machine.SupportedColorSpaces.Count != 1) { vm = await NotificationProvider.ShowDialog(vm); if (!vm.DialogResult) return; if (vm.SelectedColorSpace == ColorSpaces.Catalog) { if (catalogVM.SelectedCatalog == null) { catalogVM.SelectedCatalog = _catalogs.FirstOrDefault(); } if (_catalogs.Count == 0) { await NotificationProvider.ShowError("No color catalogs found. Please selected another color space."); return; } else if (_catalogs.Count > 1) { catalogVM = await NotificationProvider.ShowDialog(catalogVM); if (!catalogVM.DialogResult) { return; } } } } else { vm.SelectedJobType = machine.SupportedJobTypes.First(); vm.SelectedColorSpace = machine.SupportedColorSpaces.First(); } } settings.LastJobType = vm.SelectedJobType; settings.LastJobColorSpace = vm.SelectedColorSpace; if (vm.SelectedColorSpace == ColorSpaces.Catalog) { settings.LastSelectedCatalogGuid = catalogVM.SelectedCatalog.Guid; } settings.Save(); Job job = new Job(); job.Name = "untitled"; job.NumberOfHeads = 1; job.NumberOfUnits = 1; job.SampleUnitsOrMeters = 1; job.CreationDate = DateTime.UtcNow; job.JobStatus = JobStatuses.Draft; job.JobType = vm.SelectedJobType; job.EnableLubrication = true; job.ColorSpaceGuid = Adapter.ColorSpaces.FirstOrDefault(x => x.Code == vm.SelectedColorSpace.ToInt32()).Guid; job.MachineGuid = MachineProvider.Machine.Guid; job.UserGuid = AuthenticationProvider.CurrentUser.Guid; job.RmlGuid = machine.DefaultRml != null ? machine.DefaultRmlGuid : Adapter.Rmls.FirstOrDefault().Guid; job.WindingMethodGuid = Adapter.WindingMethods.FirstOrDefault().Guid; job.SpoolTypeGuid = machine.DefaultSpoolType != null ? machine.DefaultSpoolTypeGuid : Adapter.SpoolTypes.FirstOrDefault().Guid; if (vm.SelectedColorSpace == ColorSpaces.Catalog) { job.ColorCatalogGuid = catalogVM.SelectedCatalog.Guid; } if (Jobs.Count > 0) { job.JobIndex = Jobs.Max(x => x.JobIndex) + 1; } if (colorProfile == null) { job.AddSolidSegment(machine.DefaultSegmentLength > 0 ? machine.DefaultSegmentLength : 100); } else { job.AddSolidSegment(colorProfile.Value, machine.DefaultSegmentLength > 0 ? machine.DefaultSegmentLength : 100); job.Name = $"SnapMatch {colorProfile.Value.R}, {colorProfile.Value.G}, {colorProfile.Value.B}"; } if (twnFile != null) { job.Name = twnFile.Name; job.NumberOfUnits = Math.Max(twnFile.NumberOfCopies, 1); job.EmbroideryFileName = twnFile.Name + "." + twnFile.EmbroideryFileFormat; job.EmbroideryFileData = twnFile.EmbroideryFile; job.EmbroideryJpeg = twnFile.ThumbnailData; job.Segments.Clear(); int index = 1; foreach (var segment in twnFile.Segments) { Segment s = new Segment(); s.Job = job; s.SegmentIndex = index++; s.Name = "Embroidery Segment"; s.Length = segment.Length / 100d; int sIndex = 1; foreach (var stop in segment.BrushStops) { BrushStop st = new BrushStop(); st.Segment = s; st.StopIndex = sIndex++; st.OffsetPercent = stop.Offset * 100d; st.Red = stop.R; st.Green = stop.G; st.Blue = stop.B; st.ColorSpaceGuid = job.ColorSpaceGuid; s.BrushStops.Add(st); } job.Segments.Add(s); } } _db.Jobs.Add(job); await _db.SaveChangesAsync(); Jobs.Add(job); LogManager.Log($"Job {job.Name} added successfully."); RaiseMessage(new JobSelectedMessage() { Job = job, Context = _db }); await Task.Delay(200); await NavigationManager.NavigateWithObject(new JobNavigationObject() { Job = job, Intent = JobNavigationIntent.NewJob }); } catch (Exception ex) { LogManager.Log(ex, "Error creating new job."); await NotificationProvider.ShowError("An error occurred while trying to add a new job."); } } /// /// Deletes the specified jobs from db. /// /// The jobs. private async void DeleteJobs(ObservableCollection jobs) { try { LogManager.Log($"Removing selected jobs:\n{jobs.Select(x => x.Name).ToList().ToJsonString()}"); if (await NotificationProvider.ShowQuestion("Are you sure you want to delete the selected jobs")) { foreach (var job in jobs) { await job.DeleteCascadeAsync(_db); Jobs.Remove(job); } await _db.SaveChangesAsync(); ClearSelection(); } } catch (Exception ex) { LogManager.Log(ex, "Error removing selected jobs."); await NotificationProvider.ShowError("An error occurred while trying to remove the selected jobs."); } } /// /// Clones the specified jobs. /// /// The jobs. private async void CloneJobs(ObservableCollection jobs) { try { LogManager.Log($"Cloning selected jobs:\n{jobs.Select(x => x.Name).ToList().ToJsonString()}"); int index = Jobs.Max(x => x.JobIndex); List clonedJobs = new List(); foreach (var job in SelectedJobs) { var cloned = job.Clone(); cloned.JobIndex = ++index; _db.Jobs.Add(cloned); clonedJobs.Add(cloned); } await _db.SaveChangesAsync(); foreach (var job in clonedJobs) { Jobs.Add(job); } ClearSelection(); } catch (Exception ex) { LogManager.Log(ex, "Error cloning selected jobs."); await NotificationProvider.ShowError("An error occurred while trying to clone the selected jobs."); } } /// /// Called when the search filter has been changed /// private void OnFilterChanged() { if (DraftJobsCollectionView != null && HistoryJobsCollectionView != null) { CollectionFilter.RaiseFilterChanged(); View.ScrollToTop(); } } #endregion #region Message Handling /// /// Handles the job removed message. /// /// The MSG. private void HandleJobRemovedMessage(JobRemovedMessage msg) { try { LogManager.Log("JobRemovedMessage message received, removing job from list..."); var job = Jobs.SingleOrDefault(x => x.Guid == msg.Job.Guid); Jobs.Remove(job); _db.Entry(job).State = System.Data.Entity.EntityState.Detached; } catch (Exception ex) { LogManager.Log(ex, "Could not remove job"); } } /// /// Handles the job saved message. /// /// The MSG. private void HandleJobSavedMessage(JobSavedMessage msg) { LogManager.Log("JobSavedMessage message received."); LoadJobs(); } #endregion #region Override Methods /// /// Called when the application has been started. /// public override void OnApplicationStarted() { LoadJobs(); ExternalBridgeService.ColorProfileRequest += ExternalBridgeService_ColorProfileRequest; } public async override void OnApplicationReady() { base.OnApplicationReady(); StorageProvider.RegisterFileHandler(ExplorerFileDefinition.Job.Extension, HandleJobFileLoaded); StorageProvider.RegisterFileHandler(ExplorerFileDefinition.ColorProfile.Extension, HandleColorProfileFileLoaded); StorageProvider.RegisterFileHandler(ExplorerFileDefinition.Pulse.Extension, HandlePulseFileLoaded); //Load catalogs. using (ObservablesContext c = ObservablesContext.CreateDefault()) { _catalogs = (await c.ColorCatalogs.ToListAsync()).ToObservableCollection(); } } public override void OnNavigatedTo() { base.OnNavigatedTo(); Filter = null; } #endregion #region Job Export private async void ExportJob() { var selected_job = SelectedJobs.FirstOrDefault(); if (selected_job == null) return; ClearSelection(); var result = await NavigationManager. NavigateForResult( new Storage.Models.StorageNavigationRequest() { Intent = Storage.Models.StorageNavigationIntent.SaveFile, DefaultFileName = selected_job.Name, Filter = ExplorerFileDefinition.Job.Extension, Title = "Save Job File", }); if (result != null) { try { var jobFile = await selected_job.ToJobFile(); using (FileStream fs = new FileStream(result.Path + ExplorerFileDefinition.Job.Extension, FileMode.Create)) { jobFile.WriteTo(fs); } await NotificationProvider.ShowSuccess("Job saved successfully."); } catch (Exception ex) { LogManager.Log(ex, $"Error saving job {selected_job.Name} to file."); await NotificationProvider.ShowError($"An error occurred while trying to save the job.\n{ex.Message}"); } } } #endregion #region Handle Job File Loading From Storage private async void HandleJobFileLoaded(ExplorerFileItem jobFile) { var vm = await NotificationProvider.ShowDialog(); if (vm.DialogResult) { using (ObservablesContext jobContext = ObservablesContext.CreateDefault()) { try { JobFile jFile = JobFile.Parser.ParseFrom(File.ReadAllBytes(jobFile.Path)); var job = await Job.FromJobFile(jFile, MachineProvider.Machine.Guid, AuthenticationProvider.CurrentUser.Guid); jobContext.Jobs.Add(job); await jobContext.SaveChangesAsync(); LoadJobs(() => { if (vm.ImportAndEdit) { var postJob = Jobs.SingleOrDefault(x => x.Guid == job.Guid); if (postJob != null) { SelectJob(postJob, true); } } }); } catch (Exception ex) { LogManager.Log(ex, $"Error occurred while trying to import job from file {jobFile.Path}."); await NotificationProvider.ShowError($"An error occurred while trying to import the selected job file.\n{ex.Message}"); } } } } #endregion #region Handle TCC File Loading From Storage private async void HandleColorProfileFileLoaded(ExplorerFileItem tccFile) { try { DetectionColorFile tcc = DetectionColorFile.Parser.ParseFrom(File.ReadAllBytes(tccFile.Path)); var vm = await NotificationProvider.ShowDialog(new ImportColorProfileViewVM() { Color = Color.FromRgb( (byte)tcc.ProcessedColor.R, (byte)tcc.ProcessedColor.G, (byte)tcc.ProcessedColor.B), }); if (vm.DialogResult) { AddNewJob(vm.Color); } } catch (Exception ex) { LogManager.Log(ex, $"Error occurred while trying to import detection color from file {tccFile.Path}."); await NotificationProvider.ShowError($"An error occurred while trying to import the selected detection color.\n{ex.Message}"); } } #endregion #region Handle Pulse TWN Loading From Storage private async void HandlePulseFileLoaded(ExplorerFileItem twnFile) { TwnFile twn = TwnFile.FromFile(twnFile.Path); BitmapSource preview = twn.Thumbnail.ToBitmapSource(); var vm = await NotificationProvider.ShowDialog(new ImportTwnFileViewVM() { Thumbnail = preview, }); if (vm.DialogResult) { AddNewJob(null, twn); } } #endregion #region Color Profile Request private void ExternalBridgeService_ColorProfileRequest(object sender, ColorProfileRequestEventArgs e) { InvokeUI(async () => { var vm = await NotificationProvider.ShowDialog(new ColorProfileReceivedViewVM() { Color = Color.FromRgb( (byte)e.Request.DetectionColor.R, (byte)e.Request.DetectionColor.G, (byte)e.Request.DetectionColor.B), }); if (vm.DialogResult) { e.Approve(); AddNewJob(vm.Color); } else { e.Decline(); } }); } #endregion } }