diff --git a/src/Files.App/Actions/Global/OpenHelpAction.cs b/src/Files.App/Actions/Global/OpenHelpAction.cs new file mode 100644 index 000000000000..a37977a42850 --- /dev/null +++ b/src/Files.App/Actions/Global/OpenHelpAction.cs @@ -0,0 +1,21 @@ +using Files.App.Commands; +using Files.App.Extensions; +using System; +using System.Threading.Tasks; +using Windows.System; + +namespace Files.App.Actions +{ + internal class OpenHelpAction : IAction + { + public string Label { get; } = "Help".GetLocalizedResource(); + + public HotKey HotKey { get; } = new(VirtualKey.F1); + + public async Task ExecuteAsync() + { + var url = new Uri(Constants.GitHub.DocumentationUrl); + await Launcher.LaunchUriAsync(url); + } + } +} diff --git a/src/Files.App/Actions/Global/ToggleFullScreenAction.cs b/src/Files.App/Actions/Global/ToggleFullScreenAction.cs new file mode 100644 index 000000000000..d1a615e14720 --- /dev/null +++ b/src/Files.App/Actions/Global/ToggleFullScreenAction.cs @@ -0,0 +1,37 @@ +using Files.App.Commands; +using Files.App.Extensions; +using Microsoft.UI.Windowing; +using System.Threading.Tasks; +using Windows.System; + +namespace Files.App.Actions +{ + internal class ToggleFullScreenAction : IToggleAction + { + public string Label { get; } = "FullScreen".GetLocalizedResource(); + + public HotKey HotKey { get; } = new(VirtualKey.F11); + + public bool IsOn + { + get + { + var window = App.GetAppWindow(App.Window); + return window.Presenter.Kind is AppWindowPresenterKind.FullScreen; + } + } + + public Task ExecuteAsync() + { + var window = App.GetAppWindow(App.Window); + + var newKind = window.Presenter.Kind is AppWindowPresenterKind.FullScreen + ? AppWindowPresenterKind.Overlapped + : AppWindowPresenterKind.FullScreen; + + window.SetPresenter(newKind); + + return Task.CompletedTask; + } + } +} diff --git a/src/Files.App/Actions/IAction.cs b/src/Files.App/Actions/IAction.cs index 2f2a3d3ee0fc..549e2b521d7a 100644 --- a/src/Files.App/Actions/IAction.cs +++ b/src/Files.App/Actions/IAction.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using Files.App.Commands; +using System.Threading.Tasks; namespace Files.App.Actions { @@ -6,6 +7,8 @@ public interface IAction { string Label { get; } + HotKey HotKey => HotKey.None; + bool IsExecutable => true; Task ExecuteAsync(); diff --git a/src/Files.App/Actions/Show/ShowFileExtensionsAction.cs b/src/Files.App/Actions/Show/ToggleShowFileExtensionsAction.cs similarity index 66% rename from src/Files.App/Actions/Show/ShowFileExtensionsAction.cs rename to src/Files.App/Actions/Show/ToggleShowFileExtensionsAction.cs index a5aa4c86fa71..a827df0344ae 100644 --- a/src/Files.App/Actions/Show/ShowFileExtensionsAction.cs +++ b/src/Files.App/Actions/Show/ToggleShowFileExtensionsAction.cs @@ -7,15 +7,15 @@ namespace Files.App.Actions { - internal class ShowFileExtensionsAction : ObservableObject, IToggleAction + internal class ToggleShowFileExtensionsAction : ObservableObject, IToggleAction { private readonly IFoldersSettingsService settings = Ioc.Default.GetRequiredService(); - public string Label => "ShowFileExtensions".GetLocalizedResource(); + public string Label { get; } = "ShowFileExtensions".GetLocalizedResource(); public bool IsOn => settings.ShowFileExtensions; - public ShowFileExtensionsAction() => settings.PropertyChanged += Settings_PropertyChanged; + public ToggleShowFileExtensionsAction() => settings.PropertyChanged += Settings_PropertyChanged; public Task ExecuteAsync() { @@ -23,7 +23,7 @@ public Task ExecuteAsync() return Task.CompletedTask; } - private void Settings_PropertyChanged(object? _, PropertyChangedEventArgs e) + private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is nameof(IFoldersSettingsService.ShowFileExtensions)) OnPropertyChanged(nameof(IsOn)); diff --git a/src/Files.App/Actions/Show/ShowHiddenItemsAction.cs b/src/Files.App/Actions/Show/ToggleShowHiddenItemsAction.cs similarity index 66% rename from src/Files.App/Actions/Show/ShowHiddenItemsAction.cs rename to src/Files.App/Actions/Show/ToggleShowHiddenItemsAction.cs index 3df1ec3dbb66..b2003c61fe6a 100644 --- a/src/Files.App/Actions/Show/ShowHiddenItemsAction.cs +++ b/src/Files.App/Actions/Show/ToggleShowHiddenItemsAction.cs @@ -7,15 +7,15 @@ namespace Files.App.Actions { - internal class ShowHiddenItemsAction : ObservableObject, IToggleAction + internal class ToggleShowHiddenItemsAction : ObservableObject, IToggleAction { private readonly IFoldersSettingsService settings = Ioc.Default.GetRequiredService(); - public string Label => "ShowHiddenItems".GetLocalizedResource(); + public string Label { get; } = "ShowHiddenItems".GetLocalizedResource(); public bool IsOn => settings.ShowHiddenItems; - public ShowHiddenItemsAction() => settings.PropertyChanged += Settings_PropertyChanged; + public ToggleShowHiddenItemsAction() => settings.PropertyChanged += Settings_PropertyChanged; public Task ExecuteAsync() { @@ -23,7 +23,7 @@ public Task ExecuteAsync() return Task.CompletedTask; } - private void Settings_PropertyChanged(object? _, PropertyChangedEventArgs e) + private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName is nameof(IFoldersSettingsService.ShowHiddenItems)) OnPropertyChanged(nameof(IsOn)); diff --git a/src/Files.App/BaseLayout.cs b/src/Files.App/BaseLayout.cs index 737e1fb74e24..571972a9c01e 100644 --- a/src/Files.App/BaseLayout.cs +++ b/src/Files.App/BaseLayout.cs @@ -588,7 +588,7 @@ public async void BaseContextFlyout_Opening(object? sender, object e) shellContextMenuItemCancellationToken = new CancellationTokenSource(); var shiftPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down); - var items = ContextFlyoutItemHelper.GetBaseContextCommandsWithoutShellItems(currentInstanceViewModel: InstanceViewModel!, itemViewModel: ParentShellPageInstance!.FilesystemViewModel, commandsViewModel: CommandsViewModel!, shiftPressed: shiftPressed); + var items = ContextFlyoutItemHelper.GetItemContextCommandsWithoutShellItems(currentInstanceViewModel: InstanceViewModel!, selectedItems: new List { ParentShellPageInstance!.FilesystemViewModel.CurrentFolder }, commandsViewModel: CommandsViewModel!, shiftPressed: shiftPressed, itemViewModel: ParentShellPageInstance!.FilesystemViewModel, selectedItemsPropertiesViewModel: null); BaseContextMenuFlyout.PrimaryCommands.Clear(); BaseContextMenuFlyout.SecondaryCommands.Clear(); @@ -605,7 +605,7 @@ public async void BaseContextFlyout_Opening(object? sender, object e) if (!InstanceViewModel!.IsPageTypeSearchResults && !InstanceViewModel.IsPageTypeZipFolder) { - var shellMenuItems = await ContextFlyoutItemHelper.GetBaseContextShellCommandsAsync(workingDir: ParentShellPageInstance.FilesystemViewModel.WorkingDirectory, shiftPressed: shiftPressed, showOpenMenu: false, shellContextMenuItemCancellationToken.Token); + var shellMenuItems = await ContextFlyoutItemHelper.GetItemContextShellCommandsAsync(workingDir: ParentShellPageInstance.FilesystemViewModel.WorkingDirectory, selectedItems: new List(), shiftPressed: shiftPressed, showOpenMenu: false, shellContextMenuItemCancellationToken.Token); if (shellMenuItems.Any()) AddShellItemsToMenu(shellMenuItems, BaseContextMenuFlyout, shiftPressed); else @@ -650,7 +650,7 @@ private async Task LoadMenuItemsAsync() shellContextMenuItemCancellationToken = new CancellationTokenSource(); SelectedItemsPropertiesViewModel.CheckAllFileExtensions(SelectedItems!.Select(selectedItem => selectedItem?.FileExtension).ToList()!); var shiftPressed = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(VirtualKey.Shift).HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down); - var items = ContextFlyoutItemHelper.GetItemContextCommandsWithoutShellItems(currentInstanceViewModel: InstanceViewModel!, selectedItems: SelectedItems!, selectedItemsPropertiesViewModel: SelectedItemsPropertiesViewModel, commandsViewModel: CommandsViewModel!, shiftPressed: shiftPressed); + var items = ContextFlyoutItemHelper.GetItemContextCommandsWithoutShellItems(currentInstanceViewModel: InstanceViewModel!, selectedItems: SelectedItems!, selectedItemsPropertiesViewModel: SelectedItemsPropertiesViewModel, commandsViewModel: CommandsViewModel!, shiftPressed: shiftPressed, itemViewModel: null); ItemContextMenuFlyout.PrimaryCommands.Clear(); ItemContextMenuFlyout.SecondaryCommands.Clear(); var (primaryElements, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(items); diff --git a/src/Files.App/Commands/CommandCodes.cs b/src/Files.App/Commands/CommandCodes.cs index 3fd91442bbc7..87e69e251af6 100644 --- a/src/Files.App/Commands/CommandCodes.cs +++ b/src/Files.App/Commands/CommandCodes.cs @@ -4,8 +4,12 @@ public enum CommandCodes { None, + // global + OpenHelp, + ToggleFullScreen, + // show - ShowHiddenItems, - ShowFileExtensions, + ToggleShowHiddenItems, + ToggleShowFileExtensions, } } diff --git a/src/Files.App/Commands/HotKey.cs b/src/Files.App/Commands/HotKey.cs new file mode 100644 index 000000000000..c52c06a820b3 --- /dev/null +++ b/src/Files.App/Commands/HotKey.cs @@ -0,0 +1,157 @@ +using System; +using System.Linq; +using System.Text; +using Windows.System; + +namespace Files.App.Commands +{ + public readonly struct HotKey : IEquatable + { + public static HotKey None { get; } = new(VirtualKey.None, VirtualKeyModifiers.None); + + public bool IsNone => Key is VirtualKey.None; + + public VirtualKey Key { get; } = VirtualKey.None; + public VirtualKeyModifiers Modifiers { get; } = VirtualKeyModifiers.None; + + public HotKey(VirtualKey key) : this(key, VirtualKeyModifiers.None) { } + public HotKey(VirtualKey key, VirtualKeyModifiers modifiers) + { + if (key is VirtualKey.None) + return; + + if (IsModifier(key)) + throw new ArgumentException("The key cannot be a modifier.", nameof(key)); + + Key = key; + Modifiers = modifiers; + + static bool IsModifier(VirtualKey key) + => key is VirtualKey.Menu or VirtualKey.LeftMenu or VirtualKey.RightMenu + or VirtualKey.Control or VirtualKey.LeftControl or VirtualKey.RightControl + or VirtualKey.Shift or VirtualKey.LeftShift or VirtualKey.RightShift + or VirtualKey.LeftWindows or VirtualKey.RightWindows; + } + + public void Deconstruct(out VirtualKey key, out VirtualKeyModifiers modifiers) + => (key, modifiers) = (Key, Modifiers); + + public static explicit operator HotKey(string hotKey) => Parse(hotKey); + public static implicit operator string(HotKey hotKey) => hotKey.ToString(); + + public static bool operator ==(HotKey a, HotKey b) => a.Equals(b); + public static bool operator !=(HotKey a, HotKey b) => !a.Equals(b); + + public static HotKey Parse(string hotKey) + { + var key = VirtualKey.None; + var modifiers = VirtualKeyModifiers.None; + + var parts = hotKey.Split('+').Select(item => item.Trim().ToLower()); + foreach (string part in parts) + { + var m = ToModifiers(part); + if (m is not VirtualKeyModifiers.None) + { + modifiers |= m; + continue; + } + + if (key is not VirtualKey.None) + { + var k = ToKey(part); + if (k is not VirtualKey.None) + { + key = k; + continue; + } + } + + throw new FormatException($"{hotKey} is not a valid hot key"); + } + + return new(key, modifiers); + + static VirtualKeyModifiers ToModifiers(string modifiers) => modifiers switch + { + "alt" or "menu " => VirtualKeyModifiers.Menu, + "ctrl" or "control" => VirtualKeyModifiers.Control, + "shift" => VirtualKeyModifiers.Shift, + _ => VirtualKeyModifiers.None, + }; + + static VirtualKey ToKey(string part) => part switch + { + "alt" or "menu" => VirtualKey.None, + "ctrl" or "control" => VirtualKey.None, + "shift" => VirtualKey.None, + "windows" => VirtualKey.None, + "0" => VirtualKey.Number0, + "1" => VirtualKey.Number1, + "2" => VirtualKey.Number2, + "3" => VirtualKey.Number3, + "4" => VirtualKey.Number4, + "5" => VirtualKey.Number5, + "6" => VirtualKey.Number6, + "7" => VirtualKey.Number7, + "8" => VirtualKey.Number8, + "9" => VirtualKey.Number9, + "Pad0" => VirtualKey.NumberPad0, + "Pad1" => VirtualKey.NumberPad1, + "Pad2" => VirtualKey.NumberPad2, + "Pad3" => VirtualKey.NumberPad3, + "Pad4" => VirtualKey.NumberPad4, + "Pad5" => VirtualKey.NumberPad5, + "Pad6" => VirtualKey.NumberPad6, + "Pad7" => VirtualKey.NumberPad7, + "Pad8" => VirtualKey.NumberPad8, + "Pad9" => VirtualKey.NumberPad9, + _ => Enum.TryParse(part, true, out VirtualKey key) ? key : VirtualKey.None, + }; + } + + public override string ToString() + { + StringBuilder builder = new(); + if (Modifiers.HasFlag(VirtualKeyModifiers.Menu)) + builder.Append("Alt+"); + if (Modifiers.HasFlag(VirtualKeyModifiers.Control)) + builder.Append("Ctrl+"); + if (Modifiers.HasFlag(VirtualKeyModifiers.Shift)) + builder.Append("Shift+"); + if (Modifiers.HasFlag(VirtualKeyModifiers.Windows)) + builder.Append("Win+"); + builder.Append(ToString(Key)); + return builder.ToString(); + + static string ToString(VirtualKey key) => key switch + { + VirtualKey.Number0 => "0", + VirtualKey.Number1 => "1", + VirtualKey.Number2 => "2", + VirtualKey.Number3 => "3", + VirtualKey.Number4 => "4", + VirtualKey.Number5 => "5", + VirtualKey.Number6 => "6", + VirtualKey.Number7 => "7", + VirtualKey.Number8 => "8", + VirtualKey.Number9 => "9", + VirtualKey.NumberPad0 => "Pad0", + VirtualKey.NumberPad1 => "Pad1", + VirtualKey.NumberPad2 => "Pad2", + VirtualKey.NumberPad3 => "Pad3", + VirtualKey.NumberPad4 => "Pad4", + VirtualKey.NumberPad5 => "Pad5", + VirtualKey.NumberPad6 => "Pad6", + VirtualKey.NumberPad7 => "Pad7", + VirtualKey.NumberPad8 => "Pad8", + VirtualKey.NumberPad9 => "Pad9", + _ => key.ToString(), + }; + } + + public override int GetHashCode() => (Key, Modifiers).GetHashCode(); + public override bool Equals(object? other) => other is HotKey hotKey && Equals(hotKey); + public bool Equals(HotKey other) => (other.Key, other.Modifiers).Equals((Key, Modifiers)); + } +} diff --git a/src/Files.App/Commands/IRichCommand.cs b/src/Files.App/Commands/IRichCommand.cs index 4cae03a9a767..3745000d2828 100644 --- a/src/Files.App/Commands/IRichCommand.cs +++ b/src/Files.App/Commands/IRichCommand.cs @@ -10,13 +10,17 @@ public interface IRichCommand : ICommand, INotifyPropertyChanging, INotifyProper CommandCodes Code { get; } string Label { get; } + string LabelWithHotKey { get; } + string AutomationName { get; } + + HotKey DefaultHotKey { get; } + HotKey CustomHotKey { get; set; } bool IsToggle { get; } bool IsOn { get; set; } bool IsExecutable { get; } Task ExecuteAsync(); - void ExecuteTapped(object sender, TappedRoutedEventArgs e); } } diff --git a/src/Files.App/Commands/Manager/CommandManager.cs b/src/Files.App/Commands/Manager/CommandManager.cs index 6dad5cd8f529..397dfa998cda 100644 --- a/src/Files.App/Commands/Manager/CommandManager.cs +++ b/src/Files.App/Commands/Manager/CommandManager.cs @@ -5,8 +5,10 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using System.Windows.Input; @@ -14,63 +16,80 @@ namespace Files.App.Commands { internal class CommandManager : ICommandManager { - private readonly IDictionary commands = new Dictionary - { - [CommandCodes.None] = new NoneCommand(), - }; + public event EventHandler? HotKeyChanged; - public IRichCommand this[CommandCodes code] => GetCommand(code); + private readonly IImmutableDictionary commands; + private readonly IDictionary customHotKeys; - public IRichCommand None => GetCommand(CommandCodes.None); - public IRichCommand ShowHiddenItems => GetCommand(CommandCodes.ShowHiddenItems); - public IRichCommand ShowFileExtensions => GetCommand(CommandCodes.ShowFileExtensions); + public IRichCommand this[CommandCodes code] => commands.TryGetValue(code, out var command) ? command : None; + public IRichCommand this[HotKey customHotKey] => customHotKeys.TryGetValue(customHotKey, out var command) ? command : None; - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public IEnumerator GetEnumerator() => commands.Values.GetEnumerator(); + public IRichCommand None => commands[CommandCodes.None]; + public IRichCommand OpenHelp => commands[CommandCodes.OpenHelp]; + public IRichCommand ToggleFullScreen => commands[CommandCodes.ToggleFullScreen]; + public IRichCommand ToggleShowHiddenItems => commands[CommandCodes.ToggleShowHiddenItems]; + public IRichCommand ToggleShowFileExtensions => commands[CommandCodes.ToggleShowFileExtensions]; - private IRichCommand GetCommand(CommandCodes code) + public CommandManager() { - if (commands.TryGetValue(code, out IRichCommand? command)) - return command; - - var action = GetAction(code); - var newCommand = new ActionCommand(code, action); - commands.Add(code, newCommand); - - return newCommand; + commands = CreateActions() + .Select(action => new ActionCommand(this, action.Key, action.Value)) + .Cast() + .Append(new NoneCommand()) + .ToImmutableDictionary(command => command.Code); + + customHotKeys = commands.Values + .Where(command => !command.CustomHotKey.IsNone) + .ToDictionary(command => command.CustomHotKey); } - private static IAction GetAction(CommandCodes code) => code switch + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public IEnumerator GetEnumerator() => commands.Values.GetEnumerator(); + + private static IDictionary CreateActions() => new Dictionary { - CommandCodes.ShowHiddenItems => new ShowHiddenItemsAction(), - CommandCodes.ShowFileExtensions => new ShowFileExtensionsAction(), - _ => throw new ArgumentOutOfRangeException(nameof(code)), + [CommandCodes.OpenHelp] = new OpenHelpAction(), + [CommandCodes.ToggleFullScreen] = new ToggleFullScreenAction(), + [CommandCodes.ToggleShowHiddenItems] = new ToggleShowHiddenItemsAction(), + [CommandCodes.ToggleShowFileExtensions] = new ToggleShowFileExtensionsAction(), }; [DebuggerDisplay("Command None")] private class NoneCommand : IRichCommand { public event EventHandler? CanExecuteChanged { add {} remove {} } - public event PropertyChangingEventHandler? PropertyChanging { add { } remove { } } - public event PropertyChangedEventHandler? PropertyChanged { add { } remove { } } + public event PropertyChangingEventHandler? PropertyChanging { add {} remove {} } + public event PropertyChangedEventHandler? PropertyChanged { add {} remove {} } public CommandCodes Code => CommandCodes.None; public string Label => string.Empty; + public string LabelWithHotKey => string.Empty; + public string AutomationName => string.Empty; + + public HotKey DefaultHotKey => HotKey.None; + + public HotKey CustomHotKey + { + get => HotKey.None; + set => throw new InvalidOperationException("The None command cannot have hotkey."); + } public bool IsToggle => false; public bool IsOn { get => false; set {} } public bool IsExecutable => false; - public bool CanExecute(object? _) => false; - public void Execute(object? _) {} + public bool CanExecute(object? parameter) => false; + public void Execute(object? parameter) {} public Task ExecuteAsync() => Task.CompletedTask; - public void ExecuteTapped(object _, TappedRoutedEventArgs e) {} + public void ExecuteTapped(object sender, TappedRoutedEventArgs e) {} } [DebuggerDisplay("Command {Code}")] private class ActionCommand : ObservableObject, IRichCommand { + private readonly CommandManager manager; + public event EventHandler? CanExecuteChanged; private readonly IAction action; @@ -79,6 +98,40 @@ private class ActionCommand : ObservableObject, IRichCommand public CommandCodes Code { get; } public string Label => action.Label; + public string LabelWithHotKey => $"{Label} ({CustomHotKey})"; + public string AutomationName => Label; + + public HotKey DefaultHotKey => action.HotKey; + + private HotKey customHotKey; + public HotKey CustomHotKey + { + get => customHotKey; + set + { + if (customHotKey == value) + return; + + if (manager.customHotKeys.ContainsKey(value)) + manager[value].CustomHotKey = HotKey.None; + + if (!customHotKey.IsNone) + manager.customHotKeys.Remove(customHotKey); + + if (!value.IsNone) + manager.customHotKeys.Add(value, this); + + var args = new HotKeyChangedEventArgs + { + Command = this, + OldHotKey = customHotKey, + NewHotKey= value, + }; + + SetProperty(ref customHotKey, value); + manager.HotKeyChanged?.Invoke(manager, args); + } + } public bool IsToggle => action is IToggleAction; @@ -94,11 +147,12 @@ public bool IsOn public bool IsExecutable => action.IsExecutable; - public ActionCommand(CommandCodes code, IAction action) + public ActionCommand(CommandManager manager, CommandCodes code, IAction action) { + this.manager = manager; Code = code; this.action = action; - + customHotKey = action.HotKey; command = new AsyncRelayCommand(ExecuteAsync, () => action.IsExecutable); if (action is INotifyPropertyChanging notifyPropertyChanging) @@ -117,14 +171,16 @@ public Task ExecuteAsync() return Task.CompletedTask; } - public async void ExecuteTapped(object _, TappedRoutedEventArgs e) => await action.ExecuteAsync(); + public async void ExecuteTapped(object sender, TappedRoutedEventArgs e) => await action.ExecuteAsync(); - private void Action_PropertyChanging(object? _, PropertyChangingEventArgs e) + private void Action_PropertyChanging(object? sender, PropertyChangingEventArgs e) { switch (e.PropertyName) { case nameof(IAction.Label): OnPropertyChanging(nameof(Label)); + OnPropertyChanging(nameof(LabelWithHotKey)); + OnPropertyChanging(nameof(AutomationName)); break; case nameof(IToggleAction.IsOn) when IsToggle: OnPropertyChanging(nameof(IsOn)); @@ -140,6 +196,8 @@ private void Action_PropertyChanged(object? sender, PropertyChangedEventArgs e) { case nameof(IAction.Label): OnPropertyChanged(nameof(Label)); + OnPropertyChanged(nameof(LabelWithHotKey)); + OnPropertyChanged(nameof(AutomationName)); break; case nameof(IToggleAction.IsOn) when IsToggle: OnPropertyChanged(nameof(IsOn)); diff --git a/src/Files.App/Commands/Manager/HotKeyChangedEventArgs.cs b/src/Files.App/Commands/Manager/HotKeyChangedEventArgs.cs new file mode 100644 index 000000000000..9839533a5699 --- /dev/null +++ b/src/Files.App/Commands/Manager/HotKeyChangedEventArgs.cs @@ -0,0 +1,12 @@ +using System; + +namespace Files.App.Commands +{ + public class HotKeyChangedEventArgs : EventArgs + { + public required IRichCommand Command { get; init; } + + public HotKey OldHotKey { get; init; } = HotKey.None; + public HotKey NewHotKey { get; init; } = HotKey.None; + } +} diff --git a/src/Files.App/Commands/Manager/ICommandManager.cs b/src/Files.App/Commands/Manager/ICommandManager.cs index b33c1bba7615..143bbd0c3d21 100644 --- a/src/Files.App/Commands/Manager/ICommandManager.cs +++ b/src/Files.App/Commands/Manager/ICommandManager.cs @@ -1,14 +1,21 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Files.App.Commands { public interface ICommandManager : IEnumerable { + event EventHandler? HotKeyChanged; + IRichCommand this[CommandCodes code] { get; } + IRichCommand this[HotKey customHotKey] { get; } IRichCommand None { get; } - IRichCommand ShowHiddenItems { get; } - IRichCommand ShowFileExtensions { get; } + IRichCommand OpenHelp { get; } + IRichCommand ToggleFullScreen { get; } + + IRichCommand ToggleShowHiddenItems { get; } + IRichCommand ToggleShowFileExtensions { get; } } } diff --git a/src/Files.App/DataModels/NavigationControlItems/DriveItem.cs b/src/Files.App/DataModels/NavigationControlItems/DriveItem.cs index e766789c413b..38959e869faf 100644 --- a/src/Files.App/DataModels/NavigationControlItems/DriveItem.cs +++ b/src/Files.App/DataModels/NavigationControlItems/DriveItem.cs @@ -169,9 +169,11 @@ public static async Task CreateFromPropertiesAsync(StorageFolder root IsLocationItem = true, ShowEjectDevice = item.IsRemovable, ShowShellItems = true, + ShowFormatDrive = !(item.Type == DriveType.Network || string.Equals(root.Path, "C:\\", StringComparison.OrdinalIgnoreCase)), ShowProperties = true }; item.Path = string.IsNullOrEmpty(root.Path) ? $"\\\\?\\{root.Name}\\" : root.Path; + App.Logger.Warn(item.Path); item.DeviceID = deviceId; item.Root = root; diff --git a/src/Files.App/DataModels/NavigationControlItems/INavigationControlItem.cs b/src/Files.App/DataModels/NavigationControlItems/INavigationControlItem.cs index 0e3473e1e0d8..edecbea303fd 100644 --- a/src/Files.App/DataModels/NavigationControlItems/INavigationControlItem.cs +++ b/src/Files.App/DataModels/NavigationControlItems/INavigationControlItem.cs @@ -54,6 +54,8 @@ public class ContextMenuOptions public bool ShowEjectDevice { get; set; } + public bool ShowFormatDrive { get; set; } + public bool ShowShellItems { get; set; } } } diff --git a/src/Files.App/Dialogs/FilesystemOperationDialog.xaml b/src/Files.App/Dialogs/FilesystemOperationDialog.xaml index d2a8a69a47e9..f67d3b0bc41b 100644 --- a/src/Files.App/Dialogs/FilesystemOperationDialog.xaml +++ b/src/Files.App/Dialogs/FilesystemOperationDialog.xaml @@ -28,35 +28,12 @@ 800 - - - - - - - - + @@ -128,13 +105,14 @@ + @@ -190,7 +168,7 @@ Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" - Text="Apply this action to all conflicting items" + Text="{helpers:ResourceString Name=ApplyToAllConflictingItems}" TextWrapping="Wrap" /> 1 && DetailsGrid.SelectedItems.Count == 1 && !DetailsGrid.SelectedItems.Any(x => (x as FileSystemDialogConflictItemViewModel).IsDefault)) - { - ApplyToAllOption.Visibility = Visibility.Visible; - ApplyToAllSeparator.Visibility = Visibility.Visible; - } - else - { - ApplyToAllOption.Visibility = Visibility.Collapsed; - ApplyToAllSeparator.Visibility = Visibility.Collapsed; - } - } - private void RootDialog_Closing(ContentDialog sender, ContentDialogClosingEventArgs args) { + if (args.Result == ContentDialogResult.Primary) + ViewModel.SaveConflictResolveOption(); + App.Window.SizeChanged -= Current_SizeChanged; ViewModel.CancelCts(); } @@ -181,6 +136,7 @@ private void FilesystemOperationDialog_Opened(ContentDialog sender, ContentDialo DescriptionText.Foreground = App.Current.Resources["TextControlForeground"] as SolidColorBrush; } + ViewModel.LoadConflictResolveOption(); UpdateDialogLayout(); } } diff --git a/src/Files.App/Filesystem/ListedItem.cs b/src/Files.App/Filesystem/ListedItem.cs index f2a1be329de5..5b5428f09b38 100644 --- a/src/Files.App/Filesystem/ListedItem.cs +++ b/src/Files.App/Filesystem/ListedItem.cs @@ -420,6 +420,7 @@ public override string ToString() public bool IsAlternateStream => this is AlternateStreamItem; public virtual bool IsExecutable => FileExtensionHelpers.IsExecutableFile(ItemPath); public bool IsPinned => App.QuickAccessManager.Model.FavoriteItems.Contains(itemPath); + public bool IsDriveRoot => ItemPath == PathNormalization.GetPathRoot(ItemPath); private BaseStorageFile itemFile; public BaseStorageFile ItemFile diff --git a/src/Files.App/Helpers/ContextFlyoutItemHelper.cs b/src/Files.App/Helpers/ContextFlyoutItemHelper.cs index ccd76d858801..5d0d9277078f 100644 --- a/src/Files.App/Helpers/ContextFlyoutItemHelper.cs +++ b/src/Files.App/Helpers/ContextFlyoutItemHelper.cs @@ -8,6 +8,7 @@ using Files.Backend.Services; using Files.Backend.Services.Settings; using Files.Shared.Enums; +using Microsoft.UI.Text; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Imaging; using System; @@ -26,9 +27,9 @@ public static class ContextFlyoutItemHelper { private static readonly IAddItemService addItemService = Ioc.Default.GetRequiredService(); - public static List GetItemContextCommandsWithoutShellItems(CurrentInstanceViewModel currentInstanceViewModel, List selectedItems, BaseLayoutCommandsViewModel commandsViewModel, bool shiftPressed, SelectedItemsPropertiesViewModel selectedItemsPropertiesViewModel) + public static List GetItemContextCommandsWithoutShellItems(CurrentInstanceViewModel currentInstanceViewModel, List selectedItems, BaseLayoutCommandsViewModel commandsViewModel, bool shiftPressed, SelectedItemsPropertiesViewModel? selectedItemsPropertiesViewModel, ItemViewModel? itemViewModel = null) { - var menuItemsList = GetBaseItemMenuItems(commandsViewModel: commandsViewModel, selectedItems: selectedItems, selectedItemsPropertiesViewModel: selectedItemsPropertiesViewModel, currentInstanceViewModel: currentInstanceViewModel); + var menuItemsList = GetBaseItemMenuItems(commandsViewModel: commandsViewModel, selectedItems: selectedItems, selectedItemsPropertiesViewModel: selectedItemsPropertiesViewModel, currentInstanceViewModel: currentInstanceViewModel, itemViewModel: itemViewModel); menuItemsList = Filter(items: menuItemsList, shiftPressed: shiftPressed, currentInstanceViewModel: currentInstanceViewModel, selectedItems: selectedItems, removeOverflowMenu: false); return menuItemsList; } @@ -36,16 +37,6 @@ public static List GetItemContextCommandsWithout public static Task> GetItemContextShellCommandsAsync(string workingDir, List selectedItems, bool shiftPressed, bool showOpenMenu, CancellationToken cancellationToken) => ShellContextmenuHelper.GetShellContextmenuAsync(shiftPressed: shiftPressed, showOpenMenu: showOpenMenu, workingDirectory: workingDir, selectedItems: selectedItems, cancellationToken: cancellationToken); - public static List GetBaseContextCommandsWithoutShellItems(CurrentInstanceViewModel currentInstanceViewModel, ItemViewModel itemViewModel, BaseLayoutCommandsViewModel commandsViewModel, bool shiftPressed) - { - var menuItemsList = GetBaseLayoutMenuItems(currentInstanceViewModel, itemViewModel, commandsViewModel); - menuItemsList = Filter(items: menuItemsList, shiftPressed: shiftPressed, currentInstanceViewModel: currentInstanceViewModel, selectedItems: new List(), removeOverflowMenu: false); - return menuItemsList; - } - - public static Task> GetBaseContextShellCommandsAsync(string workingDir, bool shiftPressed, bool showOpenMenu, CancellationToken cancellationToken) - => ShellContextmenuHelper.GetShellContextmenuAsync(shiftPressed: shiftPressed, showOpenMenu: showOpenMenu, workingDirectory: workingDir, selectedItems: new List(), cancellationToken: cancellationToken); - public static List Filter(List items, List selectedItems, bool shiftPressed, CurrentInstanceViewModel currentInstanceViewModel, bool removeOverflowMenu = true) { items = items.Where(x => Check(item: x, currentInstanceViewModel: currentInstanceViewModel, selectedItems: selectedItems)).ToList(); @@ -86,10 +77,27 @@ private static bool Check(ContextMenuFlyoutItemViewModel item, CurrentInstanceVi && item.ShowItem; } - public static List GetBaseLayoutMenuItems(CurrentInstanceViewModel currentInstanceViewModel, ItemViewModel itemViewModel, BaseLayoutCommandsViewModel commandsViewModel) + public static List GetBaseItemMenuItems( + BaseLayoutCommandsViewModel commandsViewModel, + SelectedItemsPropertiesViewModel? selectedItemsPropertiesViewModel, + List selectedItems, + CurrentInstanceViewModel currentInstanceViewModel, + ItemViewModel itemViewModel = null) { IUserSettingsService userSettingsService = Ioc.Default.GetRequiredService(); + bool itemsSelected = itemViewModel is null; + bool canDecompress = selectedItems.Any() && selectedItems.All(x => x.IsArchive) + || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension)); + bool canCompress = !canDecompress || selectedItems.Count > 1; + bool showOpenItemWith = selectedItems.All( + i => (i.PrimaryItemAttribute == StorageItemTypes.File && !i.IsShortcut && !i.IsExecutable) || (i.PrimaryItemAttribute == StorageItemTypes.Folder && i.IsArchive)); + bool areAllItemsFolders = selectedItems.All(i => i.PrimaryItemAttribute == StorageItemTypes.Folder); + bool isFirstFileExecutable = FileExtensionHelpers.IsExecutableFile(selectedItems.FirstOrDefault()?.FileExtension); + string newArchiveName = + Path.GetFileName(selectedItems.Count is 1 ? selectedItems[0].ItemPath : Path.GetDirectoryName(selectedItems[0].ItemPath)) + ?? string.Empty; + return new List() { new ContextMenuFlyoutItemViewModel() @@ -200,7 +208,8 @@ public static List GetBaseLayoutMenuItems(Curren KeyboardAcceleratorTextOverride = "BaseLayoutContextFlyoutAdaptive/KeyboardAcceleratorTextOverride".GetLocalizedResource(), KeyboardAccelerator = new KeyboardAccelerator{Key = VirtualKey.Number7, Modifiers = VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift, IsEnabled = false} }, - } + }, + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -219,19 +228,19 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "Name".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByName, + IsChecked = itemViewModel?.IsSortedByName ?? false, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, - Command = new RelayCommand(() => itemViewModel.IsSortedByName = true), + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByName = true) : null!, ItemType = ItemType.Toggle, }, new ContextMenuFlyoutItemViewModel() { Text = "DateModifiedLowerCase".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByDate, - Command = new RelayCommand(() => itemViewModel.IsSortedByDate = true), + IsChecked = itemViewModel?.IsSortedByDate ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByDate = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -241,8 +250,8 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "DateCreated".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByDateCreated, - Command = new RelayCommand(() => itemViewModel.IsSortedByDateCreated = true), + IsChecked = itemViewModel?.IsSortedByDateCreated ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByDateCreated = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -252,8 +261,8 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutSortByType/Text".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByType, - Command = new RelayCommand(() => itemViewModel.IsSortedByType = true), + IsChecked = itemViewModel?.IsSortedByType ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByType = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -263,8 +272,8 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "Size".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedBySize, - Command = new RelayCommand(() => itemViewModel.IsSortedBySize = true), + IsChecked = itemViewModel?.IsSortedBySize ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedBySize = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -274,16 +283,16 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "SyncStatus".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedBySyncStatus, - Command = new RelayCommand(() => itemViewModel.IsSortedBySyncStatus = true), + IsChecked = itemViewModel?.IsSortedBySyncStatus ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedBySyncStatus = true) : null!, ShowItem = currentInstanceViewModel.IsPageTypeCloudDrive, ItemType = ItemType.Toggle }, new ContextMenuFlyoutItemViewModel() { Text = "FileTags".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByFileTag, - Command = new RelayCommand(() => itemViewModel.IsSortedByFileTag = true), + IsChecked = itemViewModel?.IsSortedByFileTag ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByFileTag = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ItemType = ItemType.Toggle @@ -291,17 +300,17 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutSortByOriginalPath/Text".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByOriginalPath, + IsChecked = itemViewModel?.IsSortedByOriginalPath ?? false, ShowInRecycleBin = true, - Command = new RelayCommand(() => itemViewModel.IsSortedByOriginalPath = true), + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByOriginalPath = true) : null!, ShowItem = currentInstanceViewModel.IsPageTypeRecycleBin, ItemType = ItemType.Toggle, }, new ContextMenuFlyoutItemViewModel() { Text = "DateDeleted".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedByDateDeleted, - Command = new RelayCommand(() => itemViewModel.IsSortedByDateDeleted = true), + IsChecked = itemViewModel?.IsSortedByDateDeleted ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedByDateDeleted = true) : null!, ShowInRecycleBin = true, ShowItem = currentInstanceViewModel.IsPageTypeRecycleBin, ItemType = ItemType.Toggle @@ -317,8 +326,8 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "Ascending".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedAscending, - Command = new RelayCommand(() => itemViewModel.IsSortedAscending = true), + IsChecked = itemViewModel?.IsSortedAscending ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedAscending = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -328,15 +337,16 @@ public static List GetBaseLayoutMenuItems(Curren new ContextMenuFlyoutItemViewModel() { Text = "Descending".GetLocalizedResource(), - IsChecked = itemViewModel.IsSortedDescending, - Command = new RelayCommand(() => itemViewModel.IsSortedDescending = true), + IsChecked = itemViewModel?.IsSortedDescending ?? false, + Command = itemViewModel is not null ? new RelayCommand(() => itemViewModel.IsSortedDescending = true) : null!, ShowInRecycleBin = true, ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, ItemType = ItemType.Toggle }, - } + }, + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -502,7 +512,8 @@ public static List GetBaseLayoutMenuItems(Curren CommandParameter = SortDirection.Descending, ItemType = ItemType.Toggle, }, - } + }, + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -517,34 +528,15 @@ public static List GetBaseLayoutMenuItems(Curren { Key = VirtualKey.F5, IsEnabled = false, - } - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "BaseLayoutContextFlyoutPaste/Text".GetLocalizedResource(), - IsPrimary = true, - ShowInFtpPage = true, - ShowInZipPage = true, - ColoredIcon = new ColoredIconModel() - { - BaseBackdropGlyph = "\uF052", - BaseLayerGlyph = "\uF023", - OverlayLayerGlyph = "\uF024", }, - Command = commandsViewModel.PasteItemsFromClipboardCommand, - IsEnabled = currentInstanceViewModel.CanPasteInPage && App.AppModel.IsPasteEnabled, - KeyboardAccelerator = new KeyboardAccelerator - { - Key = VirtualKey.V, - Modifiers = VirtualKeyModifiers.Control, - IsEnabled = false, - } + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { ItemType = ItemType.Separator, ShowInFtpPage = true, ShowInZipPage = true, + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -564,57 +556,14 @@ public static List GetBaseLayoutMenuItems(Curren Items = GetNewItemItems(commandsViewModel, currentInstanceViewModel.CanCreateFileInPage), ShowInFtpPage = true, ShowInZipPage = true, + ShowItem = !itemsSelected }, new ContextMenuFlyoutItemViewModel() { - Text = "BaseLayoutItemContextFlyoutPinToFavorites/Text".GetLocalizedResource(), - Glyph = "\uE840", - Command = commandsViewModel.PinDirectoryToFavoritesCommand, - ShowItem = itemViewModel.CurrentFolder is not null && !itemViewModel.CurrentFolder.IsPinned & userSettingsService.PreferencesSettingsService.ShowFavoritesSection, - ShowInFtpPage = true, - ShowInRecycleBin = true, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "BaseLayoutContextFlyoutUnpinFromFavorites/Text".GetLocalizedResource(), - Glyph = "\uE77A", - Command = commandsViewModel.UnpinDirectoryFromFavoritesCommand, - ShowItem = itemViewModel.CurrentFolder is not null && itemViewModel.CurrentFolder.IsPinned & userSettingsService.PreferencesSettingsService.ShowFavoritesSection, - ShowInFtpPage = true, - ShowInRecycleBin = true, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "PinItemToStart/Text".GetLocalizedResource(), - Glyph = "\uE840", - Command = commandsViewModel.PinItemToStartCommand, - ShowInRecycleBin = true, - ShowInFtpPage = true, - ShowOnShift = true, - ShowItem = itemViewModel.CurrentFolder is not null && !itemViewModel.CurrentFolder.IsItemPinnedToStart, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "UnpinItemFromStart/Text".GetLocalizedResource(), - Glyph = "\uE77A", - Command = commandsViewModel.UnpinItemFromStartCommand, - ShowInRecycleBin = true, - ShowInFtpPage = true, - ShowOnShift = true, - ShowItem = itemViewModel.CurrentFolder is not null && itemViewModel.CurrentFolder.IsItemPinnedToStart, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "BaseLayoutContextFlyoutPropertiesFolder/Text".GetLocalizedResource(), - IsPrimary = true, - ColoredIcon = new ColoredIconModel() - { - BaseLayerGlyph = "\uF031", - OverlayLayerGlyph = "\uF032" - }, - Command = commandsViewModel.ShowFolderPropertiesCommand, - ShowInFtpPage = true, - ShowInZipPage = true, + Text = "FormatDriveText".GetLocalizedResource(), + Command = commandsViewModel.FormatDriveCommand, + CommandParameter = itemViewModel?.CurrentFolder, + ShowItem = itemViewModel.CurrentFolder is not null && (App.DrivesManager.Drives.Where(x => string.Equals(x.Path, itemViewModel?.CurrentFolder.ItemPath)).FirstOrDefault()?.MenuOptions.ShowFormatDrive ?? false), }, new ContextMenuFlyoutItemViewModel() { @@ -622,7 +571,7 @@ public static List GetBaseLayoutMenuItems(Curren Glyph = "\uEF88", GlyphFontFamilyName = "RecycleBinIcons", Command = commandsViewModel.EmptyRecycleBinCommand, - ShowItem = currentInstanceViewModel.IsPageTypeRecycleBin, + ShowItem = !itemsSelected && currentInstanceViewModel.IsPageTypeRecycleBin, ShowInRecycleBin = true, }, new ContextMenuFlyoutItemViewModel() @@ -630,51 +579,16 @@ public static List GetBaseLayoutMenuItems(Curren Text = "RestoreAllItems".GetLocalizedResource(), Glyph = "\xE777", Command = commandsViewModel.RestoreRecycleBinCommand, - ShowItem = currentInstanceViewModel.IsPageTypeRecycleBin, + ShowItem = !itemsSelected && currentInstanceViewModel.IsPageTypeRecycleBin, ShowInRecycleBin = true, }, - new ContextMenuFlyoutItemViewModel() - { - ItemType = ItemType.Separator, - Tag = "OverflowSeparator", - IsHidden = true, - }, - new ContextMenuFlyoutItemViewModel() - { - Text = "Loading".GetLocalizedResource(), - Glyph = "\xE712", - Items = new List(), - ID = "ItemOverflow", - Tag = "ItemOverflow", - IsEnabled = false, - }, - }; - } - - public static List GetBaseItemMenuItems(BaseLayoutCommandsViewModel commandsViewModel, List selectedItems, SelectedItemsPropertiesViewModel selectedItemsPropertiesViewModel, CurrentInstanceViewModel currentInstanceViewModel) - { - IUserSettingsService userSettingsService = Ioc.Default.GetRequiredService(); - - bool canDecompress = selectedItems.Any() && selectedItems.All(x => x.IsArchive) - || selectedItems.All(x => x.PrimaryItemAttribute == StorageItemTypes.File && FileExtensionHelpers.IsZipFile(x.FileExtension)); - bool canCompress = !canDecompress || selectedItems.Count > 1; - bool showOpenItemWith = selectedItems.All( - i => (i.PrimaryItemAttribute == StorageItemTypes.File && !i.IsShortcut && !i.IsExecutable) || (i.PrimaryItemAttribute == StorageItemTypes.Folder && i.IsArchive)); - bool areAllItemsFolders = selectedItems.All(i => i.PrimaryItemAttribute == StorageItemTypes.Folder); - bool isFirstFileExecutable = FileExtensionHelpers.IsExecutableFile(selectedItems.FirstOrDefault()?.FileExtension); - string newArchiveName = - Path.GetFileName(selectedItems.Count is 1 ? selectedItems[0].ItemPath : Path.GetDirectoryName(selectedItems[0].ItemPath)) - ?? string.Empty; - - return new List() - { new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutItemContextFlyoutRestore/Text".GetLocalizedResource(), Glyph = "\xE777", Command = commandsViewModel.RestoreItemCommand, ShowInRecycleBin = true, - ShowItem = selectedItems.All(x => x.IsRecycleBinItem) + ShowItem = itemsSelected && selectedItems.All(x => x.IsRecycleBinItem) }, new ContextMenuFlyoutItemViewModel() { @@ -688,7 +602,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, - ShowItem = selectedItems.Count <= 10, + ShowItem = itemsSelected && selectedItems.Count <= 10, }, new ContextMenuFlyoutItemViewModel() { @@ -702,7 +616,7 @@ public static List GetBaseItemMenuItems(BaseLayo Tag = "OpenWith", CollapseLabel = true, ShowInSearchPage = true, - ShowItem = showOpenItemWith + ShowItem = itemsSelected && showOpenItemWith }, new ContextMenuFlyoutItemViewModel() { @@ -723,14 +637,14 @@ public static List GetBaseItemMenuItems(BaseLayo } }, ShowInSearchPage = true, - ShowItem = showOpenItemWith + ShowItem = itemsSelected && showOpenItemWith }, new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutItemContextFlyoutOpenFileLocation/Text".GetLocalizedResource(), Glyph = "\uE8DA", Command = commandsViewModel.OpenFileLocationCommand, - ShowItem = selectedItems.All(i => i.IsShortcut), + ShowItem = itemsSelected && selectedItems.All(i => i.IsShortcut), ShowInSearchPage = true, }, new ContextMenuFlyoutItemViewModel() @@ -739,7 +653,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uF113", GlyphFontFamilyName = "CustomGlyph", Command = commandsViewModel.OpenDirectoryInNewTabCommand, - ShowItem = selectedItems.Count < 5 && areAllItemsFolders && userSettingsService.PreferencesSettingsService.ShowOpenInNewTab, + ShowItem = itemsSelected && selectedItems.Count < 5 && areAllItemsFolders && userSettingsService.PreferencesSettingsService.ShowOpenInNewTab, ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, @@ -749,7 +663,7 @@ public static List GetBaseItemMenuItems(BaseLayo Text = "BaseLayoutItemContextFlyoutOpenInNewWindow/Text".GetLocalizedResource(), Glyph = "\uE737", Command = commandsViewModel.OpenInNewWindowItemCommand, - ShowItem = selectedItems.Count < 5 && areAllItemsFolders && userSettingsService.PreferencesSettingsService.ShowOpenInNewWindow, + ShowItem = itemsSelected && selectedItems.Count < 5 && areAllItemsFolders && userSettingsService.PreferencesSettingsService.ShowOpenInNewWindow, ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, @@ -764,7 +678,7 @@ public static List GetBaseItemMenuItems(BaseLayo OverlayLayerGlyph = "\uF03C", }, Command = commandsViewModel.OpenDirectoryInNewPaneCommand, - ShowItem = userSettingsService.PreferencesSettingsService.ShowOpenInNewPane && areAllItemsFolders, + ShowItem = itemsSelected && userSettingsService.PreferencesSettingsService.ShowOpenInNewPane && areAllItemsFolders, SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -773,7 +687,7 @@ public static List GetBaseItemMenuItems(BaseLayo new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutItemContextFlyoutSetAs/Text".GetLocalizedResource(), - ShowItem = selectedItemsPropertiesViewModel.IsSelectedItemImage, + ShowItem = itemsSelected && (selectedItemsPropertiesViewModel?.IsSelectedItemImage ?? false), ShowInSearchPage = true, Items = new List() { @@ -783,7 +697,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE91B", Command = commandsViewModel.SetAsDesktopBackgroundItemCommand, ShowInSearchPage = true, - ShowItem = selectedItemsPropertiesViewModel.SelectedItemsCount == 1 + ShowItem = selectedItemsPropertiesViewModel?.SelectedItemsCount == 1 }, new ContextMenuFlyoutItemViewModel() { @@ -792,7 +706,7 @@ public static List GetBaseItemMenuItems(BaseLayo GlyphFontFamilyName = "CustomGlyph", Command = commandsViewModel.SetAsLockscreenBackgroundItemCommand, ShowInSearchPage = true, - ShowItem = selectedItemsPropertiesViewModel.SelectedItemsCount == 1 + ShowItem = selectedItemsPropertiesViewModel?.SelectedItemsCount == 1 }, new ContextMenuFlyoutItemViewModel() { @@ -801,7 +715,7 @@ public static List GetBaseItemMenuItems(BaseLayo GlyphFontFamilyName = "CustomGlyph", Command = commandsViewModel.SetAsDesktopBackgroundItemCommand, ShowInSearchPage = true, - ShowItem = selectedItemsPropertiesViewModel.SelectedItemsCount > 1 + ShowItem = selectedItemsPropertiesViewModel?.SelectedItemsCount > 1 }, } }, @@ -815,7 +729,7 @@ public static List GetBaseItemMenuItems(BaseLayo }, Command = commandsViewModel.RotateImageRightCommand, ShowInSearchPage = true, - ShowItem = selectedItemsPropertiesViewModel.IsSelectedItemImage + ShowItem = selectedItemsPropertiesViewModel?.IsSelectedItemImage ?? false }, new ContextMenuFlyoutItemViewModel { @@ -827,7 +741,7 @@ public static List GetBaseItemMenuItems(BaseLayo }, Command = commandsViewModel.RotateImageLeftCommand, ShowInSearchPage = true, - ShowItem = selectedItemsPropertiesViewModel.IsSelectedItemImage + ShowItem = selectedItemsPropertiesViewModel?.IsSelectedItemImage ?? false }, new ContextMenuFlyoutItemViewModel() { @@ -835,7 +749,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE7EF", Command = commandsViewModel.RunAsAdminCommand, ShowInSearchPage = true, - ShowItem = isFirstFileExecutable + ShowItem = itemsSelected && isFirstFileExecutable }, new ContextMenuFlyoutItemViewModel() { @@ -843,7 +757,7 @@ public static List GetBaseItemMenuItems(BaseLayo Glyph = "\uE7EE", Command = commandsViewModel.RunAsAnotherUserCommand, ShowInSearchPage = true, - ShowItem = isFirstFileExecutable + ShowItem = itemsSelected && isFirstFileExecutable }, new ContextMenuFlyoutItemViewModel() { @@ -852,6 +766,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -872,6 +787,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -894,6 +810,7 @@ public static List GetBaseItemMenuItems(BaseLayo Modifiers = VirtualKeyModifiers.Control, IsEnabled = false, }, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -908,6 +825,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, ShowInFtpPage = true, ShowInZipPage = true, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -921,7 +839,7 @@ public static List GetBaseItemMenuItems(BaseLayo OverlayLayerGlyph = "\uF024", }, Command = commandsViewModel.PasteItemsFromClipboardCommand, - ShowItem = areAllItemsFolders, + ShowItem = areAllItemsFolders || !itemsSelected, SingleItemOnly = true, ShowInSearchPage = true, ShowInFtpPage = true, @@ -943,6 +861,7 @@ public static List GetBaseItemMenuItems(BaseLayo OverlayLayerGlyph = "\uF034" }, Command = commandsViewModel.CreateFolderWithSelection, + ShowItem = itemsSelected, }, new ContextMenuFlyoutItemViewModel() { @@ -953,7 +872,7 @@ public static List GetBaseItemMenuItems(BaseLayo OverlayLayerGlyph = "\uF04C" }, Command = commandsViewModel.CreateShortcutCommand, - ShowItem = !selectedItems.FirstOrDefault()?.IsShortcut ?? false, + ShowItem = itemsSelected && (!selectedItems.FirstOrDefault()?.IsShortcut ?? false), SingleItemOnly = true, ShowInSearchPage = true, }, @@ -977,6 +896,7 @@ public static List GetBaseItemMenuItems(BaseLayo Key = VirtualKey.F2, IsEnabled = false, }, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -988,7 +908,7 @@ public static List GetBaseItemMenuItems(BaseLayo OverlayLayerGlyph = "\uF026", }, Command = commandsViewModel.ShareItemCommand, - ShowItem = DataTransferManager.IsSupported() && !selectedItems.Any(i => i.IsHiddenItem || (i.IsShortcut && !i.IsLinkItem) || (i.PrimaryItemAttribute == StorageItemTypes.Folder && !i.IsArchive)), + ShowItem = itemsSelected && DataTransferManager.IsSupported() && !selectedItems.Any(i => i.IsHiddenItem || (i.IsShortcut && !i.IsLinkItem) || (i.PrimaryItemAttribute == StorageItemTypes.Folder && !i.IsArchive)), }, new ContextMenuFlyoutItemViewModel() { @@ -1010,6 +930,7 @@ public static List GetBaseItemMenuItems(BaseLayo Key = VirtualKey.Delete, IsEnabled = false, }, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -1031,7 +952,7 @@ public static List GetBaseItemMenuItems(BaseLayo Text = "BaseLayoutItemContextFlyoutOpenParentFolder/Text".GetLocalizedResource(), Glyph = "\uE197", Command = commandsViewModel.OpenParentFolderCommand, - ShowItem = currentInstanceViewModel.IsPageTypeSearchResults, + ShowItem = itemsSelected && currentInstanceViewModel.IsPageTypeSearchResults, SingleItemOnly = true, ShowInSearchPage = true, }, @@ -1141,6 +1062,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, }, }, + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -1148,7 +1070,7 @@ public static List GetBaseItemMenuItems(BaseLayo Tag = "SendTo", CollapseLabel = true, ShowInSearchPage = true, - ShowItem = true + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -1164,7 +1086,7 @@ public static List GetBaseItemMenuItems(BaseLayo } }, ShowInSearchPage = true, - ShowItem = true + ShowItem = itemsSelected }, new ContextMenuFlyoutItemViewModel() { @@ -1182,7 +1104,7 @@ public static List GetBaseItemMenuItems(BaseLayo ShowInSearchPage = true, IsEnabled = false }, - }; + }.Where(x => x.ShowItem).ToList(); } public static List GetNewItemItems(BaseLayoutCommandsViewModel commandsViewModel, bool canCreateFileInPage) diff --git a/src/Files.App/Helpers/ShellContextMenuHelper.cs b/src/Files.App/Helpers/ShellContextMenuHelper.cs index 510ec65ecf52..24e6977dc661 100644 --- a/src/Files.App/Helpers/ShellContextMenuHelper.cs +++ b/src/Files.App/Helpers/ShellContextMenuHelper.cs @@ -49,7 +49,7 @@ Func FilterMenuItems(bool showOpenMenu) "cut", "copy", "paste", "delete", "properties", "link", "Windows.ModernShare", "Windows.Share", "setdesktopwallpaper", "eject", "rename", "explore", "openinfiles", "extract", - "copyaspath", "undelete", "empty", "rotate90", "rotate270", + "copyaspath", "undelete", "empty", "format", "rotate90", "rotate270", Win32API.ExtractStringFromDLL("shell32.dll", 34593), // Add to collection Win32API.ExtractStringFromDLL("shell32.dll", 5384), // Pin to Start Win32API.ExtractStringFromDLL("shell32.dll", 5385), // Unpin from Start diff --git a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs index 1a6e2bff2907..4769e5f0850a 100644 --- a/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs +++ b/src/Files.App/Interacts/BaseLayoutCommandImplementationModel.cs @@ -895,6 +895,11 @@ public async Task PlayAll() await NavigationHelpers.OpenSelectedItems(associatedInstance); } + public void FormatDrive(ListedItem? e) + { + Win32API.OpenFormatDriveDialog(e?.ItemPath ?? string.Empty); + } + #endregion Command Implementation } } diff --git a/src/Files.App/Interacts/BaseLayoutCommandsViewModel.cs b/src/Files.App/Interacts/BaseLayoutCommandsViewModel.cs index c4ea24428140..c24205ff2f7c 100644 --- a/src/Files.App/Interacts/BaseLayoutCommandsViewModel.cs +++ b/src/Files.App/Interacts/BaseLayoutCommandsViewModel.cs @@ -1,4 +1,5 @@ using CommunityToolkit.Mvvm.Input; +using Files.App.Filesystem; using Files.Shared; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; @@ -82,6 +83,7 @@ private void InitializeCommands() RotateImageRightCommand = new AsyncRelayCommand(CommandsModel.RotateImageRight); InstallFontCommand = new AsyncRelayCommand(CommandsModel.InstallFont); PlayAllCommand = new AsyncRelayCommand(CommandsModel.PlayAll); + FormatDriveCommand = new RelayCommand(CommandsModel.FormatDrive); } #endregion Command Initialization @@ -199,6 +201,7 @@ private void InitializeCommands() public ICommand InstallFontCommand { get; private set; } public ICommand PlayAllCommand { get; private set; } + public ICommand FormatDriveCommand { get; private set; } #endregion Commands diff --git a/src/Files.App/Interacts/IBaseLayoutCommandImplementationModel.cs b/src/Files.App/Interacts/IBaseLayoutCommandImplementationModel.cs index f13d6918b430..5cbff7da33d8 100644 --- a/src/Files.App/Interacts/IBaseLayoutCommandImplementationModel.cs +++ b/src/Files.App/Interacts/IBaseLayoutCommandImplementationModel.cs @@ -1,3 +1,4 @@ +using Files.App.Filesystem; using Files.Shared; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; @@ -119,5 +120,7 @@ public interface IBaseLayoutCommandImplementationModel : IDisposable Task InstallFont(); Task PlayAll(); + + void FormatDrive(ListedItem? obj); } } diff --git a/src/Files.App/ResourceDictionaries/DefaultGridSplitterStyle.xaml b/src/Files.App/ResourceDictionaries/DefaultGridSplitterStyle.xaml index c685085d1fd3..f069b7ef620c 100644 --- a/src/Files.App/ResourceDictionaries/DefaultGridSplitterStyle.xaml +++ b/src/Files.App/ResourceDictionaries/DefaultGridSplitterStyle.xaml @@ -9,7 +9,7 @@ - + diff --git a/src/Files.App/ServicesImplementation/Settings/PreferencesSettingsService.cs b/src/Files.App/ServicesImplementation/Settings/PreferencesSettingsService.cs index 4c8885f4f90b..582a83c3a3ff 100644 --- a/src/Files.App/ServicesImplementation/Settings/PreferencesSettingsService.cs +++ b/src/Files.App/ServicesImplementation/Settings/PreferencesSettingsService.cs @@ -207,6 +207,12 @@ public bool ShowOpenInNewPane set => Set(value); } + public FileNameConflictResolveOptionType ConflictsResolveOption + { + get => (FileNameConflictResolveOptionType)Get((long)FileNameConflictResolveOptionType.GenerateNewName); + set => Set((long)value); + } + protected override void RaiseOnSettingChangedEvent(object sender, SettingChangedEventArgs e) { switch (e.SettingName) @@ -236,6 +242,7 @@ protected override void RaiseOnSettingChangedEvent(object sender, SettingChanged case nameof(ShowOpenInNewTab): case nameof(ShowOpenInNewWindow): case nameof(ShowOpenInNewPane): + case nameof(ConflictsResolveOption): Analytics.TrackEvent($"Set {e.SettingName} to {e.NewValue}"); break; } diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index f3f8dced148e..b29869c25f0e 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -1,4 +1,4 @@ - + + (OpenProperties); } @@ -271,6 +276,13 @@ private List GetLocationItemMenuItems(INavigatio ShowItem = options.ShowEjectDevice }, new ContextMenuFlyoutItemViewModel() + { + Text = "FormatDriveText".GetLocalizedResource(), + Command = FormatDriveCommand, + CommandParameter = item, + ShowItem = options.ShowFormatDrive + }, + new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutPropertiesFolder/Text".GetLocalizedResource(), Glyph = "\uE946", @@ -377,6 +389,11 @@ private async void EjectDevice() await UIHelpers.ShowDeviceEjectResultAsync(result); } + private void FormatDrive() + { + Win32API.OpenFormatDriveDialog(rightClickedItem.Path); + } + private async void Sidebar_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) { if (IsInPointerPressed || args.InvokedItem is null || args.InvokedItemContainer is null) @@ -434,9 +451,8 @@ private void NavigationViewItem_RightTapped(object sender, RightTappedRoutedEven var menuItems = GetLocationItemMenuItems(item, itemContextMenuFlyout); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - if (!userSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu) - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); // Set menu min width if the overflow menu setting is disabled + secondaryElements.OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); itemContextMenuFlyout.ShowAt(sidebarItem, new FlyoutShowOptions { Position = e.GetPosition(sidebarItem) }); diff --git a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs index c4efc48a7dfe..65183f6d9ddc 100644 --- a/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/DrivesWidget.xaml.cs @@ -5,6 +5,7 @@ using Files.App.Extensions; using Files.App.Filesystem; using Files.App.Helpers; +using Files.App.Shell; using Files.App.ViewModels; using Files.App.ViewModels.Widgets; using Files.Backend.Services.Settings; @@ -18,6 +19,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; @@ -81,6 +83,7 @@ public sealed partial class DrivesWidget : HomePageWidget, IWidgetItemModel, INo private IShellPage associatedInstance; + public ICommand FormatDriveCommand; public ICommand EjectDeviceCommand; public ICommand DisconnectNetworkDriveCommand; public ICommand GoToStorageSenseCommand; @@ -126,6 +129,7 @@ public DrivesWidget() App.DrivesManager.DataChanged += Manager_DataChanged; + FormatDriveCommand = new RelayCommand(FormatDrive); EjectDeviceCommand = new AsyncRelayCommand(EjectDevice); OpenInNewTabCommand = new RelayCommand(OpenInNewTab); OpenInNewWindowCommand = new RelayCommand(OpenInNewWindow); @@ -139,7 +143,8 @@ public DrivesWidget() public override List GetItemMenuItems(WidgetCardItem item, bool isPinned) { - var options = (item.Item as DriveItem)?.MenuOptions; + var drive = ItemsAdded.Where(x => string.Equals(PathNormalization.NormalizePath(x.Path), PathNormalization.NormalizePath(item.Path), StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + var options = drive?.Item.MenuOptions; return new List() { @@ -199,6 +204,13 @@ public override List GetItemMenuItems(WidgetCard ShowItem = options?.ShowEjectDevice ?? false }, new ContextMenuFlyoutItemViewModel() + { + Text = "FormatDriveText".GetLocalizedResource(), + Command = FormatDriveCommand, + CommandParameter = item, + ShowItem = options?.ShowFormatDrive ?? false + }, + new ContextMenuFlyoutItemViewModel() { Text = "BaseLayoutContextFlyoutPropertiesFolder/Text".GetLocalizedResource(), Glyph = "\uE946", @@ -233,7 +245,7 @@ await DispatcherQueue.EnqueueAsync(async () => { foreach (DriveItem drive in App.DrivesManager.Drives) { - if (!ItemsAdded.Any(x => x.Item == drive) && drive.Type != DriveType.VirtualDrive) + if (!ItemsAdded.Any(x => x.Item == drive) && drive.Type != DataModels.NavigationControlItems.DriveType.VirtualDrive) { var cardItem = new DriveCardItem(drive); ItemsAdded.AddSorted(cardItem); @@ -260,6 +272,10 @@ private async Task EjectDevice(DriveCardItem item) await UIHelpers.ShowDeviceEjectResultAsync(result); } + private void FormatDrive(DriveCardItem? item) + { + Win32API.OpenFormatDriveDialog(item?.Path ?? string.Empty); + } private async Task OpenProperties(DriveCardItem item) { diff --git a/src/Files.App/UserControls/Widgets/HomePageWidget.cs b/src/Files.App/UserControls/Widgets/HomePageWidget.cs index 2f2bb6b49b35..bfe093a9501b 100644 --- a/src/Files.App/UserControls/Widgets/HomePageWidget.cs +++ b/src/Files.App/UserControls/Widgets/HomePageWidget.cs @@ -43,9 +43,8 @@ public void Button_RightTapped(object sender, RightTappedRoutedEventArgs e) var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path)); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - if (!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu) - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); // Set menu min width if the overflow menu setting is disabled + secondaryElements.OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); itemContextMenuFlyout.ShowAt(widgetCardItem, new FlyoutShowOptions { Position = e.GetPosition(widgetCardItem) }); diff --git a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs index 97acfd132dfa..5a28676d189e 100644 --- a/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs +++ b/src/Files.App/UserControls/Widgets/RecentFilesWidget.xaml.cs @@ -110,9 +110,8 @@ private void Grid_RightTapped(object sender, RightTappedRoutedEventArgs e) var menuItems = GetItemMenuItems(item, false); var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems); - if (!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu) - secondaryElements.OfType() - .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); // Set menu min width if the overflow menu setting is disabled + secondaryElements.OfType() + .ForEach(i => i.MinWidth = Constants.UI.ContextMenuItemsMaxWidth); secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i)); itemContextMenuFlyout.ShowAt(recentItemsGrid, new FlyoutShowOptions { Position = e.GetPosition(recentItemsGrid) }); @@ -199,7 +198,7 @@ private void OpenFileLocation(RecentItem item) ItemName = Path.GetFileName(item.RecentPath), // file name w extension }); } - + private async Task UpdateRecentsList(NotifyCollectionChangedEventArgs e) { try diff --git a/src/Files.App/ValueConverters/EnumConverters/ConflictResolveOptionToIndexConverter.cs b/src/Files.App/ValueConverters/EnumConverters/ConflictResolveOptionToIndexConverter.cs index f765e161351e..e1f58231a577 100644 --- a/src/Files.App/ValueConverters/EnumConverters/ConflictResolveOptionToIndexConverter.cs +++ b/src/Files.App/ValueConverters/EnumConverters/ConflictResolveOptionToIndexConverter.cs @@ -10,11 +10,11 @@ public object Convert(object value, Type targetType, object parameter, string la { return (FileNameConflictResolveOptionType)value switch { - FileNameConflictResolveOptionType.None => 0, + FileNameConflictResolveOptionType.None => -1, FileNameConflictResolveOptionType.GenerateNewName => 0, FileNameConflictResolveOptionType.ReplaceExisting => 1, FileNameConflictResolveOptionType.Skip => 2, - _ => 0 + _ => -1 }; } diff --git a/src/Files.App/Views/ColumnShellPage.xaml b/src/Files.App/Views/ColumnShellPage.xaml index 55a8462029e7..bb466d8e16b4 100644 --- a/src/Files.App/Views/ColumnShellPage.xaml +++ b/src/Files.App/Views/ColumnShellPage.xaml @@ -2,7 +2,6 @@ x:Class="Files.App.Views.ColumnShellPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:Custom="using:CommunityToolkit.WinUI.UI.Controls" xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="using:Files.App.Views" @@ -115,10 +114,6 @@ Key="F3" Invoked="KeyboardAccelerator_Invoked" IsEnabled="{x:Bind IsCurrentInstance, Mode=OneWay}" /> - - - - - - - - @@ -326,6 +319,10 @@ Grid.Column="1" x:Load="{x:Bind ShouldPreviewPaneBeActive, Mode=OneWay}" ManipulationCompleted="PaneSplitter_ManipulationCompleted" + ManipulationStarted="PaneSplitter_ManipulationStarted" + PointerCanceled="PaneSplitter_PointerExited" + PointerEntered="PaneSplitter_PointerEntered" + PointerExited="PaneSplitter_PointerExited" ResizeBehavior="BasedOnAlignment" Style="{StaticResource DefaultGridSplitterStyle}" /> diff --git a/src/Files.App/Views/MainPage.xaml.cs b/src/Files.App/Views/MainPage.xaml.cs index ad7c0a502cb7..1d35cb70ee83 100644 --- a/src/Files.App/Views/MainPage.xaml.cs +++ b/src/Files.App/Views/MainPage.xaml.cs @@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.WinUI.Helpers; using CommunityToolkit.WinUI.UI.Controls; +using Files.App.Commands; using Files.App.DataModels; using Files.App.DataModels.NavigationControlItems; using Files.App.Extensions; @@ -13,6 +14,7 @@ using Files.Backend.Extensions; using Files.Backend.Services.Settings; using Files.Shared.EventArguments; +using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -20,9 +22,11 @@ using Microsoft.UI.Xaml.Navigation; using System; using System.ComponentModel; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using System.Windows.Input; +using UWPToWinAppSDKUpgradeHelpers; using Windows.ApplicationModel; using Windows.Graphics; using Windows.Services.Store; @@ -36,6 +40,7 @@ namespace Files.App.Views public sealed partial class MainPage : Page, INotifyPropertyChanged { public IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + public ICommandManager Commands { get; } = Ioc.Default.GetRequiredService(); public AppModel AppModel => App.AppModel; @@ -45,12 +50,16 @@ public MainPageViewModel ViewModel set => DataContext = value; } + + /// + /// True if the user is currently resizing the preview pane + /// + private bool draggingPreviewPane; + public SidebarViewModel SidebarAdaptiveViewModel = new SidebarViewModel(); public OngoingTasksViewModel OngoingTasksViewModel => App.OngoingTasksViewModel; - public ICommand ToggleFullScreenAcceleratorCommand { get; } - private ICommand ToggleCompactOverlayCommand { get; } private ICommand SetCompactOverlayCommand { get; } @@ -64,7 +73,6 @@ public MainPage() if (flowDirectionSetting == "RTL") FlowDirection = FlowDirection.RightToLeft; - ToggleFullScreenAcceleratorCommand = new RelayCommand(ToggleFullScreenAccelerator); ToggleCompactOverlayCommand = new RelayCommand(ToggleCompactOverlay); SetCompactOverlayCommand = new RelayCommand(SetCompactOverlay); @@ -312,19 +320,16 @@ private void Page_Loaded(object sender, RoutedEventArgs e) FindName(nameof(TabControl)); FindName(nameof(NavToolbar)); + var commands = Commands.Where(command => !command.CustomHotKey.IsNone); + foreach (var command in commands) + KeyboardAccelerators.Add(new CommandAccelerator(command)); + Commands.HotKeyChanged += Commands_HotKeyChanged; + if (Package.Current.Id.Name != "49306atecsolution.FilesUWP" || UserSettingsService.ApplicationSettingsService.ClickedToReviewApp) return; var totalLaunchCount = SystemInformation.Instance.TotalLaunchCount; - - if - ( - totalLaunchCount == 10 || - totalLaunchCount == 20 || - totalLaunchCount == 30 || - totalLaunchCount == 40 || - totalLaunchCount == 50 - ) + if (totalLaunchCount is 10 or 20 or 30 or 40 or 50) { // Prompt user to review app in the Store DispatcherQueue.TryEnqueue(async () => await PromptForReview()); @@ -346,18 +351,6 @@ private void Page_SizeChanged(object sender, SizeChangedEventArgs e) } } - private void ToggleFullScreenAccelerator(KeyboardAcceleratorInvokedEventArgs? e) - { - var view = App.GetAppWindow(App.Window); - - view.SetPresenter(view.Presenter.Kind == AppWindowPresenterKind.FullScreen - ? AppWindowPresenterKind.Overlapped - : AppWindowPresenterKind.FullScreen); - - if (e is not null) - e.Handled = true; - } - private void ToggleSidebarCollapsedState(KeyboardAcceleratorInvokedEventArgs? e) { SidebarAdaptiveViewModel.IsSidebarOpen = !SidebarAdaptiveViewModel.IsSidebarOpen; @@ -431,6 +424,11 @@ private void UpdatePositioning() } } + private void PaneSplitter_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) + { + draggingPreviewPane = true; + } + private void PaneSplitter_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) { switch (PreviewPane?.Position) @@ -442,6 +440,23 @@ private void PaneSplitter_ManipulationCompleted(object sender, ManipulationCompl UserSettingsService.PreviewPaneSettingsService.HorizontalSizePx = PreviewPane.ActualHeight; break; } + + draggingPreviewPane = false; + } + + private void PaneSplitter_PointerExited(object sender, PointerRoutedEventArgs e) + { + if (draggingPreviewPane) + return; + + var paneSplitter = (GridSplitter)sender; + paneSplitter.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.Arrow)); + } + + private void PaneSplitter_PointerEntered(object sender, PointerRoutedEventArgs e) + { + var paneSplitter = (GridSplitter)sender; + paneSplitter.ChangeCursor(InputSystemCursor.Create(InputSystemCursorShape.SizeWestEast)); } public bool ShouldPreviewPaneBeActive => UserSettingsService.PreviewPaneSettingsService.IsEnabled && ShouldPreviewPaneBeDisplayed; @@ -498,5 +513,51 @@ private void RootGrid_PreviewKeyDown(object sender, KeyRoutedEventArgs e) } private void NavToolbar_Loaded(object sender, RoutedEventArgs e) => UpdateNavToolbarProperties(); + + private void Commands_HotKeyChanged(object? sender, HotKeyChangedEventArgs e) + { + if (!e.OldHotKey.IsNone) + { + var oldAccelerator = KeyboardAccelerators.FirstOrDefault(IsOldHotKey); + if (oldAccelerator is CommandAccelerator commandAccelerator) + { + commandAccelerator.Dispose(); + KeyboardAccelerators.Remove(commandAccelerator); + } + } + + if (!e.NewHotKey.IsNone) + { + var newAccelerator = new CommandAccelerator(e.Command); + KeyboardAccelerators.Add(newAccelerator); + } + + bool IsOldHotKey(KeyboardAccelerator accelerator) + => accelerator is CommandAccelerator commandAccelerator + && accelerator.Key == e.OldHotKey.Key + && accelerator.Modifiers == e.OldHotKey.Modifiers; + } + + private class CommandAccelerator : KeyboardAccelerator, IDisposable + { + public IRichCommand Command { get; } + + public CommandAccelerator(IRichCommand command) + { + Command = command; + + Key = Command.CustomHotKey.Key; + Modifiers = Command.CustomHotKey.Modifiers; + Invoked += CommandAccelerator_Invoked; + } + + public void Dispose() => Invoked -= CommandAccelerator_Invoked; + + private async void CommandAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs e) + { + e.Handled = true; + await Command.ExecuteAsync(); + } + } } } \ No newline at end of file diff --git a/src/Files.App/Views/ModernShellPage.xaml b/src/Files.App/Views/ModernShellPage.xaml index 10676ddee92a..9dbbbe510b52 100644 --- a/src/Files.App/Views/ModernShellPage.xaml +++ b/src/Files.App/Views/ModernShellPage.xaml @@ -118,10 +118,6 @@ Key="F3" Invoked="KeyboardAccelerator_Invoked" IsEnabled="{x:Bind IsCurrentInstance, Mode=OneWay}" /> - + Click="PreRemoveTag_Click" + Content="{helpers:ResourceString Name=Delete}"> + + + + + + bool ShowOpenInNewPane { get; set; } + + /// + /// Gets or sets a value indicating the default option to resolve conflicts. + /// + FileNameConflictResolveOptionType ConflictsResolveOption { get; set; } } } diff --git a/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogConflictItemViewModel.cs b/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogConflictItemViewModel.cs index df2ad6564433..963d70c2de9f 100644 --- a/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogConflictItemViewModel.cs +++ b/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogConflictItemViewModel.cs @@ -53,11 +53,6 @@ public string DestinationDirectoryDisplayName get => Path.GetFileName(Path.GetDirectoryName(DestinationPath)); } - public bool IsDefault - { - get => ConflictResolveOption == FileNameConflictResolveOptionType.GenerateNewName; // Default value - } - public bool IsConflict { get => ConflictResolveOption != FileNameConflictResolveOptionType.None; diff --git a/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogViewModel.cs b/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogViewModel.cs index cb05278eb08e..b69c820fda03 100644 --- a/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogViewModel.cs +++ b/src/Files.Backend/ViewModels/Dialogs/FileSystemDialog/FileSystemDialogViewModel.cs @@ -4,6 +4,7 @@ using Files.Backend.Extensions; using Files.Backend.Messages; using Files.Backend.Services; +using Files.Backend.Services.Settings; using Files.Shared.Enums; using Files.Shared.Extensions; using System; @@ -17,6 +18,8 @@ namespace Files.Backend.ViewModels.Dialogs.FileSystemDialog { public sealed class FileSystemDialogViewModel : BaseDialogViewModel, IRecipient { + private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService(); + private readonly CancellationTokenSource _dialogClosingCts; private readonly IMessenger _messenger; @@ -85,7 +88,7 @@ public void ApplyConflictOptionToAll(FileNameConflictResolveOptionType e) { foreach (var item in Items) { - if (item is FileSystemDialogConflictItemViewModel conflictItem) + if (item is FileSystemDialogConflictItemViewModel conflictItem && conflictItem.ConflictResolveOption != FileNameConflictResolveOptionType.None) { conflictItem.ConflictResolveOption = e; } @@ -102,15 +105,26 @@ public IEnumerable GetItemsResult() public void Receive(FileSystemDialogOptionChangedMessage message) { - if (Items.Count == 1) + if (message.Value.ConflictResolveOption != FileNameConflictResolveOptionType.None) { - AggregatedResolveOption = message.Value.ConflictResolveOption; + var itemsWithoutNone = Items.Where(x => (x as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption != FileNameConflictResolveOptionType.None); + // If all items have the same resolve option -- set the aggregated option to that choice + var first = (itemsWithoutNone.First() as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption; + AggregatedResolveOption = itemsWithoutNone.All(x => (x as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption == first) ? first : FileNameConflictResolveOptionType.None; } - else + } + + public void LoadConflictResolveOption() + { + AggregatedResolveOption = UserSettingsService.PreferencesSettingsService.ConflictsResolveOption; + } + + public void SaveConflictResolveOption() + { + if (AggregatedResolveOption != FileNameConflictResolveOptionType.None + && AggregatedResolveOption != UserSettingsService.PreferencesSettingsService.ConflictsResolveOption) { - // If all items have the same resolve option -- set the aggregated option to that choice - var first = (Items.First() as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption; - AggregatedResolveOption = Items.All(x => (x as FileSystemDialogConflictItemViewModel)!.ConflictResolveOption == first) ? first : FileNameConflictResolveOptionType.None; + UserSettingsService.PreferencesSettingsService.ConflictsResolveOption = AggregatedResolveOption; } }