diff --git a/EyePaint/App.config b/EyePaint/App.config index 4c3dbb8..a6e5dcc 100644 --- a/EyePaint/App.config +++ b/EyePaint/App.config @@ -2,7 +2,7 @@ -
+
@@ -26,7 +26,7 @@ 10 - 30 + 10 30 @@ -34,6 +34,24 @@ 100 + + 10 + + + + + + Måla med ögonen + + + Målning med ögonen + + + En målning gjord med ögonen. + + + + diff --git a/EyePaint/App.xaml b/EyePaint/App.xaml index 32f40b3..0dc0936 100644 --- a/EyePaint/App.xaml +++ b/EyePaint/App.xaml @@ -1,40 +1,38 @@  + Startup="onStartup" + ShutdownMode="OnExplicitShutdown"> + + + + + + + - - + + - + - - - + + + + + + + - - + + - + - - + + - - - - + + + + + + + + + + - + - + - + - + + + + + + diff --git a/EyePaint/App.xaml.cs b/EyePaint/App.xaml.cs index f5ac38a..25f0ee0 100644 --- a/EyePaint/App.xaml.cs +++ b/EyePaint/App.xaml.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Net; using System.Net.Mail; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -18,110 +17,112 @@ namespace EyePaint /// /// Globally XAML bindable properties /// - /* TODO - public class AppProperties : DependencyObject + public class GlobalProperties : DependencyObject { - public static DependencyProperty TrackingProperty = DependencyProperty.Register("Tracking", typeof(bool), typeof(AppProperties)); + public static DependencyProperty TrackingProperty = DependencyProperty.Register("Tracking", typeof(bool), typeof(GlobalProperties)); public bool Tracking { get { return (bool)GetValue(TrackingProperty); } set { SetValue(TrackingProperty, value); } } - - public static DependencyProperty GazeProperty = DependencyProperty.Register("Gaze", typeof(Point), typeof(AppProperties)); - public Point Gaze - { - get { return (Point)GetValue(GazeProperty); } - set { SetValue(GazeProperty, value); } - } - }*/ - - public class TrackingChangedEventArgs - { - public bool Tracking { get; set; } } /// - /// Eye tracking logic + /// Eye tracking logic and gaze enabled UI elements. /// public partial class App : Application { - //TODO public AppProperties AppProperties { get; set; } IEyeTracker iet; Size resolution; List gazes; Dictionary offsets; TimeSpan? time; - public event EventHandler TrackingChanged; - public bool Tracking; + PaintWindow paintWindow; + ResultWindow resultWindow; + public GlobalProperties Globals { get; set; } + bool tracking; + public bool Resettable; int notTracking; [DllImport("User32.dll")] static public extern bool SetCursorPos(int X, int Y); /// - /// Connects to the eye tracker on application startup. + /// Connect to the eye tracker on application startup. /// void onStartup(object s, StartupEventArgs e) { - gazes = new List(); - offsets = new Dictionary(); - //AppProperties = new AppProperties(); + new SettingsWindow(); - try - { - Uri url = new EyeTrackerCoreLibrary().GetConnectedEyeTracker(); - iet = new EyeTracker(url); - iet.EyeTrackerError += onEyeTrackerError; - iet.GazeData += onGazeData; - Task.Factory.StartNew(() => iet.RunEventLoop()); - iet.Connect(); - iet.StartTracking(); - Mouse.OverrideCursor = Cursors.None; - } - catch (EyeTrackerException) - { - MessageBox.Show("Kameran verkar ha gått sönder. Prova att starta om datorn."); - Application.Current.Shutdown(); - } - catch (NullReferenceException) - { - MessageBox.Show("Kameran verkar saknas. Säkerställ att den är inkopplad och prova att starta om datorn."); - Application.Current.Shutdown(); - } - finally + Globals = new GlobalProperties(); + var connected = connect(); + if (!connected) { - (MainWindow = new MainWindow()).Show(); - resolution = new Size(MainWindow.ActualWidth, MainWindow.ActualHeight); + SendErrorReport("(EyePaint) Startup Error", "The eye tracker could not be found. Ensure that the cable is connected, restart the computer, and try launching the application again."); + new ErrorWindow().ShowDialog(); } } /// - /// Handles error data from the eye tracker. The application will be halted, the user will see an error on the screen, and a notification email is sent to the museum's technical staff. + /// Connect to the eye tracker and start gaze tracking. Return true iff a successful connection has been established. + /// + bool connect() + { + int retry = 0; + while (++retry < EyePaint.Properties.Settings.Default.ConnectionAttempts) try + { + // Connect to eye tracker. + gazes = new List(); + offsets = new Dictionary(); + Uri url = new EyeTrackerCoreLibrary().GetConnectedEyeTracker(); + iet = new EyeTracker(url); + iet.EyeTrackerError += onEyeTrackerError; + iet.GazeData += onGazeData; + Task.Factory.StartNew(() => iet.RunEventLoop()); + iet.Connect(); + iet.StartTracking(); + Mouse.OverrideCursor = Cursors.None; + + // Display image result on a secondary screen. + resultWindow = new ResultWindow(); + if (System.Windows.Forms.SystemInformation.MonitorCount > 1) + { + var wa = System.Windows.Forms.Screen.AllScreens[1].WorkingArea; + resultWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner; + resultWindow.Left = wa.Left; + resultWindow.Top = wa.Top; + resultWindow.Topmost = true; + resultWindow.Show(); + } + + // Open paint window. + paintWindow = new PaintWindow(); + paintWindow.Loaded += (_, __) => resolution = new Size(paintWindow.ActualWidth, paintWindow.ActualHeight); + paintWindow.ContentRendered += (_, __) => resultWindow.SetImageSource(paintWindow.Raster.Source); + paintWindow.Show(); + return true; + } + catch (Exception) + { + Thread.Sleep(1000); + } + return false; + } + + /// + /// Handles error data from the eye tracker. The application will be halted and the user will see an error on the screen while the application attempts to reestablish a connection to the eye tracker. If a connection cannot be reestablished a notification email is sent to the admin email. /// void onEyeTrackerError(object s, EyeTrackerErrorEventArgs e) { - new ErrorWindow(); - try - { - var message = new MailMessage( - EyePaint.Properties.Settings.Default.AdminEmail, - EyePaint.Properties.Settings.Default.AdminEmail, - "Tekniskt problem med stationen Måla med ögonen", - "Kameran verkar ha gått sönder. Prova att starta om datorn." - ); - var client = new SmtpClient(EyePaint.Properties.Settings.Default.SmtpServer); - client.Credentials = CredentialCache.DefaultNetworkCredentials; - client.Send(message); - } - catch (Exception) - { - MessageBox.Show("Felmeddelande kunde ej skickas via epost. Kameran verkar ha gått sönder. Prova att starta om datorn."); - } + var ew = new ErrorWindow(); + ew.Show(); + var reconnected = connect(); + if (reconnected) ew.Close(); + else SendErrorReport("(EyePaint) Error " + e.ErrorCode, "The eye tracker encountered an error. Error: " + e.Message + ". Try rebooting the system."); } /// - /// Handles data from the eye tracker. + /// Handles data from the eye tracker. Places the mouse cursor at the average gaze point so that WPF mouse events can be used throughout the application. /// void onGazeData(object s, GazeDataEventArgs e) { @@ -130,11 +131,10 @@ void onGazeData(object s, GazeDataEventArgs e) // Determine if the user is inactive or just blinking. if (++notTracking > EyePaint.Properties.Settings.Default.Blink) { - //TODO Dispatcher.Invoke(() => { if (AppProperties.Tracking) AppProperties.Tracking = false; }); - if (Tracking) + if (tracking) { - Tracking = false; - if (TrackingChanged != null) TrackingChanged(this, new TrackingChangedEventArgs { Tracking = Tracking }); + tracking = false; + Dispatcher.Invoke(() => Globals.Tracking = tracking); } } } @@ -144,11 +144,10 @@ void onGazeData(object s, GazeDataEventArgs e) if (notTracking > 0) { notTracking = 0; - if (!Tracking) + if (!tracking) { - //TODO Dispatcher.Invoke(() => { if (AppProperties.Tracking) AppProperties.Tracking = true; }); - Tracking = true; - if (TrackingChanged != null) TrackingChanged(this, new TrackingChangedEventArgs { Tracking = Tracking }); + tracking = true; + Dispatcher.Invoke(() => Globals.Tracking = tracking); } } @@ -169,18 +168,16 @@ void onGazeData(object s, GazeDataEventArgs e) var distances = offsets.Select(kvp => (gazePoint - kvp.Key).Length); var distancesRatios = distances.Select(d => d / distances.Sum()); foreach (var v in offsets.Values.Zip(distancesRatios, (o, d) => (1.0 - d) * o)) gazePoint += v; - // Place the mouse cursor at the gaze point so mouse events can be used throughout the app. - SetCursorPos((int)gazePoint.X, (int)gazePoint.Y); - // Store the gaze point. - //TODO Dispatcher.Invoke(() => AppProperties.Gaze = gazePoint); + // Place the mouse cursor at the gaze point so mouse events can be used throughout the application. + SetCursorPos((int)gazePoint.X, (int)gazePoint.Y); } } /// - /// Gaze click event handler for gaze enabled buttons. + /// When a gaze button is stared at for long enough a click event is raised on each animation completion. /// - void onGazeClick(object s, EventArgs e) + void onGazeButtonFocused(object s, EventArgs e) { var c = s as Clock; @@ -190,7 +187,7 @@ void onGazeClick(object s, EventArgs e) time = null; // Find button. - var activeWindow = Application.Current.Windows.OfType().Single(x => x.IsActive); + var activeWindow = Application.Current.Windows.OfType().Single(w => w.IsActive); var focusedButton = FocusManager.GetFocusedElement(activeWindow) as Button; // Store calibration offset. @@ -208,27 +205,49 @@ void onGazeClick(object s, EventArgs e) } /// - /// Gaze inactivity event handler for gaze enabled windows. + /// When a gaze button is touched a button click event is raised. /// - void onInactivity(object s, EventArgs e) + void onGazeButtonTouched(object s, EventArgs e) { - Reset(); + (s as Button).RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); //TODO Verify this works with an actual touch screen. } /// - /// Clear previous gaze data and restart the main window. + /// Attempt to send an error report via email to an application defined admin email. If email could not be sent the error message will be displayed in a message box instead. + /// + public void SendErrorReport(string subject, string body) + { + try + { + var m = new MailMessage(EyePaint.Properties.Settings.Default.AdminEmail, EyePaint.Properties.Settings.Default.AdminEmail, subject, body); + var c = new SmtpClient(EyePaint.Properties.Settings.Default.SmtpServer); + c.Credentials = CredentialCache.DefaultNetworkCredentials; + c.Send(m); + } + catch (Exception) + { + MessageBox.Show("Email could not be sent. Make sure the application is configured correctly. Email: " + subject + " - " + body); + } + } + + /// + /// Clear previous gaze data if neccessary. /// public void Reset() { - gazes.Clear(); - offsets.Clear(); - time = null; - TrackingChanged = null; - Tracking = false; - notTracking = 0; - var mw = new MainWindow(); - mw.ContentRendered += (s, e) => { MainWindow.Close(); MainWindow = mw; }; - mw.Show(); + if (Resettable) + { + offsets.Clear(); + time = null; + var oldPaintWindow = paintWindow; + paintWindow = new PaintWindow(); + paintWindow.ContentRendered += (s, e) => + { + oldPaintWindow.Close(); + resultWindow.SetImageSource(paintWindow.Raster.Source); + }; + paintWindow.Show(); + } } } } diff --git a/EyePaint/CalibrationWindow.xaml b/EyePaint/CalibrationWindow.xaml index 1b6aded..48bc89e 100644 --- a/EyePaint/CalibrationWindow.xaml +++ b/EyePaint/CalibrationWindow.xaml @@ -2,23 +2,16 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Style="{StaticResource GazeWindow}" - Topmost="True" - AllowsTransparency="True" - Background="#AA000000"> - - - - - - - - - + Background="#AA000000" + AllowsTransparency="True"> - - - + diff --git a/EyePaint/DialogWindow.xaml.cs b/EyePaint/DialogWindow.xaml.cs index 9e5cdc1..7c0b67d 100644 --- a/EyePaint/DialogWindow.xaml.cs +++ b/EyePaint/DialogWindow.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Windows; namespace EyePaint @@ -8,18 +9,13 @@ namespace EyePaint /// public partial class DialogWindow : Window { - public DialogWindow() + public DialogWindow(Window owner) { InitializeComponent(); + Owner = owner; ShowDialog(); } - void onContentVisible(object s, EventArgs e) - { - IsEnabled = ((App)Application.Current).Tracking; - ((App)Application.Current).TrackingChanged += (_s, _e) => Dispatcher.Invoke(() => IsEnabled = _e.Tracking); - } - void onConfirm(object s, EventArgs e) { DialogResult = true; diff --git a/EyePaint/ErrorWindow.xaml b/EyePaint/ErrorWindow.xaml index 44ff6fa..1e3879e 100644 --- a/EyePaint/ErrorWindow.xaml +++ b/EyePaint/ErrorWindow.xaml @@ -2,8 +2,8 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Style="{StaticResource GazeWindow}" - Topmost="True" - KeyDown="onKeyDown"> + Background="Black" + Topmost="True"> diff --git a/EyePaint/ErrorWindow.xaml.cs b/EyePaint/ErrorWindow.xaml.cs index ceb6359..9ff6d6e 100644 --- a/EyePaint/ErrorWindow.xaml.cs +++ b/EyePaint/ErrorWindow.xaml.cs @@ -11,15 +11,6 @@ public partial class ErrorWindow : Window public ErrorWindow() { InitializeComponent(); - ShowDialog(); - } - - void onKeyDown(object s, KeyEventArgs e) - { - switch (e.Key) - { - case Key.Escape: Close(); break; - } } } } diff --git a/EyePaint/EyePaint.csproj b/EyePaint/EyePaint.csproj index 9115854..6104d04 100644 --- a/EyePaint/EyePaint.csproj +++ b/EyePaint/EyePaint.csproj @@ -15,7 +15,7 @@ 4 false - D:\ + C:\Users\Carl\Desktop\EyePaint\ true Disk false @@ -25,9 +25,10 @@ false false true - 12 - 1.0.0.%2a + 2 + 2.0.0.%2a false + true true true @@ -62,7 +63,13 @@ true - true + false + + + LocalIntranet + + + Properties\app.manifest @@ -73,6 +80,7 @@ + @@ -97,15 +105,15 @@ CalibrationWindow.xaml - - CountdownWindow.xaml - DialogWindow.xaml ErrorWindow.xaml + + ResultWindow.xaml + SettingsWindow.xaml @@ -113,10 +121,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -125,7 +129,7 @@ Designer MSBuild:Compile - + MSBuild:Compile Designer @@ -133,10 +137,14 @@ App.xaml Code - - MainWindow.xaml + + PaintWindow.xaml Code + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -164,6 +172,7 @@ + SettingsSingleFileGenerator Settings.Designer.cs @@ -174,9 +183,9 @@ - + False - Microsoft .NET Framework 4.5 %28x86 and x64%29 + Microsoft .NET Framework 4.5.1 %28x86 and x64%29 true @@ -199,6 +208,10 @@ + + + + - + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + diff --git a/EyePaint/Properties/Settings.Designer.cs b/EyePaint/Properties/Settings.Designer.cs index 7abd926..bc37c67 100644 --- a/EyePaint/Properties/Settings.Designer.cs +++ b/EyePaint/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.18444 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -85,7 +85,7 @@ public int Spacing { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Configuration.DefaultSettingValueAttribute("30")] + [global::System.Configuration.DefaultSettingValueAttribute("10")] public int Stability { get { return ((int)(this["Stability"])); @@ -118,5 +118,88 @@ public int Inertia { this["Inertia"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("10")] + public int ConnectionAttempts { + get { + return ((int)(this["ConnectionAttempts"])); + } + set { + this["ConnectionAttempts"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + public global::FlickrNet.OAuthAccessToken FlickrAccessToken { + get { + return ((global::FlickrNet.OAuthAccessToken)(this["FlickrAccessToken"])); + } + set { + this["FlickrAccessToken"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string FlickrVerificationCode { + get { + return ((string)(this["FlickrVerificationCode"])); + } + set { + this["FlickrVerificationCode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Måla med ögonen")] + public string FlickrPhotoset { + get { + return ((string)(this["FlickrPhotoset"])); + } + set { + this["FlickrPhotoset"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Målning med ögonen")] + public string FlickrTitle { + get { + return ((string)(this["FlickrTitle"])); + } + set { + this["FlickrTitle"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("En målning gjord med ögonen.")] + public string FlickrDescription { + get { + return ((string)(this["FlickrDescription"])); + } + set { + this["FlickrDescription"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string FlickrTags { + get { + return ((string)(this["FlickrTags"])); + } + set { + this["FlickrTags"] = value; + } + } } } diff --git a/EyePaint/Properties/Settings.settings b/EyePaint/Properties/Settings.settings index 42ca82c..7f5411a 100644 --- a/EyePaint/Properties/Settings.settings +++ b/EyePaint/Properties/Settings.settings @@ -18,7 +18,7 @@ 10 - 30 + 10 30 @@ -26,5 +26,26 @@ 100 + + 10 + + + + + + + + + Måla med ögonen + + + Målning med ögonen + + + En målning gjord med ögonen. + + + + \ No newline at end of file diff --git a/EyePaint/Properties/app.manifest b/EyePaint/Properties/app.manifest new file mode 100644 index 0000000..9a0d40a --- /dev/null +++ b/EyePaint/Properties/app.manifest @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EyePaint/ResultWindow.xaml b/EyePaint/ResultWindow.xaml new file mode 100644 index 0000000..c9b4d2d --- /dev/null +++ b/EyePaint/ResultWindow.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/EyePaint/ResultWindow.xaml.cs b/EyePaint/ResultWindow.xaml.cs new file mode 100644 index 0000000..e70e357 --- /dev/null +++ b/EyePaint/ResultWindow.xaml.cs @@ -0,0 +1,20 @@ +using System.Windows; +using System.Windows.Media; + +namespace EyePaint +{ + /// + /// Used to display the resulting drawing in its own window. + /// + public partial class ResultWindow : Window + { + public ResultWindow() + { + InitializeComponent(); + } + + public void SetImageSource(ImageSource imageSource) { + Result.Source = imageSource; + } + } +} diff --git a/EyePaint/SettingsWindow.xaml b/EyePaint/SettingsWindow.xaml index edc9f14..814a264 100644 --- a/EyePaint/SettingsWindow.xaml +++ b/EyePaint/SettingsWindow.xaml @@ -1,26 +1,107 @@  - - -