aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.PMR/ThreadLoading/ContinueThreadLoadingRequest.cs
blob: 94c8b8f48b72cb0f06891dadbadb97c07ad47f99 (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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: ContinueThreadLoadingRequest.proto
#pragma warning disable 1591, 0612, 3021
#region Designer generated code

using pb = global::Google.Protobuf;
using pbc = global::Google.Protobuf.Collections;
using pbr = global::Google.Protobuf.Reflection;
using scg = global::System.Collections.Generic;
namespace Tango.PMR.ThreadLoading {

  /// <summary>Holder for reflection information generated from ContinueThreadLoadingRequest.proto</summary>
  public static partial class ContinueThreadLoadingRequestReflection {

    #region Descriptor
    /// <summary>File descriptor for ContinueThreadLoadingRequest.proto</summary>
    public static pbr::FileDescriptor Descriptor {
      get { return descriptor; }
    }
    private static pbr::FileDescriptor descriptor;

    static ContinueThreadLoadingRequestReflection() {
      byte[] descriptorData = global::System.Convert.FromBase64String(
          string.Concat(
            "CiJDb250aW51ZVRocmVhZExvYWRpbmdSZXF1ZXN0LnByb3RvEhdUYW5nby5Q",
            "TVIuVGhyZWFkTG9hZGluZxoXUHJvY2Vzc1BhcmFtZXRlcnMucHJvdG8iYAoc",
            "Q29udGludWVUaHJlYWRMb2FkaW5nUmVxdWVzdBJAChFQcm9jZXNzUGFyYW1l",
            "dGVycxgBIAEoCzIlLlRhbmdvLlBNUi5QcmludGluZy5Qcm9jZXNzUGFyYW1l",
            "dGVyc0IjCiFjb20udHdpbmUudGFuZ28ucG1yLnRocmVhZGxvYWRpbmdiBnBy",
            "b3RvMw=="));
      descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
          new pbr::FileDescriptor[] { global::Tango.PMR.Printing.ProcessParametersReflection.Descriptor, },
          new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
            new pbr::GeneratedClrTypeInfo(typeof(global::Tango.PMR.ThreadLoading.ContinueThreadLoadingRequest), global::Tango.PMR.ThreadLoading.ContinueThreadLoadingRequest.Parser, new[]{ "ProcessParameters" }, null, null, null)
          }));
    }
    #endregion

  }
  #region Messages
  public sealed partial class ContinueThreadLoadingRequest : pb::IMessage<ContinueThreadLoadingRequest> {
    private static readonly pb::MessageParser<ContinueThreadLoadingRequest> _parser = new pb::MessageParser<ContinueThreadLoadingRequest>(() => new ContinueThreadLoadingRequest());
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public static pb::MessageParser<ContinueThreadLoadingRequest> Parser { get { return _parser; } }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public static pbr::MessageDescriptor Descriptor {
      get { return global::Tango.PMR.ThreadLoading.ContinueThreadLoadingRequestReflection.Descriptor.MessageTypes[0]; }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    pbr::MessageDescriptor pb::IMessage.Descriptor {
      get { return Descriptor; }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public ContinueThreadLoadingRequest() {
      OnConstruction();
    }

    partial void OnConstruction();

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public ContinueThreadLoadingRequest(ContinueThreadLoadingRequest other) : this() {
      ProcessParameters = other.processParameters_ != null ? other.ProcessParameters.Clone() : null;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public ContinueThreadLoadingRequest Clone() {
      return new ContinueThreadLoadingRequest(this);
    }

    /// <summary>Field number for the "ProcessParameters" field.</summary>
    public const int ProcessParametersFieldNumber = 1;
    private global::Tango.PMR.Printing.ProcessParameters processParameters_;
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public global::Tango.PMR.Printing.ProcessParameters ProcessParameters {
      get { return processParameters_; }
      set {
        processParameters_ = value;
      }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override bool Equals(object other) {
      return Equals(other as ContinueThreadLoadingRequest);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public bool Equals(ContinueThreadLoadingRequest other) {
      if (ReferenceEquals(other, null)) {
        return false;
      }
      if (ReferenceEquals(other, this)) {
        return true;
      }
      if (!object.Equals(ProcessParameters, other.ProcessParameters)) return false;
      return true;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override int GetHashCode() {
      int hash = 1;
      if (processParameters_ != null) hash ^= ProcessParameters.GetHashCode();
      return hash;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override string ToString() {
      return pb::JsonFormatter.ToDiagnosticString(this);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void WriteTo(pb::CodedOutputStream output) {
      if (processParameters_ != null) {
        output.WriteRawTag(10);
        output.WriteMessage(ProcessParameters);
      }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public int CalculateSize() {
      int size = 0;
      if (processParameters_ != null) {
        size += 1 + pb::CodedOutputStream.ComputeMessageSize(ProcessParameters);
      }
      return size;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void MergeFrom(ContinueThreadLoadingRequest other) {
      if (other == null) {
        return;
      }
      if (other.processParameters_ != null) {
        if (processParameters_ == null) {
          processParameters_ = new global::Tango.PMR.Printing.ProcessParameters();
        }
        ProcessParameters.MergeFrom(other.ProcessParameters);
      }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void MergeFrom(pb::CodedInputStream input) {
      uint tag;
      while ((tag = input.ReadTag()) != 0) {
        switch(tag) {
          default:
            input.SkipLastField();
            break;
          case 10: {
            if (processParameters_ == null) {
              processParameters_ = new global::Tango.PMR.Printing.ProcessParameters();
            }
            input.ReadMessage(processParameters_);
            break;
          }
        }
      }
    }

  }

  #endregion

}

#endregion Designer generated code
using Google.Protobuf.Collections;
using Microsoft.Win32;
using RealTimeGraphEx.Controllers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using Tango.Core.Helpers;
using Tango.Editors;
using Tango.BL.Entities;
using Tango.Integration.Operation;
using Tango.MachineStudio.Common.Diagnostics;
using Tango.MachineStudio.Common.Notifications;
using Tango.MachineStudio.Common.StudioApplication;
using Tango.MachineStudio.Technician.Editors;
using Tango.MachineStudio.Technician.Project;
using Tango.MachineStudio.Technician.TechItems;
using Tango.PMR.Diagnostics;
using Tango.Settings;
using Tango.SharedUI;
using Tango.Integration.ExternalBridge;
using Tango.BL.Enumerations;
using Tango.BL;
using Tango.MachineStudio.Common.EventLogging;
using Tango.MachineStudio.Common;
using Tango.Core.Commands;
using Tango.MachineStudio.Technician.Helpers;
using Tango.MachineStudio.Technician.Models;
using Tango.Logging;
using Microsoft.WindowsAPICodePack.Dialogs;
using RealTimeGraphX;
using RealTimeGraphX.WPF.DataSeries;
using RealTimeGraphX.DataPoints;
using Tango.MachineStudio.Technician.Views;

namespace Tango.MachineStudio.Technician.ViewModels
{
    /// <summary>
    /// Represents the MachineTechView View Model.
    /// </summary>
    /// <seealso cref="Tango.SharedUI.ViewModel" />
    /// <seealso cref="Tango.MachineStudio.Common.StudioApplication.IShutdownListener" />
    public class MachineTechViewVM : StudioViewModel
    {
        private List<PropertyInfo> _diagnoticsMonitorsDataProperties;
        private IDiagnosticsFrameProvider _diagnosticsFrameProvider;
        private Dictionary<SingleGraphItem, TechGraphController> _singleControllers;
        private Dictionary<MultiGraphItem, TechGraphController> _multiControllers;
        private static object _elementsLock = new object();
        private String _lastTechProjectFile;
        private INotificationProvider _notification;
        private IEventLogger _eventLogger;
        private DateTime _lastDiagnosticsResponseUpdate;
        private const int MIN_DIAGNOSTICS_UPDATE_MILI = 500;
        private TechnicianModuleSettings _settings;

        private List<SingleTechRecordingData> _single_graphs_recordings;
        private List<MultiTechRecordingData> _multi_graph_recordings;
        private List<SingleTechRecordingData> _single_monitors_recordings;
        private List<MultiTechRecordingData> _multi_monitors_recordings;

        private DateTime _start_time = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);
        private DateTime _last_time = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 0, 0, 0);

        #region Properties

        private ObservableCollection<MachineTechTabVM> _tabs;
        /// <summary>
        /// Gets or sets the elements tabs.
        /// </summary>
        public ObservableCollection<MachineTechTabVM> Tabs
        {
            get { return _tabs; }
            set { _tabs = value; RaisePropertyChangedAuto(); }
        }

        private MachineTechTabVM _selectedTab;
        /// <summary>
        /// Gets or sets the selected tab.
        /// </summary>
        public MachineTechTabVM SelectedTab
        {
            get { return _selectedTab; }
            set
            {
                _selectedTab = value;
                RaisePropertyChangedAuto();

                foreach (var tab in Tabs.Where(x => x != _selectedTab))
                {
                    tab.IsSelected = false;
                }

                if (_selectedTab != null)
                {
                    _selectedTab.IsSelected = true;
                }
            }
        }

        private ObservableCollection<TechItem> _availableTechItems;
        /// <summary>
        /// Gets or sets the available tech items.
        /// </summary>
        public ObservableCollection<TechItem> AvailableTechItems
        {
            get { return _availableTechItems; }
            set { _availableTechItems = value; RaisePropertyChangedAuto(); }
        }

        private TechItem _selectedTechItem;
        /// <summary>
        /// Gets or sets the selected available tech item.
        /// </summary>
        public TechItem SelectedTechItem
        {
            get { return _selectedTechItem; }
            set { _selectedTechItem = value; RaisePropertyChangedAuto(); }
        }

        /// <summary>
        /// Gets or sets the db adapter.
        /// </summary>
        public ObservablesStaticCollections Adapter { get; set; }

        /// <summary>
        /// Gets or sets the application manager.
        /// </summary>
        public IStudioApplicationManager ApplicationManager { get; set; }

        private IMachineOperator _machineOperator;
        /// <summary>
        /// Gets or sets the machine operator.
        /// </summary>
        public IMachineOperator MachineOperator
        {
            get { return _machineOperator; }
            set { _machineOperator = value; RaisePropertyChangedAuto(); }
        }

        private bool _disableRendering;
        /// <summary>
        /// Gets or sets a value indicating whether [disable rendering].
        /// </summary>
        public bool DisableRendering
        {
            get { return _disableRendering; }
            set { _disableRendering = value; RaisePropertyChangedAuto(); OnDisableRenderingChanged(); }
        }

        private bool _hideMenu;
        /// <summary>
        /// Gets or sets a value indicating whether [hide menu].
        /// </summary>
        public bool HideMenu
        {
            get { return _hideMenu; }
            set { _hideMenu = value; RaisePropertyChangedAuto(); }
        }

        private StartDiagnosticsResponse _currentDiagnosticsResponse;
        /// <summary>
        /// Gets or sets the current diagnostics response.
        /// </summary>
        public StartDiagnosticsResponse CurrentDiagnosticsResponse
        {
            get { return _currentDiagnosticsResponse; }
            set { _currentDiagnosticsResponse = value; RaisePropertyChanged(nameof(CurrentDiagnosticsResponse)); }
        }

        private int _currentDiagnosticsResponseSize;
        /// <summary>
        /// Gets or sets the size of the current diagnostics response.
        /// </summary>
        /// <value>
        /// The size of the current diagnostics response.
        /// </value>
        public int CurrentDiagnosticsResponseSize
        {
            get { return _currentDiagnosticsResponseSize; }
            set { _currentDiagnosticsResponseSize = value; RaisePropertyChanged(nameof(CurrentDiagnosticsResponseSize)); }
        }

        private int _graphsDurationSeconds;
        /// <summary>
        /// Gets or sets the graphs duration seconds.
        /// </summary>
        public int GraphsDurationSeconds
        {
            get { return _graphsDurationSeconds; }
            set { _graphsDurationSeconds = value; RaisePropertyChangedAuto(); }
        }

        private int _tempGraphsDurationSeconds;
        /// <summary>
        /// Gets or sets the temporary graphs duration seconds.
        /// </summary>
        public int TempGraphsDurationSeconds
        {
            get { return _tempGraphsDurationSeconds; }
            set { _tempGraphsDurationSeconds = value; RaisePropertyChangedAuto(); }
        }



        #endregion

        #region Commands

        /// <summary>
        /// Gets or sets the save as project command.
        /// </summary>
        public RelayCommand SaveAsProjectCommand { get; set; }

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

        /// <summary>
        /// Gets or sets the open project command.
        /// </summary>
        public RelayCommand OpenProjectCommand { get; set; }

        /// <summary>
        /// Gets or sets the synchronize hardware configuration command.
        /// </summary>
        public RelayCommand SyncHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the upload hardware configuration command.
        /// </summary>
        public RelayCommand UploadHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the reset hardware configuration command.
        /// </summary>
        public RelayCommand ResetHardwareConfigurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the update graphs duration command.
        /// </summary>
        public RelayCommand UpdateGraphsDurationCommand { get; set; }

        /// <summary>
        /// Gets or sets the add tab command.
        /// </summary>
        public RelayCommand AddTabCommand { get; set; }

        /// <summary>
        /// Gets or sets the remove tab command.
        /// </summary>
        public RelayCommand<MachineTechTabVM> RemoveTabCommand { get; set; }

        /// <summary>
        /// Gets or sets the new project command.
        /// </summary>
        public RelayCommand NewProjectCommand { get; set; }

        /// <summary>
        /// Gets or sets the rename tab command.
        /// </summary>
        public RelayCommand RenameTabCommand { get; set; }

        /// <summary>
        /// Gets or sets the import project tabs command.
        /// </summary>
        /// <value>
        /// The import project tabs command.
        /// </value>
        public RelayCommand ImportProjectTabsCommand { get; set; }

        /// <summary>
        /// Gets or sets the upload partial hardware configuration command.
        /// </summary>
        public RelayCommand UploadPartialHardwareConfigurationCommand { get; set; }
        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MachineTechViewVM"/> class.
        /// </summary>
        /// <param name="applicationManager">The application manager.</param>
        /// <param name="notificationProvider">The notification provider.</param>
        public MachineTechViewVM(IStudioApplicationManager applicationManager, INotificationProvider notificationProvider, IDiagnosticsFrameProvider diagnosticsFrameProvider, IEventLogger eventLogger)
        {
            Tabs = new ObservableCollection<MachineTechTabVM>();
            Tabs.Add(new MachineTechTabVM() { IsSelected = true, Name = "Untitled" });
            SelectedTab = Tabs.First();

            _settings = SettingsManager.Default.GetOrCreate<TechnicianModuleSettings>();

            _single_graphs_recordings = new List<SingleTechRecordingData>();
            _multi_graph_recordings = new List<MultiTechRecordingData>();

            _single_monitors_recordings = new List<SingleTechRecordingData>();
            _multi_monitors_recordings = new List<MultiTechRecordingData>();

            GraphsDurationSeconds = _settings.GraphsDuration;
            TempGraphsDurationSeconds = GraphsDurationSeconds;

            _notification = notificationProvider;
            _eventLogger = eventLogger;
            _singleControllers = new Dictionary<SingleGraphItem, TechGraphController>();
            _multiControllers = new Dictionary<MultiGraphItem, TechGraphController>();
            AvailableTechItems = TechItem.GetAvailableTechItems().ToObservableCollection();
            SelectedTechItem = AvailableTechItems.FirstOrDefault();
            _diagnoticsMonitorsDataProperties = typeof(DiagnosticsMonitors).GetProperties(BindingFlags.Public | BindingFlags.Instance).ToList();
            ApplicationManager = applicationManager;
            ApplicationManager.ConnectedMachineChanged += ApplicationManager_ConnectedMachineChanged;

            Adapter = ObservablesStaticCollections.Instance;

            OpenProjectCommand = new RelayCommand(OpenProject);
            SaveAsProjectCommand = new RelayCommand(SaveAsProject);
            SaveProjectCommand = new RelayCommand(SaveProject);

            _lastTechProjectFile = _settings.LastTechProjectFile;

            if (File.Exists(_lastTechProjectFile))
            {
                try
                {
                    OpenProjectFile(_lastTechProjectFile);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, "Error loading last project file.");
                }
            }

            _diagnosticsFrameProvider = diagnosticsFrameProvider;
            _diagnosticsFrameProvider.FrameReceived += DiagnosticsFrameProvider_FrameReceived;

            UploadHardwareConfigurationCommand = new RelayCommand(UploadHardwareConfiguration);
            SyncHardwareConfigurationCommand = new RelayCommand(SyncHardwareConfiguration);
            ResetHardwareConfigurationCommand = new RelayCommand(() => ResetHardwareConfiguration());
            UpdateGraphsDurationCommand = new RelayCommand(() =>
            {
                GraphsDurationSeconds = TempGraphsDurationSeconds;
                _settings.GraphsDuration = GraphsDurationSeconds;
                ClearAllGraphs();
            });

            AddTabCommand = new RelayCommand(() => AddNewTab());
            RemoveTabCommand = new RelayCommand<MachineTechTabVM>(RemoveTab);
            NewProjectCommand = new RelayCommand(CreateNewProject);
            RenameTabCommand = new RelayCommand(RenameTab);
            ImportProjectTabsCommand = new RelayCommand(ImportProjectTabs);
            UploadPartialHardwareConfigurationCommand = new RelayCommand(UploadPartialHardwareConfiguration);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Applications the manager connected machine changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="machine">The machine.</param>
        private void ApplicationManager_ConnectedMachineChanged(object sender, IExternalBridgeClient machine)
        {
            MachineOperator = machine;

            if (MachineOperator != null)
            {
                ResetHardwareConfiguration(false);
            }
        }

        /// <summary>
        /// Handles the diagnostics frame provider data.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="response">The response.</param>
        private void DiagnosticsFrameProvider_FrameReceived(object sender, StartDiagnosticsResponse response)
        {
            PopulateDiagnosticsData(response);
        }

        #endregion

        #region Populate Diagnostics Data

        /// <summary>
        /// Populates the diagnostics data to the proper elements.
        /// </summary>
        /// <param name="data">The data.</param>
        private void PopulateDiagnosticsData(StartDiagnosticsResponse data)
        {
            TimeSpan delta_base = DateTime.Now - _start_time;
            TimeSpan delta = (DateTime.Now - _last_time);
            double delta_mili = delta.TotalMilliseconds;
            _last_time = DateTime.Now;

            if (DateTime.Now > _lastDiagnosticsResponseUpdate.AddMilliseconds(MIN_DIAGNOSTICS_UPDATE_MILI))
            {
                CurrentDiagnosticsResponse = data;
                _lastDiagnosticsResponseUpdate = DateTime.Now;
                CurrentDiagnosticsResponseSize = data.CalculateSize();
            }

            foreach (var sr in _single_monitors_recordings)
            {
                var techMonitor = (sr.Tag as TechMonitor);

                var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == techMonitor.Name);

                if (prop != null)
                {
                    var points = GetDataArray(techMonitor, prop.GetValue(data.Monitors));
                    sr.PushData(points, delta_base, delta);
                }
            }

            foreach (var mr in _multi_monitors_recordings)
            {
                var techMonitor = (mr.Tag as TechMonitor);

                var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == techMonitor.Name);

                if (prop != null)
                {
                    var points = GetDataMatrix(techMonitor, prop.GetValue(data.Monitors));
                    mr.PushData(points, delta_base, delta);
                }
            }

            lock (_elementsLock)
            {
                var elements = Tabs.SelectMany(x => x.Elements).ToList();

                foreach (var item in elements.Select(x => x.HostedElement as TechItem))
                {
                    if (item.GetType() == typeof(MonitorItem))
                    {
                        MonitorItem monitorItem = item as MonitorItem;

                        if (DateTime.Now > monitorItem.LastUpdateTime.AddMilliseconds(monitorItem.UpdateInterval))
                        {
                            var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == monitorItem.TechMonitor.Name);

                            if (prop != null)
                            {
                                monitorItem.Value = GetDataLastValue(monitorItem.TechMonitor, prop.GetValue(data.Monitors));
                            }
                        }
                    }
                    else if (item.GetType() == typeof(MeterItem))
                    {
                        MeterItem meterItem = item as MeterItem;

                        if (DateTime.Now > meterItem.LastUpdateTime.AddMilliseconds(meterItem.UpdateInterval))
                        {
                            var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == meterItem.TechMonitor.Name);

                            if (prop != null)
                            {
                                meterItem.Value = GetDataLastValue(meterItem.TechMonitor, prop.GetValue(data.Monitors));
                            }
                        }
                    }
                    else if (item.GetType() == typeof(SingleGraphItem))
                    {
                        SingleGraphItem graphItem = item as SingleGraphItem;

                        var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == graphItem.TechMonitor.Name);

                        if (prop != null)
                        {
                            TechGraphController controller = null;

                            if (_singleControllers.TryGetValue(graphItem, out controller))
                            {
                                var points = GetDataArray(graphItem.TechMonitor, prop.GetValue(data.Monitors));

                                List<TimeSpanDataPoint> times = new List<TimeSpanDataPoint>();
                                var dPoints = points.Select(x => new DoubleDataPoint(x)).ToList();

                                for (int i = 0; i < points.Count; i++)
                                {
                                    times.Add(delta_base.Add(TimeSpan.FromMilliseconds((delta_mili / points.Count) * i)));
                                }

                                controller.PushData(times, dPoints);

                                var _graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == graphItem);
                                if (_graph_recording != null)
                                {
                                    _graph_recording.PushData(points, delta_base, delta);
                                }
                            }
                        }
                    }
                    else if (item.GetType() == typeof(MultiGraphItem))
                    {
                        MultiGraphItem graphItem = item as MultiGraphItem;

                        var prop = _diagnoticsMonitorsDataProperties.SingleOrDefault(x => x.Name == graphItem.TechMonitor.Name);

                        if (prop != null)
                        {
                            TechGraphController controller = null;

                            if (_multiControllers.TryGetValue(graphItem, out controller))
                            {
                                var points = GetDataMatrix(graphItem.TechMonitor, prop.GetValue(data.Monitors));

                                if (points.Count > 0)
                                {
                                    List<TimeSpanDataPoint> times = new List<TimeSpanDataPoint>();
                                    var dPoints = points.Select(x => new List<DoubleDataPoint>(x.Select(y => new DoubleDataPoint(y)))).ToList();

                                    for (int i = 0; i < points[0].Count; i++)
                                    {
                                        times.Add(delta_base.Add(TimeSpan.FromMilliseconds((delta_mili / points[0].Count) * i)));
                                    }

                                    List<List<TimeSpanDataPoint>> timesMat = new List<List<TimeSpanDataPoint>>();

                                    for (int i = 0; i < controller.DataSeriesCollection.Count; i++)
                                    {
                                        timesMat.Add(times);
                                    }

                                    controller.PushData(timesMat, dPoints);
                                }

                                var _graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == graphItem);
                                if (_graph_recording != null)
                                {
                                    _graph_recording.PushData(points, delta_base, delta);
                                }
                            }
                        }
                    }
                    else if (item.GetType() == typeof(DigitalOutItem))
                    {
                        DigitalOutItem digitalOutItem = item as DigitalOutItem;

                        var digitalPin = data.DigitalInterfaceStates.SingleOrDefault(x => x.InterfaceIO == (InterfaceIOs)digitalOutItem.TechIo.Code);

                        if (digitalPin != null)
                        {
                            digitalOutItem.EffectiveValue = digitalPin.Value;
                        }
                    }
                    else if (item.GetType() == typeof(DigitalInItem))
                    {
                        DigitalInItem digitalInItem = item as DigitalInItem;

                        var digitalPin = data.DigitalInterfaceStates.SingleOrDefault(x => x.InterfaceIO == (InterfaceIOs)digitalInItem.TechIo.Code);

                        if (digitalPin != null)
                        {
                            digitalInItem.Value = digitalPin.Value;
                        }
                    }
                    else if (item.GetType() == typeof(HeaterItem))
                    {
                        HeaterItem heaterItem = item as HeaterItem;

                        var heaterState = data.HeatersStates.SingleOrDefault(x => x.HeaterType == (HeaterType)heaterItem.TechHeater.Code);

                        if (heaterState != null)
                        {
                            heaterItem.HeaterState = heaterState;
                        }
                    }
                    else if (item.GetType() == typeof(ValveItem))
                    {
                        ValveItem valveItem = item as ValveItem;

                        var valveState = data.ValvesStates.SingleOrDefault(x => x.ValveType == (ValveType)valveItem.TechValve.Code);

                        if (valveState != null)
                        {
                            valveItem.EffectiveState = valveState.State;
                        }
                    }
                    else if (item.GetType() == typeof(BlowerItem))
                    {
                        BlowerItem blowerItem = item as BlowerItem;

                        if (data.Monitors.BlowerVoltage.Count > 0)
                        {
                            blowerItem.EffectiveActive = data.Monitors.BlowerVoltage.Last() > 0;
                        }
                    }
                    else if (item.GetType() == typeof(ControllerItem))
                    {
                        ControllerItem controllerItem = item as ControllerItem;

                        if (DateTime.Now > controllerItem.LastUpdateTime.AddMilliseconds(controllerItem.UpdateInterval))
                        {
                            var componentState = data.ComponentsStates.SingleOrDefault(x => (int)x.Component == controllerItem.TechController.Code);

                            if (componentState != null)
                            {
                                controllerItem.EffectiveValue = componentState.Value;
                            }
                        }
                    }
                }
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Gets the last data point from a protobuf repeated field.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private double GetDataLastValue(TechMonitor monitor, object value)
        {
            if (!monitor.MultiChannel)
            {
                RepeatedField<double> arr = value as RepeatedField<double>;
                return arr.LastOrDefault();
            }
            else
            {
                RepeatedField<DoubleArray> arr = value as RepeatedField<DoubleArray>;
                return arr.Last().Data.Last();
            }
        }

        /// <summary>
        /// Gets the data array from a protobuf repeated field.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private List<double> GetDataArray(TechMonitor monitor, object value)
        {
            return (value as RepeatedField<double>).ToList();
        }

        /// <summary>
        /// Gets the data matrix from a protobuf repeated field of <see cref="DoubleArray"/>.
        /// </summary>
        /// <param name="monitor">The monitor.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private List<List<double>> GetDataMatrix(TechMonitor monitor, object value)
        {
            DoubleArray[] arrayOfDoubles = Enumerable.ToArray(value as IEnumerable<DoubleArray>);
            return arrayOfDoubles.Select(x => x.Data.ToList()).ToList();
        }

        #endregion

        #region Virtual Methods

        /// <summary>
        /// Called when the disable rendering has been changed
        /// </summary>
        protected virtual void OnDisableRenderingChanged()
        {
            //foreach (var controller in _singleControllers)
            //{
            //    controller.Value.ChangeRenderMode(!DisableRendering);
            //}

            //foreach (var controller in _multiControllers)
            //{
            //    controller.Value.ChangeRenderMode(!DisableRendering);
            //}
        }

        #endregion

        #region Add/Remove Element

        /// <summary>
        /// Creates a new tech element by the specified bounds and the current selected element.
        /// </summary>
        /// <param name="bounds">The bounds.</param>
        public void CreateElement(Rect bounds)
        {
            CreateElement(SelectedTechItem, bounds);
        }

        /// <summary>
        /// Creates a new tech element by the specified tech item instance and bounds.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="bounds">The bounds.</param>
        private void CreateElement(TechItem item, Rect bounds)
        {
            if (item is MonitorItem)
            {
                CreateElement<MonitorElementEditor, MonitorItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
            }
            else if (item is MeterItem)
            {
                CreateElement<MeterElementEditor, MeterItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
            }
            else if (item is HeaterItem)
            {
                var editor = CreateElement<HeaterElementEditor, HeaterItem, TechHeater>(bounds, Adapter.TechHeaters.FirstOrDefault());
                InitTechHeater(editor.HeaterItem);
            }
            else if (item is ValveItem)
            {
                var editor = CreateElement<ValveElementEditor, ValveItem, TechValve>(bounds, Adapter.TechValves.FirstOrDefault());
                InitTechValveItem(editor.ValveItem);
            }
            else if (item is SingleGraphItem)
            {
                var editor = CreateElement<SingleGraphElementEditor, SingleGraphItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault());
                InitSingleGraphitem(editor.GraphItem, editor);
            }
            else if (item is MultiGraphItem)
            {
                var editor = CreateElement<MultiGraphElementEditor, MultiGraphItem, TechMonitor>(bounds, Adapter.TechMonitors.Where(x => x.MultiChannel).FirstOrDefault());
                InitMultiGraphItem(editor.GraphItem, editor);
            }
            else if (item is MotorItem)
            {
                var editor = CreateElement<MotorElementEditor, MotorItem, HardwareMotorType>(bounds, Adapter.HardwareMotorTypes.FirstOrDefault());
                InitMotorItem(editor.MotorItem);
            }
            else if (item is DispenserItem)
            {
                var editor = CreateElement<DispenserElementEditor, DispenserItem, TechDispenser>(bounds, Adapter.TechDispensers.FirstOrDefault());
                InitDispenserItem(editor.DispenserItem);
            }
            else if (item is ThreadMotionItem)
            {
                var editor = CreateElement<ThreadMotionElementEditor, ThreadMotionItem, object>(bounds, null);
                InitThreadMotionItem(editor.ThreadMotionItem);
            }
            else if (item is MotorGroupItem)
            {
                var editor = CreateElement<MotorGroupElementEditor, MotorGroupItem, object>(bounds, null);
                InitMotorGroupItem(editor.MotorGroupItem);
            }
            else if (item is DigitalOutItem)
            {
                var editor = CreateElement<DigitalOutElementEditor, DigitalOutItem, TechIo>(bounds, Adapter.TechIos.Where(x => x.Type == IOType.DigitalOutput.ToInt32()).FirstOrDefault());
                InitDigitalOutItem(editor.DigitalOutItem);
            }
            else if (item is DigitalInItem)
            {
                CreateElement<DigitalInElementEditor, DigitalInItem, TechIo>(bounds, Adapter.TechIos.Where(x => x.Type == IOType.DigitalInput.ToInt32()).FirstOrDefault());
            }
            else if (item is ControllerItem)
            {
                var editor = CreateElement<ControllerElementEditor, ControllerItem, TechController>(bounds, Adapter.TechControllers.FirstOrDefault());
                InitControllerItem(editor.ControllerItem);
            }
            else if (item is PidItem)
            {
                CreateElement<PidElementEditor, PidItem, HardwarePidControlType>(bounds, Adapter.HardwarePidControlTypes.FirstOrDefault());
            }
            else if (item is WinderItem)
            {
                CreateElement<WinderElementEditor, WinderItem, HardwareWinderType>(bounds, Adapter.HardwareWinderTypes.FirstOrDefault());
            }
            else if (item is DancerItem)
            {
                CreateElement<DancerElementEditor, DancerItem, HardwareDancerType>(bounds, Adapter.HardwareDancerTypes.FirstOrDefault());
            }
            else if (item is SpeedSensorItem)
            {
                CreateElement<SpeedSensorElementEditor, SpeedSensorItem, HardwareSpeedSensorType>(bounds, Adapter.HardwareSpeedSensorTypes.FirstOrDefault());
            }
            else if (item is BlowerItem)
            {
                var editor = CreateElement<BlowerElementEditor, BlowerItem, HardwareBlowerType>(bounds, Adapter.HardwareBlowerTypes.FirstOrDefault());
                InitBlowerItem(editor.BlowerItem);
            }
            else if (item is BreakSensorItem)
            {
                CreateElement<BreakSensorElementEditor, BreakSensorItem, HardwareBreakSensorType>(bounds, Adapter.HardwareBreakSensorTypes.FirstOrDefault());
            }
            else if (item is ProcessParametersItem)
            {
                var editor = CreateElement<ProcessParametersElementEditor, ProcessParametersItem, object>(bounds, null);
                InitProcessParameterItem(editor.ProcessParametersItem);
            }
            else if (item is JobRunnerItem)
            {
                var editor = CreateElement<JobRunnerElementEditor, JobRunnerItem, object>(bounds, null);
                InitJobRunnerItem(editor.JobRunnerItem);
            }
            else if (item is TextItem)
            {
                CreateElement<TextElementEditor, TextItem, object>(bounds, null);
            }
            else if (item is MonitorRecorderItem)
            {
                var editor = CreateElement<MonitorRecorderElementEditor, MonitorRecorderItem, object>(bounds, null);
                InitMonitorRecorderItem(editor.MonitorRecorderItem);
            }
        }

        /// <summary>
        /// Creates a new element by the specified editor type, tech item type, bounds and tech item constructor value.
        /// </summary>
        /// <typeparam name="Editor">The type of the editor.</typeparam>
        /// <typeparam name="Tech">The type of the tech.</typeparam>
        /// <typeparam name="Value">The type of the value.</typeparam>
        /// <param name="bounds">The bounds.</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private Editor CreateElement<Editor, Tech, Value>(Rect bounds, Value value) where Editor : IElementEditor where Tech : TechItem
        {
            TechItem item = Activator.CreateInstance(typeof(Tech), new object[] { value }) as TechItem;
            IElementEditor editor = Activator.CreateInstance(typeof(Editor), new object[] { ((Tech)item), bounds }) as IElementEditor;
            SelectedTab.Elements.Add(editor);
            return (Editor)editor;
        }

        /// <summary>
        /// Creates a new element by the specified editor type and tech item instance.
        /// </summary>
        /// <typeparam name="Editor">The type of the editor.</typeparam>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        private Editor CreateElement<Editor>(TechItem item) where Editor : IElementEditor
        {
            IElementEditor editor = Activator.CreateInstance(typeof(Editor), new object[] { item, item.GetBounds() }) as IElementEditor;
            SelectedTab.Elements.Add(editor);
            return (Editor)editor;
        }

        /// <summary>
        /// Adds a new tech item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void AddTechItem(TechItem item)
        {
            if (item is MonitorItem)
            {
                (item as MonitorItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<MonitorElementEditor>(item);
            }
            else if (item is HeaterItem)
            {
                (item as HeaterItem).TechHeater = Adapter.TechHeaters.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<HeaterElementEditor>(item);
            }
            else if (item is ValveItem)
            {
                (item as ValveItem).TechValve = Adapter.TechValves.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<ValveElementEditor>(item);
            }
            else if (item is MeterItem)
            {
                (item as MeterItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<MeterElementEditor>(item);
            }
            else if (item is SingleGraphItem)
            {
                (item as SingleGraphItem).TechMonitor = Adapter.TechMonitors.Where(x => !x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<SingleGraphElementEditor>(item);
                InitSingleGraphitem(editor.GraphItem, editor);
            }
            else if (item is MultiGraphItem)
            {
                (item as MultiGraphItem).TechMonitor = Adapter.TechMonitors.Where(x => x.MultiChannel).FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<MultiGraphElementEditor>(item);
                InitMultiGraphItem(editor.GraphItem, editor);
            }
            else if (item is MotorItem)
            {
                (item as MotorItem).HardwareMotorType = Adapter.HardwareMotorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<MotorElementEditor>(item);
                InitMotorItem(editor.MotorItem);
            }
            else if (item is DispenserItem)
            {
                (item as DispenserItem).TechDispenser = Adapter.TechDispensers.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<DispenserElementEditor>(item);
                InitDispenserItem(editor.DispenserItem);
            }
            else if (item is ThreadMotionItem)
            {
                var editor = CreateElement<ThreadMotionElementEditor>(item);
                InitThreadMotionItem(editor.ThreadMotionItem);
            }
            else if (item is MotorGroupItem)
            {
                var editor = CreateElement<MotorGroupElementEditor>(item);
                InitMotorGroupItem(editor.MotorGroupItem);
            }
            else if (item is DigitalOutItem)
            {
                (item as DigitalOutItem).TechIo = Adapter.TechIos.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<DigitalOutElementEditor>(item);
                InitDigitalOutItem(editor.DigitalOutItem);
            }
            else if (item is DigitalInItem)
            {
                (item as DigitalInItem).TechIo = Adapter.TechIos.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<DigitalInElementEditor>(item);
            }
            else if (item is ControllerItem)
            {
                (item as ControllerItem).TechController = Adapter.TechControllers.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<ControllerElementEditor>(item);
                InitControllerItem(editor.ControllerItem);
            }
            else if (item is PidItem)
            {
                (item as PidItem).HardwarePidType = Adapter.HardwarePidControlTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<PidElementEditor>(item);
            }
            else if (item is WinderItem)
            {
                (item as WinderItem).HardwareWinderType = Adapter.HardwareWinderTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<WinderElementEditor>(item);
            }
            else if (item is DancerItem)
            {
                (item as DancerItem).HardwareDancerType = Adapter.HardwareDancerTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<DancerElementEditor>(item);
            }
            else if (item is SpeedSensorItem)
            {
                (item as SpeedSensorItem).HardwareSpeedSensorType = Adapter.HardwareSpeedSensorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<SpeedSensorElementEditor>(item);
            }
            else if (item is BlowerItem)
            {
                (item as BlowerItem).HardwareBlowerType = Adapter.HardwareBlowerTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                var editor = CreateElement<BlowerElementEditor>(item);
                InitBlowerItem(editor.BlowerItem);
            }
            else if (item is BreakSensorItem)
            {
                (item as BreakSensorItem).HardwareBreakSensorType = Adapter.HardwareBreakSensorTypes.FirstOrDefault(x => x.Guid == item.ItemGuid);
                CreateElement<BreakSensorElementEditor>(item);
            }
            else if (item is ProcessParametersItem)
            {
                var editor = CreateElement<ProcessParametersElementEditor>(item);
                InitProcessParameterItem(editor.ProcessParametersItem);
            }
            else if (item is JobRunnerItem)
            {
                var editor = CreateElement<JobRunnerElementEditor>(item);
                InitJobRunnerItem(editor.JobRunnerItem);
            }
            else if (item is TextItem)
            {
                CreateElement<TextElementEditor>(item);
            }
            else if (item is MonitorRecorderItem)
            {
                var editor = CreateElement<MonitorRecorderElementEditor>(item);
                InitMonitorRecorderItem(editor.MonitorRecorderItem);
            }
        }

        /// <summary>
        /// Called when elements have been removed
        /// </summary>
        /// <param name="elements">The elements.</param>
        public void OnElementsRemoved(List<IElementEditor> elements)
        {
            foreach (var element in elements)
            {
                if (element.HostedElement is SingleGraphItem)
                {
                    var _graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == element.HostedElement);

                    if (_graph_recording != null)
                    {
                        (_graph_recording.Tag as SingleGraphItem).StopRecording();
                        _single_graphs_recordings.Remove(_graph_recording);
                        _graph_recording.Dispose();
                    }
                }
                else if (element.HostedElement is MultiGraphItem)
                {
                    var _graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == element.HostedElement);

                    if (_graph_recording != null)
                    {
                        (_graph_recording.Tag as MultiGraphItem).StopRecording();
                        _multi_graph_recordings.Remove(_graph_recording);
                        _graph_recording.Dispose();
                    }
                }
            }
        }

        /// <summary>
        /// Called when elements have been pasted
        /// </summary>
        /// <param name="elements">The elements.</param>
        public void OnElementsPasted(List<IElementEditor> elements)
        {
            foreach (var element in elements)
            {
                if (element is SingleGraphElementEditor)
                {
                    var graphItem = element.HostedElement as SingleGraphItem;
                    var editor = element as SingleGraphElementEditor;

                    TechGraphController controller = new TechGraphController();
                    controller.AddDataSeries(new WpfDataSeries()
                    {
                        Stroke = Colors.DodgerBlue,
                    });
                    editor.InnerGraph.Controller = controller;

                    graphItem.Editor = editor;

                    _singleControllers.Add(graphItem, controller);
                }
                else if (element is MultiGraphElementEditor)
                {
                    var graphItem = element.HostedElement as MultiGraphItem;
                    var editor = element as MultiGraphElementEditor;

                    TechGraphController controller = new TechGraphController(500);

                    for (int i = 0; i < graphItem.TechMonitor.ChannelCount; i++)
                    {
                        controller.AddDataSeries(new WpfDataSeries()
                        {
                            Stroke = ColorHelper.GetRandomColor(),
                            Name = graphItem.TechMonitor.Name.First() + (i + 1).ToString(),
                        });
                    }

                    editor.InnerGraph.Controller = controller;

                    graphItem.Editor = editor;

                    _multiControllers.Add(graphItem, controller);
                }
                else if (element is MotorElementEditor)
                {
                    var motorItem = element.HostedElement as MotorItem;
                    InitMotorItem(motorItem);
                }
                else if (element is DispenserElementEditor)
                {
                    var dispenser = element.HostedElement as DispenserItem;
                    InitDispenserItem(dispenser);
                }
                else if (element is DigitalOutElementEditor)
                {
                    var ioItem = element.HostedElement as DigitalOutItem;
                    InitDigitalOutItem(ioItem);
                }
                else if (element is ThreadMotionElementEditor)
                {
                    var threadMotionItem = element.HostedElement as ThreadMotionItem;
                    InitThreadMotionItem(threadMotionItem);
                }
                else if (element is MotorGroupElementEditor)
                {
                    var motorGroupItem = element.HostedElement as MotorGroupItem;
                    InitMotorGroupItem(motorGroupItem);
                }
                else if (element is ControllerElementEditor)
                {
                    var controllerItem = element.HostedElement as ControllerItem;
                    InitControllerItem(controllerItem);
                }
                else if (element is MonitorRecorderElementEditor)
                {
                    var item = element.HostedElement as MonitorRecorderItem;
                    InitMonitorRecorderItem(item);
                }
                else if (element is ValveElementEditor)
                {
                    var item = element.HostedElement as ValveItem;
                    InitTechValveItem(item);
                }
                else if (element is BlowerElementEditor)
                {
                    var item = element.HostedElement as BlowerItem;
                    InitBlowerItem(item);
                }
                else if (element is HeaterElementEditor)
                {
                    var item = element.HostedElement as HeaterItem;
                    InitTechHeater(item);
                }
                else if (element is ProcessParametersElementEditor)
                {
                    var item = element.HostedElement as ProcessParametersItem;
                    InitProcessParameterItem(item);
                }
            }
        }

        #endregion

        #region Init Tech Items

        /// <summary>
        /// Initializes the monitor recorder item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMonitorRecorderItem(MonitorRecorderItem item)
        {
            item.RecordingStarted += () =>
            {
                CommonOpenFileDialog dlg = new CommonOpenFileDialog();
                dlg.Title = "Select a folder to place all CSV files.";
                dlg.IsFolderPicker = true;
                if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
                {
                    foreach (var monitor in item.GetSelectedMonitors())
                    {
                        if (!monitor.MultiChannel)
                        {
                            _single_monitors_recordings.Add(new SingleTechRecordingData(monitor.Description, dlg.FileName + "\\" + monitor.Description + ".csv") { Tag = monitor });
                        }
                        else
                        {
                            _multi_monitors_recordings.Add(new MultiTechRecordingData(monitor.Description, monitor.ChannelCount, dlg.FileName + "\\" + monitor.Description + ".csv") { Tag = monitor });
                        }
                        item.StartRecording();
                    }
                }
            };

            item.RecordingStopped += () =>
            {
                item.StopRecording();

                foreach (var sr in _single_monitors_recordings)
                {
                    sr.Dispose();
                }

                _single_monitors_recordings.Clear();

                foreach (var mr in _multi_monitors_recordings)
                {
                    mr.Dispose();
                }

                _multi_monitors_recordings.Clear();
            };
        }

        /// <summary>
        /// Initializes the tech valve.
        /// </summary>
        /// <param name="item">The valve item.</param>
        private void InitTechValveItem(ValveItem item)
        {
            item.StateChanged += async (x, state) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetValveState((ValveType)item.TechValve.Code, state);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set valve state command on '{0}'.", item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician set valve state command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the blower item.
        /// </summary>
        /// <param name="item">The blower item.</param>
        private void InitBlowerItem(BlowerItem item)
        {
            item.SetCommandClicked += async (_, isActive) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetBlowerState((PMR.Hardware.HardwareBlowerType)item.HardwareBlower.HardwareBlowerType.Code, isActive, item.HardwareBlower.Voltage);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, $"Error executing SetBlowerState command for blower {item.HardwareBlower.HardwareBlowerType.Name}.");
                    _eventLogger.Log(ex, $"Error executing SetBlowerState command for blower {item.HardwareBlower.HardwareBlowerType.Name}.");
                }
            };
        }

        /// <summary>
        /// Initializes the tech heater.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitTechHeater(HeaterItem item)
        {
            item.SetCommandClicked += async () =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetHeaterState((HeaterType)item.TechHeater.Code, item.SetPoint);
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, $"Error executing SetHeaterState command for heater {item.TechHeater.Name}.");
                    _eventLogger.Log(ex, $"Error executing SetHeaterState command for heater {item.TechHeater.Name}.");
                }
            };
        }

        /// <summary>
        /// Initializes the motor item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMotorItem(MotorItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = MotorDirection.Forward,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = MotorDirection.Backward,
                        });
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });
                    }
                    else if (action == MotorActionType.HomingForwardStarted || action == MotorActionType.HomingBackwardStarted)
                    {
                        item.HomingProgress = 0;
                        item.IsHoming = true;
                        item.IsHomingCompleted = false;

                        MachineOperator.StartMotorHoming(new MotorHomingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                            Speed = item.Speed,
                            Direction = action == MotorActionType.HomingBackwardStarted ? MotorDirection.Backward : MotorDirection.Forward
                        })
                        .Subscribe((response) =>
                        {
                            item.HomingMaximumProgress = response.MaxProgress;
                            item.HomingProgress = response.Progress;

                        }, (ex) =>
                         {

                             item.IsHoming = false;
                             item.IsHomingCompleted = true;

                         }, () =>
                         {

                             item.IsHoming = false;
                             item.IsHomingCompleted = true;

                         });
                    }
                    else if (action == MotorActionType.HomingStopped)
                    {
                        await MachineOperator.StopMotorHoming(new MotorAbortHomingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)item.HardwareMotorType.Code,
                        });

                        item.IsHoming = false;
                    }
                }
                catch (Exception ex)
                {
                    item.IsHomingCompleted = true;
                    item.IsHoming = false;
                    item.HomingProgress = 0;
                    item.HomingMaximumProgress = 0;
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the dispenser item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitDispenserItem(DispenserItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartDispenserJogging(new DispenserJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Direction = MotorDirection.Forward,
                            Speed = item.Speed,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await MachineOperator.StopDispenserJogging(new DispenserAbortJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await MachineOperator.StartDispenserJogging(new DispenserJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Direction = MotorDirection.Backward,
                            Speed = item.Speed,
                        });
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopDispenserJogging(new DispenserAbortJoggingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });
                    }
                    else if (action == MotorActionType.HomingForwardStarted || action == MotorActionType.HomingBackwardStarted)
                    {
                        item.HomingProgress = 0;
                        item.IsHoming = true;
                        item.IsHomingCompleted = false;

                        MachineOperator.StartDispenserHoming(new DispenserHomingRequest()
                        {
                            Index = item.TechDispenser.Code,
                            Speed = item.Speed,
                            Direction = action == MotorActionType.HomingBackwardStarted ? MotorDirection.Backward : MotorDirection.Forward
                        })
                        .Subscribe((response) =>
                        {

                            item.HomingMaximumProgress = response.MaxProgress;
                            item.HomingProgress = response.Progress;

                        }, (ex) =>
                        {

                            item.IsHoming = false;
                            item.IsHomingCompleted = true;

                        }, () =>
                        {

                            item.IsHoming = false;
                            item.IsHomingCompleted = true;

                        });
                    }
                    else if (action == MotorActionType.HomingStopped)
                    {
                        await MachineOperator.StopDispenserHoming(new DispenserAbortHomingRequest()
                        {
                            Index = item.TechDispenser.Code,
                        });

                        item.IsHoming = false;
                    }
                }
                catch (Exception ex)
                {
                    item.IsHomingCompleted = true;
                    item.IsHoming = false;
                    item.HomingProgress = 0;
                    item.HomingMaximumProgress = 0;
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the single graph item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="editor">The editor.</param>
        private void InitSingleGraphitem(SingleGraphItem item, SingleGraphElementEditor editor)
        {
            TechGraphController controller = new TechGraphController();
            controller.Range.AutoY = true;
            controller.Range.MinimumY = item.TechMonitor.Min;
            controller.Range.MaximumY = item.TechMonitor.Max;
            controller.Range.MaximumX = TimeSpan.FromSeconds(10);

            controller.AddDataSeries(new WpfDataSeries()
            {
                Stroke = Colors.DodgerBlue,
            });

            editor.InnerGraph.Controller = controller;

            item.Editor = editor;

            _singleControllers.Add(item, controller);

            item.RecordingStarted += () =>
            {
                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Title = "Save graph data as csv file";
                dlg.Filter = "CSV Files|*.csv";
                dlg.DefaultExt = ".csv";
                dlg.FileName = item.TechName;
                if (dlg.ShowDialog().Value)
                {
                    _single_graphs_recordings.Add(new SingleTechRecordingData(item.TechName, dlg.FileName) { Tag = item });
                    item.StartRecording();
                }
            };

            item.RecordingStopped += () =>
            {
                try
                {
                    item.StopRecording();

                    var graph_recording = _single_graphs_recordings.SingleOrDefault(x => x.Tag == item);

                    if (graph_recording != null)
                    {
                        _single_graphs_recordings.Remove(graph_recording);
                        graph_recording.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(LogManager.Log(ex).Message);
                }
            };
        }

        /// <summary>
        /// Initializes the multi graph item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <param name="editor">The editor.</param>
        private void InitMultiGraphItem(MultiGraphItem item, MultiGraphElementEditor editor)
        {
            TechGraphController controller = new TechGraphController(500);
            controller.Range.AutoY = true;
            controller.Range.MinimumY = item.TechMonitor.Min;
            controller.Range.MaximumY = item.TechMonitor.Max;
            controller.Range.MaximumX = TimeSpan.FromSeconds(10);

            for (int i = 0; i < item.TechMonitor.ChannelCount; i++)
            {
                controller.AddDataSeries(new WpfDataSeries()
                {
                    Stroke = ColorHelper.GetRandomColor(),
                    Name = item.TechMonitor.Name.First() + (i + 1).ToString(),
                });
            }

            editor.InnerGraph.Controller = controller;

            item.Editor = editor;

            _multiControllers.Add(item, controller);

            item.RecordingStarted += () =>
            {
                SaveFileDialog dlg = new SaveFileDialog();
                dlg.Title = "Save graph data as csv file";
                dlg.Filter = "CSV Files|*.csv";
                dlg.DefaultExt = ".csv";
                dlg.FileName = item.TechName;
                if (dlg.ShowDialog().Value)
                {
                    _multi_graph_recordings.Add(new MultiTechRecordingData(item.TechName, item.TechMonitor.ChannelCount, dlg.FileName) { Tag = item });
                    item.StartRecording();
                }
            };

            item.RecordingStopped += () =>
            {
                try
                {
                    item.StopRecording();

                    var graph_recording = _multi_graph_recordings.SingleOrDefault(x => x.Tag == item);

                    if (graph_recording != null)
                    {
                        _multi_graph_recordings.Remove(graph_recording);
                        graph_recording.Dispose();
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(LogManager.Log(ex).Message);
                }
            };
        }

        /// <summary>
        /// Initializes the thread motion item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitThreadMotionItem(ThreadMotionItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await MachineOperator.StartThreadJogging(new ThreadJoggingRequest()
                        {
                            Direction = MotorDirection.Forward,
                        });
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await MachineOperator.StartThreadJogging(new ThreadJoggingRequest()
                        {
                            Direction = MotorDirection.Backward,
                        });
                    }
                    else if (action == MotorActionType.ForwardReleased || action == MotorActionType.BackwardReleased)
                    {
                        await MachineOperator.StopThreadJogging(new ThreadAbortJoggingRequest());
                    }
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format(String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName)));
                    _eventLogger.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the motor group item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitMotorGroupItem(MotorGroupItem item)
        {
            item.ActionExecuted += async (x, action) =>
            {
                try
                {
                    CheckMachineOperator();

                    if (action == MotorActionType.ForwardPressed)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                            Direction = MotorDirection.Forward,
                        })));
                    }
                    else if (action == MotorActionType.ForwardReleased)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                        })));
                    }
                    else if (action == MotorActionType.BackwardPressed)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StartMotorJogging(new MotorJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                            Direction = MotorDirection.Backward,
                        })));
                    }
                    else if (action == MotorActionType.BackwardReleased)
                    {
                        await Task.WhenAll(item.TechMotors.Select(motor => MachineOperator.StopMotorJogging(new MotorAbortJoggingRequest()
                        {
                            MotorType = (PMR.Hardware.HardwareMotorType)motor.Code,
                        })));
                    }
                    //else if (action == MotorActionType.HomingStarted)
                    //{
                    //    item.HomingProgress = 0;
                    //    item.IsHoming = true;
                    //    item.IsHomingCompleted = false;

                    //    MachineOperator.StartMotorHoming(new MotorHomingRequest()
                    //    {
                    //        Code = item.TechMotor.Code
                    //    })
                    //    .Subscribe((response) =>
                    //    {

                    //        item.HomingMaximumProgress = response.Message.MaxProgress;
                    //        item.HomingProgress = response.Message.Progress;

                    //    }, () =>
                    //    {

                    //        item.IsHoming = false;
                    //        item.IsHomingCompleted = true;

                    //    });
                    //}
                    //else if (action == MotorActionType.HomingStopped)
                    //{
                    //    await MachineOperator.StopMotorHoming(new MotorAbortHomingRequest()
                    //    {
                    //        Code = item.TechMotor.Code,
                    //    });

                    //    item.IsHoming = false;
                    //}
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician command '{0}' on item '{1}'.", action, item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the digital out item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitDigitalOutItem(DigitalOutItem item)
        {
            item.ValueChanged += async (x, value) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetDigitalOut(new SetDigitalOutRequest() { InterfaceIO = (InterfaceIOs)item.TechIo.Code, Value = value });
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set digital out command on '{0}'.", item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician set digital out command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the controller item.
        /// </summary>
        /// <param name="item">The controller item.</param>
        private void InitControllerItem(ControllerItem item)
        {
            item.SetCommandClicked += async (_, __) =>
            {
                try
                {
                    CheckMachineOperator();
                    await MachineOperator.SetComponentValue(new SetComponentValueRequest()
                    {
                        Component = (ValueComponent)item.TechController.Code,
                        Value = item.Value
                    });
                }
                catch (Exception ex)
                {
                    LogManager.Log(ex, String.Format("Error executing technician set value component command on '{0}'.", item.TechName));
                    _eventLogger.Log(ex, String.Format("Error executing technician set value component command on '{0}'.", item.TechName));
                }
            };
        }

        /// <summary>
        /// Initializes the process parameter item.
        /// </summary>
        /// <param name="item">The item.</param>
        private void InitProcessParameterItem(ProcessParametersItem item)
        {
            item.PushParametersPressed += async (x, parameters) =>
            {
                try
                {
                    CheckMachineOperator();

                    using (_notification.PushTaskItem("Uploading process parameters..."))
                    {
                        try
                        {
                            await MachineOperator.UploadProcessParameters(parameters);
                        }
                        catch (Exception ex)
                        {
                            String msg = "Error uploading process parameters:" + Environment.NewLine + parameters.ToJsonString();
                            _eventLogger.Log(ex, msg);
                            LogManager.Log(ex, msg);
                            _notification.ShowError("Could not upload process parameters." + Environment.NewLine + ex.Message);
                        }
                    }
                }
                catch (Exception ex)
                {
                    _notification.ShowError(ex.Message);
                }
            };
        }

        /// <summary>
        /// Initializes the job runner item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <exception cref="NotImplementedException"></exception>
        private void InitJobRunnerItem(JobRunnerItem item)
        {
            item.StartJob += () =>
            {
                try
                {
                    CheckMachineOperator();

                    var handler = MachineOperator.Print(item.Job, item.ProcessParameters);

                    item.JobHandler = handler;

                    handler.StatusChanged += (x, status) =>
                    {
                        item.RunningJobStatus = status;
                    };

                    handler.Stopped += (x, e) =>
                    {
                        item.IsJobStarted = false;
                    };
                }
                catch (Exception ex)
                {
                    item.IsJobStarted = false;
                    _notification.ShowError(ex.Message);
                }
            };

            item.StopJob += () =>
            {
                if (item.JobHandler != null)
                {
                    item.JobHandler.Cancel();
                    item.JobHandler = null;
                }
            };
        }

        /// <summary>
        /// Checks the machine operator.
        /// </summary>
        /// <exception cref="InvalidOperationException">No machine connected.</exception>
        private void CheckMachineOperator()
        {
            if (MachineOperator == null || MachineOperator.State != Transport.TransportComponentState.Connected)
            {
                throw new InvalidOperationException("No machine connected.");
            }
        }

        #endregion

        #region Project Management

        /// <summary>
        /// Opens a file open dialog to select a project file.
        /// </summary>
        public void OpenProject()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Select Technician Project File";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                OpenProjectFile(dlg.FileName);
            }
        }

        /// <summary>
        /// Opens the specified project file path.
        /// </summary>
        /// <param name="fileName">File path.</param>
        public MachineTechViewProject OpenProjectFile(String fileName, bool load = true)
        {
            MachineTechViewProject project = null;

            try
            {
                project = MachineTechViewProject.Load(fileName);

                if (project.Tabs.Count == 0)
                {
                    LogManager.Log($"Error loading project file {fileName}. Trying to load using legacy project loader.", LogCategory.Warning);

                    MachineTechViewProjectTab tab = new MachineTechViewProjectTab();
                    tab.Name = "Untitled";
                    tab.Items.AddRange(project.Items);
                    project.Items.Clear();
                    project.Tabs.Add(tab);
                }

                if (load)
                {
                    LoadProject(project);
                    _lastTechProjectFile = fileName;
                }
            }
            catch (Exception ex)
            {
                LogManager.Log(ex, $"Error loading project file {fileName}.");
                _notification.ShowError("An error occurred while trying to load the tech board project file.");
            }

            return project;
        }

        /// <summary>
        /// Loads the specified project.
        /// </summary>
        /// <param name="project">The project.</param>
        public void LoadProject(MachineTechViewProject project, bool appendToCurrentProject = false)
        {
            using (_notification.PushTaskItem("Loading technician project file..."))
            {
                if (!appendToCurrentProject)
                {
                    Tabs.Clear();
                    _singleControllers.Clear();
                    _multiControllers.Clear();
                }

                foreach (var tab in project.Tabs)
                {
                    MachineTechTabVM t = new MachineTechTabVM();
                    t.Name = tab.Name;
                    Tabs.Add(t);

                    SelectedTab = t;

                    foreach (var item in tab.Items)
                    {
                        if (item is MotorGroupItem)
                        {
                            (item as MotorGroupItem).TechMotors = ObservablesStaticCollections.Instance.HardwareMotorTypes.Where(x => (item as MotorGroupItem).ItemsGuids.Contains(x.Guid)).ToObservableCollection();
                        }
                        else if (item is MonitorRecorderItem)
                        {
                            (item as MonitorRecorderItem).SetSelectedMonitors(ObservablesStaticCollections.Instance.TechMonitors.Where(x => (item as MonitorRecorderItem).SelectedMonitorsGuids.Contains(x.Guid)).ToList());
                        }

                        AddTechItem(item);
                    }
                }

                SelectedTab = Tabs.ElementAt(project.SelectedTabIndex);
            }
        }

        /// <summary>
        /// Opens the file save dialog for selecting a project file target.
        /// </summary>
        private void SaveAsProject()
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Title = "Select Technician Project Location";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                SaveProjectFile(dlg.FileName);
            }
        }

        /// <summary>
        /// Saves the current project to the specified file path.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        private void SaveProjectFile(String fileName)
        {
            using (_notification.PushTaskItem("Saving technician project file..."))
            {
                MachineTechViewProject project = GenerateProjectFile();
                project.Save(fileName);
                _lastTechProjectFile = fileName;
            }
        }

        /// <summary>
        /// Saves the current opened project file. If not project file is opened will call <see cref="SaveAsProject"/>.
        /// </summary>
        private void SaveProject()
        {
            if (File.Exists(_lastTechProjectFile))
            {
                SaveProjectFile(_lastTechProjectFile);
            }
            else
            {
                SaveAsProject();
            }
        }

        /// <summary>
        /// Generates a project file from the current element setup.
        /// </summary>
        /// <returns></returns>
        private MachineTechViewProject GenerateProjectFile()
        {
            MachineTechViewProject project = new MachineTechViewProject();
            project.SelectedTabIndex = Tabs.IndexOf(SelectedTab);

            foreach (var tab in Tabs)
            {
                MachineTechViewProjectTab pTab = new MachineTechViewProjectTab();
                pTab.Name = tab.Name;

                foreach (var element in tab.Elements)
                {
                    if (element.HostedElement is MotorGroupItem)
                    {
                        var group = element.HostedElement as MotorGroupItem;
                        group.ItemsGuids = group.TechMotors.Select(x => x.Guid).ToList();
                    }

                    (element.HostedElement as TechItem).SetBounds(element.GetBounds());
                    pTab.Items.Add(element.HostedElement as TechItem);
                }

                project.Tabs.Add(pTab);
            }

            return project;
        }

        /// <summary>
        /// Removes the specified tab.
        /// </summary>
        /// <param name="tab">The tab.</param>
        private void RemoveTab(MachineTechTabVM tab)
        {
            if (_notification.ShowQuestion("Are you sure you want to delete the selected tab?"))
            {
                Tabs.Remove(tab);
                SelectedTab = Tabs.LastOrDefault();

                if (SelectedTab == null)
                {
                    AddNewTab("Untitled");
                }
            }
        }

        /// <summary>
        /// Adds a new tab.
        /// </summary>
        private bool AddNewTab(String name = null)
        {
            if (Tabs.Count > 7)
            {
                _notification.ShowError("Cannot exceed the maximum number of 8 tabs. You can remove a tab, or create a new project.");
                return false;
            }

            if (name == null)
            {
                name = _notification.ShowTextInput("Enter tab name", "Tab Name", "Untitled");
            }

            if (!String.IsNullOrWhiteSpace(name))
            {
                MachineTechTabVM t = new MachineTechTabVM();
                t.Name = name;
                Tabs.Add(t);
                SelectedTab = t;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Creates a new project.
        /// </summary>
        private void CreateNewProject()
        {
            var to_remove = Tabs.ToList();

            if (AddNewTab())
            {
                _singleControllers.Clear();
                _multiControllers.Clear();

                foreach (var tab in to_remove)
                {
                    Tabs.Remove(tab);
                }
            }
        }

        /// <summary>
        /// Renames the tab.
        /// </summary>
        /// <param name="tab">The tab.</param>
        private void RenameTab()
        {
            if (SelectedTab != null)
            {
                var name = _notification.ShowTextInput("Enter tab name", "Tab Name", SelectedTab.Name);

                if (!String.IsNullOrWhiteSpace(name))
                {
                    SelectedTab.Name = name;
                }
            }
        }

        /// <summary>
        /// Imports a project file tabs to the current project.
        /// </summary>
        private void ImportProjectTabs()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Title = "Select Technician Project File";
            dlg.Filter = "Technician Project File|*.tpf";

            if (dlg.ShowDialog().Value)
            {
                var project = OpenProjectFile(dlg.FileName, false);

                if (project != null)
                {
                    var vm = new ImportProjectTabViewVM(project.Tabs, Path.GetFileNameWithoutExtension(dlg.FileName));
                    _notification.ShowModalDialog<ImportProjectTabViewVM, ImportProjectTabView>(vm, (x) =>
                    {
                        project.Tabs.Clear();
                        project.Tabs.AddRange(vm.Tabs.SynchedSource.ToList());
                        LoadProject(project, true);

                    }, () => { });
                }
            }
        }

        #endregion

        #region IStudioModuleVM

        public override void OnNavigatedTo()
        {
            base.OnNavigatedTo();
            //_singleControllers.ToList().ForEach(x => x.Value.ChangeRenderMode(true));
        }

        public override void OnNavigatedFrom()
        {
            base.OnNavigatedFrom();
            //_singleControllers.ToList().ForEach(x => x.Value.ChangeRenderMode(false));
        }

        public override void OnShuttingDown()
        {
            InvokeUINow(() =>
            {
                _settings.LastTechProjectFile = _lastTechProjectFile;
            });
        }

        public override void OnApplicationReady()
        {

        }

        #endregion

        #region Hardware Configuration

        private void SyncHardwareConfiguration()
        {
            var elements = Tabs.SelectMany(x => x.Elements).ToList();

            if (MachineOperator != null && MachineOperator.CurrentHardwareConfiguration != null)
            {
                var config = MachineOperator.CurrentHardwareConfiguration;

                foreach (var motorConfig in config.Motors)
                {
                    var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.ToInt32());

                    if (itemConfig != null)
                    {
                        motorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var pidConfig in config.PidControls)
                {
                    var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.ToInt32());

                    if (itemConfig != null)
                    {
                        pidConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var winderConfig in config.Winders)
                {
                    var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.ToInt32());

                    if (itemConfig != null)
                    {
                        winderConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var dancerConfig in config.Dancers)
                {
                    var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.ToInt32());

                    if (itemConfig != null)
                    {
                        dancerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var speedSensorConfig in config.SpeedSensors)
                {
                    var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.ToInt32());

                    if (itemConfig != null)
                    {
                        speedSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var blowerConfig in config.Blowers)
                {
                    var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerConfig.HardwareBlowerType.ToInt32());

                    if (itemConfig != null)
                    {
                        blowerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var breakSensorConfig in config.BreakSensors)
                {
                    var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.ToInt32());

                    if (itemConfig != null)
                    {
                        breakSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                _notification.ShowInfo("Visual elements synced to the last uploaded hardware configuration.");
            }
            else
            {
                ResetHardwareConfiguration();
            }
        }

        private async void UploadHardwareConfiguration()
        {
            if (MachineOperator != null)
            {
                using (_notification.PushTaskItem("Uploading hardware configuration..."))
                {
                    try
                    {
                        HardwareVersion hw = null;
                        Configuration config = null;

                        await Task.Factory.StartNew(() =>
                        {
                            using (ObservablesContext db = ObservablesContext.CreateDefault())
                            {
                                config = db.Adapter.GetConfiguration(x => x.Guid == ApplicationManager.Machine.ConfigurationGuid).Clone();
                                hw = db.Adapter.GetHardwareVersionByMachine(ApplicationManager.Machine.Guid).Clone();
                            }
                        });

                        foreach (var motorConfig in hw.HardwareMotors)
                        {
                            var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(motorConfig);
                            }
                        }

                        foreach (var pidConfig in hw.HardwarePidControls)
                        {
                            var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(pidConfig);
                            }
                        }

                        foreach (var winderConfig in hw.HardwareWinders)
                        {
                            var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(winderConfig);
                            }
                        }

                        foreach (var dancerConfig in hw.HardwareDancers)
                        {
                            var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(dancerConfig);
                            }
                        }

                        foreach (var speedSensorConfig in hw.HardwareSpeedSensors)
                        {
                            var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(speedSensorConfig);
                            }
                        }

                        foreach (var blowerConfig in hw.HardwareBlowers)
                        {
                            var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerConfig.HardwareBlowerType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(blowerConfig);
                            }
                        }

                        foreach (var breakSensorConfig in hw.HardwareBreakSensors)
                        {
                            var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.Code);

                            if (itemConfig != null)
                            {
                                itemConfig.MapPrimitivesTo(breakSensorConfig);
                            }
                        }

                        await MachineOperator.UploadHardwareConfiguration(hw, config);
                    }
                    catch (Exception ex)
                    {
                        LogManager.Log(ex, "Error uploading hardware configuration.");
                        _notification.ShowError("An error occurred while trying to upload the hardware configuration." + Environment.NewLine + ex.Message);
                    }
                }
            }
        }

        private async void UploadPartialHardwareConfiguration()
        {
            if (MachineOperator != null)
            {
                if (_notification.ShowQuestion("This will upload only visible elements across all tabs as a hardware configuration package. Are you sure?"))
                {
                    using (_notification.PushTaskItem("Uploading partial hardware configuration..."))
                    {
                        try
                        {
                            HardwareVersion hw = new HardwareVersion();
                            hw.Name = "Partial HW Configuration";
                            hw.Version = 1;

                            Configuration config = null;

                            foreach (var element in Tabs.SelectMany(x => x.Elements).ToList())
                            {
                                var item = element.HostedElement as TechItem;

                                if (item is MotorItem)
                                {
                                    hw.HardwareMotors.Add((item as MotorItem).HardwareMotor);
                                }
                                else if (item is DancerItem)
                                {
                                    hw.HardwareDancers.Add((item as DancerItem).HardwareDancer);
                                }
                                else if (item is PidItem)
                                {
                                    hw.HardwarePidControls.Add((item as PidItem).HardwarePid);
                                }
                                else if (item is BlowerItem)
                                {
                                    hw.HardwareBlowers.Add((item as BlowerItem).HardwareBlower);
                                }
                                else if (item is BreakSensorItem)
                                {
                                    hw.HardwareBreakSensors.Add((item as BreakSensorItem).HardwareBreakSensor);
                                }
                                else if (item is SpeedSensorItem)
                                {
                                    hw.HardwareSpeedSensors.Add((item as SpeedSensorItem).HardwareSpeedSensor);
                                }
                                else if (item is WinderItem)
                                {
                                    hw.HardwareWinders.Add((item as WinderItem).HardwareWinder);
                                }
                            }

                            await Task.Factory.StartNew(() =>
                            {
                                using (ObservablesContext db = ObservablesContext.CreateDefault())
                                {
                                    config = db.Adapter.GetConfiguration(x => x.Guid == ApplicationManager.Machine.ConfigurationGuid).Clone();
                                }
                            });

                            await MachineOperator.UploadHardwareConfiguration(hw, config);
                        }
                        catch (Exception ex)
                        {
                            LogManager.Log(ex, "Error uploading partial hardware configuration.");
                            _notification.ShowError("An error occurred while trying to upload the partial hardware configuration." + Environment.NewLine + ex.Message);
                        }
                    }
                }
            }
        }

        private async void ResetHardwareConfiguration(bool showMessage = true)
        {
            if (MachineOperator != null)
            {
                ObservablesContext db = ObservablesContext.CreateDefault();
                ObservablesContextAdapter adapter = new ObservablesContextAdapter(db);

                HardwareVersion hw = null;

                await Task.Factory.StartNew(() =>
                {
                    hw = adapter.GetHardwareVersionByMachine(ApplicationManager.Machine.Guid);
                });

                foreach (var motorConfig in hw.HardwareMotors)
                {
                    var itemConfig = MotorItem.MotorConfigurations.SingleOrDefault(x => x.HardwareMotorType.Code == motorConfig.HardwareMotorType.Code);

                    if (itemConfig != null)
                    {
                        motorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var pidConfig in hw.HardwarePidControls)
                {
                    var itemConfig = PidItem.PidConfigurations.SingleOrDefault(x => x.HardwarePidControlType.Code == pidConfig.HardwarePidControlType.Code);

                    if (itemConfig != null)
                    {
                        pidConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var winderConfig in hw.HardwareWinders)
                {
                    var itemConfig = WinderItem.WinderConfigurations.SingleOrDefault(x => x.HardwareWinderType.Code == winderConfig.HardwareWinderType.Code);

                    if (itemConfig != null)
                    {
                        winderConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var dancerConfig in hw.HardwareDancers)
                {
                    var itemConfig = DancerItem.DancerConfigurations.SingleOrDefault(x => x.HardwareDancerType.Code == dancerConfig.HardwareDancerType.Code);

                    if (itemConfig != null)
                    {
                        dancerConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var speedSensorConfig in hw.HardwareSpeedSensors)
                {
                    var itemConfig = SpeedSensorItem.SpeedSensorConfigurations.SingleOrDefault(x => x.HardwareSpeedSensorType.Code == speedSensorConfig.HardwareSpeedSensorType.Code);

                    if (itemConfig != null)
                    {
                        speedSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var blowerSensorConfig in hw.HardwareBlowers)
                {
                    var itemConfig = BlowerItem.BlowerConfigurations.SingleOrDefault(x => x.HardwareBlowerType.Code == blowerSensorConfig.HardwareBlowerType.Code);

                    if (itemConfig != null)
                    {
                        blowerSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                foreach (var breakSensorConfig in hw.HardwareBreakSensors)
                {
                    var itemConfig = BreakSensorItem.BreakSensorConfigurations.SingleOrDefault(x => x.HardwareBreakSensorType.Code == breakSensorConfig.HardwareBreakSensorType.Code);

                    if (itemConfig != null)
                    {
                        breakSensorConfig.MapPrimitivesTo(itemConfig);
                    }
                }

                if (showMessage)
                {
                    _notification.ShowInfo("Visual elements synced to hardware version " + hw.Name + ", " + hw.Version + ".");
                }
            }
        }

        #endregion

        #region Graphs

        public void ClearAllGraphs()
        {
            foreach (var controller in _singleControllers)
            {
                controller.Value.Clear();
            }

            foreach (var controller in _multiControllers)
            {
                controller.Value.Clear();
            }
        }

        #endregion
    }
}