using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Linq; using System.Runtime.Remoting.Contexts; using Wox.Core.UserSettings; using Wox.Plugin; using Wox.Storage; namespace Wox { [Synchronization] public partial class ResultPanel : UserControl { public event Action LeftMouseClickEvent; public event Action RightMouseClickEvent; public event Action ItemDropEvent; private readonly ObservableCollection _results; //todo, for better performance, override the default linear search private readonly object _resultsUpdateLock = new object(); protected virtual void OnRightMouseClick(Result result) { Action handler = RightMouseClickEvent; if (handler != null) handler(result); } protected virtual void OnLeftMouseClick(Result result) { Action handler = LeftMouseClickEvent; if (handler != null) handler(result); } public int MaxResultsToShow { get { return UserSettingStorage.Instance.MaxResultsToShow * 50; } } public void AddResults(List newResults) { if (newResults != null && newResults.Count > 0) { lock (_resultsUpdateLock) { var pluginId = newResults[0].PluginID; var actionKeyword = newResults[0].OriginQuery.ActionKeyword; List oldResults; if (string.IsNullOrEmpty(actionKeyword)) { oldResults = _results.Where(r => r.PluginID == pluginId).ToList(); } else { oldResults = _results.ToList(); } // intersection of A (old results) and B (new newResults) var intersection = oldResults.Intersect(newResults).ToList(); // remove result of relative complement of B in A foreach (var result in oldResults.Except(intersection)) { _results.Remove(result); } // update scores foreach (var result in newResults) { if (IsTopMostResult(result)) { result.Score = int.MaxValue; } } // update index for result in intersection of A and B foreach (var result in intersection) { int oldIndex = _results.IndexOf(result); int oldScore = _results[oldIndex].Score; if (result.Score != oldScore) { int newIndex = InsertIndexOf(result.Score); if (newIndex != oldIndex) { _results.Move(oldIndex, newIndex); } } } // insert result in relative complement of A in B foreach (var result in newResults.Except(intersection)) { int newIndex = InsertIndexOf(result.Score); _results.Insert(newIndex, result); } lbResults.Margin = lbResults.Items.Count > 0 ? new Thickness { Top = 8 } : new Thickness { Top = 0 }; SelectFirst(); } } } private bool IsTopMostResult(Result result) { return TopMostRecordStorage.Instance.IsTopMost(result); } private int InsertIndexOf(int newScore) { int index = 0; for (; index < lbResults.Items.Count; index++) { Result result = lbResults.Items[index] as Result; if (newScore > result?.Score) { break; } } return index; } public void SelectNext() { int index = lbResults.SelectedIndex; if (index == lbResults.Items.Count - 1) { index = -1; } Select(index + 1); } public void SelectPrev() { int index = lbResults.SelectedIndex; if (index == 0) { index = lbResults.Items.Count; } Select(index - 1); } private void SelectFirst() { Select(0); } private void Select(int index) { if (index >= 0 && index < lbResults.Items.Count) { lbResults.SelectedItem = lbResults.Items.GetItemAt(index); } } public List GetVisibleResults() { List visibleElements = new List(); VirtualizingStackPanel virtualizingStackPanel = GetInnerStackPanel(lbResults); for (int i = (int)virtualizingStackPanel.VerticalOffset; i <= virtualizingStackPanel.VerticalOffset + virtualizingStackPanel.ViewportHeight; i++) { ListBoxItem item = lbResults.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem; if (item != null) { visibleElements.Add(item.DataContext as Result); } } return visibleElements; } private void UpdateItemNumber() { //VirtualizingStackPanel virtualizingStackPanel = GetInnerStackPanel(lbResults); //int index = 0; //for (int i = (int)virtualizingStackPanel.VerticalOffset; i <= virtualizingStackPanel.VerticalOffset + virtualizingStackPanel.ViewportHeight; i++) //{ // index++; // ListBoxItem item = lbResults.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem; // if (item != null) // { // ContentPresenter myContentPresenter = FindVisualChild(item); // if (myContentPresenter != null) // { // DataTemplate dataTemplate = myContentPresenter.ContentTemplate; // TextBlock tbItemNumber = (TextBlock)dataTemplate.FindName("tbItemNumber", myContentPresenter); // tbItemNumber.Text = index.ToString(); // } // } //} } private childItem FindVisualChild(DependencyObject obj) where childItem : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++) { DependencyObject child = VisualTreeHelper.GetChild(obj, i); if (child != null && child is childItem) return (childItem)child; else { childItem childOfChild = FindVisualChild(child); if (childOfChild != null) return childOfChild; } } return null; } private VirtualizingStackPanel GetInnerStackPanel(FrameworkElement element) { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement; if (child == null) continue; if (child is VirtualizingStackPanel) return child as VirtualizingStackPanel; var panel = GetInnerStackPanel(child); if (panel != null) return panel; } return null; } public Result GetActiveResult() { int index = lbResults.SelectedIndex; if (index < 0) return null; return lbResults.Items[index] as Result; } public ResultPanel() { InitializeComponent(); _results = new ObservableCollection(); lbResults.ItemsSource = _results; } public void Clear() { lock (_resultsUpdateLock) { _results.Clear(); lbResults.Margin = new Thickness { Top = 0 }; } } private void lbResults_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.AddedItems.Count > 0 && e.AddedItems[0] != null) { lbResults.ScrollIntoView(e.AddedItems[0]); Dispatcher.DelayInvoke("UpdateItemNumber", () => { UpdateItemNumber(); }, TimeSpan.FromMilliseconds(3)); } } private void LbResults_OnPreviewMouseDown(object sender, MouseButtonEventArgs e) { var item = ItemsControl.ContainerFromElement(lbResults, e.OriginalSource as DependencyObject) as ListBoxItem; if (item != null && e.ChangedButton == MouseButton.Left) { OnLeftMouseClick(item.DataContext as Result); } if (item != null && e.ChangedButton == MouseButton.Right) { OnRightMouseClick(item.DataContext as Result); } } public void SelectNextPage() { int index = lbResults.SelectedIndex; index += 5; if (index >= lbResults.Items.Count) { index = lbResults.Items.Count - 1; } Select(index); } public void SelectPrevPage() { int index = lbResults.SelectedIndex; index -= 5; if (index < 0) { index = 0; } Select(index); } private void ListBoxItem_OnDrop(object sender, DragEventArgs e) { var item = ItemsControl.ContainerFromElement(lbResults, e.OriginalSource as DependencyObject) as ListBoxItem; if (item != null) { OnItemDropEvent(item.DataContext as Result, e.Data, e); } } protected virtual void OnItemDropEvent(Result obj, IDataObject data, DragEventArgs e) { var handler = ItemDropEvent; if (handler != null) handler(obj, data, e); } } }