context: //
  // SearchEntry.cs
  //
  // Author:
  //   Aaron Bockover <abockover@novell.com>
  //   Gabriel Burt <gburt@novell.com>
  //
  // Copyright 2007-2010 Novell, Inc.
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  //
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using Gtk;
  
  namespace MonoDevelop.Components
  {
  	[System.ComponentModel.ToolboxItem(true)]
  	public class SearchEntry : EventBox
  	{
  		private HBox box;
  		private Entry entry;
  		private HoverImageButton filter_button;
  		private HoverImageButton clear_button;
  
  		private Menu menu;
  		private int active_filter_id = -1;
  
  		private uint changed_timeout_id = 0;
  
  		private string empty_message;
  		private bool ready = false;
  
  		private event EventHandler filter_changed;
  		private event EventHandler entry_changed;
  
  		public event EventHandler Changed {
  			add { entry_changed += value; }
  			remove { entry_changed -= value; }
  		}
  
  		public event EventHandler Activated {
  			add { entry.Activated += value; }
  			remove { entry.Activated -= value; }
  		}
  
  		public event EventHandler FilterChanged {
  			add { filter_changed += value; }
  			remove { filter_changed -= value; }
  		}
  		
  		bool forceFilterButtonVisible;
  		public bool ForceFilterButtonVisible {
  			get {
  				return forceFilterButtonVisible; 
  			}
  			set {
  				forceFilterButtonVisible = value;
  				ShowHideButtons ();
  			}
  		}
  		
  		public Menu Menu {
  			get { return menu; }
  			set { menu = value; menu.Deactivated += OnMenuDeactivated; }
  		}
  
  		public Entry Entry {
  			get { return this.entry; }
  		}
  
  		public SearchEntry ()
  		{
  			AppPaintable = true;
  			
  			BuildWidget ();
  			BuildMenu ();
  			
  			NoShowAll = true;
  		}
  		public Gdk.Pixbuf FilterButtonPixbuf {
  			get {
  				return filter_button.Pixbuf;
  			}
  			set {
  				filter_button.Pixbuf = value;
  			}
  		}
  		
  		private void BuildWidget ()
  		{
  			box = new HBox ();
  			entry = new FramelessEntry (this);
  			filter_button = new HoverImageButton (IconSize.Menu, new string[] { Stock.Find });
  			clear_button = new HoverImageButton (IconSize.Menu, new string[] { Stock.Clear });
  			
  			box.PackStart (filter_button, false, false, 0);
  			box.PackStart (entry, true, true, 0);
  			box.PackStart (clear_button, false, false, 0);
  			Add (box);
  			box.ShowAll ();
  			
  			entry.StyleSet += OnInnerEntryStyleSet;
  			entry.StateChanged += OnInnerEntryStateChanged;
  			entry.FocusInEvent += OnInnerEntryFocusEvent;
  			entry.FocusOutEvent += OnInnerEntryFocusEvent;
  			entry.Changed += OnInnerEntryChanged;
  			
  			filter_button.Image.Xpad = 2;
  			clear_button.Image.Xpad = 2;
  			filter_button.CanFocus = false;
  			clear_button.CanFocus = false;
  			
  			filter_button.ButtonReleaseEvent += OnButtonReleaseEvent;
  			clear_button.ButtonReleaseEvent += OnButtonReleaseEvent;
  			clear_button.Clicked += OnClearButtonClicked;
  			
  			filter_button.Visible = false;
  			clear_button.Visible = false;
  			
  		}
  
  		Gtk.EventBox statusLabelEventBox;
  		public Gtk.EventBox AddLabelWidget (Gtk.Label label)
  		{
  			box.Remove (clear_button);
  			statusLabelEventBox = new Gtk.EventBox ();
  			statusLabelEventBox.Child = label;
  			box.PackStart (statusLabelEventBox, false, false, 0);
  			box.PackStart (clear_button, false, false, 0);
  			UpdateStyle ();
  			box.ShowAll ();
  			return statusLabelEventBox;
  		}
  
  		private void BuildMenu ()
  		{
  			menu = new Menu ();
  			menu.Deactivated += OnMenuDeactivated;
  		}
  
  		public void PopupFilterMenu ()
  		{
  			ShowMenu (0);
  		}
  		
  		void ShowMenu (uint time)
  		{
  			OnRequestMenu (EventArgs.Empty);
  			if (menu.Children.Length > 0) {
  				menu.Popup (null, null, OnPositionMenu, 0, time);
  				menu.ShowAll ();
  			}
  		}
  
  		private void ShowHideButtons ()
  		{
  			clear_button.Visible = entry.Text.Length > 0;
  			filter_button.Visible = ForceFilterButtonVisible || (menu != null && menu.Children.Length > 0);
  		}
  
  		private void OnPositionMenu (Menu menu, out int x, out int y, out bool push_in)
  		{
  			int origin_x, origin_y, tmp;
  			
  			filter_button.GdkWindow.GetOrigin (out origin_x, out tmp);
  			GdkWindow.GetOrigin (out tmp, out origin_y);
  			
  			x = origin_x + filter_button.Allocation.X;
  			y = origin_y + SizeRequest ().Height;
  			push_in = true;
  		}
  
  		private void OnMenuDeactivated (object o, EventArgs args)
  		{
  			filter_button.QueueDraw ();
  		}
  
  		private bool toggling = false;
  		public bool IsCheckMenu { get; set; }
  		private void OnMenuItemToggled (object o, EventArgs args)
  		{
  			if (IsCheckMenu || toggling || !(o is FilterMenuItem)) {
  				return;
  			}
  			
  			toggling = true;
  			FilterMenuItem item = (FilterMenuItem)o;
  			
  			foreach (MenuItem child_item in menu) {
  				if (!(child_item is FilterMenuItem)) {
  					continue;
  				}
  				
  				FilterMenuItem filter_child = (FilterMenuItem)child_item;
  				if (filter_child != item) {
  					filter_child.Active = false;
  				}
  			}
  			
  			item.Active = true;
  			ActiveFilterID = item.ID;
  			toggling = false;
  		}
  
  		private void OnInnerEntryChanged (object o, EventArgs args)
  		{
  			ShowHideButtons ();
  			
  			if (changed_timeout_id > 0) {
  				GLib.Source.Remove (changed_timeout_id);
  			}
  			
  			if (Ready)
  				changed_timeout_id = GLib.Timeout.Add (25, OnChangedTimeout);
  		}
  
  		private bool OnChangedTimeout ()
  		{
  			OnChanged ();
  			return false;
  		}
  
  		private void UpdateStyle ()
  		{
  			Gdk.Color color = entry.Style.Base (entry.State);
  			filter_button.ModifyBg (entry.State, color);
  			clear_button.ModifyBg (entry.State, color);
  			if (statusLabelEventBox != null)
  				statusLabelEventBox.ModifyBg (entry.State, color);
  			box.BorderWidth = (uint)entry.Style.XThickness;
  		}
  
  		private void OnInnerEntryStyleSet (object o, StyleSetArgs args)
  		{
  			UpdateStyle ();
  		}
  
  		private void OnInnerEntryStateChanged (object o, EventArgs args)
  		{
  			UpdateStyle ();
  		}
  
  		private void OnInnerEntryFocusEvent (object o, EventArgs args)
  		{
  			QueueDraw ();
  		}
  
  		private void OnButtonReleaseEvent (object o, ButtonReleaseEventArgs args)
  		{
  			if (args.Event.Button != 1) {
  				return;
  			}
  			
  			entry.HasFocus = true;
  			
  			if (o == filter_button) {
  				ShowMenu (args.Event.Time);
  			}
  		}
  		
  		protected virtual void OnRequestMenu (EventArgs e)
  		{
  			EventHandler handler = this.RequestMenu;
  			if (handler != null)
  				handler (this, e);
  		}
  		
  		public event EventHandler RequestMenu;
  		
  		public void GrabFocusEntry ()
  		{
  			this.entry.GrabFocus ();
  		}
  		private void OnClearButtonClicked (object o, EventArgs args)
  		{
  			active_filter_id = 0;
  			entry.Text = String.Empty;
  		}
  		
  		protected override void OnDestroyed ()
  		{
  			if (menu != null) {
  				menu.Destroy ();
  				menu = null;
  			}
  			base.OnDestroyed ();
  		}
  
  		protected override bool OnKeyPressEvent (Gdk.EventKey evnt)
  		{
  			if (evnt.Key == Gdk.Key.Escape) {
  				active_filter_id = 0;
  				entry.Text = String.Empty;
  				return true;
  			}
  			return base.OnKeyPressEvent (evnt);
  		}
  
  		protected override bool OnExposeEvent (Gdk.EventExpose evnt)
  		{
  			Style.PaintFlatBox (entry.Style, GdkWindow, State, ShadowType.None,
  				evnt.Area, this, "entry_bg", 0, 0, Allocation.Width, Allocation.Height);
  			PropagateExpose (Child, evnt);
  			Style.PaintShadow (entry.Style, GdkWindow, StateType.Normal, ShadowType.In,
  				evnt.Area, entry, "entry", 0, 0, Allocation.Width, Allocation.Height);
  			return true;
  		}
  
  		protected override void OnShown ()
  		{
  			base.OnShown ();
  			ShowHideButtons ();
  		}
  
  		protected virtual void OnChanged ()
  		{
  			if (!Ready) {
  				return;
  			}
  			
  			EventHandler handler = entry_changed;
  			if (handler != null) {
  				handler (this, EventArgs.Empty);
  			}
  		}
  
  		protected virtual void OnFilterChanged ()
  		{
  			EventHandler handler = filter_changed;
  			if (handler != null) {
  
gt: 				handler (this, EventArgs.Empty);