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