Merge pull request #494 from LingForCC/MVVM_MainWindow_ResultPanel

Refactor MainWindow and ResultPanel
This commit is contained in:
bao-qian 2016-02-19 18:39:41 +00:00
commit 93682fb42b
22 changed files with 1944 additions and 1108 deletions

View File

@ -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<App>.Restart)
{
Window.CloseApp();
API.CloseApp();
}
else
{

View File

@ -34,7 +34,7 @@ namespace Wox.CommandArgs
}
else
{
App.Window.ShowApp();
App.API.ShowApp();
}
}
}

View File

@ -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();
}
}
}

View File

@ -12,7 +12,7 @@ namespace Wox.CommandArgs
public void Execute(IList<string> args)
{
PluginManager.Init(App.Window);
PluginManager.Init(App.API);
}
}
}

View File

@ -11,7 +11,14 @@ namespace Wox.CommandArgs
public void Execute(IList<string> args)
{
App.Window.ToggleWox();
if (App.Window.IsVisible)
{
App.API.HideApp();
}
else
{
App.API.ShowApp();
}
}
}
}

View File

@ -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<VisibilityConverter>
{
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;
}
}
}

View File

@ -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<HotkeyEventArgs> 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);
}
}
}
}

View File

@ -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<Result>
// todo implement custom moveItem,removeItem,insertItem for better performance
{
public void RemoveAll(Predicate<Result> predicate)
{
CheckReentrancy();
List<Result> 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<Result> 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);
}
}
}
}
}

View File

@ -10,7 +10,7 @@ namespace Wox.Helper
{
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(x => x.GetType() == typeof(T))
?? (T)Activator.CreateInstance(typeof(T), args);
Application.Current.MainWindow.Hide();
App.API.HideApp();
window.Show();
window.Focus();

View File

@ -1,6 +1,9 @@
<Window x:Class="Wox.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:wox="clr-namespace:Wox"
xmlns:vm="clr-namespace:Wox.ViewModel" xmlns:converters="clr-namespace:Wox.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
Title="Wox"
Topmost="True"
Loaded="MainWindow_OnLoaded"
@ -15,14 +18,29 @@
Style="{DynamicResource WindowStyle}"
Icon="Images\app.png"
AllowsTransparency="True"
>
Visibility="{Binding IsVisible,Converter={converters:VisibilityConverter}}"
PreviewKeyDown="Window_PreviewKeyDown" d:DataContext="{d:DesignInstance vm:MainViewModel, IsDesignTimeCreatable=True}">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:ResultPanelViewModel}">
<wox:ResultPanel></wox:ResultPanel>
</DataTemplate>
<converters:VisibilityConverter x:Key="VisibilityConverter" />
</Window.Resources>
<Border Style="{DynamicResource WindowBorderStyle}" MouseDown="Border_OnMouseDown">
<StackPanel Orientation="Vertical">
<TextBox Style="{DynamicResource QueryBoxStyle}" PreviewDragOver="TbQuery_OnPreviewDragOver" AllowDrop="True"
x:Name="tbQuery" PreviewKeyDown="TbQuery_OnPreviewKeyDown" TextChanged="TbQuery_OnTextChanged" />
<Line Style="{DynamicResource PendingLineStyle}" x:Name="progressBar" Y1="0" Y2="0" X2="100" Height="2" StrokeThickness="1"></Line>
<wox:ResultPanel x:Name="pnlResult" />
<wox:ResultPanel x:Name="pnlContextMenu" Visibility="Collapsed" />
<TextBox Style="{DynamicResource QueryBoxStyle}" Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
PreviewDragOver="TbQuery_OnPreviewDragOver" AllowDrop="True"
x:Name="tbQuery" />
<Line Style="{DynamicResource PendingLineStyle}" x:Name="progressBar" Y1="0" Y2="0" X2="100" Height="2" StrokeThickness="1"
Visibility="{Binding IsProgressBarVisible,Converter={StaticResource VisibilityConverter}}">
<Line.ToolTip>
<ToolTip IsOpen="{Binding IsProgressBarTooltipVisible}"></ToolTip>
</Line.ToolTip>
</Line>
<ContentControl Content="{Binding SearchResultPanel}" Visibility="{Binding IsSearchResultPanelVisible,Converter={StaticResource VisibilityConverter}}">
</ContentControl>
<ContentControl Content="{Binding ActionPanel}" Visibility="{Binding IsActionPanelVisible,Converter={StaticResource VisibilityConverter}}">
</ContentControl>
</StackPanel>
</Border>
</Window>

View File

@ -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<Result> CurrentContextMenus = new List<Result>();
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<App>.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<SettingWindow>(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<PluginPair> 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<Result> 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<Result> 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<HotkeyEventArgs> action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
public void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> 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);
}
}
/// <summary>
/// Checks if Wox should ignore any hotkeys
/// </summary>
/// <returns></returns>
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<Result> filterResults = new List<Result>();
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<Result>
{
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<Result> 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<Result> 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<Result> 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<Result> 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)

40
Wox/NotifyIconManager.cs Normal file
View File

@ -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);
}
}
}

293
Wox/PublicAPIInstance.cs Normal file
View File

@ -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<App>.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<SettingWindow>(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<PluginPair> 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<Result> 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<Result> 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<HotkeyEventArgs> action)
{
var hotkey = new HotkeyModel(hotkeyStr);
SetHotkey(hotkey, action);
}
public void SetHotkey(HotkeyModel hotkey, EventHandler<HotkeyEventArgs> 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);
}
}
/// <summary>
/// Checks if Wox should ignore any hotkeys
/// </summary>
/// <returns></returns>
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
}
}

View File

@ -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">
<ListBox x:Name="lbResults" MaxHeight="{Binding ElementName=Results, Path=MaxResultsToShow}"
HorizontalContentAlignment="Stretch" PreviewMouseDown="LbResults_OnPreviewMouseDown"
xmlns:vm ="clr-namespace:Wox.ViewModel"
mc:Ignorable="d" d:DesignWidth="100" d:DesignHeight="100" d:DataContext="{d:DesignInstance vm:ResultPanelViewModel}">
<ListBox x:Name="lbResults" MaxHeight="{Binding MaxHeight}" SelectedItem="{Binding SelectedResult}"
HorizontalContentAlignment="Stretch" ItemsSource="{Binding Results}" Margin="{Binding Margin}"
Style="{DynamicResource BaseListboxStyle}" SelectionChanged="lbResults_SelectionChanged" Focusable="False"
KeyboardNavigation.DirectionalNavigation="Cycle" SelectionMode="Single"
VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Standard">
<ListBox.ItemTemplate>
<DataTemplate>
<!-- a result item height is 50 including margin -->
<Grid HorizontalAlignment="Stretch" Height="40" VerticalAlignment="Stretch" Margin="5"
<DataTemplate.DataType>
<x:Type TypeName="vm:ResultItemViewModel" />
</DataTemplate.DataType>
<Button Command="{Binding OpenResultCommand}">
<Button.InputBindings>
<MouseBinding Command="{Binding OpenResultActionPanelCommand}" MouseAction="RightClick"></MouseBinding>
</Button.InputBindings>
<Button.Template>
<ControlTemplate>
<ContentPresenter Content="{TemplateBinding Button.Content}"></ContentPresenter>
</ControlTemplate>
</Button.Template>
<Button.Content>
<Grid HorizontalAlignment="Stretch" Height="40" VerticalAlignment="Stretch" Margin="5"
Cursor="Hand">
<Grid.Resources>
<converters:ImagePathConverter x:Key="ImageConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<Image x:Name="imgIco" Width="32" Height="32" HorizontalAlignment="Left"
<Grid.Resources>
<converters:ImagePathConverter x:Key="ImageConverter" />
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition />
<ColumnDefinition Width="0" />
</Grid.ColumnDefinitions>
<Image x:Name="imgIco" Width="32" Height="32" HorizontalAlignment="Left"
Source="{Binding FullIcoPath,Converter={StaticResource ImageConverter},IsAsync=True}" />
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" x:Name="SubTitleRowDefinition" />
</Grid.RowDefinitions>
<TextBlock Style="{DynamicResource ItemTitleStyle}" DockPanel.Dock="Left"
<Grid Margin="5 0 5 0" Grid.Column="1" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" x:Name="SubTitleRowDefinition" />
</Grid.RowDefinitions>
<TextBlock Style="{DynamicResource ItemTitleStyle}" DockPanel.Dock="Left"
VerticalAlignment="Center" ToolTip="{Binding Title}" x:Name="tbTitle"
Text="{Binding Title}" />
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding SubTitle}"
Text="{Binding Title}" >
</TextBlock>
<TextBlock Style="{DynamicResource ItemSubTitleStyle}" ToolTip="{Binding SubTitle}"
Visibility="{Binding SubTitle, Converter={converters:StringNullOrEmptyToVisibilityConverter}}"
Grid.Row="1" x:Name="tbSubTitle" Text="{Binding SubTitle}" />
</Grid>
<TextBlock Grid.Column="2" x:Name="tbItemNumber" Style="{DynamicResource ItemNumberStyle}" Text="9"/>
</Grid>
Grid.Row="1" x:Name="tbSubTitle" Text="{Binding SubTitle}" >
</TextBlock>
</Grid>
</Grid>
</Button.Content>
</Button>
<!-- a result item height is 50 including margin -->
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}, Path=IsSelected}"
Value="True">
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter TargetName="tbTitle" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
<Setter TargetName="tbSubTitle" Property="Style"
Value="{DynamicResource ItemSubTitleSelectedStyle}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter TargetName="tbSubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>

View File

@ -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<Result> LeftMouseClickEvent;
public event Action<Result> RightMouseClickEvent;
public event Action<Result, IDataObject, DragEventArgs> ItemDropEvent;
private readonly ListBoxItems _results;
private readonly object _resultsUpdateLock = new object();
protected virtual void OnRightMouseClick(Result result)
{
Action<Result> handler = RightMouseClickEvent;
if (handler != null) handler(result);
}
protected virtual void OnLeftMouseClick(Result result)
{
Action<Result> 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<Result> 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<Result> 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<Result> GetVisibleResults()
{
List<Result> visibleElements = new List<Result>();
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<ContentPresenter>(item);
// if (myContentPresenter != null)
// {
// DataTemplate dataTemplate = myContentPresenter.ContentTemplate;
// TextBlock tbItemNumber = (TextBlock)dataTemplate.FindName("tbItemNumber", myContentPresenter);
// tbItemNumber.Text = index.ToString();
// }
// }
//}
}
private childItem FindVisualChild<childItem>(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<childItem>(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);
}
}
}

View File

@ -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<ISettingProvider, Control> featureControls = new Dictionary<ISettingProvider, Control>();
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<HotkeyEventArgs> 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);
}
}

View File

@ -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<object> _action;
public RelayCommand(Action<object> 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);
}
}
}
}

View File

@ -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<Result> CurrentContextMenus = new List<Result>();
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<Result> 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<Result> 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<Result> filterResults = new List<Result>();
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<Result> 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<Result>
{
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<Result> 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<Result> actions, string pluginID)
{
this.DisplayActionPanel(actions, pluginID);
}
#endregion
public event EventHandler<ListeningKeyPressedEventArgs> ListeningKeyPressed;
}
public class ListeningKeyPressedEventArgs : EventArgs
{
public System.Windows.Input.KeyEventArgs KeyEventArgs
{
get;
private set;
}
public ListeningKeyPressedEventArgs(System.Windows.Input.KeyEventArgs keyEventArgs)
{
this.KeyEventArgs = keyEventArgs;
}
}
}

View File

@ -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();
}
}
}

View File

@ -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<ResultItemViewModel> 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<Result> newRawResults, string resultId)
{
lock (_resultsUpdateLock)
{
var newResults = new List<ResultItemViewModel>();
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<ResultItemViewModel>
// todo implement custom moveItem,removeItem,insertItem for better performance
{
public ResultCollection()
{
}
public void RemoveAll(Predicate<ResultItemViewModel> predicate)
{
CheckReentrancy();
List<ResultItemViewModel> 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<ResultItemViewModel> 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);
}
}
}
}
}
}

View File

@ -123,12 +123,18 @@
<Compile Include="Converters\OpacityModeConverter.cs" />
<Compile Include="Converters\StringEmptyConverter.cs" />
<Compile Include="Converters\StringNullOrEmptyToVisibilityConverter.cs" />
<Compile Include="Helper\ListBoxItems.cs" />
<Compile Include="Converters\VisibilityConverter.cs" />
<Compile Include="Helper\SingletonWindowOpener.cs" />
<Compile Include="ImageLoader\ImageCacheStroage.cs" />
<Compile Include="NotifyIconManager.cs" />
<Compile Include="PublicAPIInstance.cs" />
<Compile Include="Storage\QueryHistoryStorage.cs" />
<Compile Include="Storage\TopMostRecordStorage.cs" />
<Compile Include="Storage\UserSelectedRecordStorage.cs" />
<Compile Include="ViewModel\BaseViewModel.cs" />
<Compile Include="ViewModel\MainViewModel.cs" />
<Compile Include="ViewModel\ResultItemViewModel.cs" />
<Compile Include="ViewModel\ResultPanelViewModel.cs" />
<Compile Include="WoxUpdate.xaml.cs">
<DependentUpon>WoxUpdate.xaml</DependentUpon>
</Compile>

15
less.exe.stackdump Normal file
View File

@ -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