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