// // SearchPopupWindow.cs // // Author: // Mike Krüger // // Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) // // 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 System.Threading; using System.Threading.Tasks; using MonoDevelop.Core; using System.Collections.Generic; using Gtk; using System.Linq; using ICSharpCode.NRefactory.TypeSystem; using MonoDevelop.Ide; using MonoDevelop.Ide.CodeCompletion; using Mono.Addins; using Mono.TextEditor; namespace MonoDevelop.Components.MainToolbar { class SearchPopupWindow : PopoverWindow { const int yMargin = 0; const int xMargin = 6; const int itemSeparatorHeight = 2; const int marginIconSpacing = 4; const int iconTextSpacing = 6; const int categorySeparatorHeight = 8; const int headerMarginSize = 100; List categories = new List (); List> results = new List> (); List> incompleteResults = new List> (); Pango.Layout layout, headerLayout; CancellationTokenSource src; Cairo.Color headerColor; Cairo.Color separatorLine; Cairo.Color darkSearchBackground; Cairo.Color lightSearchBackground; Cairo.Color selectionBackgroundColor; bool isInSearch; class NullDataSource : ISearchDataSource { #region ISearchDataSource implementation Xwt.Drawing.Image ISearchDataSource.GetIcon (int item) { throw new NotImplementedException (); } string ISearchDataSource.GetMarkup (int item, bool isSelected) { throw new NotImplementedException (); } string ISearchDataSource.GetDescriptionMarkup (int item, bool isSelected) { throw new NotImplementedException (); } TooltipInformation ISearchDataSource.GetTooltip (int item) { throw new NotImplementedException (); } double ISearchDataSource.GetWeight (int item) { throw new NotImplementedException (); } DomRegion ISearchDataSource.GetRegion (int item) { throw new NotImplementedException (); } bool ISearchDataSource.CanActivate (int item) { throw new NotImplementedException (); } void ISearchDataSource.Activate (int item) { throw new NotImplementedException (); } int ISearchDataSource.ItemCount { get { return 0; } } #endregion } public SearchPopupWindow () { headerColor = CairoExtensions.ParseColor ("8c8c8c"); separatorLine = CairoExtensions.ParseColor ("dedede"); lightSearchBackground = CairoExtensions.ParseColor ("ffffff"); darkSearchBackground = CairoExtensions.ParseColor ("f7f7f7"); selectionBackgroundColor = CairoExtensions.ParseColor ("cccccc"); TypeHint = Gdk.WindowTypeHint.Combo; this.SkipTaskbarHint = true; this.SkipPagerHint = true; this.TransientFor = IdeApp.Workbench.RootWindow; this.AllowShrink = false; this.AllowGrow = false; categories.Add (new ProjectSearchCategory (this)); categories.Add (new FileSearchCategory (this)); categories.Add (new CommandSearchCategory (this)); categories.Add (new SearchInSolutionSearchCategory ()); categories.AddRange (AddinManager.GetExtensionObjects ("/MonoDevelop/Ide/SearchCategories")); layout = new Pango.Layout (PangoContext); headerLayout = new Pango.Layout (PangoContext); layout.Ellipsize = Pango.EllipsizeMode.End; headerLayout.Ellipsize = Pango.EllipsizeMode.End; Events = Gdk.EventMask.ButtonPressMask | Gdk.EventMask.ButtonMotionMask | Gdk.EventMask.ButtonReleaseMask | Gdk.EventMask.ExposureMask | Gdk.EventMask.PointerMotionMask; ItemActivated += (sender, e) => OpenFile (); /* SizeRequested += delegate(object o, SizeRequestedArgs args) { if (inResize) return; if (args.Requisition.Width != Allocation.Width || args.Requisition.Height != Allocation.Height) { inResize = true; // Visible = false; Resize (args.Requisition.Width, args.Requisition.Height); // Visible = true; if (!Visible) Visible = true; inResize = false; } };*/ } bool inResize = false; public bool SearchForMembers { get; set; } protected override void OnDestroyed () { if (src != null) src.Cancel (); HideTooltip (); this.declarationviewwindow.Destroy (); base.OnDestroyed (); } internal void OpenFile () { if (selectedItem == null || selectedItem.Item < 0 || selectedItem.Item >= selectedItem.DataSource.ItemCount) return; if (selectedItem.DataSource.CanActivate (selectedItem.Item)) { Destroy (); selectedItem.DataSource.Activate (selectedItem.Item); } else { var region = SelectedItemRegion; Destroy (); if (string.IsNullOrEmpty (region.FileName)) return; if (region.Begin.IsEmpty) { if (Pattern.LineNumber == 0) { IdeApp.Workbench.OpenDocument (region.FileName); } else { IdeApp.Workbench.OpenDocument (region.FileName, Pattern.LineNumber, Pattern.HasColumn ? Pattern.Column : 1); } } else { IdeApp.Workbench.OpenDocument (region.FileName, region.BeginLine, region.BeginColumn); } } } SearchPopupSearchPattern pattern; public SearchPopupSearchPattern Pattern { get { return pattern; } } public void Update (SearchPopupSearchPattern pattern) { // in case of 'string:' it's not clear if the user ment 'tag:pattern' or 'pattern:line' therefore guess // 'tag:', if no valid tag is found guess 'pattern:' if (!string.IsNullOrEmpty (pattern.Tag) && string.IsNullOrEmpty (pattern.Pattern) && !categories.Any (c => c.IsValidTag (pattern.Tag))) { pattern = new SearchPopupSearchPattern (null, pattern.Tag, pattern.LineNumber, pattern.Column, pattern.UnparsedPattern); } this.pattern = pattern; if (src != null) src.Cancel (); HideTooltip (); src = new CancellationTokenSource (); isInSearch = true; if (results.Count == 0) { QueueDraw (); } incompleteResults.Clear (); foreach (var _cat in categories) { var cat = _cat; var token = src.Token; cat.GetResults (pattern, maxItems, token).ContinueWith (t => { if (t.IsCanceled) return; if (t.IsFaulted) { LoggingService.LogError ("Error getting search results", t.Exception); } else { Application.Invoke (delegate { ShowResult (cat, t.Result ?? new NullDataSource ()); }); } }, token); } } void ShowResult (SearchCategory cat, ISearchDataSource result) { incompleteResults.Add (Tuple.Create (cat, result)); incompleteResults.Sort ((x, y) => { return categories.IndexOf (x.Item1).CompareTo (categories.IndexOf (y.Item1)); } ); if (incompleteResults.Count == categories.Count) { results.Clear (); results.AddRange (incompleteResults); List> failedResults = null; topItem = null; for (int i = 0; i < results.Count; i++) { var tuple = results [i]; try { if (tuple.Item2.ItemCount == 0) continue; if (topItem == null || topItem.DataSource.GetWeight (topItem.Item) < tuple.Item2.GetWeight (0)) topItem = new ItemIdentifier(tuple.Item1, tuple.Item2, 0); } catch (Exception e) { LoggingService.LogError ("Error while showing result " + i, e); if (failedResults == null) failedResults = new List> (); failedResults.Add (results [i]); continue; } } selectedItem = topItem; if (failedResults != null) failedResults.ForEach (failedResult => results.Remove (failedResult)); ShowTooltip (); isInSearch = false; AnimatedResize (); } } int calculatedItems; Gdk.Size GetIdealSize () { Gdk.Size retVal = new Gdk.Size (); int ox, oy; GetPosition (out ox, out oy); Gdk.Rectangle geometry = DesktopService.GetUsableMonitorGeometry (Screen, Screen.GetMonitorAtPoint (ox, oy)); var maxHeight = geometry.Height * 4 / 5; double startY = yMargin + ChildAllocation.Y; double y = startY; calculatedItems = 0; foreach (var result in results) { var dataSrc = result.Item2; if (dataSrc.ItemCount == 0) continue; for (int i = 0; i < maxItems && i < dataSrc.ItemCount; i++) { layout.SetMarkup (GetRowMarkup (dataSrc, i)); int w, h; layout.GetPixelSize (out w, out h); if (y + h + itemSeparatorHeight > maxHeight) break; y += h + itemSeparatorHeight; calculatedItems++; } } retVal.Width = Math.Min (geometry.Width * 4 / 5, 480); if (Math.Abs (y - startY) < 1) { layout.SetMarkup (GettextCatalog.GetString ("No matches")); int w, h; layout.GetPixelSize (out w, out h); var realHeight = h + itemSeparatorHeight + 4; y += realHeight; } else { y -= itemSeparatorHeight; } var calculatedHeight = Math.Min ( maxHeight, (int)y + yMargin + results.Count (res => res.Item2.ItemCount > 0) * categorySeparatorHeight ); retVal.Height = calculatedHeight; return retVal; } const int maxItems = 8; protected override void OnSizeRequested (ref Requisition requisition) { base.OnSizeRequested (ref requisition); if (!inResize) { Gdk.Size idealSize = GetIdealSize (); requisition.Width = idealSize.Width; requisition.Height = idealSize.Height; } } ItemIdentifier GetItemAt (double px, double py) { double y = ChildAllocation.Y + yMargin; if (topItem != null){ layout.SetMarkup (GetRowMarkup (topItem.DataSource, topItem.Item)); int w, h; layout.GetPixelSize (out w, out h); y += h + itemSeparatorHeight; if (y > py) return new ItemIdentifier (topItem.Category, topItem.DataSource, topItem.Item); } foreach (var result in results) { var category = result.Item1; var dataSrc = result.Item2; int itemsAdded = 0; for (int i = 0; i < maxItems && i < dataSrc.ItemCount; i++) { if (topItem != null && topItem.DataSource == dataSrc && topItem.Item == i) continue; layout.SetMarkup (GetRowMarkup (dataSrc, i)); int w, h; layout.GetPixelSize (out w, out h); y += h + itemSeparatorHeight; if (y > py){ return new ItemIdentifier (category, dataSrc, i); } var region = dataSrc.GetRegion (i); if (!region.Begin.IsEmpty) { layout.SetMarkup (region.BeginLine.ToString ()); int w2, h2; layout.GetPixelSize (out w2, out h2); w += w2; } itemsAdded++; } if (itemsAdded > 0) y += categorySeparatorHeight; } return null; } protected override bool OnMotionNotifyEvent (Gdk.EventMotion evnt) { var item = GetItemAt (evnt.X, evnt.Y); if (item == null && selectedItem != null || item != null && selectedItem == null || item != null && !item.Equals (selectedItem)) { selectedItem = item; ShowTooltip (); QueueDraw (); } return base.OnMotionNotifyEvent (evnt); } protected override bool OnButtonPressEvent (Gdk.EventButton evnt) { if (evnt.Button == 1) { if (selectedItem != null) OnItemActivated (EventArgs.Empty); } return base.OnButtonPressEvent (evnt); } int SelectedCategoryIndex { get { for (int i = 0; i < results.Count; i++) { if (results [i].Item1 == selectedItem.Category) { return i; } } return -1; } } void SelectItemUp () { if (selectedItem == null || selectedItem == topItem) return; int i = SelectedCategoryIndex; if (selectedItem.Item > 0) { selectedItem = new ItemIdentifier (selectedItem.Category, selectedItem.DataSource, selectedItem.Item - 1); if (i > 0 && selectedItem.Equals (topItem)) { SelectItemUp (); return; } } else { if (i == 0) { selectedItem = topItem; } else { do { i--; selectedItem = new ItemIdentifier ( results [i].Item1, results [i].Item2, Math.Min (maxItems, results [i].Item2.ItemCount) - 1 ); if (selectedItem.Category == topItem.Category && selectedItem.Item == topItem.Item && i > 0) { i--; selectedItem = new ItemIdentifier ( results [i].Item1, results [i].Item2, Math.Min (maxItems, results [i].Item2.ItemCount) - 1 ); } } while (i > 0 && selectedItem.DataSource.ItemCount <= 0); if (selectedItem.DataSource.ItemCount <= 0) { selectedItem = topItem; } } } ShowTooltip (); QueueDraw (); } void SelectItemDown () { if (selectedItem == null) return; if (selectedItem.Equals (topItem)) { for (int j = 0; j < results.Count; j++) { if (results[j].Item2.ItemCount == 0 || results[j].Item2.ItemCount == 1 && topItem.DataSource == results[j].Item2) continue; selectedItem = new ItemIdentifier ( results [j].Item1, results [j].Item2, 0 ); if (selectedItem.Equals (topItem)) goto normalDown; break; } ShowTooltip (); QueueDraw (); return; } normalDown: var i = SelectedCategoryIndex; // check real upper bound if (selectedItem != null) { var curAbsoluteIndex = selectedItem == topItem ? 1 : 0; for (int j = 0; j < i; j++) { curAbsoluteIndex += Math.Min (maxItems, results [j].Item2.ItemCount); } curAbsoluteIndex += selectedItem.Item + 1; if (curAbsoluteIndex + 1 > calculatedItems) return; } var upperBound = Math.Min (maxItems, selectedItem.DataSource.ItemCount); if (selectedItem.Item + 1 < upperBound) { if (topItem.DataSource == selectedItem.DataSource && selectedItem.Item == upperBound - 1) return; selectedItem = new ItemIdentifier (selectedItem.Category, selectedItem.DataSource, selectedItem.Item + 1); } else { for (int j = i + 1; j < results.Count; j++) { if (results[j].Item2.ItemCount == 0 || results[j].Item2.ItemCount == 1 && topItem.DataSource == results[j].Item2) continue; selectedItem = new ItemIdentifier ( results [j].Item1, results [j].Item2, 0 ); if (selectedItem.Equals (topItem)) { selectedItem = new ItemIdentifier ( results [j].Item1, results [j].Item2, 1 ); } break; } } ShowTooltip (); QueueDraw (); } TooltipInformationWindow declarationviewwindow = new TooltipInformationWindow (); uint declarationViewTimer, declarationViewWindowOpacityTimer; void RemoveDeclarationViewTimer () { if (declarationViewWindowOpacityTimer != 0) { GLib.Source.Remove (declarationViewWindowOpacityTimer); declarationViewWindowOpacityTimer = 0; } if (declarationViewTimer != 0) { GLib.Source.Remove (declarationViewTimer); declarationViewTimer = 0; } } void HideTooltip () { RemoveDeclarationViewTimer (); if (declarationviewwindow != null) { declarationviewwindow.Hide (); declarationviewwindow.Opacity = 0; } if (tooltipSrc != null) tooltipSrc.Cancel (); } CancellationTokenSource tooltipSrc = null; void ShowTooltip () { HideTooltip (); var currentSelectedItem = selectedItem; if (currentSelectedItem == null || currentSelectedItem.DataSource == null) return; var i = currentSelectedItem.Item; if (i < 0 || i >= currentSelectedItem.DataSource.ItemCount) return; if (tooltipSrc != null) tooltipSrc.Cancel (); tooltipSrc = new CancellationTokenSource (); var token = tooltipSrc.Token; ThreadPool.QueueUserWorkItem (delegate { TooltipInformation tooltip; try { tooltip = currentSelectedItem.DataSource.GetTooltip (i); } catch (Exception e) { LoggingService.LogError ("Error while creating search popup window tooltip", e); return; } if (tooltip == null || string.IsNullOrEmpty (tooltip.SignatureMarkup) || token.IsCancellationRequested) return; Application.Invoke (delegate { if (token.IsCancellationRequested) return; declarationviewwindow.Clear (); declarationviewwindow.AddOverload (tooltip); declarationviewwindow.CurrentOverload = 0; declarationViewTimer = GLib.Timeout.Add (250, DelayedTooltipShow); }); }); } bool DelayedTooltipShow () { declarationviewwindow.ShowArrow = true; var rect = SelectedItemRectangle; declarationviewwindow.ShowPopup (this, new Gdk.Rectangle (0, (int)rect.Y, Allocation.Width, (int)rect.Height), PopupPosition.Right); if (declarationViewWindowOpacityTimer != 0) GLib.Source.Remove (declarationViewWindowOpacityTimer); declarationViewWindowOpacityTimer = GLib.Timeout.Add (50, new OpacityTimer (this).Timer); declarationViewTimer = 0; return false; } class OpacityTimer { public double Opacity { get; private set; } SearchPopupWindow window; // static int num = 0; // int id; public OpacityTimer (SearchPopupWindow window) { // id = num++; this.window = window; Opacity = 0.0; window.declarationviewwindow.Opacity = Opacity; } public bool Timer () { Opacity = System.Math.Min (1.0, Opacity + 0.33); window.declarationviewwindow.Opacity = Opacity; bool result = Math.Round (Opacity * 10.0) < 10; if (!result) window.declarationViewWindowOpacityTimer = 0; return result; } } void SelectNextCategory () { if (selectedItem == null) return; var i = SelectedCategoryIndex; if (selectedItem.Equals (topItem)) { if (i > 0) { selectedItem = new ItemIdentifier ( results [0].Item1, results [0].Item2, 0 ); } else { if (topItem.DataSource.ItemCount > 1) { selectedItem = new ItemIdentifier ( results [0].Item1, results [0].Item2, 1 ); } else if (i < results.Count - 1) { selectedItem = new ItemIdentifier ( results [i + 1].Item1, results [i + 1].Item2, 0 ); } } } else { while (i < results.Count - 1 && results [i + 1].Item2.ItemCount == 0) i++; if (i < results.Count - 1) { selectedItem = new ItemIdentifier ( results [i + 1].Item1, results [i + 1].Item2, 0 ); } } QueueDraw (); } void SelectPrevCategory () { if (selectedItem == null) return; var i = SelectedCategoryIndex; if (i > 0) { selectedItem = new ItemIdentifier ( results [i - 1].Item1, results [i - 1].Item2, 0 ); if (selectedItem.Equals (topItem)) { if (topItem.DataSource.ItemCount > 1) { selectedItem = new ItemIdentifier ( results [i - 1].Item1, results [i - 1].Item2, 1 ); } else if (i > 1) { selectedItem = new ItemIdentifier ( results [i - 2].Item1, results [i - 2].Item2, 0 ); } } } else { selectedItem = topItem; } QueueDraw (); } void SelectFirstCategory () { selectedItem = topItem; QueueDraw (); } void SelectLastCatgory () { var r = results.LastOrDefault (r2 => r2.Item2.ItemCount > 0 && !(r2.Item2.ItemCount == 1 && topItem.Category == r2.Item1)); if (r == null) return; selectedItem = new ItemIdentifier ( r.Item1, r.Item2, r.Item2.ItemCount - 1 ); QueueDraw (); } internal bool ProcessKey (Xwt.Key key, Xwt.ModifierKeys state) { switch (key) { case Xwt.Key.Up: if (state.HasFlag (Xwt.ModifierKeys.Command)) goto case Xwt.Key.PageUp; if (state.HasFlag (Xwt.ModifierKeys.Control)) goto case Xwt.Key.Home; SelectItemUp (); return true; case Xwt.Key.Down: if (state.HasFlag (Xwt.ModifierKeys.Command)) goto case Xwt.Key.PageDown; if (state.HasFlag (Xwt.ModifierKeys.Control)) goto case Xwt.Key.End; SelectItemDown (); return true; case (Xwt.Key)Gdk.Key.KP_Page_Down: case Xwt.Key.PageDown: SelectNextCategory (); return true; case (Xwt.Key)Gdk.Key.KP_Page_Up: case Xwt.Key.PageUp: SelectPrevCategory (); return true; case Xwt.Key.Home: SelectFirstCategory (); return true; case Xwt.Key.End: SelectLastCatgory (); return true; case Xwt.Key.Return: OnItemActivated (EventArgs.Empty); return true; } return false; } public event EventHandler ItemActivated; protected virtual void OnItemActivated (EventArgs e) { EventHandler handler = this.ItemActivated; if (handler != null) handler (this, e); } public DomRegion SelectedItemRegion { get { if (selectedItem == null || selectedItem.Item < 0 || selectedItem.Item >= selectedItem.DataSource.ItemCount) return DomRegion.Empty; return selectedItem.DataSource.GetRegion (selectedItem.Item); } } class ItemIdentifier { public SearchCategory Category { get; private set; } public ISearchDataSource DataSource { get; private set; } public int Item { get; private set; } public ItemIdentifier (SearchCategory category, ISearchDataSource dataSource, int item) { this.Category = category; this.DataSource = dataSource; this.Item = item; } public override bool Equals (object obj) { if (obj == null) return false; if (ReferenceEquals (this, obj)) return true; if (obj.GetType () != typeof(ItemIdentifier)) return false; ItemIdentifier other = (ItemIdentifier)obj; return Category == other.Category && DataSource == other.DataSource && Item == other.Item; } public override int GetHashCode () { unchecked { return (Category != null ? Category.GetHashCode () : 0) ^ (DataSource != null ? DataSource.GetHashCode () : 0) ^ Item.GetHashCode (); } } public override string ToString () { return string.Format ("[ItemIdentifier: Category={0}, DataSource=#{1}, Item={2}]", Category.Name, DataSource.ItemCount, Item); } } ItemIdentifier selectedItem = null, topItem = null; Cairo.Rectangle SelectedItemRectangle { get { if (selectedItem == null) return new Cairo.Rectangle (0, 0, Allocation.Width, 16); double y = ChildAllocation.Y + yMargin; if (topItem != null){ layout.SetMarkup (GetRowMarkup (topItem.DataSource, topItem.Item)); int w, h; layout.GetPixelSize (out w, out h); if (topItem.Category == selectedItem.Category && topItem.Item == selectedItem.Item) return new Cairo.Rectangle (0, y, Allocation.Width, h + itemSeparatorHeight); y += h + itemSeparatorHeight; } foreach (var result in results) { var category = result.Item1; var dataSrc = result.Item2; int itemsAdded = 0; for (int i = 0; i < maxItems && i < dataSrc.ItemCount; i++) { if (topItem != null && topItem.DataSource == dataSrc && topItem.Item == i) continue; layout.SetMarkup (GetRowMarkup (dataSrc, i)); int w, h; layout.GetPixelSize (out w, out h); if (selectedItem.Category == category && selectedItem.DataSource == dataSrc && selectedItem.Item == i) return new Cairo.Rectangle (0, y, Allocation.Width, h + itemSeparatorHeight); y += h + itemSeparatorHeight; var region = dataSrc.GetRegion (i); if (!region.Begin.IsEmpty) { layout.SetMarkup (region.BeginLine.ToString ()); int w2, h2; layout.GetPixelSize (out w2, out h2); w += w2; } itemsAdded++; } if (itemsAdded > 0) y += categorySeparatorHeight; } return new Cairo.Rectangle (0, 0, Allocation.Width, 16); } } protected override void OnDrawContent (Gdk.EventExpose evnt, Cairo.Context context) { context.LineWidth = 1; var alloc = ChildAllocation; var adjustedMarginSize = alloc.X - Allocation.X + headerMarginSize; var r = results.Where (res => res.Item2.ItemCount > 0).ToArray (); if (r.Any ()) { context.SetSourceColor (lightSearchBackground); context.Rectangle (Allocation.X, Allocation.Y, adjustedMarginSize, Allocation.Height); context.Fill (); context.SetSourceColor (darkSearchBackground); context.Rectangle (Allocation.X + adjustedMarginSize, Allocation.Y, Allocation.Width - adjustedMarginSize, Allocation.Height); context.Fill (); context.MoveTo (0.5 + Allocation.X + adjustedMarginSize, 0); context.LineTo (0.5 + Allocation.X + adjustedMarginSize, Allocation.Height); context.SetSourceColor (separatorLine); context.Stroke (); } else { context.SetSourceRGB (1, 1, 1); context.Rectangle (Allocation.X, Allocation.Y, Allocation.Width, Allocation.Height); context.Fill (); } double y = alloc.Y + yMargin; int w, h; if (topItem != null) { headerLayout.SetText (GettextCatalog.GetString ("Top Result")); headerLayout.GetPixelSize (out w, out h); context.MoveTo (alloc.Left + headerMarginSize - w - xMargin, y); context.SetSourceColor (headerColor); Pango.CairoHelper.ShowLayout (context, headerLayout); var category = topItem.Category; var dataSrc = topItem.DataSource; var i = topItem.Item; double x = alloc.X + xMargin + headerMarginSize; context.SetSourceRGB (0, 0, 0); layout.SetMarkup (GetRowMarkup (dataSrc, i)); layout.GetPixelSize (out w, out h); if (selectedItem != null && selectedItem.Category == category && selectedItem.Item == i) { context.SetSourceColor (selectionBackgroundColor); context.Rectangle (alloc.X + headerMarginSize, y, Allocation.Width - adjustedMarginSize, h); context.Fill (); context.SetSourceRGB (1, 1, 1); } var px = dataSrc.GetIcon (i); if (px != null) { context.DrawImage (this, px, (int)x + marginIconSpacing, (int)y + (h - px.Height) / 2); x += px.Width + iconTextSpacing + marginIconSpacing; } context.MoveTo (x, y); context.SetSourceRGB (0, 0, 0); Pango.CairoHelper.ShowLayout (context, layout); y += h + itemSeparatorHeight; } foreach (var result in r) { var category = result.Item1; var dataSrc = result.Item2; if (dataSrc.ItemCount == 0) continue; if (dataSrc.ItemCount == 1 && topItem != null && topItem.DataSource == dataSrc) continue; headerLayout.SetText (category.Name); headerLayout.GetPixelSize (out w, out h); if (y + h > Allocation.Height) break; context.MoveTo (alloc.X + headerMarginSize - w - xMargin, y); context.SetSourceColor (headerColor); Pango.CairoHelper.ShowLayout (context, headerLayout); layout.Width = Pango.Units.FromPixels (Allocation.Width - adjustedMarginSize - 35); for (int i = 0; i < maxItems && i < dataSrc.ItemCount; i++) { if (topItem != null && topItem.Category == category && topItem.Item == i) continue; double x = alloc.X + xMargin + headerMarginSize; context.SetSourceRGB (0, 0, 0); layout.SetMarkup (GetRowMarkup (dataSrc, i)); layout.GetPixelSize (out w, out h); if (y + h + itemSeparatorHeight > Allocation.Height) break; if (selectedItem != null && selectedItem.Category == category && selectedItem.Item == i) { context.SetSourceColor (selectionBackgroundColor); context.Rectangle (alloc.X + headerMarginSize, y, Allocation.Width - adjustedMarginSize, h); context.Fill (); context.SetSourceRGB (1, 1, 1); } var px = dataSrc.GetIcon (i); if (px != null) { context.DrawImage (this, px, (int)x + marginIconSpacing, (int)y + (h - px.Height) / 2); x += px.Width + iconTextSpacing + marginIconSpacing; } context.MoveTo (x, y); context.SetSourceRGB (0, 0, 0); Pango.CairoHelper.ShowLayout (context, layout); y += h + itemSeparatorHeight; } if (result != r.Last ()) { y += categorySeparatorHeight; } } if (y == alloc.Y + yMargin) { context.SetSourceRGB (0, 0, 0); layout.SetMarkup (isInSearch ? GettextCatalog.GetString ("Searching...") : GettextCatalog.GetString ("No matches")); context.MoveTo (alloc.X + xMargin, y); Pango.CairoHelper.ShowLayout (context, layout); } } string GetRowMarkup (ISearchDataSource dataSrc, int i) { string txt = "" + dataSrc.GetMarkup (i, false) +""; string desc = dataSrc.GetDescriptionMarkup (i, false); if (!string.IsNullOrEmpty (desc)) txt += "\n" + desc + ""; return txt; } } }