diff --git a/Wox/App.xaml.cs b/Wox/App.xaml.cs index 19ff2c2f17..b5847ac164 100644 --- a/Wox/App.xaml.cs +++ b/Wox/App.xaml.cs @@ -10,6 +10,8 @@ using Wox.CommandArgs; using Wox.Core.Plugin; using Wox.Helper; using Wox.Infrastructure; +using Wox.Plugin; +using Wox.ViewModel; using Stopwatch = Wox.Infrastructure.Stopwatch; @@ -20,6 +22,8 @@ namespace Wox private const string Unique = "Wox_Unique_Application_Mutex"; public static MainWindow Window { get; private set; } + public static IPublicAPI API { get; private set; } + [STAThread] public static void Main() { @@ -40,8 +44,15 @@ namespace Wox WoxDirectroy.Executable = Directory.GetParent(Assembly.GetExecutingAssembly().Location).ToString(); RegisterUnhandledException(); ThreadPool.QueueUserWorkItem(o => { ImageLoader.ImageLoader.PreloadImages(); }); + + MainViewModel mainVM = new MainViewModel(); + API = new PublicAPIInstance(mainVM); Window = new MainWindow(); - PluginManager.Init(Window); + Window.DataContext = mainVM; + + NotifyIconManager notifyIconManager = new NotifyIconManager(API); + + PluginManager.Init(API); CommandArgsFactory.Execute(e.Args.ToList()); }); @@ -59,7 +70,7 @@ namespace Wox { if (args.Count > 0 && args[0] == SingleInstance.Restart) { - Window.CloseApp(); + API.CloseApp(); } else { diff --git a/Wox/CommandArgs/CommandArgsFactory.cs b/Wox/CommandArgs/CommandArgsFactory.cs index e6e4a91b74..14a3f99a72 100644 --- a/Wox/CommandArgs/CommandArgsFactory.cs +++ b/Wox/CommandArgs/CommandArgsFactory.cs @@ -34,7 +34,7 @@ namespace Wox.CommandArgs } else { - App.Window.ShowApp(); + App.API.ShowApp(); } } } diff --git a/Wox/CommandArgs/QueryCommandArg.cs b/Wox/CommandArgs/QueryCommandArg.cs index 22f0443528..d1df8e0a5b 100644 --- a/Wox/CommandArgs/QueryCommandArg.cs +++ b/Wox/CommandArgs/QueryCommandArg.cs @@ -16,9 +16,9 @@ namespace Wox.CommandArgs if (args.Count > 0) { string query = args[0]; - App.Window.ChangeQuery(query); + App.API.ChangeQuery(query); } - App.Window.ShowApp(); + App.API.ShowApp(); } } } diff --git a/Wox/CommandArgs/ReloadPluginCommandArg.cs b/Wox/CommandArgs/ReloadPluginCommandArg.cs index aec5724c11..a866446d2d 100644 --- a/Wox/CommandArgs/ReloadPluginCommandArg.cs +++ b/Wox/CommandArgs/ReloadPluginCommandArg.cs @@ -12,7 +12,7 @@ namespace Wox.CommandArgs public void Execute(IList args) { - PluginManager.Init(App.Window); + PluginManager.Init(App.API); } } } diff --git a/Wox/CommandArgs/ToggleCommandArg.cs b/Wox/CommandArgs/ToggleCommandArg.cs index 623648e311..e509316291 100644 --- a/Wox/CommandArgs/ToggleCommandArg.cs +++ b/Wox/CommandArgs/ToggleCommandArg.cs @@ -11,7 +11,14 @@ namespace Wox.CommandArgs public void Execute(IList args) { - App.Window.ToggleWox(); + if (App.Window.IsVisible) + { + App.API.HideApp(); + } + else + { + App.API.ShowApp(); + } } } } diff --git a/Wox/Converters/VisibilityConverter.cs b/Wox/Converters/VisibilityConverter.cs new file mode 100644 index 0000000000..46488bde37 --- /dev/null +++ b/Wox/Converters/VisibilityConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Data; + +namespace Wox.Converters +{ + class VisibilityConverter : ConvertorBase + { + public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null || value == DependencyProperty.UnsetValue) + { + return null; + } + + return bool.Parse(value.ToString()) ? Visibility.Visible : Visibility.Collapsed; + } + + public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return null; + } + } + +} diff --git a/Wox/CustomQueryHotkeySetting.xaml.cs b/Wox/CustomQueryHotkeySetting.xaml.cs index 7c58babe1c..8c29324509 100644 --- a/Wox/CustomQueryHotkeySetting.xaml.cs +++ b/Wox/CustomQueryHotkeySetting.xaml.cs @@ -1,8 +1,12 @@ -using System.Collections.Generic; +using NHotkey; +using NHotkey.Wpf; +using System; +using System.Collections.Generic; using System.Linq; using System.Windows; using Wox.Core.Resource; using Wox.Core.UserSettings; +using Wox.Infrastructure.Hotkey; namespace Wox { @@ -44,10 +48,11 @@ namespace Wox ActionKeyword = tbAction.Text }; UserSettingStorage.Instance.CustomPluginHotkeys.Add(pluginHotkey); - settingWidow.MainWindow.SetHotkey(ctlHotkey.CurrentHotkey, delegate + + SetHotkey(ctlHotkey.CurrentHotkey, delegate { - settingWidow.MainWindow.ChangeQuery(pluginHotkey.ActionKeyword); - settingWidow.MainWindow.ShowApp(); + App.API.ChangeQuery(pluginHotkey.ActionKeyword); + App.API.ShowApp(); }); MessageBox.Show(InternationalizationManager.Instance.GetTranslation("succeed")); } @@ -62,11 +67,11 @@ namespace Wox updateCustomHotkey.ActionKeyword = tbAction.Text; updateCustomHotkey.Hotkey = ctlHotkey.CurrentHotkey.ToString(); //remove origin hotkey - settingWidow.MainWindow.RemoveHotkey(oldHotkey); - settingWidow.MainWindow.SetHotkey(updateCustomHotkey.Hotkey, delegate + RemoveHotkey(oldHotkey); + SetHotkey(new HotkeyModel(updateCustomHotkey.Hotkey), delegate { - settingWidow.MainWindow.ShowApp(); - settingWidow.MainWindow.ChangeQuery(updateCustomHotkey.ActionKeyword); + App.API.ShowApp(); + App.API.ChangeQuery(updateCustomHotkey.ActionKeyword); }); MessageBox.Show(InternationalizationManager.Instance.GetTranslation("succeed")); } @@ -94,8 +99,30 @@ namespace Wox private void BtnTestActionKeyword_OnClick(object sender, RoutedEventArgs e) { - settingWidow.MainWindow.ShowApp(); - settingWidow.MainWindow.ChangeQuery(tbAction.Text); + App.API.ShowApp(); + App.API.ChangeQuery(tbAction.Text); + } + + private void RemoveHotkey(string hotkeyStr) + { + if (!string.IsNullOrEmpty(hotkeyStr)) + { + HotkeyManager.Current.Remove(hotkeyStr); + } + } + + private void SetHotkey(HotkeyModel hotkey, EventHandler action) + { + string hotkeyStr = hotkey.ToString(); + try + { + HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); + } + catch (Exception) + { + string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + MessageBox.Show(errorMsg); + } } } } diff --git a/Wox/Helper/ListBoxItems.cs b/Wox/Helper/ListBoxItems.cs deleted file mode 100644 index bb2bdc2a2e..0000000000 --- a/Wox/Helper/ListBoxItems.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; -using System.Linq; -using Wox.Plugin; - -namespace Wox.Helper -{ - class ListBoxItems : ObservableCollection - // todo implement custom moveItem,removeItem,insertItem for better performance - { - public void RemoveAll(Predicate predicate) - { - CheckReentrancy(); - - List itemsToRemove = Items.Where(x => predicate(x)).ToList(); - if (itemsToRemove.Count > 0) - { - - itemsToRemove.ForEach(item => Items.Remove(item)); - - OnPropertyChanged(new PropertyChangedEventArgs("Count")); - OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); - // fuck ms - // http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx - // http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx - // PS: don't use Reset for other data updates, it will cause UI flickering - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - } - - public void Update(List newItems) - { - int newCount = newItems.Count; - int oldCount = Items.Count; - int location = newCount > oldCount ? oldCount : newCount; - for (int i = 0; i < location; i++) - { - Result oldItem = Items[i]; - Result newItem = newItems[i]; - if (!oldItem.Equals(newItem)) - { - this[i] = newItem; - } - else if (oldItem.Score != newItem.Score) - { - this[i].Score = newItem.Score; - } - } - - if (newCount > oldCount) - { - for (int i = oldCount; i < newCount; i++) - { - Add(newItems[i]); - } - } - else - { - int removeIndex = newCount; - for (int i = newCount; i < oldCount; i++) - { - RemoveAt(removeIndex); - } - } - - } - } -} diff --git a/Wox/Helper/SingletonWindowOpener.cs b/Wox/Helper/SingletonWindowOpener.cs index 2b49503981..f7786ea04b 100644 --- a/Wox/Helper/SingletonWindowOpener.cs +++ b/Wox/Helper/SingletonWindowOpener.cs @@ -10,7 +10,7 @@ namespace Wox.Helper { var window = Application.Current.Windows.OfType().FirstOrDefault(x => x.GetType() == typeof(T)) ?? (T)Activator.CreateInstance(typeof(T), args); - Application.Current.MainWindow.Hide(); + App.API.HideApp(); window.Show(); window.Focus(); diff --git a/Wox/MainWindow.xaml b/Wox/MainWindow.xaml index e4873a677a..fd00239538 100644 --- a/Wox/MainWindow.xaml +++ b/Wox/MainWindow.xaml @@ -1,6 +1,9 @@  + Visibility="{Binding IsVisible,Converter={converters:VisibilityConverter}}" + PreviewKeyDown="Window_PreviewKeyDown" d:DataContext="{d:DesignInstance vm:MainViewModel, IsDesignTimeCreatable=True}"> + + + + + + - - - - + + + + + + + + + + \ No newline at end of file diff --git a/Wox/MainWindow.xaml.cs b/Wox/MainWindow.xaml.cs index aa802a3ad6..4430fd6fe9 100644 --- a/Wox/MainWindow.xaml.cs +++ b/Wox/MainWindow.xaml.cs @@ -1,244 +1,38 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media.Animation; -using NHotkey; -using NHotkey.Wpf; using Wox.Core.Plugin; using Wox.Core.Resource; using Wox.Core.Updater; using Wox.Core.UserSettings; using Wox.Helper; -using Wox.Infrastructure; using Wox.Infrastructure.Hotkey; -using Wox.Plugin; -using Wox.Storage; -using Application = System.Windows.Application; -using ContextMenu = System.Windows.Forms.ContextMenu; using DataFormats = System.Windows.DataFormats; using DragEventArgs = System.Windows.DragEventArgs; -using IDataObject = System.Windows.IDataObject; using KeyEventArgs = System.Windows.Input.KeyEventArgs; -using MenuItem = System.Windows.Forms.MenuItem; using MessageBox = System.Windows.MessageBox; -using Stopwatch = Wox.Infrastructure.Stopwatch; -using ToolTip = System.Windows.Controls.ToolTip; +using Wox.ViewModel; +using Wox.Plugin; namespace Wox { - public partial class MainWindow : IPublicAPI + public partial class MainWindow { #region Properties - private readonly Storyboard progressBarStoryboard = new Storyboard(); - private NotifyIcon notifyIcon; - private bool _queryHasReturn; - private Query _lastQuery = new Query(); - private ToolTip toolTip = new ToolTip(); - - private bool _ignoreTextChange; - private List CurrentContextMenus = new List(); - private string textBeforeEnterContextMenuMode; + private readonly Storyboard progressBarStoryboard = new Storyboard(); #endregion - - #region Public API - - public void ChangeQuery(string query, bool requery = false) - { - Dispatcher.Invoke(() => - { - tbQuery.Text = query; - tbQuery.CaretIndex = tbQuery.Text.Length; - if (requery) - { - TbQuery_OnTextChanged(null, null); - } - }); - } - - public void ChangeQueryText(string query, bool selectAll = false) - { - Dispatcher.Invoke(() => - { - _ignoreTextChange = true; - tbQuery.Text = query; - tbQuery.CaretIndex = tbQuery.Text.Length; - if (selectAll) - { - tbQuery.SelectAll(); - } - }); - } - - public void CloseApp() - { - notifyIcon.Visible = false; - Application.Current.Shutdown(); - } - - public void RestarApp() - { - ProcessStartInfo info = new ProcessStartInfo - { - FileName = Application.ResourceAssembly.Location, - Arguments = SingleInstance.Restart - }; - Process.Start(info); - } - - public void HideApp() - { - Dispatcher.Invoke(HideWox); - } - - public void ShowApp() - { - Dispatcher.Invoke(() => ShowWox()); - } - - public void ShowMsg(string title, string subTitle, string iconPath) - { - Dispatcher.Invoke(() => - { - var m = new Msg { Owner = GetWindow(this) }; - m.Show(title, subTitle, iconPath); - }); - } - - public void OpenSettingDialog(string tabName = "general") - { - Dispatcher.Invoke(() => - { - SettingWindow sw = SingletonWindowOpener.Open(this); - sw.SwitchTo(tabName); - }); - } - - public void StartLoadingBar() - { - Dispatcher.Invoke(StartProgress); - } - - public void StopLoadingBar() - { - Dispatcher.Invoke(StopProgress); - } - - public void InstallPlugin(string path) - { - Dispatcher.Invoke(() => PluginManager.InstallPlugin(path)); - } - - public void ReloadPlugins() - { - Dispatcher.Invoke(() => PluginManager.Init(this)); - } - - public string GetTranslation(string key) - { - return InternationalizationManager.Instance.GetTranslation(key); - } - - public List GetAllPlugins() - { - return PluginManager.AllPlugins.ToList(); - } - - public event WoxKeyDownEventHandler BackKeyDownEvent; - public event WoxGlobalKeyboardEventHandler GlobalKeyboardEvent; - public event ResultItemDropEventHandler ResultItemDropEvent; - - public void PushResults(Query query, PluginMetadata plugin, List results) - { - results.ForEach(o => - { - o.PluginDirectory = plugin.PluginDirectory; - o.PluginID = plugin.ID; - o.OriginQuery = query; - }); - UpdateResultView(results, plugin, query); - } - - public void ShowContextMenu(PluginMetadata plugin, List results) - { - if (results != null && results.Count > 0) - { - results.ForEach(o => - { - o.PluginDirectory = plugin.PluginDirectory; - o.PluginID = plugin.ID; - o.ContextMenu = null; - }); - pnlContextMenu.Clear(); - pnlContextMenu.AddResults(results, plugin.ID); - pnlContextMenu.Visibility = Visibility.Visible; - pnlResult.Visibility = Visibility.Collapsed; - } - } - - #endregion - + public MainWindow() { InitializeComponent(); - ThreadPool.SetMaxThreads(30, 10); - ThreadPool.SetMinThreads(10, 5); - - WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); - GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback; - progressBar.ToolTip = toolTip; - pnlResult.LeftMouseClickEvent += SelectResult; - pnlResult.ItemDropEvent += pnlResult_ItemDropEvent; - pnlContextMenu.LeftMouseClickEvent += SelectResult; - pnlResult.RightMouseClickEvent += pnlResult_RightMouseClickEvent; + Closing += MainWindow_Closing; - - - SetHotkey(UserSettingStorage.Instance.Hotkey, OnHotkey); - SetCustomPluginHotkey(); - InitialTray(); - } - - void pnlResult_ItemDropEvent(Result result, IDataObject dropDataObject, DragEventArgs args) - { - PluginPair pluginPair = PluginManager.AllPlugins.FirstOrDefault(o => o.Metadata.ID == result.PluginID); - if (ResultItemDropEvent != null && pluginPair != null) - { - foreach (var delegateHandler in ResultItemDropEvent.GetInvocationList()) - { - if (delegateHandler.Target == pluginPair.Plugin) - { - delegateHandler.DynamicInvoke(result, dropDataObject, args); - } - } - } - } - - private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state) - { - if (GlobalKeyboardEvent != null) - { - return GlobalKeyboardEvent((int)keyevent, vkcode, state); - } - return true; - } - - void pnlResult_RightMouseClickEvent(Result result) - { - ShowContextMenu(result); } void MainWindow_Closing(object sender, CancelEventArgs e) @@ -246,7 +40,6 @@ namespace Wox UserSettingStorage.Instance.WindowLeft = Left; UserSettingStorage.Instance.WindowTop = Top; UserSettingStorage.Instance.Save(); - HideWox(); e.Cancel = true; } @@ -255,12 +48,46 @@ namespace Wox ThemeManager.Theme.ChangeTheme(UserSettingStorage.Instance.Theme); InternationalizationManager.Instance.ChangeLanguage(UserSettingStorage.Instance.Language); - Left = GetWindowsLeft(); - Top = GetWindowsTop(); - InitProgressbarAnimation(); WindowIntelopHelper.DisableControlBox(this); CheckUpdate(); + + var vm = this.DataContext as MainViewModel; + vm.PropertyChanged += (o, eve) => + { + if(eve.PropertyName == "SelectAllText") + { + if (vm.SelectAllText) + { + this.tbQuery.SelectAll(); + } + } + else if(eve.PropertyName == "CaretIndex") + { + this.tbQuery.CaretIndex = vm.CaretIndex; + } + else if(eve.PropertyName == "Left") + { + this.Left = vm.Left; + } + else if(eve.PropertyName == "Top") + { + this.Top = vm.Top; + } + else if(eve.PropertyName == "IsVisible") + { + if (vm.IsVisible) + { + this.tbQuery.Focus(); + } + } + }; + + vm.Left = GetWindowsLeft(); + vm.Top = GetWindowsTop(); + this.Activate(); + this.Focus(); + this.tbQuery.Focus(); } private double GetWindowsLeft() @@ -304,82 +131,6 @@ namespace Wox }); } - public void SetHotkey(string hotkeyStr, EventHandler action) - { - var hotkey = new HotkeyModel(hotkeyStr); - SetHotkey(hotkey, action); - } - - public void SetHotkey(HotkeyModel hotkey, EventHandler action) - { - string hotkeyStr = hotkey.ToString(); - try - { - HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); - } - catch (Exception) - { - string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); - MessageBox.Show(errorMsg); - } - } - - public void RemoveHotkey(string hotkeyStr) - { - if (!string.IsNullOrEmpty(hotkeyStr)) - { - HotkeyManager.Current.Remove(hotkeyStr); - } - } - - /// - /// Checks if Wox should ignore any hotkeys - /// - /// - private bool ShouldIgnoreHotkeys() - { - //double if to omit calling win32 function - if (UserSettingStorage.Instance.IgnoreHotkeysOnFullscreen) - if (WindowIntelopHelper.IsWindowFullscreen()) - return true; - - return false; - } - - private void SetCustomPluginHotkey() - { - if (UserSettingStorage.Instance.CustomPluginHotkeys == null) return; - foreach (CustomPluginHotkey hotkey in UserSettingStorage.Instance.CustomPluginHotkeys) - { - CustomPluginHotkey hotkey1 = hotkey; - SetHotkey(hotkey.Hotkey, delegate - { - if (ShouldIgnoreHotkeys()) return; - ShowApp(); - ChangeQuery(hotkey1.ActionKeyword, true); - }); - } - } - - private void OnHotkey(object sender, HotkeyEventArgs e) - { - if (ShouldIgnoreHotkeys()) return; - ToggleWox(); - e.Handled = true; - } - - public void ToggleWox() - { - if (!IsVisible) - { - ShowWox(); - } - else - { - HideWox(); - } - } - private void InitProgressbarAnimation() { var da = new DoubleAnimation(progressBar.X2, ActualWidth + 100, new Duration(new TimeSpan(0, 0, 0, 0, 1600))); @@ -393,197 +144,41 @@ namespace Wox progressBar.BeginStoryboard(progressBarStoryboard); } - private void InitialTray() - { - notifyIcon = new NotifyIcon { Text = "Wox", Icon = Properties.Resources.app, Visible = true }; - notifyIcon.Click += (o, e) => ShowWox(); - var open = new MenuItem(GetTranslation("iconTrayOpen")); - open.Click += (o, e) => ShowWox(); - var setting = new MenuItem(GetTranslation("iconTraySettings")); - setting.Click += (o, e) => OpenSettingDialog(); - var about = new MenuItem(GetTranslation("iconTrayAbout")); - about.Click += (o, e) => OpenSettingDialog("about"); - var exit = new MenuItem(GetTranslation("iconTrayExit")); - exit.Click += (o, e) => CloseApp(); - MenuItem[] childen = { open, setting, about, exit }; - notifyIcon.ContextMenu = new ContextMenu(childen); - } - - private void QueryContextMenu() - { - var contextMenuId = "Context Menu Id"; - pnlContextMenu.Clear(); - var query = tbQuery.Text.ToLower(); - if (string.IsNullOrEmpty(query)) - { - pnlContextMenu.AddResults(CurrentContextMenus, contextMenuId); - } - else - { - List filterResults = new List(); - foreach (Result contextMenu in CurrentContextMenus) - { - if (StringMatcher.IsMatch(contextMenu.Title, query) - || StringMatcher.IsMatch(contextMenu.SubTitle, query)) - { - filterResults.Add(contextMenu); - } - } - pnlContextMenu.AddResults(filterResults, contextMenuId); - } - } - - private void TbQuery_OnTextChanged(object sender, TextChangedEventArgs e) - { - if (_ignoreTextChange) { _ignoreTextChange = false; return; } - - toolTip.IsOpen = false; - if (IsInContextMenuMode) - { - QueryContextMenu(); - return; - } - - string query = tbQuery.Text.Trim(); - if (!string.IsNullOrEmpty(query)) - { - Query(query); - //reset query history index after user start new query - ResetQueryHistoryIndex(); - } - else - { - pnlResult.Clear(); - } - } - - private void ResetQueryHistoryIndex() - { - pnlResult.RemoveResultsFor(QueryHistoryStorage.MetaData); - QueryHistoryStorage.Instance.Reset(); - } - private void Query(string text) - { - _queryHasReturn = false; - var query = PluginManager.QueryInit(text); - if (query != null) - { - // handle the exclusiveness of plugin using action keyword - string lastKeyword = _lastQuery.ActionKeyword; - string keyword = query.ActionKeyword; - if (string.IsNullOrEmpty(lastKeyword)) - { - if (!string.IsNullOrEmpty(keyword)) - { - pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } - else - { - if (string.IsNullOrEmpty(keyword)) - { - pnlResult.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); - } - else if (lastKeyword != keyword) - { - pnlResult.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); - } - } - _lastQuery = query; - Dispatcher.InvokeAsync(async () => - { - await Task.Delay(150); - if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) - { - StartProgress(); - } - }); - PluginManager.QueryForAllPlugins(query); - } - StopProgress(); - } - - private void BackToResultMode() - { - ChangeQueryText(textBeforeEnterContextMenuMode); - pnlResult.Visibility = Visibility.Visible; - pnlContextMenu.Visibility = Visibility.Collapsed; - } - private void Border_OnMouseDown(object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) DragMove(); } - private void StartProgress() - { - progressBar.Visibility = Visibility.Visible; - } - - private void StopProgress() - { - progressBar.Visibility = Visibility.Hidden; - } - - private void HideWox() - { - UserSettingStorage.Instance.WindowLeft = Left; - UserSettingStorage.Instance.WindowTop = Top; - if (IsInContextMenuMode) - { - BackToResultMode(); - } - Hide(); - } - - private void ShowWox(bool selectAll = true) - { - UserSettingStorage.Instance.IncreaseActivateTimes(); - Left = GetWindowsLeft(); - Top = GetWindowsTop(); - - Show(); - Activate(); - Focus(); - tbQuery.Focus(); - ResetQueryHistoryIndex(); - if (selectAll) tbQuery.SelectAll(); - } - private void MainWindow_OnDeactivated(object sender, EventArgs e) { if (UserSettingStorage.Instance.HideWhenDeactive) { - HideWox(); + App.API.HideApp(); } } - private void TbQuery_OnPreviewKeyDown(object sender, KeyEventArgs e) + private void Window_PreviewKeyDown(object sender, KeyEventArgs e) { + var vm = this.DataContext as MainViewModel; + + if (null == vm) return; //when alt is pressed, the real key should be e.SystemKey - Key key = (e.Key == Key.System ? e.SystemKey : e.Key); + var key = (e.Key == Key.System ? e.SystemKey : e.Key); switch (key) { case Key.Escape: - if (IsInContextMenuMode) - { - BackToResultMode(); - } - else - { - HideWox(); - } + vm.EscCommand.Execute(null); e.Handled = true; break; case Key.Tab: if (GlobalHotkey.Instance.CheckModifiers().ShiftPressed) { - SelectPrevItem(); + vm.SelectPrevItemCommand.Execute(null); } else { - SelectNextItem(); + vm.SelectNextItemCommand.Execute(null); } e.Handled = true; break; @@ -592,7 +187,7 @@ namespace Wox case Key.J: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - SelectNextItem(); + vm.SelectNextItemCommand.Execute(null); } break; @@ -600,32 +195,25 @@ namespace Wox case Key.K: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - SelectPrevItem(); + vm.SelectPrevItemCommand.Execute(null); } break; case Key.O: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - if (IsInContextMenuMode) - { - BackToResultMode(); - } - else - { - ShowContextMenu(GetActiveResult()); - } + vm.CtrlOCommand.Execute(null); } break; case Key.Down: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - DisplayNextQuery(); + vm.DisplayNextQueryCommand.Execute(null); } else { - SelectNextItem(); + vm.SelectNextItemCommand.Execute(null); } e.Handled = true; break; @@ -633,11 +221,11 @@ namespace Wox case Key.Up: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - DisplayPrevQuery(); + vm.DisplayPrevQueryCommand.Execute(null); } else { - SelectPrevItem(); + vm.SelectPrevItemCommand.Execute(null); } e.Handled = true; break; @@ -645,272 +233,92 @@ namespace Wox case Key.D: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - pnlResult.SelectNextPage(); + vm.SelectNextPageCommand.Execute(null); } break; case Key.PageDown: - pnlResult.SelectNextPage(); + vm.SelectNextPageCommand.Execute(null); e.Handled = true; break; case Key.U: if (GlobalHotkey.Instance.CheckModifiers().CtrlPressed) { - pnlResult.SelectPrevPage(); + vm.SelectPrevPageCommand.Execute(null); } break; case Key.PageUp: - pnlResult.SelectPrevPage(); + vm.SelectPrevPageCommand.Execute(null); e.Handled = true; break; case Key.Back: - if (BackKeyDownEvent != null) - { - BackKeyDownEvent(new WoxKeyDownEventArgs - { - Query = tbQuery.Text, - keyEventArgs = e - }); - } + vm.BackCommand.Execute(e); break; case Key.F1: - Process.Start("http://doc.getwox.com"); + vm.StartHelpCommand.Execute(null); break; case Key.Enter: - Result activeResult = GetActiveResult(); if (GlobalHotkey.Instance.CheckModifiers().ShiftPressed) { - ShowContextMenu(activeResult); + vm.ShiftEnterCommand.Execute(null); } else { - SelectResult(activeResult); + vm.OpenResultCommand.Execute(null); } e.Handled = true; break; case Key.D1: - SelectItem(1); + + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(0); + } break; case Key.D2: - SelectItem(2); + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(1); + } break; case Key.D3: - SelectItem(3); + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(2); + } break; case Key.D4: - SelectItem(4); + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(3); + } break; case Key.D5: - SelectItem(5); + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(4); + } break; case Key.D6: - SelectItem(6); + if (GlobalHotkey.Instance.CheckModifiers().AltPressed) + { + vm.OpenResultCommand.Execute(5); + } break; } } - private void DisplayPrevQuery() - { - var prev = QueryHistoryStorage.Instance.Previous(); - DisplayQueryHistory(prev); - } - - private void DisplayNextQuery() - { - var nextQuery = QueryHistoryStorage.Instance.Next(); - DisplayQueryHistory(nextQuery); - } - - private void DisplayQueryHistory(HistoryItem history) - { - if (history != null) - { - var historyMetadata = QueryHistoryStorage.MetaData; - ChangeQueryText(history.Query, true); - var executeQueryHistoryTitle = GetTranslation("executeQuery"); - var lastExecuteTime = GetTranslation("lastExecuteTime"); - pnlResult.RemoveResultsExcept(historyMetadata); - UpdateResultViewInternal(new List - { - new Result - { - Title = string.Format(executeQueryHistoryTitle,history.Query), - SubTitle = string.Format(lastExecuteTime,history.ExecutedDateTime), - IcoPath = "Images\\history.png", - PluginDirectory = WoxDirectroy.Executable, - Action = _ =>{ - ChangeQuery(history.Query,true); - return false; - } - } - }, historyMetadata); - } - } - - private void SelectItem(int index) - { - int zeroBasedIndex = index - 1; - SpecialKeyState keyState = GlobalHotkey.Instance.CheckModifiers(); - if (keyState.AltPressed || keyState.CtrlPressed) - { - List visibleResults = pnlResult.GetVisibleResults(); - if (zeroBasedIndex < visibleResults.Count) - { - SelectResult(visibleResults[zeroBasedIndex]); - } - } - } - - private bool IsInContextMenuMode - { - get { return pnlContextMenu.Visibility == Visibility.Visible; } - } - - private Result GetActiveResult() - { - if (IsInContextMenuMode) - { - return pnlContextMenu.GetActiveResult(); - } - else - { - return pnlResult.GetActiveResult(); - } - } - - private void SelectPrevItem() - { - if (IsInContextMenuMode) - { - pnlContextMenu.SelectPrev(); - } - else - { - pnlResult.SelectPrev(); - } - toolTip.IsOpen = false; - } - - private void SelectNextItem() - { - if (IsInContextMenuMode) - { - pnlContextMenu.SelectNext(); - } - else - { - pnlResult.SelectNext(); - } - toolTip.IsOpen = false; - } - - private void SelectResult(Result result) - { - if (result != null) - { - if (result.Action != null) - { - bool hideWindow = result.Action(new ActionContext - { - SpecialKeyState = GlobalHotkey.Instance.CheckModifiers() - }); - if (hideWindow) - { - HideWox(); - } - UserSelectedRecordStorage.Instance.Add(result); - QueryHistoryStorage.Instance.Add(tbQuery.Text); - } - } - } - - private void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) - { - _queryHasReturn = true; - progressBar.Dispatcher.Invoke(StopProgress); - - list.ForEach(o => - { - o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5; - }); - if (originQuery.RawQuery == _lastQuery.RawQuery) - { - UpdateResultViewInternal(list, metadata); - } - } - - private void UpdateResultViewInternal(List list, PluginMetadata metadata) - { - Dispatcher.Invoke(() => - { - Stopwatch.Normal($"UI update cost for {metadata.Name}", - () => { pnlResult.AddResults(list, metadata.ID); }); - }); - } - - private Result GetTopMostContextMenu(Result result) - { - if (TopMostRecordStorage.Instance.IsTopMost(result)) - { - return new Result(GetTranslation("cancelTopMostInThisQuery"), "Images\\down.png") - { - PluginDirectory = WoxDirectroy.Executable, - Action = _ => - { - TopMostRecordStorage.Instance.Remove(result); - ShowMsg("Succeed", "", ""); - return false; - } - }; - } - else - { - return new Result(GetTranslation("setAsTopMostInThisQuery"), "Images\\up.png") - { - PluginDirectory = WoxDirectroy.Executable, - Action = _ => - { - TopMostRecordStorage.Instance.AddOrUpdate(result); - ShowMsg("Succeed", "", ""); - return false; - } - }; - } - } - - private void ShowContextMenu(Result result) - { - if (result == null) return; - List results = PluginManager.GetContextMenusForPlugin(result); - results.ForEach(o => - { - o.PluginDirectory = PluginManager.GetPluginForId(result.PluginID).Metadata.PluginDirectory; - o.PluginID = result.PluginID; - o.OriginQuery = result.OriginQuery; - }); - - results.Add(GetTopMostContextMenu(result)); - - textBeforeEnterContextMenuMode = tbQuery.Text; - ChangeQueryText(""); - pnlContextMenu.Clear(); - pnlContextMenu.AddResults(results, result.PluginID); - CurrentContextMenus = results; - pnlContextMenu.Visibility = Visibility.Visible; - pnlResult.Visibility = Visibility.Collapsed; - } - private void MainWindow_OnDrop(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) @@ -926,6 +334,7 @@ namespace Wox MessageBox.Show(InternationalizationManager.Instance.GetTranslation("invalidWoxPluginFileFormat")); } } + e.Handled = false; } private void TbQuery_OnPreviewDragOver(object sender, DragEventArgs e) diff --git a/Wox/NotifyIconManager.cs b/Wox/NotifyIconManager.cs new file mode 100644 index 0000000000..28ab61ad86 --- /dev/null +++ b/Wox/NotifyIconManager.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Wox.Core.Resource; +using Wox.Plugin; + +namespace Wox +{ + public class NotifyIconManager + { + + private NotifyIcon notifyIcon; + private IPublicAPI _api; + + public NotifyIconManager(IPublicAPI api) + { + this.InitialTray(); + this._api = api; + } + + private void InitialTray() + { + notifyIcon = new NotifyIcon { Text = "Wox", Icon = Properties.Resources.app, Visible = true }; + notifyIcon.Click += (o, e) => this._api.ShowApp(); + var open = new MenuItem(InternationalizationManager.Instance.GetTranslation("iconTrayOpen")); + open.Click += (o, e) => this._api.ShowApp(); + var setting = new MenuItem(InternationalizationManager.Instance.GetTranslation("iconTraySettings")); + setting.Click += (o, e) => this._api.OpenSettingDialog(); + var about = new MenuItem(InternationalizationManager.Instance.GetTranslation("iconTrayAbout")); + about.Click += (o, e) => this._api.OpenSettingDialog("about"); + var exit = new MenuItem(InternationalizationManager.Instance.GetTranslation("iconTrayExit")); + exit.Click += (o, e) => this._api.CloseApp(); + MenuItem[] childen = { open, setting, about, exit }; + notifyIcon.ContextMenu = new ContextMenu(childen); + } + } +} diff --git a/Wox/PublicAPIInstance.cs b/Wox/PublicAPIInstance.cs new file mode 100644 index 0000000000..7cb7f296f4 --- /dev/null +++ b/Wox/PublicAPIInstance.cs @@ -0,0 +1,293 @@ +using NHotkey; +using NHotkey.Wpf; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Input; +using System.Windows.Threading; +using Wox.Core.Plugin; +using Wox.Core.Resource; +using Wox.Core.UserSettings; +using Wox.Helper; +using Wox.Infrastructure.Hotkey; +using Wox.Plugin; +using Wox.ViewModel; + +namespace Wox +{ + public class PublicAPIInstance : IPublicAPI + { + + #region Constructor + + public PublicAPIInstance(MainViewModel mainVM) + { + this.MainVM = mainVM; + + ThreadPool.SetMaxThreads(30, 10); + ThreadPool.SetMinThreads(10, 5); + GlobalHotkey.Instance.hookedKeyboardCallback += KListener_hookedKeyboardCallback; + WebRequest.RegisterPrefix("data", new DataWebRequestFactory()); + + SetHotkey(UserSettingStorage.Instance.Hotkey, OnHotkey); + SetCustomPluginHotkey(); + + this.MainVM.ListeningKeyPressed += (o, e) => { + + if(e.KeyEventArgs.Key == Key.Back) + { + if (null != this.BackKeyDownEvent) + { + BackKeyDownEvent(new WoxKeyDownEventArgs + { + Query = this.MainVM.QueryText, + keyEventArgs = e.KeyEventArgs + }); + } + } + + }; + } + + #endregion + + #region Properties + + private MainViewModel MainVM + { + get; + set; + } + + #endregion + + #region Public API + + public void ChangeQuery(string query, bool requery = false) + { + this.MainVM.QueryText = query; + this.MainVM.CaretIndex = this.MainVM.QueryText.Length; + + } + + public void ChangeQueryText(string query, bool selectAll = false) + { + this.MainVM.QueryText = query; + this.MainVM.SelectAllText = true; + } + + public void CloseApp() + { + //notifyIcon.Visible = false; + Application.Current.Shutdown(); + } + + public void RestarApp() + { + ProcessStartInfo info = new ProcessStartInfo + { + FileName = Application.ResourceAssembly.Location, + Arguments = SingleInstance.Restart + }; + Process.Start(info); + } + + public void HideApp() + { + HideWox(); + } + + public void ShowApp() + { + ShowWox(); + } + + public void ShowMsg(string title, string subTitle, string iconPath) + { + Application.Current.Dispatcher.Invoke(() => + { + var m = new Msg { Owner = Application.Current.MainWindow }; + m.Show(title, subTitle, iconPath); + }); + } + + public void OpenSettingDialog(string tabName = "general") + { + Application.Current.Dispatcher.Invoke(() => + { + SettingWindow sw = SingletonWindowOpener.Open(this); + sw.SwitchTo(tabName); + }); + } + + public void StartLoadingBar() + { + this.MainVM.IsProgressBarVisible = true; + } + + public void StopLoadingBar() + { + this.MainVM.IsProgressBarVisible = false; + } + + public void InstallPlugin(string path) + { + Application.Current.Dispatcher.Invoke(() => PluginManager.InstallPlugin(path)); + } + + public void ReloadPlugins() + { + Application.Current.Dispatcher.Invoke(() => PluginManager.Init(this)); + } + + public string GetTranslation(string key) + { + return InternationalizationManager.Instance.GetTranslation(key); + } + + public List GetAllPlugins() + { + return PluginManager.AllPlugins.ToList(); + } + + public event WoxKeyDownEventHandler BackKeyDownEvent; + public event WoxGlobalKeyboardEventHandler GlobalKeyboardEvent; + public event ResultItemDropEventHandler ResultItemDropEvent; + + public void PushResults(Query query, PluginMetadata plugin, List results) + { + results.ForEach(o => + { + o.PluginDirectory = plugin.PluginDirectory; + o.PluginID = plugin.ID; + o.OriginQuery = query; + }); + this.MainVM.UpdateResultView(results, plugin, query); + } + + public void ShowContextMenu(PluginMetadata plugin, List results) + { + if (results != null && results.Count > 0) + { + results.ForEach(o => + { + o.PluginDirectory = plugin.PluginDirectory; + o.PluginID = plugin.ID; + }); + + this.MainVM.ShowActionPanel(results, plugin.ID); + } + } + + #endregion + + #region Private Methods + + private bool KListener_hookedKeyboardCallback(KeyEvent keyevent, int vkcode, SpecialKeyState state) + { + if (GlobalKeyboardEvent != null) + { + return GlobalKeyboardEvent((int)keyevent, vkcode, state); + } + return true; + } + + private void HideWox() + { + UserSettingStorage.Instance.WindowLeft = this.MainVM.Left; + UserSettingStorage.Instance.WindowTop = this.MainVM.Top; + this.MainVM.IsVisible = false; + } + + private void ShowWox(bool selectAll = true) + { + UserSettingStorage.Instance.IncreaseActivateTimes(); + this.MainVM.IsVisible = true; + this.MainVM.SelectAllText = true; + } + + public void SetHotkey(string hotkeyStr, EventHandler action) + { + var hotkey = new HotkeyModel(hotkeyStr); + SetHotkey(hotkey, action); + } + + public void SetHotkey(HotkeyModel hotkey, EventHandler action) + { + string hotkeyStr = hotkey.ToString(); + try + { + HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); + } + catch (Exception) + { + string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + MessageBox.Show(errorMsg); + } + } + + public void RemoveHotkey(string hotkeyStr) + { + if (!string.IsNullOrEmpty(hotkeyStr)) + { + HotkeyManager.Current.Remove(hotkeyStr); + } + } + + /// + /// Checks if Wox should ignore any hotkeys + /// + /// + private bool ShouldIgnoreHotkeys() + { + //double if to omit calling win32 function + if (UserSettingStorage.Instance.IgnoreHotkeysOnFullscreen) + if (WindowIntelopHelper.IsWindowFullscreen()) + return true; + + return false; + } + + private void SetCustomPluginHotkey() + { + if (UserSettingStorage.Instance.CustomPluginHotkeys == null) return; + foreach (CustomPluginHotkey hotkey in UserSettingStorage.Instance.CustomPluginHotkeys) + { + CustomPluginHotkey hotkey1 = hotkey; + SetHotkey(hotkey.Hotkey, delegate + { + if (ShouldIgnoreHotkeys()) return; + ShowApp(); + ChangeQuery(hotkey1.ActionKeyword, true); + }); + } + } + + private void OnHotkey(object sender, HotkeyEventArgs e) + { + if (ShouldIgnoreHotkeys()) return; + ToggleWox(); + e.Handled = true; + } + + private void ToggleWox() + { + if (!MainVM.IsVisible) + { + ShowWox(); + } + else + { + HideWox(); + } + } + + #endregion + } +} diff --git a/Wox/ResultPanel.xaml b/Wox/ResultPanel.xaml index 6abd51faec..d4e088186e 100644 --- a/Wox/ResultPanel.xaml +++ b/Wox/ResultPanel.xaml @@ -5,49 +5,64 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:converters="clr-namespace:Wox.Converters" - mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100"> - - + - - + + + + - + - + + + diff --git a/Wox/ResultPanel.xaml.cs b/Wox/ResultPanel.xaml.cs index 4a622815de..05f3870f7b 100644 --- a/Wox/ResultPanel.xaml.cs +++ b/Wox/ResultPanel.xaml.cs @@ -10,252 +10,23 @@ using Wox.Core.UserSettings; using Wox.Helper; using Wox.Plugin; using Wox.Storage; +using Wox.ViewModel; namespace Wox { [Synchronization] public partial class ResultPanel : UserControl { - public event Action LeftMouseClickEvent; - public event Action RightMouseClickEvent; - public event Action ItemDropEvent; - private readonly ListBoxItems _results; - 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; } } - - internal void RemoveResultsFor(PluginMetadata metadata) - { - lock (_resultsUpdateLock) - { - _results.RemoveAll(r => r.PluginID == metadata.ID); - } - } - - internal void RemoveResultsExcept(PluginMetadata metadata) - { - lock (_resultsUpdateLock) - { - _results.RemoveAll(r => r.PluginID != metadata.ID); - } - } - public void AddResults(List newResults, string resultId) { - lock (_resultsUpdateLock) - { - // todo use async to do new result calculation - var resultsCopy = _results.ToList(); - var oldResults = resultsCopy.Where(r => r.PluginID == resultId).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)) - { - resultsCopy.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 commonResult in intersection) - { - int oldIndex = resultsCopy.IndexOf(commonResult); - int oldScore = resultsCopy[oldIndex].Score; - int newScore = newResults[newResults.IndexOf(commonResult)].Score; - if (newScore != oldScore) - { - var oldResult = resultsCopy[oldIndex]; - oldResult.Score = newScore; - resultsCopy.RemoveAt(oldIndex); - int newIndex = InsertIndexOf(newScore, resultsCopy); - resultsCopy.Insert(newIndex, oldResult); - - } - } - - // insert result in relative complement of A in B - foreach (var result in newResults.Except(intersection)) - { - int newIndex = InsertIndexOf(result.Score, resultsCopy); - resultsCopy.Insert(newIndex, result); - } - - // update UI in one run, so it can avoid UI flickering - _results.Update(resultsCopy); - - 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, IList list) - { - int index = 0; - for (; index < list.Count; index++) - { - var result = list[index]; - 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; + var vm = this.DataContext as ResultPanelViewModel; + vm.AddResults(newResults, resultId); } + public ResultPanel() { InitializeComponent(); - _results = new ListBoxItems(); - lbResults.ItemsSource = _results; - } - - public void Clear() - { - lock (_resultsUpdateLock) - { - _results.Clear(); - lbResults.Margin = new Thickness { Top = 0 }; - } } private void lbResults_SelectionChanged(object sender, SelectionChangedEventArgs e) @@ -263,61 +34,8 @@ namespace Wox 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); - } } } \ No newline at end of file diff --git a/Wox/SettingWindow.xaml.cs b/Wox/SettingWindow.xaml.cs index effdddb618..f47bbbde3b 100644 --- a/Wox/SettingWindow.xaml.cs +++ b/Wox/SettingWindow.xaml.cs @@ -19,20 +19,25 @@ using Wox.Helper; using Wox.Plugin; using Application = System.Windows.Forms.Application; using Stopwatch = Wox.Infrastructure.Stopwatch; +using Wox.Infrastructure.Hotkey; +using NHotkey.Wpf; +using NHotkey; +using Wox.ViewModel; namespace Wox { public partial class SettingWindow : Window { - public readonly MainWindow MainWindow; + public readonly IPublicAPI _api; bool settingsLoaded; private Dictionary featureControls = new Dictionary(); private bool themeTabLoaded; - public SettingWindow(MainWindow mainWindow) + public SettingWindow(IPublicAPI api) { - MainWindow = mainWindow; + this._api = api; InitializeComponent(); + this.resultPanelPreview.DataContext = new ResultPanelViewModel(); Loaded += Setting_Loaded; } @@ -94,7 +99,7 @@ namespace Wox { UserSettingStorage.Instance.MaxResultsToShow = (int)comboMaxResultsToShow.SelectedItem; UserSettingStorage.Instance.Save(); - MainWindow.pnlResult.lbResults.GetBindingExpression(MaxHeightProperty).UpdateTarget(); + //MainWindow.pnlResult.lbResults.GetBindingExpression(MaxHeightProperty).UpdateTarget(); }; cbHideWhenDeactive.IsChecked = UserSettingStorage.Instance.HideWhenDeactive; @@ -250,23 +255,45 @@ namespace Wox { if (ctlHotkey.CurrentHotkeyAvailable) { - MainWindow.SetHotkey(ctlHotkey.CurrentHotkey, delegate + SetHotkey(ctlHotkey.CurrentHotkey, delegate { - if (!MainWindow.IsVisible) + if (!App.Window.IsVisible) { - MainWindow.ShowApp(); + this._api.ShowApp(); } else { - MainWindow.HideApp(); + this._api.HideApp(); } }); - MainWindow.RemoveHotkey(UserSettingStorage.Instance.Hotkey); + RemoveHotkey(UserSettingStorage.Instance.Hotkey); UserSettingStorage.Instance.Hotkey = ctlHotkey.CurrentHotkey.ToString(); UserSettingStorage.Instance.Save(); } } + void SetHotkey(HotkeyModel hotkey, EventHandler action) + { + string hotkeyStr = hotkey.ToString(); + try + { + HotkeyManager.Current.AddOrReplace(hotkeyStr, hotkey.CharKey, hotkey.ModifierKeys, action); + } + catch (Exception) + { + string errorMsg = string.Format(InternationalizationManager.Instance.GetTranslation("registerHotkeyFailed"), hotkeyStr); + MessageBox.Show(errorMsg); + } + } + + void RemoveHotkey(string hotkeyStr) + { + if (!string.IsNullOrEmpty(hotkeyStr)) + { + HotkeyManager.Current.Remove(hotkeyStr); + } + } + private void OnHotkeyTabSelected() { ctlHotkey.HotkeyChanged += ctlHotkey_OnHotkeyChanged; @@ -289,7 +316,7 @@ namespace Wox UserSettingStorage.Instance.CustomPluginHotkeys.Remove(item); lvCustomHotkey.Items.Refresh(); UserSettingStorage.Instance.Save(); - MainWindow.RemoveHotkey(item.Hotkey); + RemoveHotkey(item.Hotkey); } } diff --git a/Wox/ViewModel/BaseViewModel.cs b/Wox/ViewModel/BaseViewModel.cs new file mode 100644 index 0000000000..2635df6f4a --- /dev/null +++ b/Wox/ViewModel/BaseViewModel.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace Wox.ViewModel +{ + public class BaseViewModel : INotifyPropertyChanged + { + + protected virtual void OnPropertyChanged(string propertyName) + { + if (null != this.PropertyChanged) + { + this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + } + + public class RelayCommand : ICommand + { + + private Action _action; + + public RelayCommand(Action action) + { + this._action = action; + } + + public virtual bool CanExecute(object parameter) + { + return true; + } + + public event EventHandler CanExecuteChanged; + + public virtual void Execute(object parameter) + { + if (null != this._action) + { + this._action(parameter); + } + } + } +} diff --git a/Wox/ViewModel/MainViewModel.cs b/Wox/ViewModel/MainViewModel.cs new file mode 100644 index 0000000000..e6e5912376 --- /dev/null +++ b/Wox/ViewModel/MainViewModel.cs @@ -0,0 +1,699 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Forms; +using System.Windows.Input; +using Wox.Core.Plugin; +using Wox.Core.Resource; +using Wox.Core.UserSettings; +using Wox.Infrastructure; +using Wox.Infrastructure.Hotkey; +using Wox.Plugin; +using Wox.Storage; + +namespace Wox.ViewModel +{ + public class MainViewModel : BaseViewModel + { + #region Private Fields + + private ResultPanelViewModel _searchResultPanel; + private ResultPanelViewModel _actionPanel; + private string _queryText; + private bool _isVisible; + private bool _isSearchResultPanelVisible; + private bool _isActionPanelVisible; + private bool _isProgressBarVisible; + private bool _isProgressBarTooltipVisible; + private bool _selectAllText; + private int _caretIndex; + private double _left; + private double _top; + + private bool _queryHasReturn; + private Query _lastQuery = new Query(); + private bool _ignoreTextChange; + private List CurrentContextMenus = new List(); + private string _textBeforeEnterContextMenuMode; + + #endregion + + #region Constructor + + public MainViewModel() + { + this.InitializeResultPanel(); + this.InitializeActionPanel(); + this.InitializeKeyCommands(); + + this._queryHasReturn = false; + } + + #endregion + + #region ViewModel Properties + + public ResultPanelViewModel SearchResultPanel + { + get + { + return this._searchResultPanel; + } + } + + public ResultPanelViewModel ActionPanel + { + get + { + return this._actionPanel; + } + } + + public string QueryText + { + get + { + return this._queryText; + } + set + { + this._queryText = value; + OnPropertyChanged("QueryText"); + + this.HandleQueryTextUpdated(); + } + } + + public bool SelectAllText + { + get + { + return this._selectAllText; + } + set + { + this._selectAllText = value; + OnPropertyChanged("SelectAllText"); + } + } + + public int CaretIndex + { + get + { + return this._caretIndex; + } + set + { + this._caretIndex = value; + OnPropertyChanged("CaretIndex"); + } + } + + public bool IsVisible + { + get + { + return this._isVisible; + } + set + { + this._isVisible = value; + OnPropertyChanged("IsVisible"); + + if (!value && this.IsActionPanelVisible) + { + this.BackToSearchMode(); + } + } + } + + public bool IsSearchResultPanelVisible + { + get + { + return this._isSearchResultPanelVisible; + } + set + { + this._isSearchResultPanelVisible = value; + OnPropertyChanged("IsSearchResultPanelVisible"); + } + } + + public bool IsActionPanelVisible + { + get + { + return this._isActionPanelVisible; + } + set + { + this._isActionPanelVisible = value; + OnPropertyChanged("IsActionPanelVisible"); + } + } + + public bool IsProgressBarVisible + { + get + { + return this._isProgressBarVisible; + } + set + { + this._isProgressBarVisible = value; + OnPropertyChanged("IsProgressBarVisible"); + } + } + + public bool IsProgressBarTooltipVisible + { + get + { + return this._isProgressBarTooltipVisible; + } + set + { + this._isProgressBarTooltipVisible = value; + OnPropertyChanged("IsProgressBarTooltipVisible"); + } + } + + public double Left + { + get + { + return this._left; + } + set + { + this._left = value; + OnPropertyChanged("Left"); + } + } + + public double Top + { + get + { + return this._top; + } + set + { + this._top = value; + OnPropertyChanged("Top"); + } + } + + public ICommand EscCommand + { + get; + set; + } + + public ICommand SelectNextItemCommand + { + get; + set; + } + + public ICommand SelectPrevItemCommand + { + get; + set; + } + + public ICommand CtrlOCommand + { + get; + set; + } + + public ICommand DisplayNextQueryCommand + { + get; + set; + } + + public ICommand DisplayPrevQueryCommand + { + get; + set; + } + + public ICommand SelectNextPageCommand + { + get; + set; + } + + public ICommand SelectPrevPageCommand + { + get; + set; + } + + public ICommand StartHelpCommand + { + get; + set; + } + + public ICommand ShiftEnterCommand + { + get; + set; + } + + public ICommand OpenResultCommand + { + get; + set; + } + + public ICommand BackCommand + { + get; + set; + } + + #endregion + + #region Private Methods + + private void InitializeKeyCommands() + { + this.EscCommand = new RelayCommand((parameter) => + { + + if (this.IsActionPanelVisible) + { + this.BackToSearchMode(); + } + else + { + this.IsVisible = false; + } + + }); + + this.SelectNextItemCommand = new RelayCommand((parameter) => + { + + if (this.IsActionPanelVisible) + { + this._actionPanel.SelectNextResult(); + } + else + { + this._searchResultPanel.SelectNextResult(); + } + + }); + + this.SelectPrevItemCommand = new RelayCommand((parameter) => + { + + if (this.IsActionPanelVisible) + { + this._actionPanel.SelectPrevResult(); + } + else + { + this._searchResultPanel.SelectPrevResult(); + } + + }); + + this.CtrlOCommand = new RelayCommand((parameter) => + { + + if (this.IsActionPanelVisible) + { + BackToSearchMode(); + } + else + { + ShowActionPanel(this._searchResultPanel.SelectedResult.RawResult); + } + }); + + this.DisplayNextQueryCommand = new RelayCommand((parameter) => + { + + var nextQuery = QueryHistoryStorage.Instance.Next(); + DisplayQueryHistory(nextQuery); + + }); + + this.DisplayPrevQueryCommand = new RelayCommand((parameter) => + { + + var prev = QueryHistoryStorage.Instance.Previous(); + DisplayQueryHistory(prev); + + }); + + this.SelectNextPageCommand = new RelayCommand((parameter) => + { + + this._searchResultPanel.SelectNextPage(); + + }); + + this.SelectPrevPageCommand = new RelayCommand((parameter) => + { + + this._searchResultPanel.SelectPrevPage(); + + }); + + this.StartHelpCommand = new RelayCommand((parameter) => + { + Process.Start("http://doc.getwox.com"); + }); + + this.ShiftEnterCommand = new RelayCommand((parameter) => + { + + if (!this.IsActionPanelVisible && null != this._searchResultPanel.SelectedResult) + { + this.ShowActionPanel(this._searchResultPanel.SelectedResult.RawResult); + } + + }); + + this.OpenResultCommand = new RelayCommand((parameter) => + { + + if (null != parameter) + { + var index = int.Parse(parameter.ToString()); + this._searchResultPanel.SelectResult(index); + } + + if (null != this._searchResultPanel.SelectedResult) + { + this._searchResultPanel.SelectedResult.OpenResultCommand.Execute(null); + } + }); + + this.BackCommand = new RelayCommand((parameter) => + { + if (null != ListeningKeyPressed) + { + this.ListeningKeyPressed(this, new ListeningKeyPressedEventArgs(parameter as System.Windows.Input.KeyEventArgs)); + } + + }); + } + + private void InitializeResultPanel() + { + this._searchResultPanel = new ResultPanelViewModel(); + this.IsSearchResultPanelVisible = false; + } + + private void ShowActionPanel(Result result) + { + if (result == null) return; + this.ShowActionPanel(result, PluginManager.GetContextMenusForPlugin(result)); + } + + private void ShowActionPanel(Result result, List actions) + { + actions.ForEach(o => + { + o.PluginDirectory = PluginManager.GetPluginForId(result.PluginID).Metadata.PluginDirectory; + o.PluginID = result.PluginID; + o.OriginQuery = result.OriginQuery; + }); + + actions.Add(GetTopMostContextMenu(result)); + + this.DisplayActionPanel(actions, result.PluginID); + } + + private void DisplayActionPanel(List actions, string pluginID) + { + _textBeforeEnterContextMenuMode = this.QueryText; + + this._actionPanel.Clear(); + this._actionPanel.AddResults(actions, pluginID); + CurrentContextMenus = actions; + + this.IsActionPanelVisible = true; + this.IsSearchResultPanelVisible = false; + + this.QueryText = ""; + } + + private Result GetTopMostContextMenu(Result result) + { + if (TopMostRecordStorage.Instance.IsTopMost(result)) + { + return new Result(InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"), "Images\\down.png") + { + PluginDirectory = WoxDirectroy.Executable, + Action = _ => + { + TopMostRecordStorage.Instance.Remove(result); + App.API.ShowMsg("Succeed", "", ""); + return false; + } + }; + } + else + { + return new Result(InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"), "Images\\up.png") + { + PluginDirectory = WoxDirectroy.Executable, + Action = _ => + { + TopMostRecordStorage.Instance.AddOrUpdate(result); + App.API.ShowMsg("Succeed", "", ""); + return false; + } + }; + } + } + + private void InitializeActionPanel() + { + this._actionPanel = new ResultPanelViewModel(); + this.IsActionPanelVisible = false; + } + + private void HandleQueryTextUpdated() + { + if (_ignoreTextChange) { _ignoreTextChange = false; return; } + + this.IsProgressBarTooltipVisible = false; + if (this.IsActionPanelVisible) + { + QueryActionPanel(); + } + else + { + string query = this.QueryText.Trim(); + if (!string.IsNullOrEmpty(query)) + { + Query(query); + //reset query history index after user start new query + ResetQueryHistoryIndex(); + } + else + { + this._searchResultPanel.Clear(); + } + } + } + + private void QueryActionPanel() + { + var contextMenuId = "Context Menu Id"; + this._actionPanel.Clear(); + var query = this.QueryText.ToLower(); + if (string.IsNullOrEmpty(query)) + { + this._actionPanel.AddResults(CurrentContextMenus, contextMenuId); + } + else + { + List filterResults = new List(); + foreach (Result contextMenu in CurrentContextMenus) + { + if (StringMatcher.IsMatch(contextMenu.Title, query) + || StringMatcher.IsMatch(contextMenu.SubTitle, query)) + { + filterResults.Add(contextMenu); + } + } + this._actionPanel.AddResults(filterResults, contextMenuId); + } + } + + private void Query(string text) + { + _queryHasReturn = false; + var query = PluginManager.QueryInit(text); + if (query != null) + { + // handle the exclusiveness of plugin using action keyword + string lastKeyword = _lastQuery.ActionKeyword; + string keyword = query.ActionKeyword; + if (string.IsNullOrEmpty(lastKeyword)) + { + if (!string.IsNullOrEmpty(keyword)) + { + this._searchResultPanel.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + else + { + if (string.IsNullOrEmpty(keyword)) + { + this._searchResultPanel.RemoveResultsFor(PluginManager.NonGlobalPlugins[lastKeyword].Metadata); + } + else if (lastKeyword != keyword) + { + this._searchResultPanel.RemoveResultsExcept(PluginManager.NonGlobalPlugins[keyword].Metadata); + } + } + _lastQuery = query; + + Action action = new Action(async () => + { + await Task.Delay(150); + if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + { + this.IsProgressBarTooltipVisible = true; + } + }); + action.Invoke(); + + //Application.Current.Dispatcher.InvokeAsync(async () => + //{ + // await Task.Delay(150); + // if (!string.IsNullOrEmpty(query.RawQuery) && query.RawQuery == _lastQuery.RawQuery && !_queryHasReturn) + // { + // StartProgress(); + // } + //}); + PluginManager.QueryForAllPlugins(query); + } + + this.IsProgressBarTooltipVisible = false; + } + + private void ResetQueryHistoryIndex() + { + this._searchResultPanel.RemoveResultsFor(QueryHistoryStorage.MetaData); + QueryHistoryStorage.Instance.Reset(); + } + + private void UpdateResultViewInternal(List list, PluginMetadata metadata) + { + Infrastructure.Stopwatch.Normal($"UI update cost for {metadata.Name}", + () => { this._searchResultPanel.AddResults(list, metadata.ID); }); + } + + private void BackToSearchMode() + { + this.QueryText = _textBeforeEnterContextMenuMode; + this.IsActionPanelVisible = false; + this.IsSearchResultPanelVisible = true; + this.CaretIndex = this.QueryText.Length; + } + + private void DisplayQueryHistory(HistoryItem history) + { + if (history != null) + { + var historyMetadata = QueryHistoryStorage.MetaData; + + this.QueryText = history.Query; + this.SelectAllText = true; + + var executeQueryHistoryTitle = InternationalizationManager.Instance.GetTranslation("executeQuery"); + var lastExecuteTime = InternationalizationManager.Instance.GetTranslation("lastExecuteTime"); + this._searchResultPanel.RemoveResultsExcept(historyMetadata); + UpdateResultViewInternal(new List + { + new Result + { + Title = string.Format(executeQueryHistoryTitle,history.Query), + SubTitle = string.Format(lastExecuteTime,history.ExecutedDateTime), + IcoPath = "Images\\history.png", + PluginDirectory = WoxDirectroy.Executable, + Action = _ =>{ + + this.QueryText = history.Query; + this.SelectAllText = true; + + return false; + } + } + }, historyMetadata); + } + } + + #endregion + + #region Public Methods + + public void UpdateResultView(List list, PluginMetadata metadata, Query originQuery) + { + _queryHasReturn = true; + this.IsProgressBarTooltipVisible = false; + + list.ForEach(o => + { + o.Score += UserSelectedRecordStorage.Instance.GetSelectedCount(o) * 5; + }); + if (originQuery.RawQuery == _lastQuery.RawQuery) + { + System.Windows.Application.Current.Dispatcher.Invoke(() => + { + UpdateResultViewInternal(list, metadata); + }); + } + + if (list.Count > 0) + { + this.IsSearchResultPanelVisible = true; + } + } + + public void ShowActionPanel(List actions, string pluginID) + { + this.DisplayActionPanel(actions, pluginID); + } + + #endregion + + public event EventHandler ListeningKeyPressed; + + } + + public class ListeningKeyPressedEventArgs : EventArgs + { + + public System.Windows.Input.KeyEventArgs KeyEventArgs + { + get; + private set; + } + + public ListeningKeyPressedEventArgs(System.Windows.Input.KeyEventArgs keyEventArgs) + { + this.KeyEventArgs = keyEventArgs; + } + + } +} diff --git a/Wox/ViewModel/ResultItemViewModel.cs b/Wox/ViewModel/ResultItemViewModel.cs new file mode 100644 index 0000000000..a963e294cd --- /dev/null +++ b/Wox/ViewModel/ResultItemViewModel.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Wox.Core.Plugin; +using Wox.Core.Resource; +using Wox.Infrastructure; +using Wox.Infrastructure.Hotkey; +using Wox.Plugin; +using Wox.Storage; + +namespace Wox.ViewModel +{ + public class ResultItemViewModel : BaseViewModel + { + #region Private Fields + + private Result _result; + private bool _isSelected; + + #endregion + + #region Constructor + + public ResultItemViewModel(Result result) + { + if(null!= result) + { + this._result = result; + + this.OpenResultCommand = new RelayCommand((parameter) => { + + bool hideWindow = result.Action(new ActionContext + { + SpecialKeyState = GlobalHotkey.Instance.CheckModifiers() + }); + + if (hideWindow) + { + App.API.HideApp(); + UserSelectedRecordStorage.Instance.Add(this._result); + QueryHistoryStorage.Instance.Add(this._result.OriginQuery.RawQuery); + } + }); + + this.OpenResultActionPanelCommand = new RelayCommand((parameter) => + { + + var actions = PluginManager.GetContextMenusForPlugin(result); + + var pluginMetaData = PluginManager.GetPluginForId(result.PluginID).Metadata; + actions.ForEach(o => + { + o.PluginDirectory = pluginMetaData.PluginDirectory; + o.PluginID = result.PluginID; + o.OriginQuery = result.OriginQuery; + }); + + actions.Add(GetTopMostContextMenu(result)); + + App.API.ShowContextMenu(pluginMetaData, actions); + + }); + } + } + + + #endregion + + #region ViewModel Properties + + public string Title + { + get + { + return this._result.Title; + } + } + + public string SubTitle + { + get + { + return this._result.SubTitle; + } + } + + public string FullIcoPath + { + get + { + return this._result.FullIcoPath; + } + } + + public bool IsSelected + { + get { return _isSelected; } + set + { + _isSelected = value; + OnPropertyChanged("IsSelected"); + } + } + + public RelayCommand OpenResultCommand + { + get; + set; + } + + public RelayCommand OpenResultActionPanelCommand + { + get; + set; + } + + #endregion + + #region Properties + + public Result RawResult + { + get + { + return this._result; + } + } + + #endregion + + #region Private Methods + + private Result GetTopMostContextMenu(Result result) + { + if (TopMostRecordStorage.Instance.IsTopMost(result)) + { + return new Result(InternationalizationManager.Instance.GetTranslation("cancelTopMostInThisQuery"), "Images\\down.png") + { + PluginDirectory = WoxDirectroy.Executable, + Action = _ => + { + TopMostRecordStorage.Instance.Remove(result); + App.API.ShowMsg("Succeed", "", ""); + return false; + } + }; + } + else + { + return new Result(InternationalizationManager.Instance.GetTranslation("setAsTopMostInThisQuery"), "Images\\up.png") + { + PluginDirectory = WoxDirectroy.Executable, + Action = _ => + { + TopMostRecordStorage.Instance.AddOrUpdate(result); + App.API.ShowMsg("Succeed", "", ""); + return false; + } + }; + } + } + + + #endregion + + public override bool Equals(object obj) + { + ResultItemViewModel r = obj as ResultItemViewModel; + if (r != null) + { + return _result.Equals(r.RawResult); + } + + return false; + } + + public override int GetHashCode() + { + return _result.GetHashCode(); + } + + public override string ToString() + { + return _result.ToString(); + } + + } +} diff --git a/Wox/ViewModel/ResultPanelViewModel.cs b/Wox/ViewModel/ResultPanelViewModel.cs new file mode 100644 index 0000000000..2cc946df62 --- /dev/null +++ b/Wox/ViewModel/ResultPanelViewModel.cs @@ -0,0 +1,352 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Wox.Core.UserSettings; +using Wox.Plugin; +using Wox.Storage; + +namespace Wox.ViewModel +{ + public class ResultPanelViewModel : BaseViewModel + { + #region Private Fields + + private ResultItemViewModel _selectedResult; + private ResultCollection _results; + private bool _isVisible; + private Thickness _margin; + + private readonly object _resultsUpdateLock = new object(); + + #endregion + + #region Constructor + + public ResultPanelViewModel() + { + this._results = new ResultCollection(); + } + + #endregion + + #region ViewModel Properties + + public int MaxHeight + { + get + { + return UserSettingStorage.Instance.MaxResultsToShow * 50; + } + } + + public ResultCollection Results + { + get + { + return this._results; + } + } + + public ResultItemViewModel SelectedResult + { + get + { + return this._selectedResult; + } + set + { + if (null != value) + { + if (null != _selectedResult) + { + _selectedResult.IsSelected = false; + } + + _selectedResult = value; + + if (null != _selectedResult) + { + _selectedResult.IsSelected = true; + } + + } + + OnPropertyChanged("SelectedResult"); + + } + } + + public Thickness Margin + { + get + { + return this._margin; + } + set + { + this._margin = value; + OnPropertyChanged("Margin"); + } + } + + #endregion + + #region Private Methods + + private bool IsTopMostResult(Result result) + { + return TopMostRecordStorage.Instance.IsTopMost(result); + } + + private int InsertIndexOf(int newScore, IList list) + { + int index = 0; + for (; index < list.Count; index++) + { + var result = list[index]; + if (newScore > result.RawResult.Score) + { + break; + } + } + return index; + } + + #endregion + + #region Public Methods + + public void SelectResult(int index) + { + if(index <= this.Results.Count - 1) + { + this.SelectedResult = this.Results[index]; + } + } + + public void SelectNextResult() + { + if (null != this.SelectedResult) + { + var index = this.Results.IndexOf(this.SelectedResult); + if(index == this.Results.Count - 1) + { + index = -1; + } + this.SelectedResult = this.Results.ElementAt(index + 1); + } + } + + public void SelectPrevResult() + { + if (null != this.SelectedResult) + { + var index = this.Results.IndexOf(this.SelectedResult); + if (index == 0) + { + index = this.Results.Count; + } + this.SelectedResult = this.Results.ElementAt(index - 1); + } + } + + public void SelectNextPage() + { + var index = 0; + if (null != this.SelectedResult) + { + index = this.Results.IndexOf(this.SelectedResult); + } + index += 5; + if (index > this.Results.Count - 1) + { + index = this.Results.Count - 1; + } + this.SelectedResult = this.Results.ElementAt(index); + } + + public void SelectPrevPage() + { + var index = 0; + if (null != this.SelectedResult) + { + index = this.Results.IndexOf(this.SelectedResult); + } + index -= 5; + if (index < 0) + { + index = 0; + } + this.SelectedResult = this.Results.ElementAt(index); + } + + public void Clear() + { + this._results.Clear(); + } + + public void RemoveResultsExcept(PluginMetadata metadata) + { + lock (_resultsUpdateLock) + { + _results.RemoveAll(r => r.RawResult.PluginID != metadata.ID); + } + } + + public void RemoveResultsFor(PluginMetadata metadata) + { + lock (_resultsUpdateLock) + { + _results.RemoveAll(r => r.RawResult.PluginID == metadata.ID); + } + } + + public void AddResults(List newRawResults, string resultId) + { + lock (_resultsUpdateLock) + { + var newResults = new List(); + newRawResults.ForEach((re) => { newResults.Add(new ResultItemViewModel(re)); }); + // todo use async to do new result calculation + var resultsCopy = _results.ToList(); + var oldResults = resultsCopy.Where(r => r.RawResult.PluginID == resultId).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)) + { + resultsCopy.Remove(result); + } + + // update scores + foreach (var result in newResults) + { + if (IsTopMostResult(result.RawResult)) + { + result.RawResult.Score = int.MaxValue; + } + } + + // update index for result in intersection of A and B + foreach (var commonResult in intersection) + { + int oldIndex = resultsCopy.IndexOf(commonResult); + int oldScore = resultsCopy[oldIndex].RawResult.Score; + int newScore = newResults[newResults.IndexOf(commonResult)].RawResult.Score; + if (newScore != oldScore) + { + var oldResult = resultsCopy[oldIndex]; + oldResult.RawResult.Score = newScore; + resultsCopy.RemoveAt(oldIndex); + int newIndex = InsertIndexOf(newScore, resultsCopy); + resultsCopy.Insert(newIndex, oldResult); + + } + } + + // insert result in relative complement of A in B + foreach (var result in newResults.Except(intersection)) + { + int newIndex = InsertIndexOf(result.RawResult.Score, resultsCopy); + resultsCopy.Insert(newIndex, result); + } + + // update UI in one run, so it can avoid UI flickering + _results.Update(resultsCopy); + + if(this._results.Count > 0) + { + this.Margin = new Thickness { Top = 8 }; + this.SelectedResult = this._results[0]; + } + else + { + this.Margin = new Thickness { Top = 0 }; + } + } + } + + + #endregion + + public class ResultCollection : ObservableCollection + // todo implement custom moveItem,removeItem,insertItem for better performance + { + + public ResultCollection() + { + } + + public void RemoveAll(Predicate predicate) + { + CheckReentrancy(); + + List itemsToRemove = Items.Where(x => predicate(x)).ToList(); + if (itemsToRemove.Count > 0) + { + + itemsToRemove.ForEach(item => { + + Items.Remove(item); + + }); + + OnPropertyChanged(new PropertyChangedEventArgs("Count")); + OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); + // fuck ms + // http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx + // http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx + // PS: don't use Reset for other data updates, it will cause UI flickering + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + + } + + public void Update(List newItems) + { + int newCount = newItems.Count; + int oldCount = Items.Count; + int location = newCount > oldCount ? oldCount : newCount; + for (int i = 0; i < location; i++) + { + ResultItemViewModel oldItem = Items[i]; + ResultItemViewModel newItem = newItems[i]; + if (!oldItem.Equals(newItem)) + { + this[i] = newItem; + } + else if (oldItem.RawResult.Score != newItem.RawResult.Score) + { + this[i].RawResult.Score = newItem.RawResult.Score; + } + } + + if (newCount > oldCount) + { + for (int i = oldCount; i < newCount; i++) + { + Add(newItems[i]); + } + } + else + { + int removeIndex = newCount; + for (int i = newCount; i < oldCount; i++) + { + RemoveAt(removeIndex); + } + } + + } + } + + } + +} diff --git a/Wox/Wox.csproj b/Wox/Wox.csproj index 4afdc609e0..5f45652b01 100644 --- a/Wox/Wox.csproj +++ b/Wox/Wox.csproj @@ -123,12 +123,18 @@ - + + + + + + + WoxUpdate.xaml diff --git a/less.exe.stackdump b/less.exe.stackdump new file mode 100644 index 0000000000..ec6e43c4a0 --- /dev/null +++ b/less.exe.stackdump @@ -0,0 +1,15 @@ +Stack trace: +Frame Function Args +00000010000 0018007208E (0018024F7D0, 00180215E59, 00000010000, 0000022B940) +00000010000 00180046DF2 (0000022C9A8, 001803253F8, 00000000001, 001803253F8) +00000010000 00180046E32 (00000000001, 00180325608, 00000010000, 00000000002) +00000010000 0018006C65D (001800CDED2, 00000000000, 00000000000, 00000000000) +0000022CBC0 0018006C6EE (00000000020, 00000000023, 00180047805, 00000000000) +0000022CBC0 001800475B7 (000002B6150, 00002080014, 0000022CDB0, 00000000000) +00000000000 0018004602C (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 001800460C4 (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 00100414FC1 (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 00100401010 (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 000779A59CD (00000000000, 00000000000, 00000000000, 00000000000) +00000000000 00077ADB981 (00000000000, 00000000000, 00000000000, 00000000000) +End of stack trace