vbAccelerator - Contents of code file: FontCombo_FontComboBox.cs

using System;
using System.Drawing;
using System.Drawing.Text;

using vbAccelerator.Components.Controls;
using vbAccelerator.Components.Collections;
using vbAccelerator.Components.Text;

namespace vbAccelerator.Components.Controls
{
   /// <summary>
   /// Summary description for FontCombo.
   /// </summary>
   public class FontComboBox : IconComboBox
   {
      /// <summary>
      /// Collection of Most-Recently used fonts
      /// </summary>
      private MRUFonts mruFonts;
      /// <summary>
      /// Number of MRU fonts currently displayed
      /// </summary>
      private int mruItemCount = 0;
      /// <summary>
      /// Enable state before/whilst background thread loading in progress
      /// </summary>
      private bool origEnable = true;
      /// <summary>
      /// Whether the actual control's enable state can be modified
      /// </summary>
      private bool allowEnable = false;
      /// <summary>
      /// An interlock which prevents events from being raised when items
      /// are being added or swapped into the MRU font list
      /// </summary>
      private bool interlock = false;

      #region Events
      /// <summary>
      /// Raised when the font combo box has been populated.  Fonts are
      /// populated asynchronously on a background thread.
      /// </summary>
      public event EventHandler Populated;
      #endregion

      /// <summary>
      /// Constructs a new instance of this class.
      /// </summary>
      public FontComboBox() : base()
      {
         base.MaxDropDownItems = 16;
         base.DropDownWidth = 300;
         base.AutoComplete = true;
         mruFonts = new MRUFonts(this);
      }

      /// <summary>
      /// Gets/sets whether the control is enabled.  Whilst font loading
      /// is occuring on a background thread, changes to this property
      /// won't appear until loading has completed.
      /// </summary>
      public new bool Enabled
      {
         get
         {
            if (!allowEnable)
            {
               return origEnable;
            }
            else
            {
               return base.Enabled;
            }
         }
         set
         {
            if (!allowEnable)
            {
               origEnable = value;
            }
            else
            {
               base.Enabled = value;
            }
         }
      }

      /// <summary>
      /// Raises the <see cref="HandleCreated"/> event and populates
      /// the combo box with fonts.
      /// </summary>
      /// <param name="e">Not used</param>
      protected override void OnHandleCreated(EventArgs e)
      {
         base.OnHandleCreated(e);         

         LoadFonts();
      }

      /// <summary>
      /// Raises the <see cref="SelectedIndexChanged"/> event,
      /// also the MRU cache is correctly populated.
      /// </summary>
      /// <param name="e"></param>
      protected override void OnSelectedIndexChanged(EventArgs e)
      {
         if (!interlock)
         {
            base.OnSelectedIndexChanged(e);

            if ((base.SelectedIndex > -1) && (!base.DroppedDown))
            {
               addMRUFont(base.SelectedItem.ToString());
            }
         }
      }

      /// <summary>
      /// Gets the collection of most-recently used fonts in the control.
      /// </summary>
      public MRUFonts MostRecentlyUsedFonts
      {
         get
         {
            return this.mruFonts;
         }
      }

      /// <summary>
      /// Adds a font family to the Most-Recently Used
      /// font cache.
      /// </summary>
      /// <param name="familyName">Font family name to add</param>
      private void addMRUFont(string familyName)
      {
         interlock = true;

         if (mruFonts.Size > 0)
         {
            int presentIndex = -1;
            for (int i = 0; i < mruItemCount; i++)
            {
               IconComboItem ici = (IconComboItem) base.Items[i];
               if (ici.Text.ToString().Equals(familyName))
               {
                  presentIndex = i;
                  break;
               }
            }

            int oldMruItemCount = mruItemCount;
            if (presentIndex > -1)
            {
               // remove existing item:
               base.Items.RemoveAt(presentIndex);
               mruItemCount--;
            }
            else if ((this.mruFonts.Size == mruItemCount))
            {            
               // remove the last MRU font:
               base.Items.RemoveAt(mruItemCount - 1);
               mruItemCount--;
            }
            
            // Find the item:
            int realItem = base.FindStringExact(familyName);
            IconComboItem realIconComboItem = (IconComboItem)
             base.Items[realItem];

            // insert the item:         
            IconComboItem mruItem = new IconComboItem();
            mruItem.Text = realIconComboItem.Text;
            mruItem.Font = realIconComboItem.Font;
            mruItem.Tag = "MRU";
            base.Items.Insert(0, mruItem);
      
            mruItemCount++;
            
            if (oldMruItemCount > 0)
            {
               // remove underscore:
               ((IconComboItem) base.Items[oldMruItemCount - 1]).LineBelow =
                false;
            }
            ((IconComboItem) base.Items[mruItemCount - 1]).LineBelow = true;

            mruFonts.Add(familyName);

            base.SelectedIndex = 0;
         }

         interlock = false;
      }

      /// <summary>
      /// Reparses the contents of the MRU Font object after it
      /// has been updated by the user of the control.
      /// </summary>
      protected virtual void OnMRUChanged()
      {
         if (!interlock)
         {
            // redraw the MRU Fonts based on the newly specified ones.
            interlock = true;

            base.BeginUpdate();
            for (int i = 0; i < mruItemCount; i++)
            {
               base.Items.Remove(0);
            }
            mruItemCount = 0;

            if (mruFonts.Size > 0)
            {
               for (int i = mruFonts.Size - 1; i >= 0; i--)
               {
                  IconComboItem ici = new IconComboItem();

                  int realItem = base.FindStringExact(mruFonts[i]);
                  IconComboItem realIconComboItem = (IconComboItem)
                   base.Items[realItem];
                  
                  // insert the item:         
                  IconComboItem mruItem = new IconComboItem();
                  mruItem.Text = realIconComboItem.Text;
                  mruItem.Font = realIconComboItem.Font;
                  mruItem.Tag = "MRU";
                  base.Items.Insert(0, ici);
               }
               ((IconComboItem) base.Items[mruItemCount - 1]).LineBelow = true;
            }

            base.EndUpdate();

            interlock = false;
         }
      }

      /// <summary>
      /// Initiates a load of all fonts asynchronously.  The
      /// <see cref="Populated"/> event will be fired once
      /// all fonts are available.  Called automatically
      /// whenever the control is created.
      /// </summary>
      public void LoadFonts()
      {

         if (this.Site != null)
         {
            if (this.Site.DesignMode)
            {
               return;
            }
         }

         origEnable = base.Enabled;
         base.Enabled = false;
         allowEnable = false;
         interlock = true;
         
         base.Items.Clear();

         // run font population on a background thread
         PopulateFontComboHandler populator = new
          PopulateFontComboHandler(PopulateFontCombo);
         populator.BeginInvoke(null, null);

      }

      #region Asynchronous Font Population
      private delegate void PopulateFontComboHandler();

      private void PopulateFontCombo()
      {

         // This graphics object is used for determining the Panose
         // number of the font
         Graphics graphics = Graphics.FromHwnd(base.Handle);

         // Get the collection of installed fonts:
         InstalledFontCollection fonts = new InstalledFontCollection();

         // For each item:
         foreach (FontFamily family in fonts.Families)
         {
            // Find which style can be rendered (if any!)
            Font theFont = null;
            if (family.IsStyleAvailable(FontStyle.Regular))
            {
               theFont = new Font(family.Name, 12);
            }
            else if (family.IsStyleAvailable(FontStyle.Bold))
            {
               theFont = new Font(family.Name, 12, FontStyle.Bold);
            }
            else if (family.IsStyleAvailable(FontStyle.Italic))
            {
               theFont = new Font(family.Name, 12, FontStyle.Italic);
            }
            
            // Here you could still add the item even if there are no
            // styles you can render it in (although that probably
            // wouldn't be much use...)
            if (theFont != null)
            {
               // Create a new item:
               IconComboItem ici = new IconComboItem();
               // Set the text:
               ici.Text = family.Name;

               // Set the font to display the item in.
               // Don't try and display fonts like Wingdings using 
               // the font itself:
               if (FontUtility.PanoseFontFamilyType(graphics, theFont) != 
                  FontUtility.PanoseFontFamilyTypes.PAN_FAMILY_PICTORIAL)
               {                                 
                  ici.Font = theFont;
               }
               base.Items.Add(ici);
            }            
         }
         graphics.Dispose();

         PopulateFontComboCompleteHandler complete = new
          PopulateFontComboCompleteHandler(PopulateFontComboComplete);
         complete.BeginInvoke(null, null);

      }

      private delegate void PopulateFontComboCompleteHandler();

      private void PopulateFontComboComplete()
      {
         // Shift the MRU fonts to the top of the list:
         for (int i = mruFonts.Count - 1; i >= 0; i--)
         {
            addMRUFont(mruFonts[i]);
         }
         base.Enabled = origEnable;
         allowEnable = true;
         interlock = false;

         OnPopulateComplete(new EventArgs());

      }
      #endregion

      /// <summary>
      /// Raises the <see cref="Populated"/> event.
      /// </summary>
      /// <param name="e">Not used.</param>
      protected virtual void OnPopulateComplete(EventArgs e)
      {
         if (this.Populated != null)
         {
            this.Populated(this, e);
         }
      }


      /// <summary>
      /// Most Recently Used fonts collection associated with 
      /// a <see cref="FontComboBox"/> control.
      /// </summary>
      [SerializableAttribute()]
      public class MRUFonts : MRUQueue
      {
         /// <summary>
         /// The owning control for this collection
         /// </summary>
         private FontComboBox owner = null;

         /// <summary>
         /// Constructs a new instance of this class, associating
         /// it with the owning <see cref="FontComboBox"/>.  Used
         /// internally by the <see cref="FontComboBox"/> control.
         /// </summary>
         /// <param name="owner"></param>
         public MRUFonts(FontComboBox owner) : base()         
         {
            this.owner = owner;
         }

         /// <summary>
         /// Gets the font family at the specified index.
         /// </summary>
         public new string this[int index]
         {
            get
            {
               return (string) base.innerList[index];
            }
         }

         /// <summary>
         /// Adds the specified font name to the MRU queue.  If the queue
         /// already contains the specified item, it is shifted up
         /// to the first position.  Otherwise, it is added at the
         /// first position and any existing items are shuffled 
         /// downwards.
         /// </summary>
         /// <param name="fontName">fontName to add</param>
         public virtual void Add(string fontName)
         {
            base.Add(fontName);
            owner.OnMRUChanged();
         }

         /// <summary>
         /// Clears the MRU collection and notifies the control
         /// of the change.
         /// </summary>
         protected override void OnClear()
         {
            base.OnClear();
            owner.OnMRUChanged();
         }

         /// <summary>
         /// Changes the size of the MRU collection and notifies the
         /// control of the change.
         /// </summary>
         protected override void OnSizeChanged()
         {
            base.OnSizeChanged();
            owner.OnMRUChanged();
         }

      }

   }

}