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