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