From 7fb0d442ff7f7bd2264fc5ec7d179480a8ab3812 Mon Sep 17 00:00:00 2001 From: Philip-S-Martin Date: Mon, 18 Jan 2021 12:51:41 -0600 Subject: [PATCH 01/12] Beginning the port --- McIntyreAFC/Generator/Interval.cs | 59 +++ McIntyreAFC/Generator/Protocol.cs | 395 +++++++++++++++ McIntyreAFC/Generator/Sound.cs | 36 ++ McIntyreAFC/Generator/SoundInterval.cs | 32 ++ McIntyreAFC/Generator/Stimulus.cs | 21 + McIntyreAFC/Generator/StimulusInterval.cs | 33 ++ McIntyreAFC/McIntyreAFC.csproj | 15 + McIntyreAFC/SchedulinoDriver.cs | 457 ++++++++++++++++++ McIntyreAFC/SpreadsheetAFC.cs | 244 ++++++++++ ProtocolMaster.sln | 135 ++++++ ProtocolMaster/App.xaml | 3 + ...{ICallDropdown.cs => IPromptUserSelect.cs} | 6 +- .../{CallHandler.cs => PromptHandler.cs} | 2 +- .../Model/Protocol/ExtensionManager.cs | 7 +- .../Model/Protocol/ExtensionSystem.cs | 14 +- ProtocolMaster/Model/Protocol/IExtension.cs | 2 +- .../Interpreter/InterpreterManager.cs | 5 +- .../Protocol/NullExtensions/NullDriver.cs | 5 +- .../NullExtensions/NullInterpreter.cs | 5 +- ProtocolMaster/Model/SnippetsToComplete.cs | 26 + ProtocolMaster/Model/Video/BitMapHelper.cs | 22 - ProtocolMaster/Model/Video/WebCam.cs | 9 - ProtocolMaster/ProtocolMaster.csproj | 22 + ProtocolMaster/Theme/Icons/Cancel.png | Bin 0 -> 601 bytes ProtocolMaster/Theme/Icons/Load.png | Bin 0 -> 2973 bytes ProtocolMaster/Theme/Icons/Reset.png | Bin 0 -> 4485 bytes ProtocolMaster/Theme/Icons/Start.png | Bin 0 -> 2084 bytes ProtocolMaster/Theme/TabControlStyles.xaml | 63 +++ ProtocolMaster/View/Drive.xaml | 2 +- ProtocolMaster/View/Drive.xaml.cs | 9 +- ProtocolMaster/View/Log.xaml | 2 +- ProtocolMaster/View/MainWindow.xaml | 31 +- ProtocolMaster/View/MainWindow.xaml.cs | 72 +++ ProtocolMaster/View/PopupDropdown.xaml.cs | 4 +- ProtocolMaster/View/Timeline.xaml | 59 ++- ProtocolMaster/View/Timeline.xaml.cs | 148 ++---- ProtocolMasterCore/Prompt/DefaultPrompts.cs | 18 + .../Prompt/IPromptUserSelect.cs | 14 + .../Prompt/PromptTargetStore.cs | 18 + .../Protocol/Driver/DriverManager.cs | 26 + .../Protocol/Driver/DriverMeta.cs | 34 ++ ProtocolMasterCore/Protocol/Driver/IDriver.cs | 35 ++ .../Protocol/ExtensionManager.cs | 104 ++++ .../Protocol/ExtensionSystem.cs | 147 ++++++ ProtocolMasterCore/Protocol/IExtension.cs | 13 + ProtocolMasterCore/Protocol/IExtensionMeta.cs | 14 + .../Interpreter/ExcelDataInterpreter.cs | 18 + .../Protocol/Interpreter/IInterpreter.cs | 9 + .../Interpreter/InterpreterManager.cs | 31 ++ .../Protocol/Interpreter/InterpreterMeta.cs | 33 ++ .../Protocol/NullExtensions/NullDriver.cs | 21 + .../NullExtensions/NullInterpreter.cs | 22 + ProtocolMasterCore/Protocol/ProtocolEvent.cs | 65 +++ ProtocolMasterCore/ProtocolMasterCore.csproj | 15 + ProtocolMasterCore/Utility/AppEnvironment.cs | 54 +++ ProtocolMasterCore/Utility/Log.cs | 162 +++++++ ProtocolMasterCore/Utility/LogFile.cs | 70 +++ ProtocolMasterUWP/App.xaml | 17 + ProtocolMasterUWP/App.xaml.cs | 100 ++++ .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 1430 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 7700 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 2937 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 1647 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1255 bytes ProtocolMasterUWP/Assets/StoreLogo.png | Bin 0 -> 1451 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 3204 bytes ProtocolMasterUWP/MainPage.xaml | 26 + ProtocolMasterUWP/MainPage.xaml.cs | 41 ++ ProtocolMasterUWP/Observable/TreeItem.cs | 20 + ProtocolMasterUWP/Package.appxmanifest | 49 ++ ProtocolMasterUWP/Properties/AssemblyInfo.cs | 29 ++ ProtocolMasterUWP/Properties/Default.rd.xml | 31 ++ ProtocolMasterUWP/ProtocolMasterUWP.csproj | 248 ++++++++++ ProtocolMasterUWP/Theme/CancelButton.xaml | 97 ++++ ProtocolMasterUWP/Theme/Colors.xaml | 7 + ProtocolMasterUWP/View/LogView.xaml | 16 + ProtocolMasterUWP/View/LogView.xaml.cs | 27 ++ ProtocolMasterUWP/View/SelectionPaneView.xaml | 35 ++ .../View/SelectionPaneView.xaml.cs | 48 ++ .../View/SessionControlView.xaml | 27 ++ .../View/SessionControlView.xaml.cs | 35 ++ ProtocolMasterUWP/View/SessionView.xaml | 37 ++ ProtocolMasterUWP/View/SessionView.xaml.cs | 45 ++ ProtocolMasterUWP/View/TimelineView.xaml | 16 + ProtocolMasterUWP/View/TimelineView.xaml.cs | 27 ++ ProtocolMasterUWP/View/TopBarView.xaml | 32 ++ ProtocolMasterUWP/View/TopBarView.xaml.cs | 57 +++ .../ViewModel/SessionControlViewModel.cs | 107 ++++ .../ViewModel/SimpleBaseViewModel.cs | 16 + ProtocolMasterWPF/App.xaml | 43 ++ ProtocolMasterWPF/App.xaml.cs | 17 + ProtocolMasterWPF/AssemblyInfo.cs | 10 + ProtocolMasterWPF/MainWindow.xaml | 56 +++ ProtocolMasterWPF/MainWindow.xaml.cs | 28 ++ .../Properties/Settings.Designer.cs | 62 +++ .../Properties/Settings.settings | 15 + ProtocolMasterWPF/ProtocolMasterWPF.csproj | 38 ++ ProtocolMasterWPF/View/TitleBarView.xaml | 49 ++ ProtocolMasterWPF/View/TitleBarView.xaml.cs | 46 ++ Schedulino/SchedulinoDriver.cs | 32 +- Schedulino/SpreadsheetAFC.cs | 28 +- 101 files changed, 4272 insertions(+), 210 deletions(-) create mode 100644 McIntyreAFC/Generator/Interval.cs create mode 100644 McIntyreAFC/Generator/Protocol.cs create mode 100644 McIntyreAFC/Generator/Sound.cs create mode 100644 McIntyreAFC/Generator/SoundInterval.cs create mode 100644 McIntyreAFC/Generator/Stimulus.cs create mode 100644 McIntyreAFC/Generator/StimulusInterval.cs create mode 100644 McIntyreAFC/McIntyreAFC.csproj create mode 100644 McIntyreAFC/SchedulinoDriver.cs create mode 100644 McIntyreAFC/SpreadsheetAFC.cs rename ProtocolMaster/Model/{ICallDropdown.cs => IPromptUserSelect.cs} (51%) rename ProtocolMaster/Model/{CallHandler.cs => PromptHandler.cs} (91%) create mode 100644 ProtocolMaster/Model/SnippetsToComplete.cs delete mode 100644 ProtocolMaster/Model/Video/BitMapHelper.cs delete mode 100644 ProtocolMaster/Model/Video/WebCam.cs create mode 100644 ProtocolMaster/Theme/Icons/Cancel.png create mode 100644 ProtocolMaster/Theme/Icons/Load.png create mode 100644 ProtocolMaster/Theme/Icons/Reset.png create mode 100644 ProtocolMaster/Theme/Icons/Start.png create mode 100644 ProtocolMaster/Theme/TabControlStyles.xaml create mode 100644 ProtocolMasterCore/Prompt/DefaultPrompts.cs create mode 100644 ProtocolMasterCore/Prompt/IPromptUserSelect.cs create mode 100644 ProtocolMasterCore/Prompt/PromptTargetStore.cs create mode 100644 ProtocolMasterCore/Protocol/Driver/DriverManager.cs create mode 100644 ProtocolMasterCore/Protocol/Driver/DriverMeta.cs create mode 100644 ProtocolMasterCore/Protocol/Driver/IDriver.cs create mode 100644 ProtocolMasterCore/Protocol/ExtensionManager.cs create mode 100644 ProtocolMasterCore/Protocol/ExtensionSystem.cs create mode 100644 ProtocolMasterCore/Protocol/IExtension.cs create mode 100644 ProtocolMasterCore/Protocol/IExtensionMeta.cs create mode 100644 ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs create mode 100644 ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs create mode 100644 ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs create mode 100644 ProtocolMasterCore/Protocol/Interpreter/InterpreterMeta.cs create mode 100644 ProtocolMasterCore/Protocol/NullExtensions/NullDriver.cs create mode 100644 ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs create mode 100644 ProtocolMasterCore/Protocol/ProtocolEvent.cs create mode 100644 ProtocolMasterCore/ProtocolMasterCore.csproj create mode 100644 ProtocolMasterCore/Utility/AppEnvironment.cs create mode 100644 ProtocolMasterCore/Utility/Log.cs create mode 100644 ProtocolMasterCore/Utility/LogFile.cs create mode 100644 ProtocolMasterUWP/App.xaml create mode 100644 ProtocolMasterUWP/App.xaml.cs create mode 100644 ProtocolMasterUWP/Assets/LockScreenLogo.scale-200.png create mode 100644 ProtocolMasterUWP/Assets/SplashScreen.scale-200.png create mode 100644 ProtocolMasterUWP/Assets/Square150x150Logo.scale-200.png create mode 100644 ProtocolMasterUWP/Assets/Square44x44Logo.scale-200.png create mode 100644 ProtocolMasterUWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 ProtocolMasterUWP/Assets/StoreLogo.png create mode 100644 ProtocolMasterUWP/Assets/Wide310x150Logo.scale-200.png create mode 100644 ProtocolMasterUWP/MainPage.xaml create mode 100644 ProtocolMasterUWP/MainPage.xaml.cs create mode 100644 ProtocolMasterUWP/Observable/TreeItem.cs create mode 100644 ProtocolMasterUWP/Package.appxmanifest create mode 100644 ProtocolMasterUWP/Properties/AssemblyInfo.cs create mode 100644 ProtocolMasterUWP/Properties/Default.rd.xml create mode 100644 ProtocolMasterUWP/ProtocolMasterUWP.csproj create mode 100644 ProtocolMasterUWP/Theme/CancelButton.xaml create mode 100644 ProtocolMasterUWP/Theme/Colors.xaml create mode 100644 ProtocolMasterUWP/View/LogView.xaml create mode 100644 ProtocolMasterUWP/View/LogView.xaml.cs create mode 100644 ProtocolMasterUWP/View/SelectionPaneView.xaml create mode 100644 ProtocolMasterUWP/View/SelectionPaneView.xaml.cs create mode 100644 ProtocolMasterUWP/View/SessionControlView.xaml create mode 100644 ProtocolMasterUWP/View/SessionControlView.xaml.cs create mode 100644 ProtocolMasterUWP/View/SessionView.xaml create mode 100644 ProtocolMasterUWP/View/SessionView.xaml.cs create mode 100644 ProtocolMasterUWP/View/TimelineView.xaml create mode 100644 ProtocolMasterUWP/View/TimelineView.xaml.cs create mode 100644 ProtocolMasterUWP/View/TopBarView.xaml create mode 100644 ProtocolMasterUWP/View/TopBarView.xaml.cs create mode 100644 ProtocolMasterUWP/ViewModel/SessionControlViewModel.cs create mode 100644 ProtocolMasterUWP/ViewModel/SimpleBaseViewModel.cs create mode 100644 ProtocolMasterWPF/App.xaml create mode 100644 ProtocolMasterWPF/App.xaml.cs create mode 100644 ProtocolMasterWPF/AssemblyInfo.cs create mode 100644 ProtocolMasterWPF/MainWindow.xaml create mode 100644 ProtocolMasterWPF/MainWindow.xaml.cs create mode 100644 ProtocolMasterWPF/Properties/Settings.Designer.cs create mode 100644 ProtocolMasterWPF/Properties/Settings.settings create mode 100644 ProtocolMasterWPF/ProtocolMasterWPF.csproj create mode 100644 ProtocolMasterWPF/View/TitleBarView.xaml create mode 100644 ProtocolMasterWPF/View/TitleBarView.xaml.cs diff --git a/McIntyreAFC/Generator/Interval.cs b/McIntyreAFC/Generator/Interval.cs new file mode 100644 index 0000000..c1aa28f --- /dev/null +++ b/McIntyreAFC/Generator/Interval.cs @@ -0,0 +1,59 @@ +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + class Interval : IEquatable, IComparable + { + public uint begin, end; + + public Interval(uint begin, uint end) + { + this.begin = begin; + this.end = end; + } + + public int CompareTo(Interval other) + { + if (other == null) return 1; + else return this.begin.CompareTo(other.begin); + } + + public bool Equals(Interval other) + { + return (this.begin == other.begin && this.end == other.end); + } + + public bool Overlap(Interval other) + { + if ((this.begin < other.end && this.end >= other.end) || + (other.begin < this.end && other.end >= this.end) || + (this.begin < other.begin && this.end >= other.begin) || + (other.begin < this.begin && other.end >= this.begin)) + return true; + else return false; + } + public bool BufferOverlap(Interval other, uint bufferleft, uint bufferright) + { + uint tBegin = this.begin - bufferleft; + uint tEnd = this.end + bufferright; + uint oBegin = other.begin - bufferleft; + uint oEnd = other.end + bufferright; + if ((tBegin < oEnd && tEnd >= oEnd) || + (oBegin < tEnd && oEnd >= tEnd) || + (tBegin < oBegin && tEnd >= oBegin) || + (oBegin < tBegin && oEnd >= tBegin)) + return true; + else return false; + } + public virtual ProtocolEvent ToProtocolEvent() + { + return null; + } + + } +} diff --git a/McIntyreAFC/Generator/Protocol.cs b/McIntyreAFC/Generator/Protocol.cs new file mode 100644 index 0000000..232a408 --- /dev/null +++ b/McIntyreAFC/Generator/Protocol.cs @@ -0,0 +1,395 @@ +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + // Pairing Handler: calls the delivery handler for + delegate void SoundHandler(); + delegate void PairingHandler(Stimulus stim); + delegate Interval WindowHandler(SoundInterval si); + delegate void DeliveryHandler(Stimulus stim, UInt32 begin, UInt32 end); + class Protocol + { + public string name, owner, sound_order; + public uint extra_time, num_presounds, num_exp_sounds, interval_min, interval_max, exp_begin, exp_end; + public List Sounds { get; set; } + public List Stimuli { get; set; } + + private Dictionary soundDictionary; + private Dictionary deliveryDictionary; + private Dictionary pairingDictionary; + private Dictionary windowDictionary; + + private int randomSeed; + Random random; + public Protocol() + { + Sounds = new List(); + Stimuli = new List(); + soundDictionary = new Dictionary() + { + { "Alternate", GenerateSoundsAlternating }, + { "Block", GenerateSoundsBlock } + }; + deliveryDictionary = new Dictionary() + { + { "Front", FrontDelivery }, + { "Back", BackDelivery }, + { "Center", CenteredDelivery }, + { "Throughout", ThroughoutDelivery }, + { "Distributed", DistributedDelivery }, + { "Random", RandomDelivery }, + { "RandomSynced", RandomSyncedDelivery } + }; + pairingDictionary = new Dictionary() + { + { "First", FrontPairing }, + { "Last", BackPairing }, + { "Random", RandomPairing }, + { "Unpaired", UnpairedPairing } + }; + windowDictionary = new Dictionary() + { + {"Before", BeforeWindow }, + {"After", AfterWindow }, + {"During", DuringWindow } + }; + } + public void GrowExtraTimeIfNeeded(uint byAmount) + { + if (byAmount > extra_time) extra_time = byAmount; + } + public List Generate() + { + random = new Random(); + randomSeed = random.Next(); + soundDictionary[sound_order](); + GenerateStimuli(); + List allIntervals = new List(); + foreach (Sound sound in Sounds) + { + allIntervals.AddRange(sound.presoundIntervals); + allIntervals.AddRange(sound.expSoundIntervals); + } + foreach (Stimulus stim in Stimuli) + { + allIntervals.AddRange(stim.stimIntervals); + } + allIntervals.Sort(); + List allEvents = new List(allIntervals.Count); + foreach (Interval i in allIntervals) + allEvents.Add(i.ToProtocolEvent()); + return allEvents; + } + private void GenerateSoundsAlternating() + { + uint timeMs = extra_time; + SoundInterval prevSoundInterval = null; + for (int i = 0; i < num_presounds; i++) + { + foreach (Sound s in Sounds) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.presoundIntervals.Add(si); + timeMs += s.duration; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + } + exp_begin = timeMs; + uint potential_end = timeMs; + for (int i = 0; i < num_exp_sounds; i++) + { + foreach (Sound s in Sounds) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.expSoundIntervals.Add(si); + timeMs += s.duration; + potential_end = timeMs; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + } + exp_end = potential_end; + } + private void GenerateSoundsBlock() + { + uint timeMs = extra_time; + SoundInterval prevSoundInterval = null; + + foreach (Sound s in Sounds) + { + for (int i = 0; i < num_presounds; i++) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.presoundIntervals.Add(si); + timeMs += s.duration; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + exp_begin = timeMs; + uint potential_end = timeMs; + for (int i = 0; i < num_exp_sounds; i++) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.expSoundIntervals.Add(si); + timeMs += s.duration; + potential_end = timeMs; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + exp_end = potential_end; + } + } + + private void GeneratePreSounds(ref UInt32 timeMs, ref SoundInterval prevSoundInterval) + { + for (int i = 0; i < num_presounds; i++) + { + foreach (Sound s in Sounds) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.presoundIntervals.Add(si); + timeMs += s.duration; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + } + } + private void GenerateExperimentalSounds(ref UInt32 timeMs, ref SoundInterval prevSoundInterval) + { + exp_begin = timeMs; + uint potential_end = timeMs; + for (int i = 0; i < num_exp_sounds; i++) + { + foreach (Sound s in Sounds) + { + SoundInterval si = new SoundInterval(s, prevSoundInterval, timeMs, timeMs + s.duration); + s.expSoundIntervals.Add(si); + timeMs += s.duration; + potential_end = timeMs; + timeMs += (UInt32)random.Next((int)interval_min, (int)interval_max); + if (prevSoundInterval != null) prevSoundInterval.next = si; + prevSoundInterval = si; + } + } + exp_end = potential_end; + } + private void GenerateStimuli() + { + foreach (Sound s in Sounds) + { + foreach (Stimulus stim in s.stimuli) + { + pairingDictionary[stim.stim_sound_pairing](stim); + } + } + } + private void FrontPairing(Stimulus stim) + { + int iterations = (int)Math.Min(stim.num_paired_sounds, stim.sound.expSoundIntervals.Count); + for (int i = 0; i < iterations; i++) + { + Interval interval = windowDictionary[stim.stim_sound_window](stim.sound.expSoundIntervals[i]); + deliveryDictionary[stim.stim_delivery](stim, interval.begin, interval.end); + } + } + private void BackPairing(Stimulus stim) + { + int iterations = (int)Math.Min(stim.num_paired_sounds, stim.sound.expSoundIntervals.Count); + int max = stim.sound.expSoundIntervals.Count - 1; + for (int i = 0; i < iterations; i++) + { + Interval interval = windowDictionary[stim.stim_sound_window](stim.sound.expSoundIntervals[max - i]); + deliveryDictionary[stim.stim_delivery](stim, interval.begin, interval.end); + } + } + private void RandomPairing(Stimulus stim) + { + int iterations = (int)Math.Min(stim.num_paired_sounds, stim.sound.expSoundIntervals.Count); + int max = stim.sound.expSoundIntervals.Count; + List randomList = new List(iterations); + for (int i = 0; i < iterations; i++) + { + int randomIndex; + do + { + randomIndex = random.Next(max); + } + while (randomList.Contains(randomIndex)); + randomList.Add(randomIndex); + Interval interval = windowDictionary[stim.stim_sound_window](stim.sound.expSoundIntervals[randomIndex]); + deliveryDictionary[stim.stim_delivery](stim, interval.begin, interval.end); + } + } + private void UnpairedPairing(Stimulus stim) + { + List soundIntervals = stim.sound.expSoundIntervals; + if (soundIntervals != null) + deliveryDictionary[stim.stim_delivery](stim, soundIntervals[0].begin, soundIntervals[soundIntervals.Count - 1].end); + else + deliveryDictionary[stim.stim_delivery](stim, exp_begin, exp_end); + } + private Interval BeforeWindow(SoundInterval si) + { + if (si.previous != null) + return new Interval(si.previous.end, si.begin); + else + { + return new Interval(si.begin - (UInt32)random.Next((int)interval_min, (int)interval_max), si.end); + } + } + private Interval AfterWindow(SoundInterval si) + { + if (si.next != null) + return new Interval(si.end, si.next.begin); + else + { + return new Interval(si.end, si.end + (UInt32)random.Next((int)interval_min, (int)interval_max)); + } + } + private Interval DuringWindow(SoundInterval si) + { + return (Interval)si; + } + private void FrontDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + uint timeMs = begin; + timeMs += (uint)random.Next((int)stim.delay_min, (int)stim.delay_max); + for (int i = 0; i < stim.stims_per_sound; i++) + { + uint duration = (uint)random.Next((int)stim.dur_min, (int)stim.dur_max); + stim.stimIntervals.Add(new StimulusInterval(stim, timeMs, timeMs + duration)); + timeMs += duration; + timeMs += (uint)random.Next((int)stim.interval_min, (int)stim.interval_max); + } + } + private void BackDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + uint timeMs = end; + timeMs -= (uint)random.Next((int)stim.delay_min, (int)stim.delay_max); + for (int i = 0; i < stim.stims_per_sound; i++) + { + uint duration = (uint)random.Next((int)stim.dur_min, (int)stim.dur_max); + stim.stimIntervals.Add(new StimulusInterval(stim, timeMs - duration, timeMs)); + timeMs -= duration; + timeMs -= (uint)random.Next((int)stim.interval_min, (int)stim.interval_max); + } + } + private void ThroughoutDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + stim.stimIntervals.Add(new StimulusInterval(stim, begin, end)); + } + private void RandomDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + for (int i = 0; i < stim.stims_per_sound; i++) + { + StimulusInterval interval; + do + { + uint duration = (uint)random.Next((int)stim.dur_min, (int)stim.dur_max); + uint randomStim = (uint)random.Next((int)begin, (int)(end - duration)); + interval = new StimulusInterval(stim, randomStim, randomStim + duration); + } while (stim.stimIntervals.Exists(a => a.Overlap(interval))); + stim.stimIntervals.Add(interval); + } + } + private void RandomSyncedDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + Random syncRandom = new Random(randomSeed + begin.GetHashCode() + end.GetHashCode()); + List bufferIntervals = new List(); + for (int i = 0; i < stim.stims_per_sound; i++) + { + StimulusInterval interval; + Interval bufferInterval; + do + { + uint duration = (uint)syncRandom.Next((int)(stim.dur_min), (int)(stim.dur_max)); + uint buffer = stim.dur_max + stim.interval_min; + uint randomStim = ((uint)syncRandom.Next((int)(begin), (int)(end - buffer))); + bufferInterval = new Interval(randomStim, randomStim + buffer); + interval = new StimulusInterval(stim, randomStim, randomStim + duration); + } while (bufferIntervals.Exists(a => a.Overlap(bufferInterval))); + stim.stimIntervals.Add(interval); + bufferIntervals.Add(bufferInterval); + } + } + private void CenteredDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + uint[] duration = new uint[stim.stims_per_sound]; + uint[] between = new uint[stim.stims_per_sound]; + uint totalTime = 0; + for (int i = 0; i < stim.stims_per_sound; i++) + { + duration[i] = (uint)random.Next((int)stim.dur_min, (int)stim.dur_max); + between[i] = (uint)random.Next((int)stim.interval_min, (int)stim.interval_max); + totalTime += duration[i] + between[i]; + } + uint timeMs; + if (end-begin > totalTime) + timeMs = begin + ((end - begin) - totalTime) / 2; + else timeMs = begin - (totalTime - (end - begin)) / 2; + for (int i = 0; i < stim.stims_per_sound; i++) + { + stim.stimIntervals.Add(new StimulusInterval(stim, timeMs, timeMs + duration[i])); + timeMs += duration[i]; + timeMs += between[i]; + } + } + private void DistributedDelivery(Stimulus stim, UInt32 begin, UInt32 end) + { + uint[] duration = new uint[stim.stims_per_sound]; + uint totalDuration = 0; + for (int i = 0; i < stim.stims_per_sound; i++) + { + duration[i] = (uint)random.Next((int)stim.dur_min, (int)stim.dur_max); + totalDuration += duration[i]; + } + uint delay = (uint)random.Next((int)stim.delay_min, (int)stim.delay_max); + uint sub = begin + totalDuration + delay; + uint between; + uint timeMs = begin; + if (sub < end) + { + between = (end - sub) / stim.stims_per_sound; + timeMs += delay; + } + else + between = 0; + for (int i = 0; i < stim.stims_per_sound; i++) + { + stim.stimIntervals.Add(new StimulusInterval(stim, timeMs, timeMs + duration[i])); + timeMs += duration[i]; + timeMs += between; + } + } + public Protocol CloneProtocolWithoutEvents() + { + Protocol clone = new Protocol(); + foreach (Sound s in Sounds) + { + clone.Sounds.Add(s.CloneSoundWithoutProtocolData()); + } + clone.name = this.name; + clone.owner = this.owner; + clone.extra_time = this.extra_time; + clone.num_presounds = this.num_presounds; + clone.num_exp_sounds = this.num_exp_sounds; + clone.interval_min = this.interval_min; + clone.interval_max = this.interval_max; + clone.exp_begin = this.exp_begin; + clone.exp_end = this.exp_end; + clone.sound_order = this.sound_order; + return clone; + } + } +} diff --git a/McIntyreAFC/Generator/Sound.cs b/McIntyreAFC/Generator/Sound.cs new file mode 100644 index 0000000..b9595b8 --- /dev/null +++ b/McIntyreAFC/Generator/Sound.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + class Sound + { + public string name, handler, behavior_pin, duration_pin, sound_id; + public uint duration; + public List presoundIntervals; + public List expSoundIntervals; + public List stimuli; + + public Sound(string name) + { + this.name = name; + presoundIntervals = new List(); + expSoundIntervals = new List(); + stimuli = new List(); + } + + public Sound CloneSoundWithoutProtocolData() + { + Sound clone = new Sound(this.name); + clone.handler = this.handler; + clone.behavior_pin = this.behavior_pin; + clone.duration_pin = this.duration_pin; + clone.sound_id = this.sound_id; + clone.duration = this.duration; + return clone; + } + } +} diff --git a/McIntyreAFC/Generator/SoundInterval.cs b/McIntyreAFC/Generator/SoundInterval.cs new file mode 100644 index 0000000..f0694f8 --- /dev/null +++ b/McIntyreAFC/Generator/SoundInterval.cs @@ -0,0 +1,32 @@ +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + class SoundInterval : Interval + { + public Sound sound; + public SoundInterval previous; + public SoundInterval next; + public SoundInterval(Sound sound, SoundInterval previous, uint begin, uint end):base(begin, end) + { + this.sound = sound; + this.previous = previous; + } + + + public override ProtocolEvent ToProtocolEvent() + { + return new ProtocolEvent(sound.handler, (sound.name), + new KeyValuePair("SignalPin", sound.behavior_pin), + new KeyValuePair("DurationPin", sound.duration_pin), + new KeyValuePair("Value", sound.sound_id), + new KeyValuePair("TimeStartMs", begin.ToString()), + new KeyValuePair("TimeEndMs", end.ToString())); + } + } +} diff --git a/McIntyreAFC/Generator/Stimulus.cs b/McIntyreAFC/Generator/Stimulus.cs new file mode 100644 index 0000000..2d06013 --- /dev/null +++ b/McIntyreAFC/Generator/Stimulus.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + class Stimulus + { + public string name, sound_group, stim_sound_pairing, stim_sound_window, stim_delivery, handler, behavior_pin, duration_pin; + public uint num_paired_sounds, stims_per_sound, dur_min, dur_max, interval_min, interval_max, delay_min, delay_max; + public Sound sound; + public List stimIntervals; + + public Stimulus() + { + stimIntervals = new List(); + } + } +} diff --git a/McIntyreAFC/Generator/StimulusInterval.cs b/McIntyreAFC/Generator/StimulusInterval.cs new file mode 100644 index 0000000..5d6862d --- /dev/null +++ b/McIntyreAFC/Generator/StimulusInterval.cs @@ -0,0 +1,33 @@ +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Schedulino.Generator +{ + class StimulusInterval : Interval + { + public Stimulus stim; + public StimulusInterval(Stimulus stim, uint begin, uint end) : base(begin, end) + { + this.stim = stim; + } + public override ProtocolEvent ToProtocolEvent() + { + if (stim.sound != null) + return new ProtocolEvent(stim.handler, (stim.name), (stim.sound.name), + new KeyValuePair("SignalPin", stim.behavior_pin), + new KeyValuePair("DurationPin", stim.duration_pin), + new KeyValuePair("TimeStartMs", begin.ToString()), + new KeyValuePair("TimeEndMs", end.ToString())); + else + return new ProtocolEvent(stim.handler, (stim.name + "\n(Stim)"), + new KeyValuePair("SignalPin", stim.behavior_pin), + new KeyValuePair("DurationPin", stim.duration_pin), + new KeyValuePair("TimeStartMs", begin.ToString()), + new KeyValuePair("TimeEndMs", end.ToString())); + } + } +} diff --git a/McIntyreAFC/McIntyreAFC.csproj b/McIntyreAFC/McIntyreAFC.csproj new file mode 100644 index 0000000..7923688 --- /dev/null +++ b/McIntyreAFC/McIntyreAFC.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.1 + + + + + + + + + + + diff --git a/McIntyreAFC/SchedulinoDriver.cs b/McIntyreAFC/SchedulinoDriver.cs new file mode 100644 index 0000000..abc3fce --- /dev/null +++ b/McIntyreAFC/SchedulinoDriver.cs @@ -0,0 +1,457 @@ +using System.ComponentModel.Composition; +using System.IO.Ports; +using System.Collections.Generic; +using System.Threading; +using System.Net.Http.Headers; +using System.Collections.Concurrent; +using System; +using System.Xml; +using System.Diagnostics; +using ProtocolMasterCore.Protocol.Driver; +using ProtocolMasterCore.Prompt; +using ProtocolMasterCore.Protocol; +using ProtocolMasterCore.Utility; + +namespace McIntyreAFC +{ + [DriverMeta("McIntyreAFC", "1.1", "DigitalDuration", "DigitalPulse", "DigitalStringDuration")] + public class Schedulino : IDriver, IPromptUserSelect + { + List schedule; + int scheduleIndex = 0; + private enum ScheduleState { SETUP = 0, RUNNING, DONE } + ScheduleState _state; + uint _run_time; + byte _serial_available; + private uint _capacity; + + SerialPort serial; + public SerialPort Serial { get => serial; set => serial = value; } + public UserSelectHandler UserSelectPrompt { private get; set; } + + // Data processing handlers + delegate void Handler(ProtocolEvent item); + readonly Dictionary handlers; + readonly Handler invalidKeyHander; + + // Arduino Serial receivers + delegate void Receiver(); + readonly Dictionary receivers; + readonly Receiver invalidKeyReceiver; + + public Schedulino() + { + schedule = new List(); + _state = ScheduleState.SETUP; + _run_time = 0; + _serial_available = 0; + _capacity = 0; + + // Handlers for processing data + handlers = new Dictionary + { + { "DigitalDuration", DigitalDurationHandler }, + { "DigitalPulse", DigitalPulseHandler }, + { "DigitalStringDuration", DigitalStringDurationHandler } + }; + invalidKeyHander = InvalidKeyHandler; + + // Reveivers for receiving data from Arduino + receivers = new Dictionary + { + { 'C', CapacityReceiver }, + { 'D', DoneReceiver }, + { 'E', ErrorReceiver }, + { 'P', ReportReceiver }, + { 'R', ReplyReceiver } + }; + invalidKeyReceiver = InvalidKeyReceiver; + } + + private bool isCanceled; + public bool IsCanceled + { + get => isCanceled; + set + { + isCanceled = true; + Log.Error("Cancelling McIntyreAFC"); + //scheduleIndex = schedule.Count; + Serial.Write("X"); + while (serial.BytesToRead >= 1) + { + int read = Serial.ReadByte(); + if (read != -1) + Receive((char)read); + } + //Serial.Close();} } + } + } + + public bool Setup(List dataList) + { + foreach (ProtocolEvent data in dataList) + { + Handle(data); + } + schedule.Sort(); + + string[] portOptions = SerialPort.GetPortNames(); + string port; + if (portOptions.Length == 0) + { + return false; + } + else if (portOptions.Length == 1) + port = portOptions[0]; + else + port = UserSelectPrompt(portOptions); + + // SerialPort setup + Serial = new SerialPort + { + RtsEnable = true, + DtrEnable = true, + PortName = port, + BaudRate = 9600, + NewLine = "\n" + }; + Serial.Open(); + + // Handshake + // Read serial buffer until arduino tells us how much capacity it has + while (_state != ScheduleState.DONE && _capacity == 0) + { + ReadSerialBuffer(); + } + // Pre-Load as many events as possible + while (_state != ScheduleState.DONE && _capacity > 0 && scheduleIndex < schedule.Count) + { + SendNextEvent(); + ReadSerialBuffer(); + } + return true; + } + + public void Start() + { + // Send start signal + if (_state != ScheduleState.DONE) + { + while (serial.IsOpen && !TrySendStartSignal()) + { + ReadSerialBuffer(); + } + } + // Send events and send serial data until Done event is recieved + while (_state == ScheduleState.RUNNING) + { + SendNextEvent(); + ReadSerialBuffer(); + } + if (serial.IsOpen) Serial.Close(); + } + private void SendNextEvent() + { + if (serial.IsOpen && _capacity > 0 && _serial_available >= 7 && scheduleIndex < schedule.Count) + { + byte[] bytes = schedule[scheduleIndex].ToBytes(); + Serial.Write("E"); + Serial.Write(bytes, 0, bytes.Length); + _serial_available -= 7; + _capacity--; + scheduleIndex++; + } + } + + private bool TrySendStartSignal() + { + if (serial.IsOpen && _serial_available > 0) + { + Serial.Write("S"); + _state = ScheduleState.RUNNING; + return true; + } + return false; + } + + private void ReadSerialBuffer() + { + while (serial.IsOpen && serial.BytesToRead >= 1) + { + int read = Serial.ReadByte(); + if (read != -1) + Receive((char)read); + } + } + + private void Handle(ProtocolEvent data) + { + //Log.Error("Handling: " + data.Handler); + if (handlers.TryGetValue(data.Handler, out Handler thisKeyHandler)) + { + thisKeyHandler(data); + } + else + { + invalidKeyHander(data); + } + } + + #region Handler Functions + + private byte PinMnemonicToNumeric(string pinstring) + { + try + { + byte value = Convert.ToByte(pinstring); + return value; + } + catch (FormatException) + { + if (pinstring == "A0") return 14; + if (pinstring == "A1") return 15; + if (pinstring == "A2") return 16; + if (pinstring == "A3") return 17; + if (pinstring == "A4") return 18; + if (pinstring == "A5") return 19; + throw new FormatException("Invalid Pinstring"); + } + } + + private byte[] PinSetParseHelper(string pinstring) + { + if (pinstring.Length == 0) return null; + + string[] pinstrings = pinstring.Split(','); + byte[] pins = new byte[pinstrings.Length]; + for (int i = 0; i < pinstrings.Length; i++) + { + pins[i] = PinMnemonicToNumeric(pinstrings[i]); + } + return pins; + } + + private byte[] PinRangeParseHelper(string pinstring) + { + // there should only be 2 strings, add error checking for that + string[] pinstrings = pinstring.Split(':'); + byte[] pins = new byte[pinstrings.Length]; + for (int i = 0; i < pinstrings.Length; i++) + { + pins[i] = PinMnemonicToNumeric(pinstrings[i]); + } + Array.Sort(pins); + return pins; + } + + private byte PinStateStringHelper(byte value, byte place) + { + return (byte)((value >> place) & 1); + } + private void DigitalDurationHandler(ProtocolEvent item) + { + item.Arguments.TryGetValue("SignalPin", out string commStr); + byte[] pins_signal = PinSetParseHelper(commStr); + item.Arguments.TryGetValue("DurationPin", out commStr); + byte[] pins_dur = PinSetParseHelper(commStr); + item.Arguments.TryGetValue("TimeStartMs", out commStr); + uint time_start = Convert.ToUInt32(commStr); + item.Arguments.TryGetValue("TimeEndMs", out commStr); + uint time_end = Convert.ToUInt32(commStr); + + if (pins_signal != null) + for (int i = 0; i < pins_signal.Length; i++) + { + schedule.Add(new SchedulePinState(pins_signal[i], (byte)1, time_start)); + schedule.Add(new SchedulePinState(pins_signal[i], (byte)0, time_end)); + } + if (pins_dur != null) + for (int i = 0; i < pins_dur.Length; i++) + { + schedule.Add(new SchedulePinState(pins_dur[i], (byte)1, time_start)); + schedule.Add(new SchedulePinState(pins_dur[i], (byte)0, time_end)); + } + } + private void DigitalPulseHandler(ProtocolEvent item) + { + item.Arguments.TryGetValue("SignalPin", out string commStr); + byte[] pins_signal = PinSetParseHelper(commStr); + item.Arguments.TryGetValue("DurationPin", out commStr); + byte[] pins_dur = PinSetParseHelper(commStr); + item.Arguments.TryGetValue("TimeStartMs", out commStr); + uint time_start = Convert.ToUInt32(commStr); + item.Arguments.TryGetValue("TimeEndMs", out commStr); + uint time_end = Convert.ToUInt32(commStr); + + if (pins_signal != null) + for (int i = 0; i < pins_signal.Length; i++) + { + schedule.Add(new SchedulePinState(pins_signal[i], (byte)1, time_start)); + schedule.Add(new SchedulePinState(pins_signal[i], (byte)0, time_start + 5)); + } + if (pins_dur != null) + for (int i = 0; i < pins_dur.Length; i++) + { + schedule.Add(new SchedulePinState(pins_dur[i], (byte)1, time_start)); + schedule.Add(new SchedulePinState(pins_dur[i], (byte)0, time_end)); + } + } + private void DigitalStringDurationHandler(ProtocolEvent item) + { + // This uses a pin range instead of a pin swr + item.Arguments.TryGetValue("SignalPin", out string commStr); + byte[] pins_signal = PinRangeParseHelper(commStr); + item.Arguments.TryGetValue("DurationPin", out commStr); + byte[] pins_dur = PinSetParseHelper(commStr); + item.Arguments.TryGetValue("Value", out commStr); + byte value = Convert.ToByte(commStr); + item.Arguments.TryGetValue("TimeStartMs", out commStr); + uint time_start = Convert.ToUInt32(commStr); + item.Arguments.TryGetValue("TimeEndMs", out commStr); + uint time_end = Convert.ToUInt32(commStr); + + + if (pins_signal != null) + for (int i = pins_signal[0]; i <= pins_signal[1]; i++) + { + schedule.Add(new SchedulePinState((byte)i, PinStateStringHelper(value, (byte)(i - pins_signal[0])), time_start)); + schedule.Add(new SchedulePinState((byte)i, (byte)0, time_end)); + } + if (pins_dur != null) + for (int i = 0; i < pins_dur.Length; i++) + { + schedule.Add(new SchedulePinState(pins_dur[i], (byte)1, time_start)); + schedule.Add(new SchedulePinState(pins_dur[i], (byte)0, time_end)); + } + + } + private void InvalidKeyHandler(ProtocolEvent item) + { + + } + #endregion + + private void Receive(char input) + { + //Log.Error("Receiving: " + input); + if (receivers.TryGetValue(input, out Receiver thisKeyReceiver)) + { + thisKeyReceiver(); + } + else + { + //Log.Error("Byte:" + (byte)input); + invalidKeyReceiver(); + } + } + + #region Receiver Functions + // These functions may be broken out into their own classes + private void CapacityReceiver() + { + _capacity = Convert.ToUInt16(Serial.ReadLine()); + _serial_available = Convert.ToByte(Serial.ReadLine()); + //Log.Error("McIntyreAFC CAPACITY\ncapacity:" + _capacity + "\nserial_available:" + _serial_available); + } + private void ErrorReceiver() + { + byte file, error, ext; + file = Convert.ToByte(Serial.ReadLine()); + error = Convert.ToByte(Serial.ReadLine()); + ext = Convert.ToByte(Serial.ReadLine()); + //Log.Error("McIntyreAFC ERROR\nfile:" + file + "\nerror:" + error + "\next:" + ext); ; + + } + private void DoneReceiver() + { + _run_time = Convert.ToUInt32(Serial.ReadLine()); + _state = ScheduleState.DONE; + //Log.Error("McIntyreAFC DONE\nrun_time:" + _run_time + "\nstate:" + _state); + } + private void ReportReceiver() + { + ushort index = Convert.ToUInt16(Serial.ReadLine()); + uint time = Convert.ToUInt32(Serial.ReadLine()); + byte pin = Convert.ToByte(Serial.ReadLine()); + byte pinstate = Convert.ToByte(Serial.ReadLine()); + _capacity++; + //Log.Error("McIntyreAFC REPORT\nindex:" + index + "\ntime/otime:" + time + "/" + schedule[index].Time + "\npin:" + pin + "\npinstate:" + pinstate); + } + private void ReplyReceiver() + { + _serial_available += 7; + //Log.Error("McIntyreAFC REPLY\nserial_available:" + _serial_available); + } + private void InvalidKeyReceiver() + { + Log.Error("McIntyreAFC UNEXPECTED BYTE"); + } + + + #endregion + + #region Read Helper Functions + /* + private uint NumReadSerial(int length) + { + byte[] readBytes = new byte[length]; + for (int i = 0; i < length; i++) + { + readBytes[i] = (byte)Serial.ReadByte(); + } + return NumRead(readBytes); + } + private static uint NumRead(byte[] input) + { + uint result = 0; + uint pow = 1; + for (int i = input.Length - 1; i >= 0; i--) + { + result += input[i] * pow; + pow <<= 8; + } + return result; + } + */ + + #endregion + + struct SchedulePinState : IComparable + { + public readonly byte Pin; + public readonly byte State; + public readonly uint Time; + public SchedulePinState(byte pin, byte state, uint time) + { + this.Pin = pin; + this.State = state; + this.Time = time; + } + public byte[] ToBytes() + { + byte[] response = new byte[6]; + response[4] = Pin; + response[5] = State; + + uint tempTime = Time; + for (int i = 0; i < 4; i++) + { + response[i] = (byte)tempTime; + tempTime >>= 8; + } + return response; + } + public override int GetHashCode() + { + long lTime = Time + int.MinValue; + return (int)lTime; + } + + public int CompareTo(SchedulePinState other) + { + return (int)(this.Time - other.Time + this.State - other.State); + } + } + } +} diff --git a/McIntyreAFC/SpreadsheetAFC.cs b/McIntyreAFC/SpreadsheetAFC.cs new file mode 100644 index 0000000..5a82d42 --- /dev/null +++ b/McIntyreAFC/SpreadsheetAFC.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Schedulino.Generator; +using ProtocolMasterCore.Protocol.Interpreter; +using ProtocolMasterCore.Prompt; +using ProtocolMasterCore.Protocol; + +namespace McIntyreAFC +{ + [InterpreterMeta("SpreadsheetAFC", "1.1")] + + public class SpreadsheetAFC : ExcelDataInterpreter, IInterpreter, IPromptUserSelect + { + private delegate bool RowReader(Dictionary map); + Dictionary mappedRowReaders; + Dictionary protocols; + Protocol baseData; + public UserSelectHandler UserSelectPrompt { get; set; } + public SpreadsheetAFC() + { + mappedRowReaders = new Dictionary(){ + { "Experiment", ReadExperimentRow }, + { "Protocols", ReadProtocolRow }, + { "Stims", ReadStimsRow }, + { "SoundConfig", ReadSoundConfigRow }, + { "StimConfig", ReadStimConfigRow } + }; + protocols = new Dictionary(); + } + public bool IsCanceled { get; set; } + private Protocol GetOrCreateProtocol(string protocolName) + { + Protocol result; + if (protocols.TryGetValue(protocolName, out result)) + return result; + else + { + result = baseData.CloneProtocolWithoutEvents(); + result.name = protocolName; + protocols.Add(protocolName, result); + return result; + } + } + public List Generate(string protocolName = null) + { + if (DataReader == null) return null; + do + { + // instantiate header map + Dictionary sheetMap = new Dictionary(); + // Get header row + DataReader.Read(); + for (int i = 0; i < DataReader.FieldCount; i++) + { + string colName = DataReader.GetString(i); + if (colName != null) + sheetMap.Add(colName, i); + } + // Select a filler function (sheetFiller) based on sheet name (DataReader.Name) + // such as FillProtocols for sheet name "Protocols" + RowReader readRow; + if (mappedRowReaders.ContainsKey(DataReader.Name)) + readRow = mappedRowReaders[DataReader.Name]; + else readRow = ReadNothing; + // Run the selected filler function until end of sheet + while (readRow(sheetMap)) { } + // Go to next sheet + } while (DataReader.NextResult()); + DataReader.Close(); + + if (!IsCanceled) + { + if (protocolName == null) + { + string dropdownResponse = UserSelectPrompt(protocols.Keys.ToArray()); + if (dropdownResponse != null) + return protocols[dropdownResponse].Generate(); + else return null; + } + else + { + return protocols[protocolName].Generate(); + } + } + else return null; + } + private bool ReadExperimentRow(Dictionary headerMap) + { + if (DataReader.Read()) + { + baseData = new Protocol(); + baseData.owner = DataReader.GetValue(headerMap["Owner"]).ToString(); + baseData.sound_order = DataReader.GetValue(headerMap["Sound Order"]).ToString(); + + string extra_time_str = DataReader.GetValue(headerMap["Extra Time (seconds)"]).ToString(); + string interval_min_str = DataReader.GetValue(headerMap["Inter-sound Interval Minimum (seconds)"]).ToString(); + string interval_max_str = DataReader.GetValue(headerMap["Inter-sound Interval Maximum (seconds)"]).ToString(); + + baseData.Sounds.Add(new Sound(DataReader.GetValue(headerMap["Sound A"]).ToString())); + if (!DataReader.IsDBNull(headerMap["Sound B"])) + baseData.Sounds.Add(new Sound(DataReader.GetValue(headerMap["Sound B"]).ToString())); + + baseData.extra_time = Convert.ToUInt32(extra_time_str) * 1000; + baseData.interval_min = Convert.ToUInt32(interval_min_str) * 1000; + baseData.interval_max = Convert.ToUInt32(interval_max_str) * 1000; + + return true; + } + else return false; + } + private bool ReadProtocolRow(Dictionary headerMap) + { + if (DataReader.Read()) + { + if (!DataReader.IsDBNull(headerMap["Protocol"])) + { + string name = DataReader.GetValue(headerMap["Protocol"]).ToString(); + Protocol protocol = GetOrCreateProtocol(name); + + string presounds_str = DataReader.GetValue(headerMap["Number of Presounds (each)"]).ToString(); + string sounds_str = DataReader.GetValue(headerMap["Number of Sounds (each)"]).ToString(); + + + protocol.num_presounds = Convert.ToUInt32(presounds_str); + protocol.num_exp_sounds = Convert.ToUInt32(sounds_str); + } + return true; + } + else return false; + } + private bool ReadStimsRow(Dictionary headerMap) + { + if (DataReader.Read()) + { + if (!DataReader.IsDBNull(headerMap["Protocol"])) + { + string name = DataReader.GetValue(headerMap["Protocol"]).ToString(); + Protocol protocol = GetOrCreateProtocol(name); + + if (DataReader.IsDBNull(0)) return true; + Stimulus stim = new Stimulus(); + stim.name = DataReader.GetValue(headerMap["Stimulus"]).ToString(); + stim.sound_group = DataReader.GetValue(headerMap["Sound Group"]).ToString(); + stim.stim_sound_pairing = DataReader.GetValue(headerMap["Stim-Sound Pairing"]).ToString(); + stim.stim_sound_window = DataReader.GetValue(headerMap["Stim-Sound Window"]).ToString(); + stim.stim_delivery = DataReader.GetValue(headerMap["Intra-Window Stim Delivery"]).ToString(); + + // This is a very hacky way to handle timeline integer underflow, but it works + // It should probably be replaced by allowing intervals to have negative values + // and then increasing all intervals by the most negative value + if (stim.stim_sound_window == "Before") + { + protocol.GrowExtraTimeIfNeeded(stim.dur_max); + } + + string paired_sounds_str = DataReader.GetValue(headerMap["Number of Paired Sounds"]).ToString(); + string stims_per_sound_str = DataReader.GetValue(headerMap["Stim Repetitions Per Window"]).ToString(); + string dur_min_str = DataReader.GetValue(headerMap["Stim Min Duration (ms)"]).ToString(); + string dur_max_str = DataReader.GetValue(headerMap["Stim Max Duration (ms)"]).ToString(); + string interval_min_str = DataReader.GetValue(headerMap["Time Between Stims Min"]).ToString(); + string interval_max_str = DataReader.GetValue(headerMap["Time Between Stims Max"]).ToString(); + string delay_min_str = DataReader.GetValue(headerMap["Stimulus Delay Min (ms)"]).ToString(); + string delay_max_str = DataReader.GetValue(headerMap["Stimulus Delay Max (ms)"]).ToString(); + + stim.num_paired_sounds = Convert.ToUInt32(paired_sounds_str); + stim.stims_per_sound = Convert.ToUInt32(stims_per_sound_str); + stim.dur_min = Convert.ToUInt32(dur_min_str); + stim.dur_max = Convert.ToUInt32(dur_max_str); + stim.interval_min = Convert.ToUInt32(interval_min_str); + stim.interval_max = Convert.ToUInt32(interval_max_str); + stim.delay_min = Convert.ToUInt32(delay_min_str); + stim.delay_max = Convert.ToUInt32(delay_max_str); + + stim.sound = protocol.Sounds.Find(a => a.name == stim.sound_group); + if (stim.sound == null) throw new NullReferenceException("Stimulus sound is invalid"); + stim.sound.stimuli.Add(stim); + + protocol.Stimuli.Add(stim); + } + return true; + } + else return false; + } + private bool ReadSoundConfigRow(Dictionary headerMap) + { + if (DataReader.Read()) + { + if (DataReader.IsDBNull(0)) return true; + string name = DataReader.GetValue(headerMap["Sound"]).ToString(); + foreach (KeyValuePair kvp in protocols) + { + Sound sound = kvp.Value.Sounds.Find(a => a.name == name); + if (sound != null) + { + sound.handler = DataReader.GetValue(headerMap["Handler"]).ToString(); + sound.behavior_pin = DataReader.GetValue(headerMap["Behavior_Pin"]).ToString(); + sound.duration_pin = DataReader.GetValue(headerMap["Duration_Pin"]).ToString(); + sound.sound_id = DataReader.GetValue(headerMap["Sound_ID"]).ToString(); + + string duration_str = DataReader.GetValue(headerMap["Duration (seconds)"]).ToString(); + + sound.duration = Convert.ToUInt32(duration_str) * 1000; + } + } + return true; + } + return false; + } + private bool ReadStimConfigRow(Dictionary headerMap) + { + if (DataReader.Read()) + { + if (DataReader.IsDBNull(0)) return true; + string name = DataReader.GetValue(headerMap["Stimulator"]).ToString(); + foreach (KeyValuePair kvp in protocols) + { + IEnumerable stims = kvp.Value.Stimuli.Where(a => a.name == name); + if (stims != null) + { + string handler = DataReader.GetValue(headerMap["Handler"]).ToString(); + string behavior_pin = DataReader.GetValue(headerMap["Behavior_Pin"]).ToString(); + string duration_pin = DataReader.GetValue(headerMap["Duration_Pin"]).ToString(); + foreach (Stimulus stim in stims) + { + stim.handler = handler; + stim.behavior_pin = behavior_pin; + stim.duration_pin = duration_pin; + } + } + } + return true; + } + return false; + } + private bool ReadNothing(Dictionary headerMap) + { + return false; + } + + } + + +} diff --git a/ProtocolMaster.sln b/ProtocolMaster.sln index 8f0d5ee..a3d4ee5 100644 --- a/ProtocolMaster.sln +++ b/ProtocolMaster.sln @@ -7,20 +7,155 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtocolMaster", "ProtocolM EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schedulino", "Schedulino\Schedulino.csproj", "{EDBA6B70-6719-4118-977D-F10BA534B394}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterUWP", "ProtocolMasterUWP\ProtocolMasterUWP.csproj", "{2C51DBDE-14C6-4537-BF15-D43788D57DC6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterCore", "ProtocolMasterCore\ProtocolMasterCore.csproj", "{44917BA5-F366-4054-95A9-A8371FCEBFB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McIntyreAFC", "McIntyreAFC\McIntyreAFC.csproj", "{4D707372-0064-4BC7-B744-45DD2F63488B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterWPF", "ProtocolMasterWPF\ProtocolMasterWPF.csproj", "{35ACDD96-A12B-4F38-84F1-2E498AF2E914}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|ARM.Build.0 = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|ARM64.Build.0 = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|x64.Build.0 = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Debug|x86.Build.0 = Debug|Any CPU {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|Any CPU.Build.0 = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|ARM.ActiveCfg = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|ARM.Build.0 = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|ARM64.ActiveCfg = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|ARM64.Build.0 = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|x64.ActiveCfg = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|x64.Build.0 = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|x86.ActiveCfg = Release|Any CPU + {CE4D7148-D741-4ED7-95A1-DC1BD0532E1B}.Release|x86.Build.0 = Release|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|ARM.ActiveCfg = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|ARM.Build.0 = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|ARM64.Build.0 = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|x64.ActiveCfg = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|x64.Build.0 = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|x86.ActiveCfg = Debug|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Debug|x86.Build.0 = Debug|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|Any CPU.ActiveCfg = Release|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|Any CPU.Build.0 = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|ARM.ActiveCfg = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|ARM.Build.0 = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|ARM64.ActiveCfg = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|ARM64.Build.0 = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x64.ActiveCfg = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x64.Build.0 = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x86.ActiveCfg = Release|Any CPU + {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x86.Build.0 = Release|Any CPU + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|Any CPU.ActiveCfg = Debug|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|Any CPU.Build.0 = Debug|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.ActiveCfg = Debug|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.Build.0 = Debug|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.Deploy.0 = Debug|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.Build.0 = Debug|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.ActiveCfg = Debug|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.Build.0 = Debug|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.Deploy.0 = Debug|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.ActiveCfg = Debug|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.Build.0 = Debug|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.Deploy.0 = Debug|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|Any CPU.ActiveCfg = Release|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.ActiveCfg = Release|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.Build.0 = Release|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.Deploy.0 = Release|ARM + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.ActiveCfg = Release|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.Build.0 = Release|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.Deploy.0 = Release|ARM64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.ActiveCfg = Release|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.Build.0 = Release|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.Deploy.0 = Release|x64 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.ActiveCfg = Release|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.Build.0 = Release|x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.Deploy.0 = Release|x86 + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|ARM.ActiveCfg = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|ARM.Build.0 = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|ARM64.Build.0 = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|x64.ActiveCfg = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|x64.Build.0 = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|x86.ActiveCfg = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|x86.Build.0 = Debug|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|Any CPU.Build.0 = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|ARM.ActiveCfg = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|ARM.Build.0 = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|ARM64.ActiveCfg = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|ARM64.Build.0 = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|x64.ActiveCfg = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|x64.Build.0 = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|x86.ActiveCfg = Release|Any CPU + {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Release|x86.Build.0 = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|ARM.Build.0 = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|ARM64.Build.0 = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|x64.ActiveCfg = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|x64.Build.0 = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|x86.ActiveCfg = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Debug|x86.Build.0 = Debug|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|Any CPU.Build.0 = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|ARM.ActiveCfg = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|ARM.Build.0 = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|ARM64.ActiveCfg = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|ARM64.Build.0 = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|x64.ActiveCfg = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|x64.Build.0 = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|x86.ActiveCfg = Release|Any CPU + {4D707372-0064-4BC7-B744-45DD2F63488B}.Release|x86.Build.0 = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|Any CPU.Build.0 = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|ARM.ActiveCfg = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|ARM.Build.0 = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|ARM64.Build.0 = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|x64.ActiveCfg = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|x64.Build.0 = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|x86.ActiveCfg = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Debug|x86.Build.0 = Debug|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|Any CPU.ActiveCfg = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|Any CPU.Build.0 = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|ARM.ActiveCfg = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|ARM.Build.0 = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|ARM64.ActiveCfg = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|ARM64.Build.0 = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|x64.ActiveCfg = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|x64.Build.0 = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|x86.ActiveCfg = Release|Any CPU + {35ACDD96-A12B-4F38-84F1-2E498AF2E914}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ProtocolMaster/App.xaml b/ProtocolMaster/App.xaml index f33256f..a99b398 100644 --- a/ProtocolMaster/App.xaml +++ b/ProtocolMaster/App.xaml @@ -13,8 +13,11 @@ + + + diff --git a/ProtocolMaster/Model/ICallDropdown.cs b/ProtocolMaster/Model/IPromptUserSelect.cs similarity index 51% rename from ProtocolMaster/Model/ICallDropdown.cs rename to ProtocolMaster/Model/IPromptUserSelect.cs index 4f7bf60..375ef94 100644 --- a/ProtocolMaster/Model/ICallDropdown.cs +++ b/ProtocolMaster/Model/IPromptUserSelect.cs @@ -6,9 +6,9 @@ namespace ProtocolMaster.Model { - public delegate string CallDropdownHandler(string[] keys); - public interface ICallDropdown + public delegate string UserSelectHandler(string[] keys); + public interface IPromptUserSelect { - public CallDropdownHandler CallDropdown { set; } + public UserSelectHandler UserSelectPrompt { set; } } } diff --git a/ProtocolMaster/Model/CallHandler.cs b/ProtocolMaster/Model/PromptHandler.cs similarity index 91% rename from ProtocolMaster/Model/CallHandler.cs rename to ProtocolMaster/Model/PromptHandler.cs index 33394c4..0367c8e 100644 --- a/ProtocolMaster/Model/CallHandler.cs +++ b/ProtocolMaster/Model/PromptHandler.cs @@ -7,7 +7,7 @@ namespace ProtocolMaster.Model { - public static class CallHandler + public static class PromptHandler { public static string CallDropdown(string[] keys) { diff --git a/ProtocolMaster/Model/Protocol/ExtensionManager.cs b/ProtocolMaster/Model/Protocol/ExtensionManager.cs index 664ef00..62f3b04 100644 --- a/ProtocolMaster/Model/Protocol/ExtensionManager.cs +++ b/ProtocolMaster/Model/Protocol/ExtensionManager.cs @@ -20,6 +20,7 @@ abstract class ExtensionManager where T : IExtensionMeta where E : IExtens E extension; public bool IsRunning { get => !isDisposed; } bool isDisposed = true; + bool isCanceled = false; public event EventHandler OnOptionsLoaded; protected Dispatcher UIDispatcher { get; set; } @@ -78,9 +79,9 @@ protected E CreateSelectedExtension() isDisposed = false; extensionContext = extensionFactory.CreateExport(); extension = extensionContext.Value; - if (typeof(ICallDropdown).IsAssignableFrom(extension.GetType())) + if (typeof(IPromptUserSelect).IsAssignableFrom(extension.GetType())) { - (extension as ICallDropdown).CallDropdown = CallHandler.CallDropdown; + (extension as IPromptUserSelect).UserSelectPrompt = PromptHandler.CallDropdown; } return extension; } @@ -96,7 +97,7 @@ protected void DisposeSelectedExtension() } public void Cancel() { - extension.Cancel(); + extension.IsCanceled = true; DisposeSelectedExtension(); } } diff --git a/ProtocolMaster/Model/Protocol/ExtensionSystem.cs b/ProtocolMaster/Model/Protocol/ExtensionSystem.cs index be78ca5..53d059f 100644 --- a/ProtocolMaster/Model/Protocol/ExtensionSystem.cs +++ b/ProtocolMaster/Model/Protocol/ExtensionSystem.cs @@ -13,6 +13,7 @@ using ProtocolMaster.Model.Protocol.Driver; using System.ComponentModel; using System.Collections.ObjectModel; +using System.Windows.Threading; namespace ProtocolMaster.Model.Protocol { @@ -59,7 +60,7 @@ public void LoadExtensions() DriverManager.LoadOptions(_container); InterpreterManager.LoadOptions(_container); } - public void Interpret(string selectionID) + public void Interpret(string selectionID, string argument) { if (isRunning) { @@ -82,19 +83,18 @@ public void Interpret(string selectionID) InterpreterManager.Cancel(); } })); - return InterpreterManager.GenerateData(selectionID); - }, TaskCreationOptions.LongRunning); - + return InterpreterManager.GenerateData(selectionID, argument); + }); Task UITask = generator.ContinueWith((data) => { - List result = generator.Result; + List result = data.Result; Data = result; if (result != null) { - App.Window.TimelineView.LoadPlotData(result); + App.Current.Dispatcher.Invoke(() => { App.Window.TimelineView.LoadPlotData(result); }); } - }, TaskScheduler.FromCurrentSynchronizationContext()); + }); } public void Run() { diff --git a/ProtocolMaster/Model/Protocol/IExtension.cs b/ProtocolMaster/Model/Protocol/IExtension.cs index 9395a58..5fb63f5 100644 --- a/ProtocolMaster/Model/Protocol/IExtension.cs +++ b/ProtocolMaster/Model/Protocol/IExtension.cs @@ -8,6 +8,6 @@ namespace ProtocolMaster.Model.Protocol { public interface IExtension { - void Cancel(); + bool IsCanceled { get; set; } } } diff --git a/ProtocolMaster/Model/Protocol/Interpreter/InterpreterManager.cs b/ProtocolMaster/Model/Protocol/Interpreter/InterpreterManager.cs index f2f4a04..9ae2b8b 100644 --- a/ProtocolMaster/Model/Protocol/Interpreter/InterpreterManager.cs +++ b/ProtocolMaster/Model/Protocol/Interpreter/InterpreterManager.cs @@ -17,7 +17,7 @@ internal class InterpreterManager : ExtensionManager GenerateData(string selectionID) + public List GenerateData(string selectionID, string argument) { interpreter = CreateSelectedExtension(); @@ -32,7 +32,8 @@ public List GenerateData(string selectionID) } // pre-fill event data - List result = interpreter.Generate("Protocol"); + List result = interpreter.Generate(argument); + if (interpreter.IsCanceled) return null; DisposeSelectedExtension(); return result; } diff --git a/ProtocolMaster/Model/Protocol/NullExtensions/NullDriver.cs b/ProtocolMaster/Model/Protocol/NullExtensions/NullDriver.cs index 60f6477..5f744df 100644 --- a/ProtocolMaster/Model/Protocol/NullExtensions/NullDriver.cs +++ b/ProtocolMaster/Model/Protocol/NullExtensions/NullDriver.cs @@ -16,9 +16,8 @@ namespace ProtocolMaster.Model.Protocol.NullExtensions [DriverMeta("None", "")] public class NullDriver : IDriver { - public void Cancel() - { - } + public bool IsCanceled { get; set; } + public bool Setup(List dataList) { diff --git a/ProtocolMaster/Model/Protocol/NullExtensions/NullInterpreter.cs b/ProtocolMaster/Model/Protocol/NullExtensions/NullInterpreter.cs index b7b79c3..562f0fd 100644 --- a/ProtocolMaster/Model/Protocol/NullExtensions/NullInterpreter.cs +++ b/ProtocolMaster/Model/Protocol/NullExtensions/NullInterpreter.cs @@ -11,10 +11,7 @@ namespace ProtocolMaster.Model.Protocol.NullExtensions [InterpreterMeta("None", "")] public class NullInterpreter : IInterpreter { - public void Cancel() - { - } - + public bool IsCanceled { get; set; } public List Generate(string protocolName) { return null; diff --git a/ProtocolMaster/Model/SnippetsToComplete.cs b/ProtocolMaster/Model/SnippetsToComplete.cs new file mode 100644 index 0000000..fe28965 --- /dev/null +++ b/ProtocolMaster/Model/SnippetsToComplete.cs @@ -0,0 +1,26 @@ +using ProtocolMaster.Model.Protocol; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMaster.Model +{ + class SnippetsToComplete + { + // should load data to plot model on UI thread + /* + public void OnEventsLoaded(List data) + { + Task UITask = new Task(() => + { + if (data != null) + { + App.Window.TimelineView.LoadPlotData(data); + } + }, canc,TaskScheduler.FromCurrentSynchronizationContext()); + } + */ + } +} diff --git a/ProtocolMaster/Model/Video/BitMapHelper.cs b/ProtocolMaster/Model/Video/BitMapHelper.cs deleted file mode 100644 index 4aeca4f..0000000 --- a/ProtocolMaster/Model/Video/BitMapHelper.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Windows.Media.Imaging; - -namespace ProtocolMaster.Model.Video -{ - static class BitMapHelper - { - public static BitmapImage ToBitmapImage(this Bitmap bitmap) - { - BitmapImage bi = new BitmapImage(); - bi.BeginInit(); - MemoryStream ms = new MemoryStream(); - bitmap.Save(ms, ImageFormat.Bmp); - ms.Seek(0, SeekOrigin.Begin); - bi.StreamSource = ms; - bi.EndInit(); - return bi; - } - } -} diff --git a/ProtocolMaster/Model/Video/WebCam.cs b/ProtocolMaster/Model/Video/WebCam.cs deleted file mode 100644 index 214421f..0000000 --- a/ProtocolMaster/Model/Video/WebCam.cs +++ /dev/null @@ -1,9 +0,0 @@ - - -namespace ProtocolMaster.Model.Video -{ - public class WebCam - { - - } -} diff --git a/ProtocolMaster/ProtocolMaster.csproj b/ProtocolMaster/ProtocolMaster.csproj index 715a706..c17a3ab 100644 --- a/ProtocolMaster/ProtocolMaster.csproj +++ b/ProtocolMaster/ProtocolMaster.csproj @@ -16,6 +16,13 @@ bin\Release\ + + + + + + + @@ -38,4 +45,19 @@ + + + Always + + + Always + + + Always + + + Always + + + \ No newline at end of file diff --git a/ProtocolMaster/Theme/Icons/Cancel.png b/ProtocolMaster/Theme/Icons/Cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..d9da488216633b9c64e7608d1965c8ab38dad1a9 GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7xzrU{dgOaSW-5dwXMVE>oaDYoOVE zK8*uHraMATEo_^r(O|V9B`D{m;JNY+^O@!S&)VPadVhX${5MuJhD|sBi}5*F*E2l& z$;{B8YLNTjyzGPDMV|I&wmix|{&Cxqdd8Z$=P%z=j6DDMqXpxHo`0bx3=cmrcC_;| zFt|C~ZID&a13F_)V=A+igJtc1<|CKc85oom@_>Rdj1WQZ1e;n01_P!?KnEWX4KQ24 zJmvCsmSdv|$qJqy6)Y9IEcFYl`?ep=xn5g;PBU0_wLZgvSKYk7tDt`V6-rJy85}Sb4q9e0Bxtqk*oEObV2BES|NyISrEF&ag z?AbDjY$r>|o+*_j%R9Y)!TaHU?(4p;=l9|HEzfl)T3ea&af@>U0Qk(!Ol(=5_`5ja ztg{1stsej$FLRS~m;ZL%$ng(2F(%wIlvM|bHNH`e&3Bi!7ZOyZbS92zxnlh6^Zckr zQ|f^-O;&#H5$Q%{x0MTLEO4c^d0r8A>D9GdvNPOfDVOmQDqPmL$dB$>7?t!jS@)sX zJYhelSMQ7Tbwu3q@%4$g^(8C#&Gya1rJm@4{Rnt$ssij)p*`RN*TbqftiYQb0u=LY zUkI>)Kq%D*^O8eUH?V%BeFw4gp2JX7OTidOKV_E8gg?~GO;R$1Lot0L{A`xM>}eDW zx9}T>fY4BwNYKkA131B-@V)^s2sC}4eSw+fMnj1p?-V8!&IzDID~KivgCY)06kFyk zOenD~^XCM)dC8w~ql#M>N8;12XQ}_=i0+1-hJVru;DNgbc7q8#K)11V;%@LDE)2O zeG)v_22yWjH=h)&4rw#IzANV*#I)V-)O3G|8(C)@=hFySz=t*4meQ0Nk&Do)n=J}ocqT#- zQf^_vkhy50``H?Cfk%nBir@f}`K8;%r`v3ly16B%wxq8b5R9%S%!wm}T+mk_9TMlB zr!FY6Jy!S2(vM$R^T_N@3QAg6uHAfEauR2@LGamEYXm%hw09TsAOIX>OzU z8RWh)nd_TN_t)cUTE-QfCn=d8^T}X*E8`iq4 zxZYpP-%$!9Xs_&ZYMmv=Ejg-VyiG;5+a~N-=t#L- zcQC^O<^|`D-UxxY9?G#DSK=nua;Ph$U&h$)=1dUR;@F(^K5^bph{-L^tWpd)@d8pqud86q}>C^UR(|8 zs>H$TCPqhZOUsj=%@Th|{L3dEb@~)UqxwVzpDY}$$$euueI{jNRj@}C)q~xzAn{P)I-X8bIwIfb^koY@!E!CVw)78c(Rd7AWgZK06I_1f20<@wmCnqr z#{5Nk(Lf>TvQ^;Ml)>wHF{!Gdq1x9B+iu<1D7!bh@!&- zA0lvypC{!V#(aRZ4c2#z+1^V$?HyY`^N8DOz_voiP3)} z>8yKd8Pvdx_uR z2buPXjJ$PR=H&2*2**)CLrS5Z)94mpx64MAfZ=C0 zt*#_!SE&;PqtB<-SsKgGFTwefIwkzuf9Fh_ZO?ehz+;8Rb1#_1M$D2Og5kD~@b0upZGO+4jlc`DIr6~kO);!dYy(TZQSvC>5`1;Gd?tic%dEa1&=F51;+U#VDk?m35 z5G;lzGP^*DRGH%$&lMw*VZRT>C>wVwZOP<%+Uh~MgQ(OS?B8QF=z{kb~n{7N-*$N6AS zoVdb6(5F=ToIM&1G|xQ)f7#klg=Tq3ZV2~k9lGETUY_+~{Z2^-yb_4VCRwk83?sxtRfH-7E literal 0 HcmV?d00001 diff --git a/ProtocolMaster/Theme/Icons/Reset.png b/ProtocolMaster/Theme/Icons/Reset.png new file mode 100644 index 0000000000000000000000000000000000000000..796da6dce96df24c7b92e72378f73e97788785b2 GIT binary patch literal 4485 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 literal 0 HcmV?d00001 diff --git a/ProtocolMaster/Theme/TabControlStyles.xaml b/ProtocolMaster/Theme/TabControlStyles.xaml new file mode 100644 index 0000000..90114ba --- /dev/null +++ b/ProtocolMaster/Theme/TabControlStyles.xaml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMaster/View/Drive.xaml b/ProtocolMaster/View/Drive.xaml index 4b0c903..b7f9b44 100644 --- a/ProtocolMaster/View/Drive.xaml +++ b/ProtocolMaster/View/Drive.xaml @@ -9,7 +9,7 @@ - Drive + diff --git a/ProtocolMaster/View/Drive.xaml.cs b/ProtocolMaster/View/Drive.xaml.cs index 6a1b80b..72e1cca 100644 --- a/ProtocolMaster/View/Drive.xaml.cs +++ b/ProtocolMaster/View/Drive.xaml.cs @@ -13,8 +13,6 @@ public partial class Drive : UserControl { readonly Dictionary idChildDictionary; readonly Dictionary childIdDictionary; - - String selectedItemID = null; public Drive() { idChildDictionary = new Dictionary(); @@ -107,12 +105,7 @@ void SelectionHandler(object sender, RoutedEventArgs e) { TreeViewItem selected = sender as TreeViewItem; - selectedItemID = childIdDictionary[selected]; - } - - public string GetSelectedItemID() - { - return selectedItemID; + App.Window.TimelineView.SetSelection(childIdDictionary[selected], selected.Header as string); } } } diff --git a/ProtocolMaster/View/Log.xaml b/ProtocolMaster/View/Log.xaml index 214b362..88fe552 100644 --- a/ProtocolMaster/View/Log.xaml +++ b/ProtocolMaster/View/Log.xaml @@ -8,7 +8,7 @@ d:DesignHeight="450" d:DesignWidth="800"> - Log + diff --git a/ProtocolMaster/View/MainWindow.xaml b/ProtocolMaster/View/MainWindow.xaml index 0497046..95333f2 100644 --- a/ProtocolMaster/View/MainWindow.xaml +++ b/ProtocolMaster/View/MainWindow.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro" - Title="ProtocolMaster" WindowState="Maximized" Width="900" Height="500" + Title="ProtocolMaster" Width="1400" Height="500" Loaded="OnLoad" Closed="Window_Closed" mc:Ignorable="d"> @@ -15,7 +15,13 @@ - + + + + + + + @@ -59,27 +65,34 @@ - + + - + - - + - + + + + + + + + + - diff --git a/ProtocolMaster/View/MainWindow.xaml.cs b/ProtocolMaster/View/MainWindow.xaml.cs index 647cf9c..dceda7c 100644 --- a/ProtocolMaster/View/MainWindow.xaml.cs +++ b/ProtocolMaster/View/MainWindow.xaml.cs @@ -1,6 +1,10 @@ using MahApps.Metro.Controls; +using ProtocolMaster.Model.Protocol.Driver; +using ProtocolMaster.Model.Protocol.Interpreter; using System; using System.IO; +using System.Windows; +using System.Windows.Controls; namespace ProtocolMaster.View { @@ -12,6 +16,8 @@ public partial class MainWindow : MetroWindow public MainWindow() { InitializeComponent(); + App.Instance.ExtensionSystem.InterpreterManager.OnOptionsLoaded += LoadInterpreters; + App.Instance.ExtensionSystem.DriverManager.OnOptionsLoaded += LoadDrivers; } private void Click_Documentation(object sender, EventArgs e) @@ -56,5 +62,71 @@ private void Window_Closed(object sender, EventArgs e) { App.Instance.Window_Closed(); } + + public void LoadDrivers(object sender, EventArgs e) + { + DriverDropdown.Items.Clear(); + foreach (DriverMeta meta in App.Instance.ExtensionSystem.DriverManager.Options) + { + ListDriver(meta); + } + } + public void ListDriver(DriverMeta data) + { + MenuItem newDriver = new MenuItem + { + Header = data.ToString() + }; + newDriver.Resources.Add("data", data); + newDriver.Click += new RoutedEventHandler(DriverClickHandler); + DriverDropdown.Items.Add(newDriver); + } + + public void DriverClickHandler(object sender, RoutedEventArgs e) + { + MenuItem src = e.Source as MenuItem; + DriverMeta data = src.Resources["data"] as DriverMeta; + + App.Instance.ExtensionSystem.DriverManager.Selected = data; + ShowSelectedDriver(); + } + + public void ShowSelectedDriver() + { + SelectedDriver.Header = "Driver: " + App.Instance.ExtensionSystem.DriverManager.Selected.ToString(); + } + + public void LoadInterpreters(object sender, EventArgs e) + { + InterpreterDropdown.Items.Clear(); + foreach (InterpreterMeta meta in App.Instance.ExtensionSystem.InterpreterManager.Options) + { + ListInterpreter(meta); + } + } + + public void ListInterpreter(InterpreterMeta data) + { + MenuItem newInterpreter = new MenuItem + { + Header = data.ToString() + }; + newInterpreter.Resources.Add("data", data); + newInterpreter.Click += new RoutedEventHandler(InterpreterClickHandler); + InterpreterDropdown.Items.Add(newInterpreter); + } + + public void InterpreterClickHandler(object sender, RoutedEventArgs e) + { + MenuItem src = e.Source as MenuItem; + InterpreterMeta data = src.Resources["data"] as InterpreterMeta; + App.Instance.ExtensionSystem.InterpreterManager.Selected = data; + ShowSelectedInterpreter(); + } + + public void ShowSelectedInterpreter() + { + SelectedInterpreter.Header = "Interpreter: " + App.Instance.ExtensionSystem.InterpreterManager.Selected.ToString(); + } } } diff --git a/ProtocolMaster/View/PopupDropdown.xaml.cs b/ProtocolMaster/View/PopupDropdown.xaml.cs index 41956c0..f469ad8 100644 --- a/ProtocolMaster/View/PopupDropdown.xaml.cs +++ b/ProtocolMaster/View/PopupDropdown.xaml.cs @@ -22,8 +22,8 @@ public partial class PopupDropdown : Window public PopupDropdown(string[] options) { InitializeComponent(); - this.Left = (App.Window.Width - this.Width) / 2; - this.Top = (App.Window.Height - this.Height) / 2; + this.Left = App.Window.Left + (App.Window.Width - this.Width) / 2; + this.Top = App.Window.Top + (App.Window.Height - this.Height) / 2; foreach (string s in options) Dropdown.Items.Add(s); Dropdown.SelectionChanged += DropdownSelectionHandler; diff --git a/ProtocolMaster/View/Timeline.xaml b/ProtocolMaster/View/Timeline.xaml index 18e024a..d40b361 100644 --- a/ProtocolMaster/View/Timeline.xaml +++ b/ProtocolMaster/View/Timeline.xaml @@ -14,28 +14,57 @@ - Timeline + - - - - + + + + + + + - - - - + + + + + Load + + + + + + + + Start + + + + + + + + Cancel + + + + + + + + Reset + + - - - - - - + + + + + diff --git a/ProtocolMaster/View/Timeline.xaml.cs b/ProtocolMaster/View/Timeline.xaml.cs index b65c9ac..bae8c35 100644 --- a/ProtocolMaster/View/Timeline.xaml.cs +++ b/ProtocolMaster/View/Timeline.xaml.cs @@ -28,132 +28,97 @@ public Timeline() { InitializeComponent(); SetUpPlot(); - App.Instance.ExtensionSystem.InterpreterManager.OnOptionsLoaded += LoadInterpreters; - App.Instance.ExtensionSystem.DriverManager.OnOptionsLoaded += LoadDrivers; + SetSelection(null, null); App.Instance.ExtensionSystem.DriverManager.OnProtocolStart += StartAnimation; App.Instance.ExtensionSystem.DriverManager.OnProtocolEnd += EndAnimation; } - public void LoadDrivers(object sender, EventArgs e) - { - DriverDropdown.Items.Clear(); - foreach (DriverMeta meta in App.Instance.ExtensionSystem.DriverManager.Options) - { - ListDriver(meta); - } - } - public void ListDriver(DriverMeta data) - { - MenuItem newDriver = new MenuItem - { - Header = data.ToString() - }; - newDriver.Resources.Add("data", data); - newDriver.Click += new RoutedEventHandler(DriverClickHandler); - DriverDropdown.Items.Add(newDriver); - } - - public void DriverClickHandler(object sender, RoutedEventArgs e) - { - MenuItem src = e.Source as MenuItem; - DriverMeta data = src.Resources["data"] as DriverMeta; - - App.Instance.ExtensionSystem.DriverManager.Selected = data; - ShowSelectedDriver(); - } - public void ShowSelectedDriver() - { - SelectedDriver.Header = "Selected: " + App.Instance.ExtensionSystem.DriverManager.Selected.ToString(); - } + DateTime start; + Task bgWorker; + Progress animationProgress; + CancellationTokenSource tokenSource; + private string selectionID; + private string selectionName; + private string runningName; - public void LoadInterpreters(object sender, EventArgs e) + public void SetSelection(string selectionID, string selectionName) { - InterpreterDropdown.Items.Clear(); - foreach (InterpreterMeta meta in App.Instance.ExtensionSystem.InterpreterManager.Options) + if(selectionID == null) { - ListInterpreter(meta); + SetControlsUsability(false, false, false, false); + this.selectionID = null; + this.selectionName = "No Selection"; } - } - - public void ListInterpreter(InterpreterMeta data) - { - MenuItem newInterpreter = new MenuItem + else { - Header = data.ToString() - }; - newInterpreter.Resources.Add("data", data); - newInterpreter.Click += new RoutedEventHandler(InterpreterClickHandler); - InterpreterDropdown.Items.Add(newInterpreter); + SetControlsUsability(true, true, false, false); + this.selectionID = selectionID; + this.selectionName = selectionName; + } + Reset(); + SelectionHeader.Text = this.selectionName; } - - public void InterpreterClickHandler(object sender, RoutedEventArgs e) + private void SetControlsUsability(bool load, bool start, bool cancel, bool reset) { - MenuItem src = e.Source as MenuItem; - InterpreterMeta data = src.Resources["data"] as InterpreterMeta; - App.Instance.ExtensionSystem.InterpreterManager.Selected = data; - ShowSelectedInterpreter(); + LoadButton.IsEnabled = load; + LoadButton.Visibility = MapVis(load); + StartButton.IsEnabled = start; + StartButton.Visibility = MapVis(start); + CancelButton.IsEnabled = cancel; + CancelButton.Visibility = MapVis(cancel); + ResetButton.IsEnabled = reset; + ResetButton.Visibility = MapVis(reset); } - - public void ShowSelectedInterpreter() + private Visibility MapVis(bool booleanValue) { - SelectedInterpreter.Header = "Selected: " + App.Instance.ExtensionSystem.InterpreterManager.Selected.ToString(); + if (booleanValue) return Visibility.Visible; + else return Visibility.Collapsed; } - - DateTime start; - public void Load_Click(object sender, RoutedEventArgs e) { - LoadButton.IsEnabled = true; - StartButton.IsEnabled = true; - CancelButton.IsEnabled = false; - ResetButton.IsEnabled = true; - App.Instance.ExtensionSystem.Interpret(App.Window.DriveView.GetSelectedItemID()); + App.Instance.ExtensionSystem.Interpret(selectionID, null); + SetControlsUsability(false, false, false, true); } public void Start_Click(object sender, RoutedEventArgs e) { - LoadButton.IsEnabled = false; - StartButton.IsEnabled = false; - CancelButton.IsEnabled = true; - ResetButton.IsEnabled = false; - + SetControlsUsability(false,false,true,false); + App.Instance.ExtensionSystem.Interpret(selectionID, null); App.Instance.ExtensionSystem.Run(); } public void Cancel_Click(object sender, RoutedEventArgs e) { - LoadButton.IsEnabled = true; - StartButton.IsEnabled = false; - CancelButton.IsEnabled = false; - ResetButton.IsEnabled = true; - + SetControlsUsability(false, false, false, true); App.Instance.ExtensionSystem.End(); } public void Reset_Click(object sender, RoutedEventArgs e) { - LoadButton.IsEnabled = true; - StartButton.IsEnabled = false; - CancelButton.IsEnabled = false; - ResetButton.IsEnabled = false; + SetControlsUsability(true, true, false, false); + Reset(); + } + private void Reset() + { App.Instance.ExtensionSystem.Reset(); + ResetPlot(); + } + private void ResetPlot() + { Line.X = 0; plot.Model.Series.Clear(); categoryAxis.Labels.Clear(); + dateTimeAxis.AbsoluteMinimum = 0; dateTimeAxis.AbsoluteMaximum = 1.035; + categoryAxis.AbsoluteMinimum = -0.6; categoryAxis.AbsoluteMaximum = 0.6; categoryAxis.MaximumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; categoryAxis.MinimumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; plot.Model.InvalidatePlot(true); } - private void GeneratePlotModel() - { - - } - static OxyColor[] pallete = { OxyColors.DarkRed, OxyColors.DodgerBlue, OxyColors.Green }; internal class CategoryNode @@ -317,8 +282,6 @@ private void SetUpPlot() MinorGridlineColor = OxyColors.Gray, MajorGridlineColor = OxyColors.Gray, StartPosition = 0, - AbsoluteMinimum = 0, - AbsoluteMaximum = 1.035, }; model.Axes.Add(dateTimeAxis); categoryAxis = new CategoryAxis() @@ -338,10 +301,6 @@ private void SetUpPlot() MajorGridlineColor = OxyColors.Gray, GapWidth = 0.0f, ExtraGridlines = new double[32], - AbsoluteMinimum = -0.6, - AbsoluteMaximum = 0.6, - MaximumRange = 1.2, - MinimumRange = 1.2, }; model.Axes.Add(categoryAxis); @@ -373,11 +332,12 @@ private void SetUpPlot() model.Annotations.Add(Line); dateTimeAxis.Minimum = 0; plot.Model = model; + + ResetPlot(); } - Task bgWorker; - Progress animationProgress; - CancellationTokenSource tokenSource; + + public void StartAnimation(object sender, EventArgs e) { @@ -422,9 +382,5 @@ void bgWorker_DoWork(IProgress progress, CancellationToken cancelToken) } progress.Report(0); } - private void MenuItem_Click(object sender, System.Windows.RoutedEventArgs e) - { - - } } } diff --git a/ProtocolMasterCore/Prompt/DefaultPrompts.cs b/ProtocolMasterCore/Prompt/DefaultPrompts.cs new file mode 100644 index 0000000..a53d6af --- /dev/null +++ b/ProtocolMasterCore/Prompt/DefaultPrompts.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Prompt +{ + public static class DefaultPrompts + { + public static string UserSelect(string[] options) + { + if(options != null && options.Length > 0 && options[0] != null) + return options[0]; + return "null"; + } + } +} diff --git a/ProtocolMasterCore/Prompt/IPromptUserSelect.cs b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs new file mode 100644 index 0000000..e0f0f65 --- /dev/null +++ b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Prompt +{ + public delegate string UserSelectHandler(string[] keys); + public interface IPromptUserSelect + { + public UserSelectHandler UserSelectPrompt { set; } + } +} diff --git a/ProtocolMasterCore/Prompt/PromptTargetStore.cs b/ProtocolMasterCore/Prompt/PromptTargetStore.cs new file mode 100644 index 0000000..cc11f9e --- /dev/null +++ b/ProtocolMasterCore/Prompt/PromptTargetStore.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Prompt +{ + public class PromptTargetStore + { + public UserSelectHandler UserSelect { get; set; } + + public PromptTargetStore() + { + UserSelect = DefaultPrompts.UserSelect; + } + } +} diff --git a/ProtocolMasterCore/Protocol/Driver/DriverManager.cs b/ProtocolMasterCore/Protocol/Driver/DriverManager.cs new file mode 100644 index 0000000..55460a7 --- /dev/null +++ b/ProtocolMasterCore/Protocol/Driver/DriverManager.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; + +namespace ProtocolMasterCore.Protocol.Driver +{ + internal class DriverManager : ExtensionManager + { + IDriver driver; + public event EventHandler OnProtocolStart; + public event EventHandler OnProtocolEnd; + public bool Run(List data) + { + bool didStart = false; + driver = CreateSelectedExtension(); + if (driver.Setup(data)) + { + didStart = true; + OnProtocolStart?.Invoke(this, new EventArgs()); + driver.Start(); + OnProtocolEnd?.Invoke(this, new EventArgs()); + } + DisposeSelectedExtension(); + return didStart; + } + } +} diff --git a/ProtocolMasterCore/Protocol/Driver/DriverMeta.cs b/ProtocolMasterCore/Protocol/Driver/DriverMeta.cs new file mode 100644 index 0000000..f4cdc5a --- /dev/null +++ b/ProtocolMasterCore/Protocol/Driver/DriverMeta.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace ProtocolMasterCore.Protocol.Driver +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class DriverMeta : ExportAttribute, IExtensionMeta + { + public string Name { get; private set; } + public string Version { get; private set; } + public string[] HandlerLabels { get; private set; } + + public DriverMeta(string name, string version, params string[] eventNames) : base(typeof(IDriver)) + { + this.Name = name; + this.Version = version; + this.HandlerLabels = eventNames; + } + + public DriverMeta(IDictionary inputs) + { + HandlerLabels = (string[])inputs["HandlerLabels"]; + Name = (string)inputs["Name"]; + Version = (string)inputs["Version"]; + } + + public override string ToString() + { + return Name + " " + Version; + } + } +} diff --git a/ProtocolMasterCore/Protocol/Driver/IDriver.cs b/ProtocolMasterCore/Protocol/Driver/IDriver.cs new file mode 100644 index 0000000..15236d5 --- /dev/null +++ b/ProtocolMasterCore/Protocol/Driver/IDriver.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace ProtocolMasterCore.Protocol.Driver +{ + public enum DriverProgress + { + LOADING, + READY, + PRERUN, + RUNNING, + DONE, + CANCELLED + } + + /// + /// Primary interface for Drivers + /// Functions should be executed in order ProcessData() -> Run() + /// Cancel should be used as a callback to safely stop (such as properly closing IO streams) when driver is running in another thread. + /// + public interface IDriver : IExtension + { + /// + /// Data processing function, this takes DriverData and converts it into hardware-compatible data + /// + /// List of DriverData for the driver to handle, such as events or behaviors + bool Setup(List dataList); + /// + /// Driver Run Function, this is called after all Data Processing is complete + /// + void Start(); + + } +} diff --git a/ProtocolMasterCore/Protocol/ExtensionManager.cs b/ProtocolMasterCore/Protocol/ExtensionManager.cs new file mode 100644 index 0000000..bf41d15 --- /dev/null +++ b/ProtocolMasterCore/Protocol/ExtensionManager.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.Threading; +using System.Threading.Tasks; +using ProtocolMasterCore.Prompt; + +namespace ProtocolMasterCore.Protocol +{ + abstract class ExtensionManager where T : IExtensionMeta where E : IExtension + { + [ImportMany] + IEnumerable> AvailableExtensions { get; set; } + ExportFactory extensionFactory; + ExportLifetimeContext extensionContext; + E extension; + public bool IsRunning { get => !isDisposed; } + bool isDisposed = true; + + public event EventHandler OnOptionsLoaded; + + private PromptTargetStore promptTargets = new PromptTargetStore(); + public PromptTargetStore PromptTargets { get => promptTargets; set => promptTargets = value; } + + public void LoadOptions(CompositionContainer container) + { + try + { + container.ComposeParts(this); + } + catch (CompositionException compositionException) + { + //Debug.Log.Error(compositionException.ToString()); + } + + foreach (ExportFactory i in AvailableExtensions) + { + if (i.Metadata.Name == "None" && i.Metadata.Version == "") + { + Selected = i.Metadata; + } + //Debug.Log.Error(typeof(E).ToString() + " found: '" + i.Metadata.Name + "' version: '" + i.Metadata.Version + "'"); + } + OnOptionsLoaded?.Invoke(this, new EventArgs()); + } + public IExtensionMeta Selected + { + get { return extensionFactory.Metadata; } + set + { + foreach (ExportFactory i in AvailableExtensions) + { + if (i.Metadata.Name == value.Name && i.Metadata.Version == value.Version) + { + extensionFactory = i; + } + } + throw new Exception($"No extension of type {typeof(E)} with name {value.Name} is loaded"); + } + } + public IEnumerable Options + { + get + { + List optionsList = new List(); + foreach (ExportFactory i in AvailableExtensions) + { + optionsList.Add(i.Metadata); + } + return optionsList; + } + } + protected E CreateSelectedExtension() + { + if (isDisposed) + { + isDisposed = false; + extensionContext = extensionFactory.CreateExport(); + extension = extensionContext.Value; + if (typeof(IPromptUserSelect).IsAssignableFrom(extension.GetType())) + { + (extension as IPromptUserSelect).UserSelectPrompt = PromptTargets.UserSelect; + } + return extension; + } + else + { + throw new Exception("A new extension cannot be started before currently running extension is disposed"); + } + } + protected void DisposeSelectedExtension() + { + extensionContext.Dispose(); + isDisposed = true; + } + + public void CancelRunningExtension() + { + extension.IsCanceled = true; + DisposeSelectedExtension(); + } + } +} diff --git a/ProtocolMasterCore/Protocol/ExtensionSystem.cs b/ProtocolMasterCore/Protocol/ExtensionSystem.cs new file mode 100644 index 0000000..5ff6548 --- /dev/null +++ b/ProtocolMasterCore/Protocol/ExtensionSystem.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using ProtocolMasterCore.Protocol.Interpreter; +using ProtocolMasterCore.Protocol.Driver; +using ProtocolMasterCore.Prompt; + +namespace ProtocolMasterCore.Protocol +{ + internal class ExtensionSystem + { + // Import All Extensions so that they can be Composed (ComposeParts()) + public DriverManager DriverManager { get; private set; } + public InterpreterManager InterpreterManager { get; private set; } + // Composition Container for all extension managers + private readonly CompositionContainer _container; + public List Data { get; private set; } + + bool isRunning; + bool isReady; + + Task> generator; + private Task runTask; + private CancellationToken cancelToken; + private CancellationTokenSource tokenSource; + + public ExtensionSystem(string directory) + { + isRunning = false; + isReady = false; + + DriverManager = new DriverManager(); + InterpreterManager = new InterpreterManager(); + + AggregateCatalog catalog = new AggregateCatalog(); + catalog.Catalogs.Add(new AssemblyCatalog(typeof(IExtension).Assembly)); + catalog.Catalogs.Add(new DirectoryCatalog(directory)); + _container = new CompositionContainer(catalog); + } + + ~ExtensionSystem() + { + Terminate(); + } + public void LoadExtensions() + { + DriverManager.LoadOptions(_container); + InterpreterManager.LoadOptions(_container); + } + public void Interpret(string selectionID, string argument, FileStream fs) + { + if (isRunning) + { + throw new Exception("Cannot interpret a new protocol while a protocol is running"); + } + else if (isReady) + { + Reset(); + //then fall through + } + isReady = true; + tokenSource = new CancellationTokenSource(); + cancelToken = tokenSource.Token; + generator = Task.Factory.StartNew>(() => + { + cancelToken.Register(new Action(() => + { + if (InterpreterManager.IsRunning) + { + InterpreterManager.CancelRunningExtension(); + } + })); + return InterpreterManager.GenerateData(selectionID, argument, fs); + }, TaskCreationOptions.LongRunning); + + Task UITask = generator.ContinueWith((data) => + { + List result = generator.Result; + Data = result; + if (result != null) + { + InterpreterManager.OnEventsLoaded(result); + } + }); + } + public void Run() + { + if (isRunning) + { + throw new Exception("A protocol is already running"); + } + else if (!isReady) + { + throw new Exception("A protocol cannot be run before it is ready"); + } + else + { + isReady = false; + isRunning = true; + Progress driverProgress = new Progress(); + + Task UITask = generator.ContinueWith((data) => + { + runTask = Task.Run(new Action(() => + { + cancelToken.Register(new Action(() => + { + if (DriverManager.IsRunning) + { + DriverManager.CancelRunningExtension(); + } + })); + DriverManager.Run(Data); + }), tokenSource.Token); + + }, TaskScheduler.FromCurrentSynchronizationContext()); + } + } + + public void End() + { + if (!isRunning) + { + throw new Exception("A protocol cannot be cancelled/ended when not running"); + } + Terminate(); + } + public void Reset() + { + if (isRunning) + { + throw new Exception("A protocol cannot be reset when running"); + } + Terminate(); + } + public void Terminate() + { + if (tokenSource != null && !tokenSource.IsCancellationRequested) + tokenSource.Cancel(); + isRunning = false; + } + } +} diff --git a/ProtocolMasterCore/Protocol/IExtension.cs b/ProtocolMasterCore/Protocol/IExtension.cs new file mode 100644 index 0000000..499f75b --- /dev/null +++ b/ProtocolMasterCore/Protocol/IExtension.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Protocol +{ + public interface IExtension + { + bool IsCanceled { get; set; } + } +} diff --git a/ProtocolMasterCore/Protocol/IExtensionMeta.cs b/ProtocolMasterCore/Protocol/IExtensionMeta.cs new file mode 100644 index 0000000..5864421 --- /dev/null +++ b/ProtocolMasterCore/Protocol/IExtensionMeta.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Protocol +{ + interface IExtensionMeta + { + string Name { get; } + string Version { get; } + } +} diff --git a/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs b/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs new file mode 100644 index 0000000..a21858b --- /dev/null +++ b/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs @@ -0,0 +1,18 @@ +using ExcelDataReader; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Protocol.Interpreter +{ + public abstract class ExcelDataInterpreter + { + protected IExcelDataReader DataReader { get; private set; } + internal void SetReader(IExcelDataReader newReader) + { + DataReader = newReader; + } + } +} diff --git a/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs b/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs new file mode 100644 index 0000000..7694210 --- /dev/null +++ b/ProtocolMasterCore/Protocol/Interpreter/IInterpreter.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace ProtocolMasterCore.Protocol.Interpreter +{ + public interface IInterpreter : IExtension + { + List Generate(string protocolName); + } +} diff --git a/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs new file mode 100644 index 0000000..48a386a --- /dev/null +++ b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs @@ -0,0 +1,31 @@ +using ExcelDataReader; +using System.Collections.Generic; +using System.IO; + +namespace ProtocolMasterCore.Protocol.Interpreter +{ + public delegate void ProtocolEventsLoader(List events); + internal class InterpreterManager : ExtensionManager + { + IInterpreter interpreter; + public ProtocolEventsLoader OnEventsLoaded; + public List GenerateData(string selectionID, string argument, Stream stream) + { + interpreter = CreateSelectedExtension(); + + if (typeof(ExcelDataInterpreter).IsAssignableFrom(interpreter.GetType())) + { + ExcelDataInterpreter spreadSheetInterpreter = interpreter as ExcelDataInterpreter; + if (stream != null) + spreadSheetInterpreter.SetReader(ExcelReaderFactory.CreateReader(stream)); + else return null; + } + // pre-fill event data + List result = interpreter.Generate(argument); + if (interpreter.IsCanceled) return null; + DisposeSelectedExtension(); + OnEventsLoaded?.Invoke(result); + return result; + } + } +} diff --git a/ProtocolMasterCore/Protocol/Interpreter/InterpreterMeta.cs b/ProtocolMasterCore/Protocol/Interpreter/InterpreterMeta.cs new file mode 100644 index 0000000..1d45cfe --- /dev/null +++ b/ProtocolMasterCore/Protocol/Interpreter/InterpreterMeta.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; + +namespace ProtocolMasterCore.Protocol.Interpreter +{ + [MetadataAttribute] + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class InterpreterMeta : ExportAttribute, IExtensionMeta + { + public string Name { get; private set; } + public string Version { get; private set; } + public string[] PageHeadersCSV { get; private set; } + + public InterpreterMeta(string name, string version, params string[] pageHeadersCSV) : base(typeof(IInterpreter)) + { + this.Name = name; + this.Version = version; + this.PageHeadersCSV = pageHeadersCSV; + } + + public InterpreterMeta(IDictionary inputs) + { + PageHeadersCSV = (string[])inputs["PageHeadersCSV"]; + Name = (string)inputs["Name"]; + Version = (string)inputs["Version"]; + } + public override string ToString() + { + return Name + " " + Version; + } + } +} diff --git a/ProtocolMasterCore/Protocol/NullExtensions/NullDriver.cs b/ProtocolMasterCore/Protocol/NullExtensions/NullDriver.cs new file mode 100644 index 0000000..32c0b4a --- /dev/null +++ b/ProtocolMasterCore/Protocol/NullExtensions/NullDriver.cs @@ -0,0 +1,21 @@ +using ProtocolMasterCore.Protocol.Driver; +using System.Collections.Generic; + +namespace ProtocolMasterCore.Protocol.NullExtensions +{ + [DriverMeta("None", "")] + public class NullDriver : IDriver + { + public bool IsCanceled { get; set; } + + + public bool Setup(List dataList) + { + return false; + } + + public void Start() + { + } + } +} diff --git a/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs new file mode 100644 index 0000000..7e7a787 --- /dev/null +++ b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs @@ -0,0 +1,22 @@ +using ProtocolMasterCore.Protocol.Interpreter; +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Protocol.NullExtensions +{ + [InterpreterMeta("None", "")] + public class NullInterpreter : IInterpreter + { + public bool IsCanceled { get; set; } + public List Generate(string protocolName) + { + return null; + } + } + + +} diff --git a/ProtocolMasterCore/Protocol/ProtocolEvent.cs b/ProtocolMasterCore/Protocol/ProtocolEvent.cs new file mode 100644 index 0000000..794ace4 --- /dev/null +++ b/ProtocolMasterCore/Protocol/ProtocolEvent.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; + +namespace ProtocolMasterCore.Protocol +{ + public class ProtocolEvent + { + public string Handler { get; private set; } + public string CategoryLabel { get; private set; } + public string ParentLabel { get; private set; } + + public Dictionary Arguments { get; private set; } + + public ProtocolEvent(string handler, params KeyValuePair[] args) + { + this.Handler = handler; + Arguments = new Dictionary(); + foreach(KeyValuePair arg in args) + { + Arguments.Add(arg.Key, arg.Value); + } + } + + public ProtocolEvent(string handler, string categoryLabel, params KeyValuePair[] args) : this(handler, args) + { + this.CategoryLabel = categoryLabel; + } + + public ProtocolEvent(string handler, string categoryLabel, string parentLabel, params KeyValuePair[] args) : this(handler, args) + { + this.CategoryLabel = categoryLabel; + this.ParentLabel = parentLabel; + } + public bool HasCategory() { return CategoryLabel != null; } + public bool HasParent() { return ParentLabel != null; } + + public string FullLabel() + { + if (HasCategory()) + { + if (HasParent()) + { + return (CategoryLabel + ParentLabel); + } + else + { + return CategoryLabel; + } + } + else return ""; + } + /* + private int loadOrder; + private Dictionary properties; + + public int LoadOrder { get => loadOrder; private set => loadOrder = value; } + public Dictionary Properties { get => properties; private set => properties = value; }*/ + // example driver = {"Schedulino"}, properties = {<"time_ms", 1000>, <"pin", 5>, <"state", 1>} + // would be interpreted as: + // - at 1 second (1000ms) into the protocol, change the state of pin 5 to HIGH + // in the Schedulino driver. + + // The driver MUST be looking for events with these keys and correct value types, if it recieves + // unexpected data it should have the ability to find & throw errors. + } +} diff --git a/ProtocolMasterCore/ProtocolMasterCore.csproj b/ProtocolMasterCore/ProtocolMasterCore.csproj new file mode 100644 index 0000000..90d3c7d --- /dev/null +++ b/ProtocolMasterCore/ProtocolMasterCore.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.1 + + + + + + + + + + + diff --git a/ProtocolMasterCore/Utility/AppEnvironment.cs b/ProtocolMasterCore/Utility/AppEnvironment.cs new file mode 100644 index 0000000..df738f2 --- /dev/null +++ b/ProtocolMasterCore/Utility/AppEnvironment.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterCore.Utility +{ + public sealed class AppEnvironment + { + private static readonly AppEnvironment instance = new AppEnvironment(); + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static AppEnvironment() + { + } + + Dictionary locations; + string appData; + string assembly; + + private AppEnvironment() + { + locations = new Dictionary(); + appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\ProtocolMaster"; + assembly = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); + Directory.CreateDirectory(appData); + Directory.CreateDirectory(assembly); + } + public static bool TryAddLocationAppData(string label, string subPath, out string fullPath) => instance.I_TryAddLocationAppData(label, subPath, out fullPath); + private bool I_TryAddLocationAppData(string label, string subPath, out string fullPath) + { + fullPath = Path.Combine(appData, subPath); + if (locations.TryAdd(label, fullPath)) + { + Directory.CreateDirectory(fullPath); + return true; + } + else return false; + } + public static bool IryAddLocationAssembly(string label, string subPath, out string fullPath) => instance.I_TryAddLocationAssembly(label, subPath, out fullPath); + private bool I_TryAddLocationAssembly(string label, string subPath, out string fullPath) + { + fullPath = Path.Combine(assembly, subPath); + if (locations.TryAdd(label, fullPath)) + { + Directory.CreateDirectory(fullPath); + return true; + } + else return false; + } + } +} diff --git a/ProtocolMasterCore/Utility/Log.cs b/ProtocolMasterCore/Utility/Log.cs new file mode 100644 index 0000000..49d32a0 --- /dev/null +++ b/ProtocolMasterCore/Utility/Log.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace ProtocolMasterCore.Utility +{ + public delegate void LogPrinter(string message); + public sealed class Log + { + private static readonly Log instance = new Log(); + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static Log() + { + } + + public LogPrinter OutputPrinter; + public LogPrinter ErrorPrinter; + + private readonly string appdata; + public string AppData { get { return appdata; } } + private readonly string logdata; + private readonly string archive; + + private readonly int maxUnarchived = 48; + private readonly int minUnarchived = 16; + + private readonly LogFile lfOut; + private readonly LogFile lfErr; + public bool PrintErrors { get; set; } + public bool PrintOutput { get; set; } + + private Log() + { + if(AppEnvironment.TryAddLocationAppData("Log", "Log", out logdata)) + { + + } + if (AppEnvironment.TryAddLocationAppData("LogArchive", "Log/Archive", out logdata)) + { + + } + + // First two are redundant, but it is prettier this way + + string timePrefix = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); + + lfOut = new LogFile($"{logdata}{timePrefix}_Out.log"); + lfErr = new LogFile($"{logdata}{timePrefix}_Err.log"); + + PrintErrors = true; + + ArchiveOldest(); + } + public static Log Instance + { + get + { + return instance; + } + } + + public static void Error(string message) => Instance.I_Error(message); + public void I_Error(string message) + { + string toWrite = $"{ DateTime.Now}{message}"; + lfErr.Write(toWrite); + if (PrintErrors) + { + ErrorPrinter(toWrite); + } + } + public static void Out(string message) => Log.Instance.I_Out(message); + public void I_Out(string message) + { + string toWrite = $"{ DateTime.Now}{message}"; + lfOut.Write(toWrite); + if (PrintErrors) + { + OutputPrinter(toWrite); + } + } + + public void WriteFiles() + { + lfErr.WriteBuffer(); + lfOut.WriteBuffer(); + } + + public void OpenFolder() + { + WriteFiles(); + System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo() + { + FileName = logdata, + UseShellExecute = true, + Verb = "open" + }); + } + + private void ArchiveOldest() + { + string[] files = Directory.GetFiles(logdata); + List logs = new List(); + foreach (string file in files) + { + if (file.Contains(".log")) logs.Add(file); + } + + if (logs.Count < maxUnarchived) return; + + logs = logs.OrderBy(d => d).Take(logs.Count - minUnarchived).ToList(); + + //final archive name (I use date / time) + string zipFileName = DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss") + "[" + logs.Count + "].zip"; + + using (MemoryStream zipMS = new MemoryStream()) + { + using (ZipArchive zipArchive = new ZipArchive(zipMS, ZipArchiveMode.Create, true)) + { + //loop through files to add + foreach (string zipTarget in logs) + { + //exclude some files? -I don't want to ZIP other .zips in the folder. + if (new FileInfo(zipTarget).Extension == ".zip") continue; + + //read the file bytes + byte[] fileToZipBytes = System.IO.File.ReadAllBytes(zipTarget); + + //create the entry - this is the zipped filename + //change slashes - now it's VALID + ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(zipTarget.Replace(logdata, "").Replace('\\', '/')); + + //add the file contents + using (Stream zipEntryStream = zipFileEntry.Open()) + using (BinaryWriter zipFileBinary = new BinaryWriter(zipEntryStream)) + { + zipFileBinary.Write(fileToZipBytes); + } + + //lstLog.Items.Add("zipped: " + fileToZip); + } + } + + using (FileStream finalZipFileStream = new FileStream(archive + zipFileName, FileMode.Create)) + { + zipMS.Seek(0, SeekOrigin.Begin); + zipMS.CopyTo(finalZipFileStream); + } + + //lstLog.Items.Add("ZIP Archive Created."); + + foreach (string log in logs) + { + File.Delete(log); + } + } + } + } +} diff --git a/ProtocolMasterCore/Utility/LogFile.cs b/ProtocolMasterCore/Utility/LogFile.cs new file mode 100644 index 0000000..d6cbac1 --- /dev/null +++ b/ProtocolMasterCore/Utility/LogFile.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ProtocolMasterCore.Utility +{ + class LogFile + { + private List buffer; + private long writeAfter; + private string filePath; + private string tempPath; + bool tempFail; + + public LogFile(string filePath) + { + this.filePath = filePath; + buffer = new List(); + } + + public void Write(string message, bool deepWrite = false) + { + StringBuilder builder = new StringBuilder(DateTime.Now.ToString()); + builder.Append("\t"); + builder.Append(message); + string output = builder.ToString(); + + buffer.Add(output); + + if (deepWrite || DateTime.Now.Ticks > writeAfter) + { + WriteBuffer(); + } + } + + public void WriteBuffer() + { + try + { + File.AppendAllLines(filePath, buffer); + if (tempFail) + { + if (File.Exists(tempPath)) + File.Delete(tempPath); + tempFail = false; + } + } + catch (IOException) + { + tempFail = true; + tempPath = filePath + "[temp]"; + Write("Failed to open log at " + filePath + " attempting " + tempPath + " instead."); + try + { + File.AppendAllLines(tempPath, buffer); + } + catch (IOException) + { + Write("Failed to open temporary log " + tempPath + " will wait to write buffer"); + } + } + + + if (!tempFail) buffer = new List(); + + writeAfter = DateTime.Now.Ticks + (5 * 10000000); + } + } +} diff --git a/ProtocolMasterUWP/App.xaml b/ProtocolMasterUWP/App.xaml new file mode 100644 index 0000000..2ad9f7b --- /dev/null +++ b/ProtocolMasterUWP/App.xaml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/App.xaml.cs b/ProtocolMasterUWP/App.xaml.cs new file mode 100644 index 0000000..6494acb --- /dev/null +++ b/ProtocolMasterUWP/App.xaml.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace ProtocolMasterUWP +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + { + //TODO: Load state from previously suspended application + } + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (e.PrelaunchActivated == false) + { + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(typeof(MainPage), e.Arguments); + } + // Ensure the current window is active + Window.Current.Activate(); + } + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/ProtocolMasterUWP/Assets/LockScreenLogo.scale-200.png b/ProtocolMasterUWP/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..735f57adb5dfc01886d137b4e493d7e97cf13af3 GIT binary patch literal 1430 zcmaJ>TTC2P7~aKltDttVHYH6u8Io4i*}3fO&d$gd*bA_<3j~&e7%8(eXJLfhS!M@! zKrliY>>6yT4+Kr95$!DoD(Qn-5TP|{V_KS`k~E6(LGS@#`v$hQo&^^BKsw3HIsZBT z_y6C2n`lK@apunKojRQ^(_P}Mgewt$(^BBKCTZ;*xa?J3wQ7~@S0lUvbcLeq1Bg4o zH-bvQi|wt~L7q$~a-gDFP!{&TQfc3fX*6=uHv* zT&1&U(-)L%Xp^djI2?~eBF2cxC@YOP$+9d?P&h?lPy-9M2UT9fg5jKm1t$m#iWE{M zIf%q9@;fyT?0UP>tcw-bLkz;s2LlKl2qeP0w zECS7Ate+Awk|KQ+DOk;fl}Xsy4o^CY=pwq%QAAKKl628_yNPsK>?A>%D8fQG6IgdJ ztnxttBz#NI_a@fk7SU`WtrpsfZsNs9^0(2a z@C3#YO3>k~w7?2hipBf{#b6`}Xw1hlG$yi?;1dDs7k~xDAw@jiI*+tc;t2Lflg&bM)0!Y;0_@=w%`LW^8DsYpS#-bLOklX9r?Ei}TScw|4DbpW%+7 zFgAI)f51s}{y-eWb|vrU-Ya!GuYKP)J7z#*V_k^Xo>4!1Yqj*m)x&0L^tg3GJbVAJ zJ-Pl$R=NAabouV=^z_t;^K*0AvFs!vYU>_<|I^#c?>>CR<(T?=%{;U=aI*SbZADLH z&(f2wz_Y0??Tf|g;?|1Znw6}6U43Q#qNRwv1vp9uFn1)V#*4p&%$mP9x&15^OaBiDS(XppT|z^>;B{PLVEbS3IFYV yGvCsSX*m literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/SplashScreen.scale-200.png b/ProtocolMasterUWP/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..023e7f1feda78d5100569825acedfd213a0d84e9 GIT binary patch literal 7700 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 literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/Square150x150Logo.scale-200.png b/ProtocolMasterUWP/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..af49fec1a5484db1d52a7f9b5ec90a27c7030186 GIT binary patch literal 2937 zcma)84OCO-8BSud5)jwMLRVKgX(S?$n?Ld|vrsm<$CF7)&zTbyy1FE5bU`Q17MRv`9ue$;R(@8kR;#vJ*IM0>cJIAOte!d7oRgdH zd%ySjdB6L9=gX^A6)VzH7p2l@v~3zJAMw|DFy#^)F@@F*`mqUn=Il>l)8_+ab;nOW{%+iPx z+s{Eu|&pIs)Z7{La9~?xKfyl z#43?gjEL15d4WbOZo#SiP%>DB^+BcnJ=7dHEe;r#G=tuw|ka z%q@}##Uh7;tc%L_64m(kHtw74ty%BJMb)_1)#S0j`)F8_1jF7vScpsnH=0V19bO8y zR`0SjIdCUo&=>JwMQF8KHA<{ODHTiQh}0^@5QRmCA?gOH6_H3K^-_sNB^RrdNuK-R zOO*vOrKCVvDwgUck`kF(E7j{I#iiN;b*ZdCt4m@HPA`EuEqGGf4%!K<;(=I=&Vyrw z%TwcWtxa}8mCZ%Cyf&ActJ6_$ox5z6-D!0-dvnRx6t7y3d+h6QYpKWO;8OdnvERo7 zuEf>ih5`wqY)~o@OeVt-wM?Q!>QzdGRj!bz6fzYrfw$hZfAKzr2-M+D+R>}~oT574c;_3zquHcElqKIsryILt3g8n3jcMb+j?i?-L3FpZJ z2WRVBRdDPc+G5aaYg#5hpE+6nQ|(VSoxT3|biF;BUq#==-27Xi=gihDPYP$7?=9cP zYKE$jeQ|3~_L0VG-(F~2ZPyD0=k{J4Q~h(t__{-mz_w8{JDY9{`1ouzz!Vr5!ECdE z6U~O1k8c}24V7~zzXWTV-Pe4)y}wQJS&q%H5`Fo_f_JvIU489aCX$;P`u#!I-=^4ijC2{&9!O&h>mi?9oYD=GC#%)6{GzN6nQYw+Fal50!#x^asjBBR50i`+mho*ttoqV)ubM2KD9S~k7+FR4>{29?6 z{!l6kDdyTN0YJ9LgkPWeXm|gyi@zM3?0@{&pXT12w|78&W-q!RRF)&iLCEZVH<|fR zN0fr2^t8H(>L?>K#>^+jWROLral(Qy-xoBq1U7A&DV||wClb)Otd9?(gZ|8znMF}D zf<1haWz^s0qgecz;RFGt0C-B4g`jNGHsFU+;{<%t65v^sjk^h$lmWn#B0#_)9ij&d z-~lc`A)YYExi^7sBuPM^Y|wA2g*5?`K?#7tzELQYNxGo$UB$4J8RJp1k(8Jj+~hMT zlN~>M@KTTh^--8y3PK_NZ@AC!{PT=CziBzGd+wTJ^@icH!Bd}%)g8V)%K?|c&WTUk zy}qv1C%(fjRoZ4ozC3{O%@5?)XzH35zHns$pgU*Q?fj4v?fp1Qbm+j;3l;9jam9Da zXVcKjPlQ73x78QPu|Ffm6x?`~e3oD=gl=4kYK?={kD5j~QCXU)`HSdduNNENzA*2$ zOm3PzF!lN5e*06-f1Uot67wY#{o-S1!KZ7E=!~7ynnk9_iJR#kFoNbAOT#^2Gd17F zMmvU6>lndZQGd|ax9kUoXXO+$N?|j@6qpsF&_j7YXvwo_C{JpmLw5&#e6k>atv%es z5)7r*Wvv_JkUpT}M!_o!nVlEk1Zbl=a*2hQ*<|%*K1Glj^FcF`6kTzGQ3lz~2tCc@ z&x|tj;aH&1&9HwcJBcT`;{?a+pnej;M1HO(6Z{#J!cZA04hnFl;NXA+&`=7bjW_^o zfC40u3LMG?NdPtwGl>Tq6u}*QG)}-y;)lu-_>ee3kibW(69n0$0Zy!}9rQz%*v1iO zT9_H>99yIrSPYVy6^);rR}7Yo=J_T@hi+qhTZXnVWyf;JDYm5#eYLTxr*?kiNn!+Y zQ+LUkBafNJ#rH#C(?d5^;gw9o#%daEI{mA*LHPIHPU`#|H$hD zwm>0&+kahQ)E#%~k>&5@&#Vg82H?s%71=)(soi@174pi9--2{w{1$}Sz4zGn3Du&x bht0Iza^2ykEt4(epJ78uh5nDlX8(TxzDYwP literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/Square44x44Logo.scale-200.png b/ProtocolMasterUWP/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..ce342a2ec8a61291ba76c54604aea7e9d20af11b GIT binary patch literal 1647 zcmaJ?eM}Q)7(e+G1Q(|`V9JhTI2>MkceK4;p;PR&$Pi?ejk3YQ_3o`S&|W_dsOZ8# zWPTt69g`t$ab`0cj-Y0yiBSOqmd)tG7G(}M5aP0_%&9TijB#&)I{zSE^4@#z^FF`l z`8{8`o%wlL(UI|y2!cdsuVamHH~H86F!*-15em4)NqUpCQM5?aoC_eCf@lV4wvF2a zjDQn1JBL69f&@2M3rvzJcfE!eZ8FZUBlFlC5RD)it33{mF9#B82AiyQE%w)`vlwa> zv{<1sm&kSKK$&%2jSFn7$t&P%%6Ue>R=EAnG8N7fqynWG8L3p!4801a;8{+nliO(qd(jNJ_?+9W3#hLIDLoT6~3fx9=`CC-D}-AMrpEO7HK zt3$GicGPc?GmDjy7K2P@La;eu4!$zWCZ`ym{Z$b zu-O6RM&K4JT|BIZB`E-gxqG%FzanI#+2FFmqHqXG7yxWB=w55RGOM)$xMb(>kSNR z2w=1AZi%z=AmG~yea~XaXJR!v7vLn(RUnELfiB1|6D84ICOS}^Zo2AdN}<&*h}G_u z{xZ!(%>tLT3J3<5XhWy-tg+6)0nmUUENLW8TWA{R6bgVd3X;anYFZ^IRis*_P-C-r z;i>%1^eL3UI2-{w8nuFFcs0e~7J{O2k^~Ce%+Ly4U?|=!0LH=t6()xi<^I-rs+9sF z*q{E-CxZbGPeu#a;XJwE;9S1?#R&uns>^0G3p`hEUF*v`M?@h%T%J%RChmD|EVydq zmHWh*_=S%emRC*mhxaVLzT@>Z2SX0u9v*DIJ@WC^kLVdlGV6LpK$KIrlJqc zpJ921)+3JJdTx|<`G&kXpKkjGJv=76R`yYIQ{#c-`%+`#V(7}Q;&@6U8!Td1`d;?N z_9mnI#?AA}4J!r)LN4!E-@H5eXauuB7TOawS>Y|{-P?NNx-lq+z1W-+y(;39P&&LP zL{N80?&=C*qKmdA^moMZRuPcD!B<*mq$ch=0Cnlitw#txRWhb3%TQvPqjkC`F69G4b! ze7z9MZ#+;_#l?H37UqUhDFb^l&s2{oM$3I0o^Q!yx;;V)QmCMo)Tb_ui|mit8MS?U zm##6$sZZ1$@|s%?l@>4Z<*Q}sRBSKMhb4I{e5LdEhsHIHTe8Bod5c>6QtT>$XgUBz z6MK`kO$=jmt@FqggOhJ5j~e@ygRbG;<{Vu)*+nn9aQeo0;$#j;|MS=S$&L?BeV25z xs3B`@=#`5TF{^6(A1rvdY@|-RtQ|iS5{tyX+wH?;n8E)G$kykv-D^wh{{!TZT%7;_ literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ProtocolMasterUWP/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c02ce97e0a802b85f6021e822c89f8bf57d5cd GIT binary patch literal 1255 zcmaJ>TWs4@7*5+{G#S+&C!qC#> zf>5N3P6jO*Cz>ug*(_DmW=)kea&m$gZ^+nyiF`;j%w@}y8)>p*SH}C`m?DXeieF2U zyQHecc_L%Gh!7GMt+hG06y;+|p4>m~}PjA}rKViGiEnn7G0ZO<>G|7q;2?NwGCM3s?eued6%hd$B+ z*kQJ{#~$S=DFE(%=E+UkmlEI*%3llUf~8Ja9YU1Vui0IbGBkW_gHB%Rd&!!ioX zs40O?i9I{};kle7GMvE7(rk`la=gTI)47=>%?q@^iL-nUo3}h4S}N-KHn8t5mVP8w z&bSErwp+37 zNJJ8?a|{r5Q3R0Z5s-LB1WHOwYC@7pCHWND#cL1cZ?{kJ368_*(UDWUDyb<}0y@o# zfMF016iMWPCb6obAxT$JlB6(2DrlXDTB&!0`!m??4F(qWMhjVZo?JXQmz`1*58Z=& zcDmB|S-E@j?BoFGix0flckqdS4jsPNzhfWyWIM98GxcLs89C(~dw%$_t;JjX-SD}E zfiGV;{8Q%8r}w9x>EEigW81>`kvnU@pK)4+xk9@+bNj9L!AAZ@SZ@q|)&BmY3+HZx zul~BeG4|}-;L%cHViQGQX?^zFfO0&#cHwel=d`lH9sJ-@Sl@n*(8J2>%Ac`IxyY?Q z{=GhWvC#gu-~Ia7*n{=+;qM?Ul_wy1+u7ho;=`>EwP^g~R@{unBds`!#@}tluZQpS zm)M~nYEifJWJGx?_6DcTy>#uh%>!H9=hb^(v`=m3F1{L>db=<5_tm+_&knAQ2EU$s Mu9UqpbNZeC0BbUo^Z)<= literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/StoreLogo.png b/ProtocolMasterUWP/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/ProtocolMasterUWP/Assets/Wide310x150Logo.scale-200.png b/ProtocolMasterUWP/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..288995b397fdbef1fb7e85afd71445d5de1952c5 GIT binary patch literal 3204 zcmbVPeQXow8NYmBd90>}0NP?GhXW~VaeThm=a0tV#EwJMI!)6M3}|c4_Bl3=Kd>G0 z(GHx1wl<7(tP?FsOQkTilSo*iIvF%uArExJ73~P zSv1xEy!U(Wd4A9D`FQV@W3@F^qJ@PEF$@z`Z!*BbFsS(^?B zyiAzJ+q})bkgiQHWqEb*jJD-coHYr1^iocg)l!Qa{Xqs-l~6J}p-|##ZHYofskQ3$ zI0;xzXyhazBeXhIsg5A=%ufo@f)1yy&ScKS0;HF^!r_2UE^lpZEom(+@duma3awTv zCrCL-%D_SvYWIcdHkmI}#50(fkUi)Qgx!80ju>g1za^}ff>JI8Z@^-iCiaCgg@TgF z+vtE?Q9{VQUX&MW9SYYmGcxA14%N2@7FwBTD4N<(2{nWgV8$e3?-F=L^&FrtWn~(U_Q~~^uYiyeY6-KoTnfh9AWz@ zIKje0)u!_Lw)E}G!#kEfwKVdNt(UAf9*f>tEL_(=xco-T%jTi@7YlC3hs2ik%Le0H ztj}RTeCF(5mwvi3_56>-yB?l;J>-1%!9~=fs|QcNG3J~a@JCu`4SB460s0ZO+##4fFUSGLcj_ja^fL4&BKALfb#$6$O?>P@qx2Agl^x0i&ugt zsy5Pyu=()`7HRMG3IB7F1@`_ z+-!J%#i6e^U$e#+C%Q>_qVRzWRsG^W_n+@OcX@vzI&z;mzHNb!GQ?LWA(wtpqHqTM z1OFw_{Zn?fD)p)`c`kOgv{de=v@suGRqY{N^U7gI1VF3*F=obwaXI6ob5__Yn zVTguS!%(NI09J8x#AO_aW!9W7k*UvB;IWDFC3srwftr{kHj%g)fvnAm;&h_dnl~

MY- zf+K}sCe8qU6Ujs`3ua{U0Of$R_gVQBuUA za0v=mu#vIOqiiAZOr&h*$WyOw&k-xr$;G4Ixa!#TJNr>95(h>l%)PUy4p+^SgR(uR zta%k*?ny-+nAr8spEk1fo{J4i!b^Fia`N{_F6@zidA2ZTTrjl#^5Z-2KfB@Cu}l9s z(*|Z2jc?p~vn2f)3y9i*7zJV1L{$?|&q)4oaT;uXi6>1GkRXVTOzAz(RHEmr=eFIi z`}<>-Q?K0GN8!IYxeP1XKXO+jsJbp~o^);Bc;%b7Flpe7;1`Ny@3r7ZR;?R)aJt8C ziNlEC<@3f_lIV4TwV}&e;D!Ee5_|e#g0LUh=5vmYWYm7&2h*M>QPKvGh9-)wfMMW3 z8J9b%1k7dzPzO0_NGQy92BZ^FR6R~6;^6?lqO;-QUP4BY%cG%3vEhbm#>4vIhPBh3 z-+pZGjh$x%Hp{?=FHsMp0&wNPlj00us{&`1ZOZTqs8%4X&xH=UDr*xyBW(Zp&Em94 zf)ZSfn#yg0N)>!1kWdkqJ^S*z0FF5|fj&qcE#Na|%OY0$uO>!&hP+1ywfD_WXk@4J(?MBftK7>$Nvqh@tDuarN%PrTLQ2Uzysx>UV=V zk^RrDSvdQ?0;=hY67EgII-f4`t=+i*yS=Y~!XlqIy_4x&%+OdfbKOFPXS2X5%4R{N z$SQMX^AK6(fA + + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/MainPage.xaml.cs b/ProtocolMasterUWP/MainPage.xaml.cs new file mode 100644 index 0000000..7063a96 --- /dev/null +++ b/ProtocolMasterUWP/MainPage.xaml.cs @@ -0,0 +1,41 @@ +using ProtocolMasterUWP.Observable; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel.Core; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 + +namespace ProtocolMasterUWP +{ + ///

+ /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainPage : Page + { + public MainPage() + { + this.InitializeComponent(); + + + + } + + + } +} diff --git a/ProtocolMasterUWP/Observable/TreeItem.cs b/ProtocolMasterUWP/Observable/TreeItem.cs new file mode 100644 index 0000000..203b60b --- /dev/null +++ b/ProtocolMasterUWP/Observable/TreeItem.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterUWP.Observable +{ + class TreeItem + { + public string Name { get; set; } + public ObservableCollection Children { get; set; } = new ObservableCollection(); + + public override string ToString() + { + return Name; + } + } +} diff --git a/ProtocolMasterUWP/Package.appxmanifest b/ProtocolMasterUWP/Package.appxmanifest new file mode 100644 index 0000000..853a65d --- /dev/null +++ b/ProtocolMasterUWP/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + ProtocolMasterUWP + phili + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterUWP/Properties/AssemblyInfo.cs b/ProtocolMasterUWP/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..976e0f8 --- /dev/null +++ b/ProtocolMasterUWP/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ProtocolMasterUWP")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ProtocolMasterUWP")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/ProtocolMasterUWP/Properties/Default.rd.xml b/ProtocolMasterUWP/Properties/Default.rd.xml new file mode 100644 index 0000000..af00722 --- /dev/null +++ b/ProtocolMasterUWP/Properties/Default.rd.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterUWP/ProtocolMasterUWP.csproj b/ProtocolMasterUWP/ProtocolMasterUWP.csproj new file mode 100644 index 0000000..44b1b22 --- /dev/null +++ b/ProtocolMasterUWP/ProtocolMasterUWP.csproj @@ -0,0 +1,248 @@ + + + + + Debug + x86 + {2C51DBDE-14C6-4537-BF15-D43788D57DC6} + AppContainerExe + Properties + ProtocolMasterUWP + ProtocolMasterUWP + en-US + UAP + 10.0.18362.0 + 10.0.17763.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + false + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\ARM64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM64 + false + prompt + true + true + + + bin\ARM64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM64 + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + PackageReference + + + + App.xaml + + + MainPage.xaml + + + + + + + LogView.xaml + + + SelectionPaneView.xaml + + + SessionControlView.xaml + + + SessionView.xaml + + + TimelineView.xaml + + + TopBarView.xaml + + + + + Designer + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + 1.49.0 + + + 1.49.0 + + + 1.49.0 + + + 1.49.0.2201 + + + 6.2.11 + + + 2.5.0 + + + 2.0.0 + + + 1.0.0 + + + + + + + + + + 14.0 + + + + \ No newline at end of file diff --git a/ProtocolMasterUWP/Theme/CancelButton.xaml b/ProtocolMasterUWP/Theme/CancelButton.xaml new file mode 100644 index 0000000..db93055 --- /dev/null +++ b/ProtocolMasterUWP/Theme/CancelButton.xaml @@ -0,0 +1,97 @@ + + + diff --git a/ProtocolMasterUWP/Theme/Colors.xaml b/ProtocolMasterUWP/Theme/Colors.xaml new file mode 100644 index 0000000..799c450 --- /dev/null +++ b/ProtocolMasterUWP/Theme/Colors.xaml @@ -0,0 +1,7 @@ + + + + diff --git a/ProtocolMasterUWP/View/LogView.xaml b/ProtocolMasterUWP/View/LogView.xaml new file mode 100644 index 0000000..8f3f1bf --- /dev/null +++ b/ProtocolMasterUWP/View/LogView.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/ProtocolMasterUWP/View/LogView.xaml.cs b/ProtocolMasterUWP/View/LogView.xaml.cs new file mode 100644 index 0000000..4baea05 --- /dev/null +++ b/ProtocolMasterUWP/View/LogView.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class LogView : UserControl + { + public LogView() + { + this.InitializeComponent(); + } + } +} diff --git a/ProtocolMasterUWP/View/SelectionPaneView.xaml b/ProtocolMasterUWP/View/SelectionPaneView.xaml new file mode 100644 index 0000000..55e7173 --- /dev/null +++ b/ProtocolMasterUWP/View/SelectionPaneView.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/View/SelectionPaneView.xaml.cs b/ProtocolMasterUWP/View/SelectionPaneView.xaml.cs new file mode 100644 index 0000000..d23d41d --- /dev/null +++ b/ProtocolMasterUWP/View/SelectionPaneView.xaml.cs @@ -0,0 +1,48 @@ +using ProtocolMasterUWP.Observable; +using ProtocolMasterUWP.ViewModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class SelectionPaneView : UserControl + { + ObservableCollection DataSource; + internal SessionControlViewModel SessionControl { get; set; } + + public SelectionPaneView() + { + this.InitializeComponent(); + DataSource = PopulateChildren(); + + } + private ObservableCollection PopulateChildren() + { + var list = new ObservableCollection(); + list.Add(new TreeItem { Name = "Tree0", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + list.Add(new TreeItem { Name = "Tree1", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + list.Add(new TreeItem { Name = "Tree2", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + list.Add(new TreeItem { Name = "Tree3", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + list.Add(new TreeItem { Name = "Tree4", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + list.Add(new TreeItem { Name = "Tree5", Children = new ObservableCollection { new TreeItem { Name = "A" }, new TreeItem { Name = "B" }, new TreeItem { Name = "C" } } }); + return list; + } + private void FileSelect_Click(object sender, RoutedEventArgs e) => SessionControl.MakeSelection(); + private void CancelSelect_Click(object sender, RoutedEventArgs e) => SessionControl.CancelSelection(); + } +} diff --git a/ProtocolMasterUWP/View/SessionControlView.xaml b/ProtocolMasterUWP/View/SessionControlView.xaml new file mode 100644 index 0000000..b29d33d --- /dev/null +++ b/ProtocolMasterUWP/View/SessionControlView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/View/SessionControlView.xaml.cs b/ProtocolMasterUWP/View/SessionControlView.xaml.cs new file mode 100644 index 0000000..5d8df68 --- /dev/null +++ b/ProtocolMasterUWP/View/SessionControlView.xaml.cs @@ -0,0 +1,35 @@ +using ProtocolMasterUWP.ViewModel; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class SessionControlView : UserControl + { + internal SessionControlViewModel SessionControl { get; set; } + public SessionControlView() + { + this.InitializeComponent(); + } + + private void SelectOpen_Click(object sender, RoutedEventArgs e) => SessionControl.OpenSelection(); + private void Preview_Click(object sender, RoutedEventArgs e) => SessionControl.Preview(); + private void Start_Click(object sender, RoutedEventArgs e) => SessionControl.Start(); + private void Stop_Click(object sender, RoutedEventArgs e) => SessionControl.Stop(); + private void Reset_Click(object sender, RoutedEventArgs e) => SessionControl.Reset(); + } +} diff --git a/ProtocolMasterUWP/View/SessionView.xaml b/ProtocolMasterUWP/View/SessionView.xaml new file mode 100644 index 0000000..cc6471c --- /dev/null +++ b/ProtocolMasterUWP/View/SessionView.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/View/SessionView.xaml.cs b/ProtocolMasterUWP/View/SessionView.xaml.cs new file mode 100644 index 0000000..b92e298 --- /dev/null +++ b/ProtocolMasterUWP/View/SessionView.xaml.cs @@ -0,0 +1,45 @@ +using ProtocolMasterUWP.Observable; +using ProtocolMasterUWP.ViewModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class SessionView : UserControl + { + SessionControlViewModel SessionControl; + + public SessionView() + { + this.InitializeComponent(); + SessionControl = new SessionControlViewModel(); + PrimaryControls.SessionControl = SessionControl; + SelectionPane.SessionControl = SessionControl; + } + + private void SplitView_PaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args) + { + if (SessionControl.IsSelecting) + { + args.Cancel = true; + } + } + } +} diff --git a/ProtocolMasterUWP/View/TimelineView.xaml b/ProtocolMasterUWP/View/TimelineView.xaml new file mode 100644 index 0000000..e0d3452 --- /dev/null +++ b/ProtocolMasterUWP/View/TimelineView.xaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/ProtocolMasterUWP/View/TimelineView.xaml.cs b/ProtocolMasterUWP/View/TimelineView.xaml.cs new file mode 100644 index 0000000..5a7f958 --- /dev/null +++ b/ProtocolMasterUWP/View/TimelineView.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class TimelineView : UserControl + { + public TimelineView() + { + this.InitializeComponent(); + } + } +} diff --git a/ProtocolMasterUWP/View/TopBarView.xaml b/ProtocolMasterUWP/View/TopBarView.xaml new file mode 100644 index 0000000..cc75a9a --- /dev/null +++ b/ProtocolMasterUWP/View/TopBarView.xaml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterUWP/View/TopBarView.xaml.cs b/ProtocolMasterUWP/View/TopBarView.xaml.cs new file mode 100644 index 0000000..6f6cf6e --- /dev/null +++ b/ProtocolMasterUWP/View/TopBarView.xaml.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel.Core; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.ViewManagement; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 + +namespace ProtocolMasterUWP.View +{ + public sealed partial class TopBarView : UserControl + { + public TopBarView() + { + this.InitializeComponent(); + + var titleBar = ApplicationView.GetForCurrentView().TitleBar; + + // Set active window colors + titleBar.ButtonForegroundColor = (Windows.UI.Color)App.Current.Resources["SystemBaseMediumHighColor"]; + titleBar.ButtonBackgroundColor = Windows.UI.Colors.Transparent; + titleBar.ButtonHoverForegroundColor = (Windows.UI.Color)App.Current.Resources["SystemBaseMediumHighColor"]; + titleBar.ButtonHoverBackgroundColor = Windows.UI.Color.FromArgb(64, 127, 127, 127); + titleBar.ButtonPressedForegroundColor = (Windows.UI.Color)App.Current.Resources["SystemBaseMediumHighColor"]; + titleBar.ButtonPressedBackgroundColor = Windows.UI.Color.FromArgb(128, 127, 127, 127); + + // Set inactive window colors + //titleBar.InactiveForegroundColor = Windows.UI.Colors.Gray; + titleBar.InactiveBackgroundColor = Windows.UI.Colors.Transparent; + //titleBar.ButtonInactiveForegroundColor = Windows.UI.Colors.Gray; + titleBar.ButtonInactiveBackgroundColor = Windows.UI.Colors.Transparent; + + + var coreTitleBar = CoreApplication.GetCurrentView().TitleBar; + + coreTitleBar.ExtendViewIntoTitleBar = true; + coreTitleBar.LayoutMetricsChanged += CoreTitleBar_LayoutMetricsChanged; + // Set XAML element as a draggable region. + Window.Current.SetTitleBar(AppTitleBar); + } + private void CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args) + { + AppTitleBar.Height = sender.Height; + } + } +} diff --git a/ProtocolMasterUWP/ViewModel/SessionControlViewModel.cs b/ProtocolMasterUWP/ViewModel/SessionControlViewModel.cs new file mode 100644 index 0000000..c53600f --- /dev/null +++ b/ProtocolMasterUWP/ViewModel/SessionControlViewModel.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterUWP.ViewModel +{ + internal enum SessionState + { + NotReady, + Selecting, + Ready, + Running, + Viewing, + } + class SessionControlViewModel : SimpleBaseViewModel + { + private Object selection; + SessionState state = SessionState.NotReady; + SessionState State { get => state; set { state = value; NotifyStateProperties(); } } + private Object Selection + { + get => selection; + set + { + selection = value; + // Check validity and set state! + if (selection != null) State = SessionState.Ready; + else State = SessionState.NotReady; + NotifyPropertyChanged(); + } + } + public bool CanStart { get => State == SessionState.Ready; } + public bool CanStop { get => State == SessionState.Running; } + public bool CanPreview { get => State == SessionState.Ready; } + public bool CanReset { get => State == SessionState.Viewing; } + public bool CanSelect { get => State == SessionState.Ready || State == SessionState.NotReady; } + public bool IsSelecting { get => State == SessionState.Selecting; } + + public SessionControlViewModel() + { + State = SessionState.NotReady; + } + private void NotifyStateProperties() + { + NotifyPropertyChanged("CanStart"); + NotifyPropertyChanged("CanStop"); + NotifyPropertyChanged("CanPreview"); + NotifyPropertyChanged("CanReset"); + NotifyPropertyChanged("CanSelect"); + NotifyPropertyChanged("IsSelecting"); + } + public void Start(bool overrideCheck = false) + { + if (CanStart || overrideCheck) + { + State = SessionState.Running; + } + else throw new Exception("Cannot start in state " + State.ToString()); + } + public void Stop(bool overrideCheck = false) + { + if (CanStop || overrideCheck) + { + State = SessionState.Viewing; + } + else throw new Exception("Cannot stop in state " + State.ToString()); + } + public void Reset(bool overrideCheck = false) + { + if (CanReset || overrideCheck) + { + if(Selection != null) + State = SessionState.Ready; + else + State = SessionState.NotReady; + } + else throw new Exception("Cannot reset in state " + State.ToString()); + } + public void Preview(bool overrideCheck = false) + { + if (CanPreview || overrideCheck) + { + State = SessionState.Viewing; + } + else throw new Exception("Cannot preview in state " + State.ToString()); + } + public void OpenSelection(bool overrideCheck = false) + { + if (CanSelect || overrideCheck) + { + State = SessionState.Selecting; + } + else throw new Exception("Cannot open selection in state " + State.ToString()); + } + public void MakeSelection() + { + Selection = new Object(); + Reset(true); + } + public void CancelSelection() + { + Selection = null; + } + } +} diff --git a/ProtocolMasterUWP/ViewModel/SimpleBaseViewModel.cs b/ProtocolMasterUWP/ViewModel/SimpleBaseViewModel.cs new file mode 100644 index 0000000..9484840 --- /dev/null +++ b/ProtocolMasterUWP/ViewModel/SimpleBaseViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterUWP.ViewModel +{ + class SimpleBaseViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/ProtocolMasterWPF/App.xaml b/ProtocolMasterWPF/App.xaml new file mode 100644 index 0000000..e08c7dd --- /dev/null +++ b/ProtocolMasterWPF/App.xaml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterWPF/App.xaml.cs b/ProtocolMasterWPF/App.xaml.cs new file mode 100644 index 0000000..38367e3 --- /dev/null +++ b/ProtocolMasterWPF/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace ProtocolMasterWPF +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/ProtocolMasterWPF/AssemblyInfo.cs b/ProtocolMasterWPF/AssemblyInfo.cs new file mode 100644 index 0000000..8b5504e --- /dev/null +++ b/ProtocolMasterWPF/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] diff --git a/ProtocolMasterWPF/MainWindow.xaml b/ProtocolMasterWPF/MainWindow.xaml new file mode 100644 index 0000000..f2f7f5c --- /dev/null +++ b/ProtocolMasterWPF/MainWindow.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + You're On Tab B + + + + + + diff --git a/ProtocolMasterWPF/MainWindow.xaml.cs b/ProtocolMasterWPF/MainWindow.xaml.cs new file mode 100644 index 0000000..2ee9809 --- /dev/null +++ b/ProtocolMasterWPF/MainWindow.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/ProtocolMasterWPF/Properties/Settings.Designer.cs b/ProtocolMasterWPF/Properties/Settings.Designer.cs new file mode 100644 index 0000000..6fd2fa0 --- /dev/null +++ b/ProtocolMasterWPF/Properties/Settings.Designer.cs @@ -0,0 +1,62 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ProtocolMasterWPF.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string TargetSerialPort { + get { + return ((string)(this["TargetSerialPort"])); + } + set { + this["TargetSerialPort"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string InterpreterExtension { + get { + return ((string)(this["InterpreterExtension"])); + } + set { + this["InterpreterExtension"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string DriverExtension { + get { + return ((string)(this["DriverExtension"])); + } + set { + this["DriverExtension"] = value; + } + } + } +} diff --git a/ProtocolMasterWPF/Properties/Settings.settings b/ProtocolMasterWPF/Properties/Settings.settings new file mode 100644 index 0000000..6c68fe1 --- /dev/null +++ b/ProtocolMasterWPF/Properties/Settings.settings @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterWPF/ProtocolMasterWPF.csproj b/ProtocolMasterWPF/ProtocolMasterWPF.csproj new file mode 100644 index 0000000..ae620a9 --- /dev/null +++ b/ProtocolMasterWPF/ProtocolMasterWPF.csproj @@ -0,0 +1,38 @@ + + + + WinExe + net5.0-windows + true + + + + AnyCPU + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + diff --git a/ProtocolMasterWPF/View/TitleBarView.xaml b/ProtocolMasterWPF/View/TitleBarView.xaml new file mode 100644 index 0000000..03d2dcd --- /dev/null +++ b/ProtocolMasterWPF/View/TitleBarView.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/TitleBarView.xaml.cs b/ProtocolMasterWPF/View/TitleBarView.xaml.cs new file mode 100644 index 0000000..f6109c1 --- /dev/null +++ b/ProtocolMasterWPF/View/TitleBarView.xaml.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for TitleBarView.xaml + /// + public partial class TitleBarView : UserControl + { + public TitleBarView() + { + InitializeComponent(); + } + + private void AppTitleBar_MouseDown(object sender, MouseButtonEventArgs e) + { + var window = App.Current.MainWindow; + if (e.ChangedButton == MouseButton.Left) + { + if (window.WindowState == WindowState.Maximized) + { + Point mousePos = Mouse.GetPosition(window); + double relativeX = mousePos.X / window.Width / 2; + double relativeY = mousePos.Y / window.Height / 2; + window.WindowState = WindowState.Normal; + window.Left = mousePos.X - window.Width*relativeX; + window.Top = mousePos.Y - window.Height*relativeY; + } + App.Current.MainWindow.DragMove(); + } + } + } +} diff --git a/Schedulino/SchedulinoDriver.cs b/Schedulino/SchedulinoDriver.cs index d44bb7d..e67554b 100644 --- a/Schedulino/SchedulinoDriver.cs +++ b/Schedulino/SchedulinoDriver.cs @@ -15,7 +15,7 @@ namespace Schedulino { [DriverMeta("Schedulino", "1.1", "DigitalDuration", "DigitalPulse", "DigitalStringDuration")] - public class SchedulinoDriver : IDriver, ICallDropdown + public class SchedulinoDriver : IDriver, IPromptUserSelect { List schedule; int scheduleIndex = 0; @@ -27,7 +27,7 @@ private enum ScheduleState { SETUP = 0, RUNNING, DONE } SerialPort serial; public SerialPort Serial { get => serial; set => serial = value; } - public CallDropdownHandler CallDropdown { private get; set; } + public UserSelectHandler UserSelectPrompt { private get; set; } // Data processing handlers delegate void Handler(ProtocolEvent item); @@ -68,18 +68,24 @@ public SchedulinoDriver() invalidKeyReceiver = InvalidKeyReceiver; } - public void Cancel() + private bool isCanceled; + public bool IsCanceled { - Log.Error("Cancelling Schedulino"); - //scheduleIndex = schedule.Count; - Serial.Write("X"); - while (serial.BytesToRead >= 1) + get => isCanceled; + set { - int read = Serial.ReadByte(); - if (read != -1) - Receive((char)read); + isCanceled = true; + Log.Error("Cancelling Schedulino"); + //scheduleIndex = schedule.Count; + Serial.Write("X"); + while (serial.BytesToRead >= 1) + { + int read = Serial.ReadByte(); + if (read != -1) + Receive((char)read); + } + //Serial.Close();} } } - //Serial.Close(); } public bool Setup(List dataList) @@ -99,7 +105,7 @@ public bool Setup(List dataList) else if (portOptions.Length == 1) port = portOptions[0]; else - port = CallDropdown(portOptions); + port = UserSelectPrompt(portOptions); // SerialPort setup Serial = new SerialPort @@ -143,7 +149,7 @@ public void Start() SendNextEvent(); ReadSerialBuffer(); } - if(serial.IsOpen) Serial.Close(); + if (serial.IsOpen) Serial.Close(); } private void SendNextEvent() { diff --git a/Schedulino/SpreadsheetAFC.cs b/Schedulino/SpreadsheetAFC.cs index 79c0b8c..d1ffb44 100644 --- a/Schedulino/SpreadsheetAFC.cs +++ b/Schedulino/SpreadsheetAFC.cs @@ -10,13 +10,13 @@ namespace Schedulino { [InterpreterMeta("SpreadsheetAFC", "1.1")] - public class SpreadsheetAFC : ExcelDataInterpreter, IInterpreter, ICallDropdown + public class SpreadsheetAFC : ExcelDataInterpreter, IInterpreter, IPromptUserSelect { private delegate bool RowReader(Dictionary map); Dictionary mappedRowReaders; Dictionary protocols; Protocol baseData; - public CallDropdownHandler CallDropdown { get; set; } + public UserSelectHandler UserSelectPrompt { get; set; } public SpreadsheetAFC() { mappedRowReaders = new Dictionary(){ @@ -28,10 +28,7 @@ public SpreadsheetAFC() }; protocols = new Dictionary(); } - public void Cancel() - { - - } + public bool IsCanceled { get; set; } private Protocol GetOrCreateProtocol(string protocolName) { Protocol result; @@ -45,7 +42,7 @@ private Protocol GetOrCreateProtocol(string protocolName) return result; } } - public List Generate(string protocolName) + public List Generate(string protocolName = null) { if (DataReader == null) return null; do @@ -72,9 +69,20 @@ public List Generate(string protocolName) } while (DataReader.NextResult()); DataReader.Close(); - string dropdownResponse = CallDropdown(protocols.Keys.ToArray()); - if (dropdownResponse != null) - return protocols[dropdownResponse].Generate(); + if (!IsCanceled) + { + if (protocolName == null) + { + string dropdownResponse = UserSelectPrompt(protocols.Keys.ToArray()); + if (dropdownResponse != null) + return protocols[dropdownResponse].Generate(); + else return null; + } + else + { + return protocols[protocolName].Generate(); + } + } else return null; } private bool ReadExperimentRow(Dictionary headerMap) From 54eba14f027ec7df7f73ba5c69e4e9b222ddd1d8 Mon Sep 17 00:00:00 2001 From: Philip-S-Martin Date: Sat, 23 Jan 2021 12:48:32 -0600 Subject: [PATCH 02/12] Halfway Through Port - Adjusting all logic and data flow to conform to MVVM architecture - Extracted core functionality (protocol, logging, file management) into cross platform ProtocolMasterCore library - Removed attempted UWP port - Changed to .NET Core 3.1 from .NET 5 due to XAML islands compatibility issues. Plan to update to .NET 5 or .NET 6 when XAML islands are supported. - Rebuilt main application in WPF .NET Core. - UI framework changed to Material Design from Selen (VS2012 Clone) - UX streamlined - Bugs fixed in Log and LogFile - Bugs fixed in ExtensionSystem and ExtensionManager - ExtensionSystem and ExtensionManagers now directly call the client via delegates and events. - Some architectural changes to support multiple protocol sessions simultaneously. --- McIntyreAFC/McIntyreAFC.csproj | 2 +- ProtocolMaster.sln | 35 +- ProtocolMaster/View/Log.xaml | 3 +- ProtocolMaster/View/Log.xaml.cs | 1 + ProtocolMasterCore/ProtocolMasterCore.csproj | 2 +- ProtocolMasterCore/Utility/AppEnvironment.cs | 4 +- ProtocolMasterCore/Utility/Log.cs | 44 +-- ProtocolMasterWPF/App.xaml | 29 +- ProtocolMasterWPF/App.xaml.cs | 19 +- ProtocolMasterWPF/Assets/Logo/Logo120.png | Bin 0 -> 14627 bytes ProtocolMasterWPF/Assets/Logo/Logo240.png | Bin 0 -> 36923 bytes ProtocolMasterWPF/Assets/Logo/Logo360.png | Bin 0 -> 62213 bytes ProtocolMasterWPF/Assets/Logo/Logo480.png | Bin 0 -> 91314 bytes ProtocolMasterWPF/Assets/Logo/LogoIcon.ico | Bin 0 -> 4286 bytes .../Assets/Logo/LogoSquare480.png | Bin 0 -> 39902 bytes .../Assets/Logo/LogoSquareIcon.ico | Bin 0 -> 4286 bytes .../Helpers/NotNullToBoolConverter.cs | 23 ++ ProtocolMasterWPF/MainWindow.xaml | 71 ++-- ProtocolMasterWPF/ProtocolMasterWPF.csproj | 105 ++++-- ProtocolMasterWPF/Theme/Buttons.xaml | 38 +++ ProtocolMasterWPF/Theme/Colors.xaml | 37 ++ ProtocolMasterWPF/View/CameraView.xaml | 20 ++ ProtocolMasterWPF/View/CameraView.xaml.cs | 35 ++ ProtocolMasterWPF/View/DriveSelectView.xaml | 27 ++ .../View/DriveSelectView.xaml.cs | 26 ++ ProtocolMasterWPF/View/LogView.xaml | 27 ++ ProtocolMasterWPF/View/LogView.xaml.cs | 28 ++ .../View/ProtocolSelectView.xaml | 94 ++++++ .../View/ProtocolSelectView.xaml.cs | 53 +++ .../View/SessionControlBarView.xaml | 59 ++++ .../View/SessionControlBarView.xaml.cs | 40 +++ ProtocolMasterWPF/View/SessionView.xaml | 47 +++ ProtocolMasterWPF/View/SessionView.xaml.cs | 28 ++ ProtocolMasterWPF/View/TimelineView.xaml | 12 + ProtocolMasterWPF/View/TimelineView.xaml.cs | 317 ++++++++++++++++++ ProtocolMasterWPF/View/TitleBarView.xaml | 88 ++--- ProtocolMasterWPF/View/TitleBarView.xaml.cs | 33 +- .../ViewModel/CameraViewModel.cs | 101 ++++++ ProtocolMasterWPF/ViewModel/LogViewModel.cs | 16 + .../ViewModel/ProtocolSelectViewModel.cs | 24 ++ .../ViewModel/SessionControlViewModel.cs | 107 ++++++ .../ViewModel/SessionViewModel.cs | 10 + ProtocolMasterWPF/ViewModel/ViewModelBase.cs | 18 + ProtocolMasterWPF/app.manifest | 13 + 44 files changed, 1417 insertions(+), 219 deletions(-) create mode 100644 ProtocolMasterWPF/Assets/Logo/Logo120.png create mode 100644 ProtocolMasterWPF/Assets/Logo/Logo240.png create mode 100644 ProtocolMasterWPF/Assets/Logo/Logo360.png create mode 100644 ProtocolMasterWPF/Assets/Logo/Logo480.png create mode 100644 ProtocolMasterWPF/Assets/Logo/LogoIcon.ico create mode 100644 ProtocolMasterWPF/Assets/Logo/LogoSquare480.png create mode 100644 ProtocolMasterWPF/Assets/Logo/LogoSquareIcon.ico create mode 100644 ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs create mode 100644 ProtocolMasterWPF/Theme/Buttons.xaml create mode 100644 ProtocolMasterWPF/Theme/Colors.xaml create mode 100644 ProtocolMasterWPF/View/CameraView.xaml create mode 100644 ProtocolMasterWPF/View/CameraView.xaml.cs create mode 100644 ProtocolMasterWPF/View/DriveSelectView.xaml create mode 100644 ProtocolMasterWPF/View/DriveSelectView.xaml.cs create mode 100644 ProtocolMasterWPF/View/LogView.xaml create mode 100644 ProtocolMasterWPF/View/LogView.xaml.cs create mode 100644 ProtocolMasterWPF/View/ProtocolSelectView.xaml create mode 100644 ProtocolMasterWPF/View/ProtocolSelectView.xaml.cs create mode 100644 ProtocolMasterWPF/View/SessionControlBarView.xaml create mode 100644 ProtocolMasterWPF/View/SessionControlBarView.xaml.cs create mode 100644 ProtocolMasterWPF/View/SessionView.xaml create mode 100644 ProtocolMasterWPF/View/SessionView.xaml.cs create mode 100644 ProtocolMasterWPF/View/TimelineView.xaml create mode 100644 ProtocolMasterWPF/View/TimelineView.xaml.cs create mode 100644 ProtocolMasterWPF/ViewModel/CameraViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/LogViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/ProtocolSelectViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/SessionControlViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/SessionViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/ViewModelBase.cs create mode 100644 ProtocolMasterWPF/app.manifest diff --git a/McIntyreAFC/McIntyreAFC.csproj b/McIntyreAFC/McIntyreAFC.csproj index 7923688..714678e 100644 --- a/McIntyreAFC/McIntyreAFC.csproj +++ b/McIntyreAFC/McIntyreAFC.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/ProtocolMaster.sln b/ProtocolMaster.sln index a3d4ee5..0cba2fc 100644 --- a/ProtocolMaster.sln +++ b/ProtocolMaster.sln @@ -7,13 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtocolMaster", "ProtocolM EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Schedulino", "Schedulino\Schedulino.csproj", "{EDBA6B70-6719-4118-977D-F10BA534B394}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterUWP", "ProtocolMasterUWP\ProtocolMasterUWP.csproj", "{2C51DBDE-14C6-4537-BF15-D43788D57DC6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtocolMasterCore", "ProtocolMasterCore\ProtocolMasterCore.csproj", "{44917BA5-F366-4054-95A9-A8371FCEBFB7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterCore", "ProtocolMasterCore\ProtocolMasterCore.csproj", "{44917BA5-F366-4054-95A9-A8371FCEBFB7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McIntyreAFC", "McIntyreAFC\McIntyreAFC.csproj", "{4D707372-0064-4BC7-B744-45DD2F63488B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McIntyreAFC", "McIntyreAFC\McIntyreAFC.csproj", "{4D707372-0064-4BC7-B744-45DD2F63488B}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolMasterWPF", "ProtocolMasterWPF\ProtocolMasterWPF.csproj", "{35ACDD96-A12B-4F38-84F1-2E498AF2E914}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProtocolMasterWPF", "ProtocolMasterWPF\ProtocolMasterWPF.csproj", "{35ACDD96-A12B-4F38-84F1-2E498AF2E914}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -69,33 +67,6 @@ Global {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x64.Build.0 = Release|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x86.ActiveCfg = Release|Any CPU {EDBA6B70-6719-4118-977D-F10BA534B394}.Release|x86.Build.0 = Release|Any CPU - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|Any CPU.ActiveCfg = Debug|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|Any CPU.Build.0 = Debug|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.ActiveCfg = Debug|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.Build.0 = Debug|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM.Deploy.0 = Debug|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.Build.0 = Debug|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.ActiveCfg = Debug|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.Build.0 = Debug|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x64.Deploy.0 = Debug|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.ActiveCfg = Debug|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.Build.0 = Debug|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Debug|x86.Deploy.0 = Debug|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|Any CPU.ActiveCfg = Release|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.ActiveCfg = Release|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.Build.0 = Release|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM.Deploy.0 = Release|ARM - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.ActiveCfg = Release|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.Build.0 = Release|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|ARM64.Deploy.0 = Release|ARM64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.ActiveCfg = Release|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.Build.0 = Release|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x64.Deploy.0 = Release|x64 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.ActiveCfg = Release|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.Build.0 = Release|x86 - {2C51DBDE-14C6-4537-BF15-D43788D57DC6}.Release|x86.Deploy.0 = Release|x86 {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU {44917BA5-F366-4054-95A9-A8371FCEBFB7}.Debug|ARM.ActiveCfg = Debug|Any CPU diff --git a/ProtocolMaster/View/Log.xaml b/ProtocolMaster/View/Log.xaml index 88fe552..75b0e31 100644 --- a/ProtocolMaster/View/Log.xaml +++ b/ProtocolMaster/View/Log.xaml @@ -16,8 +16,7 @@ - - + diff --git a/ProtocolMaster/View/Log.xaml.cs b/ProtocolMaster/View/Log.xaml.cs index fc8fe6a..8a11487 100644 --- a/ProtocolMaster/View/Log.xaml.cs +++ b/ProtocolMaster/View/Log.xaml.cs @@ -13,6 +13,7 @@ public partial class Log : UserControl public Log() { InitializeComponent(); + } // Log Functions diff --git a/ProtocolMasterCore/ProtocolMasterCore.csproj b/ProtocolMasterCore/ProtocolMasterCore.csproj index 90d3c7d..fd81b17 100644 --- a/ProtocolMasterCore/ProtocolMasterCore.csproj +++ b/ProtocolMasterCore/ProtocolMasterCore.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/ProtocolMasterCore/Utility/AppEnvironment.cs b/ProtocolMasterCore/Utility/AppEnvironment.cs index df738f2..d9936b2 100644 --- a/ProtocolMasterCore/Utility/AppEnvironment.cs +++ b/ProtocolMasterCore/Utility/AppEnvironment.cs @@ -23,7 +23,7 @@ static AppEnvironment() private AppEnvironment() { locations = new Dictionary(); - appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\ProtocolMaster"; + appData = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ProtocolMaster"); assembly = Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location); Directory.CreateDirectory(appData); Directory.CreateDirectory(assembly); @@ -39,7 +39,7 @@ private bool I_TryAddLocationAppData(string label, string subPath, out string fu } else return false; } - public static bool IryAddLocationAssembly(string label, string subPath, out string fullPath) => instance.I_TryAddLocationAssembly(label, subPath, out fullPath); + public static bool TryAddLocationAssembly(string label, string subPath, out string fullPath) => instance.I_TryAddLocationAssembly(label, subPath, out fullPath); private bool I_TryAddLocationAssembly(string label, string subPath, out string fullPath) { fullPath = Path.Combine(assembly, subPath); diff --git a/ProtocolMasterCore/Utility/Log.cs b/ProtocolMasterCore/Utility/Log.cs index 49d32a0..1cdac98 100644 --- a/ProtocolMasterCore/Utility/Log.cs +++ b/ProtocolMasterCore/Utility/Log.cs @@ -16,11 +16,9 @@ static Log() { } - public LogPrinter OutputPrinter; - public LogPrinter ErrorPrinter; + public static LogPrinter OutputPrinter; + public static LogPrinter ErrorPrinter; - private readonly string appdata; - public string AppData { get { return appdata; } } private readonly string logdata; private readonly string archive; @@ -34,24 +32,16 @@ static Log() private Log() { - if(AppEnvironment.TryAddLocationAppData("Log", "Log", out logdata)) - { - - } - if (AppEnvironment.TryAddLocationAppData("LogArchive", "Log/Archive", out logdata)) - { - - } - - // First two are redundant, but it is prettier this way + if (AppEnvironment.TryAddLocationAppData("Log", "Log", out logdata)) + {} + if (AppEnvironment.TryAddLocationAppData("LogArchive", "Log/Archive", out archive)) + {} string timePrefix = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); - - lfOut = new LogFile($"{logdata}{timePrefix}_Out.log"); - lfErr = new LogFile($"{logdata}{timePrefix}_Err.log"); + lfOut = new LogFile(Path.Combine(logdata, $"{timePrefix}_Out.log")); + lfErr = new LogFile(Path.Combine(logdata, $"{timePrefix}_Err.log")); PrintErrors = true; - ArchiveOldest(); } public static Log Instance @@ -62,20 +52,20 @@ public static Log Instance } } - public static void Error(string message) => Instance.I_Error(message); + public static void Error(object message) => Instance.I_Error(message == null? "NULL" : message.ToString()); public void I_Error(string message) { - string toWrite = $"{ DateTime.Now}{message}"; + string toWrite = $"E:{DateTime.Now}:\t{message}"; lfErr.Write(toWrite); if (PrintErrors) { ErrorPrinter(toWrite); } } - public static void Out(string message) => Log.Instance.I_Out(message); + public static void Out(object message) => Log.Instance.I_Out(message == null ? "NULL" : message.ToString()); public void I_Out(string message) { - string toWrite = $"{ DateTime.Now}{message}"; + string toWrite = $"O:{DateTime.Now}:\t{message}"; lfOut.Write(toWrite); if (PrintErrors) { @@ -114,7 +104,7 @@ private void ArchiveOldest() logs = logs.OrderBy(d => d).Take(logs.Count - minUnarchived).ToList(); //final archive name (I use date / time) - string zipFileName = DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss") + "[" + logs.Count + "].zip"; + string zipFileName = $"{DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss")}[{logs.Count}].zip"; using (MemoryStream zipMS = new MemoryStream()) { @@ -139,19 +129,13 @@ private void ArchiveOldest() { zipFileBinary.Write(fileToZipBytes); } - - //lstLog.Items.Add("zipped: " + fileToZip); } } - - using (FileStream finalZipFileStream = new FileStream(archive + zipFileName, FileMode.Create)) + using (FileStream finalZipFileStream = new FileStream(Path.Combine(archive, zipFileName), FileMode.Create)) { zipMS.Seek(0, SeekOrigin.Begin); zipMS.CopyTo(finalZipFileStream); } - - //lstLog.Items.Add("ZIP Archive Created."); - foreach (string log in logs) { File.Delete(log); diff --git a/ProtocolMasterWPF/App.xaml b/ProtocolMasterWPF/App.xaml index e08c7dd..29b850a 100644 --- a/ProtocolMasterWPF/App.xaml +++ b/ProtocolMasterWPF/App.xaml @@ -5,38 +5,17 @@ StartupUri="MainWindow.xaml" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:materialExtension="https://spiegelp.github.io/MaterialDesignExtensions/winfx/xaml" - xmlns:controls="clr-namespace:MaterialDesignExtensions.Controls;assembly=MaterialDesignExtensions"> + Startup="Application_Startup"> - + - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/ProtocolMasterWPF/App.xaml.cs b/ProtocolMasterWPF/App.xaml.cs index 38367e3..4e11f71 100644 --- a/ProtocolMasterWPF/App.xaml.cs +++ b/ProtocolMasterWPF/App.xaml.cs @@ -1,4 +1,6 @@ -using System; +using ProtocolMasterCore.Utility; +using ProtocolMasterWPF.ViewModel; +using System; using System.Collections.Generic; using System.Configuration; using System.Data; @@ -13,5 +15,20 @@ namespace ProtocolMasterWPF /// public partial class App : Application { + public LogViewModel LogVM { get; private set; } + private void Application_Startup(object sender, StartupEventArgs e) + { + InitializeLog(); + } + private void InitializeLog() + { + LogVM = new LogViewModel(); + Log.ErrorPrinter = LogVM.LogText.Add; + Log.OutputPrinter = LogVM.LogText.Add; + Log.Out("Output Log Running"); + Log.Error("Error Log Running"); + Task logThreadTask = new Task(() => Log.Error("Log working in parallel thread")); + logThreadTask.Start(); + } } } diff --git a/ProtocolMasterWPF/Assets/Logo/Logo120.png b/ProtocolMasterWPF/Assets/Logo/Logo120.png new file mode 100644 index 0000000000000000000000000000000000000000..f8223d898ce74670e97d8614f364f67585ff1331 GIT binary patch literal 14627 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�(4`AoGODqe)%uH zJN_NMKNW||XXBA{Hz~N(V@fiIPNQdCACfu*%xHS5TtO76R2Z3ZWOhqb?(^uFKC;dj zpM_v470#nL|6e0u{xHLK4yz4#+iM=Jt(gQOb;{)U>iHjVEj9s(-~Wi~ao6$3FMs&X zijqeoOofza`$lpTGVWfByCtZvS};w{PCY zz1#P3@Ah4LizjGE{X`n1OP>~L(x*j+EE$mT$qaZhS7v0+^(1nHXEXP_Abz>y{h{s9 zHSfxMcaxUAU;llvZNg(2kOyq^Ux#3)=NP(cXuc3ldsWx!)AK9-dM8J$|ID*V{GbwQo!nX4B0&-G*uExc|AJYYOjz6uVh>y!Wh002ovPDHLkV1oOo6p{b{ literal 0 HcmV?d00001 diff --git a/ProtocolMasterWPF/Assets/Logo/Logo240.png b/ProtocolMasterWPF/Assets/Logo/Logo240.png new file mode 100644 index 0000000000000000000000000000000000000000..9c530a3b6acbfc92bd283a175e9d16a9c123b18f GIT binary patch literal 36923 zcmV)^K!CrAP)V8vgtIEqY+}+0`k2*M5%y5FG+C zLPvUpLh1B`gtWS*rO}{04Cq;O4fkofcE67B{Tj6UbzO(1X*U4efQB2ouKylF=$H=u z5;X0irt9YcoC}9>7E$;$Ig+1>(l4GWkv{I)-=bsw<&D(-<TzAy zAJw2g0!_v}l9rr}9lM{Z5iU?JvR|1Y5L(V^|qpl{WnuPK+l!uNksSN^MS z``-lqlV1PwbBmu1hoegBTKE+WS|J^JGEgA>2mGsPTD%VZM=cau64Db^l}%S+)1TC} z|LWWPH-Yca>z{jKZkqd&-&Y<$SxrM-I$0^g{u_=p4HLt9!t#4=-C9~Y_RR}-sH^|g zm;G-7C*kY=`sCL+^v*A{E>+M<_ZUf!34-Ttq> z)PEEBHedg;Ul%=PN6jmjvE0tvdd(twQD$i_E-FR_6*LRJBzdD&*I#L3pjuAJPsZG1-OIc`O$2TBF)38 zkv?r&q)n3!8PjJ#hVlq=+jnip z)}Oay_x?SIk54cs54oQGkxW*5l38D(lbZg<_&u3~bS^T-#vdi;$z-)Zitp>pF?=4@!N^dUb=hkUvjeJe#io9!mL;ZAm1mAH(|hp=41bUQYM&^ndLv^?30ACa};s zi+)|yNCTP!cq*|2=IHTb*tlgAHg4UF-TU^KxM=jWB;ac!r-gK^xg6yCA~GlXSTNJs zNz5W|kiXl&uH(sEPoQAFXHc-fv&fn`OJci}{BDn?VaO{PULA2e7x}0C_P+^iz0t*| z7dO>HXdMoRA2pE4`q;?ibA0HR!&tXzJ$~A_9%KSjGuwq!48n@Y>q27c4VED#%Ym8i zB}&P~OiC$-Cg)j;!1_K#VjGzWCl5Q^-9}ssnIFlT4FwB4hnJpv9*<^!#I*LQB@n#7 zkA*aCz$+PF9r#b1jEQ-kfBgjh`qxE`5_ELXG(2ig+JJG}6Q@pM%};Bw;Om7To?{ki z%}|VUMJdM>&(lf5A+Pl%mg5?K5;!Y!-n@)+UOYB0Z%s-W2lhNqe%wZfrghfC zdfrI5*|qD}@Wa|4@y&OuuXdnMIDv##gQHnUmVXVI@IcC9+das*2;vIIF0= zX5+sCM>c1pHxi48t30OnwU9Y?j$9~Fv>1vNDgxr^j^usly1qvXg+8y4p~9qp%A}0s zIsSDMc+uHKMG)3MfR55KAX8Duy$AMV*~(>D^}~0DnP`k<1_y0Cvi@$q1ci4w5^k$P zUi-~4NE=~}lG#Q&DX9}%;dKtgeT5#e&2DE%8do>F&+Y|ks%Rk%C5seC*^;G^J7+Gt zd5kIJGCZ93rG1ZZeD_yl-dGvQ1o*pu_g^!Cm!4Z1ose+rld!Hgv}KLP*kxpnlH+w7 zHekt$rP#WCyJ>X>Kvn}mWZx5zHxkb4#i?!ORM2cD3DSz*WIDaPI58I|W)WU>ebIX@ z^JZHd`st_gphDU5c;UI{9rHTC`_gqxj7R*3m7*&h`MXcfAnpHGOyEVoE@}nnpF+n2 zCNBDGlfPf{0~Rb`I!HhrEf$63II}Tn8O|ph+xfeqH(K;U(@TiR%W_%+HwEOi z&28#$QgdLN6I&$J#7!Yi93*Z`&1wfrEyF{|SGE>OhZ0jqmj$TQP+`N;mI3ofw(NNA zwg(Gz&dnFWOs611M0rWLjkhH^xaGg`;1 zfA|gy7B0l0qlX<$uphULN zAa!g^EZ%(Wb(AVm(tIz9_h-k!&GQ?8aHm(JE3W=KPg1Y8|6?ccSEs+~sR12>zPO*( zufy#5^KtO7g}62n3RhsAuPg@^II%14>q1(_fyHuS8S+xnC^W@(7R$JJ5XH2BeBMax z5hoUr*mcZUCYEu;b);le>*E$N;9uV#F;ou&~V_&@9OkDI`Y zPA@2->)HSSc}<{TWRH^Lox68o*1Wmcwtc(h&#}I5`*$ovu_G_^SQ774a9=J?Do$25 zbi`_#1+8-+?{gAz;6R*MK;l4gVUIX5kK8oUC78>WIsRns+^AN$DxS`hCy*9f*Y(}W zqC%}-$?)3pf6^5GM@`^`rxtVvw5~z4%ID6X$Fx~9vFcmFx)OUta?CncO&oP5xkUm^ zGm>F#@;ER92p-;M5gAS7r73@wyGaPoGE!|WQpFk;ywBK)3v>6go2cT*5@dFHpRE){ zF)pm|Mso~Wg2LxeRF8{8Y^F?%6kM%U*ERHdJ^J-d|CCAmkC?!VeqHobIGiv{ zgI1J7Dp8?i9nW1bA5*4JLqa&r$1>Qg8V@hB)XU6rG6g4A#(ljwwAXcmD6oQX-~im$ z_STDvEK##f9afQYVXyBq3NM$&#i9Y#s>Gp2)i~b!uXz6&ur@v%Zdxg}(zd_*ME=`P z;IB@8)j-qq5jv3E?`4os(4;9-aQFzxskJ@<5Z7UFmRTp^z7%;~NK3uBGR?D;`%-Q! zVkLvL;{B->#I4U#Dx;+wl$C6=9VBKgxxCMe$U{toU@DOq4lX|q%mBSikY+SXfy$X< zV^OzeEfjqAS-+t`+M(;=@O!VvzA@7G%Xje?{?-$C;i(0~!@B;SpLY3=KmNe@$&;{b z`EpxOWKdj-6Wf!H<;X4$EO6ck5Sk*dEWg(=_pSWhB#1LR0xs&UbwW!NNxia(JQY+H zhvmHG78mx63;Q85YbkJ(T*84_e!!UPbWti(vJ~pot!)$_@YPxaqbg>q*!V7}1 z)DmE!hX$HWsG*#fLJ#YGcH^}9m!KS&-hk6zQ7tu%6AQ=I9n4k1h17zZ-nxOGHrf`~ z7+Pt*>a_}S?ORI_IaR7uXi%>n3KuHmJBguVT}X?1`_)XZ?)ke-AIfEOEViEACIvUCSb|ZrF=mPR>#jhJ#UA4DXILFR;u$sDr+R_zCpOKNB%B{xH8V{ zp`em!q+R*KK@?a4xwB(dQ?p&*4Xm^rK&g@?(WpTKBu}2)Ph)*m*HHhBOmEElJ5A!h z)dXH}YC#+5dOyDo-nDxdMtwOJ$BrN807CjZKO|<(OXIGQYM=eMu+5pB)_UF7#%&ft z{W^$sep7^Jkk9r`TV7%w=~#s2{n!paQFCG!2NtB*wgJOFgO4ZL51}(fM;ntk@6%8F z^$fHS+P@zCMql4A-X&l7+f3m3C+7{;G_ARxc6rX+`Is_oszuQ}LM?wa2NvSIG}gl@ zjAU_LDfi|%F7+EZoRk{95>6aAF3T&dWE|K@QIsgMTpX8%mV_#p3a(5iDHqo__=RIR zQ4Z{^f&BBUOD!=Kktqz5TR*bGliaOl&pSyknZ_+me?;G`9;e0Kyqjn7T|I#poLi6yH}#psS>;t}V(;F47&?47PXGEVm#c1vby=-} zj04*-6{|ZKZ?qB-Bk)2~PVA|DPAcy+6?rf@dycPwrt%3jt^7D@gdCWrC~`gu5$O|+ z{FL(s4a9N4f34CHtE>-E@S(u}FX6y+z_8NumtK4kty{G~C={|zOY@Dd=sMo0l)2KkchUTH8Jd4-+vcB~ zQ(6yei#1KVM^)eW!ljECFkmot@807G`fYD5FYHe=2WAr#0%)9r#)Tu|z)5glDe_v$ z`?7k@5#{l+HM101NkJ4{k+vh!IIxTZJMD6iIA_ks(Y8%%q)wemxi=Jmo0_Iqcq69r zT~oH_u0dcrd7Tda}8$!W->V*O^7sh_%p>aAKiqrCABY9DcVG2X+)7n+Mg3wgP&npsJpg|kTU7z)7Tq<{q7SKAQtG zUtnu0kUu9&mK>crwL`wVdDW9x)2?WGsN5Tw-&lXgkS)N4|I!IO`{?W^Lm_Pi>una} zy&E=cMDO1H)q4tY-q}QOVAhVhoqS-wxUf)p!?ra1DsKe1u4jI75Dl~h@$9T6A8mA! zXrUv@N51_yu#5xCva~vNY=>tHKBLx96Yq35T&7CaDt}ps=`WkW3yv+wln{#l20E+) z!0zQ{!}^Wr)2F|ALc(D=C9Vt^Ip}2EtpPc&c;6)9z=89F-TmQze|Onr@hYykudf!_ zQDiwULalY?Dn8qJ!&WOI%?D;pEcQVXp~PYgDrH@28Ru@_z73vxzMvWlHSCQFCo5Gk zOT|+G05i$I@|R3tB9ATOxJnt!e#Sz6aG>m@B+LhPD(ni{Z>0~w?Gvy$eppAx zPHj=JAT1Ig8Lyi5W0sh#C7#uuH96j&`1wmF@Z96G=KzkjS)9a#^Y-XLO8N@!Qo4}S zbXnn;U6{FiSQ&z|OR^6ldvRcMa=2?^1=u{#JMXPh=0zl=Nz9kSVPFnDw1T|?B2B-pLXwsTeogHgE(;AWkD6E&_LUhn{)Ui zA|F_mhZ|r=23b5RxKjL*&$m~`3Ilx{>eib`hyA(=lWVzmJB9vaL_^@rTg`F50 zQ`4Sno-|ZtIpn0zDQPFYV6v!W_~66$kt=sjB`1ca4XK>Da*ID_COL%WPrKjT6SLds zI{K-!%}0JYicdfJ3|FuI=C5TIkcSmCaSO04{_BIdT!(o4-p#?CnoquUp;>*Wv^JL2 z3Q1g;F1_6SgVcd$YiUU|&n}mN(UBZ$HjjPlK#MJ-z!jh-7TfuL=*!+l@jH7Gdp&QE z`v6Fp@npN5@hCecdwr|E(u^cydero+`CiJXhvnqx(ZN4hbd-1|0eDYBb zoH}*N+4@phCt2Wle+ma?>J-~0UA^}}60?xFnpl?iS>=s(WMMr1Aho5vq z>Qt$eA*L|!M%64;7u@;j>&Ce|a?hEEXXZ?nJnAP>2FJVszt)Gt3HYenr`Wr1pL2iL z4h7CB>W#HIj6{)SbG}Fsm|oNmd71Nxstc|1M%(uf$ca@ovl!0GacEl`TS?&A!~1N$eqnDUp&BL|+2aPJA5zkx#f}H-H!klx%U}a=VAj8%GiOeG+`XHv zot8LaFD1n5FUCC*#|-E@GPV93E%cmYv)Aa*3)#EqvDqikF|hv-{Pg2Gj`J$xq@G?` zk1Qlci6!U2Ob4_1zkzA#A&>hgriecovq5^S}7w^Jv|+1;0NumsJ)1u}apsNcMk8)C|AlCiLthGlwIj zy{Bs3E^^@pGpIA zOn16CE`1PNr^s?%25BRy18pNW(|#eC;H!ORO{+*bvG9Et^7_WT1@}FJ>gJ^#%jLC- z6;QiwP4&bJ>m#aUi))gonHlg$cXR^JIX<(29>y3c(&#!?tXPFHUyQYpSH=;YlCf6Z zWGSKr<;0BtMV1fDgoL=%K?9NKVi9~#B$XH0w@x%;UMrtpL|VJd4^v}`MN-yMQ03n; z-=}7wqdm4ss10SFb)dbp&tAxH*PXWJig4^$08CwPV^T2)%g_XA99)cxOXB48KAz7H z4GrF@k22*;^9kpSgEAb}8^&deBPEdU$mfnt;JL@9Kb4?|wrJ3jbG>T~&fUBA;^PlL zbskFOyprmSHej$_A*)y+R2ntb%}DZRDG&+wVS&MDuJv(J9~CvSY=mX5z+QlkKAC%- zb+3zYV++b`?Xq>=#d+B{GRuL<_ic0(EFTr;6;cOz^Vxa6z;&RREGVW!c}bb*ZH~bE z%-9ib1(t=boMj^G56&A;5!&gm4?pgLC!TtO_s(0BSO;z;XyF3YvsB-CCuZ;+o4_-V z&iF2*g^J4Jz1M!bhRz+j;rzMtoW?n#eAxii86(RFX4C_I95}-GuOAY7abeE;>*f7U z4EL4Ag@x-#IWKD_2hmzbpmi3#)2{qqZWT2rlRDljDy#z51QQfyp4B#W9qBWqM~|K# zBjrOWgn#%=>Huco6Tit9|k~c){nNLE8?k)pJzSKdqXjTDPR0#7U08b56`C5l+xn zF-3+LS)hiwv*u&g^f{cyS;`6h@@1u2NRqn8Tum&oW|k>~`W?QYf!uL zhqEY%MFH4|oZQAxgs~-*=R~ADp|?Ov6Y4W zY;SK$C;0@Pd1S^m&DdO<^+KA1Z^!l>=}>w(@#n# zv99aey- z$tLiOqcaL=I@XAkj2@33@g-KR{MMelOs!L1#Vb=_$pV;S%l*93swb6Phg;)h=N0W? zoujPtm_uIC`^?#YlARk=nrWFvS(ImrlV40cP>Ir&+?EL?1>wA`fgwR&7ZL?Q+6Xzb z?cj3h|NP>;s`G+n$@YV_sFx^J4DYW(_V3BTYK%JeYTO-Sv}p}=LogW5^uB3c|EeM zTsu!l5WQLJ_wZ7Y7p0*kXxf^%tksI#H4}K&FVkD;nl?a4<}-%-^=to!_N_YM@}(<` z!-(e3vJxFXZ?t&cd3>7!Qa^_z?rZEt9NUQ;)_WlP6ZaYT86HTHg10cO^O}a%RkKzfc)OGMwh?&FkvY)`TKoY$ ztUn;*zN3bZ!MERh@2;#N;gC`e=##bM+E16eZUowB)+}aY50)9EH z$2;%6Ezci@emxnIKVCUo<~c!N1Es^G>_U}AgH@BR{`mRbUAyBNP1MJiY2j=)IEb^trNg$fy}$*khg0;l6d z3_=!}%iFBryvn#M?L$BA8}xh9>Y9v0E0FhD)fGL7o0eLLhf}Akfmei(+1H7`^AL~v)ZFZ z7Nd1eTwGYl7xv2 zKD$3MgqFBOSc(T+Aq_pg`iF6IH-jPXHK6%o2DI<@m|kbPoj64l@GFU;Xrw~sw^dDL6qbPa}F>Yu=c?FlT4$ypQJ9tJkY6#&j} z2MP~uwk-bZ$91{$3QMHVkeCkDJKJMPb z28_T@Yu5AJR}kX6I4~Oz6|-)*W8pDnT$P3^S%t6P-bo&aF6~&4`?8dcne)64SSYwh zNfO<$fK%u*NarfXlvszACqi>iz@VaNNic~7GGD9c?NxDE)!$|4C_`!1kYn2sMC;7H zT+thDnwC6rD!4BvuOyRLr`y_?1LeiS&!cH;Wm^39>e*`~N}-jg2|V-Y^hV)ucqC~w zm#+#9JGSjaukL--J1GQEFeQmL+OP^G`vu=lT$qUiJI={~TIryOs#0#r@_!ZizP?B- zaA04K%ObCg^9504*$yrbtuwWIjY%Tp>moT-001BWNklTK5<_=&Kp4cEQyng zp`joOE|(?4^jGH(JK)KupD@OyTZ^tWuKq~%QIVo>q9*XPU#1?^pgl^LW;P%7)2^Rm z&+dIzBPp(UabUrlYPZPxyLfCPrGx}o=WaVLEK)#9f!N+v^_+)yT);I3ktO{ja!giB zEz2+F+?zfeR`e#@iw5mLMO@c@0I{Z(H|OQ3y=;UA;JC)mBuGpD#*HEYdATGm5pnI~ z#A%;xQHS-u%r!{t53|2=<<5bwpLVvabZ7ip?K@OGd(Fr0_ynGIcv@3U!%!J#B4#r& zpA%KjSi)(&@?;aOjg}&_yq>f@CnS?H(OPLQBo#TPZ9xR6CYcoZz%q_2<8&hO2hI1D zAholKNqCPnnr(*^TMmcSK)e1Pu;!KEh!XRD6Qwv46==e7lh%z5g_dmoghE=gYT0TG zi)0cnRZau^+#nl>C*NC96NHHVQh}#$~MykE-VWbanp(QSa>0z z;=(ql#tIHCzW{qU4V zVPK@Q&4;tneYfg+j2SjA;4XsR^^`rez=AT%30biDvO#NP`Q;D$)X-uocQ`Ui#(d%` zdnn?>etE+pcMkgfND$etMpmMbnL4<MY`!r$4(od3T94EU%!MPGz|#&*-2qL1%3Rh34@w()NmpMPw%tHc_kcJ!Cey_7nWU5h0uOI$pWDjx)N1-4iE&08_VLb zjP1iCsT;XH&)b7jp=D;O50Ph3c9R$K3a?M(xEw@DII zo+C>gT%=!&`!VBPJO+>{HX6MKeP)w-em-eCYdlgjPf#=3Z`j$_nR<9?oCeI0+3ssr zuf@orqrJJWSA0~=lT{ph<;w<(#|DTCvziHMsE4mUV|0*cjxvjuv|V0t-=N-V6~c;~ z)r-T*IJfM&K@mCt7xv`5jNei!*%srzqSo0qpM5mXEb>XRjzsxr#NKCFKM3CYcbdM9 zq9uz+xiB=XTFpml&I~e%15e;-ho`RAb-lRE+1ah#M>u-;n0Y_$ihNd$*I@S5M%&1z z_CCAs&vb!bzOWavs=U%cyw&1c@bF%{D5|hl$g~a8FtB`0;Wz#gCDYQJ86oP-rYoyB zstj2p<+dK2loN5N@{K(?Ez|JC#(f#n$rXZQCx9cZGjGb=FY6;&(7n$`{Cu&_YtX)} z@kq@QNj8Be9iCJqO4kg{vsn3I<4>C~q|b0W1un36ID#ymM8$`c@7_)UkX4$MCCj>kjusu8;hE-cJ<10nF# z!;{BD*Xr4EUxows`lv5tNgRr0XTO5?(c z>?IcHIqK!tQ$fAc5~NpgbTvm77j6k>X5*4foY(9{Ax`N!7OpQKugma|BAI}6D#Q-> zcYAVM-?t~RgyR}>fxT`}q@{#j9A7l?!3%jAalPHY_5fnS4othA%##}(KWb-PmG>!W z8Ya|yq;`Ej6!sgIn0a<)iuh|cFY7v@xRnn&4(>aI?wxy>-ZZb;PXRfupGG=K;=+ND z*+VPsN(q#3Vrnl1(N4?a!ihmx8A7w%H3+i$abiC%%uE}{Qe*5~OJU{8m{e+-Re8QD z>y>xL6XIVy*Mq4%qk>yRx7^$JUFb{ z9V_|7(*>$rLI z7A{;oZ)`4p?D%o)KXeeg_w2@*GiROjU?G;z3VDoIFLGi(?X<^v2M`!Bj2}WK)p=1C zXr(&R=QBAQ)%jcIxXAh0StV+lz2dww&g=3n`_*|CIkNNOL?2F)#C=s-a5h(fa$I|_ zCv&wS6L9#XA^6|@_d9W6Cu?kN%|~k&O7sMter)=q3Ab(?WVO!ZV&wSS<=AQpH zrJR}Rjif%$rBTUx-4BryabC%JskzNQZy@Al+~XY53KBSDfV=bC1IK+OT4*NG+ibPa zAN3m5LCNyPow%^&up?iLN}l|&>RGBEQVn*`VK(*9=LY@lp3~NS}@%u(FVWcSz(8{_gt!{U3Ji-i@!9FUN*W8(it5_T(1r zSk}I#qmxClpjk*>oY{->`f=nSUTX;l=Hj|+l>`qvo_l;&(;PIv*9-B2a8R#ge3jST zN8=nQpPHs3a;BS(M{_=gZl85##ycAg(K^?Dw06%#P2kA~Cv5`ojQHczCr+btOJSQA zFK+0A%xvf>YnH6|;FB)MkUpLDD`w^7W(U3KUvr*VA?Gh#z=FjK@ztV5NC+p$`#1;^ zyWF}yq{p>5+@>)in`}WYnFwU%E-P|EdXo9OC+7`X<4ST}2MGh@`})Lv9k#%G_t|0Z z`P@42`QYw|j*XV}`sUh?)p<7J2|V%O#HT{gc5qr_YXx37YZ2y7TOe>gR*OtSJ_5pe zd84frE_0SxeE3N>q)(sLNv3AJ+S8{S9X=ah%xYX<;~|#9Qr* zIk~o)f+E$el0EE2Q28iBd>9HtYpd6R_1h}}b<0o=g)(8;SrEy@-xG)n} z6*wpB1y=3#q^!VU=NJ2XyKRT9omyxP0G5NZapyOyRmAIYuQ4G$>-%Og<~(&Dt1I1} zREZCh4o>Q(LI1=t*&U>7R<9MVUArCu;soHtR$MqsmdyCz<1WaMA)VRR6o-X{(xrv{ zi_eo6vv=PT0Mp$}TsW+MP$x&d?y^2t zPv8R+Hffsn40T}9YJNmA`BA&>T#KCX2%cs08A*$B{DMH-M%)XdUcG_E-M6-v=P+h7zBX(u|f);7NNYp~zdY?+S|$4Zv0S2#ZXaAI3s*Zaz}&)r&ogcC%rIdxF6{REaoiwrTuYwI)or#B)_<2lm$uof#ubgTT1YFc zgMAzMzAkOE%=;{<`IR|aEC>qAw9z`yuI^)X`vsc76Aw&Sr9mqpYM)OYJAtmPmDL3J zYjaBj&w;rNQXyYBbJoo0`eA3J&yd!xfkeY{bmgVT8`f{aXFYmRmTVz5ZHC~TmT09P zdMG8bX3dI>88YCJN3$bKY-VJNjlqK{9zax7l<)n;_K3%S{BaXgrcc9yg^Pm4jc*5G zBX!0nh6A%4R=gj3JqeO?UuVo}@om}cfw`VdE-WNZH0SluI*SS{xr0lRA1%rE^^5!3 zk0!B9J{|BOGG&Qz<=X>TUH9>NC4DFGggq0|gp%RBEV8g@uMz9T;Crhc;QZK=Iq%jNM(`SM7eIu*2# zm{{zeNW1^Q0et#-PyF`VZ@eQb2nxz3QIOvy<~y=U9OODdxighXs{DD1^MN@pZ7YXt z4E7s;XF60&=2viBcSy*(kw?C-FE^LWZB8%B8Dl(}1KtF(pMU^ViPj5-Nfk77j@of+AQTmas+HfwD=(MBgAYDv&3W(kQ3psud^~2%o{c&4 z<|zS!H56>%6yvCD|FRrPjJqcFaZrxSdYAn;t>5{eab6l{m2h6$VdZFxp-XCBDeGe2 zM*gq6?lawT)k$YFnkCsEdBE6yc<7-ACEDoF#5#}Ht>-;~_m4N%6~;~?$3K0$4r7Ll zbABsBT-mxIMM8C4ICIumbpD{D>4mmeb|#0939)S4un9eT5Cpb7YV1C^c1v(Itn>{!PAk@N_T{|v{j$OVwgWiijZo_>(Kd5p7USO3ES7Yl%mXj} zP3X@C&FZ00$wKyy5bS)co1;EAQn-VOhbBI&Cxkc3sN|>tW3c{*jf~D)g~SZkWG=+* z;NYRiipd;4aQ6X}4ul!p{Ew6_keh6(^ zx5SfAJYh^=Rr{#tikv5*rRlR~Vcz_&crMK7=4Jivvho6GH0-t4nzX*67eS-Jtx0}y zTrZC6LS%n$a-jTLRZT1Q!P)%Y+t5z?aa_@Ylg52T4$ifQuVg}+{X)^_@J_1+^7Qvm zs9@bk>k^5x4NLa2F>(L+4mv>Hy4-TopmJjwj=8<7iQ>ZaRyty{W=7`^Iv7$)X<;)a zQDNhT&FJ+R@vJjTm*lE6TRMR!>>u~7uA`W^cgvB3NAPLKo`Uz;9S0WU!ZtFoNNksH z;W;wp)&zmy|FENpz>>PTY#m*Kz`c5qa5e8$R@IfZLj#ProU;rVB=(M5n@IA^GX>DJ zNn@l;nbH=Wh;@G1+(VkE}L`l8EqmmQ|nNPv?B6Gc?Km6 zkg^w5W`ABkjvLW=p=K0=Xk33Qkj|DvvdE{Z*6Fj~udTomeov9tm2YfU{7~)nB$Pw4 zd_i4$c0~5aA93wH2I$|_%kdV;J#-`Rg#F`h0eUiX0d`2!lDSJUck+C5;js`_q*V0p z_Ojv0k%f>4V;&PBN2d?k8%`|y<046BJBSmj_vihM;pJ#o>67_z-XNUVMZU~yvz)m8 zTlG-BTp5HyA>a0f!(q&uw*b>;&T>;!%W)u;57SHdfAmUG6N zVbLS37o7TU*NZPtF^F zOUty^{PruZtW}tmXR@sq;A@{8GSz+;3QVa!r!;ciDg_=K&xiWo1jP$Qu)NY zz2y9flc(_EN8NGlx8J1BTze&Exwyg-NCdl$>?eMTDRD6gxHtsyVoMt;MKZ=VuuVka zJ=pPD_uoB3NM6XR;aoJdR@JXx}2_+rLTSMbDen(F9MFXvliYX`^h9lv+% zEQGWwB8NM$`yhIEC5R#J?PDE_aberrD{^E>Tv)<62?y@x6PbW6)4DJs{^4kuIr^#rF7dV!!ij`xN^Pt2kwnb6!=o$$*tT?Dj~)1=Jkp>s|}d z8%Z44RAvDkKWmT2ay{bES=%BB&90xLepP+~?;E=tKyJsPH;2)Rh2LPtxY>4UoDE0< zXLE62Da&ToRSvP8xG+KB_FdbUUTFGtCJanT0RdLx#OnL`K$FAJ$!;NTAWqDcVf5q7 z0wyr+kIfu|j-A>YW;EMAS^Iy#aRXnB`V#Bct#_M8Liex>w2RWJ@}K=2R*Z{4mT!IE zefJ@M{(N}$nf%C-C5xdxOP(wl?!EV3?tPGf`^O)@BR)PJXV0C-@ngraW5+J++_4jv zuU_SbL!h{>)Tv_ifP)@(4?UFfAv{wsKc33-B%-4;B5k^KNR}+hSVRpE4jrGmj^BU( z0|^NUxODLn&ir~7dv@)?#&w%<_S`vhu~l(iYchy+v|U$`4m{gE3SSO zlzFX`EgG`L`M{p~Ip2PQo517tj@9Wyn=6Njpc95o!kSfUd2ND>b14;Cf^jd#X9a~+ zOy*4J(2Yl6Ro?EFOn5g6 zorxu1FO%0mQgKKYnMGqgu^ia?U9zYs6fRN-<;#{qZ01-H^G(%$zrPczb^gLd5RuKI zg-dYx$`!}b@T)7$a9Wu+mrUAME53q)&pnH@X;UNWHrg3Z2piVW*3H|nWZ^QLJbu!4 zg!=A<6lolqQ*^oGzO3em1@a=LUqam`b);IM`Z?b=AjJ63ggv8jghR<(xrXFi&(3{t zXy0L1a=K&YG3x@iB&ZR~IKwVA%vXt&vR#)pycb%*+%|96g8sb*NsG$cW-)goKB0k+ zAG0Qd#62i-eBtYD+qc3q&ld3In8XvkV8KGnnmI?#n5By&-(4}WG3eN#Ez+iCJoUnz zfBbPRh7S8ee0uKq-uv!D)v9lzNa2_8;DaO-mb9OnH*aFg=B=1MZ6?m1C1@vnRNh$u z(TG02Zv9$#JjY{5p4@i8CRt+%Iv+mx3uaHBi=EqdIoFkGon?2GI)x@%tYP2d3Ot+}lUZkt5Mc zXCesPwXMa0y{l3Z1n&3wKnH;XDyr-_vEZHdj1#k3TS*+bZoOJ4|8ki?qK+j?mSXax zX>Jaz@U}bNf0Gjvlf6UxHb&JFmDuIb;a||L>&F(7x)o5xiWNm%TxC4`@WWC=#7{&M z$$3(m_v#3#hN^Ie%`g+<=<8PcP6-5SW3Kd+JA>8Dh?-Sgpu1Z-Tt8KZ}f z#f|^FVZI-srk2RT+4k~#D_Qfw)?TZSR}5j<(tab6X7J?x&L5Zu)Dpt4)_u6n74McvN*ayKVX(C~yc3oQ=gHy~$7LQ37X+Y1x zPJ)HKRwaTH+v*55(90sRC?VoWTg}?lQQ_6{Mhi?}an6g!ix(}$)Tz^*)(Yv;qSIOQ zZ4(4;-=PiCS)5yTzY7;GqHmx6IDY&DH_3@ezH#GskoW04W@c4@_WC_fkn^Nd_U_$_ z!2^aF#q-@CtG}!+FIA=_s>W5qgDIWF5x?<}ROe3|KZP;F#^T_9s~j^^#DHseB*-he zSS*?<+I{B!!j5-&z{I|I;DP@+lh~eyTFd&m-X3hc!Et-X4%DHy6j!jLhmN2}``(WE z>=_64KxV3C7B$F{C6jRAcHP<-9LI0T-Lh#L`u7~<>xI5;PAp2FBwFekwX2yEH~=&) zS+WdMCQlDm^NTAxWWHR&Q`@y`WiE1x``x&417k*y!@6}F?E7SYBrD#1_Z`H>#s+Gq zU;iiO{Lv%FFnq{JoH%K6PO<$_S_p49s*gfNUNpFPkp4_eJN&MD>B1$9{bB;PZra9i zcqegzjj{31!B@2ukXNQ1XM9fF>jdzA?~cg!SXN1M^+9jvYS_v`;4$+7t)jRpZu)UE zM)&`c!8Kt{vBG*Ugk>-6^F0zqYTLDyVM6Yi`H7%6lTxY4Q zQM)=`sbDH+{60$-FU7PeGhAPz&Q?~kYgp^nhpx;qF)?V@p|#`nmfVnV?XSLCj5)LB z8kftSJu6zYXo_@py=}i%-nsL?9Qp-)dk?_XD@=Ygfcx&f4^3J$M*aeM&4Kn8f6kvd zhrxY^b zE`Jg_md;y&xs&EI2rR}W%`72Vonba^OkW14m9|4eyq7vtEJ5H_NJErOK70L;)sdLc zQZKaMP6v`KBfq#Yx6%vV^S2w;MTt_yeZ9BD3q5Q4T+<8f(mn@^`x5mRahjPN*r5wz zfBm#}9fl7Z1@ir-&EG}l%(1@h^Ybz%#d$L32>lr}V3^q+Xow1h(4tLKWAbXD&|T3^ z(w~R+9Ynt#gK+H{*(A`tKen^ClAv{F^0j3$O&e+Xt;I~FwGmmvo3$&VT&1#(i_7uF zY;TaeVLlUq_lzNG<~R?>X%+`My0lS!XhhOIi!3G_2=^#MTc82X51) z1=1QESi~Owm>}?=-b3a4-CA{~&0jEDY0Lbjlit3CsMzw;N(<*(w{L;`1@Z}4>2Ck6 z_*-w}q6kmaNx{gx_qYW(ki_cI=0@9Vj=!Z0_Jn=y4JRxDd(+_O&o zS}0k%xLJ!#@jSBeaPyBpaP9j4;mW1U%xE#+H}SHkPLs-rVVem$-s4Gjp6s}>c<$Gj zGi@FnO78cj@}^6I8P;&*qXTSkgnfD)DE@L$)M!}EdUt$W{Ow!~G;P?fVJYs9y7xC3 zl^Xu}2yEZ9)2)G4g*p!q08}>wcQ6wUE zl!e5f^yr3c*;%e4w@j{I|33^HIubj!lYUjyd=m4eg*}f5hi%iYg^{u4G{=3v3l}e8 z{ZAWFu6!xWi)$);s3pW&P9#(6z}|yc`_nr7ymcpT{Bc7a7q!~=|qG4S)jIQq*mo|7{o5GPA1nAVv`TI;eB zE}Z9?JZSd*yHd*jd-#tBwF!GB`+SSMS@ z@rg)E!xk-B7;nZ^LaNjc1#TyqoZqha9ur0irQ!l0FV~?W_f=@eS(i)Z?3vM}ZzsiA z(Q;{{_lzzcg0|W)TZ9DDcPlq{;)qNZkgeDf5!X0&SL(1ifOTHViLF->ojDqNPZSFSIOP?7^A+;>ORt_#EDPrw&LpORx5l z>l{6L44-uEVQq%yXsT)dtA5XNPh-Jo*TOKN{qCnaPbL7vKs>+C)-BsG=8N(8+vnJitCtsi-CX)C}1l z%^sj7q$JW!O!;yKzF+wRpL8bv+ImM^_lZcW;=JTqlGyU_nS&9EV)D(cd+NGgLmRz! zbc>LVK_a!gc>W6BZ`{SH9AOM{Nm$O#QWE39V#rD5t>#4+nPQ{Ss*7cg`e=N&ZQgE} z(23PT+h(!J+q($i-CmNTDNUx zA+T?_oNE`EymM!Ng{4cD`*U!GLdx$!%U39iH>*@c@_Ul|gF;b;|I8Zy+tyz!J1HTls-M7JcS#1zE$@yqR&(S^baO#I# z-5v^|r8Z{onC=McT}ASC{K#>9*0z_rc9WpU;wHD~otCX}3M9^yB@GBl=eMuaY^9_jP&j%9-0=H_{9BI>#KJtT3=R8@YfB1e4#*dj42z6Xvl95!`mo&pk z)Uj(@L%SSF``G^5w&iCG?LPuxB1L2Z<9u?w@P27(pzz+E*CBYzb)Zs>ig>wVIU|ha z_x?_uIE`N2`r_Jeik&$nabdTa%s8yrd|ny)TG#8+uM=Xk6Y-7Ui_)}Dwb8prjnFjB z<>uP6Z4U-_A8vDC8ap!0QAu?{B@#;#_!`Fjw~Nbc9N zZBs)^>2*Ib|CtOxQiABW-$+c;91TJ4XM|uxEx0TH)f!#a--{M6jGA?;A$js-%KlL0 zXJ5Q<2}1^qFdV2#uB)nPB~D^7PA+Jh2~e~p(%lTsZkVd~_kaI)10(v5G^*2(j~TBG znBOZ^U`gWsM*EytExCTn51S$P(>YzHmImz$GJ$7kK%BUcZd$V$Uk(`0BL({^iebI$ziTEj!>URVn*B{r2pEXCfDEP zoH#l*BU*N8j#Oz(1Xiv5q&+_qGmA{%NH}Z&ZX1LfmwBZmDpjp$iWveR=ZRw{jN${= z2>MB#30|p=yfYwE<`}eS)5H{VD3DP^R^$FX1KeMQtc)4!$AKxbHf-`1UU>1jK>b78 zOA_t}_8hE^?|LZD0`GYwR68wH;w990tC}JB@axaz3s+$F#JLjhGOM6+o5`ZaSt?;? z6j)|l+5FUKR1HPS6?TJ!rmfIM?;g1q&~l0hymZcTd^L5Ui{aQ;<(3xnfLjDajw~p+ zTo<-lOPpBE-*0NfRQ*2NH~)-beZJtort)%I2^Fj&$8!^MK|H~lRpnkSjaMsGFkg6p zPyDT0m@{J@RxVrZ=7cVid=FYOb&YnN=yT2qW%gCD* zky=CPa>Y@(+M7s5%;*3gGH8ETwFZ;MPUYjgqAx=*lj)^J=+`t;pR{J3d`rObT}~it&KDkaV0jq zueVy*-&ZhaGsa{@i!M!(nwW<^Ug)2<>@X16oRmV%EdiNa`w_DhHvIC4t@HQYe;=yX zjl&Ct3L+E=o}YSX{~`40F~I0co3lz3UMfx~ax&t?YTlM0aOyzbQ-b;kIPpsgNij@b93NO z=%_Y&*T^#f(u?>!KTP8Y9*`V&lfFM zY7%xBl^$eBB_fw~Y&!{rV+}^gsztk|NRuX&1o_<8BYCa8dkk=C-R<@S;lvSmyD7(x z&Xf@?+BLDPDgXXDb@DX&b{}9Q4g{*xOy8eS)5?v!g7=vPGV7HzY2O%mpULCfpU01! zz^5I0^1Z=oEa>Z^7N?NkYm7f34s7345(6fES*&~!RBKe#%ZG1#4DY_#Bt&v@nJjrNI?NDM9LULOz8Iv5xO4|{Jdo+h7%K-g=*?uRe6;o z@HP{~End1P3YT~ZY0{-Ji?>U>;VNVzi30XZ=VBQ@>xt`P_J^Fos_(ulsJsI?*C2*QHu z<`9hf#6(&$Ii7u?0G`N`8?jkpj4+5_J7njHH}$)3e!z@Lv%US(lEp{v&vrUEIwm8U zw|Nh#18A?~<8NX1)Oq;!8>0?*1aVnEzq+t$EBx?XyLUA9yYjml5pFD+y97(WTFx(a zf-l0XbYerV7)j;dunf^m6U_A?J0X;=P|~-Z7tdY9(4Jr5^oi3>Z$^;cE7Ow8+GAYY zYG?kJ@}ptfx7?#w*W$X`dCm)xx&E-45i8ID#M6=HC zAr*-Ov)WcM?gt$^H}AyozQlxk$fmsN|4YM;oK=wK7eFV z$@m&uB3BM@OzuU52xEqhGs>CTab$5YD&o?9mPa%};5Lm7n&Cojp?$wyy^4`TNO_op z%=R;8e{nUOBH+NTEGilDS=VdQrZMv5d&-?)AiPh)f5#1)`t!os3mD#OBu*VaZM`Nh&P(?HWep$o-cjacb$m*8s882X z;HCU%(6+vy;xK*?PY{@-c04Ff?}GK-`ut}HIiG7fAl$%F$pYx}OT z=$Ie%^PfF)7K8f?#f9@EUsl1vgK%K6Jpxv7adc*Kz1O-Cp3eW2W9cZ`d-1|$!=G{L z_$hgQx0OI(KCKYfwHb5iRa#Olk!0%#ikG|i_*Q&8Rxke!3uY{2DwU`!W+)Y8US4sF1CxotiCt`@ z;QQpnFh`jmpFqVS+eV#h;D+YWv$dD;2jA?_QoxIV0Zf@D9=tFEn#PqWk|#&l9eDX#X#mGi5%A zOp*1DdKLPkoYkeh7DF{c=*X$zr6FS`g20WfP#e#37=ND8Ki@1}jd?Q`$Q@XWn1j8( zXK0GkA`64f21LiP)3{Xw!-?hh{&sBLiBSX03aCnsYg1g2qY9%p0_3H?Bonw|jn_=o zm^*jgZa+cb5xqy@3`O8b;=Ep*R}7Og-z72NBIU^Lm;6W5utNJ{=kS>tw5p=X~##c$tfdg{kcUrt{Ah0P8P5 z>kc9LaB@{#ms{N>oY&MEJ2k?JmMelcYQARdx8&8I7tUS6$UdWS`q-~-PRfWh?3!2f z{aCpKtaq8K0?VM4=mU7GO+6GSl;6cdpkb!=#jfEaplk1nvwQFMeHiu$$@H;45g@Md zivZu}77Z&-Op+NIcW8*zBp+C+`1a9P`sptdasJlLn}*Zk$JIY!!OooTj zd#co2%jBGrxTnaeGi1tu_u9O}iCttK4zkhM*9(?o*}@erZLyvI9N>ORP9MPGMNH^C z`JeQyIc=5V(Trh4vVCm(E|th~A@d<^(D4BeXVBonH_H?ha^O#Vm{yhfkn)`+iPJm#E!k?F@TqwUB^4X0w>sxUqr6 z&i84bWr)d$cRMz8ywI$0UGzfUfn$opr7P(A&PQ&) zasXtt5tyzpGKwPcPLr=?%$yPLv~OT~p()M@(q=l@Z{56Q>`!Zc{I#}h9Q4NZ8%Anh ze0&&ZPM*Q#OIL7c-yvhtk_w3YCxEt^F*yQht!3Z0R$k9y`2Yw4zuUSYQl~Zh$o5+a z{YCOu7tUI2R8*weVrn^XT4Q?>`-JRl#gU}unD{)_F`2+m<$u!Bk6U+_yw9E6b{P|x zdmtN$gn<+U>13SJ1F?&jD}q;RR4|oyit$E%e(BsLe9`+$1A$%RQ)Vfs{-$c5{e9;P zpN#qlsnR^;lCEpI)>0d>b3}Cw`ZUpMNJt2ybzD1M=_4S>o#4a-fg5*dU?4E7u#$PK zc5T^>(f!9ciWs)i2a7C%L&$Eo=<$)|A6u4&v@(REJWrr{&tB%U4^k5ZZq({+LrUr8 zafy#lz*jRD;ro?ql7<7z;;=qkxIv5h$W!1cbCnVMn>pUGWhcHIG~UI5JvlDx8582V zT%{6L(Z?V&yNDGjR|KyU6PlDp^67)Yto@Esp$m`8DcV^QL6@y7h2Ij@dSZq zFTr=qDFQR1lH1_E&h5M_U63bi(9%R;Rd>+-+qrcYz8omLOR?kc02ijOyJ)$>c&$3& zz)V_z)Tf617@zY+uvVhOO%5Rmga&~6T&!Lvywne3BLLL0t)cup;Zy|gtpr+|(= z?fc{S;S;W!Q&O#N5^>@TvFXvMeFHpfD6s?ui*USq>u!*w3tO>e#r4D;0;v6F$NgAN z#M(<{ppbL8u>HX)d<0|`eF(k<^&;Vv)K_3lI+D!Lu+`h97n&8)3;jbl@InKDf0T1z zFJ)6eFSB3Gs<&IzGn`m7l&EU2F@Xn7@U7A1Ah~1iQ%52j*QIN@t7et$Q{m}Ck+OyH zT8#=82exK|Wc*z?dkJ6mB?x?msZPmw*9GJ?>t>5_UUM>Xx&}HPteRc4-7(o>(4lWz z8w}X-7?mt}PGf5oEdjrA`MlN`9^Lax?AU1SSk97Ub}_gO#N~j)x(*R?G-zibFcpQ` zfn=K`8k212=3N*&aDrR9YQH@nZ@3scOEhvTw6o$;Y8|WckDbRgk<#uTsz|c_kRc{L z-Z2oE&nGWfh?N!HNIQTkeJp3^Oc4jV$OL{W-xE$D zKSg_YZrhEqgC_7u?T5JZT3+wX`i{~=V%?)y`NF7B?PWU-?94j8e=nW8Y#=c4n@F>W z63uy;hoBB#UB^?;K7n^SHI(`g!XZ3pjFJ&MhUv1EYWA49_+jaffpto&*uLXT1Xgigp}j6nEykr)dCbsIsN744 zYgAcMgG)z~r<<9}{P3TLZUeNuB9whIe-#!^UTlSutQ8^&5ZAIFr0Zse%o)(I-CKqe zO9V*qc<1Kb7(Z|#Z=Q*jKImP7a$D8&MS30a>4M&$`<;Y@Ssb`wtGAFU&BLA&W8#Hg zJo{_>u;NE*FS@gW6v)X#A!jS8@lL|=-fCGFPv(2VPYR7)+je8zz=_^i>c=_RW-6{^ zvm`;9U9?;wyi)CD-~8{(=PzSSpK&;If)ui`J}OJWi}R|KSHj<4uTue~Dwc9?M6>WT zY-^IIX?_!dcMhAchxLkNrJ!*|GMTsj^fSiyB?#w%Fe)VT;>7l35VX=HEsB_j?dlsG z*lv%^JFM&2y=4!^4w&d@DP$a&4UybQjx6SzTFZ}PE}PIG#x0e|B*}lK3L#QAtU=5A zMi@zTJz^mcFZ80>OR#3e+F%?w0!}P)eyKKD)9_aFI(U*eu@w2ZwEZNaFm8y2zyY+& zWC)p)QL*EPoeku7-dxiPmo0<})nB#|Sk>N(1_F<_Ca^ny_jWli7uSb|dTnZhB=l2o zrRO#13*LLWnYqFDut>PGb9$l$@9WnhXX9u4hOo`6Z5p zM)L>crB4`0^hbUi#o%tIg5!a?l|6l~u~H zqZf9Py*2o)hShIY2TvBr?W>9WP??udp?Y~!3+*v} z2m+7mH_1pRbTCZu12*x4jZ=GYUU4s{9^*d20}uYsWod+Uv}~5Q*j2@vZ?~;$n$SLKfAW>!Gyt+-KLc6XC7SGg}h#A$c&b`aM_pea4g?QUWgp< zIcyOJfP3%17dou!|C-OhxTkVmU&=ZG{#h4kSejJxtQes_ZnfKY|%yMLYsq9OrP_3N7 zfmQuU#sxv(@%<(#IWVg@3lirQIWKDoWzUffEk0`|-JwJeBWJUG&Gy@PIPB*kHpMl% z&|1o-kC=sZ->i4?Nv&VV_6m|3_$u$K0*MK~tKY6}ATPA>(g%`cb%x=w){LYM1zpal zGtWL2>4_gl7FX27e4kyhBjC^(GNlLc?mq0}g{BDncLE!Oy2?C#W+Un(P zw!g*L227mS8Gn|K$!a5+U(c5+i0bdf*-;iY%-<|;b7REV!)ECAA-gpoH=Vq6WmvuN zdwey)qsEg55{sP4lM`DUA$?3byw$EQ9x@PEs9-M~@7=l=69!HZywIw;(~K3FL>yV% zLrJxAb0z{iJp4$$;yjU3)@wl!nC@#U*&##MK@j+B1A*85IjNbF_-9=Mfe8mz>}qKZ zX0jMh8Zw0eaK+B{UP$Vyh@p6Fx8M#hm3h%Hq4_wld;Ae0$Ao^9O$2s}ljW8Mr^Qyt zS;dMbx{gZqUq{i`iaI_U!y+@X`1Uk?y4e%L1RhMvZ&VSzm528p!H}-Qm?UQ=1(3?Y zIPbJ#MfXBtW=SMrr21{^SRB}EE!04qSd#{keRRx^^xLX>)UqljUTLMO_WJ=?q{tlL zdg-InqkhXeW`>Ffwh;s-dtCjv@+W@|EJBT6JA$0o0fNA}^O0OuZw%hEZ7(Jcp6bhi zRo-Rk)uo9B5=yB9h04B&auxzBMv^gNE}y?*n9yel0w*!&HOH?*o0@&v1drs(E^F}Y zW_epw6({iffYw76SL{%uGss|Y)i)NiaTE#`io7mPOdVo&?`uexi6C%o3xU0rO?$WP z!^DA8;04w;!HnyG6hV<_g_2@*#ZAwLKzvCp5#->1)%9`#z*Myk{Vfz=-af#y*n=z48y83^nb2qrI`Okhtc$r^8d+*dHGmB?)& zt>~?m9#v{bz5oCq07*naRNMDwQUitO7q!?@Z8j4}BnVu$MJ+RoC{7Fl@ST66;Ylt&^a^pqjiVyr?^F*_*8_dMd{X4Jq$;up$1kC)@ha9(KH zz#>kpiT$SHtPw^s8zhU5E3>o$e*cx4xgp&_UJ#We#CA_$>0+IX8?&WCbS)W8$@7O$l%ii({ykp3-NC+9lXvsE*Y17W_F>8( z3xPeny)-n&EVyoEu!1A2oU?Rs;ruZ?K;`zvgFy(|<70ur%HofZUvSq|*JBdHg* ze9lVzM4VVj`H>9};r8^fRcjE!5)18p zvCQ)*6Ia?SspDb$UAb@tlZXj@>YU((W-e%OaNZ|mi#qJ9^PJb1PnL)!f8l(n-KvKD z#<=c|3KeXgr}-x71SWqRymg>1%ZJ>!dK0D(o2B0KM^b~z(`(J<#WI{QeWtXiO-$(2 zDSh*S$xEL)XaHS{#NzaTezmt;H?8{K?~U#0xYo} z{=rBb+I`qNKUjjSksxu}XoA2s3Gv)T6S-@YGH2F-LhFq73pV@tLIhz*kaD7*r3 zWDz0!;>t44%xZljKx*RXtJSnRNWzC#J}}wdbouO+SigFsx57zQxkQGntoE3R1ABSn zt2c>5E+XXc$pD-qbaLhpr!*CgGm=^RktF z)%?|1GI^P8MpF`yC{FCkYBHBlBIKymrkd@AHv3u*9s9TK$Fw0cZ##=95JzT~J^KA1 zPFbH#yxU$J+Cbpu)y+_wNB@v?!WDB?Vcn_?_BzjUCzW^KG%Ec(5X^a5 zzOD7VHJZjDXWqw!fQS2i?%#d@(}tSXl%M5g$3-LHxJnzsx&}eu(p5_u>4aV_ARKr~ z|LHh)>b%8S=~BUPUO^;6z0LM~u{g3T6;s#oO6`|X;*H``%PMp;S=wgLH@o18z!nc) zr9&^l%DRZTeCF60^l3A|9S@dyp;bsMs|m#@xYA}yYfRvjHV0O7#sfbez_dZLr14_9 z7mx9<3^H>oHI-Qg;j}^H$ueY)gkuNbe#D7YqggeiO8bx}vXa`~E9QKI4XZYK3iOT1 z8sNTuQ32O^0M(jQMXr1~yensDz}{{9F=O~FzRIRwy8<9H<+%2wlsza(94PZFN)rxj zd!gl(JcyKX+JKpckb|$qrHBW6Rri#5r`z^xiRdiRk}Ybnx@G=WCB=nE%0vfl8Q50S z&{q~1?B8|}P9HtZPZ}XfUCxbtII-2TbTR2rvvnMUE^7X@f7<~}AHwXQNTEah6JC-V z;r9YjNS|ydKdPykB&@ePZFD-+Y*q~qr%CDc1V%}nZ{`vN-pCk@9>~ffsnt-sJq#`d zu{`K=)@V`{ISd5${ul^6Y!)v}@ZgrxtNDH3Xw$Eiw5N4lYuBnktA4>I@Wz2TwJ3eBjDs$lz8ouOuX1~% zRbFU6Bo@vS1SS(WWtx=yPVJH!Q3tji#B`E_K|@-u(ZF`-EW!2uqR8c{0mvl!Qw#bp`k zHoFMLy;sFVU{#GT>-`f1o;iGuJZp&wU5YiQf^uA2Z!5|dt&VxV^s^{cwSRl!#K0!?7@r9oQ;Zi1#iV{j5i z8-4PZQyAEOu+$svvgr*m2(Qqx5lD;^Ta$qxaE(?~@laa#UNCGvBpD?8w;sTZp|j;n zWl(Q4{k6g??t(66lG1a&oXv7@Q9G8^V*4VpupX0prX!17vp76t#er=OZ}B;LOq5tF z=YDIL&=GN3FE6_cAK17n+dn!np{wBWypP*!tTCS0{;-Y@Y(Ib*!_0T-8y5FNT5&}9 zeP0KjD_anyDi=4r(5n6tskz4g?j+pG3 z+}dXyn_CuW{j6-DD~IiXEd#oRG<+h9Gk*Nmr?~dpb>C{0vUsxJR-0*K(x67GI6P=@ zU}tvOE3IX?kmG~or4N~7a}j5hi1wcw8YAM!Hph>ED<+mR`*BeKBvxLnX;mZCW?K$y zWfKb-!U+jjId?TSuG$ow11pd;Q5+dS)h3mVFp}*3vG13}^k*3e%-3tC4j@%NaUhN> zsBUDHG76S@#zbI4$RSv6%rS9d%@{ZvXHT4qOatx1h5wi0fB1NOH+fti|KHw~fLT#o z>%Z;{2%?~2a6h9{QK6TE2&VNpkk^Nc@ z7#m7}pD2Bz8Lg=JK$OS%&b)W=_N(uRQIX5gV$2YpRw?6BTOViLI_T5Cz}7AbPzC~P z)qZW(>&`c9uyph6Z^4j=RH6>RrNB_&`9_+VA4LN*+=3amKF~Yq=yT)sf)qf@3%zi` zVys%YR+EkEkUO;bBOX1##9N$nV)eo$S6s}Hn?-?gijUvlg4dq1=Q6AYUA%s-6n25Z;jzfp5yiw5ireqk&wPGdx-BkK9 z<~_Z@)70E}>*BaNjLT*X@s(4o?>oPHD_q^XD{^wIT>$OM7lJtbo?UzJ+RWEI4J?ZL zvh}qkj4Kyz+UEz)7yv7k)`=UtETBZ9q<^bHLi5?t8&dr;W8Jjj049hu$3u3f!)K0DzIpQg@H?yj4j>swCDNvVK8y;`+KG|um*%%8hGy0 z_FU>PD?6_`ZOaa@uPxId3bekaHM;fc?0KOj{k6Q%uRbvk-`W`13&DATet+DTE9NgS z)h@W?0zC9sU-zxJ@hqJ?g5Uimz!;HgVx2eI z*3`A@*2c~Kt&j8cU>P6AEU})mrF1Li&YCaHQx?{!V$q;UV3W)plPolsyF+vA+l!1M8q3DI1 zy7$99lNwm(II%SFyr-N=*8o*hue519Uh{>$PBkOELBqARFmRvyd)Z_cxns@pLN8vh z1Zx(RB=4Zpq=7C846A@9)eO63LSKBv#oE-+E$?qhn$XIhsb-GQ&T?XPE$E!8N~u_! z_I|rJX+kG8Fk2#6hItin-z4md#Snj?2-Rv-4@@KhUEaUtpbk1iQaNTbN~YdJ33*QR zR+iV$`Zxml!%SROnk^&A7p{G7ZS?8iGl_xO{N7Y2y%a3#$kM>`o?M{KQP_k&9TY175 zqqC$0o;%LQWs`ADXK@Jb71Xou4QP~fVg=`qolnbze*Nh`2QaXon&s!C3hOR2(&v@2 zv3(?pxNWQMEzqr?3u4ypgp%>J`fF+6d9xPyH85)#$}q2l3*M?r3-o!gw{R6EG2Gj~ z#h~A&CBWAH!Q}@I{u=~NqNIDjvQ67DyXaZ*+U~^&Qs+ekaofSjG3)>3@L{NjuTl#`^nRy?Lmdk8?k5pue*&TGh2hSGjJMKO-xU`;VBr2*_t9Hf% zO&|C^EdjPJp1y9{A1F=S$#bKvPPFi)T;A3Y|XETGH*3+ zH-*K@K8C$ldN-hnd2hB#(>Yvip7EtU7Lj1tNEee#F0urexMzSnuIaDxeOYr-hmY7$ zjs(++yvYH^mn`pAg;jN4WZij%Ms`C~lqQ{fcaQv~*w;hpiv#lq-=$kfjr7X2l4+d) zxJZmiIxQpT5Jr*(F*aBCW=szox3 zh^xy4+-n}QDH%vi964py3ig|I7*;w=UYX}zyVI+y>|$I2Bh2$}2IKH4jpSUNx}Vl( zVGE0ti_Fiv0Y){}+=>b)Y=3f+Yi54ep{7-sC4^(#UEv zZQZn6kVyQI8EqFsSx%Cb@D;;Op1Yglh`m{<1&8$9VW$J|DmIU>fj`lHMm^0@oX;u1El>nSe!0iL>cYF~gp zEyBWU7p=qVNhXbb%jAC~nwWF==&&)L<)p5~#W`psSx*5=A}j_D+g>&sA`EOYb4Q|0 zrZKURrWL%X;=0Hfml0~I3y^oL@$?wkdAGl*|LJxAO~~(NCHjd5G{6Vu#r#F-U zTQ{A$X40o1v3l;q8DX3`DL?0J6<6k zU5WvNS`V2KX6Qp};G`B_JNZ*eX+4pqdUnL~`1GSPW`_X@X5!+k!nkp!;=?AIJ1nLa z)%ManM~R_TutJv%Xu|0h#a*5I3PT;7d8dfmhTA)d<3y{xx?weK*!E$tvZSAETvy5$ zwKejEO)tbf@m~qCZ-Ub8Ls}0FrF|o5VC$mEYbHKO5S!&=SlJ)LmJhb#_hVm_EFx-6 z>)uaAy_a#WxkQJ6xy+vzF}(O1N20gLG-l28{lYBYVcTXJ1M>>XizqQY^P3Vr&8n+G zC2~;79D%&q8zHW%^FIHo=uTYR{9<9gF$H;ONb4a_hMkf~8aO4ylQs}StgY7rnAb|T zDlIMzRLbC2YG2K*sBvRWmCLYP?2NS+VPFUII&n-@+?l`rXg=oOCv2QHtme#idqLb* z7uWU40r&paciz0i<+!n=YnJ_xiv@ zhq$4GX-&Au&hW8uegy_*=g-Ggc42{sfn6U8mqjEep4y#pz02V-Z}JLCF>q!-Seq6| z!0#P{TMv6FtoDs;LOTzdvS#9Y6zJ?@#MoxbyRR+6!g-5)HGYiZrK}JN5+HOnwTwB~ zB4$?@tQ-LY3%%Q_egw>Zhhyy}iAybpFxXNQ7ggjL8#AC}OgPbdne^8bvzo?v4cY}X ztBS*Io8kOCckgp`vRsdEhZ667mken$%!qX*Lv&FDKY7jMH4}OOIscTcU{4H~l`K7y z{0ZZxA-vIwyy8f4WL8*pAFC`YN`RSp%`8=1BJ7(DZ*}C^HMmfOi8YtW7;;`P!lP$8 z3@B;Q0#@k5l2M{yT%GrsRnYeTTV-wym&ryI9*1U%F$(_302%`+4Umtb`(u-qM^d{FPrxh zX-bpq9|Mh;7V|1h9M`b~S#z87_9r!JLe1U&#gJRkxOHP$)rC1jS`YPYRu)!@(-zZU?+ z)h~UtfAhF)a&%p@nN_ij=t$1zcWa58`rhETK)ny<(BZ=sjSAZpmW35swtdByc*!$f+FSxGqzH-U`VHsU~C4D2Vbr{VY)$47l@q{IJ9kVja z7+B@q<@;s&KDz9kFq%0F8d~H%W_Ba+Yfgc8*}7iD**@)z)6j4FgQ!-cnyg~Xu%WGo zPfAC49b}sBJ(AZ?T0LD<3wN`ODfWfv>m5eIqtGl zK!kDYRO>$ddgF=?O(obDtB1B3);1G@mk|wYb!6hoiQPyH3tclU**h=)^H0zG1tklu z+D!I}rK^m==HDuqMy429nh+t=5GqY*f;H2C8ZIbKkc37~(f&G23^PSX^GRuu?FW@d4VPHlQ z^Uke}2PXXnRZgkm{grt_3%NN5Z;WM(5V*%nY4PsdJxh?qM-h*{^U>Rq zbVSr4@KfgoP~$U;O$~y!-0HaQpNsL5TedO3lp# zEKTFdBCM=QIPf#T6d2khWEDnuR5ioGU{IrYxM1r|ki*!57h!-D6K;mj&o30p-RnXeS$nAOe>0a|aiNGN$t zq0@NjoaZrb*yAvoPuEXA&K;IPCD7DLOv}%=g3BBQKTWz!6 z|GnEs6Nj}ew6-eB=r7|M*y-KGRTGv0XfNvRfA;RjbC3KMyTAUod)k*i8Muh^MoTfR zbD=?;nU4#yapy3YnfX`)ADIwCdm7Bg)s|suCTq%Z5IPt;fGKUuEb@5{!~61OrQBix z*O>kvpd8$x2DX@Ha+d}0I z3sI#yYXUrJ_4q3(%~?ljf{5b7+dljRe;7R{wYQIIMQ6=AuAOx6Ba{4CNj`F*W|xV% ztJ6rP?D)gXnl)1xScGLY-gA?hQ_8$tMWv9?HMs0?xI2Cq>Nl=0{En^lmykru zp=}=hS5zTYU6(ZhwjTQUs>kjF$TKpB)7rPz;IGgBU5x03+-bq9DBDHIg5u+dVKKAn zdY%Zc%6pE0A$1rt-ShIS9ebQrMFVt23DV&vyNa5z)RKSGkjq12TTtkvcY{0i*duY&#>l}!V8XTEUhtRz)j{iJI~nON$hv% z?gtEXyuKZ-x$PPz$YJyDjA63cTaI_nXDowuTGjKRZIef8^;NyLqrI z=u@RprGa53HKbBg5|XT!N?j-7sy9eg^FZ(pSJJ{HH9dD*#tvJ z;bLb}Lz-f2RcOnwKLKyQEM%n0yXAcxtohCKBfyvn5kK}tA2{!Gz02z1m+?E06U!0q z&kT^gF`9GLuy(^X9v1?fG^Ll1y9qHeU$#~IzTNxq!kCw^d#APUstqYrwI59}vNM84 z?U|L{lo6)GcCr$?y6cX`^SWerDdv~hW)Zx@0g$SaAOQyEF|NJ-PhN7j}0)evOnQ5E}vdqB{vth7ySbDo74-GG4}|1b5V!L(tqZP@l8_(kEZxTJ-Y<7}zw1%>U3del$l+5i9# z(n&-?RAx>z&F*wW1UUX-yy_`?a~IJhQ%j6GK3cO0uS}U6M1t(DEh0_q)q+fofD!g( z2212*m5!ytTg_tVv^BMuTdR%Bnk*JZSX+WoW!`<2<`#P6B?ByaZL{w>)yfp(vXzkq zOy-*h_l8B4^nXyEbA^B||DopLZtl5K`qkXDdV$HpCP`qXzP6 z`SG#6yJ7cE@`}3~R_C#~r`C$}b zG$ZbG2+bwJ!0zQ7w_AsSb#ZUq^|JGs!ocaqX=T&HI89{UWJfde7*}L?Iht2$XHo2o z#y7JrV)wGNy0B+<-Os^p^2-~uJ)rNACqfu9t0 zELxcmTboreSa4wxZ33079F%vNW@ zVrG8SLt$v$lo>F>l$bKp*X#Prolh6T3N$V6GSS6tO*AbF*-x!m9XAgwK)oi;4!T}h zD2EOVO3M=jKOWI;#Nv=6lJVw8g#g<^+#?3OK`E+A<#Aq`D5r#6H=^B$$i>(*DaA*P06RiVDBb{2RfdTz#@4(U z^YPiH&;6L#1S7K;DGFwmQGK;DBG81HjQi>^ukO4lCXO5z)(nuu#gL?8+dorG%eNO9 zRLmOaKw{Lp?0gjT>zQO&$?O&ilv9GEMtB{AQTzE-W`nUy<6UF9Ie(&**7A8987yk8 zqVs3|2^&^yWULyAcahb!rkr1^ogxp}DBi5n)EVMFYc-v1KbmUJQ12zFhitSBis?Lr z#k9isu0DFvtbG&Q_|WxIZnO0r0@#xvxbbK(n^}20sx+|EvBy@7X`4up1t9cX)#CBa z%kN^vUsnY+vAu+fIMpJEnrt2wGpm+-ekrfamGEeMmLM)Gb0G+aRTHn3M$5VD*k`u; z@3!c1TX*$nQL+=XqGwUZ_z`tji8^^y3b57DF~wt==EN{BL2;Q36I)WZ_MavA$M4?M zYhrg2W!!50O6`v`hva7KvUN=Jbp>AW0Osbo+_Ga;c9E_>y7y6?7gin#foYSPmYJ`J zao+2G{Zq8;(L$QfYv-?TqE*mqPbgexm*D#QeuiqNOZF`Cm~RUX*P^!^33{Z7;Ia@SO#36&cI>jzGXZEbJ(kX2 zhWB1wl9EEr&%wTq+c6Yo_J^q>V`yz$TK5vV8-zX2JumF#QXd1m$)BNy4U1`YE{86C zI-^rheQ`)a$dg6wM{2({-PBaZd4tcqGP+^*jBzW*3`|tesUC}w$;V9IB6VE0{u8|U z{M-2UODkTi@J>h3%&vD?fWgAtPln0a`zIF(Cr-^jPp~T#glhescG!uI4F-6RWNlW1`d*ia4P>x5vS}_CsCF z$YMyQLp)bIswQMjEhelg$pP~|6r;E+-(Nn?ZY@&MeG0s11;*2%eoHW{j$g)7^{%}7 zN?iNP9;jN?+p9=&{oX_Zca7>as!}Rxq${}Wyk_cNm3E)8#bYm}NIXvnI!O0CvNhTI zWgGC$OAE2j+Mk#W6|rWVFoYCQvjiAk45p5XEsxdfSPbfVwL@T7pQhy&1KIr9e?P6( zX}IRj?r74kvAXVf>N{D9SnO9L+KtG14XaEG$uXya*#Q_`9DkY+^!1nS2)a8De18D% zzPuQvi%Nsz7lQk2>fM%NH-)!bi5=PNgw$L`Sz?n#i`A6;{S_KC^m|H#&Y3?Qh7C-f zB(B`NTXS^3tt+b6s4h!4XHAXgMs|p2we)MIg_gbFF)hGJle&21-7%6gn^IcEtvl`7 z7tYT1sXuI9w-pQjXCc1b@onl4VKA#mvxd3e6!V&3Y)7*>&#$cG%EZ87Ft+Wn%sW31 z-EQfU^fagD*FXQy^D<+W#mCYFEi&Qse*49@SUPVRER5_p zj9i&DH9JM^L}qe2nwr<#$I`J61Bb(~!t`g>oVUlJN&Cj=(z_GRJm*XktZOm2CXVV9 z&-gBZl_|8xl?lxc-00%b-9YJd5Zc^iP(S&g48`-8n7CIpchqVMk!BPz z$~f*fAJ>j_Y@*4y{)HPram^~zdtA`?0(88oJubYWf#D@&LyTa+hz{|L?$2xZ@T@gE z<}t8rWa3LlK28W3=1U!8G$#L1f4FQTR{murzAXFNw}FF;tQ`@~&qLdvwMO%pnBe_RW&i;aup{V7e114s?ue#FA8m{k0?bnyo$`9}%9Ec;5i(g(1b6a!}<)RPW2 z*ThY;T5?Miiekwc3d`v1`e&i_PxEnkmuA5_;gb1_k=UV%1T7lfdGw@XRWMEaeL@JZ zH5lWUj6e4f%Ey4vUz-k&)o#=3%~<#L|DkOCr>V;n-sv#+HEwGhAkJjKmgyaDNf>wB zprOcZZeP}*S$(v;wgoP2)5zdPY!fmEhsmgTw|H5|3y;a>CyW4l+IU2#1dWXZXb*2c zVCSruTWy)&pKsoQ(s$p-`%Bhaz}FRKQ;Z$JX!aw0<}*X0a25-1Z=p0XcNp)rlL^Aa znZ5ovM?s92Gg-TN_i3 zCc*E#OU}a;otvR?hsLOO>M8QQhXjirrPvrFm@ul-n3p2<{+Pb(gcV?C*5XS?+zVhR zrMSo>e#~;OcYm=P8&_^Z$(tqEzwdzeAsCpwku|qQvxUX7kwQ!g%&M&U%ZN&~)2gA( zPxEo@Kc7mw&e6SOZtx~4P4`}gd}*3xY# zD=ou^%k6Bn;~WE9^u~tG8=ztHhB&p>sp$-#CAv!p$n1FMv9F|gqvP`OlLD-n))9*f zJLSZ3?yjKpwit=!=<<(s=XY$`iR~M<PDO3jiW8brCwJNA{ zK^|(?%|qS=c{sP>IXM5ax|!UjcsU6E5Ua@jW4v4Zd&8+Yp_`o);0V)Sw5;eNa+vle zgcN|HRi=d5xzEA&#SF7Rp||5g=8o&s zuY;O-r{nZ9Yob;i``=k-;LNjY!SWnu+EGR+N@5`MDst)EaUI5OjW8W2^d%<+IL+yg zFM6ceVS<}tvDghsVXlN21IiEnfbs)}P=2r+ zhkrPf>`hM9DmbNjRaC8B1y!q^f@-Iof@(FYqxxy5pnAXbP(ld) z9IHqyhrW53o$ogxZ6yPikap60KwPQK4pTuJ1T7ce>rN`mCpmC-hL|MMyMo{`8K>ja5P&~fN_$5bZC{O6e zo)qAtX!_$z3mcUmBA4XE$VHUWh9pKBP)h3)Lax-sm$Pynq!a{Y1hkA&vW+C@)y3lLCB>Hsv40zV=YXHtrKAfy@y zs)N$%0IYx3?*Jj>0HyQ*Kn@bn?I8FLz;1x-0>w9=^lOUPm!R00OLBLdjKdxqAIAR! XUU6ypX_r^#00000NkvXXu0mjfCJs?8 literal 0 HcmV?d00001 diff --git a/ProtocolMasterWPF/Assets/Logo/Logo360.png b/ProtocolMasterWPF/Assets/Logo/Logo360.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6bb8d8b1e4507e4b993c1f26fe2a0f81a95a5b GIT binary patch literal 62213 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 wvfH}3*jBbZ=+miuibhHmrYHCwU-_z$=bm$Z z=bn4-P9{ou$)C_r#lO`aU!?++QbR>zQt2YvjUj74cepQCs;Up3vu(KO3M+0a$y?c+ ze;{w);*aL+n)}`K+L?DK&S~QseKuTjg)zqcUggilb@-QGbSq(7%ibw>=AT?sGi&?o zPewg6>UPBV2n0C;(IdP&x()U*#5rwTqYr%<4QoZQG#A-*zeYh065BdzV~wOo%Y@*vT|Gp)xo1sBk>$u$gNfeM!4)BGaF-QVVl2 z=TgZ{23cg9!~J%pdj7r5f%(VQv_?*JKo7C~H*_f#ydF+O5kl0zHS&!_4 zIVm#8s($~4q$*+W{dT|A)#k2oPIE#FwV}()TeEP`WlIJ805d%44fO+{$X!mNmz8j1O@Er5S&59r~tU$J{TbmUg$;<5GPI^x)m zz_i|2xOWl4a>JlKY6V$jlD+jxP2Tvm<8SK&Y|saa@5tU*(XB76_i?hv*i z8?VrR!-*^y@vklquzL;ND;&PM5&S~C@sHcm3?VXQvXhQ3*WSb1y}qNYSb8(;-l zWNy8(EwAdW=MKdDHm2P!=N6dW2dj^-LCo}F(1uE0hwM#q8;X*zNOM>aKQ|65n^vJ$ zra$y3@e{lEFby+mXJE?nQ`+MsR+nD&9TkK%%>{`5T@X^M_HlC<{ z!l;Ke>k(E2j}Ai7sba)DDSH|$`xh3OWal@mzST3ayTLYlReHJ%b9cjgbt_z4;ozOo1lURof%kh7PbXi*mQ`$aIAGk!*_oT=0*?}SXU zSL|PT$2Z=`xWjt5^eQI^RcAM1`20j@!z_5%X-BWo{+Lxe3*{{?^nI)kEW%G-oP@e7 z+mX2<1J+n&$Y*316dYX(SF0O+a(clUrC@O=M9W**{B|`QQ{+v_-Q{~BlkCDHg}3-# z^$7OLYV>N;Th$o8Aj#;nx!?Y|q1bw18`74g!b|w6Pp6>va-G4OxZ!b(Sdxr6b#t)o zVhvW+ufj8XohYzLm)@+2HH!=>NzvF`XftUk0F+4))U%k;t6 zf*j=6=cD9!37-1HQy4mFFm&Eic_+M+WRhL`Zry=|nepxSoM-jRVANgQj+DG)cttCN z{ZO)w$4e*Rm8-8J=J(OCB`MfP$a#;H-=btMncLu#^)R}m*`XzQiR8^Vz0f7a8$PLB zjC-N+j>fd#NxmsE$=?0hp1jGf$+z`Ld6&Wu9T$R^-+u)K^#z!=X)0!Kn}wpoMR@h< zAF*KPd_0or4Sj^1Ve0+TXiM=ZS-!)i&x!JNilm1;N4_1tQ?kh1{n=}I^*0*Bi;fh1 z++(DlykiS`kMhUUFE2%5V<8F;6{4V_0E>1lMC!5>bd^17sdCpxO3t+Z5N)iPi zCO4guc?RW~)5bNk4)%gIBndN7q%P*_c?+Sny@{G+$_O62p2K zGhF7(!#$aMHo2L??kv{QxdyWjJO|bwIhmUbvdAQxI`#dVmfaukap#u)%IK59XAJ8n zpM5p!c^tgn zppM))s*c_~mR{XfeKjdh{(DGj*G5P#^UjgazH{F>!X4`o=VlKKzU*0I7|R^Y#hgcP zHl-gH#=-0LozWfhLth+L$G>hCy`Hf1@~+m&Rg>=a7~2ij5t2Vma+!Q%|6=Rd|2@9p zBfgAb>~AV3-DR%hU%j4i{Hta~#)C%F&134sHz(D}Z{G-PxZdb4Yc9KywI%~S$N9o0 zeYK{`Z(^2clrTrPi*wqHHSwWu*6IwDHkaP0zur*6TqnLcrJ8OWdl34*wKuP}6{r-&g z^xs?V%l&iyaqjk8>ipdcwlm-VCG5o4C-V+ot3R;oy_Y`Pa&F6am93R`D9&l)8hz-? z7{>mufnqP*RjTM;N~Qf%sea!m)$LQI?5CBo7R&!Spw$04%=pui>r=_e++>hNCfWZ6 D{a_Ae literal 0 HcmV?d00001 diff --git a/ProtocolMasterWPF/Assets/Logo/LogoSquare480.png b/ProtocolMasterWPF/Assets/Logo/LogoSquare480.png new file mode 100644 index 0000000000000000000000000000000000000000..9b69e0964238dd0fd3cc8e444107c4ed17043d34 GIT binary patch literal 39902 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 literal 0 HcmV?d00001 diff --git a/ProtocolMasterWPF/Assets/Logo/LogoSquareIcon.ico b/ProtocolMasterWPF/Assets/Logo/LogoSquareIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..08d1d764cd86b370255b372a167b64fad7a5cfb7 GIT binary patch literal 4286 zcmciG`BM{T7zgk`vb%vKyFuF!R5aqjosa|)MNTV01VJ5-iaHboDI#JO8jJ@@f+(P1 z6}1R>_0U%B<)PM!YHk}zLh_I;oC{k*%`O=i$& zBIw6v)9`Q2S+hoC(P%VD6zb9xP`F=x(b(g!)~kM057waSSan##CfjR=YGH9$V4kUj zHEgn7Gq@H?q73~c3Ho>mQIs)n$zqf3+Mzm_lNIPElRbr_AM=(iHrcKlUI%juoj=>Q z@PsWk*qQca&$#(t82AI?7`X`V-EleScO|~0Ot%qWlAx#q@B?xM( zpHBNwBR-*(9@XooKdkv{&J)-9n_(uC?e(V{p*Un1Qlc=m*oN3_3-mK+&3_5k*?cyd z7d@&rI!TWyMUxSiV;$Ac>%}+~B$On;oFUUWn^b$lD^28^V999a|#L~Qn_1up0#9-4~FJNiUvQhKJ89F#t z+0lM`7wnbkkR3+VZhpNPii`ZbSOl(bM|rRUQWE*e1&sH`pyi!btk}QeuQhm1q7E5rG7!8IqJ20ZJB?s_)7ec>+(yI|#A4^ob}Z^# z3^7>;DV6R;ivAzM7!`fM+p zYshyQRQrW2QnV0heJPFh^B{b@WX}@pzSRM1 zfdyjfpX1z)&yK;itJ|^qrPYt>d7hB0h5I=ddcN#c_hE7y!1m@hwm|lf|BMx(k2(>& z6~cxy8_-D4MfK4dlmzBuYMBjsJNdL9iCQsDhoyU$V$ba^bsit9{psZA#Gw89E>sOv zseRnf5mL3ttZ}37tAlV?yI{&PsCLWwR>)o>Cd{*9?$<`x#JN0AcV$KjgRViOh}7_r!EVJ z?++kzts5qf0c^KkcoFg}@_kku_`DCR`YLHHdOw{43|Uc_ykHX6pKQdy{iASIJ0aM$ zYM$+V7Q;WEz}(G+(56K|bZDVVk3f9MMD%^pkFJmR!aC1FeUa)I*A%z;aP0e+VPBmF zlh>fy{)>Ld*+xt(w&JC`hp?u9HN*@$A17T?iq0N^gbEu5zBz)r!8%AT9fXWXtU6GM zlfRrs@J0x;>bYCZsJAbDjX^d14Wig*-k z&PS1d9voHaFy$DaqcxOVBQmP(@U<1G<2*;oqIGG$InM;Px4pX!a-IQ`$|hj=`xB@= zRtqsp3%yrB^lS+eOXJXVstFfA%Ct>wcD?>Lz!oSn5{*0u88P7`s{lUeGWahv*_A-H4tW#O*Q{Vcr7;B-gR{s zlp-U9Y+8TLKZVRW|8Ll0lkJXc9WeXI&mo(WJN7MGY_c7^9)vQV{9L-P^T-ZE*kY6I zPWlWnmyn-Fem?nO2wQBjz31j0C=19hh@j9jT}T=8md>9{w!3b1K`Eurs{*>m3uzBw z2wQBj-Scq|;+E6*!9oMY2s35uTZ3w{{pPRd(R+J8x<8`t`7mZbnQTA)|A25G{{h6V BWh4Lq literal 0 HcmV?d00001 diff --git a/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs b/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs new file mode 100644 index 0000000..10c90fd --- /dev/null +++ b/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs @@ -0,0 +1,23 @@ +using ProtocolMasterCore.Utility; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Windows.Data; + +namespace ProtocolMasterWPF.Helpers +{ + public class NotNullToBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + Log.Error(value); + return value != null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null; + } + } +} diff --git a/ProtocolMasterWPF/MainWindow.xaml b/ProtocolMasterWPF/MainWindow.xaml index f2f7f5c..0015520 100644 --- a/ProtocolMasterWPF/MainWindow.xaml +++ b/ProtocolMasterWPF/MainWindow.xaml @@ -5,52 +5,49 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ProtocolMasterWPF" mc:Ignorable="d" - Title="MainWindow" Height="450" Width="800" + Title="MainWindow" Height="Auto" Width="Auto" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:view="clr-namespace:ProtocolMasterWPF.View" TextElement.Foreground="{DynamicResource MaterialDesignBody}" + TextBlock.Foreground="{DynamicResource MaterialDesignBody}" TextElement.FontWeight="Regular" TextElement.FontSize="13" TextOptions.TextFormattingMode="Ideal" TextOptions.TextRenderingMode="Auto" Background="{DynamicResource MaterialDesignPaper}" FontFamily="{DynamicResource MaterialDesignFont}" - WindowStyle="None"> + BorderThickness="0"> - + - - - - - - - - - - - - - - - - - - - - - - - - - You're On Tab B - - - - - + + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/ProtocolMasterWPF.csproj b/ProtocolMasterWPF/ProtocolMasterWPF.csproj index ae620a9..6871c6d 100644 --- a/ProtocolMasterWPF/ProtocolMasterWPF.csproj +++ b/ProtocolMasterWPF/ProtocolMasterWPF.csproj @@ -1,38 +1,75 @@  - - WinExe - net5.0-windows - true - - - - AnyCPU - - - - - - - - - - - - - - - True - True - Settings.settings - - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - + + WinExe + netcoreapp3.1 + true + C:\Source\Philip-S-Martin\ProtocolMaster\ProtocolMasterWPF\Assets\Logo\LogoSquareIcon.ico + app.manifest + + + + x64 + 4 + + + + x64 + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + True + True + Settings.settings + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + diff --git a/ProtocolMasterWPF/Theme/Buttons.xaml b/ProtocolMasterWPF/Theme/Buttons.xaml new file mode 100644 index 0000000..d72ca01 --- /dev/null +++ b/ProtocolMasterWPF/Theme/Buttons.xaml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterWPF/Theme/Colors.xaml b/ProtocolMasterWPF/Theme/Colors.xaml new file mode 100644 index 0000000..07455cf --- /dev/null +++ b/ProtocolMasterWPF/Theme/Colors.xaml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ProtocolMasterWPF/View/CameraView.xaml b/ProtocolMasterWPF/View/CameraView.xaml new file mode 100644 index 0000000..f6d9c4f --- /dev/null +++ b/ProtocolMasterWPF/View/CameraView.xaml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/ProtocolMasterWPF/View/CameraView.xaml.cs b/ProtocolMasterWPF/View/CameraView.xaml.cs new file mode 100644 index 0000000..fc03f67 --- /dev/null +++ b/ProtocolMasterWPF/View/CameraView.xaml.cs @@ -0,0 +1,35 @@ +using ProtocolMasterWPF.ViewModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using Windows.Graphics.Imaging; +using Windows.Media; +using Windows.Media.Capture; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for CameraView.xaml + /// + public partial class CameraView : UserControl + { + + public CameraView() + { + InitializeComponent(); + DataContext = new CameraViewModel(); + } + + } +} diff --git a/ProtocolMasterWPF/View/DriveSelectView.xaml b/ProtocolMasterWPF/View/DriveSelectView.xaml new file mode 100644 index 0000000..a6cd52d --- /dev/null +++ b/ProtocolMasterWPF/View/DriveSelectView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/DriveSelectView.xaml.cs b/ProtocolMasterWPF/View/DriveSelectView.xaml.cs new file mode 100644 index 0000000..5a41c1e --- /dev/null +++ b/ProtocolMasterWPF/View/DriveSelectView.xaml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for DriveSelectView.xaml + /// + public partial class DriveSelectView : UserControl + { + public DriveSelectView() + { + InitializeComponent(); + } + } +} diff --git a/ProtocolMasterWPF/View/LogView.xaml b/ProtocolMasterWPF/View/LogView.xaml new file mode 100644 index 0000000..8188ccc --- /dev/null +++ b/ProtocolMasterWPF/View/LogView.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/LogView.xaml.cs b/ProtocolMasterWPF/View/LogView.xaml.cs new file mode 100644 index 0000000..e139c1d --- /dev/null +++ b/ProtocolMasterWPF/View/LogView.xaml.cs @@ -0,0 +1,28 @@ +using ProtocolMasterWPF.ViewModel; +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for LogView.xaml + /// + public partial class LogView : UserControl + { + public LogView() + { + InitializeComponent(); + DataContext = ((App)App.Current).LogVM; + } + } +} diff --git a/ProtocolMasterWPF/View/ProtocolSelectView.xaml b/ProtocolMasterWPF/View/ProtocolSelectView.xaml new file mode 100644 index 0000000..765ac49 --- /dev/null +++ b/ProtocolMasterWPF/View/ProtocolSelectView.xaml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/ProtocolSelectView.xaml.cs b/ProtocolMasterWPF/View/ProtocolSelectView.xaml.cs new file mode 100644 index 0000000..946342e --- /dev/null +++ b/ProtocolMasterWPF/View/ProtocolSelectView.xaml.cs @@ -0,0 +1,53 @@ +using ProtocolMasterWPF.ViewModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for ProtocolSelectView.xaml + /// + public partial class ProtocolSelectView : UserControl + { + ProtocolSelectViewModel viewModel; + public ProtocolSelectView() + { + viewModel = new ProtocolSelectViewModel(); + InitializeComponent(); + DataContext = viewModel; + } + private void DriveTab_Checked(object sender, RoutedEventArgs e) + { + SelectList.ItemsSource = viewModel.DriveOptions; + } + private void PublishedTab_Checked(object sender, RoutedEventArgs e) + { + SelectList.ItemsSource = viewModel.PublishedOptions; + } + private void LocalTab_Checked(object sender, RoutedEventArgs e) + { + SelectList.ItemsSource = viewModel.LocalOptions; + } + + private void SelectButton_Click(object sender, RoutedEventArgs e) + { + MaterialDesignThemes.Wpf.DialogHost.Close("SessionDialog",SelectList.SelectedItem); + } + + private void CancelButton_Click(object sender, RoutedEventArgs e) + { + MaterialDesignThemes.Wpf.DialogHost.Close("SessionDialog"); + } + } +} diff --git a/ProtocolMasterWPF/View/SessionControlBarView.xaml b/ProtocolMasterWPF/View/SessionControlBarView.xaml new file mode 100644 index 0000000..9e9c27a --- /dev/null +++ b/ProtocolMasterWPF/View/SessionControlBarView.xaml @@ -0,0 +1,59 @@ + + + + + + + + + + No Protocol + + + + + + + + + + + + + + + 1:33 + + 10:00 + + + + + + + + diff --git a/ProtocolMasterWPF/View/SessionControlBarView.xaml.cs b/ProtocolMasterWPF/View/SessionControlBarView.xaml.cs new file mode 100644 index 0000000..6f154a0 --- /dev/null +++ b/ProtocolMasterWPF/View/SessionControlBarView.xaml.cs @@ -0,0 +1,40 @@ +using MaterialDesignThemes.Wpf; +using ProtocolMasterWPF.ViewModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for SessionControlBarView.xaml + /// + public partial class SessionControlBarView : UserControl + { + private SessionControlViewModel _sessionControl; + internal SessionControlViewModel SessionControl { get => _sessionControl; set { _sessionControl = value; DataContext = _sessionControl; } } + public SessionControlBarView() + { + InitializeComponent(); + SessionControl = new SessionControlViewModel(); + } + private void PreviewButton_Click(object sender, RoutedEventArgs e) => SessionControl.Preview(); + private void StartButton_Click(object sender, RoutedEventArgs e) => SessionControl.Start(); + private void StopButton_Click(object sender, RoutedEventArgs e) => SessionControl.Stop(); + private void ResetButton_Click(object sender, RoutedEventArgs e) => SessionControl.Reset(); + private void Sample2_DialogHost_OnDialogClosing(object sender, DialogClosingEventArgs eventArgs) + => ProtocolLabel.Text = eventArgs.Parameter == null ? ProtocolLabel.Text : eventArgs.Parameter.ToString(); + private void SelectButton_Click(object sender, RoutedEventArgs e) => DialogHost.Show(new ProtocolSelectView(), Sample2_DialogHost_OnDialogClosing); + } +} diff --git a/ProtocolMasterWPF/View/SessionView.xaml b/ProtocolMasterWPF/View/SessionView.xaml new file mode 100644 index 0000000..29ed400 --- /dev/null +++ b/ProtocolMasterWPF/View/SessionView.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/SessionView.xaml.cs b/ProtocolMasterWPF/View/SessionView.xaml.cs new file mode 100644 index 0000000..f9a857c --- /dev/null +++ b/ProtocolMasterWPF/View/SessionView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for SessionView.xaml + /// + public partial class SessionView : UserControl + { + public SessionView() + { + InitializeComponent(); + } + } +} diff --git a/ProtocolMasterWPF/View/TimelineView.xaml b/ProtocolMasterWPF/View/TimelineView.xaml new file mode 100644 index 0000000..833d3fe --- /dev/null +++ b/ProtocolMasterWPF/View/TimelineView.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/ProtocolMasterWPF/View/TimelineView.xaml.cs b/ProtocolMasterWPF/View/TimelineView.xaml.cs new file mode 100644 index 0000000..0d55ee7 --- /dev/null +++ b/ProtocolMasterWPF/View/TimelineView.xaml.cs @@ -0,0 +1,317 @@ +using OxyPlot; +using OxyPlot.Annotations; +using OxyPlot.Axes; +using OxyPlot.Series; +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace ProtocolMasterWPF.View +{ + /// + /// Interaction logic for TimelineView.xaml + /// + public partial class TimelineView : UserControl + { + public TimelineView() + { + InitializeComponent(); + SetUpPlot(); + } + + DateTime start; + Task bgWorker; + Progress animationProgress; + CancellationTokenSource tokenSource; + private void ResetPlot() + { + Line.X = 0; + plot.Model.Series.Clear(); + categoryAxis.Labels.Clear(); + dateTimeAxis.AbsoluteMinimum = -0.00001; + dateTimeAxis.Minimum = dateTimeAxis.AbsoluteMinimum; + dateTimeAxis.AbsoluteMaximum = 0.9999; + + categoryAxis.AbsoluteMinimum = -0.6; + categoryAxis.AbsoluteMaximum = 0.6; + categoryAxis.MaximumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; + categoryAxis.MinimumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; + plot.Model.InvalidatePlot(true); + } + + static OxyColor[] pallete = { OxyColors.DarkRed, OxyColors.DodgerBlue, OxyColors.Green }; + + internal class CategoryNode + { + public string Name { get; private set; } + public CategoryNode Parent { get; private set; } + public List Children { get; private set; } + public IntervalBarSeries Series { get; private set; } + public CategoryNode(string name) + { + this.Name = name; + this.Children = new List(); + Series = new IntervalBarSeries() { Title = name, StrokeThickness = 1.5, StrokeColor = OxyColors.Gray, FillColor = OxyColor.FromArgb(255, 16, 16, 16), BarWidth = 1.0, ToolTip = name }; + } + public CategoryNode(string name, CategoryNode parent) : this(name) + { + this.Parent = parent; + Series.BarWidth = 0.35; + parent.Children.Add(this); + } + public void SetSeriesData(int categoryIndex, OxyColor color) + { + Series.StrokeColor = OxyColor.FromArgb(255, (byte)((color.R * 3 + 255) / 4), (byte)((color.G * 3 + 255) / 4), (byte)((color.B * 3 + 255) / 4)); + foreach (IntervalBarItem item in Series.Items) + { + item.CategoryIndex = categoryIndex; + item.Color = color; + } + } + public static void GeneratePlotData(List rootNodes, out List allSeries, out List labels, out List gridLines) + { + double linePos = 0.0; + int fullIndex = 0; + int palleteIndex = 0; + allSeries = new List(); + labels = new List(); + gridLines = new List(); + for (int i = 0; i < rootNodes.Count; i++) + { + CategoryNode root = rootNodes[i]; + root.SetSeriesData(fullIndex++, pallete[palleteIndex]); + allSeries.Add(root.Series); + labels.Add(root.Name); + gridLines.Add(linePos++); + Stack subtree = new Stack(root.Children); + while (subtree.Count != 0) + { + CategoryNode subNode = subtree.Pop(); + subNode.SetSeriesData(fullIndex++, pallete[palleteIndex]); + allSeries.Add(subNode.Series); + labels.Add(subNode.Name); + gridLines.Add(linePos++); + foreach (CategoryNode child in subNode.Children) + subtree.Push(child); + } + if (i < rootNodes.Count - 1) + { + palleteIndex = (palleteIndex + 1) % pallete.Length; + linePos++; + fullIndex++; + labels.Add(""); + } + } + } + // The biggest ugliest mess in the universe! + public static List BuildTrees(List eventList) + { + Dictionary nodeDictionary = new Dictionary(); + List rootNodes = new List(); + Queue eventQueue = new Queue(eventList); + if (eventList != null) + { + while (eventQueue.Count != 0) + { + ProtocolEvent plotEvent = eventQueue.Dequeue(); + if (plotEvent.HasCategory()) + { + CategoryNode targetNode; + if (plotEvent.HasParent()) + { + if (nodeDictionary.ContainsKey(plotEvent.ParentLabel)) + { + if (!nodeDictionary.TryGetValue(plotEvent.FullLabel(), out targetNode)) + { + targetNode = new CategoryNode(plotEvent.CategoryLabel, nodeDictionary[plotEvent.ParentLabel]); + nodeDictionary.Add(plotEvent.FullLabel(), targetNode); + } + } + else + { + eventQueue.Enqueue(plotEvent); + continue; + } + } + else + { + if (!nodeDictionary.TryGetValue(plotEvent.FullLabel(), out targetNode)) + { + targetNode = new CategoryNode(plotEvent.CategoryLabel); + nodeDictionary.Add(plotEvent.FullLabel(), targetNode); + rootNodes.Add(targetNode); + } + } + targetNode.Series.Items.Add(new IntervalBarItem + { + Start = new DateTime(Convert.ToInt64(plotEvent.Arguments["TimeStartMs"]) * 10000).ToOADate(), + End = new DateTime(Convert.ToInt64(plotEvent.Arguments["TimeEndMs"]) * 10000).ToOADate() + }); + } + } + } + return rootNodes; + } + } + + LineAnnotation Line; + CategoryAxis categoryAxis; + DateTimeAxis dateTimeAxis; + public void LoadPlotData(List eventList) + { + List allSeries; + List labels; + List gridLines; + CategoryNode.GeneratePlotData(CategoryNode.BuildTrees(eventList), out allSeries, out labels, out gridLines); + + // GENERATE LABELS, GRIDLINES, ETC. FROM TREE! + categoryAxis.Labels.Clear(); + categoryAxis.Labels.AddRange(labels); + gridLines.CopyTo(categoryAxis.ExtraGridlines, 0); + + plot.Model.Series.Clear(); + foreach (IntervalBarSeries series in allSeries) + plot.Model.Series.Add(series); + plot.ResetAllAxes(); + dateTimeAxis.Minimum = 0; + categoryAxis.AbsoluteMaximum = gridLines[gridLines.Count - 1] + 0.6; + categoryAxis.MaximumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; + categoryAxis.MinimumRange = categoryAxis.AbsoluteMaximum - categoryAxis.AbsoluteMinimum; + plot.Model.InvalidatePlot(true); + } + private void SetUpPlot() + { + var model = new PlotModel + { + IsLegendVisible = false + }; + + dateTimeAxis = new DateTimeAxis() + { + Position = AxisPosition.Bottom, + AxislineThickness = 1.5, + ExtraGridlineThickness = 1.5, + MajorGridlineThickness = 1.5, + MinorGridlineThickness = 1.5, + TicklineColor = OxyColors.Gray, + AxislineColor = OxyColors.Gray, + MinorTicklineColor = OxyColors.Gray, + TextColor = OxyColors.WhiteSmoke, + TitleColor = OxyColors.WhiteSmoke, + ExtraGridlineColor = OxyColors.Gray, + MinorGridlineColor = OxyColors.Gray, + MajorGridlineColor = OxyColors.Gray, + StartPosition = 0, + }; + model.Axes.Add(dateTimeAxis); + categoryAxis = new CategoryAxis() + { + Position = AxisPosition.Left, + AxislineThickness = 1.5, + ExtraGridlineThickness = 1.5, + MajorGridlineThickness = 1.5, + MinorGridlineThickness = 1.5, + TicklineColor = OxyColors.Transparent, + AxislineColor = OxyColors.Gray, + MinorTicklineColor = OxyColors.Transparent, + TextColor = OxyColors.WhiteSmoke, + TitleColor = OxyColors.WhiteSmoke, + ExtraGridlineColor = OxyColors.Gray, + MinorGridlineColor = OxyColors.Gray, + MajorGridlineColor = OxyColors.Gray, + GapWidth = 0.0f, + ExtraGridlines = new double[32], + }; + model.Axes.Add(categoryAxis); + + model.DefaultColors = new List + { + OxyColors.Gray, + OxyColors.Gray, + OxyColors.Gray, + OxyColors.Gray + }; + + model.TextColor = OxyColors.White; + model.PlotAreaBorderColor = OxyColors.Transparent; + model.Background = OxyColors.Transparent; + + plot.ActualController.UnbindMouseDown(OxyMouseButton.Left); + plot.ActualController.BindMouseDown(OxyMouseButton.Left, PlotCommands.PanAt); + plot.ActualController.UnbindMouseDown(OxyMouseButton.Right); + plot.ActualController.BindMouseDown(OxyMouseButton.Right, PlotCommands.ResetAt); + + Line = new LineAnnotation() + { + StrokeThickness = 1.5, + Color = OxyColors.Green, + Type = LineAnnotationType.Vertical, + LineStyle = LineStyle.Solid, + X = 0, + Y = 0 + }; + + model.Annotations.Add(Line); + dateTimeAxis.Minimum = 0; + plot.Model = model; + + ResetPlot(); + } + public void StartAnimation(object sender, EventArgs e) + { + + animationProgress = new Progress(); + tokenSource = new CancellationTokenSource(); + CancellationToken cancelToken = tokenSource.Token; + animationProgress.ProgressChanged += bgWorker_ProgressChanged; + bgWorker = new Task(() => + { + bgWorker_DoWork(animationProgress, cancelToken); + }, cancelToken); + bgWorker.Start(); + start = DateTime.Now; + } + public void EndAnimation(object sender, EventArgs e) + { + tokenSource.Cancel(); + } + + void bgWorker_ProgressChanged(object sender, int e) + { + //Log.Error(e.UserState.GetType().ToString()); + Line.X = (DateTime.Now.ToOADate() - start.ToOADate()); + plot.InvalidatePlot(); + } + + void bgWorker_DoWork(IProgress progress, CancellationToken cancelToken) + { + DateTime workerstart = DateTime.Now; + DateTime end = workerstart.AddSeconds(120); + DateTime now = workerstart; + DateTime nextFrame = workerstart.AddTicks(250000); + while (now < end && !cancelToken.IsCancellationRequested) + { + while (now < nextFrame) + { + Thread.Sleep(5); + now = DateTime.Now; + } + progress.Report(1); + nextFrame = now.AddMilliseconds(200); + } + progress.Report(0); + } + } +} diff --git a/ProtocolMasterWPF/View/TitleBarView.xaml b/ProtocolMasterWPF/View/TitleBarView.xaml index 03d2dcd..fb691b1 100644 --- a/ProtocolMasterWPF/View/TitleBarView.xaml +++ b/ProtocolMasterWPF/View/TitleBarView.xaml @@ -6,44 +6,54 @@ xmlns:local="clr-namespace:ProtocolMasterWPF.View" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="50" d:DesignWidth="800"> - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + diff --git a/ProtocolMasterWPF/View/TitleBarView.xaml.cs b/ProtocolMasterWPF/View/TitleBarView.xaml.cs index f6109c1..87d43cf 100644 --- a/ProtocolMasterWPF/View/TitleBarView.xaml.cs +++ b/ProtocolMasterWPF/View/TitleBarView.xaml.cs @@ -25,22 +25,25 @@ public TitleBarView() InitializeComponent(); } - private void AppTitleBar_MouseDown(object sender, MouseButtonEventArgs e) + private void Minimize_Click(object sender, RoutedEventArgs e) { - var window = App.Current.MainWindow; - if (e.ChangedButton == MouseButton.Left) - { - if (window.WindowState == WindowState.Maximized) - { - Point mousePos = Mouse.GetPosition(window); - double relativeX = mousePos.X / window.Width / 2; - double relativeY = mousePos.Y / window.Height / 2; - window.WindowState = WindowState.Normal; - window.Left = mousePos.X - window.Width*relativeX; - window.Top = mousePos.Y - window.Height*relativeY; - } - App.Current.MainWindow.DragMove(); - } + Window.GetWindow(this).WindowState = WindowState.Minimized; + } + private void Maximize_Click(object sender, RoutedEventArgs e) + { + Window window = Window.GetWindow(this); + if (window.WindowState == WindowState.Normal) window.WindowState = WindowState.Maximized; + else window.WindowState = WindowState.Normal; + } + private void Close_Click(object sender, RoutedEventArgs e) + { + Window.GetWindow(this).Close(); + } + + int logCounter = 0; + private void MenuItem_Click(object sender, RoutedEventArgs e) + { + ProtocolMasterCore.Utility.Log.Out($"Test: {logCounter}"); } } } diff --git a/ProtocolMasterWPF/ViewModel/CameraViewModel.cs b/ProtocolMasterWPF/ViewModel/CameraViewModel.cs new file mode 100644 index 0000000..b38dc3b --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/CameraViewModel.cs @@ -0,0 +1,101 @@ +using Microsoft.Toolkit.Wpf.UI.XamlHost; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Windows.Media.Capture; +using Windows.UI.Xaml.Controls; + +namespace ProtocolMasterWPF.ViewModel +{ + internal class CameraViewModel : ViewModelBase + { + internal CameraViewModel() + { + GetUwpCaptureElement(); + } + + private MediaCapture _mediaCapture; + + public MediaCapture MediaCapture + { + get + { + if (_mediaCapture == null) + _mediaCapture = new MediaCapture(); + return _mediaCapture; + } + set + { + _mediaCapture = value; + OnPropertyChanged(nameof(MediaCapture)); + } + } + + + public CaptureElement CapElement { get; set; } + + public WindowsXamlHost XamlHostCaptureElement { get; set; } + + /// + /// Create / Host UWP CaptureElement + /// + private void GetUwpCaptureElement() + { + XamlHostCaptureElement = new WindowsXamlHost + { + InitialTypeName = "Windows.UI.Xaml.Controls.CaptureElement" + }; + XamlHostCaptureElement.ChildChanged += XamlHost_ChildChangedAsync; + } + + private async void XamlHost_ChildChangedAsync(object sender, EventArgs e) + { + var windowsXamlHost = (WindowsXamlHost)sender; + + var captureElement = (CaptureElement)windowsXamlHost.Child; + if (captureElement != null) + { + CapElement = captureElement; + CapElement.Stretch = Windows.UI.Xaml.Media.Stretch.Uniform; + + try + { + await StartPreviewAsync(); + } + catch (Exception) + { + + } + } + } + + private async Task StartPreviewAsync() + { + try + { + await MediaCapture.InitializeAsync(new MediaCaptureInitializationSettings()); + } + catch (UnauthorizedAccessException) + { + //_logger.Info($"The app was denied access to the camera \n {ex}"); + return; + } + + try + { + CapElement.Source = MediaCapture; + await MediaCapture.StartPreviewAsync(); + } + catch (System.IO.FileLoadException ex) + { + System.Diagnostics.Debug.WriteLine(ex.ToString()); + } + + } + } +} + diff --git a/ProtocolMasterWPF/ViewModel/LogViewModel.cs b/ProtocolMasterWPF/ViewModel/LogViewModel.cs new file mode 100644 index 0000000..3e5d777 --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/LogViewModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; + +namespace ProtocolMasterWPF.ViewModel +{ + public class LogViewModel + { + public ObservableCollection LogText { get; private set; } + public LogViewModel() + { + LogText = new ObservableCollection(); + } + } +} diff --git a/ProtocolMasterWPF/ViewModel/ProtocolSelectViewModel.cs b/ProtocolMasterWPF/ViewModel/ProtocolSelectViewModel.cs new file mode 100644 index 0000000..d220c9d --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/ProtocolSelectViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Text; + +namespace ProtocolMasterWPF.ViewModel +{ + public class ProtocolSelectViewModel + { + public ObservableCollection DriveOptions { get; private set; } + public ObservableCollection LocalOptions { get; private set; } + public ObservableCollection PublishedOptions { get; private set; } + public ProtocolSelectViewModel() + { + DriveOptions = new ObservableCollection(); + for (int i = 0; i < 100; i++) DriveOptions.Add($"Drive: {i}"); + LocalOptions = new ObservableCollection(); + for (int i = 0; i < 100; i++) LocalOptions.Add($"Local: {i}"); + PublishedOptions = new ObservableCollection(); + for (int i = 0; i < 100; i++) PublishedOptions.Add($"Published: {i}"); + } + + } +} diff --git a/ProtocolMasterWPF/ViewModel/SessionControlViewModel.cs b/ProtocolMasterWPF/ViewModel/SessionControlViewModel.cs new file mode 100644 index 0000000..555f7b9 --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/SessionControlViewModel.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ProtocolMasterWPF.ViewModel +{ + internal enum SessionState + { + NotReady, + Selecting, + Ready, + Running, + Viewing, + } + internal class SessionControlViewModel : ViewModelBase + { + private Object selection; + SessionState state = SessionState.NotReady; + SessionState State { get => state; set { state = value; NotifyStateProperties(); } } + private Object Selection + { + get => selection; + set + { + selection = value; + // Check validity and set state! + if (selection != null) State = SessionState.Ready; + else State = SessionState.NotReady; + OnPropertyChanged(); + } + } + public bool CanStart { get => State == SessionState.Ready; } + public bool CanStop { get => State == SessionState.Running; } + public bool CanPreview { get => State == SessionState.Ready; } + public bool CanReset { get => State == SessionState.Viewing; } + public bool CanSelect { get => State == SessionState.Ready || State == SessionState.NotReady; } + public bool IsSelecting { get => State == SessionState.Selecting; } + + public SessionControlViewModel() + { + Selection = new object(); + } + private void NotifyStateProperties() + { + OnPropertyChanged("CanStart"); + OnPropertyChanged("CanStop"); + OnPropertyChanged("CanPreview"); + OnPropertyChanged("CanReset"); + OnPropertyChanged("CanSelect"); + OnPropertyChanged("IsSelecting"); + } + public void Start(bool overrideCheck = false) + { + if (CanStart || overrideCheck) + { + State = SessionState.Running; + } + else throw new Exception("Cannot start in state " + State.ToString()); + } + public void Stop(bool overrideCheck = false) + { + if (CanStop || overrideCheck) + { + State = SessionState.Viewing; + } + else throw new Exception("Cannot stop in state " + State.ToString()); + } + public void Reset(bool overrideCheck = false) + { + if (CanReset || overrideCheck) + { + if(Selection != null) + State = SessionState.Ready; + else + State = SessionState.NotReady; + } + else throw new Exception("Cannot reset in state " + State.ToString()); + } + public void Preview(bool overrideCheck = false) + { + if (CanPreview || overrideCheck) + { + State = SessionState.Viewing; + } + else throw new Exception("Cannot preview in state " + State.ToString()); + } + public void OpenSelection(bool overrideCheck = false) + { + if (CanSelect || overrideCheck) + { + State = SessionState.Selecting; + } + else throw new Exception("Cannot open selection in state " + State.ToString()); + } + public void MakeSelection() + { + Selection = new Object(); + Reset(true); + } + public void CancelSelection() + { + Selection = null; + } + } +} diff --git a/ProtocolMasterWPF/ViewModel/SessionViewModel.cs b/ProtocolMasterWPF/ViewModel/SessionViewModel.cs new file mode 100644 index 0000000..1d32528 --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/SessionViewModel.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ProtocolMasterWPF.ViewModel +{ + class SessionViewModel + { + } +} diff --git a/ProtocolMasterWPF/ViewModel/ViewModelBase.cs b/ProtocolMasterWPF/ViewModel/ViewModelBase.cs new file mode 100644 index 0000000..9bb6619 --- /dev/null +++ b/ProtocolMasterWPF/ViewModel/ViewModelBase.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +namespace ProtocolMasterWPF.ViewModel +{ + public class ViewModelBase : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/ProtocolMasterWPF/app.manifest b/ProtocolMasterWPF/app.manifest new file mode 100644 index 0000000..f125f8d --- /dev/null +++ b/ProtocolMasterWPF/app.manifest @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file From 63b22f91bd5f735b63aafb54ccb1c1ed6fd8d321 Mon Sep 17 00:00:00 2001 From: Philip-S-Martin Date: Mon, 1 Feb 2021 12:59:43 -0600 Subject: [PATCH 03/12] Transition Update - Began working on Session class - Improved code quality in ported views - Created viewmodels for ported views - Completed new protocol selection UI --- McIntyreAFC/Generator/Interval.cs | 4 - McIntyreAFC/Generator/Protocol.cs | 7 +- McIntyreAFC/Generator/Sound.cs | 6 +- McIntyreAFC/Generator/SoundInterval.cs | 6 +- McIntyreAFC/Generator/Stimulus.cs | 6 +- McIntyreAFC/Generator/StimulusInterval.cs | 4 - McIntyreAFC/SchedulinoDriver.cs | 16 +- McIntyreAFC/SpreadsheetAFC.cs | 10 +- ProtocolMasterCore/Prompt/DefaultPrompts.cs | 10 +- .../Prompt/IPromptUserSelect.cs | 8 +- .../Prompt/PromptTargetStore.cs | 8 +- .../Protocol/Driver/DriverManager.cs | 4 +- ProtocolMasterCore/Protocol/Driver/IDriver.cs | 4 +- .../Protocol/ExtensionManager.cs | 35 +- ProtocolMasterCore/Protocol/IExtension.cs | 8 +- ProtocolMasterCore/Protocol/IExtensionMeta.cs | 10 +- ...System.cs => InterpretAndDriveProtocol.cs} | 45 +- .../Interpreter/ExcelDataInterpreter.cs | 5 - .../Interpreter/InterpreterManager.cs | 6 +- .../NullExtensions/NullInterpreter.cs | 5 - ProtocolMasterCore/Protocol/ProtocolEvent.cs | 4 +- ProtocolMasterCore/Utility/AppEnvironment.cs | 3 - ProtocolMasterCore/Utility/Archiver.cs | 59 ++ ProtocolMasterCore/Utility/Log.cs | 60 +- ProtocolMasterCore/Utility/LogFile.cs | 55 +- ProtocolMasterWPF/App.xaml | 1 - ProtocolMasterWPF/App.xaml.cs | 20 +- ProtocolMasterWPF/Helpers/CategoryNode.cs | 130 ++++ .../Helpers/NotNullToBoolConverter.cs | 6 +- ProtocolMasterWPF/MainWindow.xaml | 2 +- ProtocolMasterWPF/MainWindow.xaml.cs | 15 +- ProtocolMasterWPF/Model/Camera.cs | 90 +++ ProtocolMasterWPF/Model/Google/GAuth.cs | 132 ++++ .../Model/Google/GAuthReceiver.cs | 668 ++++++++++++++++++ ProtocolMasterWPF/Model/Google/GDrive.cs | 92 +++ ProtocolMasterWPF/Model/Google/GFileData.cs | 26 + ProtocolMasterWPF/Model/Google/IService.cs | 10 + ProtocolMasterWPF/Model/IStreamStarter.cs | 12 + ProtocolMasterWPF/Model/NoFileSelection.cs | 19 + ProtocolMasterWPF/Model/Session.cs | 49 ++ ProtocolMasterWPF/Model/SessionFactory.cs | 11 + .../Properties/Settings.Designer.cs | 30 +- .../Properties/Settings.settings | 8 +- ProtocolMasterWPF/ProtocolMasterWPF.csproj | 2 + ProtocolMasterWPF/Theme/Buttons.xaml | 5 + ProtocolMasterWPF/Theme/Colors.xaml | 11 + ProtocolMasterWPF/View/CameraView.xaml | 12 +- ProtocolMasterWPF/View/CameraView.xaml.cs | 22 +- ProtocolMasterWPF/View/DriveSelectView.xaml | 16 +- .../View/DriveSelectView.xaml.cs | 20 +- ProtocolMasterWPF/View/ISelectView.cs | 12 + ProtocolMasterWPF/View/LogView.xaml.cs | 14 +- .../View/ProtocolSelectView.xaml | 47 +- .../View/ProtocolSelectView.xaml.cs | 26 +- .../View/SessionControlBarView.xaml | 11 +- .../View/SessionControlBarView.xaml.cs | 21 +- ProtocolMasterWPF/View/SessionView.xaml | 22 +- ProtocolMasterWPF/View/SessionView.xaml.cs | 15 +- ProtocolMasterWPF/View/TimelineView.xaml | 7 +- ProtocolMasterWPF/View/TimelineView.xaml.cs | 308 +++----- ProtocolMasterWPF/View/TitleBarView.xaml | 29 +- ProtocolMasterWPF/View/TitleBarView.xaml.cs | 30 +- .../ViewModel/CameraViewModel.cs | 68 +- .../ViewModel/DriveSelectViewModel.cs | 18 + ProtocolMasterWPF/ViewModel/LogViewModel.cs | 5 +- .../ViewModel/ProtocolSelectViewModel.cs | 24 - .../ViewModel/SessionControlViewModel.cs | 55 +- .../ViewModel/SessionViewModel.cs | 6 +- .../ViewModel/TitleBarViewModel.cs | 12 + ProtocolMasterWPF/ViewModel/ViewModelBase.cs | 5 +- 70 files changed, 1732 insertions(+), 800 deletions(-) rename ProtocolMasterCore/Protocol/{ExtensionSystem.cs => InterpretAndDriveProtocol.cs} (76%) create mode 100644 ProtocolMasterCore/Utility/Archiver.cs create mode 100644 ProtocolMasterWPF/Helpers/CategoryNode.cs create mode 100644 ProtocolMasterWPF/Model/Camera.cs create mode 100644 ProtocolMasterWPF/Model/Google/GAuth.cs create mode 100644 ProtocolMasterWPF/Model/Google/GAuthReceiver.cs create mode 100644 ProtocolMasterWPF/Model/Google/GDrive.cs create mode 100644 ProtocolMasterWPF/Model/Google/GFileData.cs create mode 100644 ProtocolMasterWPF/Model/Google/IService.cs create mode 100644 ProtocolMasterWPF/Model/IStreamStarter.cs create mode 100644 ProtocolMasterWPF/Model/NoFileSelection.cs create mode 100644 ProtocolMasterWPF/Model/Session.cs create mode 100644 ProtocolMasterWPF/Model/SessionFactory.cs create mode 100644 ProtocolMasterWPF/View/ISelectView.cs create mode 100644 ProtocolMasterWPF/ViewModel/DriveSelectViewModel.cs delete mode 100644 ProtocolMasterWPF/ViewModel/ProtocolSelectViewModel.cs create mode 100644 ProtocolMasterWPF/ViewModel/TitleBarViewModel.cs diff --git a/McIntyreAFC/Generator/Interval.cs b/McIntyreAFC/Generator/Interval.cs index c1aa28f..13417ef 100644 --- a/McIntyreAFC/Generator/Interval.cs +++ b/McIntyreAFC/Generator/Interval.cs @@ -1,9 +1,5 @@ using ProtocolMasterCore.Protocol; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Schedulino.Generator { diff --git a/McIntyreAFC/Generator/Protocol.cs b/McIntyreAFC/Generator/Protocol.cs index 232a408..4eee910 100644 --- a/McIntyreAFC/Generator/Protocol.cs +++ b/McIntyreAFC/Generator/Protocol.cs @@ -1,9 +1,6 @@ using ProtocolMasterCore.Protocol; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Schedulino.Generator { @@ -147,7 +144,7 @@ private void GenerateSoundsBlock() prevSoundInterval = si; } exp_end = potential_end; - } + } } private void GeneratePreSounds(ref UInt32 timeMs, ref SoundInterval prevSoundInterval) @@ -335,7 +332,7 @@ private void CenteredDelivery(Stimulus stim, UInt32 begin, UInt32 end) totalTime += duration[i] + between[i]; } uint timeMs; - if (end-begin > totalTime) + if (end - begin > totalTime) timeMs = begin + ((end - begin) - totalTime) / 2; else timeMs = begin - (totalTime - (end - begin)) / 2; for (int i = 0; i < stim.stims_per_sound; i++) diff --git a/McIntyreAFC/Generator/Sound.cs b/McIntyreAFC/Generator/Sound.cs index b9595b8..297d89c 100644 --- a/McIntyreAFC/Generator/Sound.cs +++ b/McIntyreAFC/Generator/Sound.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Schedulino.Generator { diff --git a/McIntyreAFC/Generator/SoundInterval.cs b/McIntyreAFC/Generator/SoundInterval.cs index f0694f8..6cd5181 100644 --- a/McIntyreAFC/Generator/SoundInterval.cs +++ b/McIntyreAFC/Generator/SoundInterval.cs @@ -1,9 +1,5 @@ using ProtocolMasterCore.Protocol; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Schedulino.Generator { @@ -12,7 +8,7 @@ class SoundInterval : Interval public Sound sound; public SoundInterval previous; public SoundInterval next; - public SoundInterval(Sound sound, SoundInterval previous, uint begin, uint end):base(begin, end) + public SoundInterval(Sound sound, SoundInterval previous, uint begin, uint end) : base(begin, end) { this.sound = sound; this.previous = previous; diff --git a/McIntyreAFC/Generator/Stimulus.cs b/McIntyreAFC/Generator/Stimulus.cs index 2d06013..24b2771 100644 --- a/McIntyreAFC/Generator/Stimulus.cs +++ b/McIntyreAFC/Generator/Stimulus.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Schedulino.Generator { diff --git a/McIntyreAFC/Generator/StimulusInterval.cs b/McIntyreAFC/Generator/StimulusInterval.cs index 5d6862d..4ffdac0 100644 --- a/McIntyreAFC/Generator/StimulusInterval.cs +++ b/McIntyreAFC/Generator/StimulusInterval.cs @@ -1,9 +1,5 @@ using ProtocolMasterCore.Protocol; -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Schedulino.Generator { diff --git a/McIntyreAFC/SchedulinoDriver.cs b/McIntyreAFC/SchedulinoDriver.cs index abc3fce..fd24601 100644 --- a/McIntyreAFC/SchedulinoDriver.cs +++ b/McIntyreAFC/SchedulinoDriver.cs @@ -1,16 +1,10 @@ -using System.ComponentModel.Composition; -using System.IO.Ports; -using System.Collections.Generic; -using System.Threading; -using System.Net.Http.Headers; -using System.Collections.Concurrent; -using System; -using System.Xml; -using System.Diagnostics; -using ProtocolMasterCore.Protocol.Driver; -using ProtocolMasterCore.Prompt; +using ProtocolMasterCore.Prompt; using ProtocolMasterCore.Protocol; +using ProtocolMasterCore.Protocol.Driver; using ProtocolMasterCore.Utility; +using System; +using System.Collections.Generic; +using System.IO.Ports; namespace McIntyreAFC { diff --git a/McIntyreAFC/SpreadsheetAFC.cs b/McIntyreAFC/SpreadsheetAFC.cs index 5a82d42..ef8db45 100644 --- a/McIntyreAFC/SpreadsheetAFC.cs +++ b/McIntyreAFC/SpreadsheetAFC.cs @@ -1,10 +1,10 @@ -using System; +using ProtocolMasterCore.Prompt; +using ProtocolMasterCore.Protocol; +using ProtocolMasterCore.Protocol.Interpreter; +using Schedulino.Generator; +using System; using System.Collections.Generic; using System.Linq; -using Schedulino.Generator; -using ProtocolMasterCore.Protocol.Interpreter; -using ProtocolMasterCore.Prompt; -using ProtocolMasterCore.Protocol; namespace McIntyreAFC { diff --git a/ProtocolMasterCore/Prompt/DefaultPrompts.cs b/ProtocolMasterCore/Prompt/DefaultPrompts.cs index a53d6af..9c572fb 100644 --- a/ProtocolMasterCore/Prompt/DefaultPrompts.cs +++ b/ProtocolMasterCore/Prompt/DefaultPrompts.cs @@ -1,16 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ProtocolMasterCore.Prompt +namespace ProtocolMasterCore.Prompt { public static class DefaultPrompts { public static string UserSelect(string[] options) { - if(options != null && options.Length > 0 && options[0] != null) + if (options != null && options.Length > 0 && options[0] != null) return options[0]; return "null"; } diff --git a/ProtocolMasterCore/Prompt/IPromptUserSelect.cs b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs index e0f0f65..2dd4334 100644 --- a/ProtocolMasterCore/Prompt/IPromptUserSelect.cs +++ b/ProtocolMasterCore/Prompt/IPromptUserSelect.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ProtocolMasterCore.Prompt +namespace ProtocolMasterCore.Prompt { public delegate string UserSelectHandler(string[] keys); public interface IPromptUserSelect diff --git a/ProtocolMasterCore/Prompt/PromptTargetStore.cs b/ProtocolMasterCore/Prompt/PromptTargetStore.cs index cc11f9e..b3d47c8 100644 --- a/ProtocolMasterCore/Prompt/PromptTargetStore.cs +++ b/ProtocolMasterCore/Prompt/PromptTargetStore.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ProtocolMasterCore.Prompt +namespace ProtocolMasterCore.Prompt { public class PromptTargetStore { diff --git a/ProtocolMasterCore/Protocol/Driver/DriverManager.cs b/ProtocolMasterCore/Protocol/Driver/DriverManager.cs index 55460a7..b865e25 100644 --- a/ProtocolMasterCore/Protocol/Driver/DriverManager.cs +++ b/ProtocolMasterCore/Protocol/Driver/DriverManager.cs @@ -3,12 +3,12 @@ namespace ProtocolMasterCore.Protocol.Driver { - internal class DriverManager : ExtensionManager + public class DriverManager : ExtensionManager { IDriver driver; public event EventHandler OnProtocolStart; public event EventHandler OnProtocolEnd; - public bool Run(List data) + internal bool Run(List data) { bool didStart = false; driver = CreateSelectedExtension(); diff --git a/ProtocolMasterCore/Protocol/Driver/IDriver.cs b/ProtocolMasterCore/Protocol/Driver/IDriver.cs index 15236d5..a3b01ea 100644 --- a/ProtocolMasterCore/Protocol/Driver/IDriver.cs +++ b/ProtocolMasterCore/Protocol/Driver/IDriver.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; +using System.Collections.Generic; namespace ProtocolMasterCore.Protocol.Driver { diff --git a/ProtocolMasterCore/Protocol/ExtensionManager.cs b/ProtocolMasterCore/Protocol/ExtensionManager.cs index bf41d15..aa6599b 100644 --- a/ProtocolMasterCore/Protocol/ExtensionManager.cs +++ b/ProtocolMasterCore/Protocol/ExtensionManager.cs @@ -1,27 +1,24 @@ -using System; +using ProtocolMasterCore.Prompt; +using ProtocolMasterCore.Utility; +using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.Threading; -using System.Threading.Tasks; -using ProtocolMasterCore.Prompt; namespace ProtocolMasterCore.Protocol { - abstract class ExtensionManager where T : IExtensionMeta where E : IExtension + public delegate void OptionsLoadedCallback(List options); + public abstract class ExtensionManager where T : IExtensionMeta where E : IExtension { [ImportMany] - IEnumerable> AvailableExtensions { get; set; } + IEnumerable> Extensions { get; set; } ExportFactory extensionFactory; ExportLifetimeContext extensionContext; E extension; - public bool IsRunning { get => !isDisposed; } + internal bool IsRunning { get => !isDisposed; } bool isDisposed = true; - - public event EventHandler OnOptionsLoaded; - - private PromptTargetStore promptTargets = new PromptTargetStore(); - public PromptTargetStore PromptTargets { get => promptTargets; set => promptTargets = value; } + public OptionsLoadedCallback OnOptionsLoaded { get; set; } + public PromptTargetStore PromptTargets { get; set; } public void LoadOptions(CompositionContainer container) { @@ -31,25 +28,27 @@ public void LoadOptions(CompositionContainer container) } catch (CompositionException compositionException) { - //Debug.Log.Error(compositionException.ToString()); + Log.Error(compositionException.ToString()); } - foreach (ExportFactory i in AvailableExtensions) + var extensionMeta = new List(); + foreach (ExportFactory i in Extensions) { if (i.Metadata.Name == "None" && i.Metadata.Version == "") { Selected = i.Metadata; } - //Debug.Log.Error(typeof(E).ToString() + " found: '" + i.Metadata.Name + "' version: '" + i.Metadata.Version + "'"); + extensionMeta.Add(i.Metadata); + Log.Error($"{typeof(E)} found: '{i.Metadata.Name}' version: '{i.Metadata.Version}'"); } - OnOptionsLoaded?.Invoke(this, new EventArgs()); + OnOptionsLoaded?.Invoke(extensionMeta); } public IExtensionMeta Selected { get { return extensionFactory.Metadata; } set { - foreach (ExportFactory i in AvailableExtensions) + foreach (ExportFactory i in Extensions) { if (i.Metadata.Name == value.Name && i.Metadata.Version == value.Version) { @@ -64,7 +63,7 @@ public IEnumerable Options get { List optionsList = new List(); - foreach (ExportFactory i in AvailableExtensions) + foreach (ExportFactory i in Extensions) { optionsList.Add(i.Metadata); } diff --git a/ProtocolMasterCore/Protocol/IExtension.cs b/ProtocolMasterCore/Protocol/IExtension.cs index 499f75b..6e74348 100644 --- a/ProtocolMasterCore/Protocol/IExtension.cs +++ b/ProtocolMasterCore/Protocol/IExtension.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ProtocolMasterCore.Protocol +namespace ProtocolMasterCore.Protocol { public interface IExtension { diff --git a/ProtocolMasterCore/Protocol/IExtensionMeta.cs b/ProtocolMasterCore/Protocol/IExtensionMeta.cs index 5864421..5789a87 100644 --- a/ProtocolMasterCore/Protocol/IExtensionMeta.cs +++ b/ProtocolMasterCore/Protocol/IExtensionMeta.cs @@ -1,12 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ProtocolMasterCore.Protocol +namespace ProtocolMasterCore.Protocol { - interface IExtensionMeta + public interface IExtensionMeta { string Name { get; } string Version { get; } diff --git a/ProtocolMasterCore/Protocol/ExtensionSystem.cs b/ProtocolMasterCore/Protocol/InterpretAndDriveProtocol.cs similarity index 76% rename from ProtocolMasterCore/Protocol/ExtensionSystem.cs rename to ProtocolMasterCore/Protocol/InterpretAndDriveProtocol.cs index 5ff6548..3347178 100644 --- a/ProtocolMasterCore/Protocol/ExtensionSystem.cs +++ b/ProtocolMasterCore/Protocol/InterpretAndDriveProtocol.cs @@ -1,17 +1,15 @@ -using System; +using ProtocolMasterCore.Protocol.Driver; +using ProtocolMasterCore.Protocol.Interpreter; +using System; using System.Collections.Generic; -using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.IO; using System.Threading; using System.Threading.Tasks; -using ProtocolMasterCore.Protocol.Interpreter; -using ProtocolMasterCore.Protocol.Driver; -using ProtocolMasterCore.Prompt; namespace ProtocolMasterCore.Protocol { - internal class ExtensionSystem + public class InterpretAndDriveProtocol { // Import All Extensions so that they can be Composed (ComposeParts()) public DriverManager DriverManager { get; private set; } @@ -28,7 +26,7 @@ internal class ExtensionSystem private CancellationToken cancelToken; private CancellationTokenSource tokenSource; - public ExtensionSystem(string directory) + public InterpretAndDriveProtocol(string directory) { isRunning = false; isReady = false; @@ -41,8 +39,7 @@ public ExtensionSystem(string directory) catalog.Catalogs.Add(new DirectoryCatalog(directory)); _container = new CompositionContainer(catalog); } - - ~ExtensionSystem() + ~InterpretAndDriveProtocol() { Terminate(); } @@ -76,16 +73,6 @@ public void Interpret(string selectionID, string argument, FileStream fs) })); return InterpreterManager.GenerateData(selectionID, argument, fs); }, TaskCreationOptions.LongRunning); - - Task UITask = generator.ContinueWith((data) => - { - List result = generator.Result; - Data = result; - if (result != null) - { - InterpreterManager.OnEventsLoaded(result); - } - }); } public void Run() { @@ -103,21 +90,17 @@ public void Run() isRunning = true; Progress driverProgress = new Progress(); - Task UITask = generator.ContinueWith((data) => + runTask = Task.Run(new Action(() => { - runTask = Task.Run(new Action(() => + cancelToken.Register(new Action(() => { - cancelToken.Register(new Action(() => + if (DriverManager.IsRunning) { - if (DriverManager.IsRunning) - { - DriverManager.CancelRunningExtension(); - } - })); - DriverManager.Run(Data); - }), tokenSource.Token); - - }, TaskScheduler.FromCurrentSynchronizationContext()); + DriverManager.CancelRunningExtension(); + } + })); + DriverManager.Run(Data); + }), tokenSource.Token); } } diff --git a/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs b/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs index a21858b..3d9c738 100644 --- a/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs +++ b/ProtocolMasterCore/Protocol/Interpreter/ExcelDataInterpreter.cs @@ -1,9 +1,4 @@ using ExcelDataReader; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ProtocolMasterCore.Protocol.Interpreter { diff --git a/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs index 48a386a..3a3ea2f 100644 --- a/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs +++ b/ProtocolMasterCore/Protocol/Interpreter/InterpreterManager.cs @@ -5,11 +5,11 @@ namespace ProtocolMasterCore.Protocol.Interpreter { public delegate void ProtocolEventsLoader(List events); - internal class InterpreterManager : ExtensionManager - { + public class InterpreterManager : ExtensionManager + { IInterpreter interpreter; public ProtocolEventsLoader OnEventsLoaded; - public List GenerateData(string selectionID, string argument, Stream stream) + internal List GenerateData(string selectionID, string argument, Stream stream) { interpreter = CreateSelectedExtension(); diff --git a/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs index 7e7a787..0ea99ec 100644 --- a/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs +++ b/ProtocolMasterCore/Protocol/NullExtensions/NullInterpreter.cs @@ -1,10 +1,5 @@ using ProtocolMasterCore.Protocol.Interpreter; -using System; using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ProtocolMasterCore.Protocol.NullExtensions { diff --git a/ProtocolMasterCore/Protocol/ProtocolEvent.cs b/ProtocolMasterCore/Protocol/ProtocolEvent.cs index 794ace4..f9010db 100644 --- a/ProtocolMasterCore/Protocol/ProtocolEvent.cs +++ b/ProtocolMasterCore/Protocol/ProtocolEvent.cs @@ -7,14 +7,14 @@ public class ProtocolEvent public string Handler { get; private set; } public string CategoryLabel { get; private set; } public string ParentLabel { get; private set; } - + public Dictionary Arguments { get; private set; } public ProtocolEvent(string handler, params KeyValuePair[] args) { this.Handler = handler; Arguments = new Dictionary(); - foreach(KeyValuePair arg in args) + foreach (KeyValuePair arg in args) { Arguments.Add(arg.Key, arg.Value); } diff --git a/ProtocolMasterCore/Utility/AppEnvironment.cs b/ProtocolMasterCore/Utility/AppEnvironment.cs index d9936b2..22167d5 100644 --- a/ProtocolMasterCore/Utility/AppEnvironment.cs +++ b/ProtocolMasterCore/Utility/AppEnvironment.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ProtocolMasterCore.Utility { diff --git a/ProtocolMasterCore/Utility/Archiver.cs b/ProtocolMasterCore/Utility/Archiver.cs new file mode 100644 index 0000000..0f4e329 --- /dev/null +++ b/ProtocolMasterCore/Utility/Archiver.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; + +namespace ProtocolMasterCore.Utility +{ + public static class Archiver + { + public static void ArchiveOldestInDirectory(string path, string archivePath, string extension, int unarchivedMax, int unarchivedMin) + { + // Get all files of type + IEnumerable zipTargets = + new DirectoryInfo(path) + .GetFileSystemInfos() + .Where(fi => fi.Name.Contains(extension)); + // Exit if bounds not met + if (zipTargets.Count() < unarchivedMax) + return; + // Refine target list to oldest + zipTargets = + zipTargets.OrderBy(fi => fi.CreationTime) + .Take(zipTargets.Count() - unarchivedMin); + // final archive name (I use date / time) + string archiveName = $"{DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss")}[{zipTargets.Count()}].zip"; + // zip loop + using (MemoryStream zipMS = new MemoryStream()) + { + using (ZipArchive zipArchive = new ZipArchive(zipMS, ZipArchiveMode.Create, true)) + { + // loop through files to add + foreach (FileInfo zipTarget in zipTargets) + { + // read the file bytes + byte[] fileToZipBytes = File.ReadAllBytes(zipTarget.FullName); + // create the entry - this is the zipped filename + // change slashes - now it's VALID + ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(zipTarget.Name.Replace(path, "").Replace('\\', '/')); + // add the file contents + using Stream zipEntryStream = zipFileEntry.Open(); + using BinaryWriter zipFileBinary = new BinaryWriter(zipEntryStream); + zipFileBinary.Write(fileToZipBytes); + } + } + using (FileStream finalZipFileStream = new FileStream(Path.Combine(archivePath, archiveName), FileMode.Create)) + { + zipMS.Seek(0, SeekOrigin.Begin); + zipMS.CopyTo(finalZipFileStream); + } + foreach (FileInfo target in zipTargets) + { + File.Delete(target.FullName); + } + } + } + } +} diff --git a/ProtocolMasterCore/Utility/Log.cs b/ProtocolMasterCore/Utility/Log.cs index 1cdac98..497ba4b 100644 --- a/ProtocolMasterCore/Utility/Log.cs +++ b/ProtocolMasterCore/Utility/Log.cs @@ -22,7 +22,7 @@ static Log() private readonly string logdata; private readonly string archive; - private readonly int maxUnarchived = 48; + private readonly int maxUnarchived = 20; private readonly int minUnarchived = 16; private readonly LogFile lfOut; @@ -33,9 +33,9 @@ static Log() private Log() { if (AppEnvironment.TryAddLocationAppData("Log", "Log", out logdata)) - {} + { } if (AppEnvironment.TryAddLocationAppData("LogArchive", "Log/Archive", out archive)) - {} + { } string timePrefix = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss"); lfOut = new LogFile(Path.Combine(logdata, $"{timePrefix}_Out.log")); @@ -52,7 +52,7 @@ public static Log Instance } } - public static void Error(object message) => Instance.I_Error(message == null? "NULL" : message.ToString()); + public static void Error(object message) => Instance.I_Error(message == null ? "NULL" : message.ToString()); public void I_Error(string message) { string toWrite = $"E:{DateTime.Now}:\t{message}"; @@ -90,57 +90,7 @@ public void OpenFolder() }); } - private void ArchiveOldest() - { - string[] files = Directory.GetFiles(logdata); - List logs = new List(); - foreach (string file in files) - { - if (file.Contains(".log")) logs.Add(file); - } - - if (logs.Count < maxUnarchived) return; - - logs = logs.OrderBy(d => d).Take(logs.Count - minUnarchived).ToList(); - - //final archive name (I use date / time) - string zipFileName = $"{DateTime.Now.ToString("yyyy-MM-dd_hh-mm-ss")}[{logs.Count}].zip"; + private void ArchiveOldest() => Archiver.ArchiveOldestInDirectory(logdata, archive, ".log", maxUnarchived, minUnarchived); - using (MemoryStream zipMS = new MemoryStream()) - { - using (ZipArchive zipArchive = new ZipArchive(zipMS, ZipArchiveMode.Create, true)) - { - //loop through files to add - foreach (string zipTarget in logs) - { - //exclude some files? -I don't want to ZIP other .zips in the folder. - if (new FileInfo(zipTarget).Extension == ".zip") continue; - - //read the file bytes - byte[] fileToZipBytes = System.IO.File.ReadAllBytes(zipTarget); - - //create the entry - this is the zipped filename - //change slashes - now it's VALID - ZipArchiveEntry zipFileEntry = zipArchive.CreateEntry(zipTarget.Replace(logdata, "").Replace('\\', '/')); - - //add the file contents - using (Stream zipEntryStream = zipFileEntry.Open()) - using (BinaryWriter zipFileBinary = new BinaryWriter(zipEntryStream)) - { - zipFileBinary.Write(fileToZipBytes); - } - } - } - using (FileStream finalZipFileStream = new FileStream(Path.Combine(archive, zipFileName), FileMode.Create)) - { - zipMS.Seek(0, SeekOrigin.Begin); - zipMS.CopyTo(finalZipFileStream); - } - foreach (string log in logs) - { - File.Delete(log); - } - } - } } } diff --git a/ProtocolMasterCore/Utility/LogFile.cs b/ProtocolMasterCore/Utility/LogFile.cs index d6cbac1..91622e3 100644 --- a/ProtocolMasterCore/Utility/LogFile.cs +++ b/ProtocolMasterCore/Utility/LogFile.cs @@ -1,14 +1,16 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace ProtocolMasterCore.Utility { class LogFile { - private List buffer; - private long writeAfter; + ConcurrentQueue inputbuffer; private string filePath; private string tempPath; bool tempFail; @@ -16,55 +18,58 @@ class LogFile public LogFile(string filePath) { this.filePath = filePath; - buffer = new List(); + inputbuffer = new ConcurrentQueue(); + Task fileWriter = Task.Factory.StartNew(() => + { + while (true) + { + if (tempFail || !inputbuffer.IsEmpty) WriteBuffer(); + else Thread.Sleep(1000); + } + }, TaskCreationOptions.LongRunning); } - public void Write(string message, bool deepWrite = false) + public void Write(string message) { - StringBuilder builder = new StringBuilder(DateTime.Now.ToString()); - builder.Append("\t"); - builder.Append(message); - string output = builder.ToString(); - - buffer.Add(output); - - if (deepWrite || DateTime.Now.Ticks > writeAfter) - { - WriteBuffer(); - } + inputbuffer.Enqueue(message); } public void WriteBuffer() { try { - File.AppendAllLines(filePath, buffer); + StreamWriter writer = File.AppendText(filePath); if (tempFail) { + using (Stream input = File.OpenRead(tempPath)) + { + input.CopyTo(writer.BaseStream); // Using .NET 4 + } if (File.Exists(tempPath)) File.Delete(tempPath); tempFail = false; } + while (inputbuffer.TryDequeue(out string append)) + writer.WriteLine(append); + writer.Close(); } catch (IOException) { tempFail = true; - tempPath = filePath + "[temp]"; - Write("Failed to open log at " + filePath + " attempting " + tempPath + " instead."); + tempPath = $"[temp]{filePath}"; + Log.Out($"Failed to open log at {filePath} attempting {tempPath} instead."); try { - File.AppendAllLines(tempPath, buffer); + StreamWriter writer = File.AppendText(tempPath); + while (inputbuffer.TryDequeue(out string append)) + writer.WriteLine(append); + writer.Close(); } catch (IOException) { - Write("Failed to open temporary log " + tempPath + " will wait to write buffer"); + Log.Out($"Failed to open temporary log {tempPath} will wait to write buffer"); } } - - - if (!tempFail) buffer = new List(); - - writeAfter = DateTime.Now.Ticks + (5 * 10000000); } } } diff --git a/ProtocolMasterWPF/App.xaml b/ProtocolMasterWPF/App.xaml index 29b850a..94d4be9 100644 --- a/ProtocolMasterWPF/App.xaml +++ b/ProtocolMasterWPF/App.xaml @@ -12,7 +12,6 @@ - diff --git a/ProtocolMasterWPF/App.xaml.cs b/ProtocolMasterWPF/App.xaml.cs index 4e11f71..8049158 100644 --- a/ProtocolMasterWPF/App.xaml.cs +++ b/ProtocolMasterWPF/App.xaml.cs @@ -1,10 +1,7 @@ using ProtocolMasterCore.Utility; +using ProtocolMasterWPF.Model.Google; using ProtocolMasterWPF.ViewModel; using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Linq; using System.Threading.Tasks; using System.Windows; @@ -19,6 +16,7 @@ public partial class App : Application private void Application_Startup(object sender, StartupEventArgs e) { InitializeLog(); + GAuth.Instance.PostAuthentication += AuthenticationRefocus; } private void InitializeLog() { @@ -30,5 +28,19 @@ private void InitializeLog() Task logThreadTask = new Task(() => Log.Error("Log working in parallel thread")); logThreadTask.Start(); } + + private void AuthenticationRefocus(object sender, EventArgs e) + { + MainWindow.Activate(); + } + + public async void GoogleAuthenticate() + { + await GAuth.Instance.Authenticate(GDrive.Instance); + } + public async void GoogleDeauthenticate() + { + await GAuth.Instance.DeAuthenticate(); + } } } diff --git a/ProtocolMasterWPF/Helpers/CategoryNode.cs b/ProtocolMasterWPF/Helpers/CategoryNode.cs new file mode 100644 index 0000000..ecc8245 --- /dev/null +++ b/ProtocolMasterWPF/Helpers/CategoryNode.cs @@ -0,0 +1,130 @@ +using OxyPlot; +using OxyPlot.Series; +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ProtocolMasterWPF.Helpers +{ + internal class CategoryNode + { + static OxyColor[] pallete = { OxyColors.DarkRed, OxyColors.DodgerBlue, OxyColors.Green }; + public string Name { get; private set; } + public CategoryNode Parent { get; private set; } + public List Children { get; private set; } + public IntervalBarSeries Series { get; private set; } + public CategoryNode(string name) + { + this.Name = name; + this.Children = new List(); + Series = new IntervalBarSeries() + { + Title = name, + StrokeThickness = 1.5, + StrokeColor = OxyColors.Gray, + FillColor = OxyColor.FromArgb(255, 16, 16, 16), + BarWidth = 1.0, + ToolTip = name }; + } + public CategoryNode(string name, CategoryNode parent) : this(name) + { + this.Parent = parent; + Series.BarWidth = 0.35; + parent.Children.Add(this); + } + public void SetSeriesData(int categoryIndex, OxyColor color) + { + Series.StrokeColor = OxyColor.FromArgb(255, (byte)((color.R * 3 + 255) / 4), (byte)((color.G * 3 + 255) / 4), (byte)((color.B * 3 + 255) / 4)); + foreach (IntervalBarItem item in Series.Items) + { + item.CategoryIndex = categoryIndex; + item.Color = color; + } + } + public static void GeneratePlotData(List rootNodes, out List allSeries, out List labels, out List gridLines) + { + double linePos = 0.0; + int fullIndex = 0; + int palleteIndex = 0; + allSeries = new List(); + labels = new List(); + gridLines = new List(); + for (int i = 0; i < rootNodes.Count; i++) + { + CategoryNode root = rootNodes[i]; + root.SetSeriesData(fullIndex++, pallete[palleteIndex]); + allSeries.Add(root.Series); + labels.Add(root.Name); + gridLines.Add(linePos++); + Stack subtree = new Stack(root.Children); + while (subtree.Count != 0) + { + CategoryNode subNode = subtree.Pop(); + subNode.SetSeriesData(fullIndex++, pallete[palleteIndex]); + allSeries.Add(subNode.Series); + labels.Add(subNode.Name); + gridLines.Add(linePos++); + foreach (CategoryNode child in subNode.Children) + subtree.Push(child); + } + if (i < rootNodes.Count - 1) + { + palleteIndex = (palleteIndex + 1) % pallete.Length; + linePos++; + fullIndex++; + labels.Add(""); + } + } + } + // The biggest ugliest mess in the universe! + public static List BuildTrees(List eventList) + { + Dictionary nodeDictionary = new Dictionary(); + List rootNodes = new List(); + Queue eventQueue = new Queue(eventList); + if (eventList != null) + { + while (eventQueue.Count != 0) + { + ProtocolEvent plotEvent = eventQueue.Dequeue(); + if (plotEvent.HasCategory()) + { + CategoryNode targetNode; + if (plotEvent.HasParent()) + { + if (nodeDictionary.ContainsKey(plotEvent.ParentLabel)) + { + if (!nodeDictionary.TryGetValue(plotEvent.FullLabel(), out targetNode)) + { + targetNode = new CategoryNode(plotEvent.CategoryLabel, nodeDictionary[plotEvent.ParentLabel]); + nodeDictionary.Add(plotEvent.FullLabel(), targetNode); + } + } + else + { + eventQueue.Enqueue(plotEvent); + continue; + } + } + else + { + if (!nodeDictionary.TryGetValue(plotEvent.FullLabel(), out targetNode)) + { + targetNode = new CategoryNode(plotEvent.CategoryLabel); + nodeDictionary.Add(plotEvent.FullLabel(), targetNode); + rootNodes.Add(targetNode); + } + } + targetNode.Series.Items.Add(new IntervalBarItem + { + Start = new DateTime(Convert.ToInt64(plotEvent.Arguments["TimeStartMs"]) * 10000).ToOADate(), + End = new DateTime(Convert.ToInt64(plotEvent.Arguments["TimeEndMs"]) * 10000).ToOADate() + }); + } + } + } + return rootNodes; + } + } +} diff --git a/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs b/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs index 10c90fd..2708dcd 100644 --- a/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs +++ b/ProtocolMasterWPF/Helpers/NotNullToBoolConverter.cs @@ -1,8 +1,5 @@ -using ProtocolMasterCore.Utility; -using System; -using System.Collections.Generic; +using System; using System.Globalization; -using System.Text; using System.Windows.Data; namespace ProtocolMasterWPF.Helpers @@ -11,7 +8,6 @@ public class NotNullToBoolConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - Log.Error(value); return value != null; } diff --git a/ProtocolMasterWPF/MainWindow.xaml b/ProtocolMasterWPF/MainWindow.xaml index 0015520..1c97f52 100644 --- a/ProtocolMasterWPF/MainWindow.xaml +++ b/ProtocolMasterWPF/MainWindow.xaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ProtocolMasterWPF" mc:Ignorable="d" - Title="MainWindow" Height="Auto" Width="Auto" + Title="MainWindow" Height="Auto" Width="Auto" MinWidth="450" MinHeight="500" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" xmlns:view="clr-namespace:ProtocolMasterWPF.View" TextElement.Foreground="{DynamicResource MaterialDesignBody}" TextBlock.Foreground="{DynamicResource MaterialDesignBody}" diff --git a/ProtocolMasterWPF/MainWindow.xaml.cs b/ProtocolMasterWPF/MainWindow.xaml.cs index 2ee9809..66feff5 100644 --- a/ProtocolMasterWPF/MainWindow.xaml.cs +++ b/ProtocolMasterWPF/MainWindow.xaml.cs @@ -1,17 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using System.Windows; namespace ProtocolMasterWPF { diff --git a/ProtocolMasterWPF/Model/Camera.cs b/ProtocolMasterWPF/Model/Camera.cs new file mode 100644 index 0000000..db7c0f0 --- /dev/null +++ b/ProtocolMasterWPF/Model/Camera.cs @@ -0,0 +1,90 @@ +using ProtocolMasterCore.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using Windows.Media; +using Windows.Media.Capture; +using Windows.Media.MediaProperties; +using Windows.Storage; + +namespace ProtocolMasterWPF.Model +{ + internal class Camera : INotifyPropertyChanged + { + private StorageFolder videoStore; + private string storagePath; + public MediaCapture MediaCap { get; private set; } + public Camera() + { + AppEnvironment.TryAddLocationAppData("Video", "Video", out storagePath); + InitVideoStore(); + MediaCap = new MediaCapture(); + InitializeCap(); + } + private async void InitVideoStore() + { + videoStore = await StorageFolder.GetFolderFromPathAsync(storagePath); + } + public async void InitializeCap() + { + try + { + await MediaCap.InitializeAsync(new MediaCaptureInitializationSettings()); + } + catch (UnauthorizedAccessException ex) + { + Log.Error($"The app was denied access to the camera:\t{ex}"); + } + } + public async void StartPreview() + { + try + { + await MediaCap.StartPreviewAsync(); + } + catch (System.IO.FileLoadException ex) + { + Log.Error(ex.ToString()); + } + } + public bool IsRecording { get; private set; } + public async void StartRecord() + { + try + { + StorageFile file = await videoStore.CreateFileAsync("Recording (1).wmv", CreationCollisionOption.GenerateUniqueName); + await MediaCap.StartRecordToStorageFileAsync(MediaEncodingProfile.CreateWmv(VideoEncodingQuality.Auto), file); + IsRecording = true; + Log.Error("Began Recording"); + } + catch (Exception ex) + { + Log.Error($"Failed to record:\t{ex}"); + } + } + public async void StopRecord() + { + if (IsRecording) + { + try + { + await MediaCap.StopRecordAsync(); + IsRecording = false; + Log.Error("Finished Recording"); + } + catch (Exception ex) + { + Log.Error($"Failed to save recording:\t{ex}"); + } + } + } + public event PropertyChangedEventHandler PropertyChanged; + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/ProtocolMasterWPF/Model/Google/GAuth.cs b/ProtocolMasterWPF/Model/Google/GAuth.cs new file mode 100644 index 0000000..40f76b9 --- /dev/null +++ b/ProtocolMasterWPF/Model/Google/GAuth.cs @@ -0,0 +1,132 @@ +using Google.Apis.Auth.OAuth2; +using Google.Apis.Util.Store; +using ProtocolMasterCore.Utility; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ProtocolMasterWPF.Model.Google +{ + public enum GAuthState + { + PreAuth, + DoAuth, + PostAuth + } + public sealed class GAuth : INotifyPropertyChanged + { + private static readonly GAuth instance; + private static readonly string authDir; + public event EventHandler PostAuthentication, PreAuthentication; + private UserCredential credential; + private UserCredential Credential { get => credential; set => credential = value; } + private FileDataStore userStore; + private GAuthState _state; + private GAuthState State { get => _state; set { _state = value; OnPropertyChanged("IsPreAuth"); OnPropertyChanged("IsDoAuth"); OnPropertyChanged("IsPostAuth"); } } + public bool IsPreAuth { get => State == GAuthState.PreAuth; } + public bool IsDoAuth { get => State == GAuthState.DoAuth; } + public bool IsPostAuth { get => State == GAuthState.PostAuth; } + + static GAuth() + { + if (!AppEnvironment.TryAddLocationAppData("Auth", "Auth", out authDir)) + { throw new System.Exception("Could not create authenticaton directory"); } + Log.Error($"AuthDir: {authDir}"); + instance = new GAuth(); + } + private GAuth() + { + State = GAuthState.PreAuth; + // Make sure there are no remaining credentials. + DirectoryInfo directory = new DirectoryInfo(authDir); + foreach (FileInfo file in directory.GetFiles()) + file.Delete(); + } + public static GAuth Instance + { + get + { + return instance; + } + } + public async Task Authenticate(params IService[] services) + { + if (PreAuthentication != null) + PreAuthentication.Invoke(this, new EventArgs()); + State = GAuthState.DoAuth; + userStore = new FileDataStore(authDir, true); + + Log.Error("Authenticate(): AUTHENTICATING"); + ICodeReceiver receiver = new GAuthReceiver(); + CancellationTokenSource cts = new CancellationTokenSource(); + cts.CancelAfter(40000); + + Log.Error("Authenticate(): Launching OAuth"); + + try + { + Credential = await GoogleWebAuthorizationBroker.AuthorizeAsync( + new ClientSecrets + { + ClientId = "911699926501-6ici7to17is1fet4mr6jn864lrmrsi0g.apps.googleusercontent.com", + ClientSecret = "LbVHZSa-rtPBMl9odwMBkJi_" + }, + CombineServiceTokens(services), + "user", cts.Token, userStore, receiver); + CreateServices(services); + } + catch (Exception e) + { + State = GAuthState.PreAuth; + Log.Error($"Authentication Exception: {e.Message}"); + } + + State = GAuthState.PostAuth; + Log.Error("Authenticate(): User Fully Authenticated"); + if (PostAuthentication != null) + PostAuthentication.Invoke(this, new EventArgs()); + } + private void CreateServices(IService[] services) + { + foreach (IService service in services) + { + service.CreateService(Credential); + } + } + + private string[] CombineServiceTokens(IService[] services) + { + List builder = new List(); + foreach (IService service in services) + { + foreach (string token in service.ServiceTokens()) + { + builder.Add(token); + } + } + + return builder.ToArray(); + } + + public async Task DeAuthenticate() + { + if (!IsPostAuth) return; + Log.Error("DeAuthenticate(): DEAUTHENTICATING"); + Credential = null; + await userStore.ClearAsync(); + State = GAuthState.PreAuth; + Log.Error("Authenticate(): User Fully Deauthenticated"); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + } +} diff --git a/ProtocolMasterWPF/Model/Google/GAuthReceiver.cs b/ProtocolMasterWPF/Model/Google/GAuthReceiver.cs new file mode 100644 index 0000000..2d6b718 --- /dev/null +++ b/ProtocolMasterWPF/Model/Google/GAuthReceiver.cs @@ -0,0 +1,668 @@ +using Google; +using Google.Apis.Auth.OAuth2; +using Google.Apis.Auth.OAuth2.Requests; +using Google.Apis.Auth.OAuth2.Responses; +using Google.Apis.Logging; +using Google.Apis.Util; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ProtocolMasterWPF.Model.Google +{ + /// + /// OAuth 2.0 verification code receiver that runs a local server on a free port and waits for a call with the + /// authorization verification code. + /// + public class GAuthReceiver : ICodeReceiver + { + /// + /// Describes the different strategies for the selection of the callback URI. + /// 127.0.0.1 is recommended, but can't be done in non-admin Windows 7 and 8 at least. + /// + public enum CallbackUriChooserStrategy + { + /// + /// Use heuristics to attempt to connect to the recommended URI 127.0.0.1 + /// but use localhost if that fails. + /// + Default, + + /// + /// Force 127.0.0.1 as the callback URI. No checks are performed. + /// + ForceLoopbackIp, + + /// + /// Force localhost as the callback URI. No checks are performed. + /// + ForceLocalhost + } + + private static readonly ILogger Logger = ApplicationContext.Logger.ForType(); + + /// The call back request path. + internal const string LoopbackCallbackPath = "/authorize/"; + + /// Close HTML tag to return the browser so it will close itself. + internal const string DefaultClosePageResponse = +@" + +OAuth 2.0 Authentication Token Received + + + Successfully authenticated, redirecting. + +"; + + /// + /// Create an instance of . + /// + public GAuthReceiver() : this(DefaultClosePageResponse, CallbackUriChooserStrategy.Default) { } + + /// + /// Create an instance of . + /// + /// Custom close page response for this instance + public GAuthReceiver(string closePageResponse) : + this(closePageResponse, CallbackUriChooserStrategy.Default) + { } + + /// + /// Create an instance of . + /// + /// Custom close page response for this instance + /// The strategy to use to determine the callback URI + public GAuthReceiver(string closePageResponse, CallbackUriChooserStrategy strategy) + { + _closePageResponse = closePageResponse; + // Set the instance field of which callback URI to use. + // An instance field is used to ensure any one instance of this class + // uses a consistent callback URI. + _callbackUriTemplate = CallbackUriChooser.Default.GetUriTemplate(strategy); + } + + // Callback URI used for this instance. + private string _callbackUriTemplate; + + // Close page response for this instance. + private readonly string _closePageResponse; + + // Not required in NET45, but present for testing. + /// + /// An extremely limited HTTP server that can only do exactly what is required + /// for this use-case. + /// It can only serve localhost; receive a single GET request; read only the query paremters; + /// send back a fixed response. Nothing else. + /// + internal class LimitedLocalhostHttpServer : IDisposable + { + // RFC7230 recommends supporting a request-line length of at least 8,000 octets + // https://tools.ietf.org/html/rfc7230#section-3.1.1 + private const int MaxRequestLineLength = 16 * 1024; + private const int MaxHeadersLength = 64 * 1024; + private const int NetworkReadBufferSize = 1024; + + private static ILogger Logger = ApplicationContext.Logger.ForType(); + + public class ServerException : Exception + { + public ServerException(string msg) : base(msg) { } + } + + public static LimitedLocalhostHttpServer Start(string url, string closePageResponse) + { + var uri = new Uri(url); + if (!uri.IsLoopback) + { + throw new ArgumentException($"Url must be loopback, but given: '{url}'", nameof(url)); + } + var listener = new TcpListener(IPAddress.Loopback, uri.Port); + return new LimitedLocalhostHttpServer(listener, closePageResponse); + } + + private LimitedLocalhostHttpServer(TcpListener listener, string closePageResponse) + { + _listener = listener; + _closePageResponse = closePageResponse; + _cts = new CancellationTokenSource(); + _listener.Start(); + Port = ((IPEndPoint)_listener.LocalEndpoint).Port; + } + + private readonly TcpListener _listener; + private readonly CancellationTokenSource _cts; + + // Close page response for this instance. + private readonly string _closePageResponse; + + public int Port { get; } + + public async Task> GetQueryParamsAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(_cts.Token, cancellationToken)) + using (cts.Token.Register(_listener.Stop)) + { + try + { + using (TcpClient client = await _listener.AcceptTcpClientAsync().ConfigureAwait(false)) + { + try + { + return await GetQueryParamsFromClientAsync(client, cts.Token).ConfigureAwait(false); + } + catch (ServerException e) + { + Logger.Warning("{0}", e.Message); + throw; + } + } + } + // Cancellation during the `AcceptTcpClientAsync()` call results in an `ObjectDisposedException`. + // Translate it to the expected cancellation exception. + catch (ObjectDisposedException) when (cts.IsCancellationRequested) + { + cts.Token.ThrowIfCancellationRequested(); + // Will never get here, but required to satisfy compiler. + throw; + } + } + } + + private async Task> GetQueryParamsFromClientAsync(TcpClient client, CancellationToken cancellationToken) + { + var stream = client.GetStream(); + // NetworkStream.ReadAsync() doesn't honour the cancellation-token (on all platforms), + // so use workaround + using (cancellationToken.Register(() => stream.Dispose())) + { + var buffer = new byte[NetworkReadBufferSize]; + int bufferOfs = 0; + int bufferSize = 0; + Func> getChar = async () => + { + if (bufferOfs == bufferSize) + { + try + { + bufferSize = await stream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); + } + // netcoreapp2.x throws an IOException on stream disposal; others throw ObjectDispoesdException + catch (Exception e) when (e is ObjectDisposedException || e is IOException) + { + throw new OperationCanceledException(cancellationToken); + } + // netcoreapp2.0 on Linux sometimes doesn't throw an exception on stream disposal in ReadAsync, + // so check for cancellation afterwards + cancellationToken.ThrowIfCancellationRequested(); + if (bufferSize == 0) + { + // End of stream + return null; + } + bufferOfs = 0; + } + byte b = buffer[bufferOfs++]; + // HTTP headers are generally ASCII, but historically allowed ISO-8859-1. + // Non-ASCII bytes should be treated opaquely, not further processed (e.g. as UTF8). + return (char)b; + }; + + string requestLine = await ReadRequestLine(getChar).ConfigureAwait(false); + var requestParams = ValidateAndGetRequestParams(requestLine); + await WaitForAllHeaders(getChar).ConfigureAwait(false); + await WriteResponse(stream, cancellationToken).ConfigureAwait(false); + + return requestParams; + } + } + + private async Task ReadRequestLine(Func> getChar) + { + var requestLine = new StringBuilder(MaxRequestLineLength); + do + { + if (requestLine.Length >= MaxRequestLineLength) + { + throw new ServerException($"Request line too long: > {MaxRequestLineLength} bytes."); + } + char? c = await getChar().ConfigureAwait(false); + if (c == null) + { + throw new ServerException("Unexpected end of network stream reading request line."); + } + requestLine.Append(c); + } while (requestLine.Length < 2 || requestLine[requestLine.Length - 2] != '\r' || requestLine[requestLine.Length - 1] != '\n'); + requestLine.Length -= 2; // Remove \r\n + return requestLine.ToString(); + } + + private Dictionary ValidateAndGetRequestParams(string requestLine) + { + var requestLineParts = requestLine.Split(' '); + if (requestLineParts.Length != 3) + { + throw new ServerException("Request line ill-formatted. Should be ' HTTP/1.1'"); + } + string requestVerb = requestLineParts[0]; + if (requestVerb != "GET") + { + throw new ServerException($"Expected 'GET' request, got '{requestVerb}'"); + } + string requestPath = requestLineParts[1]; + if (!requestPath.StartsWith(LoopbackCallbackPath)) + { + throw new ServerException($"Expected request path to start '{LoopbackCallbackPath}', got '{requestPath}'"); + } + var pathParts = requestPath.Split('?'); + if (pathParts.Length == 1) + { + return new Dictionary(); + } + if (pathParts.Length != 2) + { + throw new ServerException($"Expected a single '?' in request path, got '{requestPath}'"); + } + var queryParams = pathParts[1]; + var result = queryParams.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries).Select(param => + { + var keyValue = param.Split('='); + if (keyValue.Length > 2) + { + throw new ServerException($"Invalid query parameter: '{param}'"); + } + var key = WebUtility.UrlDecode(keyValue[0]); + var value = keyValue.Length == 2 ? WebUtility.UrlDecode(keyValue[1]) : ""; + return new { key, value }; + }).ToDictionary(x => x.key, x => x.value); + return result; + } + + private async Task WaitForAllHeaders(Func> getChar) + { + // Looking for an empty line, terminated by \r\n + int byteCount = 0; + int lineLength = 0; + char c0 = '\0'; + char c1 = '\0'; + while (true) + { + if (byteCount > MaxHeadersLength) + { + throw new ServerException($"Headers too long: > {MaxHeadersLength} bytes."); + } + char? c = await getChar().ConfigureAwait(false); + if (c == null) + { + throw new ServerException("Unexpected end of network stream waiting for headers."); + } + c0 = c1; + c1 = (char)c; + lineLength += 1; + byteCount += 1; + if (c0 == '\r' && c1 == '\n') + { + // End of line + if (lineLength == 2) + { + return; + } + lineLength = 0; + } + } + } + + private async Task WriteResponse(NetworkStream stream, CancellationToken cancellationToken) + { + string fullResponse = $"HTTP/1.1 200 OK\r\n\r\n{_closePageResponse}"; + var response = Encoding.ASCII.GetBytes(fullResponse); + await stream.WriteAsync(response, 0, response.Length, cancellationToken).ConfigureAwait(false); + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + public void Dispose() + { + _cts.Cancel(); + _listener.Stop(); + } + } + + // There is a race condition on the port used for the loopback callback. + // This is not good, but is now difficult to change due to RedirectUri and ReceiveCodeAsync + // being public methods. + + private string redirectUri; + /// + public string RedirectUri + { + get + { + if (string.IsNullOrEmpty(redirectUri)) + { + redirectUri = string.Format(_callbackUriTemplate, GetRandomUnusedPort()); + } + return redirectUri; + } + } + + /// + public async Task ReceiveCodeAsync(AuthorizationCodeRequestUrl url, + CancellationToken taskCancellationToken) + { + var authorizationUrl = url.Build().AbsoluteUri; + // The listener type depends on platform: + // * .NET desktop: System.Net.HttpListener + // * .NET Core: LimitedLocalhostHttpServer (above, HttpListener is not available in any version of netstandard) + using var listener = StartListener(); + Logger.Debug("Open a browser with \"{0}\" URL", authorizationUrl); + bool browserOpenedOk; + try + { + browserOpenedOk = OpenBrowser(authorizationUrl); + } + catch (Exception e) + { + Logger.Error(e, "Failed to launch browser with \"{0}\" for authorization", authorizationUrl); + throw new NotSupportedException( + $"Failed to launch browser with \"{authorizationUrl}\" for authorization. See inner exception for details.", e); + } + if (!browserOpenedOk) + { + Logger.Error("Failed to launch browser with \"{0}\" for authorization; platform not supported.", authorizationUrl); + throw new NotSupportedException( + $"Failed to launch browser with \"{authorizationUrl}\" for authorization; platform not supported."); + } + + var ret = await GetResponseFromListener(listener, taskCancellationToken).ConfigureAwait(false); + + return ret; + } + + /// Returns a random, unused port. + private static int GetRandomUnusedPort() + { + var listener = new TcpListener(IPAddress.Loopback, 65298); + try + { + listener.Start(); + return ((IPEndPoint)listener.LocalEndpoint).Port; + } + finally + { + listener.Stop(); + } + } + private HttpListener StartListener() + { + try + { + var listener = new HttpListener(); + listener.Prefixes.Add(RedirectUri); + listener.Start(); + return listener; + } + catch + { + CallbackUriChooser.Default.ReportFailure(_callbackUriTemplate); + throw; + } + } + + private async Task GetResponseFromListener(HttpListener listener, CancellationToken ct) + { + HttpListenerContext context; + // Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token, + // the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call. + using (ct.Register(listener.Stop)) + { + // Wait to get the authorization code response. + try + { + context = await listener.GetContextAsync().ConfigureAwait(false); + } + catch (Exception) when (ct.IsCancellationRequested) + { + ct.ThrowIfCancellationRequested(); + // Next line will never be reached because cancellation will always have been requested in this catch block. + // But it's required to satisfy compiler. + throw new InvalidOperationException(); + } + catch + { + CallbackUriChooser.Default.ReportFailure(_callbackUriTemplate); + throw; + } + CallbackUriChooser.Default.ReportSuccess(_callbackUriTemplate); + } + NameValueCollection coll = context.Request.QueryString; + + // Write a "close" response. + var bytes = Encoding.UTF8.GetBytes(_closePageResponse); + context.Response.ContentLength64 = bytes.Length; + context.Response.SendChunked = false; + context.Response.KeepAlive = false; + var output = context.Response.OutputStream; + await output.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await output.FlushAsync().ConfigureAwait(false); + output.Close(); + context.Response.Close(); + + // Create a new response URL with a dictionary that contains all the response query parameters. + return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k])); + } + + + private bool OpenBrowser(string url) + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = url, + UseShellExecute = true + }; + Process.Start(psi); + return true; + } + + internal class CallbackUriChooser + { + /// Localhost callback URI, expects a port parameter. + internal static readonly string CallbackUriTemplateLocalhost = $"http://localhost:{{0}}{LoopbackCallbackPath}"; + /// 127.0.0.1 callback URI, expects a port parameter. + internal static readonly string CallbackUriTemplate127001 = $"http://127.0.0.1:{{0}}{LoopbackCallbackPath}"; + + private readonly IClock _clock; + // TODO: Consider allowing user code to configure this timeout value. + private readonly TimeSpan _timeout; + private readonly Func _listenerFailsFor; + + private readonly object _lock = new object(); + // TODO: Consider using a dictionary here. But we only have two templates. + private UriStatistics _loopbackIp; + private UriStatistics _localhost; + + public static CallbackUriChooser Default { get; } = new CallbackUriChooser(); + + public CallbackUriChooser() : + this(SystemClock.Default, TimeSpan.FromMinutes(1), FailsHttpListener) + { } + + internal CallbackUriChooser(IClock clock, TimeSpan timeout, Func listenerFailsFor) + { + _clock = clock; + _timeout = timeout; + _listenerFailsFor = listenerFailsFor; + } + + internal string GetUriTemplate(CallbackUriChooserStrategy strategy) + { + lock (_lock) + { + if (strategy == CallbackUriChooserStrategy.ForceLoopbackIp) + { + // We still want to know what happens, we just won't do the initial check. + InitUriStatisticsIfNeeded(ref _loopbackIp, CallbackUriTemplate127001, false); + return _loopbackIp.Uri; + } + + if (strategy == CallbackUriChooserStrategy.ForceLocalhost) + { + // We still want to know what happens, we just won't do the initial check. + InitUriStatisticsIfNeeded(ref _localhost, CallbackUriTemplateLocalhost, false); + return _localhost.Uri; + } + + // Listening on 127.0.0.1 is recommended, but can't be done in non-admin Windows 7 & 8 at least. + // So use some tests/heuristics to maybe listen on localhost instead. + + // If this is the first time that we are called, try with the recommended IP. + InitUriStatisticsIfNeeded(ref _loopbackIp, CallbackUriTemplate127001, true); + // We now know something about the loopback IP for sure. Let's see if we can use it. If so, + // let's return it. + if (_loopbackIp.CanBeUsed) + { + return _loopbackIp.Uri; + } + + // If we are here, we know we can't use the loopback IP, either because it failed or because it + // timed out. + + // Let's try with localhost. + InitUriStatisticsIfNeeded(ref _localhost, CallbackUriTemplateLocalhost, true); + // We now know something about localhost for sure. Let's see if we can use it. If so, + // let's return it. + if (_localhost.CanBeUsed) + { + return _localhost.Uri; + } + + // If we are here then we haven't been able to use loopback IP or localhost, either + // because of failure, or timeout. + // This is probably bad, but we can still recover if + // a) Timeouts were because of user inaction. + // b) Failures were transient. + // Let's try our best. + + UriStatistics retriable = _loopbackIp.TotalResets switch + { + // We always prefer the one with less resets. + var loopbackResets when loopbackResets < _localhost.TotalResets => _loopbackIp, + var loopbackResets when loopbackResets > _localhost.TotalResets => _localhost, + // If they have the same amount of resets, then we prefer the one that has timed out + // and we prefer loopback if both have timed out. + _ when _loopbackIp.IsTimedOut => _loopbackIp, + _ when _localhost.IsTimedOut => _localhost, + // If they have the same amount of resets and none has timed out (they have failed), then we prefer loopback. + _ => _loopbackIp + }; + + retriable.Reset(); + return retriable.Uri; + } + + void InitUriStatisticsIfNeeded(ref UriStatistics statistics, string uri, bool checkListener) + { + if (statistics == null) + { + statistics = new UriStatistics(uri, _timeout, _clock); + + // If possible, preemptively check that the uri works on this environment. + // For instance, the loopback IP fails at least on Windows 7 and 8, for non-admin users. + if (checkListener && _listenerFailsFor(statistics.Uri)) + { + statistics.Failed(); + } + } + } + } + + public void ReportSuccess(string uri) => GetStatisticsFor(uri).Succeeded(); + + public void ReportFailure(string uri) => GetStatisticsFor(uri).Failed(); + + private UriStatistics GetStatisticsFor(string uri) => + uri == CallbackUriTemplate127001 ? _loopbackIp : + uri == CallbackUriTemplateLocalhost ? _localhost : + throw new ArgumentOutOfRangeException(nameof(uri)); + + private static bool FailsHttpListener(string uri) + { + + try + { + // This listener isn't used for anything except to check if it can listen on the given URI. + // Hence it is disposed immediately. + using var listener = new HttpListener(); + listener.Prefixes.Add(string.Format(uri, GetRandomUnusedPort())); + listener.Start(); + } + catch (HttpListenerException e) when (e.ErrorCode == 5) // 5: Access denied + { + // Access denied for the given URI, report failure. + return true; + } + catch + { + // Ignore any errors here, they will re-occur later. + } + return false; + } + + private class UriStatistics + { + private readonly TimeSpan _timeouts; + private readonly IClock _clock; + + public string Uri { get; } + + public DateTimeOffset FirstServedAt { get; private set; } + + public bool IsKnownToSucceed { get; private set; } + + public bool IsKnownToFail { get; private set; } + + public int TotalResets { get; private set; } + + public bool IsTimedOut => + // If we know of success or failure it is not timed out. + !IsKnownToSucceed && !IsKnownToFail && + FirstServedAt.Add(_timeouts) <= _clock.UtcNow; + + public bool CanBeUsed => + // If it's known to succeed, even if it has failed, it can be used. + IsKnownToSucceed || + (!IsKnownToFail && !IsTimedOut); + + public UriStatistics(string uri, TimeSpan timeoutsAfter, IClock clock) + { + _timeouts = timeoutsAfter; + _clock = clock; + Uri = uri; + FirstServedAt = new DateTimeOffset(_clock.UtcNow); + IsKnownToSucceed = false; + IsKnownToFail = false; + TotalResets = 0; + } + + public void Succeeded() => IsKnownToSucceed = true; + + public void Failed() => IsKnownToFail = true; + + public void Reset() + { + TotalResets++; + FirstServedAt = new DateTimeOffset(_clock.UtcNow); + IsKnownToFail = false; + } + } + } + } +} diff --git a/ProtocolMasterWPF/Model/Google/GDrive.cs b/ProtocolMasterWPF/Model/Google/GDrive.cs new file mode 100644 index 0000000..fa3bcc0 --- /dev/null +++ b/ProtocolMasterWPF/Model/Google/GDrive.cs @@ -0,0 +1,92 @@ +using Google.Apis.Auth.OAuth2; +using Google.Apis.Drive.v3; +using Google.Apis.Drive.v3.Data; +using Google.Apis.Services; +using ProtocolMasterCore.Utility; +using System.Collections.ObjectModel; +using System.IO; +using File = Google.Apis.Drive.v3.Data.File; + +namespace ProtocolMasterWPF.Model.Google +{ + public sealed class GDrive : IService + { + private static readonly GDrive instance = new GDrive(); + static GDrive() + { + } + private GDrive() + { + AvailableFiles = new ObservableCollection(); + } + public static GDrive Instance + { + get + { + return instance; + } + } + + public Stream Download(string fileID) + { + if (fileID != null) + return service.Files.Export(fileID, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet").ExecuteAsStream(); + else return null; + } + + // Core Drive Functionality. + // This all needs to be abstracted somewhere else (Model) + const string ROOT_FOLDER_NAME = "ProtocolMaster"; + public ObservableCollection AvailableFiles { get; private set; } + FileList driveFiles; + private DriveService service; + #region + private void LoadAvailable(bool clear = false) + { + AvailableFiles.Clear(); + FileList result; + do + { + FilesResource.ListRequest listRequest = service.Files.List(); + listRequest.PageSize = 20; + listRequest.Fields = "nextPageToken, files(id, name, mimeType, trashed)"; + listRequest.Q = + "mimeType = 'application/vnd.google-apps.spreadsheet' and " + + "trashed = false"; + result = listRequest.Execute(); + + foreach (File file in result.Files) + AvailableFiles.Add(new GFileData(file)); + } while (result.IncompleteSearch.HasValue && result.IncompleteSearch.Equals(true)); + } + public void RefreshAvailable() + { + LoadAvailable(true); + } + #endregion + + // IService Implementation + #region + private static readonly string[] serviceTokens = + { + DriveService.Scope.DriveFile + }; + + + string[] IService.ServiceTokens() { return serviceTokens; } + void IService.CreateService(UserCredential credential) + { + Log.Error("CreateService(): CREATING DRIVE SERVICE"); + // Create the drive service. + service = new DriveService(new BaseClientService.Initializer() + { + HttpClientInitializer = credential, + ApplicationName = "Protocol Master", + }); + + // Find root folder for application. + LoadAvailable(); + } + #endregion + } +} diff --git a/ProtocolMasterWPF/Model/Google/GFileData.cs b/ProtocolMasterWPF/Model/Google/GFileData.cs new file mode 100644 index 0000000..e79c2a1 --- /dev/null +++ b/ProtocolMasterWPF/Model/Google/GFileData.cs @@ -0,0 +1,26 @@ +using Google.Apis.Drive.v3.Data; +using System.IO; +using File = Google.Apis.Drive.v3.Data.File; + +namespace ProtocolMasterWPF.Model.Google +{ + public class GFileData : IStreamStarter + { + public string Name { get => GFile.Name; } + public string ID { get => GFile.Id; } + public File GFile { get; private set; } + public GFileData(File gfile) + { + GFile = gfile; + } + public override string ToString() + { + return Name; + } + + public Stream StartStream() + { + return GDrive.Instance.Download(ID); + } + } +} diff --git a/ProtocolMasterWPF/Model/Google/IService.cs b/ProtocolMasterWPF/Model/Google/IService.cs new file mode 100644 index 0000000..5cee04c --- /dev/null +++ b/ProtocolMasterWPF/Model/Google/IService.cs @@ -0,0 +1,10 @@ +using Google.Apis.Auth.OAuth2; + +namespace ProtocolMasterWPF.Model.Google +{ + public interface IService + { + string[] ServiceTokens(); + void CreateService(UserCredential credential); + } +} diff --git a/ProtocolMasterWPF/Model/IStreamStarter.cs b/ProtocolMasterWPF/Model/IStreamStarter.cs new file mode 100644 index 0000000..71366ad --- /dev/null +++ b/ProtocolMasterWPF/Model/IStreamStarter.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ProtocolMasterWPF.Model +{ + public interface IStreamStarter + { + public Stream StartStream(); + } +} diff --git a/ProtocolMasterWPF/Model/NoFileSelection.cs b/ProtocolMasterWPF/Model/NoFileSelection.cs new file mode 100644 index 0000000..42d540b --- /dev/null +++ b/ProtocolMasterWPF/Model/NoFileSelection.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ProtocolMasterWPF.Model +{ + public class NoFileSelection : IStreamStarter + { + public Stream StartStream() + { + return null; + } + public override string ToString() + { + return "No File Selected"; + } + } +} diff --git a/ProtocolMasterWPF/Model/Session.cs b/ProtocolMasterWPF/Model/Session.cs new file mode 100644 index 0000000..2daed00 --- /dev/null +++ b/ProtocolMasterWPF/Model/Session.cs @@ -0,0 +1,49 @@ +using ProtocolMasterCore.Protocol; +using ProtocolMasterCore.Utility; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ProtocolMasterWPF.Model +{ + internal enum SessionState + { + NotReady, + Selecting, + Ready, + Running, + Viewing, + } + internal class Session + { + string extensionDir; + private InterpretAndDriveProtocol Protocol { get; set; } + public List Options { get; private set; } + public Session() + { + AppEnvironment.TryAddLocationAssembly("Extensions", "Extensions", out extensionDir); + Protocol = new InterpretAndDriveProtocol(extensionDir); + Protocol.DriverManager.OnOptionsLoaded += LoadOptions; + } + private void LoadOptions(List options) + { + Options = options; + } + public void Preview() + { + Protocol.LoadExtensions(); + } + public void Start() + { + + } + public void Stop() + { + + } + public void Reset() + { + + } + } +} diff --git a/ProtocolMasterWPF/Model/SessionFactory.cs b/ProtocolMasterWPF/Model/SessionFactory.cs new file mode 100644 index 0000000..9edf292 --- /dev/null +++ b/ProtocolMasterWPF/Model/SessionFactory.cs @@ -0,0 +1,11 @@ +using ProtocolMasterCore.Protocol; +using System; +using System.Collections.Generic; +using System.Text; + +namespace ProtocolMasterWPF.Model +{ + internal class SessionFactory + { + } +} diff --git a/ProtocolMasterWPF/Properties/Settings.Designer.cs b/ProtocolMasterWPF/Properties/Settings.Designer.cs index 6fd2fa0..723439d 100644 --- a/ProtocolMasterWPF/Properties/Settings.Designer.cs +++ b/ProtocolMasterWPF/Properties/Settings.Designer.cs @@ -26,36 +26,12 @@ public static Settings Default { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] - public string TargetSerialPort { + public string SessionConfig { get { - return ((string)(this["TargetSerialPort"])); + return ((string)(this["SessionConfig"])); } set { - this["TargetSerialPort"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string InterpreterExtension { - get { - return ((string)(this["InterpreterExtension"])); - } - set { - this["InterpreterExtension"] = value; - } - } - - [global::System.Configuration.UserScopedSettingAttribute()] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("")] - public string DriverExtension { - get { - return ((string)(this["DriverExtension"])); - } - set { - this["DriverExtension"] = value; + this["SessionConfig"] = value; } } } diff --git a/ProtocolMasterWPF/Properties/Settings.settings b/ProtocolMasterWPF/Properties/Settings.settings index 6c68fe1..5e7af51 100644 --- a/ProtocolMasterWPF/Properties/Settings.settings +++ b/ProtocolMasterWPF/Properties/Settings.settings @@ -2,13 +2,7 @@ - - - - - - - + diff --git a/ProtocolMasterWPF/ProtocolMasterWPF.csproj b/ProtocolMasterWPF/ProtocolMasterWPF.csproj index 6871c6d..7a16266 100644 --- a/ProtocolMasterWPF/ProtocolMasterWPF.csproj +++ b/ProtocolMasterWPF/ProtocolMasterWPF.csproj @@ -44,6 +44,8 @@ + + diff --git a/ProtocolMasterWPF/Theme/Buttons.xaml b/ProtocolMasterWPF/Theme/Buttons.xaml index d72ca01..90f095e 100644 --- a/ProtocolMasterWPF/Theme/Buttons.xaml +++ b/ProtocolMasterWPF/Theme/Buttons.xaml @@ -35,4 +35,9 @@ + + \ No newline at end of file diff --git a/ProtocolMasterWPF/Theme/Colors.xaml b/ProtocolMasterWPF/Theme/Colors.xaml index 07455cf..9f51499 100644 --- a/ProtocolMasterWPF/Theme/Colors.xaml +++ b/ProtocolMasterWPF/Theme/Colors.xaml @@ -2,6 +2,17 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:ProtocolMasterWPF.Theme"> + + + + + + + + + + + diff --git a/ProtocolMasterWPF/View/CameraView.xaml b/ProtocolMasterWPF/View/CameraView.xaml index f6d9c4f..5243a80 100644 --- a/ProtocolMasterWPF/View/CameraView.xaml +++ b/ProtocolMasterWPF/View/CameraView.xaml @@ -7,14 +7,22 @@ mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> + + + + + BorderThickness="1" Margin="3"> - + diff --git a/ProtocolMasterWPF/View/CameraView.xaml.cs b/ProtocolMasterWPF/View/CameraView.xaml.cs index fc03f67..169dd2f 100644 --- a/ProtocolMasterWPF/View/CameraView.xaml.cs +++ b/ProtocolMasterWPF/View/CameraView.xaml.cs @@ -1,21 +1,5 @@ using ProtocolMasterWPF.ViewModel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Interop; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using Windows.Graphics.Imaging; -using Windows.Media; -using Windows.Media.Capture; namespace ProtocolMasterWPF.View { @@ -24,12 +8,12 @@ namespace ProtocolMasterWPF.View /// public partial class CameraView : UserControl { - + CameraViewModel ViewModel { get; set; } public CameraView() { InitializeComponent(); - DataContext = new CameraViewModel(); + ViewModel = new CameraViewModel(); + DataContext = ViewModel; } - } } diff --git a/ProtocolMasterWPF/View/DriveSelectView.xaml b/ProtocolMasterWPF/View/DriveSelectView.xaml index a6cd52d..780e3fa 100644 --- a/ProtocolMasterWPF/View/DriveSelectView.xaml +++ b/ProtocolMasterWPF/View/DriveSelectView.xaml @@ -7,21 +7,15 @@ mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="400"> - - - - - - - - diff --git a/ProtocolMasterWPF/View/DriveSelectView.xaml.cs b/ProtocolMasterWPF/View/DriveSelectView.xaml.cs index 5a41c1e..a34c8f2 100644 --- a/ProtocolMasterWPF/View/DriveSelectView.xaml.cs +++ b/ProtocolMasterWPF/View/DriveSelectView.xaml.cs @@ -1,26 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows; +using ProtocolMasterWPF.Model.Google; +using ProtocolMasterWPF.ViewModel; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; namespace ProtocolMasterWPF.View { /// /// Interaction logic for DriveSelectView.xaml /// - public partial class DriveSelectView : UserControl + public partial class DriveSelectView : UserControl, ISelectView { + public ListBox SelectList { get => SelectListBox; } + public DriveSelectViewModel ViewModel { get; private set; } public DriveSelectView() { InitializeComponent(); + ViewModel = new DriveSelectViewModel(); + DataContext = ViewModel; } + private void SignIn_Click(object sender, System.Windows.RoutedEventArgs e) => ((App)App.Current).GoogleAuthenticate(); } } diff --git a/ProtocolMasterWPF/View/ISelectView.cs b/ProtocolMasterWPF/View/ISelectView.cs new file mode 100644 index 0000000..3bf0e66 --- /dev/null +++ b/ProtocolMasterWPF/View/ISelectView.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows.Controls; + +namespace ProtocolMasterWPF.View +{ + public interface ISelectView + { + public ListBox SelectList { get; } + } +} diff --git a/ProtocolMasterWPF/View/LogView.xaml.cs b/ProtocolMasterWPF/View/LogView.xaml.cs index e139c1d..96f789e 100644 --- a/ProtocolMasterWPF/View/LogView.xaml.cs +++ b/ProtocolMasterWPF/View/LogView.xaml.cs @@ -1,16 +1,4 @@ -using ProtocolMasterWPF.ViewModel; -using System; -using System.Collections.Generic; -using System.Text; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; +using System.Windows.Controls; namespace ProtocolMasterWPF.View { diff --git a/ProtocolMasterWPF/View/ProtocolSelectView.xaml b/ProtocolMasterWPF/View/ProtocolSelectView.xaml index 765ac49..ab2a8dd 100644 --- a/ProtocolMasterWPF/View/ProtocolSelectView.xaml +++ b/ProtocolMasterWPF/View/ProtocolSelectView.xaml @@ -12,7 +12,7 @@ - + - + - - - - - - - - - + + + + + + + + + - - - - + @@ -63,26 +61,19 @@ - - - + - No Protocol - + + + + + +