context: #region License
  
  // Copyright (c) 2013, ClearCanvas Inc.
  // All rights reserved.
  // http://www.clearcanvas.ca
  //
  // This file is part of the ClearCanvas RIS/PACS open source project.
  //
  // The ClearCanvas RIS/PACS open source project is free software: you can
  // redistribute it and/or modify it under the terms of the GNU General Public
  // License as published by the Free Software Foundation, either version 3 of the
  // License, or (at your option) any later version.
  //
  // The ClearCanvas RIS/PACS open source project is distributed in the hope that it
  // will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
  // Public License for more details.
  //
  // You should have received a copy of the GNU General Public License along with
  // the ClearCanvas RIS/PACS open source project.  If not, see
  // <http://www.gnu.org/licenses/>.
  
  #endregion
  
  using System;
  using System.Collections;
  using System.Windows.Forms;
  using System.ComponentModel;
  using ClearCanvas.Common.Utilities;
  
  namespace ClearCanvas.Desktop.View.WinForms
  {
      /// <summary>
      /// Special combo-box that works with a <see cref="ISuggestionProvider"/> to populate the drop-down list
      /// with suggested items as the user types.
      /// </summary>
      public class SuggestComboBox : ComboBox
      {
          private ISuggestionProvider _suggestionProvider;
  
          private event EventHandler _valueChanged;
  
          #region Public properties
  
          /// <summary>
          /// Gets or sets the <see cref="ISuggestionProvider"/>.
          /// </summary>
          [Browsable(false)]
          public ISuggestionProvider SuggestionProvider
          {
              get { return _suggestionProvider; }
              set
              {
                  if (_suggestionProvider != null)
                      _suggestionProvider.SuggestionsProvided -= ItemsProvidedEventHandler;
  
                  _suggestionProvider = value;
  
                  if (_suggestionProvider != null)
                  {
                      _suggestionProvider.SuggestionsProvided += ItemsProvidedEventHandler;
  
  					// Bug #2222: only call SetQuery if this is a "simple" dropdown, in which case
  					// we need to populate the list immediately
  					// calling it for other styles causes behaviour described in #2222,
  					// and there is really no need to get suggestions until user actually types
  					if (this.DropDownStyle == ComboBoxStyle.Simple)
  						_suggestionProvider.SetQuery(this.Text);
                  }
              }
          }
  
          /// <summary>
          /// Gets or sets the current value of the control.
          /// </summary>
          [Browsable(false)]
          public object Value
          {
              get { return this.SelectedItem; }
              set
              {
                  if(!Equals(this.SelectedItem, value))
                  {
                      // in order to set the value, the Items collection must contain the value
                      // but if the value is null, can just use an empty list
                      // (also need to check for DBNull, for some stupid reason)
                      var items = new ArrayList();
                      if(value != null && value != DBNull.Value)
                      {
                          items.Add(value);
                      }
                      else
                      {
                          // if value is null or DBNull, clear the text
                          this.Text = null;
                      }
  
                      UpdateListItems(items);
                      this.SelectedItem = value;
  
                      OnValueChanged(EventArgs.Empty);
                  }
  
              }
          }
  
          /// <summary>
          /// Occurs when the <see cref="Value"/> property changes.
          /// </summary>
          [Browsable(false)]
          public event EventHandler ValueChanged
          {
              add { _valueChanged += value; }
              remove { _valueChanged -= value; }
          }
  
          #endregion
  
          #region Overrides and Helpers
  
          protected virtual void OnValueChanged(EventArgs args)
          {
              EventsHelper.Fire(_valueChanged, this, EventArgs.Empty);
          }
  
          protected override void OnCreateControl()
          {
              this.SelectedItem = null;
  
              base.OnCreateControl();
          }
  
          protected override void OnSelectionChangeCommitted(EventArgs e)
          {
              // there are 2 ways that the value can change
              // either the selection change is comitted, or the control loses focus
              OnValueChanged(EventArgs.Empty);
  
              base.OnSelectionChangeCommitted(e);
          }
  
          protected override void OnLeave(EventArgs e)
          {
  			UpdateSelectionFromText();
              base.OnLeave(e);
          }
  
  		// Defect #5876: SuggestComboxBox mishandles 'Esc' once input is resolved
  		// Override this method to clear the text when the ESC key is pressed.
  		// Otherwise the component hosting the combobox will eat the ESC key, 
  		// and close the component if the CancelButton is defined.
  		protected override bool ProcessCmdKey(ref Message msg, Keys k)
  		{
  			if (k == Keys.Escape)
  			{
  				this.Text = null;
  				UpdateSelectionFromText();
  				this.DroppedDown = false;
  				return true;
  			}
  
  			return base.ProcessCmdKey(ref msg, k);
  		}
  
          protected override void OnKeyDown(KeyEventArgs e)
          {
              switch (e.KeyCode)
              {
  				case Keys.Escape:
  					this.Text = null;
              		OnSelectionChangeCommitted(e);
              		break;
                  default:
                      break;
              }
              base.OnKeyDown(e);
          }
  
          protected override void OnTextUpdate(EventArgs e)
          {
              base.OnTextUpdate(e);
  			CursorReset();
              _suggestionProvider.SetQuery(this.Text);
          }
  
          protected override void Dispose(bool disposing)
          {
              if (disposing)
              {
                  // setting this to null will also unsubscribe from the last subscribed-to provider
                  this.SuggestionProvider = null;
              }
  
              base.Dispose(disposing);
          }
  
          private void ItemsProvidedEventHandler(object sender, SuggestionsProvidedEventArgs e)
          {
              // Remember the current text and selection start,
              // as they exists prior to modifying the items collection
              var curText = this.Text;
              var cursorPosition = this.SelectionStart;
  
              if (e.Items.Count == 0)
              {
  				try
  				{
  					this.DroppedDown = false;
  					// there are no suggestions, so clear the items list
  					this.Items.Clear();
  					// reset text back to original text
  					// and return the cursor to the original position
  					this.Text = curText;
  					this.SelectionStart = cursorPosition;
  				}
  				catch (ArgumentOutOfRangeException)
  				{
  					// just in case...it throws the exception while clearing the list
  				}
              }
              else
              {
                  // at least 1 suggestion exists
  
                  // open the dropdown menu
                  this.DroppedDown = true;
  
                  // update list
                  UpdateListItems(e.Items);
  
                  // set cursor to the end
                  this.Text = curText;
                  this.SelectionStart = curText.Length;
                  this.SelectionLength = 0;
              }
          }
  
          private void UpdateListItems(ICollection items)
          {
  			this.Items.Clear();
  			if (items.Count > 0)
  			{
  				var array = new object[items.Count];
  				items.CopyTo(array, 0);
  				this.Items.AddRange(array);
  			}
          }
  
          #endregion
  
  
  		private static void CursorReset()
  		{
  			Cursor.Current = Cursors.Default;
  			Cursor.Show();
  		}
  
  		private void UpdateSelectionFromText()
  		{
  			try
  			{
  				// do a case-insensitive search
  				var itemIndex = this.FindStringExact(this.Text);
  				if (itemIndex > -1)
  				{
  					// update the selected index
  					this.SelectedIndex = itemIndex;
  
  					// also update the visible text, because the upper/lower-casing may not match
  
gt: 					var item = this.Items[itemIndex];