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