mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into topmost
This commit is contained in:
commit
9e85d2e364
10 changed files with 444 additions and 61 deletions
|
|
@ -45,6 +45,22 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
FilesFolders.ValidateDirectory(DirectoryPath);
|
||||
}
|
||||
|
||||
public bool Exists()
|
||||
{
|
||||
return File.Exists(FilePath);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
foreach (var path in new[] { FilePath, BackupFilePath, TempFilePath })
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<T> LoadAsync()
|
||||
{
|
||||
if (Data != null)
|
||||
|
|
|
|||
|
|
@ -299,6 +299,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
|
||||
public double WindowLeft { get; set; }
|
||||
public double WindowTop { get; set; }
|
||||
public double PreviousScreenWidth { get; set; }
|
||||
public double PreviousScreenHeight { get; set; }
|
||||
public double PreviousDpiX { get; set; }
|
||||
public double PreviousDpiY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom left position on selected monitor
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ namespace Flow.Launcher
|
|||
|
||||
InitSoundEffects();
|
||||
DataObject.AddPastingHandler(QueryTextBox, QueryTextBox_OnPaste);
|
||||
|
||||
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged += ThemeManager_ActualApplicationThemeChanged;
|
||||
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
|
||||
}
|
||||
|
||||
|
|
@ -101,6 +101,11 @@ namespace Flow.Launcher
|
|||
|
||||
#pragma warning disable VSTHRD100 // Avoid async void methods
|
||||
|
||||
private void ThemeManager_ActualApplicationThemeChanged(ModernWpf.ThemeManager sender, object args)
|
||||
{
|
||||
_theme.RefreshFrameAsync();
|
||||
}
|
||||
|
||||
private void OnSourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
var handle = Win32Helper.GetWindowHandle(this, true);
|
||||
|
|
@ -716,8 +721,26 @@ namespace Flow.Launcher
|
|||
{
|
||||
if (_settings.SearchWindowScreen == SearchWindowScreens.RememberLastLaunchLocation)
|
||||
{
|
||||
Top = _settings.WindowTop;
|
||||
var previousScreenWidth = _settings.PreviousScreenWidth;
|
||||
var previousScreenHeight = _settings.PreviousScreenHeight;
|
||||
GetDpi(out var previousDpiX, out var previousDpiY);
|
||||
|
||||
_settings.PreviousScreenWidth = SystemParameters.VirtualScreenWidth;
|
||||
_settings.PreviousScreenHeight = SystemParameters.VirtualScreenHeight;
|
||||
GetDpi(out var currentDpiX, out var currentDpiY);
|
||||
|
||||
if (previousScreenWidth != 0 && previousScreenHeight != 0 &&
|
||||
previousDpiX != 0 && previousDpiY != 0 &&
|
||||
(previousScreenWidth != SystemParameters.VirtualScreenWidth ||
|
||||
previousScreenHeight != SystemParameters.VirtualScreenHeight ||
|
||||
previousDpiX != currentDpiX || previousDpiY != currentDpiY))
|
||||
{
|
||||
AdjustPositionForResolutionChange();
|
||||
return;
|
||||
}
|
||||
|
||||
Left = _settings.WindowLeft;
|
||||
Top = _settings.WindowTop;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -730,27 +753,73 @@ namespace Flow.Launcher
|
|||
break;
|
||||
case SearchWindowAligns.CenterTop:
|
||||
Left = HorizonCenter(screen);
|
||||
Top = 10;
|
||||
Top = VerticalTop(screen);
|
||||
break;
|
||||
case SearchWindowAligns.LeftTop:
|
||||
Left = HorizonLeft(screen);
|
||||
Top = 10;
|
||||
Top = VerticalTop(screen);
|
||||
break;
|
||||
case SearchWindowAligns.RightTop:
|
||||
Left = HorizonRight(screen);
|
||||
Top = 10;
|
||||
Top = VerticalTop(screen);
|
||||
break;
|
||||
case SearchWindowAligns.Custom:
|
||||
Left = Win32Helper.TransformPixelsToDIP(this,
|
||||
screen.WorkingArea.X + _settings.CustomWindowLeft, 0).X;
|
||||
Top = Win32Helper.TransformPixelsToDIP(this, 0,
|
||||
screen.WorkingArea.Y + _settings.CustomWindowTop).Y;
|
||||
var customLeft = Win32Helper.TransformPixelsToDIP(this,
|
||||
screen.WorkingArea.X + _settings.CustomWindowLeft, 0);
|
||||
var customTop = Win32Helper.TransformPixelsToDIP(this, 0,
|
||||
screen.WorkingArea.Y + _settings.CustomWindowTop);
|
||||
Left = customLeft.X;
|
||||
Top = customTop.Y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AdjustPositionForResolutionChange()
|
||||
{
|
||||
var screenWidth = SystemParameters.VirtualScreenWidth;
|
||||
var screenHeight = SystemParameters.VirtualScreenHeight;
|
||||
GetDpi(out var currentDpiX, out var currentDpiY);
|
||||
|
||||
var previousLeft = _settings.WindowLeft;
|
||||
var previousTop = _settings.WindowTop;
|
||||
GetDpi(out var previousDpiX, out var previousDpiY);
|
||||
|
||||
var widthRatio = screenWidth / _settings.PreviousScreenWidth;
|
||||
var heightRatio = screenHeight / _settings.PreviousScreenHeight;
|
||||
var dpiXRatio = currentDpiX / previousDpiX;
|
||||
var dpiYRatio = currentDpiY / previousDpiY;
|
||||
|
||||
var newLeft = previousLeft * widthRatio * dpiXRatio;
|
||||
var newTop = previousTop * heightRatio * dpiYRatio;
|
||||
|
||||
var screenLeft = SystemParameters.VirtualScreenLeft;
|
||||
var screenTop = SystemParameters.VirtualScreenTop;
|
||||
|
||||
var maxX = screenLeft + screenWidth - ActualWidth;
|
||||
var maxY = screenTop + screenHeight - ActualHeight;
|
||||
|
||||
Left = Math.Max(screenLeft, Math.Min(newLeft, maxX));
|
||||
Top = Math.Max(screenTop, Math.Min(newTop, maxY));
|
||||
}
|
||||
|
||||
private void GetDpi(out double dpiX, out double dpiY)
|
||||
{
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
if (source != null && source.CompositionTarget != null)
|
||||
{
|
||||
var matrix = source.CompositionTarget.TransformToDevice;
|
||||
dpiX = 96 * matrix.M11;
|
||||
dpiY = 96 * matrix.M22;
|
||||
}
|
||||
else
|
||||
{
|
||||
dpiX = 96;
|
||||
dpiY = 96;
|
||||
}
|
||||
}
|
||||
|
||||
private Screen SelectedScreen()
|
||||
{
|
||||
Screen screen;
|
||||
|
|
@ -811,6 +880,13 @@ namespace Flow.Launcher
|
|||
return left;
|
||||
}
|
||||
|
||||
public double VerticalTop(Screen screen)
|
||||
{
|
||||
var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y);
|
||||
var top = dip1.Y + 10;
|
||||
return top;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Window Animation
|
||||
|
|
@ -1185,6 +1261,7 @@ namespace Flow.Launcher
|
|||
_notifyIcon?.Dispose();
|
||||
animationSoundWMP?.Close();
|
||||
animationSoundWPF?.Dispose();
|
||||
ModernWpf.ThemeManager.Current.ActualApplicationThemeChanged -= ThemeManager_ActualApplicationThemeChanged;
|
||||
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,18 +137,40 @@ public partial class SettingWindow
|
|||
|
||||
if (previousTop == null || previousLeft == null || !IsPositionValid(previousTop.Value, previousLeft.Value))
|
||||
{
|
||||
Top = WindowTop();
|
||||
Left = WindowLeft();
|
||||
SetWindowPosition(WindowTop(), WindowLeft());
|
||||
}
|
||||
else
|
||||
{
|
||||
Top = previousTop.Value;
|
||||
Left = previousLeft.Value;
|
||||
var left = _settings.SettingWindowLeft.Value;
|
||||
var top = _settings.SettingWindowTop.Value;
|
||||
AdjustWindowPosition(ref top, ref left);
|
||||
SetWindowPosition(top, left);
|
||||
}
|
||||
|
||||
WindowState = _settings.SettingWindowState;
|
||||
}
|
||||
|
||||
private void SetWindowPosition(double top, double left)
|
||||
{
|
||||
// Ensure window does not exceed screen boundaries
|
||||
top = Math.Max(top, SystemParameters.VirtualScreenTop);
|
||||
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
|
||||
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
|
||||
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
|
||||
|
||||
Top = top;
|
||||
Left = left;
|
||||
}
|
||||
|
||||
private void AdjustWindowPosition(ref double top, ref double left)
|
||||
{
|
||||
// Adjust window position if it exceeds screen boundaries
|
||||
top = Math.Max(top, SystemParameters.VirtualScreenTop);
|
||||
left = Math.Max(left, SystemParameters.VirtualScreenLeft);
|
||||
top = Math.Min(top, SystemParameters.VirtualScreenHeight - ActualHeight);
|
||||
left = Math.Min(left, SystemParameters.VirtualScreenWidth - ActualWidth);
|
||||
}
|
||||
|
||||
private static bool IsPositionValid(double top, double left)
|
||||
{
|
||||
foreach (var screen in Screen.AllScreens)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,115 @@
|
|||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
{
|
||||
public class TopMostRecord
|
||||
public class FlowLauncherJsonStorageTopMostRecord
|
||||
{
|
||||
private readonly FlowLauncherJsonStorage<MultipleTopMostRecord> _topMostRecordStorage;
|
||||
private readonly MultipleTopMostRecord _topMostRecord;
|
||||
|
||||
public FlowLauncherJsonStorageTopMostRecord()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// Get old data & new data
|
||||
var topMostRecordStorage = new FlowLauncherJsonStorage<TopMostRecord>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
_topMostRecordStorage = new FlowLauncherJsonStorage<MultipleTopMostRecord>();
|
||||
|
||||
// Check if data exist
|
||||
var oldDataExist = topMostRecordStorage.Exists();
|
||||
var newDataExist = _topMostRecordStorage.Exists();
|
||||
|
||||
// If new data exist, it means we have already migrated the old data
|
||||
// So we can safely delete the old data and load the new data
|
||||
if (newDataExist)
|
||||
{
|
||||
try
|
||||
{
|
||||
topMostRecordStorage.Delete();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored - Flow will delete the old data during next startup
|
||||
}
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
}
|
||||
// If new data does not exist and old data exist, we need to migrate the old data to the new data
|
||||
else if (oldDataExist)
|
||||
{
|
||||
// Migrate old data to new data
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
var oldTopMostRecord = topMostRecordStorage.Load();
|
||||
if (oldTopMostRecord == null || oldTopMostRecord.records.IsEmpty) return;
|
||||
foreach (var record in oldTopMostRecord.records)
|
||||
{
|
||||
var newValue = new ConcurrentQueue<Record>();
|
||||
newValue.Enqueue(record.Value);
|
||||
_topMostRecord.records.AddOrUpdate(record.Key, newValue, (key, oldValue) =>
|
||||
{
|
||||
oldValue.Enqueue(record.Value);
|
||||
return oldValue;
|
||||
});
|
||||
}
|
||||
|
||||
// Delete old data and save the new data
|
||||
try
|
||||
{
|
||||
topMostRecordStorage.Delete();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored - Flow will delete the old data during next startup
|
||||
}
|
||||
Save();
|
||||
}
|
||||
// If both data do not exist, we just need to create a new data
|
||||
else
|
||||
{
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
}
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
_topMostRecordStorage.Save();
|
||||
}
|
||||
|
||||
public bool IsTopMost(Result result)
|
||||
{
|
||||
return _topMostRecord.IsTopMost(result);
|
||||
}
|
||||
|
||||
public int GetTopMostIndex(Result result)
|
||||
{
|
||||
return _topMostRecord.GetTopMostIndex(result);
|
||||
}
|
||||
|
||||
public void Remove(Result result)
|
||||
{
|
||||
_topMostRecord.Remove(result);
|
||||
}
|
||||
|
||||
public void AddOrUpdate(Result result)
|
||||
{
|
||||
_topMostRecord.AddOrUpdate(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Old data structure to support only one top most record for the same query
|
||||
/// </summary>
|
||||
[Obsolete("Use MultipleTopMostRecord instead. This class will be removed in future versions.")]
|
||||
internal class TopMostRecord
|
||||
{
|
||||
[JsonInclude]
|
||||
public ConcurrentDictionary<string, Record> records { get; private set; } = new ConcurrentDictionary<string, Record>();
|
||||
public ConcurrentDictionary<string, Record> records { get; private set; } = new();
|
||||
|
||||
internal bool IsTopMost(Result result)
|
||||
{
|
||||
|
|
@ -39,12 +140,145 @@ namespace Flow.Launcher.Storage
|
|||
}
|
||||
}
|
||||
|
||||
public class Record
|
||||
/// <summary>
|
||||
/// New data structure to support multiple top most records for the same query
|
||||
/// </summary>
|
||||
internal class MultipleTopMostRecord
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string SubTitle { get; set; }
|
||||
public string PluginID { get; set; }
|
||||
public string RecordKey { get; set; }
|
||||
[JsonInclude]
|
||||
[JsonConverter(typeof(ConcurrentDictionaryConcurrentQueueConverter))]
|
||||
public ConcurrentDictionary<string, ConcurrentQueue<Record>> records { get; private set; } = new();
|
||||
|
||||
internal bool IsTopMost(Result result)
|
||||
{
|
||||
// origin query is null when user select the context menu item directly of one item from query list
|
||||
// in this case, we do not need to check if the result is top most
|
||||
if (records.IsEmpty || result.OriginQuery == null ||
|
||||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
return value.Any(record => record.Equals(result));
|
||||
}
|
||||
|
||||
internal int GetTopMostIndex(Result result)
|
||||
{
|
||||
// origin query is null when user select the context menu item directly of one item from query list
|
||||
// in this case, we do not need to check if the result is top most
|
||||
if (records.IsEmpty || result.OriginQuery == null ||
|
||||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// since this dictionary should be very small (or empty) going over it should be pretty fast.
|
||||
// since the latter items should be more recent, we should return the smaller index for score to subtract
|
||||
// which can make them more topmost
|
||||
// A, B, C => 2, 1, 0 => (max - 2), (max - 1), (max - 0)
|
||||
var index = 0;
|
||||
foreach (var record in value)
|
||||
{
|
||||
if (record.Equals(result))
|
||||
{
|
||||
return value.Count - 1 - index;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
internal void Remove(Result result)
|
||||
{
|
||||
// origin query is null when user select the context menu item directly of one item from query list
|
||||
// in this case, we do not need to remove the record
|
||||
if (result.OriginQuery == null ||
|
||||
!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// remove the record from the queue
|
||||
var queue = new ConcurrentQueue<Record>(value.Where(r => !r.Equals(result)));
|
||||
if (queue.IsEmpty)
|
||||
{
|
||||
// if the queue is empty, remove the queue from the dictionary
|
||||
records.TryRemove(result.OriginQuery.RawQuery, out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
// change the queue in the dictionary
|
||||
records[result.OriginQuery.RawQuery] = queue;
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddOrUpdate(Result result)
|
||||
{
|
||||
// origin query is null when user select the context menu item directly of one item from query list
|
||||
// in this case, we do not need to add or update the record
|
||||
if (result.OriginQuery == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var record = new Record
|
||||
{
|
||||
PluginID = result.PluginID,
|
||||
Title = result.Title,
|
||||
SubTitle = result.SubTitle,
|
||||
RecordKey = result.RecordKey
|
||||
};
|
||||
if (!records.TryGetValue(result.OriginQuery.RawQuery, out var value))
|
||||
{
|
||||
// create a new queue if it does not exist
|
||||
value = new ConcurrentQueue<Record>();
|
||||
value.Enqueue(record);
|
||||
records.TryAdd(result.OriginQuery.RawQuery, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// add or update the record in the queue
|
||||
var queue = new ConcurrentQueue<Record>(value.Where(r => !r.Equals(result))); // make sure we don't have duplicates
|
||||
queue.Enqueue(record);
|
||||
records[result.OriginQuery.RawQuery] = queue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Because ConcurrentQueue does not support serialization, we need to convert it to a List
|
||||
/// </summary>
|
||||
internal class ConcurrentDictionaryConcurrentQueueConverter : JsonConverter<ConcurrentDictionary<string, ConcurrentQueue<Record>>>
|
||||
{
|
||||
public override ConcurrentDictionary<string, ConcurrentQueue<Record>> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
var dictionary = JsonSerializer.Deserialize<Dictionary<string, List<Record>>>(ref reader, options);
|
||||
var concurrentDictionary = new ConcurrentDictionary<string, ConcurrentQueue<Record>>();
|
||||
foreach (var kvp in dictionary)
|
||||
{
|
||||
concurrentDictionary.TryAdd(kvp.Key, new ConcurrentQueue<Record>(kvp.Value));
|
||||
}
|
||||
return concurrentDictionary;
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, ConcurrentDictionary<string, ConcurrentQueue<Record>> value, JsonSerializerOptions options)
|
||||
{
|
||||
var dict = new Dictionary<string, List<Record>>();
|
||||
foreach (var kvp in value)
|
||||
{
|
||||
dict.Add(kvp.Key, kvp.Value.ToList());
|
||||
}
|
||||
JsonSerializer.Serialize(writer, dict, options);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Record
|
||||
{
|
||||
public string Title { get; init; }
|
||||
public string SubTitle { get; init; }
|
||||
public string PluginID { get; init; }
|
||||
public string RecordKey { get; init; }
|
||||
|
||||
public bool Equals(Result r)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,11 +39,10 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
|
||||
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
|
||||
private readonly FlowLauncherJsonStorage<TopMostRecord> _topMostRecordStorage;
|
||||
private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord;
|
||||
private readonly History _history;
|
||||
private int lastHistoryIndex = 1;
|
||||
private readonly UserSelectedRecord _userSelectedRecord;
|
||||
private readonly TopMostRecord _topMostRecord;
|
||||
|
||||
private CancellationTokenSource _updateSource; // Used to cancel old query flows
|
||||
private CancellationToken _updateToken; // Used to avoid ObjectDisposedException of _updateSource.Token
|
||||
|
|
@ -143,10 +142,9 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
_historyItemsStorage = new FlowLauncherJsonStorage<History>();
|
||||
_userSelectedRecordStorage = new FlowLauncherJsonStorage<UserSelectedRecord>();
|
||||
_topMostRecordStorage = new FlowLauncherJsonStorage<TopMostRecord>();
|
||||
_topMostRecord = new FlowLauncherJsonStorageTopMostRecord();
|
||||
_history = _historyItemsStorage.Load();
|
||||
_userSelectedRecord = _userSelectedRecordStorage.Load();
|
||||
_topMostRecord = _topMostRecordStorage.Load();
|
||||
|
||||
ContextMenu = new ResultsViewModel(Settings, this)
|
||||
{
|
||||
|
|
@ -1823,7 +1821,7 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
_historyItemsStorage.Save();
|
||||
_userSelectedRecordStorage.Save();
|
||||
_topMostRecordStorage.Save();
|
||||
_topMostRecord.Save();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -1856,9 +1854,12 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
foreach (var result in metaResults.Results)
|
||||
{
|
||||
if (_topMostRecord.IsTopMost(result))
|
||||
var deviationIndex = _topMostRecord.GetTopMostIndex(result);
|
||||
if (deviationIndex != -1)
|
||||
{
|
||||
result.Score = Result.MaxScore;
|
||||
// Adjust the score based on the result's position in the top-most list.
|
||||
// A lower deviationIndex (closer to the top) results in a higher score.
|
||||
result.Score = Result.MaxScore - deviationIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@
|
|||
|
||||
<system:String x:Key="flowlauncher_plugin_program_pls_select_program_source">Please select a program source</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_delete_program_source">Are you sure you want to delete the selected program sources?</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_not_user_added">Please select program sources that are not added by you</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_delete_program_source_select_user_added">Please select program sources that are added by you</system:String>
|
||||
<system:String x:Key="flowlauncher_plugin_program_duplicate_program_source">Another program source with the same location already exists.</system:String>
|
||||
|
||||
<system:String x:Key="flowlauncher_plugin_program_edit_program_source_title">Program Source</system:String>
|
||||
|
|
|
|||
|
|
@ -203,6 +203,12 @@
|
|||
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
|
||||
Click="btnEditProgramSource_OnClick"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_edit}" />
|
||||
<Button
|
||||
x:Name="btnDeleteProgramSource"
|
||||
MinWidth="100"
|
||||
Margin="{StaticResource SettingPanelItemLeftTopBottomMargin}"
|
||||
Click="btnDeleteProgramSource_OnClick"
|
||||
Content="{DynamicResource flowlauncher_plugin_program_delete}" />
|
||||
<Button
|
||||
x:Name="btnAddProgramSource"
|
||||
MinWidth="100"
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
{
|
||||
btnProgramSourceStatus.Visibility = Visibility.Hidden;
|
||||
btnEditProgramSource.Visibility = Visibility.Hidden;
|
||||
btnDeleteProgramSource.Visibility = Visibility.Hidden;
|
||||
}
|
||||
|
||||
if (programSourceView.Items.Count > 0
|
||||
|
|
@ -141,6 +142,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
{
|
||||
btnProgramSourceStatus.Visibility = Visibility.Visible;
|
||||
btnEditProgramSource.Visibility = Visibility.Visible;
|
||||
btnDeleteProgramSource.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
programSourceView.Items.Refresh();
|
||||
|
|
@ -270,8 +272,8 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
if (directoriesToAdd.Count > 0)
|
||||
{
|
||||
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
|
||||
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
|
||||
directoriesToAdd.ForEach(_settings.ProgramSources.Add);
|
||||
directoriesToAdd.ForEach(ProgramSettingDisplayList.Add);
|
||||
|
||||
ViewRefresh();
|
||||
ReIndexing();
|
||||
|
|
@ -296,24 +298,11 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
if (selectedItems.Count == 0)
|
||||
{
|
||||
string msg = context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source");
|
||||
context.API.ShowMsgBox(msg);
|
||||
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsAllItemsUserAdded(selectedItems))
|
||||
{
|
||||
var msg = string.Format(
|
||||
context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"));
|
||||
|
||||
if (context.API.ShowMsgBox(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteProgramSources(selectedItems);
|
||||
}
|
||||
else if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
{
|
||||
await ProgramSettingDisplay.SetProgramSourcesStatusAsync(selectedItems, false);
|
||||
|
||||
|
|
@ -341,10 +330,9 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void GridViewColumnHeaderClickedHandler(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var headerClicked = e.OriginalSource as GridViewColumnHeader;
|
||||
ListSortDirection direction;
|
||||
|
||||
if (headerClicked != null)
|
||||
if (e.OriginalSource is GridViewColumnHeader headerClicked)
|
||||
{
|
||||
if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
|
||||
{
|
||||
|
|
@ -380,7 +368,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
var dataView = CollectionViewSource.GetDefaultView(programSourceView.ItemsSource);
|
||||
|
||||
dataView.SortDescriptions.Clear();
|
||||
SortDescription sd = new SortDescription(sortBy, direction);
|
||||
var sd = new SortDescription(sortBy, direction);
|
||||
dataView.SortDescriptions.Add(sd);
|
||||
dataView.Refresh();
|
||||
}
|
||||
|
|
@ -397,11 +385,7 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
|
||||
if (IsAllItemsUserAdded(selectedItems))
|
||||
{
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_delete");
|
||||
}
|
||||
else if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
if (HasMoreOrEqualEnabledItems(selectedItems))
|
||||
{
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("flowlauncher_plugin_program_disable");
|
||||
}
|
||||
|
|
@ -420,6 +404,41 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD100:Avoid async void methods", Justification = "<Pending>")]
|
||||
private async void btnDeleteProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedItems = programSourceView
|
||||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
|
||||
if (selectedItems.Count == 0)
|
||||
{
|
||||
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_pls_select_program_source"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsAllItemsUserAdded(selectedItems))
|
||||
{
|
||||
context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source_select_user_added"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.API.ShowMsgBox(context.API.GetTranslation("flowlauncher_plugin_program_delete_program_source"),
|
||||
string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteProgramSources(selectedItems);
|
||||
|
||||
if (await selectedItems.IsReindexRequiredAsync())
|
||||
ReIndexing();
|
||||
|
||||
programSourceView.SelectedItems.Clear();
|
||||
|
||||
ViewRefresh();
|
||||
}
|
||||
|
||||
private bool IsAllItemsUserAdded(List<ProgramSource> items)
|
||||
{
|
||||
return items.All(x => _settings.ProgramSources.Any(y => y.UniqueIdentifier == x.UniqueIdentifier));
|
||||
|
|
@ -427,11 +446,14 @@ namespace Flow.Launcher.Plugin.Program.Views
|
|||
|
||||
private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ListView listView = sender as ListView;
|
||||
GridView gView = listView.View as GridView;
|
||||
var listView = sender as ListView;
|
||||
var gView = listView.View as GridView;
|
||||
|
||||
var workingWidth =
|
||||
listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
|
||||
|
||||
if (workingWidth <= 0) return;
|
||||
|
||||
var col1 = 0.25;
|
||||
var col2 = 0.15;
|
||||
var col3 = 0.60;
|
||||
|
|
|
|||
|
|
@ -21,16 +21,15 @@ namespace Flow.Launcher.Plugin.Sys
|
|||
ListView listView = sender as ListView;
|
||||
GridView gView = listView.View as GridView;
|
||||
|
||||
var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
|
||||
var workingWidth =
|
||||
listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
|
||||
|
||||
if (workingWidth <= 0) return;
|
||||
|
||||
var col1 = 0.2;
|
||||
var col2 = 0.6;
|
||||
var col3 = 0.2;
|
||||
|
||||
if (workingWidth <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
gView.Columns[0].Width = workingWidth * col1;
|
||||
gView.Columns[1].Width = workingWidth * col2;
|
||||
gView.Columns[2].Width = workingWidth * col3;
|
||||
|
|
|
|||
Loading…
Reference in a new issue