Server/MainForm.cs
author StephaneLenclud
Thu, 24 Sep 2015 14:35:50 +0200
changeset 158 e22bf44c4300
parent 157 0f3e7b21c663
child 167 d2295c186ce1
permissions -rw-r--r--
Fixing issues where layout change would not be detected beccause of partial
field similarity between new and older layout.
Therefore we now clear our fields whenever our layout is changed.
Now resetting our create bitmap flag, hoping it will fix our rather large memory
usage when minimized.
     1 ´╗┐//
     2 // Copyright (C) 2014-2015 St├ęphane Lenclud.
     3 //
     4 // This file is part of SharpDisplayManager.
     5 //
     6 // SharpDisplayManager is free software: you can redistribute it and/or modify
     7 // it under the terms of the GNU General Public License as published by
     8 // the Free Software Foundation, either version 3 of the License, or
     9 // (at your option) any later version.
    10 //
    11 // SharpDisplayManager is distributed in the hope that it will be useful,
    12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14 // GNU General Public License for more details.
    15 //
    16 // You should have received a copy of the GNU General Public License
    17 // along with SharpDisplayManager.  If not, see <http://www.gnu.org/licenses/>.
    18 //
    19 
    20 using System;
    21 using System.Collections.Generic;
    22 using System.ComponentModel;
    23 using System.Data;
    24 using System.Drawing;
    25 using System.Linq;
    26 using System.Text;
    27 using System.Threading.Tasks;
    28 using System.Windows.Forms;
    29 using System.IO;
    30 using CodeProject.Dialog;
    31 using System.Drawing.Imaging;
    32 using System.ServiceModel;
    33 using System.Threading;
    34 using System.Diagnostics;
    35 using System.Deployment.Application;
    36 using System.Reflection;
    37 //NAudio
    38 using NAudio.CoreAudioApi;
    39 using NAudio.CoreAudioApi.Interfaces;
    40 using System.Runtime.InteropServices;
    41 //Network
    42 using NETWORKLIST;
    43 //
    44 using SharpDisplayClient;
    45 using SharpDisplay;
    46 using MiniDisplayInterop;
    47 
    48 
    49 namespace SharpDisplayManager
    50 {
    51     //Types declarations
    52     public delegate uint ColorProcessingDelegate(int aX, int aY, uint aPixel);
    53     public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
    54     //Delegates are used for our thread safe method
    55     public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
    56     public delegate void RemoveClientDelegate(string aSessionId);
    57     public delegate void SetFieldDelegate(string SessionId, DataField aField);
    58     public delegate void SetFieldsDelegate(string SessionId, System.Collections.Generic.IList<DataField> aFields);
    59     public delegate void SetLayoutDelegate(string SessionId, TableLayout aLayout);
    60     public delegate void SetClientNameDelegate(string aSessionId, string aName);
    61 	public delegate void PlainUpdateDelegate();
    62 
    63 
    64     /// <summary>
    65     /// Our Display manager main form
    66     /// </summary>
    67 	[System.ComponentModel.DesignerCategory("Form")]
    68 	public partial class MainForm : MainFormHid, IMMNotificationClient
    69     {
    70         DateTime LastTickTime;
    71         Display iDisplay;
    72         System.Drawing.Bitmap iBmp;
    73         bool iCreateBitmap; //Workaround render to bitmap issues when minimized
    74         ServiceHost iServiceHost;
    75         // Our collection of clients sorted by session id.
    76         public Dictionary<string, ClientData> iClients;
    77         // The name of the client which informations are currently displayed.
    78         public string iCurrentClientSessionId;
    79         ClientData iCurrentClientData;
    80         //
    81         public bool iClosing;
    82 		//
    83 		public bool iSkipFrameRendering;
    84         //Function pointer for pixel color filtering
    85         ColorProcessingDelegate iColorFx;
    86         //Function pointer for pixel X coordinate intercept
    87         CoordinateTranslationDelegate iScreenX;
    88         //Function pointer for pixel Y coordinate intercept
    89         CoordinateTranslationDelegate iScreenY;
    90 		//NAudio
    91 		private MMDeviceEnumerator iMultiMediaDeviceEnumerator;
    92 		private MMDevice iMultiMediaDevice;
    93 		//Network
    94 		private NetworkManager iNetworkManager;
    95 		
    96 
    97 		/// <summary>
    98 		/// Manage run when Windows startup option
    99 		/// </summary>
   100 		private StartupManager iStartupManager;
   101 
   102 		/// <summary>
   103 		/// System tray icon.
   104 		/// </summary>
   105 		private NotifyIconAdv iNotifyIcon;
   106 
   107         public MainForm()
   108         {
   109 			iSkipFrameRendering = false;
   110 			iClosing = false;
   111             iCurrentClientSessionId = "";
   112             iCurrentClientData = null;
   113             LastTickTime = DateTime.Now;
   114 			//Instantiate our display and register for events notifications
   115             iDisplay = new Display();
   116 			iDisplay.OnOpened += OnDisplayOpened;
   117 			iDisplay.OnClosed += OnDisplayClosed;
   118 			//
   119 			iClients = new Dictionary<string, ClientData>();
   120 			iStartupManager = new StartupManager();
   121 			iNotifyIcon = new NotifyIconAdv();
   122 
   123 			//Have our designer initialize its controls
   124             InitializeComponent();
   125 
   126 			//Populate device types
   127 			PopulateDeviceTypes();
   128 
   129             //Populate optical drives
   130             PopulateOpticalDrives();
   131 
   132 			//Initial status update 
   133             UpdateStatus();
   134 
   135             //We have a bug when drawing minimized and reusing our bitmap
   136             //Though I could not reproduce it on Windows 10
   137             iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   138             iCreateBitmap = false;
   139 
   140 			//Minimize our window if desired
   141 			if (Properties.Settings.Default.StartMinimized)
   142 			{
   143 				WindowState = FormWindowState.Minimized;
   144 			}
   145 
   146         }
   147 
   148 		/// <summary>
   149 		///
   150 		/// </summary>
   151 		/// <param name="sender"></param>
   152 		/// <param name="e"></param>
   153         private void MainForm_Load(object sender, EventArgs e)
   154         {
   155 			//Check if we are running a Click Once deployed application
   156 			if (ApplicationDeployment.IsNetworkDeployed)
   157 			{
   158 				//This is a proper Click Once installation, fetch and show our version number
   159 				this.Text += " - v" + ApplicationDeployment.CurrentDeployment.CurrentVersion;
   160 			}
   161 			else
   162 			{
   163 				//Not a proper Click Once installation, assuming development build then
   164 				this.Text += " - development";
   165 			}
   166 
   167 			//NAudio
   168 			iMultiMediaDeviceEnumerator = new MMDeviceEnumerator();
   169 			iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this);			
   170 			UpdateAudioDeviceAndMasterVolumeThreadSafe();
   171 
   172 			//Network
   173 			iNetworkManager = new NetworkManager();
   174 			iNetworkManager.OnConnectivityChanged += OnConnectivityChanged;
   175 			UpdateNetworkStatus();
   176 
   177 			//Setup notification icon
   178 			SetupTrayIcon();
   179 
   180 			// To make sure start up with minimize to tray works
   181 			if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
   182 			{
   183 				Visible = false;
   184 			}
   185 
   186 #if !DEBUG
   187 			//When not debugging we want the screen to be empty until a client takes over
   188 			ClearLayout();
   189 #else
   190 			//When developing we want at least one client for testing
   191 			StartNewClient("abcdefghijklmnopqrst-0123456789","ABCDEFGHIJKLMNOPQRST-0123456789");
   192 #endif
   193 
   194 			//Open display connection on start-up if needed
   195 			if (Properties.Settings.Default.DisplayConnectOnStartup)
   196 			{
   197 				OpenDisplayConnection();
   198 			}
   199 
   200 			//Start our server so that we can get client requests
   201 			StartServer();
   202 
   203 			//Register for HID events
   204 			RegisterHidDevices();
   205         }
   206 
   207 		/// <summary>
   208 		/// Called when our display is opened.
   209 		/// </summary>
   210 		/// <param name="aDisplay"></param>
   211 		private void OnDisplayOpened(Display aDisplay)
   212 		{
   213 			//Make sure we resume frame rendering
   214 			iSkipFrameRendering = false;
   215 
   216 			//Set our screen size now that our display is connected
   217 			//Our panelDisplay is the container of our tableLayoutPanel
   218 			//tableLayoutPanel will resize itself to fit the client size of our panelDisplay
   219 			//panelDisplay needs an extra 2 pixels for borders on each sides
   220 			//tableLayoutPanel will eventually be the exact size of our display
   221 			Size size = new Size(iDisplay.WidthInPixels() + 2, iDisplay.HeightInPixels() + 2);
   222 			panelDisplay.Size = size;
   223 
   224 			//Our display was just opened, update our UI
   225 			UpdateStatus();
   226 			//Initiate asynchronous request
   227 			iDisplay.RequestFirmwareRevision();
   228 
   229 			//Audio
   230 			UpdateMasterVolumeThreadSafe();
   231 			//Network
   232 			UpdateNetworkStatus();
   233 
   234 #if DEBUG
   235 			//Testing icon in debug, no arm done if icon not supported
   236 			//iDisplay.SetIconStatus(Display.TMiniDisplayIconType.EMiniDisplayIconRecording, 0, 1);
   237 			//iDisplay.SetAllIconsStatus(2);
   238 #endif
   239 
   240 		}
   241 
   242 		/// <summary>
   243 		/// Called when our display is closed.
   244 		/// </summary>
   245 		/// <param name="aDisplay"></param>
   246 		private void OnDisplayClosed(Display aDisplay)
   247 		{
   248 			//Our display was just closed, update our UI consequently
   249 			UpdateStatus();
   250 		}
   251 
   252 		public void OnConnectivityChanged(NetworkManager aNetwork, NLM_CONNECTIVITY newConnectivity)
   253 		{
   254 			//Update network status
   255 			UpdateNetworkStatus();			
   256 		}
   257 
   258 		/// <summary>
   259 		/// Update our Network Status
   260 		/// </summary>
   261 		private void UpdateNetworkStatus()
   262 		{
   263 			if (iDisplay.IsOpen())
   264 			{
   265                 iDisplay.SetIconOnOff(MiniDisplay.IconType.Internet, iNetworkManager.NetworkListManager.IsConnectedToInternet);
   266                 iDisplay.SetIconOnOff(MiniDisplay.IconType.NetworkSignal, iNetworkManager.NetworkListManager.IsConnected);
   267 			}
   268 		}
   269 
   270 
   271 		int iLastNetworkIconIndex = 0;
   272 		int iUpdateCountSinceLastNetworkAnimation = 0;
   273 
   274 		/// <summary>
   275 		/// 
   276 		/// </summary>
   277 		private void UpdateNetworkSignal(DateTime aLastTickTime, DateTime aNewTickTime)
   278 		{
   279 			iUpdateCountSinceLastNetworkAnimation++;
   280 			iUpdateCountSinceLastNetworkAnimation = iUpdateCountSinceLastNetworkAnimation % 4;
   281 
   282 			if (iDisplay.IsOpen() && iNetworkManager.NetworkListManager.IsConnected && iUpdateCountSinceLastNetworkAnimation==0)
   283 			{
   284                 int iconCount = iDisplay.IconCount(MiniDisplay.IconType.NetworkSignal);
   285 				if (iconCount <= 0)
   286 				{
   287 					//Prevents div by zero and other undefined behavior
   288 					return;
   289 				}
   290 				iLastNetworkIconIndex++;
   291 				iLastNetworkIconIndex = iLastNetworkIconIndex % (iconCount*2);
   292 				for (int i=0;i<iconCount;i++)
   293 				{
   294 					if (i < iLastNetworkIconIndex && !(i == 0 && iLastNetworkIconIndex > 3) && !(i == 1 && iLastNetworkIconIndex > 4))
   295 					{
   296                         iDisplay.SetIconOn(MiniDisplay.IconType.NetworkSignal, i);
   297 					}
   298 					else
   299 					{
   300                         iDisplay.SetIconOff(MiniDisplay.IconType.NetworkSignal, i);
   301 					}
   302 				}				
   303 			}
   304 		}
   305 
   306 
   307 
   308         /// <summary>
   309         /// Receive volume change notification and reflect changes on our slider.
   310         /// </summary>
   311         /// <param name="data"></param>
   312         public void OnVolumeNotificationThreadSafe(AudioVolumeNotificationData data)
   313         {
   314 			UpdateMasterVolumeThreadSafe();
   315         }
   316 
   317         /// <summary>
   318         /// Update master volume when user moves our slider.
   319         /// </summary>
   320         /// <param name="sender"></param>
   321         /// <param name="e"></param>
   322         private void trackBarMasterVolume_Scroll(object sender, EventArgs e)
   323         {
   324 			//Just like Windows Volume Mixer we unmute if the volume is adjusted
   325 			iMultiMediaDevice.AudioEndpointVolume.Mute = false;
   326 			//Set volume level according to our volume slider new position
   327 			iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value / 100.0f;
   328         }
   329 
   330 
   331 		/// <summary>
   332 		/// Mute check box changed.
   333 		/// </summary>
   334 		/// <param name="sender"></param>
   335 		/// <param name="e"></param>
   336 		private void checkBoxMute_CheckedChanged(object sender, EventArgs e)
   337 		{
   338 			iMultiMediaDevice.AudioEndpointVolume.Mute = checkBoxMute.Checked;
   339 		}
   340 
   341         /// <summary>
   342         /// Device State Changed
   343         /// </summary>
   344         public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, [MarshalAs(UnmanagedType.I4)] DeviceState newState){}
   345 
   346         /// <summary>
   347         /// Device Added
   348         /// </summary>
   349         public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) { }
   350 
   351         /// <summary>
   352         /// Device Removed
   353         /// </summary>
   354         public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) { }
   355 
   356         /// <summary>
   357         /// Default Device Changed
   358         /// </summary>
   359         public void OnDefaultDeviceChanged(DataFlow flow, Role role, [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId)
   360         {
   361             if (role == Role.Multimedia && flow == DataFlow.Render)
   362             {
   363                 UpdateAudioDeviceAndMasterVolumeThreadSafe();
   364             }
   365         }
   366 
   367         /// <summary>
   368         /// Property Value Changed
   369         /// </summary>
   370         /// <param name="pwstrDeviceId"></param>
   371         /// <param name="key"></param>
   372         public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key){}
   373 
   374 
   375         
   376 
   377 		/// <summary>
   378 		/// Update master volume indicators based our current system states.
   379 		/// This typically includes volume levels and mute status.
   380 		/// </summary>
   381 		private void UpdateMasterVolumeThreadSafe()
   382 		{
   383 			if (this.InvokeRequired)
   384 			{
   385 				//Not in the proper thread, invoke ourselves
   386 				PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe);
   387 				this.Invoke(d, new object[] { });
   388 				return;
   389 			}
   390 
   391 			//Update volume slider
   392 			float volumeLevelScalar = iMultiMediaDevice.AudioEndpointVolume.MasterVolumeLevelScalar;
   393 			trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar * 100);
   394 			//Update mute checkbox
   395 			checkBoxMute.Checked = iMultiMediaDevice.AudioEndpointVolume.Mute;
   396 
   397 			//If our display connection is open we need to update its icons
   398 			if (iDisplay.IsOpen())
   399 			{
   400 				//First take care our our volume level icons
   401                 int volumeIconCount = iDisplay.IconCount(MiniDisplay.IconType.Volume);
   402 				if (volumeIconCount > 0)
   403 				{					
   404 					//Compute current volume level from system level and the number of segments in our display volume bar.
   405 					//That tells us how many segments in our volume bar needs to be turned on.
   406 					float currentVolume = volumeLevelScalar * volumeIconCount;
   407 					int segmentOnCount = Convert.ToInt32(currentVolume);
   408 					//Check if our segment count was rounded up, this will later be used for half brightness segment
   409 					bool roundedUp = segmentOnCount > currentVolume;
   410 
   411 					for (int i = 0; i < volumeIconCount; i++)
   412 					{
   413 						if (i < segmentOnCount)
   414 						{
   415 							//If we are dealing with our last segment and our segment count was rounded up then we will use half brightness.
   416 							if (i == segmentOnCount - 1 && roundedUp)
   417 							{
   418 								//Half brightness
   419                                 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, (iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1) / 2);
   420 							}
   421 							else
   422 							{
   423 								//Full brightness
   424                                 iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, iDisplay.IconStatusCount(MiniDisplay.IconType.Volume) - 1);
   425 							}
   426 						}
   427 						else
   428 						{
   429                             iDisplay.SetIconStatus(MiniDisplay.IconType.Volume, i, 0);
   430 						}
   431 					}
   432 				}
   433 
   434 				//Take care our our mute icon
   435                 iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iMultiMediaDevice.AudioEndpointVolume.Mute);
   436 			}
   437 
   438 		}
   439 
   440         /// <summary>
   441         /// 
   442         /// </summary>
   443         private void UpdateAudioDeviceAndMasterVolumeThreadSafe()
   444         {
   445             if (this.InvokeRequired)
   446             {
   447                 //Not in the proper thread, invoke ourselves
   448 				PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe);
   449                 this.Invoke(d, new object[] { });
   450                 return;
   451             }
   452             
   453             //We are in the correct thread just go ahead.
   454             try
   455             {                
   456                 //Get our master volume            
   457 				iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
   458 				//Update our label
   459 				labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName;
   460 
   461                 //Show our volume in our track bar
   462 				UpdateMasterVolumeThreadSafe();
   463 
   464                 //Register to get volume modifications
   465 				iMultiMediaDevice.AudioEndpointVolume.OnVolumeNotification += OnVolumeNotificationThreadSafe;
   466                 //
   467 				trackBarMasterVolume.Enabled = true;
   468             }
   469             catch (Exception ex)
   470             {
   471                 Debug.WriteLine("Exception thrown in UpdateAudioDeviceAndMasterVolume");
   472                 Debug.WriteLine(ex.ToString());
   473                 //Something went wrong S/PDIF device ca throw exception I guess
   474 				trackBarMasterVolume.Enabled = false;
   475             }
   476         }
   477 
   478 		/// <summary>
   479 		/// 
   480 		/// </summary>
   481 		private void PopulateDeviceTypes()
   482 		{
   483 			int count = Display.TypeCount();
   484 
   485 			for (int i = 0; i < count; i++)
   486 			{
   487 				comboBoxDisplayType.Items.Add(Display.TypeName((MiniDisplay.Type)i));
   488 			}
   489 		}
   490 
   491         /// <summary>
   492         /// 
   493         /// </summary>
   494         private void PopulateOpticalDrives()
   495         {
   496             //Reset our list of drives
   497             comboBoxOpticalDrives.Items.Clear();
   498             comboBoxOpticalDrives.Items.Add("None");
   499 
   500             //Go through each drives on our system and collected the optical ones in our list
   501             DriveInfo[] allDrives = DriveInfo.GetDrives();
   502             foreach (DriveInfo d in allDrives)
   503             {
   504                 Debug.WriteLine("Drive " + d.Name);
   505                 Debug.WriteLine("  Drive type: {0}", d.DriveType);
   506 
   507                 if (d.DriveType==DriveType.CDRom)
   508                 {
   509                     //This is an optical drive, add it now
   510                     comboBoxOpticalDrives.Items.Add(d.Name.Substring(0,2));
   511                 }                
   512             }           
   513         }
   514 
   515         /// <summary>
   516         /// 
   517         /// </summary>
   518         /// <returns></returns>
   519         public string OpticalDriveToEject()
   520         {
   521             return comboBoxOpticalDrives.SelectedItem.ToString();
   522         }
   523 
   524 
   525 
   526 		/// <summary>
   527 		///
   528 		/// </summary>
   529 		private void SetupTrayIcon()
   530 		{
   531 			iNotifyIcon.Icon = GetIcon("vfd.ico");
   532 			iNotifyIcon.Text = "Sharp Display Manager";
   533 			iNotifyIcon.Visible = true;
   534 
   535 			//Double click toggles visibility - typically brings up the application
   536 			iNotifyIcon.DoubleClick += delegate(object obj, EventArgs args)
   537 			{
   538 				SysTrayHideShow();
   539 			};
   540 
   541 			//Adding a context menu, useful to be able to exit the application
   542 			ContextMenu contextMenu = new ContextMenu();
   543 			//Context menu item to toggle visibility
   544 			MenuItem hideShowItem = new MenuItem("Hide/Show");
   545 			hideShowItem.Click += delegate(object obj, EventArgs args)
   546 			{
   547 				SysTrayHideShow();
   548 			};
   549 			contextMenu.MenuItems.Add(hideShowItem);
   550 
   551 			//Context menu item separator
   552 			contextMenu.MenuItems.Add(new MenuItem("-"));
   553 
   554 			//Context menu exit item
   555 			MenuItem exitItem = new MenuItem("Exit");
   556 			exitItem.Click += delegate(object obj, EventArgs args)
   557 			{
   558 				Application.Exit();
   559 			};
   560 			contextMenu.MenuItems.Add(exitItem);
   561 
   562 			iNotifyIcon.ContextMenu = contextMenu;
   563 		}
   564 
   565 		/// <summary>
   566 		/// Access icons from embedded resources.
   567 		/// </summary>
   568 		/// <param name="name"></param>
   569 		/// <returns></returns>
   570 		public static Icon GetIcon(string name)
   571 		{
   572 			name = "SharpDisplayManager.Resources." + name;
   573 
   574 			string[] names =
   575 			  Assembly.GetExecutingAssembly().GetManifestResourceNames();
   576 			for (int i = 0; i < names.Length; i++)
   577 			{
   578 				if (names[i].Replace('\\', '.') == name)
   579 				{
   580 					using (Stream stream = Assembly.GetExecutingAssembly().
   581 					  GetManifestResourceStream(names[i]))
   582 					{
   583 						return new Icon(stream);
   584 					}
   585 				}
   586 			}
   587 
   588 			return null;
   589 		}
   590 
   591 
   592         /// <summary>
   593         /// Set our current client.
   594         /// This will take care of applying our client layout and set data fields.
   595         /// </summary>
   596         /// <param name="aSessionId"></param>
   597         void SetCurrentClient(string aSessionId, bool aForce=false)
   598         {
   599             if (aSessionId == iCurrentClientSessionId)
   600             {
   601                 //Given client is already the current one.
   602                 //Don't bother changing anything then.
   603                 return;
   604             }
   605 
   606 
   607             //Check when was the last time we switched to that client
   608             if (iCurrentClientData != null)
   609             {
   610                 double lastSwitchToClientSecondsAgo = (DateTime.Now - iCurrentClientData.LastSwitchTime).TotalSeconds;
   611                 //TODO: put that hard coded value as a client property
   612                 //Clients should be able to define how often they can be interrupted
   613                 //Thus a background client can set this to zero allowing any other client to interrupt at any time
   614                 //We could also compute this delay by looking at the requests frequencies?
   615                 if (!aForce && (lastSwitchToClientSecondsAgo < 30)) //Make sure a client is on for at least 30 seconds
   616                 {
   617                     //Don't switch clients too often
   618                     return;
   619                 }
   620             }
   621 
   622             //Set current client ID.
   623             iCurrentClientSessionId = aSessionId;
   624             //Set the time we last switched to that client
   625             iClients[aSessionId].LastSwitchTime = DateTime.Now;
   626             //Fetch and set current client data.
   627             iCurrentClientData = iClients[aSessionId];
   628             //Apply layout and set data fields.
   629             UpdateTableLayoutPanel(iCurrentClientData);
   630         }
   631 
   632         private void buttonFont_Click(object sender, EventArgs e)
   633         {
   634             //fontDialog.ShowColor = true;
   635             //fontDialog.ShowApply = true;
   636             fontDialog.ShowEffects = true;
   637             fontDialog.Font = cds.Font;
   638 
   639             fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
   640 
   641             //fontDialog.ShowHelp = true;
   642 
   643             //fontDlg.MaxSize = 40;
   644             //fontDlg.MinSize = 22;
   645 
   646             //fontDialog.Parent = this;
   647             //fontDialog.StartPosition = FormStartPosition.CenterParent;
   648 
   649             //DlgBox.ShowDialog(fontDialog);
   650 
   651             //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
   652             if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
   653             {
   654                 //Set the fonts to all our labels in our layout
   655                 foreach (Control ctrl in tableLayoutPanel.Controls)
   656                 {
   657                     if (ctrl is MarqueeLabel)
   658                     {
   659                         ((MarqueeLabel)ctrl).Font = fontDialog.Font;
   660                     }
   661                 }
   662 
   663                 //Save font settings
   664                 cds.Font = fontDialog.Font;
   665                 Properties.Settings.Default.Save();
   666                 //
   667                 CheckFontHeight();
   668             }
   669         }
   670 
   671         /// <summary>
   672         ///
   673         /// </summary>
   674         void CheckFontHeight()
   675         {
   676             //Show font height and width
   677             labelFontHeight.Text = "Font height: " + cds.Font.Height;
   678             float charWidth = IsFixedWidth(cds.Font);
   679             if (charWidth == 0.0f)
   680             {
   681                 labelFontWidth.Visible = false;
   682             }
   683             else
   684             {
   685                 labelFontWidth.Visible = true;
   686                 labelFontWidth.Text = "Font width: " + charWidth;
   687             }
   688 
   689             MarqueeLabel label = null;
   690             //Get the first label control we can find
   691             foreach (Control ctrl in tableLayoutPanel.Controls)
   692             {
   693                 if (ctrl is MarqueeLabel)
   694                 {
   695                     label = (MarqueeLabel)ctrl;
   696                     break;
   697                 }
   698             }
   699 
   700             //Now check font height and show a warning if needed.
   701             if (label != null && label.Font.Height > label.Height)
   702             {
   703                 labelWarning.Text = "WARNING: Selected font is too height by " + (label.Font.Height - label.Height) + " pixels!";
   704                 labelWarning.Visible = true;
   705             }
   706             else
   707             {
   708                 labelWarning.Visible = false;
   709             }
   710 
   711         }
   712 
   713         private void buttonCapture_Click(object sender, EventArgs e)
   714         {
   715             System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
   716             tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
   717             //Bitmap bmpToSave = new Bitmap(bmp);
   718             bmp.Save("D:\\capture.png");
   719 
   720             ((MarqueeLabel)tableLayoutPanel.Controls[0]).Text = "Captured";
   721 
   722             /*
   723             string outputFileName = "d:\\capture.png";
   724             using (MemoryStream memory = new MemoryStream())
   725             {
   726                 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
   727                 {
   728                     bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
   729                     byte[] bytes = memory.ToArray();
   730                     fs.Write(bytes, 0, bytes.Length);
   731                 }
   732             }
   733              */
   734 
   735         }
   736 
   737         private void CheckForRequestResults()
   738         {
   739             if (iDisplay.IsRequestPending())
   740             {
   741                 switch (iDisplay.AttemptRequestCompletion())
   742                 {
   743                     case MiniDisplay.Request.FirmwareRevision:
   744                         toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
   745                         //Issue next request then
   746                         iDisplay.RequestPowerSupplyStatus();
   747                         break;
   748 
   749                     case MiniDisplay.Request.PowerSupplyStatus:
   750                         if (iDisplay.PowerSupplyStatus())
   751                         {
   752                             toolStripStatusLabelPower.Text = "ON";
   753                         }
   754                         else
   755                         {
   756                             toolStripStatusLabelPower.Text = "OFF";
   757                         }
   758                         //Issue next request then
   759                         iDisplay.RequestDeviceId();
   760                         break;
   761 
   762                     case MiniDisplay.Request.DeviceId:
   763                         toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
   764                         //No more request to issue
   765                         break;
   766                 }
   767             }
   768         }
   769 
   770         public static uint ColorWhiteIsOn(int aX, int aY, uint aPixel)
   771         {
   772             if ((aPixel & 0x00FFFFFF) == 0x00FFFFFF)
   773             {
   774                 return 0xFFFFFFFF;
   775             }
   776             return 0x00000000;
   777         }
   778 
   779         public static uint ColorUntouched(int aX, int aY, uint aPixel)
   780         {
   781             return aPixel;
   782         }
   783 
   784         public static uint ColorInversed(int aX, int aY, uint aPixel)
   785         {
   786             return ~aPixel;
   787         }
   788 
   789         public static uint ColorChessboard(int aX, int aY, uint aPixel)
   790         {
   791             if ((aX % 2 == 0) && (aY % 2 == 0))
   792             {
   793                 return ~aPixel;
   794             }
   795             else if ((aX % 2 != 0) && (aY % 2 != 0))
   796             {
   797                 return ~aPixel;
   798             }
   799             return 0x00000000;
   800         }
   801 
   802 
   803         public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
   804         {
   805             return aBmp.Width - aX - 1;
   806         }
   807 
   808         public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
   809         {
   810             return iBmp.Height - aY - 1;
   811         }
   812 
   813         public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
   814         {
   815             return aX;
   816         }
   817 
   818         public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
   819         {
   820             return aY;
   821         }
   822 
   823         /// <summary>
   824         /// Select proper pixel delegates according to our current settings.
   825         /// </summary>
   826         private void SetupPixelDelegates()
   827         {
   828             //Select our pixel processing routine
   829             if (cds.InverseColors)
   830             {
   831                 //iColorFx = ColorChessboard;
   832                 iColorFx = ColorInversed;
   833             }
   834             else
   835             {
   836                 iColorFx = ColorWhiteIsOn;
   837             }
   838 
   839             //Select proper coordinate translation functions
   840             //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
   841             if (cds.ReverseScreen)
   842             {
   843                 iScreenX = ScreenReversedX;
   844                 iScreenY = ScreenReversedY;
   845             }
   846             else
   847             {
   848                 iScreenX = ScreenX;
   849                 iScreenY = ScreenY;
   850             }
   851 
   852         }
   853 
   854         //This is our timer tick responsible to perform our render
   855         private void timer_Tick(object sender, EventArgs e)
   856         {
   857             //Update our animations
   858             DateTime NewTickTime = DateTime.Now;
   859 
   860 			UpdateNetworkSignal(LastTickTime, NewTickTime);
   861 
   862             //Update animation for all our marquees
   863             foreach (Control ctrl in tableLayoutPanel.Controls)
   864             {
   865                 if (ctrl is MarqueeLabel)
   866                 {
   867                     ((MarqueeLabel)ctrl).UpdateAnimation(LastTickTime, NewTickTime);
   868                 }
   869             }
   870 
   871             //Update our display
   872             if (iDisplay.IsOpen())
   873             {
   874                 CheckForRequestResults();
   875 
   876 				//Check if frame rendering is needed
   877 				//Typically used when showing clock
   878 				if (!iSkipFrameRendering)
   879 				{
   880 					//Draw to bitmap
   881 					if (iCreateBitmap)
   882 					{
   883                         iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   884                         iCreateBitmap = false;
   885                     }
   886 					tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
   887 					//iBmp.Save("D:\\capture.png");
   888 
   889 					//Send it to our display
   890 					for (int i = 0; i < iBmp.Width; i++)
   891 					{
   892 						for (int j = 0; j < iBmp.Height; j++)
   893 						{
   894 							unchecked
   895 							{
   896 								//Get our processed pixel coordinates
   897 								int x = iScreenX(iBmp, i);
   898 								int y = iScreenY(iBmp, j);
   899 								//Get pixel color
   900 								uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
   901 								//Apply color effects
   902 								color = iColorFx(x, y, color);
   903 								//Now set our pixel
   904 								iDisplay.SetPixel(x, y, color);
   905 							}
   906 						}
   907 					}
   908 
   909 					iDisplay.SwapBuffers();
   910 				}
   911             }
   912 
   913             //Compute instant FPS
   914             toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
   915 
   916             LastTickTime = NewTickTime;
   917 
   918         }
   919 
   920 		/// <summary>
   921 		/// Attempt to establish connection with our display hardware.
   922 		/// </summary>
   923         private void OpenDisplayConnection()
   924         {
   925             CloseDisplayConnection();
   926 
   927             if (!iDisplay.Open((MiniDisplay.Type)cds.DisplayType))
   928             {   
   929 				UpdateStatus();               
   930 				toolStripStatusLabelConnect.Text = "Connection error";
   931             }
   932         }
   933 
   934         private void CloseDisplayConnection()
   935         {
   936 			//Status will be updated upon receiving the closed event
   937 
   938 			if (iDisplay == null || !iDisplay.IsOpen())
   939 			{
   940 				return;
   941 			}
   942 
   943 			//Do not clear if we gave up on rendering already.
   944 			//This means we will keep on displaying clock on MDM166AA for instance.
   945 			if (!iSkipFrameRendering)
   946 			{
   947 				iDisplay.Clear();
   948 				iDisplay.SwapBuffers();
   949 			}
   950 
   951 			iDisplay.SetAllIconsStatus(0); //Turn off all icons
   952             iDisplay.Close();
   953         }
   954 
   955         private void buttonOpen_Click(object sender, EventArgs e)
   956         {
   957             OpenDisplayConnection();
   958         }
   959 
   960         private void buttonClose_Click(object sender, EventArgs e)
   961         {
   962             CloseDisplayConnection();
   963         }
   964 
   965         private void buttonClear_Click(object sender, EventArgs e)
   966         {
   967             iDisplay.Clear();
   968             iDisplay.SwapBuffers();
   969         }
   970 
   971         private void buttonFill_Click(object sender, EventArgs e)
   972         {
   973             iDisplay.Fill();
   974             iDisplay.SwapBuffers();
   975         }
   976 
   977         private void trackBarBrightness_Scroll(object sender, EventArgs e)
   978         {
   979             cds.Brightness = trackBarBrightness.Value;
   980             Properties.Settings.Default.Save();
   981             iDisplay.SetBrightness(trackBarBrightness.Value);
   982 
   983         }
   984 
   985 
   986         /// <summary>
   987         /// CDS stands for Current Display Settings
   988         /// </summary>
   989         private DisplaySettings cds
   990         {
   991             get
   992             {
   993                 DisplaysSettings settings = Properties.Settings.Default.DisplaysSettings;
   994                 if (settings == null)
   995                 {
   996                     settings = new DisplaysSettings();
   997                     settings.Init();
   998                     Properties.Settings.Default.DisplaysSettings = settings;
   999                 }
  1000 
  1001                 //Make sure all our settings have been created
  1002                 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
  1003                 {
  1004                     settings.Displays.Add(new DisplaySettings());
  1005                 }
  1006 
  1007                 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
  1008                 return displaySettings;
  1009             }
  1010         }
  1011 
  1012         /// <summary>
  1013         /// Check if the given font has a fixed character pitch.
  1014         /// </summary>
  1015         /// <param name="ft"></param>
  1016         /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
  1017         public float IsFixedWidth(Font ft)
  1018         {
  1019             Graphics g = CreateGraphics();
  1020             char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
  1021             float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
  1022 
  1023             bool fixedWidth = true;
  1024 
  1025             foreach (char c in charSizes)
  1026                 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
  1027                     fixedWidth = false;
  1028 
  1029             if (fixedWidth)
  1030             {
  1031                 return charWidth;
  1032             }
  1033 
  1034             return 0.0f;
  1035         }
  1036 
  1037 		/// <summary>
  1038 		/// Synchronize UI with settings
  1039 		/// </summary>
  1040         private void UpdateStatus()
  1041         {            
  1042             //Load settings
  1043             checkBoxShowBorders.Checked = cds.ShowBorders;
  1044             tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
  1045 
  1046             //Set the proper font to each of our labels
  1047             foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
  1048             {
  1049                 ctrl.Font = cds.Font;
  1050             }
  1051 
  1052             CheckFontHeight();
  1053 			//Check if "run on Windows startup" is enabled
  1054 			checkBoxAutoStart.Checked = iStartupManager.Startup;
  1055 			//
  1056             checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
  1057 			checkBoxMinimizeToTray.Checked = Properties.Settings.Default.MinimizeToTray;
  1058 			checkBoxStartMinimized.Checked = Properties.Settings.Default.StartMinimized;
  1059 			labelStartFileName.Text = Properties.Settings.Default.StartFileName;
  1060 
  1061             //Try find our drive in our drive list
  1062             int opticalDriveItemIndex=0;
  1063             bool driveNotFound = true;
  1064             string opticalDriveToEject=Properties.Settings.Default.OpticalDriveToEject;
  1065             foreach (object item in comboBoxOpticalDrives.Items)
  1066             {
  1067                 if (opticalDriveToEject == item.ToString())
  1068                 {
  1069                     comboBoxOpticalDrives.SelectedIndex = opticalDriveItemIndex;
  1070                     driveNotFound = false;
  1071                     break;
  1072                 }
  1073                 opticalDriveItemIndex++;
  1074             }
  1075 
  1076             if (driveNotFound)
  1077             {
  1078                 //We could not find the drive we had saved.
  1079                 //Select "None" then.
  1080                 comboBoxOpticalDrives.SelectedIndex = 0;
  1081             }
  1082 
  1083 
  1084 
  1085             checkBoxReverseScreen.Checked = cds.ReverseScreen;
  1086             checkBoxInverseColors.Checked = cds.InverseColors;
  1087 			checkBoxShowVolumeLabel.Checked = cds.ShowVolumeLabel;
  1088             checkBoxScaleToFit.Checked = cds.ScaleToFit;
  1089             maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
  1090             labelMinFontSize.Enabled = cds.ScaleToFit;
  1091             maskedTextBoxMinFontSize.Text = cds.MinFontSize.ToString();
  1092 			maskedTextBoxScrollingSpeed.Text = cds.ScrollingSpeedInPixelsPerSecond.ToString();
  1093             comboBoxDisplayType.SelectedIndex = cds.DisplayType;
  1094             timer.Interval = cds.TimerInterval;
  1095             maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
  1096             textBoxScrollLoopSeparator.Text = cds.Separator;
  1097             //
  1098             SetupPixelDelegates();
  1099 
  1100             if (iDisplay.IsOpen())
  1101             {
  1102 				//We have a display connection
  1103 				//Reflect that in our UI
  1104 
  1105 				tableLayoutPanel.Enabled = true;
  1106 				panelDisplay.Enabled = true;
  1107 
  1108                 //Only setup brightness if display is open
  1109                 trackBarBrightness.Minimum = iDisplay.MinBrightness();
  1110                 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
  1111 				if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
  1112 				{
  1113 					//Brightness out of range, this can occur when using auto-detect
  1114 					//Use max brightness instead
  1115 					trackBarBrightness.Value = iDisplay.MaxBrightness();
  1116 					iDisplay.SetBrightness(iDisplay.MaxBrightness());
  1117 				}
  1118 				else
  1119 				{
  1120 					trackBarBrightness.Value = cds.Brightness;
  1121 					iDisplay.SetBrightness(cds.Brightness);
  1122 				}
  1123 
  1124 				//Try compute the steps to something that makes sense
  1125                 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
  1126                 trackBarBrightness.SmallChange = 1;
  1127                 
  1128                 //
  1129                 buttonFill.Enabled = true;
  1130                 buttonClear.Enabled = true;
  1131                 buttonOpen.Enabled = false;
  1132                 buttonClose.Enabled = true;
  1133                 trackBarBrightness.Enabled = true;
  1134                 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
  1135                 //+ " - " + iDisplay.SerialNumber();
  1136 
  1137                 if (iDisplay.SupportPowerOnOff())
  1138                 {
  1139                     buttonPowerOn.Enabled = true;
  1140                     buttonPowerOff.Enabled = true;
  1141                 }
  1142                 else
  1143                 {
  1144                     buttonPowerOn.Enabled = false;
  1145                     buttonPowerOff.Enabled = false;
  1146                 }
  1147 
  1148                 if (iDisplay.SupportClock())
  1149                 {
  1150                     buttonShowClock.Enabled = true;
  1151                     buttonHideClock.Enabled = true;
  1152                 }
  1153                 else
  1154                 {
  1155                     buttonShowClock.Enabled = false;
  1156                     buttonHideClock.Enabled = false;
  1157                 }
  1158 
  1159 				
  1160 				//Check if Volume Label is supported. To date only MDM166AA supports that crap :)
  1161 				checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
  1162 
  1163 				if (cds.ShowVolumeLabel)
  1164 				{
  1165                     iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
  1166 				}
  1167 				else
  1168 				{
  1169                     iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
  1170 				}
  1171             }
  1172             else
  1173             {
  1174 				//Display is connection not available
  1175 				//Reflect that in our UI
  1176 				checkBoxShowVolumeLabel.Enabled = false;
  1177 				tableLayoutPanel.Enabled = false;
  1178 				panelDisplay.Enabled = false;
  1179                 buttonFill.Enabled = false;
  1180                 buttonClear.Enabled = false;
  1181                 buttonOpen.Enabled = true;
  1182                 buttonClose.Enabled = false;
  1183                 trackBarBrightness.Enabled = false;
  1184                 buttonPowerOn.Enabled = false;
  1185                 buttonPowerOff.Enabled = false;
  1186                 buttonShowClock.Enabled = false;
  1187                 buttonHideClock.Enabled = false;
  1188                 toolStripStatusLabelConnect.Text = "Disconnected";
  1189                 toolStripStatusLabelPower.Text = "N/A";
  1190             }
  1191 
  1192         }
  1193 
  1194 
  1195 		/// <summary>
  1196 		/// 
  1197 		/// </summary>
  1198 		/// <param name="sender"></param>
  1199 		/// <param name="e"></param>
  1200 		private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
  1201 		{
  1202 			cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
  1203 			Properties.Settings.Default.Save();
  1204 			UpdateStatus();
  1205 		}
  1206 
  1207         private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
  1208         {
  1209             //Save our show borders setting
  1210             tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
  1211             cds.ShowBorders = checkBoxShowBorders.Checked;
  1212             Properties.Settings.Default.Save();
  1213             CheckFontHeight();
  1214         }
  1215 
  1216         private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
  1217         {
  1218             //Save our connect on startup setting
  1219             Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
  1220             Properties.Settings.Default.Save();
  1221         }
  1222 
  1223 		private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
  1224 		{
  1225 			//Save our "Minimize to tray" setting
  1226 			Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
  1227 			Properties.Settings.Default.Save();
  1228 
  1229 		}
  1230 
  1231 		private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
  1232 		{
  1233 			//Save our "Start minimized" setting
  1234 			Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
  1235 			Properties.Settings.Default.Save();
  1236 		}
  1237 
  1238 		private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
  1239 		{
  1240 			iStartupManager.Startup = checkBoxAutoStart.Checked;
  1241 		}
  1242 
  1243 
  1244         private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
  1245         {
  1246             //Save our reverse screen setting
  1247             cds.ReverseScreen = checkBoxReverseScreen.Checked;
  1248             Properties.Settings.Default.Save();
  1249             SetupPixelDelegates();
  1250         }
  1251 
  1252         private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
  1253         {
  1254             //Save our inverse colors setting
  1255             cds.InverseColors = checkBoxInverseColors.Checked;
  1256             Properties.Settings.Default.Save();
  1257             SetupPixelDelegates();
  1258         }
  1259 
  1260         private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
  1261         {
  1262             //Save our scale to fit setting
  1263             cds.ScaleToFit = checkBoxScaleToFit.Checked;
  1264             Properties.Settings.Default.Save();
  1265             //
  1266             labelMinFontSize.Enabled = cds.ScaleToFit;
  1267             maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
  1268         }
  1269 
  1270         private void MainForm_Resize(object sender, EventArgs e)
  1271         {
  1272             if (WindowState == FormWindowState.Minimized)
  1273             {
  1274                 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
  1275                 // That's apparently not needed on Windows 10 but we better leave it in place.
  1276                 iCreateBitmap = true;
  1277             }
  1278         }
  1279 
  1280         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
  1281         {
  1282 			iNetworkManager.Dispose();
  1283 			CloseDisplayConnection();
  1284             StopServer();
  1285             e.Cancel = iClosing;
  1286         }
  1287 
  1288         public void StartServer()
  1289         {
  1290             iServiceHost = new ServiceHost
  1291                 (
  1292                     typeof(Session),
  1293                     new Uri[] { new Uri("net.tcp://localhost:8001/") }
  1294                 );
  1295 
  1296             iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
  1297             iServiceHost.Open();
  1298         }
  1299 
  1300         public void StopServer()
  1301         {
  1302             if (iClients.Count > 0 && !iClosing)
  1303             {
  1304                 //Tell our clients
  1305                 iClosing = true;
  1306                 BroadcastCloseEvent();
  1307             }
  1308             else if (iClosing)
  1309             {
  1310                 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
  1311                 {
  1312                     iClosing = false; //We make sure we force close if asked twice
  1313                 }
  1314             }
  1315             else
  1316             {
  1317                 //We removed that as it often lags for some reason
  1318                 //iServiceHost.Close();
  1319             }
  1320         }
  1321 
  1322         public void BroadcastCloseEvent()
  1323         {
  1324             Trace.TraceInformation("BroadcastCloseEvent - start");
  1325 
  1326             var inactiveClients = new List<string>();
  1327             foreach (var client in iClients)
  1328             {
  1329                 //if (client.Key != eventData.ClientName)
  1330                 {
  1331                     try
  1332                     {
  1333                         Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
  1334                         client.Value.Callback.OnCloseOrder(/*eventData*/);
  1335                     }
  1336                     catch (Exception ex)
  1337                     {
  1338                         inactiveClients.Add(client.Key);
  1339                     }
  1340                 }
  1341             }
  1342 
  1343             if (inactiveClients.Count > 0)
  1344             {
  1345                 foreach (var client in inactiveClients)
  1346                 {
  1347                     iClients.Remove(client);
  1348                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
  1349                 }
  1350             }
  1351 
  1352 			if (iClients.Count==0)
  1353 			{
  1354 				ClearLayout();
  1355 			}
  1356         }
  1357 
  1358 		/// <summary>
  1359 		/// Just remove all our fields.
  1360 		/// </summary>
  1361 		private void ClearLayout()
  1362 		{
  1363 			tableLayoutPanel.Controls.Clear();
  1364 			tableLayoutPanel.RowStyles.Clear();
  1365 			tableLayoutPanel.ColumnStyles.Clear();
  1366 			iCurrentClientData = null;
  1367 		}
  1368 
  1369 		/// <summary>
  1370 		/// Just launch a demo client.
  1371 		/// </summary>
  1372 		private void StartNewClient(string aTopText = "", string aBottomText = "")
  1373 		{
  1374 			Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
  1375 			SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
  1376 			clientThread.Start(myParams);
  1377 			BringToFront();
  1378 		}
  1379 
  1380         private void buttonStartClient_Click(object sender, EventArgs e)
  1381         {
  1382 			StartNewClient();
  1383         }
  1384 
  1385         private void buttonSuspend_Click(object sender, EventArgs e)
  1386         {
  1387             LastTickTime = DateTime.Now; //Reset timer to prevent jump
  1388             timer.Enabled = !timer.Enabled;
  1389             if (!timer.Enabled)
  1390             {
  1391                 buttonSuspend.Text = "Run";
  1392             }
  1393             else
  1394             {
  1395                 buttonSuspend.Text = "Pause";
  1396             }
  1397         }
  1398 
  1399         private void buttonCloseClients_Click(object sender, EventArgs e)
  1400         {
  1401             BroadcastCloseEvent();
  1402         }
  1403 
  1404         private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
  1405         {
  1406             //Root node must have at least one child
  1407             if (e.Node.Nodes.Count == 0)
  1408             {
  1409                 return;
  1410             }
  1411 
  1412             //If the selected node is the root node of a client then switch to it
  1413             string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
  1414             if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
  1415             {
  1416                 //We have a valid session just switch to that client
  1417                 SetCurrentClient(sessionId,true);
  1418             }
  1419             
  1420         }
  1421 
  1422 
  1423         /// <summary>
  1424         ///
  1425         /// </summary>
  1426         /// <param name="aSessionId"></param>
  1427         /// <param name="aCallback"></param>
  1428         public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
  1429         {
  1430             if (this.InvokeRequired)
  1431             {
  1432                 //Not in the proper thread, invoke ourselves
  1433                 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
  1434                 this.Invoke(d, new object[] { aSessionId, aCallback });
  1435             }
  1436             else
  1437             {
  1438                 //We are in the proper thread
  1439                 //Add this session to our collection of clients
  1440                 ClientData newClient = new ClientData(aSessionId, aCallback);
  1441                 Program.iMainForm.iClients.Add(aSessionId, newClient);
  1442                 //Add this session to our UI
  1443                 UpdateClientTreeViewNode(newClient);
  1444             }
  1445         }
  1446 
  1447         /// <summary>
  1448         ///
  1449         /// </summary>
  1450         /// <param name="aSessionId"></param>
  1451         public void RemoveClientThreadSafe(string aSessionId)
  1452         {
  1453             if (this.InvokeRequired)
  1454             {
  1455                 //Not in the proper thread, invoke ourselves
  1456                 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
  1457                 this.Invoke(d, new object[] { aSessionId });
  1458             }
  1459             else
  1460             {
  1461                 //We are in the proper thread
  1462                 //Remove this session from both client collection and UI tree view
  1463                 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
  1464                 {
  1465                     Program.iMainForm.iClients.Remove(aSessionId);
  1466                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
  1467                 }
  1468 
  1469 				if (iClients.Count == 0)
  1470 				{
  1471 					//Clear our screen when last client disconnects
  1472 					ClearLayout();
  1473 
  1474 					if (iClosing)
  1475 					{
  1476 						//We were closing our form
  1477 						//All clients are now closed
  1478 						//Just resume our close operation
  1479 						iClosing = false;
  1480 						Close();
  1481 					}
  1482 				}
  1483             }
  1484         }
  1485 
  1486         /// <summary>
  1487         ///
  1488         /// </summary>
  1489         /// <param name="aSessionId"></param>
  1490         /// <param name="aLayout"></param>
  1491         public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
  1492         {
  1493             if (this.InvokeRequired)
  1494             {
  1495                 //Not in the proper thread, invoke ourselves
  1496                 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
  1497                 this.Invoke(d, new object[] { aSessionId, aLayout });
  1498             }
  1499             else
  1500             {
  1501                 ClientData client = iClients[aSessionId];
  1502                 if (client != null)
  1503                 {
  1504                     //Don't change a thing if the layout is the same
  1505                     if (!client.Layout.IsSameAs(aLayout))
  1506                     {
  1507                         Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
  1508                         //Set our client layout then
  1509                         client.Layout = aLayout;
  1510                         //Layout has changed clear our fields then
  1511                         client.Fields.Clear();
  1512                         //
  1513                         UpdateClientTreeViewNode(client);
  1514                     }
  1515                     else
  1516                     {
  1517                         Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
  1518                     }
  1519                 }
  1520             }
  1521         }
  1522 
  1523         /// <summary>
  1524         ///
  1525         /// </summary>
  1526         /// <param name="aSessionId"></param>
  1527         /// <param name="aField"></param>
  1528         public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
  1529         {
  1530             if (this.InvokeRequired)
  1531             {
  1532                 //Not in the proper thread, invoke ourselves
  1533                 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
  1534                 this.Invoke(d, new object[] { aSessionId, aField });
  1535             }
  1536             else
  1537             {
  1538                 //We are in the proper thread
  1539                 //Call the non-thread-safe variant
  1540                 SetClientField(aSessionId, aField);
  1541             }
  1542         }
  1543 
  1544         /// <summary>
  1545         ///
  1546         /// </summary>
  1547         /// <param name="aSessionId"></param>
  1548         /// <param name="aField"></param>
  1549         private void SetClientField(string aSessionId, DataField aField)
  1550         {   
  1551             //TODO: should check if the field actually changed?
  1552 
  1553             ClientData client = iClients[aSessionId];
  1554             bool layoutChanged = false;
  1555             bool contentChanged = true;
  1556 
  1557             //Make sure all our fields are in place
  1558             while (client.Fields.Count < (aField.Index + 1))
  1559             {
  1560                 //Add a data field with proper index
  1561                 client.Fields.Add(new DataField(client.Fields.Count));
  1562                 layoutChanged = true;
  1563             }
  1564 
  1565             //Now that we know our fields are in place just update that one
  1566             client.Fields[aField.Index] = aField;
  1567 
  1568             if (client.Fields[aField.Index].IsSameLayout(aField))
  1569             {
  1570                 //If we are updating a field in our current client we need to update it in our panel
  1571                 if (aSessionId == iCurrentClientSessionId)
  1572                 {
  1573                     if (aField.IsText && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is MarqueeLabel)
  1574                     {
  1575                         //Text field control already in place, just change the text
  1576                         MarqueeLabel label = (MarqueeLabel)tableLayoutPanel.Controls[aField.Index];
  1577                         contentChanged = (label.Text != aField.Text || label.TextAlign != aField.Alignment);
  1578                         label.Text = aField.Text;
  1579                         label.TextAlign = aField.Alignment;
  1580                     }
  1581                     else if (aField.IsBitmap && aField.Index < tableLayoutPanel.Controls.Count && tableLayoutPanel.Controls[aField.Index] is PictureBox)
  1582                     {
  1583                         contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
  1584                         //Bitmap field control already in place just change the bitmap
  1585                         PictureBox pictureBox = (PictureBox)tableLayoutPanel.Controls[aField.Index];
  1586                         pictureBox.Image = aField.Bitmap;
  1587                     }
  1588                     else
  1589                     {
  1590                         layoutChanged = true;
  1591                     }
  1592                 }
  1593             }
  1594             else
  1595             {                
  1596                 layoutChanged = true;
  1597             }
  1598 
  1599             //If either content or layout changed we need to update our tree view to reflect the changes
  1600             if (contentChanged || layoutChanged)
  1601             {
  1602                 UpdateClientTreeViewNode(client);
  1603                 //
  1604                 if (layoutChanged)
  1605                 {
  1606                     Debug.Print("Layout changed");
  1607                     //Our layout has changed, if we are already the current client we need to update our panel
  1608                     if (aSessionId == iCurrentClientSessionId)
  1609                     {
  1610                         //Apply layout and set data fields.
  1611                         UpdateTableLayoutPanel(iCurrentClientData);
  1612                     }
  1613                 }
  1614                 else
  1615                 {
  1616                     Debug.Print("Layout has not changed.");
  1617                 }
  1618             }
  1619             else
  1620             {
  1621                 Debug.Print("WARNING: content and layout have not changed!");
  1622             }
  1623 
  1624             //When a client field is set we try switching to this client to present the new information to our user
  1625             SetCurrentClient(aSessionId);
  1626         }
  1627 
  1628         /// <summary>
  1629         ///
  1630         /// </summary>
  1631         /// <param name="aTexts"></param>
  1632         public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
  1633         {
  1634             if (this.InvokeRequired)
  1635             {
  1636                 //Not in the proper thread, invoke ourselves
  1637                 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
  1638                 this.Invoke(d, new object[] { aSessionId, aFields });
  1639             }
  1640             else
  1641             {
  1642                 //Put each our text fields in a label control
  1643                 foreach (DataField field in aFields)
  1644                 {
  1645                     SetClientField(aSessionId, field);
  1646                 }
  1647             }
  1648         }
  1649 
  1650         /// <summary>
  1651         ///
  1652         /// </summary>
  1653         /// <param name="aSessionId"></param>
  1654         /// <param name="aName"></param>
  1655         public void SetClientNameThreadSafe(string aSessionId, string aName)
  1656         {
  1657             if (this.InvokeRequired)
  1658             {
  1659                 //Not in the proper thread, invoke ourselves
  1660                 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
  1661                 this.Invoke(d, new object[] { aSessionId, aName });
  1662             }
  1663             else
  1664             {
  1665                 //We are in the proper thread
  1666                 //Get our client
  1667                 ClientData client = iClients[aSessionId];
  1668                 if (client != null)
  1669                 {
  1670                     //Set its name
  1671                     client.Name = aName;
  1672                     //Update our tree-view
  1673                     UpdateClientTreeViewNode(client);
  1674                 }
  1675             }
  1676         }
  1677 
  1678         /// <summary>
  1679         ///
  1680         /// </summary>
  1681         /// <param name="aClient"></param>
  1682         private void UpdateClientTreeViewNode(ClientData aClient)
  1683         {
  1684             Debug.Print("UpdateClientTreeViewNode");
  1685 
  1686             if (aClient == null)
  1687             {
  1688                 return;
  1689             }
  1690 
  1691             TreeNode node = null;
  1692             //Check that our client node already exists
  1693             //Get our client root node using its key which is our session ID
  1694             TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
  1695             if (nodes.Count()>0)
  1696             {
  1697                 //We already have a node for that client
  1698                 node = nodes[0];
  1699                 //Clear children as we are going to recreate them below
  1700                 node.Nodes.Clear();
  1701             }
  1702             else
  1703             {
  1704                 //Client node does not exists create a new one
  1705                 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
  1706                 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
  1707             }
  1708 
  1709             if (node != null)
  1710             {
  1711                 //Change its name
  1712                 if (aClient.Name != "")
  1713                 {
  1714                     //We have a name, us it as text for our root node
  1715                     node.Text = aClient.Name;
  1716                     //Add a child with SessionId
  1717                     node.Nodes.Add(new TreeNode(aClient.SessionId));
  1718                 }
  1719                 else
  1720                 {
  1721                     //No name, use session ID instead
  1722                     node.Text = aClient.SessionId;
  1723                 }
  1724 
  1725                 if (aClient.Fields.Count > 0)
  1726                 {
  1727                     //Create root node for our texts
  1728                     TreeNode textsRoot = new TreeNode("Fields");
  1729                     node.Nodes.Add(textsRoot);
  1730                     //For each text add a new entry
  1731                     foreach (DataField field in aClient.Fields)
  1732                     {
  1733                         if (!field.IsBitmap)
  1734                         {
  1735                             DataField textField = (DataField)field;
  1736                             textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
  1737                         }
  1738                         else
  1739                         {
  1740                             textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
  1741                         }
  1742                     }
  1743                 }
  1744 
  1745                 node.ExpandAll();
  1746             }
  1747         }
  1748 
  1749         /// <summary>
  1750         /// Update our table layout row styles to make sure each rows have similar height
  1751         /// </summary>
  1752         private void UpdateTableLayoutRowStyles()
  1753         {
  1754             foreach (RowStyle rowStyle in tableLayoutPanel.RowStyles)
  1755             {
  1756                 rowStyle.SizeType = SizeType.Percent;
  1757                 rowStyle.Height = 100 / tableLayoutPanel.RowCount;
  1758             }
  1759         }
  1760 
  1761         /// <summary>
  1762         /// Update our display table layout.
  1763         /// </summary>
  1764         /// <param name="aLayout"></param>
  1765         private void UpdateTableLayoutPanel(ClientData aClient)
  1766         {
  1767             Debug.Print("UpdateClientTreeViewNode");
  1768 
  1769 			if (aClient == null)
  1770 			{
  1771 				//Just drop it
  1772 				return;
  1773 			}
  1774 
  1775 
  1776             TableLayout layout = aClient.Layout;
  1777             int fieldCount = 0;
  1778 
  1779             //First clean our current panel
  1780             tableLayoutPanel.Controls.Clear();
  1781             tableLayoutPanel.RowStyles.Clear();
  1782             tableLayoutPanel.ColumnStyles.Clear();
  1783             tableLayoutPanel.RowCount = 0;
  1784             tableLayoutPanel.ColumnCount = 0;
  1785 
  1786             while (tableLayoutPanel.RowCount < layout.Rows.Count)
  1787             {
  1788                 tableLayoutPanel.RowCount++;
  1789             }
  1790 
  1791             while (tableLayoutPanel.ColumnCount < layout.Columns.Count)
  1792             {
  1793                 tableLayoutPanel.ColumnCount++;
  1794             }
  1795 
  1796             for (int i = 0; i < tableLayoutPanel.ColumnCount; i++)
  1797             {
  1798                 //Create our column styles
  1799                 this.tableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
  1800 
  1801                 for (int j = 0; j < tableLayoutPanel.RowCount; j++)
  1802                 {
  1803                     if (i == 0)
  1804                     {
  1805                         //Create our row styles
  1806                         this.tableLayoutPanel.RowStyles.Add(layout.Rows[j]);
  1807                     }
  1808 
  1809                     //Check if we already have a control
  1810                     Control existingControl = tableLayoutPanel.GetControlFromPosition(i,j);
  1811                     if (existingControl!=null)
  1812                     {
  1813                         //We already have a control in that cell as a results of row/col spanning
  1814                         //Move on to next cell then
  1815                         continue;
  1816                     }
  1817 
  1818                     fieldCount++;
  1819 
  1820                     //Check if a client field already exists for that cell
  1821                     if (aClient.Fields.Count <= tableLayoutPanel.Controls.Count)
  1822                     {
  1823                         //No client field specified, create a text field by default
  1824                         aClient.Fields.Add(new DataField(aClient.Fields.Count));
  1825                     }
  1826 
  1827                     //Create a control corresponding to the field specified for that cell
  1828                     DataField field = aClient.Fields[tableLayoutPanel.Controls.Count];
  1829                     Control control = CreateControlForDataField(field);
  1830 
  1831                     //Add newly created control to our table layout at the specified row and column
  1832                     tableLayoutPanel.Controls.Add(control, i, j);
  1833                     //Make sure we specify row and column span for that new control
  1834                     tableLayoutPanel.SetRowSpan(control,field.RowSpan);
  1835                     tableLayoutPanel.SetColumnSpan(control, field.ColumnSpan);
  1836                 }
  1837             }
  1838 
  1839             //
  1840             while (aClient.Fields.Count > fieldCount)
  1841             {
  1842                 //We have too much fields for this layout
  1843                 //Just discard them until we get there
  1844                 aClient.Fields.RemoveAt(aClient.Fields.Count-1);
  1845             }
  1846 
  1847             CheckFontHeight();
  1848         }
  1849 
  1850         /// <summary>
  1851         /// Check our type of data field and create corresponding control
  1852         /// </summary>
  1853         /// <param name="aField"></param>
  1854         private Control CreateControlForDataField(DataField aField)
  1855         {
  1856             Control control=null;
  1857             if (!aField.IsBitmap)
  1858             {
  1859                 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
  1860                 label.AutoEllipsis = true;
  1861                 label.AutoSize = true;
  1862                 label.BackColor = System.Drawing.Color.Transparent;
  1863                 label.Dock = System.Windows.Forms.DockStyle.Fill;
  1864                 label.Location = new System.Drawing.Point(1, 1);
  1865                 label.Margin = new System.Windows.Forms.Padding(0);
  1866                 label.Name = "marqueeLabel" + aField.Index;
  1867                 label.OwnTimer = false;
  1868                 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
  1869                 label.Separator = cds.Separator;
  1870                 label.MinFontSize = cds.MinFontSize;
  1871                 label.ScaleToFit = cds.ScaleToFit;
  1872                 //control.Size = new System.Drawing.Size(254, 30);
  1873                 //control.TabIndex = 2;
  1874                 label.Font = cds.Font;
  1875 
  1876 				label.TextAlign = aField.Alignment;
  1877                 label.UseCompatibleTextRendering = true;
  1878                 label.Text = aField.Text;
  1879                 //
  1880                 control = label;
  1881             }
  1882             else
  1883             {
  1884                 //Create picture box
  1885                 PictureBox picture = new PictureBox();
  1886                 picture.AutoSize = true;
  1887                 picture.BackColor = System.Drawing.Color.Transparent;
  1888                 picture.Dock = System.Windows.Forms.DockStyle.Fill;
  1889                 picture.Location = new System.Drawing.Point(1, 1);
  1890                 picture.Margin = new System.Windows.Forms.Padding(0);
  1891                 picture.Name = "pictureBox" + aField;
  1892                 //Set our image
  1893                 picture.Image = aField.Bitmap;
  1894                 //
  1895                 control = picture;
  1896             }
  1897 
  1898             return control;
  1899         }
  1900 
  1901 		/// <summary>
  1902 		/// Called when the user selected a new display type.
  1903 		/// </summary>
  1904 		/// <param name="sender"></param>
  1905 		/// <param name="e"></param>
  1906         private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
  1907         {
  1908 			//Store the selected display type in our settings
  1909             Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
  1910             cds.DisplayType = comboBoxDisplayType.SelectedIndex;
  1911             Properties.Settings.Default.Save();
  1912 
  1913 			//Try re-opening the display connection if we were already connected.
  1914 			//Otherwise just update our status to reflect display type change.
  1915             if (iDisplay.IsOpen())
  1916             {
  1917                 OpenDisplayConnection();
  1918             }
  1919             else
  1920             {
  1921                 UpdateStatus();
  1922             }
  1923         }
  1924 
  1925         private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
  1926         {
  1927             if (maskedTextBoxTimerInterval.Text != "")
  1928             {
  1929                 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
  1930 
  1931                 if (interval > 0)
  1932                 {
  1933                     timer.Interval = interval;
  1934                     cds.TimerInterval = timer.Interval;
  1935                     Properties.Settings.Default.Save();
  1936                 }
  1937             }
  1938         }
  1939 
  1940         private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
  1941         {
  1942             if (maskedTextBoxMinFontSize.Text != "")
  1943             {
  1944                 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
  1945 
  1946                 if (minFontSize > 0)
  1947                 {
  1948                     cds.MinFontSize = minFontSize;
  1949                     Properties.Settings.Default.Save();
  1950 					//We need to recreate our layout for that change to take effect
  1951 					UpdateTableLayoutPanel(iCurrentClientData);
  1952                 }
  1953             }
  1954         }
  1955 
  1956 
  1957 		private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
  1958 		{
  1959 			if (maskedTextBoxScrollingSpeed.Text != "")
  1960 			{
  1961 				int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
  1962 
  1963 				if (scrollingSpeed > 0)
  1964 				{
  1965 					cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
  1966 					Properties.Settings.Default.Save();
  1967 					//We need to recreate our layout for that change to take effect
  1968 					UpdateTableLayoutPanel(iCurrentClientData);
  1969 				}
  1970 			}
  1971 		}
  1972 
  1973         private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
  1974         {
  1975             cds.Separator = textBoxScrollLoopSeparator.Text;
  1976             Properties.Settings.Default.Save();
  1977 
  1978 			//Update our text fields
  1979 			foreach (MarqueeLabel ctrl in tableLayoutPanel.Controls)
  1980 			{
  1981 				ctrl.Separator = cds.Separator;
  1982 			}
  1983 
  1984         }
  1985 
  1986         private void buttonPowerOn_Click(object sender, EventArgs e)
  1987         {
  1988             iDisplay.PowerOn();
  1989         }
  1990 
  1991         private void buttonPowerOff_Click(object sender, EventArgs e)
  1992         {
  1993             iDisplay.PowerOff();
  1994         }
  1995 
  1996         private void buttonShowClock_Click(object sender, EventArgs e)
  1997         {
  1998 			ShowClock();
  1999         }
  2000 
  2001         private void buttonHideClock_Click(object sender, EventArgs e)
  2002         {
  2003 			HideClock();
  2004         }
  2005 
  2006         private void buttonUpdate_Click(object sender, EventArgs e)
  2007         {
  2008             InstallUpdateSyncWithInfo();
  2009         }
  2010 
  2011 		/// <summary>
  2012 		/// 
  2013 		/// </summary>
  2014 		void ShowClock()
  2015 		{
  2016 			if (!iDisplay.IsOpen())
  2017 			{
  2018 				return;
  2019 			}
  2020 
  2021 			//Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2022 			iSkipFrameRendering = true;
  2023 			//Clear our screen 
  2024 			iDisplay.Clear();
  2025 			iDisplay.SwapBuffers();
  2026 			//Then show our clock
  2027 			iDisplay.ShowClock();
  2028 		}
  2029 
  2030 		/// <summary>
  2031 		/// 
  2032 		/// </summary>
  2033 		void HideClock()
  2034 		{
  2035 			if (!iDisplay.IsOpen())
  2036 			{
  2037 				return;
  2038 			}
  2039 
  2040 			//Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2041 			iSkipFrameRendering = false;
  2042 			iDisplay.HideClock();
  2043 		}
  2044 
  2045         private void InstallUpdateSyncWithInfo()
  2046         {
  2047             UpdateCheckInfo info = null;
  2048 
  2049             if (ApplicationDeployment.IsNetworkDeployed)
  2050             {
  2051                 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
  2052 
  2053                 try
  2054                 {
  2055                     info = ad.CheckForDetailedUpdate();
  2056 
  2057                 }
  2058                 catch (DeploymentDownloadException dde)
  2059                 {
  2060                     MessageBox.Show("The new version of the application cannot be downloaded at this time. \n\nPlease check your network connection, or try again later. Error: " + dde.Message);
  2061                     return;
  2062                 }
  2063                 catch (InvalidDeploymentException ide)
  2064                 {
  2065                     MessageBox.Show("Cannot check for a new version of the application. The ClickOnce deployment is corrupt. Please redeploy the application and try again. Error: " + ide.Message);
  2066                     return;
  2067                 }
  2068                 catch (InvalidOperationException ioe)
  2069                 {
  2070                     MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
  2071                     return;
  2072                 }
  2073 
  2074 				if (info.UpdateAvailable)
  2075 				{
  2076 					Boolean doUpdate = true;
  2077 
  2078 					if (!info.IsUpdateRequired)
  2079 					{
  2080 						DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
  2081 						if (!(DialogResult.OK == dr))
  2082 						{
  2083 							doUpdate = false;
  2084 						}
  2085 					}
  2086 					else
  2087 					{
  2088 						// Display a message that the app MUST reboot. Display the minimum required version.
  2089 						MessageBox.Show("This application has detected a mandatory update from your current " +
  2090 							"version to version " + info.MinimumRequiredVersion.ToString() +
  2091 							". The application will now install the update and restart.",
  2092 							"Update Available", MessageBoxButtons.OK,
  2093 							MessageBoxIcon.Information);
  2094 					}
  2095 
  2096 					if (doUpdate)
  2097 					{
  2098 						try
  2099 						{
  2100 							ad.Update();
  2101 							MessageBox.Show("The application has been upgraded, and will now restart.");
  2102 							Application.Restart();
  2103 						}
  2104 						catch (DeploymentDownloadException dde)
  2105 						{
  2106 							MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
  2107 							return;
  2108 						}
  2109 					}
  2110 				}
  2111 				else
  2112 				{
  2113 					MessageBox.Show("You are already running the latest version.", "Application up-to-date");
  2114 				}
  2115             }
  2116         }
  2117 
  2118 
  2119 		/// <summary>
  2120 		/// Used to
  2121 		/// </summary>
  2122 		private void SysTrayHideShow()
  2123 		{
  2124 			Visible = !Visible;
  2125 			if (Visible)
  2126 			{
  2127 				Activate();
  2128 				WindowState = FormWindowState.Normal;
  2129 			}
  2130 		}
  2131 
  2132 		/// <summary>
  2133 		/// Use to handle minimize events.
  2134 		/// </summary>
  2135 		/// <param name="sender"></param>
  2136 		/// <param name="e"></param>
  2137 		private void MainForm_SizeChanged(object sender, EventArgs e)
  2138 		{
  2139 			if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
  2140 			{
  2141 				if (Visible)
  2142 				{
  2143 					SysTrayHideShow();
  2144 				}
  2145 			}
  2146 		}
  2147 
  2148 		/// <summary>
  2149 		/// 
  2150 		/// </summary>
  2151 		/// <param name="sender"></param>
  2152 		/// <param name="e"></param>
  2153 		private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
  2154 		{
  2155 			//Our table layout size has changed which means our display size has changed.
  2156 			//We need to re-create our bitmap.
  2157 			iCreateBitmap = true;
  2158 		}
  2159 
  2160 		/// <summary>
  2161 		/// 
  2162 		/// </summary>
  2163 		/// <param name="sender"></param>
  2164 		/// <param name="e"></param>
  2165 		private void buttonSelectFile_Click(object sender, EventArgs e)
  2166 		{
  2167 			//openFileDialog1.InitialDirectory = "c:\\";
  2168 			//openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
  2169 			//openFileDialog.FilterIndex = 1;
  2170 			openFileDialog.RestoreDirectory = true;
  2171 
  2172 			if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
  2173 			{
  2174 				labelStartFileName.Text = openFileDialog.FileName;
  2175 				Properties.Settings.Default.StartFileName = openFileDialog.FileName;
  2176 				Properties.Settings.Default.Save();
  2177 			}
  2178 		}
  2179 
  2180         /// <summary>
  2181         /// 
  2182         /// </summary>
  2183         /// <param name="sender"></param>
  2184         /// <param name="e"></param>
  2185         private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
  2186         {
  2187             //Save the optical drive the user selected for ejection
  2188             Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
  2189             Properties.Settings.Default.Save();
  2190         }
  2191     }
  2192 }