diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b9d6bd9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,215 @@ +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +############# +## Windows detritus +############# + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist/ +build/ +eggs/ +parts/ +var/ +sdist/ +develop-eggs/ +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg diff --git a/EyePaint.sln b/EyePaint.sln new file mode 100644 index 0000000..69bd8b9 --- /dev/null +++ b/EyePaint.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EyePaint", "EyePaint\EyePaint.csproj", "{0A8492D9-B72D-4D5E-AF92-01119AF21737}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|Any CPU.ActiveCfg = Debug|x86 + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|x86.ActiveCfg = Debug|x86 + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Debug|x86.Build.0 = Debug|x86 + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|Any CPU.ActiveCfg = Release|x86 + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|x86.ActiveCfg = Release|x86 + {0A8492D9-B72D-4D5E-AF92-01119AF21737}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EyePaint/App.config b/EyePaint/App.config new file mode 100644 index 0000000..a6e5dcc --- /dev/null +++ b/EyePaint/App.config @@ -0,0 +1,57 @@ + + + + +
+ + + + + + + + + default@default.default + + + default.default.default + + + a81591b96318cd9799982c11663df750 + + + 05e84b538d7d4cf5 + + + 10 + + + 10 + + + 30 + + + 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 new file mode 100644 index 0000000..0dc0936 --- /dev/null +++ b/EyePaint/App.xaml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EyePaint/App.xaml.cs b/EyePaint/App.xaml.cs new file mode 100644 index 0000000..25f0ee0 --- /dev/null +++ b/EyePaint/App.xaml.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Mail; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media.Animation; +using Tobii.Gaze.Core; + +namespace EyePaint +{ + /// + /// Globally XAML bindable properties + /// + public class GlobalProperties : DependencyObject + { + public static DependencyProperty TrackingProperty = DependencyProperty.Register("Tracking", typeof(bool), typeof(GlobalProperties)); + public bool Tracking + { + get { return (bool)GetValue(TrackingProperty); } + set { SetValue(TrackingProperty, value); } + } + } + + /// + /// Eye tracking logic and gaze enabled UI elements. + /// + public partial class App : Application + { + IEyeTracker iet; + Size resolution; + List gazes; + Dictionary offsets; + TimeSpan? time; + 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); + + /// + /// Connect to the eye tracker on application startup. + /// + void onStartup(object s, StartupEventArgs e) + { + new SettingsWindow(); + + Globals = new GlobalProperties(); + var connected = connect(); + if (!connected) + { + 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(); + } + } + + /// + /// 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) + { + 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. 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) + { + if (e.GazeData.TrackingStatus == TrackingStatus.NoEyesTracked) + { + // Determine if the user is inactive or just blinking. + if (++notTracking > EyePaint.Properties.Settings.Default.Blink) + { + if (tracking) + { + tracking = false; + Dispatcher.Invoke(() => Globals.Tracking = tracking); + } + } + } + else + { + // Determine if the user reactived the session. + if (notTracking > 0) + { + notTracking = 0; + if (!tracking) + { + tracking = true; + Dispatcher.Invoke(() => Globals.Tracking = tracking); + } + } + + // Retrieve available gaze information from the eye tracker. + var left = (e.GazeData.TrackingStatus == TrackingStatus.BothEyesTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedProbablyLeft || e.GazeData.TrackingStatus == TrackingStatus.OnlyLeftEyeTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedUnknownWhich) ? e.GazeData.Left.GazePointOnDisplayNormalized : new Point2D(0, 0); + var right = (e.GazeData.TrackingStatus == TrackingStatus.BothEyesTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedProbablyRight || e.GazeData.TrackingStatus == TrackingStatus.OnlyRightEyeTracked || e.GazeData.TrackingStatus == TrackingStatus.OneEyeTrackedUnknownWhich) ? e.GazeData.Right.GazePointOnDisplayNormalized : new Point2D(0, 0); + + // Determine new gaze point position on the screen. + var newGazePoint = new Point(resolution.Width * (left.X + right.X) / 2, resolution.Height * (left.Y + right.Y) / 2); + gazes.Add(newGazePoint); + + // Calculate average gaze point (i.e. naive noise reduction). + while (gazes.Count > EyePaint.Properties.Settings.Default.Stability) gazes.RemoveAt(0); + var gazePoint = new Point(gazes.Average(p => p.X), gazes.Average(p => p.Y)); + + // Calibrate average gaze point with known offsets. + if (offsets.Count == 1) gazePoint += offsets.Values.First(); + 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 application. + SetCursorPos((int)gazePoint.X, (int)gazePoint.Y); + } + } + + /// + /// When a gaze button is stared at for long enough a click event is raised on each animation completion. + /// + void onGazeButtonFocused(object s, EventArgs e) + { + var c = s as Clock; + + if (time.HasValue && c.CurrentTime.HasValue && c.CurrentTime.Value < time.Value) + { + // Claim click. + time = null; + + // Find button. + var activeWindow = Application.Current.Windows.OfType().Single(w => w.IsActive); + var focusedButton = FocusManager.GetFocusedElement(activeWindow) as Button; + + // Store calibration offset. + if (gazes.Count > 0) + { + var expectedPoint = focusedButton.PointToScreen(new Point(focusedButton.ActualWidth / 2, focusedButton.ActualHeight / 2)); + var actualPoint = Mouse.GetPosition(activeWindow); + offsets[actualPoint] = expectedPoint - actualPoint; + } + // Raise click event. + focusedButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); + } + + time = (c.CurrentState == ClockState.Active) ? c.CurrentTime : null; + } + + /// + /// When a gaze button is touched a button click event is raised. + /// + void onGazeButtonTouched(object s, EventArgs e) + { + (s as Button).RaiseEvent(new RoutedEventArgs(Button.ClickEvent)); //TODO Verify this works with an actual touch screen. + } + + /// + /// 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() + { + 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 new file mode 100644 index 0000000..48bc89e --- /dev/null +++ b/EyePaint/CalibrationWindow.xaml @@ -0,0 +1,25 @@ + + + + + diff --git a/EyePaint/CalibrationWindow.xaml.cs b/EyePaint/CalibrationWindow.xaml.cs new file mode 100644 index 0000000..553a93e --- /dev/null +++ b/EyePaint/CalibrationWindow.xaml.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows; + +namespace EyePaint +{ + /// + /// Used to reduce calibration offset errors amongst different users. + /// + public partial class CalibrationWindow : Window + { + public CalibrationWindow(Window owner) + { + InitializeComponent(); + Owner = owner; + ShowDialog(); + } + + void onClick(object s, RoutedEventArgs e) + { + DialogResult = true; + } + } +} diff --git a/EyePaint/DialogWindow.xaml b/EyePaint/DialogWindow.xaml new file mode 100644 index 0000000..613bfd8 --- /dev/null +++ b/EyePaint/DialogWindow.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + diff --git a/EyePaint/DialogWindow.xaml.cs b/EyePaint/DialogWindow.xaml.cs new file mode 100644 index 0000000..7c0b67d --- /dev/null +++ b/EyePaint/DialogWindow.xaml.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; +using System.Windows; + +namespace EyePaint +{ + /// + /// Used to display a yes/no dialog to the user. + /// + public partial class DialogWindow : Window + { + public DialogWindow(Window owner) + { + InitializeComponent(); + Owner = owner; + ShowDialog(); + } + + void onConfirm(object s, EventArgs e) + { + DialogResult = true; + } + + void onCancel(object s, EventArgs e) + { + DialogResult = false; + } + } +} diff --git a/EyePaint/ErrorWindow.xaml b/EyePaint/ErrorWindow.xaml new file mode 100644 index 0000000..1e3879e --- /dev/null +++ b/EyePaint/ErrorWindow.xaml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + diff --git a/EyePaint/ErrorWindow.xaml.cs b/EyePaint/ErrorWindow.xaml.cs new file mode 100644 index 0000000..9ff6d6e --- /dev/null +++ b/EyePaint/ErrorWindow.xaml.cs @@ -0,0 +1,16 @@ +using System.Windows; +using System.Windows.Input; + +namespace EyePaint +{ + /// + /// Used to display an error message to the user when the appliction is experiencing a critical halt, such as hardware faults. + /// + public partial class ErrorWindow : Window + { + public ErrorWindow() + { + InitializeComponent(); + } + } +} diff --git a/EyePaint/EyePaint.csproj b/EyePaint/EyePaint.csproj new file mode 100644 index 0000000..6104d04 --- /dev/null +++ b/EyePaint/EyePaint.csproj @@ -0,0 +1,222 @@ + + + + + Debug + AnyCPU + {0A8492D9-B72D-4D5E-AF92-01119AF21737} + WinExe + Properties + EyePaint + EyePaint + v4.5.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + false + C:\Users\Carl\Desktop\EyePaint\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 2 + 2.0.0.%2a + false + true + true + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + ManagedMinimumRules.ruleset + true + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + ManagedMinimumRules.ruleset + true + + + 9AD8DE16F6D79FDF7C35059EDCBF5F203AC1B2FA + + + EyePaint_TemporaryKey.pfx + + + true + + + false + + + LocalIntranet + + + Properties\app.manifest + + + + ..\packages\FlickrNet.3.14.0\lib\net20\FlickrNet.dll + + + + + + + + + + + + + + 4.0 + + + False + ..\..\..\..\..\..\..\Program Files (x86)\Tobii\Tobii EyeX\Tobii.Gaze.Core.NET.dll + + + + + + + + MSBuild:Compile + Designer + + + CalibrationWindow.xaml + + + DialogWindow.xaml + + + ErrorWindow.xaml + + + ResultWindow.xaml + + + SettingsWindow.xaml + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + PaintWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + False + Microsoft .NET Framework 4.5.1 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + + + PreserveNewest + + + + + + + + + + + + \ No newline at end of file diff --git a/EyePaint/FlickrNet.chm b/EyePaint/FlickrNet.chm new file mode 100644 index 0000000..46acf8e Binary files /dev/null and b/EyePaint/FlickrNet.chm differ diff --git a/EyePaint/PaintWindow.xaml b/EyePaint/PaintWindow.xaml new file mode 100644 index 0000000..67d55e5 --- /dev/null +++ b/EyePaint/PaintWindow.xaml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/EyePaint/PaintWindow.xaml.cs b/EyePaint/PaintWindow.xaml.cs new file mode 100644 index 0000000..0df24cb --- /dev/null +++ b/EyePaint/PaintWindow.xaml.cs @@ -0,0 +1,459 @@ +using FlickrNet; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; +using System.Windows.Threading; + +namespace EyePaint +{ + /// + /// Model representing a paint blob in the application. + /// + struct Tree + { + public Point Root; + public PointCollection Leaves; + public Dictionary Parents; + + public Tree(Point p, int maxBranches, ref Random rng) + { + Root = p; + Leaves = new PointCollection(); + Parents = new Dictionary(); + for (int i = 0; i < rng.Next((maxBranches + 1) / 2, maxBranches + 1); ++i) Leaves.Add(Root); + Parents[Root] = Root; + } + } + + /// + /// Graphical interpretation parameters of paint strokes in the application. + /// + struct Shape + { + public double MaxBranches, BranchStepLength, BranchStraightness, GenerationRotation, ColorVariety, VerticesSize, VerticesSquashVariety, CenterSize, CenterOpacity, EdgesOpacity, VerticesOpacity, HullOpacity; + } + + /// + /// Lets the user select different shapes and colors and paint them onto a canvas. + /// + public partial class PaintWindow : Window + { + Random rng = new Random(); + DispatcherTimer paintTimer; + Tree model; + Point gaze; + List gazes = new List(); + Shape shape; + Color color; + HashSet shapes = new HashSet(); + HashSet colors = new HashSet(); + DateTime usage; + Dictionary shapeUsage = new Dictionary(); + Dictionary colorUsage = new Dictionary(); + + public PaintWindow() + { + InitializeComponent(); + } + + void onLoaded(object s, RoutedEventArgs e) + { + // Render clock. Note: single-threaded. Approximately 20 FPS. + (paintTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(50), DispatcherPriority.Render, (_, __) => paint(ref this.model, Raster.Source as RenderTargetBitmap), Dispatcher)).Stop(); + + // Initialize drawing. + Raster.Source = createDrawing((int)ActualWidth, (int)ActualHeight); + } + + void onContentRendered(object s, EventArgs e) + { + // Choose initial shape and color by simulating a button click. + ShapeButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); + ColorButton.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent)); + + // Perform initial offset calibration. + while (!new CalibrationWindow(this).DialogResult.Value) ; + + // Reset window on user inactivity. Note: IsEnabled is used with the eye tracker to determine user status. + IsEnabledChanged += (_s, _e) => + { + var sb = (FindResource("InactivityStoryboard") as Storyboard); + if (!(bool)_e.NewValue) sb.Begin(); + else sb.Stop(); + }; + + (App.Current as App).Resettable = true; + } + + void onUnloaded(object s, RoutedEventArgs e) + { + foreach (var w in OwnedWindows) (w as Window).Close(); + } + + void onInactivity(object s, EventArgs e) + { + if (IsVisible) (App.Current as App).Reset(); + } + + void onCanvasMouseDown(object s, MouseButtonEventArgs e) + { + gaze = calculateGaze(e.GetPosition(s as Canvas)); + startPainting(); + PaintMarker.Visibility = Visibility.Hidden; + (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Stop(); + } + + void onCanvasMouseUp(object s, MouseButtonEventArgs e) + { + stopPainting(); + PaintMarker.Visibility = Visibility.Visible; + (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Begin(); + } + + void onCanvasMouseEnter(object s, MouseEventArgs e) + { + (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Begin(); + } + + void onCanvasMouseLeave(object s, MouseEventArgs e) + { + stopPainting(); + (PaintMarker.FindResource("GazePaintStoryboard") as Storyboard).Stop(); + } + + void onCanvasMouseMove(object s, MouseEventArgs e) + { + var p = calculateGaze(e.GetPosition(s as Canvas)); + + Canvas.SetLeft(PaintMarker, p.X - PaintMarker.ActualWidth / 2); + Canvas.SetTop(PaintMarker, p.Y - PaintMarker.ActualHeight / 2); + + if (paintTimer.IsEnabled && (model.Root - p).Length > Properties.Settings.Default.Spacing / 2) model = new Tree(p, (int)shape.MaxBranches, ref rng); + if ((gaze - p).Length > Properties.Settings.Default.Spacing) + { + var sb = PaintMarker.FindResource("GazePaintStoryboard") as Storyboard; + switch (sb.GetCurrentState()) + { + case ClockState.Active: sb.Seek(TimeSpan.Zero); break; + case ClockState.Filling: sb.Seek(TimeSpan.Zero); stopPainting(); break; + } + } + gaze = p; + } + + void onGazePaint(object s, EventArgs e) + { + startPainting(); + } + + void onPublishButtonClick(object s, EventArgs e) + { + if (new DialogWindow(this).DialogResult.Value) { (App.Current as App).Resettable = false; PaintControls.IsEnabled = false; (FindResource("SaveDrawingStoryboard") as Storyboard).Begin(); } + } + + void onResetButtonClick(object s, EventArgs e) + { + if (new DialogWindow(this).DialogResult.Value) (App.Current as App).Reset(); + } + + void onShapeButtonClick(object s, EventArgs e) + { + // Sort shapes by usage. + shapes.OrderBy(sh => shapeUsage[sh]); + + // Remove underused shapes. + foreach (var sh in shapes.ToList()) + { + if (shapeUsage[sh].Seconds < 0.1 * shapeUsage.Max(kvp => kvp.Value).Seconds || shapeUsage[sh] == TimeSpan.Zero) + { + shapes.Remove(sh); + shapeUsage.Remove(sh); + } + } + + // Determine whether to pick a previous shape or generate a new. + if (shapes.Count > 0 && rng.NextDouble() <= 0.01 * shapes.Count - 0.1) + { + // Pick a previously used shape. + shape = shapes.ElementAt((shapes.ToList().IndexOf(shape) + 1) % shapes.Count); //TODO Verify that the index applies to the HashSet. + } + else + { + // Generate a new shape. + var maxBranches = rng.Next(1, 100); + var branchStepLength = Math.Sqrt(rng.Next(1, 1001)); + var branchStraightness = Math.Sqrt(rng.NextDouble()); + var generationRotation = rng.NextDouble(); + var colorVariety = rng.NextDouble(); + var verticesSize = Math.Sqrt(rng.Next(0, 101)); + var verticesSquashVariety = rng.NextDouble() * rng.NextDouble(); + var centerSize = (branchStepLength < 10) ? rng.Next(10, 101) : Math.Sqrt(rng.Next(1, 101)); + var centerOpacity = Math.Max(0, rng.NextDouble() * (rng.NextDouble() - centerSize / 100d)); + var edgesOpacity = rng.NextDouble() * rng.NextDouble() * branchStraightness; + var verticesOpacity = (verticesSize == 0) ? 0 : rng.NextDouble(); + var hullOpacity = rng.NextDouble() * branchStraightness * generationRotation; + var sumOpacity = centerOpacity + edgesOpacity + verticesOpacity + hullOpacity; + centerOpacity /= sumOpacity; edgesOpacity /= sumOpacity; verticesOpacity /= sumOpacity; hullOpacity /= sumOpacity; + shape = new Shape { MaxBranches = maxBranches, BranchStepLength = branchStepLength, BranchStraightness = branchStraightness, GenerationRotation = generationRotation, ColorVariety = colorVariety, VerticesSize = verticesSize, VerticesSquashVariety = verticesSquashVariety, CenterSize = centerSize, CenterOpacity = centerOpacity, EdgesOpacity = edgesOpacity, VerticesOpacity = verticesOpacity, HullOpacity = hullOpacity }; + } + + // Add shape. + shapes.Add(shape); + shapeUsage[shape] = TimeSpan.Zero; + + // Update GUI. + updateIcons(); + } + + void onColorButtonClick(object s, EventArgs e) + { + // Sort colors by usage. + colors.OrderBy(c => colorUsage[c]); + + // Remove underused colors. + foreach (var c in colors.ToList()) + { + if (colorUsage[c].Seconds < 0.1 * colorUsage.Max(kvp => kvp.Value).Seconds || colorUsage[c] == TimeSpan.Zero) + { + colors.Remove(c); + colorUsage.Remove(c); + } + } + + // Either pick a previously used color or generate a new, based on how many previously used colors there are. + color = (colors.Count > 0 && rng.NextDouble() <= 0.01 * colors.Count - 0.1) ? colors.ElementAt((colors.ToList().IndexOf(color) + 1) % colors.Count) : createColor(); //TODO Verify that the index applies to the HashSet. + + // Add color. + colors.Add(color); + colorUsage[color] = TimeSpan.Zero; + + // Update GUI. + updateIcons(); + } + + void onSaveDrawing(object s, EventArgs e) + { + // Save image to file system. + var pbe = new PngBitmapEncoder(); + pbe.Frames.Add(BitmapFrame.Create(Raster.Source as RenderTargetBitmap)); + using (var fs = System.IO.File.OpenWrite("drawing.png")) pbe.Save(fs); + + // Upload image to Flickr. + try + { + var bw = new BackgroundWorker(); + bw.WorkerReportsProgress = true; + bw.ProgressChanged += (_s, _e) => ProgessBar.Value = _e.ProgressPercentage; + bw.DoWork += (_s, _e) => + { + // Login + bw.ReportProgress(0); + var f = new Flickr(Properties.Settings.Default.FlickrKey, Properties.Settings.Default.FlickrSecret); + f.OAuthAccessToken = Properties.Settings.Default.FlickrAccessToken.Token; + f.OAuthAccessTokenSecret = Properties.Settings.Default.FlickrAccessToken.TokenSecret; + f.OnUploadProgress += (__s, __e) => bw.ReportProgress(__e.ProcessPercentage); + bw.ReportProgress(100); + + // Upload photo. + var photoId = f.UploadPicture("drawing.png", Properties.Settings.Default.FlickrTitle, Properties.Settings.Default.FlickrDescription, Properties.Settings.Default.FlickrTags, true, true, true); + + // Add photo to set. If set doesn't exist, create it first. + bw.ReportProgress(0); + var photosets = f.PhotosetsGetList().Where(set => set.Title == Properties.Settings.Default.FlickrPhotoset).ToList(); + if (photosets.Count == 0) f.PhotosetsCreate(Properties.Settings.Default.FlickrPhotoset, photoId); + else f.PhotosetsAddPhoto(photosets[0].PhotosetId, photoId); + bw.ReportProgress(100); + }; + bw.RunWorkerCompleted += (_s, _e) => + { + // Restart session. + (App.Current as App).Resettable = true; + (App.Current as App).Reset(); + }; + bw.RunWorkerAsync(); + } + catch (Exception ex) + { + (App.Current as App).SendErrorReport("(EyePaint) Image Upload Error", "The application encountered an error when uploading an image. Error: " + ex.Message + ". The image is only available on the file system until the application is used again."); + new ErrorWindow().ShowDialog(); + } + } + + Point calculateGaze(Point p) + { + gazes.Add(p); + while (gazes.Count > Properties.Settings.Default.Inertia) gazes.RemoveAt(0); + return new Point(gazes.Average(_p => _p.X), gazes.Average(_p => _p.Y)); + } + + void startPainting() + { + if (!paintTimer.IsEnabled) + { + model = new Tree(gaze, (int)shape.MaxBranches, ref rng); + usage = DateTime.Now; + paintTimer.Start(); + } + } + + void stopPainting() + { + if (paintTimer.IsEnabled) + { + paintTimer.Stop(); + var t = DateTime.Now - usage; + shapeUsage[shape] += t; + colorUsage[color] += t; + } + } + + RenderTargetBitmap createDrawing(int width, int height) + { + return new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); + } + + void paint(ref Tree model, RenderTargetBitmap drawing) + { + // Grow model. + var newLeaves = new PointCollection(); + var newParents = new Dictionary(); + var rotation = shape.GenerationRotation * rng.NextDouble() * 2 * Math.PI; + for (int i = 0; i < model.Leaves.Count; ++i) + { + var q = (model.Leaves.Count == 0) ? model.Root : model.Leaves[i]; + var angle = + i * (2 * Math.PI / model.Leaves.Count) + + rotation + + (1 - shape.BranchStraightness) * rng.NextDouble() * 2 * Math.PI; + var p = new Point( + q.X + shape.BranchStepLength * Math.Cos(angle), + q.Y + shape.BranchStepLength * Math.Sin(angle) + ); + newParents[p] = q; + newLeaves.Add(p); + } + + // Render model. + var dv = new DrawingVisual(); + using (var dc = dv.RenderOpen()) + { + var centerSize = rng.NextDouble() * shape.CenterSize; + var centerBrush = new SolidColorBrush(createColor(color, shape.ColorVariety)); + centerBrush.Opacity = rng.NextDouble() * shape.CenterOpacity; + var centerPen = new Pen(centerBrush, 1); + dc.DrawEllipse(centerBrush, centerPen, model.Root, centerSize, centerSize); + + var edges = new GeometryGroup(); + var edgesPen = new Pen(new SolidColorBrush(createColor(color, shape.ColorVariety)), 1); + edgesPen.StartLineCap = edgesPen.EndLineCap = PenLineCap.Round; + edgesPen.LineJoin = PenLineJoin.Round; + edgesPen.Brush.Opacity = shape.EdgesOpacity; + foreach (var p in model.Leaves) edges.Children.Add(new LineGeometry(p, model.Parents[p])); + dc.DrawGeometry(null, edgesPen, edges); + + var vertices = new GeometryGroup(); + var verticesBrush = new SolidColorBrush(createColor(color, shape.ColorVariety)); + verticesBrush.Opacity = rng.NextDouble() * shape.VerticesOpacity; + var verticesPen = new Pen(verticesBrush, 1); + foreach (var p in model.Leaves) + { + var r = rng.NextDouble(); + var eg = new EllipseGeometry(p, shape.VerticesSize * (r + shape.VerticesSquashVariety * rng.NextDouble()), shape.VerticesSize * (r + shape.VerticesSquashVariety * rng.NextDouble())); + vertices.Children.Add(eg); + } + dc.DrawGeometry(verticesBrush, verticesPen, vertices); + + var hull = new StreamGeometry(); + var hullBrush = new SolidColorBrush(createColor(color, shape.ColorVariety)); + hullBrush.Opacity = rng.NextDouble() * shape.HullOpacity; + var hullPen = new Pen(hullBrush, 1); + using (var sgc = hull.Open()) + { + sgc.BeginFigure(model.Leaves[0], true, true); + sgc.PolyLineTo(model.Leaves, true, true); + } + dc.DrawGeometry(hullBrush, hullPen, hull); + } + //dv.Opacity = rng.NextDouble(); + var dse = new DropShadowEffect(); + dse.BlurRadius = 20; + dse.ShadowDepth = 0; + dse.Opacity = 0.9;// rng.NextDouble(); + dse.Color = createColor(color, shape.ColorVariety); + dv.Effect = dse; + + // Persist update. + model.Leaves = newLeaves; + model.Parents = newParents; + drawing.Render(dv); + } + + Color createColor(Color? baseColor = null, double randomness = 1) + { + // Generate a random color. + var H = rng.NextDouble(); + var S = rng.NextDouble() > 0 || baseColor.HasValue ? 1.0 : 0; + var V = rng.NextDouble() > 0 || baseColor.HasValue ? 1.0 : 0; + byte R, G, B; + if (S == 0) + { + R = (byte)(V * 255); + G = (byte)(V * 255); + B = (byte)(V * 255); + } + else + { + var h = (H * 6 == 6) ? 0 : H * 6; + var i = (int)Math.Floor(h); + var d1 = V * (1 - S); + var d2 = V * (1 - S * (h - i)); + var d3 = V * (1 - S * (1 - (h - i))); + + double r, g, b; + switch (i) + { + case 0: r = V; g = d3; b = d1; break; + case 1: r = d2; g = V; b = d1; break; + case 2: r = d1; g = V; b = d3; break; + case 3: r = d1; g = d2; b = V; break; + case 4: r = d3; g = d1; b = V; break; + default: r = V; g = d1; b = d2; break; + } + + R = (byte)(r * 255); + G = (byte)(g * 255); + B = (byte)(b * 255); + } + + // Mix colors if neccessary. + if (baseColor.HasValue) + { + var c1 = baseColor.Value; + var c2 = Color.Multiply(Color.FromRgb(R, G, B), (float)randomness); + var brightness = System.Drawing.Color.FromArgb(baseColor.Value.A, baseColor.Value.R, baseColor.Value.G, baseColor.Value.B).GetBrightness(); + return (brightness >= 0.5) ? c1 + c2 : c1 - c2; + } + else return Color.FromRgb(R, G, B); + } + + void updateIcons() + { + ColorButtonBackgroundBaseColor.Color = color; + ColorButtonBackgroundColorVariety.Color = createColor(color, shape.ColorVariety); + var toolIcon = new Image(); + toolIcon.Source = createDrawing((int)(ShapeButton.ActualWidth), (int)(ShapeButton.ActualHeight)); + var model = new Tree(new Point(ShapeButton.ActualWidth / 2, ShapeButton.ActualHeight / 2), (int)shape.MaxBranches, ref rng); + for (int i = 0; i < 10; ++i) paint(ref model, toolIcon.Source as RenderTargetBitmap); + ShapeButton.Content = toolIcon; + } + } +} diff --git a/EyePaint/Properties/AssemblyInfo.cs b/EyePaint/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fe35726 --- /dev/null +++ b/EyePaint/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// 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("EyePaint")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EyePaint")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[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) +)] + + +// 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")] diff --git a/EyePaint/Properties/Resources.Designer.cs b/EyePaint/Properties/Resources.Designer.cs new file mode 100644 index 0000000..09631a1 --- /dev/null +++ b/EyePaint/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18444 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace EyePaint.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EyePaint.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/EyePaint/Properties/Resources.resx b/EyePaint/Properties/Resources.resx new file mode 100644 index 0000000..2aacd41 --- /dev/null +++ b/EyePaint/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/EyePaint/Properties/Settings.Designer.cs b/EyePaint/Properties/Settings.Designer.cs new file mode 100644 index 0000000..bc37c67 --- /dev/null +++ b/EyePaint/Properties/Settings.Designer.cs @@ -0,0 +1,205 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18444 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace EyePaint.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.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("default@default.default")] + public string AdminEmail { + get { + return ((string)(this["AdminEmail"])); + } + set { + this["AdminEmail"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("default.default.default")] + public string SmtpServer { + get { + return ((string)(this["SmtpServer"])); + } + set { + this["SmtpServer"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("a81591b96318cd9799982c11663df750")] + public string FlickrKey { + get { + return ((string)(this["FlickrKey"])); + } + set { + this["FlickrKey"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("05e84b538d7d4cf5")] + public string FlickrSecret { + get { + return ((string)(this["FlickrSecret"])); + } + set { + this["FlickrSecret"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("10")] + public int Spacing { + get { + return ((int)(this["Spacing"])); + } + set { + this["Spacing"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("10")] + public int Stability { + get { + return ((int)(this["Stability"])); + } + set { + this["Stability"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("30")] + public int Blink { + get { + return ((int)(this["Blink"])); + } + set { + this["Blink"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("100")] + public int Inertia { + get { + return ((int)(this["Inertia"])); + } + set { + 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 new file mode 100644 index 0000000..7f5411a --- /dev/null +++ b/EyePaint/Properties/Settings.settings @@ -0,0 +1,51 @@ + + + + + + default@default.default + + + default.default.default + + + a81591b96318cd9799982c11663df750 + + + 05e84b538d7d4cf5 + + + 10 + + + 10 + + + 30 + + + 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 new file mode 100644 index 0000000..814a264 --- /dev/null +++ b/EyePaint/SettingsWindow.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +