Server/MainForm.cs
author sl
Sun, 21 Sep 2014 19:07:18 +0200
changeset 57 544132d07c3b
parent 55 b5ed2e29be23
child 58 2a527027024d
permissions -rw-r--r--
Simplifying client construction.
Fixing our borders and sorting out our color issues.
Adding inverse colors options.
     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 //
    17 using SharpDisplayClient;
    18 using SharpDisplay;
    19 
    20 namespace SharpDisplayManager
    21 {
    22     public partial class MainForm : Form
    23     {
    24         DateTime LastTickTime;
    25         Display iDisplay;
    26         System.Drawing.Bitmap iBmp;
    27         bool iCreateBitmap; //Workaround render to bitmap issues when minimized
    28         ServiceHost iServiceHost;
    29         /// <summary>
    30         /// Our collection of clients
    31         /// </summary>
    32         public Dictionary<string, ClientData> iClients;
    33         public bool iClosing;
    34 
    35         public MainForm()
    36         {
    37             LastTickTime = DateTime.Now;
    38             iDisplay = new Display();
    39             iClients = new Dictionary<string, ClientData>();
    40 
    41             InitializeComponent();
    42             UpdateStatus();
    43             //We have a bug when drawing minimized and reusing our bitmap
    44             iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
    45             iCreateBitmap = false;
    46             //
    47             //this.tableLayoutPanel.CellPaint += new TableLayoutCellPaintEventHandler(tableLayoutPanel_CellPaint);
    48         }
    49 
    50         private void MainForm_Load(object sender, EventArgs e)
    51         {
    52             StartServer();
    53 
    54             if (Properties.Settings.Default.DisplayConnectOnStartup)
    55             {
    56                 OpenDisplayConnection();
    57             }
    58         }
    59 
    60         //Testing that stuff
    61         private void tableLayoutPanel_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
    62         {
    63             var panel = sender as TableLayoutPanel;
    64             //e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
    65             var rectangle = e.CellBounds;
    66             using (var pen = new Pen(Color.Black, 1))
    67             {
    68                 pen.Alignment = System.Drawing.Drawing2D.PenAlignment.Center;
    69                 pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Solid;
    70 
    71                 if (e.Row == (panel.RowCount - 1))
    72                 {
    73                     rectangle.Height -= 1;
    74                 }
    75 
    76                 if (e.Column == (panel.ColumnCount - 1))
    77                 {
    78                     rectangle.Width -= 1;
    79                 }
    80 
    81                 e.Graphics.DrawRectangle(pen, rectangle);
    82             }
    83         }
    84 
    85 
    86         private void buttonFont_Click(object sender, EventArgs e)
    87         {
    88             //fontDialog.ShowColor = true;
    89             //fontDialog.ShowApply = true;
    90             fontDialog.ShowEffects = true;
    91             fontDialog.Font = marqueeLabelTop.Font;
    92 
    93             fontDialog.FixedPitchOnly = checkBoxFixedPitchFontOnly.Checked;
    94 
    95             //fontDialog.ShowHelp = true;
    96 
    97             //fontDlg.MaxSize = 40;
    98             //fontDlg.MinSize = 22;
    99 
   100             //fontDialog.Parent = this;
   101             //fontDialog.StartPosition = FormStartPosition.CenterParent;
   102 
   103             //DlgBox.ShowDialog(fontDialog);
   104 
   105             //if (fontDialog.ShowDialog(this) != DialogResult.Cancel)
   106             if (DlgBox.ShowDialog(fontDialog) != DialogResult.Cancel)
   107             {
   108 
   109                 //MsgBox.Show("MessageBox MsgBox", "MsgBox caption");
   110 
   111                 //MessageBox.Show("Ok");
   112                 marqueeLabelTop.Font = fontDialog.Font;
   113                 marqueeLabelBottom.Font = fontDialog.Font;
   114                 cds.Font = fontDialog.Font;
   115                 Properties.Settings.Default.Save();
   116                 //
   117                 CheckFontHeight();
   118             }
   119         }
   120 
   121         /// <summary>
   122         ///
   123         /// </summary>
   124         void CheckFontHeight()
   125         {
   126             //Show font height and width
   127             labelFontHeight.Text = "Font height: " + cds.Font.Height;
   128             float charWidth = IsFixedWidth(cds.Font);
   129             if (charWidth == 0.0f)
   130             {
   131                 labelFontWidth.Visible = false;
   132             }
   133             else
   134             {
   135                 labelFontWidth.Visible = true;
   136                 labelFontWidth.Text = "Font width: " + charWidth;
   137             }
   138 
   139             //Now check font height and show a warning if needed.
   140             if (marqueeLabelBottom.Font.Height > marqueeLabelBottom.Height)
   141             {
   142                 labelWarning.Text = "WARNING: Selected font is too height by " + (marqueeLabelBottom.Font.Height - marqueeLabelBottom.Height) + " pixels!";
   143                 labelWarning.Visible = true;
   144             }
   145             else
   146             {
   147                 labelWarning.Visible = false;
   148             }
   149 
   150         }
   151 
   152         private void buttonCapture_Click(object sender, EventArgs e)
   153         {
   154             System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height);
   155             tableLayoutPanel.DrawToBitmap(bmp, tableLayoutPanel.ClientRectangle);
   156             //Bitmap bmpToSave = new Bitmap(bmp);
   157             bmp.Save("D:\\capture.png");
   158 
   159             marqueeLabelTop.Text = "Sweet";
   160 
   161             /*
   162             string outputFileName = "d:\\capture.png";
   163             using (MemoryStream memory = new MemoryStream())
   164             {
   165                 using (FileStream fs = new FileStream(outputFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite))
   166                 {
   167                     bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Png);
   168                     byte[] bytes = memory.ToArray();
   169                     fs.Write(bytes, 0, bytes.Length);
   170                 }
   171             }
   172              */
   173 
   174         }
   175 
   176         private void CheckForRequestResults()
   177         {
   178             if (iDisplay.IsRequestPending())
   179             {
   180                 switch (iDisplay.AttemptRequestCompletion())
   181                 {
   182                     case Display.TMiniDisplayRequest.EMiniDisplayRequestFirmwareRevision:
   183                         toolStripStatusLabelConnect.Text += " v" + iDisplay.FirmwareRevision();
   184                         //Issue next request then
   185                         iDisplay.RequestPowerSupplyStatus();
   186                         break;
   187 
   188                     case Display.TMiniDisplayRequest.EMiniDisplayRequestPowerSupplyStatus:
   189                         if (iDisplay.PowerSupplyStatus())
   190                         {
   191                             toolStripStatusLabelPower.Text = "ON";
   192                         }
   193                         else
   194                         {
   195                             toolStripStatusLabelPower.Text = "OFF";
   196                         }
   197                         //Issue next request then
   198                         iDisplay.RequestDeviceId();
   199                         break;
   200 
   201                     case Display.TMiniDisplayRequest.EMiniDisplayRequestDeviceId:
   202                         toolStripStatusLabelConnect.Text += " - " + iDisplay.DeviceId();
   203                         //No more request to issue
   204                         break;
   205                 }
   206             }
   207         }
   208 
   209 
   210         public delegate uint ColorProcessingDelegate(uint aPixel);
   211 
   212         public static uint ColorUntouched(uint aPixel)
   213         {
   214             return aPixel;
   215         }
   216 
   217         public static uint ColorInversed(uint aPixel)
   218         {
   219             return ~aPixel;
   220         }
   221 
   222         public delegate int CoordinateTranslationDelegate(System.Drawing.Bitmap aBmp, int aInt);
   223 
   224         public static int ScreenReversedX(System.Drawing.Bitmap aBmp, int aX)
   225         {
   226             return aBmp.Width - aX - 1;
   227         }
   228 
   229         public int ScreenReversedY(System.Drawing.Bitmap aBmp, int aY)
   230         {
   231             return iBmp.Height - aY - 1;
   232         }
   233 
   234         public int ScreenX(System.Drawing.Bitmap aBmp, int aX)
   235         {
   236             return aX;
   237         }
   238 
   239         public int ScreenY(System.Drawing.Bitmap aBmp, int aY)
   240         {
   241             return aY;
   242         }
   243 
   244 
   245         //This is our timer tick responsible to perform our render
   246         private void timer_Tick(object sender, EventArgs e)
   247         {
   248             //Update our animations
   249             DateTime NewTickTime = DateTime.Now;
   250 
   251             marqueeLabelTop.UpdateAnimation(LastTickTime, NewTickTime);
   252             marqueeLabelBottom.UpdateAnimation(LastTickTime, NewTickTime);
   253 
   254             //Update our display
   255             if (iDisplay.IsOpen())
   256             {
   257                 CheckForRequestResults();
   258 
   259                 //Draw to bitmap
   260                 if (iCreateBitmap)
   261                 {
   262                     iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   263                 }
   264                 tableLayoutPanel.DrawToBitmap(iBmp, tableLayoutPanel.ClientRectangle);
   265                 //iBmp.Save("D:\\capture.png");
   266 
   267 
   268                 //Select our pixel processing routine
   269                 ColorProcessingDelegate colorFx;
   270 
   271                 if (cds.InverseColors)
   272                 {
   273                     colorFx = ColorInversed;
   274                 }
   275                 else
   276                 {
   277                     colorFx = ColorUntouched;
   278                 }
   279 
   280                 //Select proper coordinate translation functions
   281                 //We used delegate/function pointer to support reverse screen without doing an extra test on each pixels
   282                 CoordinateTranslationDelegate screenX;
   283                 CoordinateTranslationDelegate screenY;
   284 
   285                 if (cds.ReverseScreen)
   286                 {
   287                     screenX = ScreenReversedX;
   288                     screenY = ScreenReversedY;
   289                 }
   290                 else
   291                 {
   292                     screenX = ScreenX;
   293                     screenY = ScreenY;
   294                 }
   295 
   296                 //Send it to our display
   297                 for (int i = 0; i < iBmp.Width; i++)
   298                 {
   299                     for (int j = 0; j < iBmp.Height; j++)
   300                     {
   301                         unchecked
   302                         {
   303                             uint color = (uint)iBmp.GetPixel(i, j).ToArgb();
   304                             //Apply color effects
   305                             color = colorFx(color);
   306                             //For some reason when the app is minimized in the task bar only the alpha of our color is set.
   307                             //Thus that strange test for rendering to work both when the app is in the task bar and when it isn't.
   308                             //iDisplay.SetPixel(screenX(iBmp, i), screenY(iBmp, j), Convert.ToInt32(!(color != 0xFF000000)));
   309                             iDisplay.SetPixel(screenX(iBmp, i), screenY(iBmp, j), color);
   310                         }
   311                     }
   312                 }
   313 
   314                 iDisplay.SwapBuffers();
   315 
   316             }
   317 
   318             //Compute instant FPS
   319             toolStripStatusLabelFps.Text = (1.0/NewTickTime.Subtract(LastTickTime).TotalSeconds).ToString("F0") + " / " + (1000/timer.Interval).ToString() + " FPS";
   320 
   321             LastTickTime = NewTickTime;
   322 
   323         }
   324 
   325         private void OpenDisplayConnection()
   326         {
   327             CloseDisplayConnection();
   328 
   329             if (iDisplay.Open((Display.TMiniDisplayType)cds.DisplayType))
   330             {
   331                 UpdateStatus();
   332                 iDisplay.RequestFirmwareRevision();
   333             }
   334             else
   335             {
   336                 UpdateStatus();
   337                 toolStripStatusLabelConnect.Text = "Connection error";
   338             }
   339         }
   340 
   341         private void CloseDisplayConnection()
   342         {
   343             iDisplay.Close();
   344             UpdateStatus();
   345         }
   346 
   347         private void buttonOpen_Click(object sender, EventArgs e)
   348         {
   349             OpenDisplayConnection();
   350         }
   351 
   352         private void buttonClose_Click(object sender, EventArgs e)
   353         {
   354             CloseDisplayConnection();
   355         }
   356 
   357         private void buttonClear_Click(object sender, EventArgs e)
   358         {
   359             iDisplay.Clear();
   360             iDisplay.SwapBuffers();
   361         }
   362 
   363         private void buttonFill_Click(object sender, EventArgs e)
   364         {
   365             iDisplay.Fill();
   366             iDisplay.SwapBuffers();
   367         }
   368 
   369         private void trackBarBrightness_Scroll(object sender, EventArgs e)
   370         {
   371             cds.Brightness = trackBarBrightness.Value;
   372             Properties.Settings.Default.Save();
   373             iDisplay.SetBrightness(trackBarBrightness.Value);
   374 
   375         }
   376 
   377 
   378         /// <summary>
   379         /// CDS stands for Current Display Settings
   380         /// </summary>
   381         private DisplaySettings cds
   382         {
   383             get
   384             {
   385                 DisplaysSettings settings = Properties.Settings.Default.DisplaySettings;
   386                 if (settings == null)
   387                 {
   388                     settings = new DisplaysSettings();
   389                     settings.Init();
   390                     Properties.Settings.Default.DisplaySettings = settings;
   391                 }
   392 
   393                 //Make sure all our settings have been created
   394                 while (settings.Displays.Count <= Properties.Settings.Default.CurrentDisplayIndex)
   395                 {
   396                     settings.Displays.Add(new DisplaySettings());
   397                 }
   398 
   399                 DisplaySettings displaySettings = settings.Displays[Properties.Settings.Default.CurrentDisplayIndex];
   400                 return displaySettings;
   401             }
   402         }
   403 
   404         /// <summary>
   405         /// Check if the given font has a fixed character pitch.
   406         /// </summary>
   407         /// <param name="ft"></param>
   408         /// <returns>0.0f if this is not a monospace font, otherwise returns the character width.</returns>
   409         public float IsFixedWidth(Font ft)
   410         {
   411             Graphics g = CreateGraphics();
   412             char[] charSizes = new char[] { 'i', 'a', 'Z', '%', '#', 'a', 'B', 'l', 'm', ',', '.' };
   413             float charWidth = g.MeasureString("I", ft, Int32.MaxValue, StringFormat.GenericTypographic).Width;
   414 
   415             bool fixedWidth = true;
   416 
   417             foreach (char c in charSizes)
   418                 if (g.MeasureString(c.ToString(), ft, Int32.MaxValue, StringFormat.GenericTypographic).Width != charWidth)
   419                     fixedWidth = false;
   420 
   421             if (fixedWidth)
   422             {
   423                 return charWidth;
   424             }
   425 
   426             return 0.0f;
   427         }
   428 
   429         private void UpdateStatus()
   430         {
   431             //Synchronize UI with settings
   432             //Load settings
   433 
   434             checkBoxShowBorders.Checked = cds.ShowBorders;
   435             tableLayoutPanel.CellBorderStyle = (cds.ShowBorders ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
   436             marqueeLabelTop.Font = cds.Font;
   437             marqueeLabelBottom.Font = cds.Font;
   438             CheckFontHeight();
   439             checkBoxConnectOnStartup.Checked = Properties.Settings.Default.DisplayConnectOnStartup;
   440             checkBoxReverseScreen.Checked = cds.ReverseScreen;
   441             checkBoxInverseColors.Checked = cds.InverseColors;
   442             comboBoxDisplayType.SelectedIndex = cds.DisplayType;
   443             timer.Interval = cds.TimerInterval;
   444             maskedTextBoxTimerInterval.Text = cds.TimerInterval.ToString();
   445 
   446 
   447             if (iDisplay.IsOpen())
   448             {
   449                 //Only setup brightness if display is open
   450                 trackBarBrightness.Minimum = iDisplay.MinBrightness();
   451                 trackBarBrightness.Maximum = iDisplay.MaxBrightness();
   452                 trackBarBrightness.Value = cds.Brightness;
   453                 trackBarBrightness.LargeChange = Math.Max(1, (iDisplay.MaxBrightness() - iDisplay.MinBrightness()) / 5);
   454                 trackBarBrightness.SmallChange = 1;
   455                 iDisplay.SetBrightness(cds.Brightness);
   456                 //
   457                 buttonFill.Enabled = true;
   458                 buttonClear.Enabled = true;
   459                 buttonOpen.Enabled = false;
   460                 buttonClose.Enabled = true;
   461                 trackBarBrightness.Enabled = true;
   462                 toolStripStatusLabelConnect.Text = "Connected - " + iDisplay.Vendor() + " - " + iDisplay.Product();
   463                 //+ " - " + iDisplay.SerialNumber();
   464 
   465                 if (iDisplay.SupportPowerOnOff())
   466                 {
   467                     buttonPowerOn.Enabled = true;
   468                     buttonPowerOff.Enabled = true;
   469                 }
   470                 else
   471                 {
   472                     buttonPowerOn.Enabled = false;
   473                     buttonPowerOff.Enabled = false;
   474                 }
   475 
   476                 if (iDisplay.SupportClock())
   477                 {
   478                     buttonShowClock.Enabled = true;
   479                     buttonHideClock.Enabled = true;
   480                 }
   481                 else
   482                 {
   483                     buttonShowClock.Enabled = false;
   484                     buttonHideClock.Enabled = false;
   485                 }
   486             }
   487             else
   488             {
   489                 buttonFill.Enabled = false;
   490                 buttonClear.Enabled = false;
   491                 buttonOpen.Enabled = true;
   492                 buttonClose.Enabled = false;
   493                 trackBarBrightness.Enabled = false;
   494                 buttonPowerOn.Enabled = false;
   495                 buttonPowerOff.Enabled = false;
   496                 buttonShowClock.Enabled = false;
   497                 buttonHideClock.Enabled = false;
   498                 toolStripStatusLabelConnect.Text = "Disconnected";
   499                 toolStripStatusLabelPower.Text = "N/A";
   500             }
   501         }
   502 
   503 
   504 
   505         private void checkBoxShowBorders_CheckedChanged(object sender, EventArgs e)
   506         {
   507             //Save our show borders setting
   508             tableLayoutPanel.CellBorderStyle = (checkBoxShowBorders.Checked ? TableLayoutPanelCellBorderStyle.Single : TableLayoutPanelCellBorderStyle.None);
   509             cds.ShowBorders = checkBoxShowBorders.Checked;
   510             Properties.Settings.Default.Save();
   511             CheckFontHeight();
   512         }
   513 
   514         private void checkBoxConnectOnStartup_CheckedChanged(object sender, EventArgs e)
   515         {
   516             //Save our connect on startup setting
   517             Properties.Settings.Default.DisplayConnectOnStartup = checkBoxConnectOnStartup.Checked;
   518             Properties.Settings.Default.Save();
   519         }
   520 
   521         private void checkBoxReverseScreen_CheckedChanged(object sender, EventArgs e)
   522         {
   523             //Save our reverse screen setting
   524             cds.ReverseScreen = checkBoxReverseScreen.Checked;
   525             Properties.Settings.Default.Save();
   526         }
   527 
   528         private void checkBoxInverseColors_CheckedChanged(object sender, EventArgs e)
   529         {
   530             //Save our inverse colors setting
   531             cds.InverseColors = checkBoxInverseColors.Checked;
   532             Properties.Settings.Default.Save();
   533         }
   534 
   535         private void MainForm_Resize(object sender, EventArgs e)
   536         {
   537             if (WindowState == FormWindowState.Minimized)
   538             {
   539                 // Do some stuff
   540                 //iBmp = new System.Drawing.Bitmap(tableLayoutPanel.Width, tableLayoutPanel.Height, PixelFormat.Format32bppArgb);
   541                 iCreateBitmap = true;
   542             }
   543         }
   544 
   545         private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
   546         {
   547             StopServer();
   548             e.Cancel = iClosing;
   549         }
   550 
   551         public void StartServer()
   552         {
   553             iServiceHost = new ServiceHost
   554                 (
   555                     typeof(Session),
   556                     new Uri[] { new Uri("net.tcp://localhost:8001/") }
   557                 );
   558 
   559             iServiceHost.AddServiceEndpoint(typeof(IService), new NetTcpBinding(SecurityMode.None, true), "DisplayService");
   560             iServiceHost.Open();
   561         }
   562 
   563         public void StopServer()
   564         {
   565             if (iClients.Count > 0 && !iClosing)
   566             {
   567                 //Tell our clients
   568                 iClosing = true;
   569                 BroadcastCloseEvent();
   570             }
   571             else if (iClosing)
   572             {
   573                 if (MessageBox.Show("Force exit?", "Waiting for clients...", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
   574                 {
   575                     iClosing = false; //We make sure we force close if asked twice
   576                 }
   577             }
   578             else
   579             {
   580                 //We removed that as it often lags for some reason
   581                 //iServiceHost.Close();
   582             }
   583         }
   584 
   585         public void BroadcastCloseEvent()
   586         {
   587             Trace.TraceInformation("BroadcastCloseEvent - start");
   588 
   589             var inactiveClients = new List<string>();
   590             foreach (var client in iClients)
   591             {
   592                 //if (client.Key != eventData.ClientName)
   593                 {
   594                     try
   595                     {
   596                         Trace.TraceInformation("BroadcastCloseEvent - " + client.Key);
   597                         client.Value.Callback.OnCloseOrder(/*eventData*/);
   598                     }
   599                     catch (Exception ex)
   600                     {
   601                         inactiveClients.Add(client.Key);
   602                     }
   603                 }
   604             }
   605 
   606             if (inactiveClients.Count > 0)
   607             {
   608                 foreach (var client in inactiveClients)
   609                 {
   610                     iClients.Remove(client);
   611                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(client, false)[0]);
   612                 }
   613             }
   614         }
   615 
   616         private void buttonStartClient_Click(object sender, EventArgs e)
   617         {
   618             Thread clientThread = new Thread(SharpDisplayClient.Program.Main);
   619             clientThread.Start();
   620             BringToFront();
   621         }
   622 
   623         private void buttonSuspend_Click(object sender, EventArgs e)
   624         {
   625             LastTickTime = DateTime.Now; //Reset timer to prevent jump
   626             timer.Enabled = !timer.Enabled;
   627             if (!timer.Enabled)
   628             {
   629                 buttonSuspend.Text = "Run";
   630             }
   631             else
   632             {
   633                 buttonSuspend.Text = "Pause";
   634             }
   635         }
   636 
   637         private void buttonCloseClients_Click(object sender, EventArgs e)
   638         {
   639             BroadcastCloseEvent();
   640         }
   641 
   642         private void treeViewClients_AfterSelect(object sender, TreeViewEventArgs e)
   643         {
   644 
   645         }
   646 
   647         //Delegates are used for our thread safe method
   648         public delegate void AddClientDelegate(string aSessionId, ICallback aCallback);
   649         public delegate void RemoveClientDelegate(string aSessionId);
   650         public delegate void SetTextDelegate(string SessionId, TextField aTextField);
   651         public delegate void SetTextsDelegate(string SessionId, System.Collections.Generic.IList<TextField> aTextFields);
   652         public delegate void SetClientNameDelegate(string aSessionId, string aName);
   653 
   654 
   655         /// <summary>
   656         ///
   657         /// </summary>
   658         /// <param name="aSessionId"></param>
   659         /// <param name="aCallback"></param>
   660         public void AddClientThreadSafe(string aSessionId, ICallback aCallback)
   661         {
   662             if (this.InvokeRequired)
   663             {
   664                 //Not in the proper thread, invoke ourselves
   665                 AddClientDelegate d = new AddClientDelegate(AddClientThreadSafe);
   666                 this.Invoke(d, new object[] { aSessionId, aCallback });
   667             }
   668             else
   669             {
   670                 //We are in the proper thread
   671                 //Add this session to our collection of clients
   672                 ClientData newClient = new ClientData(aSessionId, aCallback);
   673                 Program.iMainForm.iClients.Add(aSessionId, newClient);
   674                 //Add this session to our UI
   675                 UpdateClientTreeViewNode(newClient);
   676             }
   677         }
   678 
   679         /// <summary>
   680         ///
   681         /// </summary>
   682         /// <param name="aSessionId"></param>
   683         public void RemoveClientThreadSafe(string aSessionId)
   684         {
   685             if (this.InvokeRequired)
   686             {
   687                 //Not in the proper thread, invoke ourselves
   688                 RemoveClientDelegate d = new RemoveClientDelegate(RemoveClientThreadSafe);
   689                 this.Invoke(d, new object[] { aSessionId });
   690             }
   691             else
   692             {
   693                 //We are in the proper thread
   694                 //Remove this session from both client collection and UI tree view
   695                 if (Program.iMainForm.iClients.Keys.Contains(aSessionId))
   696                 {
   697                     Program.iMainForm.iClients.Remove(aSessionId);
   698                     Program.iMainForm.treeViewClients.Nodes.Remove(Program.iMainForm.treeViewClients.Nodes.Find(aSessionId, false)[0]);
   699                 }
   700 
   701                 if (iClosing && iClients.Count == 0)
   702                 {
   703                     //We were closing our form
   704                     //All clients are now closed
   705                     //Just resume our close operation
   706                     iClosing = false;
   707                     Close();
   708                 }
   709             }
   710         }
   711 
   712         /// <summary>
   713         ///
   714         /// </summary>
   715         /// <param name="aLineIndex"></param>
   716         /// <param name="aText"></param>
   717         public void SetTextThreadSafe(string aSessionId, TextField aTextField)
   718         {
   719             if (this.InvokeRequired)
   720             {
   721                 //Not in the proper thread, invoke ourselves
   722                 SetTextDelegate d = new SetTextDelegate(SetTextThreadSafe);
   723                 this.Invoke(d, new object[] { aSessionId, aTextField });
   724             }
   725             else
   726             {
   727                 ClientData client = iClients[aSessionId];
   728                 if (client != null)
   729                 {
   730                     //Make sure all our texts are in place
   731                     while (client.Texts.Count < (aTextField.Index + 1))
   732                     {
   733                         //Add a text field with proper index
   734                         client.Texts.Add(new TextField(client.Texts.Count));
   735                     }
   736                     client.Texts[aTextField.Index] = aTextField;
   737 
   738                     //We are in the proper thread
   739                     //Only support two lines for now
   740                     if (aTextField.Index == 0)
   741                     {
   742                         marqueeLabelTop.Text = aTextField.Text;
   743                         marqueeLabelTop.TextAlign = aTextField.Alignment;
   744                     }
   745                     else if (aTextField.Index == 1)
   746                     {
   747                         marqueeLabelBottom.Text = aTextField.Text;
   748                         marqueeLabelBottom.TextAlign = aTextField.Alignment;
   749                     }
   750 
   751 
   752                     UpdateClientTreeViewNode(client);
   753                 }
   754             }
   755         }
   756 
   757         /// <summary>
   758         ///
   759         /// </summary>
   760         /// <param name="aTexts"></param>
   761         public void SetTextsThreadSafe(string aSessionId, System.Collections.Generic.IList<TextField> aTextFields)
   762         {
   763             if (this.InvokeRequired)
   764             {
   765                 //Not in the proper thread, invoke ourselves
   766                 SetTextsDelegate d = new SetTextsDelegate(SetTextsThreadSafe);
   767                 this.Invoke(d, new object[] { aSessionId, aTextFields });
   768             }
   769             else
   770             {
   771                 //We are in the proper thread
   772                 ClientData client = iClients[aSessionId];
   773                 if (client != null)
   774                 {
   775                     //Populate our client with the given text fields
   776                     int j = 0;
   777                     foreach (TextField textField in aTextFields)
   778                     {
   779                         if (client.Texts.Count < (j + 1))
   780                         {
   781                             client.Texts.Add(textField);
   782                         }
   783                         else
   784                         {
   785                             client.Texts[j] = textField;
   786                         }
   787                         j++;
   788                     }
   789                     //Only support two lines for now
   790                     for (int i = 0; i < aTextFields.Count; i++)
   791                     {
   792                         if (aTextFields[i].Index == 0)
   793                         {
   794                             marqueeLabelTop.Text = aTextFields[i].Text;
   795                             marqueeLabelTop.TextAlign = aTextFields[i].Alignment;
   796                         }
   797                         else if (aTextFields[i].Index == 1)
   798                         {
   799                             marqueeLabelBottom.Text = aTextFields[i].Text;
   800                             marqueeLabelBottom.TextAlign = aTextFields[i].Alignment;
   801                         }
   802                     }
   803 
   804 
   805                     UpdateClientTreeViewNode(client);
   806                 }
   807             }
   808         }
   809 
   810 
   811         /// <summary>
   812         ///
   813         /// </summary>
   814         /// <param name="aSessionId"></param>
   815         /// <param name="aName"></param>
   816         public void SetClientNameThreadSafe(string aSessionId, string aName)
   817         {
   818             if (this.InvokeRequired)
   819             {
   820                 //Not in the proper thread, invoke ourselves
   821                 SetClientNameDelegate d = new SetClientNameDelegate(SetClientNameThreadSafe);
   822                 this.Invoke(d, new object[] { aSessionId, aName });
   823             }
   824             else
   825             {
   826                 //We are in the proper thread
   827                 //Get our client
   828                 ClientData client = iClients[aSessionId];
   829                 if (client != null)
   830                 {
   831                     //Set its name
   832                     client.Name = aName;
   833                     //Update our tree-view
   834                     UpdateClientTreeViewNode(client);
   835                 }
   836             }
   837         }
   838 
   839         /// <summary>
   840         ///
   841         /// </summary>
   842         /// <param name="aClient"></param>
   843         private void UpdateClientTreeViewNode(ClientData aClient)
   844         {
   845             if (aClient == null)
   846             {
   847                 return;
   848             }
   849 
   850             TreeNode node = null;
   851             //Check that our client node already exists
   852             //Get our client root node using its key which is our session ID
   853             TreeNode[] nodes = treeViewClients.Nodes.Find(aClient.SessionId, false);
   854             if (nodes.Count()>0)
   855             {
   856                 //We already have a node for that client
   857                 node = nodes[0];
   858                 //Clear children as we are going to recreate them below
   859                 node.Nodes.Clear();
   860             }
   861             else
   862             {
   863                 //Client node does not exists create a new one
   864                 treeViewClients.Nodes.Add(aClient.SessionId, aClient.SessionId);
   865                 node = treeViewClients.Nodes.Find(aClient.SessionId, false)[0];
   866             }
   867 
   868             if (node != null)
   869             {
   870                 //Change its name
   871                 if (aClient.Name != "")
   872                 {
   873                     //We have a name, us it as text for our root node
   874                     node.Text = aClient.Name;
   875                     //Add a child with SessionId
   876                     node.Nodes.Add(new TreeNode(aClient.SessionId));
   877                 }
   878                 else
   879                 {
   880                     //No name, use session ID instead
   881                     node.Text = aClient.SessionId;
   882                 }
   883 
   884                 if (aClient.Texts.Count > 0)
   885                 {
   886                     //Create root node for our texts
   887                     TreeNode textsRoot = new TreeNode("Text");
   888                     node.Nodes.Add(textsRoot);
   889                     //For each text add a new entry
   890                     foreach (TextField field in aClient.Texts)
   891                     {
   892                         textsRoot.Nodes.Add(new TreeNode(field.Text));
   893                     }
   894                 }
   895 
   896                 node.ExpandAll();
   897             }
   898         }
   899 
   900         private void buttonAddRow_Click(object sender, EventArgs e)
   901         {
   902             if (tableLayoutPanel.RowCount < 6)
   903             {
   904                 tableLayoutPanel.RowCount++;
   905                 CheckFontHeight();
   906             }
   907         }
   908 
   909         private void buttonRemoveRow_Click(object sender, EventArgs e)
   910         {
   911             if (tableLayoutPanel.RowCount > 1)
   912             {
   913                 tableLayoutPanel.RowCount--;
   914                 CheckFontHeight();
   915             }
   916         }
   917 
   918         private void buttonAddColumn_Click(object sender, EventArgs e)
   919         {
   920             if (tableLayoutPanel.ColumnCount < 8)
   921             {
   922                 tableLayoutPanel.ColumnCount++;
   923                 //CheckFontHeight();
   924             }
   925         }
   926 
   927         private void buttonRemoveColumn_Click(object sender, EventArgs e)
   928         {
   929             if (tableLayoutPanel.ColumnCount > 1)
   930             {
   931                 tableLayoutPanel.ColumnCount--;
   932                 //CheckFontHeight();
   933             }
   934         }
   935 
   936         private void buttonAlignLeft_Click(object sender, EventArgs e)
   937         {
   938             marqueeLabelTop.TextAlign = ContentAlignment.MiddleLeft;
   939             marqueeLabelBottom.TextAlign = ContentAlignment.MiddleLeft;
   940         }
   941 
   942         private void buttonAlignCenter_Click(object sender, EventArgs e)
   943         {
   944             marqueeLabelTop.TextAlign = ContentAlignment.MiddleCenter;
   945             marqueeLabelBottom.TextAlign = ContentAlignment.MiddleCenter;
   946         }
   947 
   948         private void buttonAlignRight_Click(object sender, EventArgs e)
   949         {
   950             marqueeLabelTop.TextAlign = ContentAlignment.MiddleRight;
   951             marqueeLabelBottom.TextAlign = ContentAlignment.MiddleRight;
   952         }
   953 
   954         private void comboBoxDisplayType_SelectedIndexChanged(object sender, EventArgs e)
   955         {
   956             Properties.Settings.Default.CurrentDisplayIndex = comboBoxDisplayType.SelectedIndex;
   957             cds.DisplayType = comboBoxDisplayType.SelectedIndex;
   958             Properties.Settings.Default.Save();
   959             if (iDisplay.IsOpen())
   960             {
   961                 OpenDisplayConnection();
   962             }
   963             else
   964             {
   965                 UpdateStatus();
   966             }
   967         }
   968 
   969 
   970         private void maskedTextBoxTimerInterval_TextChanged(object sender, EventArgs e)
   971         {
   972             if (maskedTextBoxTimerInterval.Text != "")
   973             {
   974                 int interval = Convert.ToInt32(maskedTextBoxTimerInterval.Text);
   975 
   976                 if (interval > 0)
   977                 {
   978                     timer.Interval = interval;
   979                     cds.TimerInterval = timer.Interval;
   980                     Properties.Settings.Default.Save();
   981                 }
   982             }
   983         }
   984 
   985         private void buttonPowerOn_Click(object sender, EventArgs e)
   986         {
   987             iDisplay.PowerOn();
   988         }
   989 
   990         private void buttonPowerOff_Click(object sender, EventArgs e)
   991         {
   992             iDisplay.PowerOff();
   993         }
   994 
   995         private void buttonShowClock_Click(object sender, EventArgs e)
   996         {
   997             iDisplay.ShowClock();
   998         }
   999 
  1000         private void buttonHideClock_Click(object sender, EventArgs e)
  1001         {
  1002             iDisplay.HideClock();
  1003         }
  1004 
  1005     }
  1006 
  1007     /// <summary>
  1008     /// A UI thread copy of a client relevant data.
  1009     /// Keeping this copy in the UI thread helps us deal with threading issues.
  1010     /// </summary>
  1011     public class ClientData
  1012     {
  1013         public ClientData(string aSessionId, ICallback aCallback)
  1014         {
  1015             SessionId = aSessionId;
  1016             Name = "";
  1017             Texts = new List<TextField>();
  1018             Callback = aCallback;
  1019         }
  1020 
  1021         public string SessionId { get; set; }
  1022         public string Name { get; set; }
  1023         public List<TextField> Texts { get; set; }
  1024         public ICallback Callback { get; set; }
  1025     }
  1026 }