babysmash/Controller.cs

631 строка
23 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Deployment.Application;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
using BabySmash.Properties;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using WinForms = System.Windows.Forms;
namespace BabySmash
{
using System.Globalization;
using System.IO;
using System.Speech.Synthesis;
using System.Text;
using Newtonsoft.Json;
public class Controller
{
[DllImport("user32.dll")]
private static extern IntPtr SetFocus(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private static Controller instance = new Controller();
public bool isOptionsDialogShown { get; set; }
private bool isDrawing = false;
private readonly SpeechSynthesizer objSpeech = new SpeechSynthesizer();
private readonly List<MainWindow> windows = new List<MainWindow>();
private DispatcherTimer timer = new DispatcherTimer();
private Queue<Shape> ellipsesQueue = new Queue<Shape>();
private Dictionary<string, List<UserControl>> figuresUserControlQueue = new Dictionary<string, List<UserControl>>();
private ApplicationDeployment deployment = null;
private WordFinder wordFinder = new WordFinder("Words.txt");
/// <summary>Prevents a default instance of the Controller class from being created.</summary>
private Controller() { }
public static Controller Instance
{
get { return instance; }
}
void deployment_CheckForUpdateCompleted(object sender, CheckForUpdateCompletedEventArgs e)
{
if (e.Error == null && e.UpdateAvailable)
{
try
{
MainWindow w = this.windows[0];
w.updateProgress.Value = 0;
w.UpdateAvailableLabel.Visibility = Visibility.Visible;
deployment.UpdateAsync();
}
catch (InvalidOperationException ex)
{
Debug.WriteLine(ex.ToString());
MainWindow w = this.windows[0];
w.UpdateAvailableLabel.Visibility = Visibility.Hidden;
}
}
}
void deployment_UpdateProgressChanged(object sender, DeploymentProgressChangedEventArgs e)
{
MainWindow w = this.windows[0];
w.updateProgress.Value = e.ProgressPercentage;
}
void deployment_UpdateCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Error != null)
{
Debug.WriteLine(e.ToString());
return;
}
MainWindow w = this.windows[0];
w.UpdateAvailableLabel.Visibility = Visibility.Hidden;
}
public void Launch()
{
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = new TimeSpan(0, 0, 1);
int Number = 0;
if (ApplicationDeployment.IsNetworkDeployed)
{
deployment = ApplicationDeployment.CurrentDeployment;
deployment.UpdateCompleted += new System.ComponentModel.AsyncCompletedEventHandler(deployment_UpdateCompleted);
deployment.UpdateProgressChanged += deployment_UpdateProgressChanged;
deployment.CheckForUpdateCompleted += deployment_CheckForUpdateCompleted;
try
{
deployment.CheckForUpdateAsync();
}
catch (InvalidOperationException e)
{
Debug.WriteLine(e.ToString());
}
}
foreach (WinForms.Screen s in WinForms.Screen.AllScreens)
{
MainWindow m = new MainWindow(this)
{
WindowStartupLocation = WindowStartupLocation.Manual,
Left = s.WorkingArea.Left,
Top = s.WorkingArea.Top,
Width = s.WorkingArea.Width,
Height = s.WorkingArea.Height,
WindowStyle = WindowStyle.None,
ResizeMode = ResizeMode.NoResize,
Topmost = true,
AllowsTransparency = Settings.Default.TransparentBackground,
Background = (Settings.Default.TransparentBackground ? new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)) : Brushes.WhiteSmoke),
Name = "Window" + Number++.ToString()
};
figuresUserControlQueue[m.Name] = new List<UserControl>();
m.Show();
m.MouseLeftButtonDown += HandleMouseLeftButtonDown;
m.MouseWheel += HandleMouseWheel;
m.WindowState = WindowState.Maximized;
windows.Add(m);
}
//Only show the info label on the FIRST monitor.
windows[0].infoLabel.Visibility = Visibility.Visible;
//Startup sound
Win32Audio.PlayWavResourceYield("EditedJackPlaysBabySmash.wav");
string[] args = Environment.GetCommandLineArgs();
string ext = System.IO.Path.GetExtension(System.Reflection.Assembly.GetExecutingAssembly().CodeBase);
if (ApplicationDeployment.IsNetworkDeployed && (ApplicationDeployment.CurrentDeployment.IsFirstRun || ApplicationDeployment.CurrentDeployment.UpdatedVersion != ApplicationDeployment.CurrentDeployment.CurrentVersion))
{
//if someone made us a screensaver, then don't show the options dialog.
if ((args != null && args[0] != "/s") && String.CompareOrdinal(ext, ".SCR") != 0)
{
ShowOptionsDialog();
}
}
#if !false
timer.Start();
#endif
}
void timer_Tick(object sender, EventArgs e)
{
if (isOptionsDialogShown)
{
return;
}
try
{
IntPtr windowHandle = new WindowInteropHelper(Application.Current.MainWindow).Handle;
SetForegroundWindow(windowHandle);
SetFocus(windowHandle);
}
catch (Exception)
{
//Wish me luck!
}
}
public void ProcessKey(FrameworkElement uie, KeyEventArgs e)
{
if (uie.IsMouseCaptured)
{
uie.ReleaseMouseCapture();
}
char displayChar = GetDisplayChar(e.Key);
AddFigure(uie, displayChar);
}
private char GetDisplayChar(Key key)
{
// If a number on the normal number track is pressed, display the number.
if (key >= Key.D0 && key <= Key.D9)
{
return (char)('0' + key - Key.D0);
}
// If a number on the numpad is pressed, display the number.
if (key >= Key.NumPad0 && key <= Key.NumPad9)
{
return (char)('0' + key - Key.NumPad0);
}
try
{
return char.ToUpperInvariant(TryGetLetter(key));
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
return '*';
}
}
public enum MapType : uint
{
MAPVK_VK_TO_VSC = 0x0,
MAPVK_VSC_TO_VK = 0x1,
MAPVK_VK_TO_CHAR = 0x2,
MAPVK_VSC_TO_VK_EX = 0x3,
}
[DllImport("user32.dll")]
public static extern int ToUnicode(
uint wVirtKey,
uint wScanCode,
byte[] lpKeyState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeParamIndex = 4)]
StringBuilder pwszBuff,
int cchBuff,
uint wFlags);
[DllImport("user32.dll")]
public static extern bool GetKeyboardState(byte[] lpKeyState);
[DllImport("user32.dll")]
public static extern uint MapVirtualKey(uint uCode, MapType uMapType);
private static char TryGetLetter(Key key)
{
char ch = ' ';
int virtualKey = KeyInterop.VirtualKeyFromKey(key);
byte[] keyboardState = new byte[256];
GetKeyboardState(keyboardState);
uint scanCode = MapVirtualKey((uint)virtualKey, MapType.MAPVK_VK_TO_VSC);
StringBuilder stringBuilder = new StringBuilder(2);
int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, stringBuilder, stringBuilder.Capacity, 0);
switch (result)
{
case -1:
break;
case 0:
break;
case 1:
{
ch = stringBuilder[0];
break;
}
default:
{
ch = stringBuilder[0];
break;
}
}
return ch;
}
private void AddFigure(FrameworkElement uie, char c)
{
FigureTemplate template = FigureGenerator.GenerateFigureTemplate(c);
foreach (MainWindow window in this.windows)
{
UserControl f = FigureGenerator.NewUserControlFrom(template);
window.AddFigure(f);
var queue = figuresUserControlQueue[window.Name];
queue.Add(f);
// Letters should already have accurate width and height, but others may them assigned.
if (double.IsNaN(f.Width) || double.IsNaN(f.Height))
{
f.Width = 300;
f.Height = 300;
}
Canvas.SetLeft(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(window.ActualWidth - f.Width)));
Canvas.SetTop(f, Utils.RandomBetweenTwoNumbers(0, Convert.ToInt32(window.ActualHeight - f.Height)));
Storyboard storyboard = Animation.CreateDPAnimation(uie, f,
UIElement.OpacityProperty,
new Duration(TimeSpan.FromSeconds(Settings.Default.FadeAfter)), 1, 0);
if (Settings.Default.FadeAway) storyboard.Begin(uie);
IHasFace face = f as IHasFace;
if (face != null)
{
face.FaceVisible = Settings.Default.FacesOnShapes ? Visibility.Visible : Visibility.Hidden;
}
if (queue.Count > Settings.Default.ClearAfter)
{
window.RemoveFigure(queue[0]);
queue.RemoveAt(0);
}
}
// Find the last word typed, if applicable.
string lastWord = this.wordFinder.LastWord(figuresUserControlQueue.Values.First());
if (lastWord != null)
{
foreach (MainWindow window in this.windows)
{
this.wordFinder.AnimateLettersIntoWord(figuresUserControlQueue[window.Name], lastWord);
}
SpeakString(lastWord);
}
else
{
PlaySound(template);
}
}
//private static DoubleAnimationUsingKeyFrames ApplyZoomOut(UserControl u)
//{
// Tweener.TransitionType rt1 = Tweener.TransitionType.EaseOutExpo;
// var ani1 = Tweener.Tween.CreateAnimation(rt1, 1, 0, TimeSpan.FromSeconds(0.5));
// u.RenderTransformOrigin = new Point(0.5, 0.5);
// var group = new TransformGroup();
// u.RenderTransform = group;
// ani1.Completed += new EventHandler(ani1_Completed);
// group.Children.Add(new ScaleTransform());
// group.Children[0].BeginAnimation(ScaleTransform.ScaleXProperty, ani1);
// group.Children[0].BeginAnimation(ScaleTransform.ScaleYProperty, ani1);
// return ani1;
//}
//static void ani1_Completed(object sender, EventArgs e)
//{
// AnimationClock clock = sender as AnimationClock;
// Debug.Write(sender.ToString());
// UserControl foo = sender as UserControl;
// UserControl toBeRemoved = queue.Dequeue() as UserControl;
// Canvas container = toBeRemoved.Parent as Canvas;
// container.Children.Remove(toBeRemoved);
//}
void HandleMouseWheel(object sender, MouseWheelEventArgs e)
{
UserControl foo = sender as UserControl; //expected this on Sender!
if (foo != null)
{
if (e.Delta < 0)
{
Animation.ApplyZoom(foo, new Duration(TimeSpan.FromSeconds(0.5)), 2.5);
}
else
{
Animation.ApplyZoom(foo, new Duration(TimeSpan.FromSeconds(0.5)), 0.5);
}
}
}
void HandleMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
UserControl f = e.Source as UserControl;
if (f != null && f.Opacity > 0.1) //can it be seen?
{
isDrawing = true; //HACK: This is a cheat to stop the mouse draw action.
Animation.ApplyRandomAnimationEffect(f, Duration.Automatic);
PlayLaughter(); //Might be better to re-speak the name, color, etc.
}
}
public void PlaySound(FigureTemplate template)
{
if (Settings.Default.Sounds == "Laughter")
{
PlayLaughter();
}
if (objSpeech != null && Settings.Default.Sounds == "Speech")
{
if (template.Letter != null && template.Letter.Length == 1 && Char.IsLetterOrDigit(template.Letter[0]))
{
SpeakString(template.Letter);
}
else
{
SpeakString(GetLocalizedString(Utils.ColorToString(template.Color)) + " " + template.Name);
}
}
}
/// <summary>
/// Returns <param name="key"></param> if value or culture is not found.
/// </summary>
public static string GetLocalizedString(string key)
{
CultureInfo keyboardLanguage = System.Windows.Forms.InputLanguage.CurrentInputLanguage.Culture;
string culture = keyboardLanguage.Name;
string path = $@"Resources\Strings\{culture}.json";
string path2 = @"Resources\Strings\en-EN.json";
string jsonConfig = null;
if (File.Exists(path))
{
jsonConfig = File.ReadAllText(path);
}
else if (File.Exists(path2))
{
jsonConfig = File.ReadAllText(path2);
}
if (jsonConfig != null)
{
Dictionary<string, object> config = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonConfig);
if (config.ContainsKey(key))
{
return config[key].ToString();
}
}
else
{
System.Diagnostics.Debug.Assert(false, "No file");
}
return key;
}
private void PlayLaughter()
{
Win32Audio.PlayWavResource(Utils.GetRandomSoundFile());
}
private void SpeakString(string s)
{
ThreadedSpeak ts = new ThreadedSpeak(s);
ts.Speak();
}
private class ThreadedSpeak
{
private string Word = null;
SpeechSynthesizer SpeechSynth = new SpeechSynthesizer();
public ThreadedSpeak(string Word)
{
this.Word = Word;
CultureInfo keyboardLanguage = System.Windows.Forms.InputLanguage.CurrentInputLanguage.Culture;
InstalledVoice neededVoice = this.SpeechSynth.GetInstalledVoices(keyboardLanguage).FirstOrDefault();
if (neededVoice == null)
{
//http://superuser.com/questions/590779/how-to-install-more-voices-to-windows-speech
//https://msdn.microsoft.com/en-us/library/windows.media.speechsynthesis.speechsynthesizer.voice.aspx
//http://stackoverflow.com/questions/34776593/speechsynthesizer-selectvoice-fails-with-no-matching-voice-is-installed-or-th
this.Word = "Unsupported Language";
}
else if (!neededVoice.Enabled)
{
this.Word = "Voice Disabled";
}
else
{
try
{
this.SpeechSynth.SelectVoice(neededVoice.VoiceInfo.Name);
}
catch (Exception ex)
{
Debug.Assert(false, ex.ToString());
}
}
SpeechSynth.Rate = -1;
SpeechSynth.Volume = 100;
}
public void Speak()
{
Thread oThread = new Thread(new ThreadStart(this.Start));
oThread.Start();
}
private void Start()
{
try
{
SpeechSynth.Speak(Word);
}
catch (Exception e)
{
System.Diagnostics.Trace.WriteLine(e.ToString());
}
}
}
public void ShowOptionsDialog()
{
bool foo = Settings.Default.TransparentBackground;
isOptionsDialogShown = true;
var o = new Options();
Mouse.Capture(null);
foreach (MainWindow m in this.windows)
{
m.Topmost = false;
}
o.Topmost = true;
o.Focus();
o.ShowDialog();
Debug.Write("test");
foreach (MainWindow m in this.windows)
{
m.Topmost = true;
//m.ResetCanvas();
}
isOptionsDialogShown = false;
if (foo != Settings.Default.TransparentBackground)
{
MessageBoxResult result = MessageBox.Show(
"You've changed the Window Transparency Option. We'll need to restart BabySmash! for you to see the change. Pressing YES will restart BabySmash!. Is that OK?",
"Need to Restart", MessageBoxButton.YesNo, MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
{
Application.Current.Shutdown();
System.Windows.Forms.Application.Restart();
}
}
}
public void MouseDown(MainWindow main, MouseButtonEventArgs e)
{
if (isDrawing || Settings.Default.MouseDraw) return;
// Create a new Ellipse object and add it to canvas.
Point ptCenter = e.GetPosition(main.mouseCursorCanvas);
MouseDraw(main, ptCenter);
isDrawing = true;
main.CaptureMouse();
Win32Audio.PlayWavResource("smallbumblebee.wav");
}
public void MouseWheel(MainWindow main, MouseWheelEventArgs e)
{
if (e.Delta > 0)
{
Win32Audio.PlayWavResourceYield("rising.wav");
}
else
{
Win32Audio.PlayWavResourceYield("falling.wav");
}
}
public void MouseUp(MainWindow main, MouseButtonEventArgs e)
{
isDrawing = false;
if (Settings.Default.MouseDraw) return;
main.ReleaseMouseCapture();
}
public void MouseMove(MainWindow main, MouseEventArgs e)
{
if (isOptionsDialogShown)
{
main.ReleaseMouseCapture();
return;
}
if (Settings.Default.MouseDraw && main.IsMouseCaptured == false)
main.CaptureMouse();
if (isDrawing || Settings.Default.MouseDraw)
{
MouseDraw(main, e.GetPosition(main));
}
// Cheesy, but hotkeys are ignored when the mouse is captured.
// However, if we don't capture and release, the shapes will draw forever.
if (Settings.Default.MouseDraw && main.IsMouseCaptured)
main.ReleaseMouseCapture();
}
private void MouseDraw(MainWindow main, Point p)
{
//randomize at some point?
Shape shape = new Ellipse
{
Stroke = SystemColors.WindowTextBrush,
StrokeThickness = 0,
Fill = Utils.GetGradientBrush(Utils.GetRandomColor()),
Width = 50,
Height = 50
};
ellipsesQueue.Enqueue(shape);
main.mouseDragCanvas.Children.Add(shape);
Canvas.SetLeft(shape, p.X - 25);
Canvas.SetTop(shape, p.Y - 25);
if (Settings.Default.MouseDraw)
Win32Audio.PlayWavResourceYield("smallbumblebee.wav");
if (ellipsesQueue.Count > 30) //this is arbitrary
{
Shape shapeToRemove = ellipsesQueue.Dequeue();
main.mouseDragCanvas.Children.Remove(shapeToRemove);
}
}
//private static void ResetCanvas(MainWindow main)
//{
// main.ResetCanvas();
//}
public void LostMouseCapture(MainWindow main, MouseEventArgs e)
{
if (Settings.Default.MouseDraw) return;
if (isDrawing) isDrawing = false;
}
}
}