Server/MainForm.cs
author StephaneLenclud
Mon, 25 Jan 2016 21:26:30 +0100
changeset 185 729e91c7d5b2
parent 184 7b6aa551eb6c
child 186 e10744f29ef3
permissions -rw-r--r--
Publishing v0.8.4.0
Now taking priority into account before switching client.
     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 
  1154 				iTableLayoutPanel.Enabled = true;
  1155 				panelDisplay.Enabled = true;
  1156 
  1157                 //Only setup brightness if display is open
  1158                 trackBarBrightness.Minimum = iDisplay.MinBrightness();
  1159                 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
  1160 				if (cds.Brightness < iDisplay.MinBrightness() || cds.Brightness > iDisplay.MaxBrightness())
  1161 				{
  1162 					//Brightness out of range, this can occur when using auto-detect
  1163 					//Use max brightness instead
  1164 					trackBarBrightness.Value = iDisplay.MaxBrightness();
  1165 					iDisplay.SetBrightness(iDisplay.MaxBrightness());
  1166 				}
  1167 				else
  1168 				{
  1169 					trackBarBrightness.Value = cds.Brightness;
  1170 					iDisplay.SetBrightness(cds.Brightness);
  1171 				}
  1172 
  1173 				//Try compute the steps to something that makes sense
  1174                 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
  1175                 trackBarBrightness.SmallChange = 1;
  1176                 
  1177                 //
  1178                 buttonFill.Enabled = true;
  1179                 buttonClear.Enabled = true;
  1180                 buttonOpen.Enabled = false;
  1181                 buttonClose.Enabled = true;
  1182                 trackBarBrightness.Enabled = true;
  1183                 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
  1184                 //+ " - " + iDisplay.SerialNumber();
  1185 
  1186                 if (iDisplay.SupportPowerOnOff())
  1187                 {
  1188                     buttonPowerOn.Enabled = true;
  1189                     buttonPowerOff.Enabled = true;
  1190                 }
  1191                 else
  1192                 {
  1193                     buttonPowerOn.Enabled = false;
  1194                     buttonPowerOff.Enabled = false;
  1195                 }
  1196 
  1197                 if (iDisplay.SupportClock())
  1198                 {
  1199                     buttonShowClock.Enabled = true;
  1200                     buttonHideClock.Enabled = true;
  1201                 }
  1202                 else
  1203                 {
  1204                     buttonShowClock.Enabled = false;
  1205                     buttonHideClock.Enabled = false;
  1206                 }
  1207 
  1208 				
  1209 				//Check if Volume Label is supported. To date only MDM166AA supports that crap :)
  1210 				checkBoxShowVolumeLabel.Enabled = iDisplay.IconCount(MiniDisplay.IconType.VolumeLabel)>0;
  1211 
  1212 				if (cds.ShowVolumeLabel)
  1213 				{
  1214                     iDisplay.SetIconOn(MiniDisplay.IconType.VolumeLabel);
  1215 				}
  1216 				else
  1217 				{
  1218                     iDisplay.SetIconOff(MiniDisplay.IconType.VolumeLabel);
  1219 				}
  1220             }
  1221             else
  1222             {
  1223 				//Display is connection not available
  1224 				//Reflect that in our UI
  1225 				checkBoxShowVolumeLabel.Enabled = false;
  1226 				iTableLayoutPanel.Enabled = false;
  1227 				panelDisplay.Enabled = false;
  1228                 buttonFill.Enabled = false;
  1229                 buttonClear.Enabled = false;
  1230                 buttonOpen.Enabled = true;
  1231                 buttonClose.Enabled = false;
  1232                 trackBarBrightness.Enabled = false;
  1233                 buttonPowerOn.Enabled = false;
  1234                 buttonPowerOff.Enabled = false;
  1235                 buttonShowClock.Enabled = false;
  1236                 buttonHideClock.Enabled = false;
  1237                 toolStripStatusLabelConnect.Text = "Disconnected";
  1238                 toolStripStatusLabelPower.Text = "N/A";
  1239             }
  1240 
  1241         }
  1242 
  1243 
  1244 		/// <summary>
  1245 		/// 
  1246 		/// </summary>
  1247 		/// <param name="sender"></param>
  1248 		/// <param name="e"></param>
  1249 		private void checkBoxShowVolumeLabel_CheckedChanged(object sender, EventArgs e)
  1250 		{
  1251 			cds.ShowVolumeLabel = checkBoxShowVolumeLabel.Checked;
  1252 			Properties.Settings.Default.Save();
  1253 			UpdateStatus();
  1254 		}
  1255 
  1256         private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
  1257         {
  1258             //Save our show borders setting
  1259             iTableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
  1260             cds.ShowBorders = checkBoxShowBorders.Checked;
  1261             Properties.Settings.Default.Save();
  1262             CheckFontHeight();
  1263         }
  1264 
  1265         private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
  1266         {
  1267             //Save our connect on startup setting
  1268             Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
  1269             Properties.Settings.Default.Save();
  1270         }
  1271 
  1272 		private void checkBoxMinimizeToTray_CheckedChanged(object sender, EventArgs e)
  1273 		{
  1274 			//Save our "Minimize to tray" setting
  1275 			Properties.Settings.Default.MinimizeToTray = checkBoxMinimizeToTray.Checked;
  1276 			Properties.Settings.Default.Save();
  1277 
  1278 		}
  1279 
  1280 		private void checkBoxStartMinimized_CheckedChanged(object sender, EventArgs e)
  1281 		{
  1282 			//Save our "Start minimized" setting
  1283 			Properties.Settings.Default.StartMinimized = checkBoxStartMinimized.Checked;
  1284 			Properties.Settings.Default.Save();
  1285 		}
  1286 
  1287 		private void checkBoxAutoStart_CheckedChanged(object sender, EventArgs e)
  1288 		{
  1289 			iStartupManager.Startup = checkBoxAutoStart.Checked;
  1290 		}
  1291 
  1292 
  1293         private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
  1294         {
  1295             //Save our reverse screen setting
  1296             cds.ReverseScreen = checkBoxReverseScreen.Checked;
  1297             Properties.Settings.Default.Save();
  1298             SetupPixelDelegates();
  1299         }
  1300 
  1301         private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
  1302         {
  1303             //Save our inverse colors setting
  1304             cds.InverseColors = checkBoxInverseColors.Checked;
  1305             Properties.Settings.Default.Save();
  1306             SetupPixelDelegates();
  1307         }
  1308 
  1309         private void checkBoxScaleToFit_CheckedChanged(object sender, EventArgs e)
  1310         {
  1311             //Save our scale to fit setting
  1312             cds.ScaleToFit = checkBoxScaleToFit.Checked;
  1313             Properties.Settings.Default.Save();
  1314             //
  1315             labelMinFontSize.Enabled = cds.ScaleToFit;
  1316             maskedTextBoxMinFontSize.Enabled = cds.ScaleToFit;
  1317         }
  1318 
  1319         private void MainForm_Resize(object sender, EventArgs e)
  1320         {
  1321             if (WindowState == FormWindowState.Minimized)
  1322             {
  1323                 // To workaround our empty bitmap bug on Windows 7 we need to recreate our bitmap when the application is minimized
  1324                 // That's apparently not needed on Windows 10 but we better leave it in place.
  1325                 iCreateBitmap = true;
  1326             }
  1327         }
  1328 
  1329         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
  1330         {
  1331             iCecManager.Stop();
  1332 			iNetworkManager.Dispose();
  1333 			CloseDisplayConnection();
  1334             StopServer();
  1335             e.Cancel = iClosing;
  1336         }
  1337 
  1338         public void StartServer()
  1339         {
  1340             iServiceHost = new ServiceHost
  1341                 (
  1342                     typeof(Session),
  1343                     new Uri[] { new Uri("net.tcp://localhost:8001/") }
  1344                 );
  1345 
  1346             iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
  1347             iServiceHost.Open();
  1348         }
  1349 
  1350         public void StopServer()
  1351         {
  1352             if (iClients.Count > 0 && !iClosing)
  1353             {
  1354                 //Tell our clients
  1355                 iClosing = true;
  1356                 BroadcastCloseEvent();
  1357             }
  1358             else if (iClosing)
  1359             {
  1360                 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
  1361                 {
  1362                     iClosing = false; //We make sure we force close if asked twice
  1363                 }
  1364             }
  1365             else
  1366             {
  1367                 //We removed that as it often lags for some reason
  1368                 //iServiceHost.Close();
  1369             }
  1370         }
  1371 
  1372         public void BroadcastCloseEvent()
  1373         {
  1374             Trace.TraceInformation("BroadcastCloseEvent - start");
  1375 
  1376             var inactiveClients = new List<string>();
  1377             foreach (var client in iClients)
  1378             {
  1379                 //if (client.Key != eventData.ClientName)
  1380                 {
  1381                     try
  1382                     {
  1383                         Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
  1384                         client.Value.Callback.OnCloseOrder(/*eventData*/);
  1385                     }
  1386                     catch (Exception ex)
  1387                     {
  1388                         inactiveClients.Add(client.Key);
  1389                     }
  1390                 }
  1391             }
  1392 
  1393             if (inactiveClients.Count > 0)
  1394             {
  1395                 foreach (var client in inactiveClients)
  1396                 {
  1397                     iClients.Remove(client);
  1398                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
  1399                 }
  1400             }
  1401 
  1402 			if (iClients.Count==0)
  1403 			{
  1404 				ClearLayout();
  1405 			}
  1406         }
  1407 
  1408 		/// <summary>
  1409 		/// Just remove all our fields.
  1410 		/// </summary>
  1411 		private void ClearLayout()
  1412 		{
  1413 			iTableLayoutPanel.Controls.Clear();
  1414 			iTableLayoutPanel.RowStyles.Clear();
  1415 			iTableLayoutPanel.ColumnStyles.Clear();
  1416 			iCurrentClientData = null;
  1417 		}
  1418 
  1419 		/// <summary>
  1420 		/// Just launch a demo client.
  1421 		/// </summary>
  1422 		private void StartNewClient(string aTopText = "", string aBottomText = "")
  1423 		{
  1424 			Thread clientThread = new Thread(SharpDisplayClient.Program.MainWithParams);
  1425 			SharpDisplayClient.StartParams myParams = new SharpDisplayClient.StartParams(new Point(this.Right, this.Top),aTopText,aBottomText);
  1426 			clientThread.Start(myParams);
  1427 			BringToFront();
  1428 		}
  1429 
  1430         private void buttonStartClient_Click(object sender, EventArgs e)
  1431         {
  1432 			StartNewClient();
  1433         }
  1434 
  1435         private void buttonSuspend_Click(object sender, EventArgs e)
  1436         {
  1437             LastTickTime = DateTime.Now; //Reset timer to prevent jump
  1438             timer.Enabled = !timer.Enabled;
  1439             if (!timer.Enabled)
  1440             {
  1441                 buttonSuspend.Text = "Run";
  1442             }
  1443             else
  1444             {
  1445                 buttonSuspend.Text = "Pause";
  1446             }
  1447         }
  1448 
  1449         private void buttonCloseClients_Click(object sender, EventArgs e)
  1450         {
  1451             BroadcastCloseEvent();
  1452         }
  1453 
  1454         private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
  1455         {
  1456             //Root node must have at least one child
  1457             if (e.Node.Nodes.Count == 0)
  1458             {
  1459                 return;
  1460             }
  1461 
  1462             //If the selected node is the root node of a client then switch to it
  1463             string sessionId=e.Node.Nodes[0].Text; //First child of a root node is the sessionId
  1464             if (iClients.ContainsKey(sessionId)) //Check that's actually what we are looking at
  1465             {
  1466                 //We have a valid session just switch to that client
  1467                 SetCurrentClient(sessionId,true);
  1468             }
  1469             
  1470         }
  1471 
  1472 
  1473         /// <summary>
  1474         ///
  1475         /// </summary>
  1476         /// <param name="aSessionId"></param>
  1477         /// <param name="aCallback"></param>
  1478         public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
  1479         {
  1480             if (this.InvokeRequired)
  1481             {
  1482                 //Not in the proper thread, invoke ourselves
  1483                 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
  1484                 this.Invoke(d, new object[] { aSessionId, aCallback });
  1485             }
  1486             else
  1487             {
  1488                 //We are in the proper thread
  1489                 //Add this session to our collection of clients
  1490                 ClientData newClient = new ClientData(aSessionId, aCallback);
  1491                 Program.iMainForm.iClients.Add(aSessionId, newClient);
  1492                 //Add this session to our UI
  1493                 UpdateClientTreeViewNode(newClient);
  1494             }
  1495         }
  1496 
  1497         /// <summary>
  1498         ///
  1499         /// </summary>
  1500         /// <param name="aSessionId"></param>
  1501         public void RemoveClientThreadSafe(string aSessionId)
  1502         {
  1503             if (this.InvokeRequired)
  1504             {
  1505                 //Not in the proper thread, invoke ourselves
  1506                 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
  1507                 this.Invoke(d, new object[] { aSessionId });
  1508             }
  1509             else
  1510             {
  1511                 //We are in the proper thread
  1512                 //Remove this session from both client collection and UI tree view
  1513                 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
  1514                 {
  1515                     Program.iMainForm.iClients.Remove(aSessionId);
  1516                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
  1517                 }
  1518 
  1519 				if (iClients.Count == 0)
  1520 				{
  1521 					//Clear our screen when last client disconnects
  1522 					ClearLayout();
  1523 
  1524 					if (iClosing)
  1525 					{
  1526 						//We were closing our form
  1527 						//All clients are now closed
  1528 						//Just resume our close operation
  1529 						iClosing = false;
  1530 						Close();
  1531 					}
  1532 				}
  1533             }
  1534         }
  1535 
  1536         /// <summary>
  1537         ///
  1538         /// </summary>
  1539         /// <param name="aSessionId"></param>
  1540         /// <param name="aLayout"></param>
  1541         public void SetClientLayoutThreadSafe(string aSessionId, TableLayout aLayout)
  1542         {
  1543             if (this.InvokeRequired)
  1544             {
  1545                 //Not in the proper thread, invoke ourselves
  1546                 SetLayoutDelegate d = new SetLayoutDelegate(SetClientLayoutThreadSafe);
  1547                 this.Invoke(d, new object[] { aSessionId, aLayout });
  1548             }
  1549             else
  1550             {
  1551                 ClientData client = iClients[aSessionId];
  1552                 if (client != null)
  1553                 {
  1554                     //Don't change a thing if the layout is the same
  1555                     if (!client.Layout.IsSameAs(aLayout))
  1556                     {
  1557                         Debug.Print("SetClientLayoutThreadSafe: Layout updated.");
  1558                         //Set our client layout then
  1559                         client.Layout = aLayout;
  1560                         //So that next time we update all our fields at ones
  1561                         client.HasNewLayout = true;
  1562                         //Layout has changed clear our fields then
  1563                         client.Fields.Clear();
  1564                         //
  1565                         UpdateClientTreeViewNode(client);
  1566                     }
  1567                     else
  1568                     {
  1569                         Debug.Print("SetClientLayoutThreadSafe: Layout has not changed.");
  1570                     }
  1571                 }
  1572             }
  1573         }
  1574 
  1575         /// <summary>
  1576         ///
  1577         /// </summary>
  1578         /// <param name="aSessionId"></param>
  1579         /// <param name="aField"></param>
  1580         public void SetClientFieldThreadSafe(string aSessionId, DataField aField)
  1581         {
  1582             if (this.InvokeRequired)
  1583             {
  1584                 //Not in the proper thread, invoke ourselves
  1585                 SetFieldDelegate d = new SetFieldDelegate(SetClientFieldThreadSafe);
  1586                 this.Invoke(d, new object[] { aSessionId, aField });
  1587             }
  1588             else
  1589             {
  1590                 //We are in the proper thread
  1591                 //Call the non-thread-safe variant
  1592                 SetClientField(aSessionId, aField);
  1593             }
  1594         }
  1595 
  1596 
  1597 
  1598 
  1599         /// <summary>
  1600         /// Set a data field in the given client.
  1601         /// </summary>
  1602         /// <param name="aSessionId"></param>
  1603         /// <param name="aField"></param>
  1604         private void SetClientField(string aSessionId, DataField aField)
  1605         {   
  1606             //TODO: should check if the field actually changed?
  1607 
  1608             ClientData client = iClients[aSessionId];
  1609             bool layoutChanged = false;
  1610             bool contentChanged = true;
  1611 
  1612             //Fetch our field index
  1613             int fieldIndex = client.FindSameFieldIndex(aField);
  1614 
  1615             if (fieldIndex < 0)
  1616             {
  1617                 //No corresponding field, just bail out
  1618                 return;
  1619             }
  1620 
  1621             //Keep our previous field in there
  1622             DataField previousField = client.Fields[fieldIndex];
  1623             //Just update that field then 
  1624             client.Fields[fieldIndex] = aField;
  1625 
  1626             if (!aField.IsTableField)
  1627             {
  1628                 //We are done then if that field is not in our table layout
  1629                 return;
  1630             }
  1631 
  1632             TableField tableField = (TableField) aField;
  1633 
  1634             if (previousField.IsSameLayout(aField))
  1635             {
  1636                 //If we are updating a field in our current client we need to update it in our panel
  1637                 if (aSessionId == iCurrentClientSessionId)
  1638                 {
  1639                     Control ctrl=iTableLayoutPanel.GetControlFromPosition(tableField.Column, tableField.Row);
  1640                     if (aField.IsTextField && ctrl is MarqueeLabel)
  1641                     {
  1642                         TextField textField=(TextField)aField;
  1643                         //Text field control already in place, just change the text
  1644                         MarqueeLabel label = (MarqueeLabel)ctrl;
  1645                         contentChanged = (label.Text != textField.Text || label.TextAlign != textField.Alignment);
  1646                         label.Text = textField.Text;
  1647                         label.TextAlign = textField.Alignment;
  1648                     }
  1649                     else if (aField.IsBitmapField && ctrl is PictureBox)
  1650                     {
  1651                         BitmapField bitmapField = (BitmapField)aField;
  1652                         contentChanged = true; //TODO: Bitmap comp or should we leave that to clients?
  1653                         //Bitmap field control already in place just change the bitmap
  1654                         PictureBox pictureBox = (PictureBox)ctrl;
  1655                         pictureBox.Image = bitmapField.Bitmap;
  1656                     }
  1657                     else
  1658                     {
  1659                         layoutChanged = true;
  1660                     }
  1661                 }
  1662             }
  1663             else
  1664             {                
  1665                 layoutChanged = true;
  1666             }
  1667 
  1668             //If either content or layout changed we need to update our tree view to reflect the changes
  1669             if (contentChanged || layoutChanged)
  1670             {
  1671                 UpdateClientTreeViewNode(client);
  1672                 //
  1673                 if (layoutChanged)
  1674                 {
  1675                     Debug.Print("Layout changed");
  1676                     //Our layout has changed, if we are already the current client we need to update our panel
  1677                     if (aSessionId == iCurrentClientSessionId)
  1678                     {
  1679                         //Apply layout and set data fields.
  1680                         UpdateTableLayoutPanel(iCurrentClientData);
  1681                     }
  1682                 }
  1683                 else
  1684                 {
  1685                     Debug.Print("Layout has not changed.");
  1686                 }
  1687             }
  1688             else
  1689             {
  1690                 Debug.Print("WARNING: content and layout have not changed!");
  1691             }
  1692 
  1693             //When a client field is set we try switching to this client to present the new information to our user
  1694             SetCurrentClient(aSessionId);
  1695         }
  1696 
  1697         /// <summary>
  1698         ///
  1699         /// </summary>
  1700         /// <param name="aTexts"></param>
  1701         public void SetClientFieldsThreadSafe(string aSessionId, System.Collections.Generic.IList<DataField> aFields)
  1702         {
  1703             if (this.InvokeRequired)
  1704             {
  1705                 //Not in the proper thread, invoke ourselves
  1706                 SetFieldsDelegate d = new SetFieldsDelegate(SetClientFieldsThreadSafe);
  1707                 this.Invoke(d, new object[] { aSessionId, aFields });
  1708             }
  1709             else
  1710             {
  1711                 ClientData client = iClients[aSessionId];
  1712 
  1713                 if (client.HasNewLayout)
  1714                 {
  1715                     //TODO: Assert client.Count == 0
  1716                     //Our layout was just changed
  1717                     //Do some special handling to avoid re-creating our panel N times, once for each fields
  1718                     client.HasNewLayout = false;
  1719                     //Just set all our fields then
  1720                     client.Fields.AddRange(aFields);
  1721                     //Try switch to that client
  1722                     SetCurrentClient(aSessionId);
  1723 
  1724                     //If we are updating the current client update our panel
  1725                     if (aSessionId == iCurrentClientSessionId)
  1726                     {
  1727                         //Apply layout and set data fields.
  1728                         UpdateTableLayoutPanel(iCurrentClientData);
  1729                     }
  1730 
  1731                     UpdateClientTreeViewNode(client);
  1732                 }
  1733                 else
  1734                 {
  1735                     //Put each our text fields in a label control
  1736                     foreach (DataField field in aFields)
  1737                     {
  1738                         SetClientField(aSessionId, field);
  1739                     }
  1740                 }
  1741             }
  1742         }
  1743 
  1744         /// <summary>
  1745         ///
  1746         /// </summary>
  1747         /// <param name="aSessionId"></param>
  1748         /// <param name="aName"></param>
  1749         public void SetClientNameThreadSafe(string aSessionId, string aName)
  1750         {
  1751             if (this.InvokeRequired)
  1752             {
  1753                 //Not in the proper thread, invoke ourselves
  1754                 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
  1755                 this.Invoke(d, new object[] { aSessionId, aName });
  1756             }
  1757             else
  1758             {
  1759                 //We are in the proper thread
  1760                 //Get our client
  1761                 ClientData client = iClients[aSessionId];
  1762                 if (client != null)
  1763                 {
  1764                     //Set its name
  1765                     client.Name = aName;
  1766                     //Update our tree-view
  1767                     UpdateClientTreeViewNode(client);
  1768                 }
  1769             }
  1770         }
  1771 
  1772         ///
  1773         public void SetClientPriorityThreadSafe(string aSessionId, uint aPriority)
  1774         {
  1775             if (this.InvokeRequired)
  1776             {
  1777                 //Not in the proper thread, invoke ourselves
  1778                 SetClientPriorityDelegate d = new SetClientPriorityDelegate(SetClientPriorityThreadSafe);
  1779                 this.Invoke(d, new object[] { aSessionId, aPriority });
  1780             }
  1781             else
  1782             {
  1783                 //We are in the proper thread
  1784                 //Get our client
  1785                 ClientData client = iClients[aSessionId];
  1786                 if (client != null)
  1787                 {
  1788                     //Set its name
  1789                     client.Priority = aPriority;
  1790                     //Update our tree-view
  1791                     UpdateClientTreeViewNode(client);
  1792                 }
  1793             }
  1794         }
  1795 
  1796         /// <summary>
  1797         /// 
  1798         /// </summary>
  1799         /// <param name="value"></param>
  1800         /// <param name="maxChars"></param>
  1801         /// <returns></returns>
  1802         public static string Truncate(string value, int maxChars)
  1803         {
  1804             return value.Length <= maxChars ? value : value.Substring(0, maxChars-3) + "...";
  1805         }
  1806 
  1807         /// <summary>
  1808         /// Update our recording notification.
  1809         /// </summary>
  1810         private void UpdateRecordingNotification()
  1811         {
  1812             //Go through each 
  1813             bool activeRecording = false;
  1814             string text="";
  1815             RecordingField recField=new RecordingField();
  1816             foreach (var client in iClients)
  1817             {
  1818                 RecordingField rec=(RecordingField)client.Value.FindSameFieldAs(recField);
  1819                 if (rec!=null && rec.IsActive)
  1820                 {
  1821                     activeRecording = true;
  1822                     //Don't break cause we are collecting the names/texts.
  1823                     if (!String.IsNullOrEmpty(rec.Text))
  1824                     {
  1825                         text += (rec.Text + "\n");
  1826                     }
  1827                     else
  1828                     {
  1829                         //Not text for that recording, use client name instead
  1830                         text += client.Value.Name + " recording\n";
  1831                     }
  1832                     
  1833                 }
  1834             }
  1835 
  1836             //Update our text no matter what, can't have more than 63 characters otherwise it throws an exception.
  1837             iRecordingNotification.Text = Truncate(text,63);
  1838 
  1839             //Change visibility of notification if needed
  1840             if (iRecordingNotification.Visible != activeRecording)
  1841             {                
  1842                 iRecordingNotification.Visible = activeRecording;
  1843                 //Assuming the notification icon is in sync with our display icon
  1844                 //Take care of our REC icon
  1845                 if (iDisplay.IsOpen())
  1846                 {
  1847                     iDisplay.SetIconOnOff(MiniDisplay.IconType.Recording, activeRecording);
  1848                 }                
  1849             }
  1850         }
  1851 
  1852         /// <summary>
  1853         ///
  1854         /// </summary>
  1855         /// <param name="aClient"></param>
  1856         private void UpdateClientTreeViewNode(ClientData aClient)
  1857         {
  1858             Debug.Print("UpdateClientTreeViewNode");
  1859 
  1860             if (aClient == null)
  1861             {
  1862                 return;
  1863             }
  1864 
  1865             //Hook in record icon update too
  1866             UpdateRecordingNotification();
  1867 
  1868             TreeNode node = null;
  1869             //Check that our client node already exists
  1870             //Get our client root node using its key which is our session ID
  1871             TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
  1872             if (nodes.Count()>0)
  1873             {
  1874                 //We already have a node for that client
  1875                 node = nodes[0];
  1876                 //Clear children as we are going to recreate them below
  1877                 node.Nodes.Clear();
  1878             }
  1879             else
  1880             {
  1881                 //Client node does not exists create a new one
  1882                 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
  1883                 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
  1884             }
  1885 
  1886             if (node != null)
  1887             {
  1888                 //Change its name
  1889                 if (!String.IsNullOrEmpty(aClient.Name))
  1890                 {
  1891                     //We have a name, use it as text for our root node
  1892                     node.Text = aClient.Name;
  1893                     //Add a child with SessionId
  1894                     node.Nodes.Add(new TreeNode(aClient.SessionId));
  1895                 }
  1896                 else
  1897                 {
  1898                     //No name, use session ID instead
  1899                     node.Text = aClient.SessionId;
  1900                 }
  1901 
  1902                 //Display client priority
  1903                 node.Nodes.Add(new TreeNode("Priority: " + aClient.Priority));
  1904 
  1905                 if (aClient.Fields.Count > 0)
  1906                 {
  1907                     //Create root node for our texts
  1908                     TreeNode textsRoot = new TreeNode("Fields");
  1909                     node.Nodes.Add(textsRoot);
  1910                     //For each text add a new entry
  1911                     foreach (DataField field in aClient.Fields)
  1912                     {
  1913                         if (field.IsTextField)
  1914                         {
  1915                             TextField textField = (TextField)field;
  1916                             textsRoot.Nodes.Add(new TreeNode("[Text]" + textField.Text));
  1917                         }
  1918                         else if (field.IsBitmapField)
  1919                         {
  1920                             textsRoot.Nodes.Add(new TreeNode("[Bitmap]"));
  1921                         }
  1922                         else if (field.IsRecordingField)
  1923                         {
  1924                             RecordingField recordingField = (RecordingField)field;
  1925                             textsRoot.Nodes.Add(new TreeNode("[Recording]" + recordingField.IsActive));
  1926                         }
  1927                     }
  1928                 }
  1929 
  1930                 node.ExpandAll();
  1931             }
  1932         }
  1933 
  1934         /// <summary>
  1935         /// Update our table layout row styles to make sure each rows have similar height
  1936         /// </summary>
  1937         private void UpdateTableLayoutRowStyles()
  1938         {
  1939             foreach (RowStyle rowStyle in iTableLayoutPanel.RowStyles)
  1940             {
  1941                 rowStyle.SizeType = SizeType.Percent;
  1942                 rowStyle.Height = 100 / iTableLayoutPanel.RowCount;
  1943             }
  1944         }
  1945 
  1946         /// <summary>
  1947         /// Update our display table layout.
  1948         /// Will instanciated every field control as defined by our client.
  1949         /// Fields must be specified by rows from the left.
  1950         /// </summary>
  1951         /// <param name="aLayout"></param>
  1952         private void UpdateTableLayoutPanel(ClientData aClient)
  1953         {
  1954             Debug.Print("UpdateTableLayoutPanel");
  1955 
  1956 			if (aClient == null)
  1957 			{
  1958 				//Just drop it
  1959 				return;
  1960 			}
  1961 
  1962 
  1963             TableLayout layout = aClient.Layout;
  1964 
  1965             //First clean our current panel
  1966             iTableLayoutPanel.Controls.Clear();
  1967             iTableLayoutPanel.RowStyles.Clear();
  1968             iTableLayoutPanel.ColumnStyles.Clear();
  1969             iTableLayoutPanel.RowCount = 0;
  1970             iTableLayoutPanel.ColumnCount = 0;
  1971 
  1972             //Then recreate our rows...
  1973             while (iTableLayoutPanel.RowCount < layout.Rows.Count)
  1974             {
  1975                 iTableLayoutPanel.RowCount++;
  1976             }
  1977 
  1978             // ...and columns 
  1979             while (iTableLayoutPanel.ColumnCount < layout.Columns.Count)
  1980             {
  1981                 iTableLayoutPanel.ColumnCount++;
  1982             }
  1983 
  1984             //For each column
  1985             for (int i = 0; i < iTableLayoutPanel.ColumnCount; i++)
  1986             {
  1987                 //Create our column styles
  1988                 this.iTableLayoutPanel.ColumnStyles.Add(layout.Columns[i]);
  1989 
  1990                 //For each rows
  1991                 for (int j = 0; j < iTableLayoutPanel.RowCount; j++)
  1992                 {
  1993                     if (i == 0)
  1994                     {
  1995                         //Create our row styles
  1996                         this.iTableLayoutPanel.RowStyles.Add(layout.Rows[j]);
  1997                     }
  1998                     else
  1999                     {
  2000                         continue;
  2001                     }
  2002                 }
  2003             }
  2004 
  2005             //For each field
  2006             foreach (DataField field in aClient.Fields)
  2007             {
  2008                 if (!field.IsTableField)
  2009                 {
  2010                     //That field is not taking part in our table layout skip it
  2011                     continue;
  2012                 }
  2013 
  2014                 TableField tableField = (TableField)field;
  2015 
  2016                 //Create a control corresponding to the field specified for that cell
  2017                 Control control = CreateControlForDataField(tableField);
  2018 
  2019                 //Add newly created control to our table layout at the specified row and column
  2020                 iTableLayoutPanel.Controls.Add(control, tableField.Column, tableField.Row);
  2021                 //Make sure we specify column and row span for that new control
  2022                 iTableLayoutPanel.SetColumnSpan(control, tableField.ColumnSpan);
  2023                 iTableLayoutPanel.SetRowSpan(control, tableField.RowSpan);
  2024             }
  2025 
  2026 
  2027             CheckFontHeight();
  2028         }
  2029 
  2030         /// <summary>
  2031         /// Check our type of data field and create corresponding control
  2032         /// </summary>
  2033         /// <param name="aField"></param>
  2034         private Control CreateControlForDataField(DataField aField)
  2035         {
  2036             Control control=null;
  2037             if (aField.IsTextField)
  2038             {
  2039                 MarqueeLabel label = new SharpDisplayManager.MarqueeLabel();
  2040                 label.AutoEllipsis = true;
  2041                 label.AutoSize = true;
  2042                 label.BackColor = System.Drawing.Color.Transparent;
  2043                 label.Dock = System.Windows.Forms.DockStyle.Fill;
  2044                 label.Location = new System.Drawing.Point(1, 1);
  2045                 label.Margin = new System.Windows.Forms.Padding(0);
  2046                 label.Name = "marqueeLabel" + aField;
  2047                 label.OwnTimer = false;
  2048                 label.PixelsPerSecond = cds.ScrollingSpeedInPixelsPerSecond;
  2049                 label.Separator = cds.Separator;
  2050                 label.MinFontSize = cds.MinFontSize;
  2051                 label.ScaleToFit = cds.ScaleToFit;
  2052                 //control.Size = new System.Drawing.Size(254, 30);
  2053                 //control.TabIndex = 2;
  2054                 label.Font = cds.Font;
  2055 
  2056                 TextField field = (TextField)aField;
  2057                 label.TextAlign = field.Alignment;
  2058                 label.UseCompatibleTextRendering = true;
  2059                 label.Text = field.Text;
  2060                 //
  2061                 control = label;
  2062             }
  2063             else if (aField.IsBitmapField)
  2064             {
  2065                 //Create picture box
  2066                 PictureBox picture = new PictureBox();
  2067                 picture.AutoSize = true;
  2068                 picture.BackColor = System.Drawing.Color.Transparent;
  2069                 picture.Dock = System.Windows.Forms.DockStyle.Fill;
  2070                 picture.Location = new System.Drawing.Point(1, 1);
  2071                 picture.Margin = new System.Windows.Forms.Padding(0);
  2072                 picture.Name = "pictureBox" + aField;
  2073                 //Set our image
  2074                 BitmapField field = (BitmapField)aField;
  2075                 picture.Image = field.Bitmap;
  2076                 //
  2077                 control = picture;
  2078             }
  2079             //TODO: Handle recording field?
  2080 
  2081             return control;
  2082         }
  2083 
  2084 		/// <summary>
  2085 		/// Called when the user selected a new display type.
  2086 		/// </summary>
  2087 		/// <param name="sender"></param>
  2088 		/// <param name="e"></param>
  2089         private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
  2090         {
  2091 			//Store the selected display type in our settings
  2092             Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
  2093             cds.DisplayType = comboBoxDisplayType.SelectedIndex;
  2094             Properties.Settings.Default.Save();
  2095 
  2096 			//Try re-opening the display connection if we were already connected.
  2097 			//Otherwise just update our status to reflect display type change.
  2098             if (iDisplay.IsOpen())
  2099             {
  2100                 OpenDisplayConnection();
  2101             }
  2102             else
  2103             {
  2104                 UpdateStatus();
  2105             }
  2106         }
  2107 
  2108         private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
  2109         {
  2110             if (maskedTextBoxTimerInterval.Text != "")
  2111             {
  2112                 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
  2113 
  2114                 if (interval > 0)
  2115                 {
  2116                     timer.Interval = interval;
  2117                     cds.TimerInterval = timer.Interval;
  2118                     Properties.Settings.Default.Save();
  2119                 }
  2120             }
  2121         }
  2122 
  2123         private void maskedTextBoxMinFontSize_TextChanged(object sender, EventArgs e)
  2124         {
  2125             if (maskedTextBoxMinFontSize.Text != "")
  2126             {
  2127                 int minFontSize = Convert.ToInt32(maskedTextBoxMinFontSize.Text);
  2128 
  2129                 if (minFontSize > 0)
  2130                 {
  2131                     cds.MinFontSize = minFontSize;
  2132                     Properties.Settings.Default.Save();
  2133 					//We need to recreate our layout for that change to take effect
  2134 					UpdateTableLayoutPanel(iCurrentClientData);
  2135                 }
  2136             }
  2137         }
  2138 
  2139 
  2140 		private void maskedTextBoxScrollingSpeed_TextChanged(object sender, EventArgs e)
  2141 		{
  2142 			if (maskedTextBoxScrollingSpeed.Text != "")
  2143 			{
  2144 				int scrollingSpeed = Convert.ToInt32(maskedTextBoxScrollingSpeed.Text);
  2145 
  2146 				if (scrollingSpeed > 0)
  2147 				{
  2148 					cds.ScrollingSpeedInPixelsPerSecond = scrollingSpeed;
  2149 					Properties.Settings.Default.Save();
  2150 					//We need to recreate our layout for that change to take effect
  2151 					UpdateTableLayoutPanel(iCurrentClientData);
  2152 				}
  2153 			}
  2154 		}
  2155 
  2156         private void textBoxScrollLoopSeparator_TextChanged(object sender, EventArgs e)
  2157         {
  2158             cds.Separator = textBoxScrollLoopSeparator.Text;
  2159             Properties.Settings.Default.Save();
  2160 
  2161 			//Update our text fields
  2162 			foreach (MarqueeLabel ctrl in iTableLayoutPanel.Controls)
  2163 			{
  2164 				ctrl.Separator = cds.Separator;
  2165 			}
  2166 
  2167         }
  2168 
  2169         private void buttonPowerOn_Click(object sender, EventArgs e)
  2170         {
  2171             iDisplay.PowerOn();
  2172         }
  2173 
  2174         private void buttonPowerOff_Click(object sender, EventArgs e)
  2175         {
  2176             iDisplay.PowerOff();
  2177         }
  2178 
  2179         private void buttonShowClock_Click(object sender, EventArgs e)
  2180         {
  2181 			ShowClock();
  2182         }
  2183 
  2184         private void buttonHideClock_Click(object sender, EventArgs e)
  2185         {
  2186 			HideClock();
  2187         }
  2188 
  2189         private void buttonUpdate_Click(object sender, EventArgs e)
  2190         {
  2191             InstallUpdateSyncWithInfo();
  2192         }
  2193 
  2194 		/// <summary>
  2195 		/// 
  2196 		/// </summary>
  2197 		void ShowClock()
  2198 		{
  2199 			if (!iDisplay.IsOpen())
  2200 			{
  2201 				return;
  2202 			}
  2203 
  2204 			//Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2205 			iSkipFrameRendering = true;
  2206 			//Clear our screen 
  2207 			iDisplay.Clear();
  2208 			iDisplay.SwapBuffers();
  2209 			//Then show our clock
  2210 			iDisplay.ShowClock();
  2211 		}
  2212 
  2213 		/// <summary>
  2214 		/// 
  2215 		/// </summary>
  2216 		void HideClock()
  2217 		{
  2218 			if (!iDisplay.IsOpen())
  2219 			{
  2220 				return;
  2221 			}
  2222 
  2223 			//Devices like MDM166AA don't support windowing and frame rendering must be stopped while showing our clock
  2224 			iSkipFrameRendering = false;
  2225 			iDisplay.HideClock();
  2226 		}
  2227 
  2228         private void InstallUpdateSyncWithInfo()
  2229         {
  2230             UpdateCheckInfo info = null;
  2231 
  2232             if (ApplicationDeployment.IsNetworkDeployed)
  2233             {
  2234                 ApplicationDeployment ad = ApplicationDeployment.CurrentDeployment;
  2235 
  2236                 try
  2237                 {
  2238                     info = ad.CheckForDetailedUpdate();
  2239 
  2240                 }
  2241                 catch (DeploymentDownloadException dde)
  2242                 {
  2243                     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);
  2244                     return;
  2245                 }
  2246                 catch (InvalidDeploymentException ide)
  2247                 {
  2248                     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);
  2249                     return;
  2250                 }
  2251                 catch (InvalidOperationException ioe)
  2252                 {
  2253                     MessageBox.Show("This application cannot be updated. It is likely not a ClickOnce application. Error: " + ioe.Message);
  2254                     return;
  2255                 }
  2256 
  2257 				if (info.UpdateAvailable)
  2258 				{
  2259 					Boolean doUpdate = true;
  2260 
  2261 					if (!info.IsUpdateRequired)
  2262 					{
  2263 						DialogResult dr = MessageBox.Show("An update is available. Would you like to update the application now?", "Update Available", MessageBoxButtons.OKCancel);
  2264 						if (!(DialogResult.OK == dr))
  2265 						{
  2266 							doUpdate = false;
  2267 						}
  2268 					}
  2269 					else
  2270 					{
  2271 						// Display a message that the app MUST reboot. Display the minimum required version.
  2272 						MessageBox.Show("This application has detected a mandatory update from your current " +
  2273 							"version to version " + info.MinimumRequiredVersion.ToString() +
  2274 							". The application will now install the update and restart.",
  2275 							"Update Available", MessageBoxButtons.OK,
  2276 							MessageBoxIcon.Information);
  2277 					}
  2278 
  2279 					if (doUpdate)
  2280 					{
  2281 						try
  2282 						{
  2283 							ad.Update();
  2284 							MessageBox.Show("The application has been upgraded, and will now restart.");
  2285 							Application.Restart();
  2286 						}
  2287 						catch (DeploymentDownloadException dde)
  2288 						{
  2289 							MessageBox.Show("Cannot install the latest version of the application. \n\nPlease check your network connection, or try again later. Error: " + dde);
  2290 							return;
  2291 						}
  2292 					}
  2293 				}
  2294 				else
  2295 				{
  2296 					MessageBox.Show("You are already running the latest version.", "Application up-to-date");
  2297 				}
  2298             }
  2299         }
  2300 
  2301 
  2302 		/// <summary>
  2303 		/// Used to
  2304 		/// </summary>
  2305 		private void SysTrayHideShow()
  2306 		{
  2307 			Visible = !Visible;
  2308 			if (Visible)
  2309 			{
  2310 				Activate();
  2311 				WindowState = FormWindowState.Normal;
  2312 			}
  2313 		}
  2314 
  2315 		/// <summary>
  2316 		/// Use to handle minimize events.
  2317 		/// </summary>
  2318 		/// <param name="sender"></param>
  2319 		/// <param name="e"></param>
  2320 		private void MainForm_SizeChanged(object sender, EventArgs e)
  2321 		{
  2322 			if (WindowState == FormWindowState.Minimized && Properties.Settings.Default.MinimizeToTray)
  2323 			{
  2324 				if (Visible)
  2325 				{
  2326 					SysTrayHideShow();
  2327 				}
  2328 			}
  2329 		}
  2330 
  2331 		/// <summary>
  2332 		/// 
  2333 		/// </summary>
  2334 		/// <param name="sender"></param>
  2335 		/// <param name="e"></param>
  2336 		private void tableLayoutPanel_SizeChanged(object sender, EventArgs e)
  2337 		{
  2338 			//Our table layout size has changed which means our display size has changed.
  2339 			//We need to re-create our bitmap.
  2340 			iCreateBitmap = true;
  2341 		}
  2342 
  2343 		/// <summary>
  2344 		/// 
  2345 		/// </summary>
  2346 		/// <param name="sender"></param>
  2347 		/// <param name="e"></param>
  2348 		private void buttonSelectFile_Click(object sender, EventArgs e)
  2349 		{
  2350 			//openFileDialog1.InitialDirectory = "c:\\";
  2351 			//openFileDialog.Filter = "EXE files (*.exe)|*.exe|All files (*.*)|*.*";
  2352 			//openFileDialog.FilterIndex = 1;
  2353 			openFileDialog.RestoreDirectory = true;
  2354 
  2355 			if (DlgBox.ShowDialog(openFileDialog) == DialogResult.OK)
  2356 			{
  2357 				labelStartFileName.Text = openFileDialog.FileName;
  2358 				Properties.Settings.Default.StartFileName = openFileDialog.FileName;
  2359 				Properties.Settings.Default.Save();
  2360 			}
  2361 		}
  2362 
  2363         /// <summary>
  2364         /// 
  2365         /// </summary>
  2366         /// <param name="sender"></param>
  2367         /// <param name="e"></param>
  2368         private void comboBoxOpticalDrives_SelectedIndexChanged(object sender, EventArgs e)
  2369         {
  2370             //Save the optical drive the user selected for ejection
  2371             Properties.Settings.Default.OpticalDriveToEject = comboBoxOpticalDrives.SelectedItem.ToString();
  2372             Properties.Settings.Default.Save();
  2373         }
  2374 
  2375         /// <summary>
  2376         /// Broadcast messages to subscribers.
  2377         /// </summary>
  2378         /// <param name="message"></param>
  2379         protected override void WndProc(ref Message aMessage)
  2380         {
  2381             if (OnWndProc!=null)
  2382             {
  2383                 OnWndProc(ref aMessage);
  2384             }
  2385             
  2386             base.WndProc(ref aMessage);
  2387         }
  2388 
  2389         private void checkBoxCecEnabled_CheckedChanged(object sender, EventArgs e)
  2390         {
  2391             //Save CEC enabled status
  2392             Properties.Settings.Default.CecEnabled = checkBoxCecEnabled.Checked;
  2393             Properties.Settings.Default.Save();
  2394             //
  2395             ResetCec();
  2396         }
  2397 
  2398         private void comboBoxHdmiPort_SelectedIndexChanged(object sender, EventArgs e)
  2399         {
  2400             //Save CEC HDMI port
  2401             Properties.Settings.Default.CecHdmiPort = Convert.ToByte(comboBoxHdmiPort.SelectedIndex);
  2402             Properties.Settings.Default.CecHdmiPort++;
  2403             Properties.Settings.Default.Save();
  2404             //
  2405             ResetCec();
  2406         }
  2407 
  2408         private void checkBoxCecMonitorOff_CheckedChanged(object sender, EventArgs e)
  2409         {
  2410             Properties.Settings.Default.CecMonitorOff = checkBoxCecMonitorOff.Checked;
  2411             Properties.Settings.Default.Save();
  2412             //
  2413             ResetCec();
  2414         }
  2415 
  2416         private void checkBoxCecMonitorOn_CheckedChanged(object sender, EventArgs e)
  2417         {
  2418             Properties.Settings.Default.CecMonitorOn = checkBoxCecMonitorOn.Checked;
  2419             Properties.Settings.Default.Save();
  2420             //
  2421             ResetCec();
  2422         }
  2423 
  2424         /// <summary>
  2425         /// 
  2426         /// </summary>
  2427         private void ResetCec()
  2428         {
  2429             if (iCecManager==null)
  2430             {
  2431                 //Thus skipping initial UI setup
  2432                 return;
  2433             }
  2434 
  2435             iCecManager.Stop();
  2436             //
  2437             if (Properties.Settings.Default.CecEnabled)
  2438             {
  2439                 iCecManager.Start(Handle, "CEC",
  2440                 Properties.Settings.Default.CecHdmiPort,
  2441                 Properties.Settings.Default.CecMonitorOn,
  2442                 Properties.Settings.Default.CecMonitorOff);
  2443             }
  2444         }
  2445     }
  2446 }