Menu item keyboard accelerators implemented (#589)

* Added icon to "Recent >" menu item.

* Refactored app menu implementation, and implemented keyboard accelerators. Still doesn't work though.

* Few things fixed, but still doesn't work.

* Clicking menu items works.

* Fixing an issue with resources.

* New menu binding implemented. Still doesn't work though.

* Menu displayed properly, except for "Settings".

* Menu works, but it doesn't refresh.

* Works. But needs to be polished.

* All known issues with the new app menu solved.

* Significantly simplifying app menu binding.

* Removing some obsolete classes.

* Menu binding mechanism additionally optimized for performances.

* Removing unused translations.

* Further refactoring app menu binding, in order to make it more general.

* New window mechanism simplified. Terminal-launching code refactored to be more readable. A bug with "SSH Info" fixed.

* Merging the latest changed.
This commit is contained in:
Aleksandar Pesic authored and Felix Seidl committed 2019-11-26 22:03:22 +01:00
1 parent 4b21a5abb6
commit 7ab42ec1de
22 files changed
+904 -164

No files matched your search

+191 -53
View File
@@ -1,29 +1,23 @@
using FluentTerminal.App.Services;
using FluentTerminal.App.Services;
using FluentTerminal.App.Services.EventArgs;
using FluentTerminal.App.Services.Utilities;
using FluentTerminal.App.ViewModels.Infrastructure;
using FluentTerminal.Models;
using FluentTerminal.Models.Enums;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using FluentTerminal.App.ViewModels.Menu;
using FluentTerminal.Models.Messages;
using GalaSoft.MvvmLight.Command;
namespace FluentTerminal.App.ViewModels
{
public class MainViewModel : ViewModelBase
{
#region Constants
private const int RecentItemsMaxCount = 10;
#endregion Constants
private readonly IClipboardService _clipboardService;
private readonly IDialogService _dialogService;
private readonly IDispatcherTimer _dispatcherTimer;
@@ -31,16 +25,17 @@ namespace FluentTerminal.App.ViewModels
private readonly ISettingsService _settingsService;
private readonly ITrayProcessCommunicationService _trayProcessCommunicationService;
private readonly ICommandHistoryService _commandHistoryService;
private readonly IAcceleratorKeyValidator _acceleratorKeyValidator;
private ApplicationSettings _applicationSettings;
private string _background;
private double _backgroundOpacity;
private TerminalViewModel _selectedTerminal;
private TabsPosition _tabsPosition;
private string _windowTitle;
private List<KeyBinding> _keyBindings;
private IDictionary<string, ICollection<KeyBinding>> _keyBindings;
public MainViewModel(ISettingsService settingsService, ITrayProcessCommunicationService trayProcessCommunicationService, IDialogService dialogService, IKeyboardCommandService keyboardCommandService,
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, ICommandHistoryService commandHistoryService)
IApplicationView applicationView, IDispatcherTimer dispatcherTimer, IClipboardService clipboardService, ICommandHistoryService commandHistoryService, IAcceleratorKeyValidator acceleratorKeyValidator)
{
MessengerInstance.Register<ApplicationSettingsChangedMessage>(this, OnApplicationSettingsChanged);
MessengerInstance.Register<CurrentThemeChangedMessage>(this, OnCurrentThemeChanged);
@@ -50,6 +45,7 @@ namespace FluentTerminal.App.ViewModels
MessengerInstance.Register<SshProfileDeletedMessage>(this, OnSshProfileDeleted);
MessengerInstance.Register<TerminalOptionsChangedMessage>(this, OnTerminalOptionsChanged);
MessengerInstance.Register<CommandHistoryChangedMessage>(this, OnCommandHistoryChanged);
MessengerInstance.Register<KeyBindingsChangedMessage>(this, OnKeyBindingChanged);
_settingsService = settingsService;
@@ -60,6 +56,7 @@ namespace FluentTerminal.App.ViewModels
_clipboardService = clipboardService;
_keyboardCommandService = keyboardCommandService;
_commandHistoryService = commandHistoryService;
_acceleratorKeyValidator = acceleratorKeyValidator;
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewTab), async () => await AddDefaultProfileAsync(NewTerminalLocation.Tab));
_keyboardCommandService.RegisterCommandHandler(nameof(Command.NewWindow), async () => await AddDefaultProfileAsync(NewTerminalLocation.Window));
@@ -111,39 +108,17 @@ namespace FluentTerminal.App.ViewModels
TabsPosition = _applicationSettings.TabsPosition;
AddLocalShellCommand = new RelayCommand(async () => await AddDefaultProfileAsync(NewTerminalLocation.Tab));
AddSshShellCommand = new RelayCommand(async () => await AddSshProfileAsync(NewTerminalLocation.Tab));
AddQuickShellCommand = new RelayCommand(async () => await AddQuickLaunchProfileAsync(NewTerminalLocation.Tab));
ShowAboutCommand = new AsyncCommand(ShowAbout);
ShowSettingsCommand = new RelayCommand(ShowSettings);
ApplicationView.CloseRequested += OnCloseRequest;
ApplicationView.Closed += OnClosed;
Terminals.CollectionChanged += OnTerminalsCollectionChanged;
LoadKeyBindings();
MessengerInstance.Register<KeyBindingsChangedMessage>(this, message => LoadKeyBindings());
InitializeAppMenu();
}
private IList<ProfileCommandViewModel> _recentCommands;
public IList<ProfileCommandViewModel> RecentCommands
{
get => _recentCommands ?? (_recentCommands = _commandHistoryService
.GetHistoryRecentFirst(top: RecentItemsMaxCount).Select(c =>
new ProfileCommandViewModel(c.ShellProfile,
new RelayCommand(() => AddTabAsync(c.ShellProfile)))).ToList());
private set => Set(ref _recentCommands, value);
}
private void OnCommandHistoryChanged(CommandHistoryChangedMessage message)
{
_recentCommands = null;
ApplicationView.RunOnDispatcherThread(() => RaisePropertyChanged(nameof(RecentCommands)), false);
}
private void LoadKeyBindings() =>
_keyBindings = _settingsService.GetCommandKeyBindings().Values.SelectMany(v => v).ToList();
private void LoadKeyBindings() => _keyBindings = _settingsService.GetCommandKeyBindings();
private void OnClosed(object sender, EventArgs e)
{
@@ -158,10 +133,6 @@ namespace FluentTerminal.App.ViewModels
_applicationSettings = null;
AddLocalShellCommand = null;
AddSshShellCommand = null;
AddQuickShellCommand = null;
ShowAboutCommand = null;
ShowSettingsCommand = null;
Closed?.Invoke(this, e);
}
@@ -214,8 +185,6 @@ namespace FluentTerminal.App.ViewModels
}
public RelayCommand AddLocalShellCommand { get; private set; }
public RelayCommand AddSshShellCommand { get; private set; }
public RelayCommand AddQuickShellCommand { get; private set; }
public string WindowTitle
{
@@ -279,10 +248,6 @@ namespace FluentTerminal.App.ViewModels
}
}
public IAsyncCommand ShowAboutCommand { get; private set; }
public RelayCommand ShowSettingsCommand { get; private set; }
public TabsPosition TabsPosition
{
get => _tabsPosition;
@@ -374,6 +339,9 @@ namespace FluentTerminal.App.ViewModels
#endregion For serialization
private Task AddProfileAsync(ShellProfile profile) =>
AddProfileAsync(profile, _settingsService.GetApplicationSettings().NewTerminalLocation);
private Task AddProfileAsync(ShellProfile profile, NewTerminalLocation location)
{
if (profile == null)
@@ -597,6 +565,20 @@ namespace FluentTerminal.App.ViewModels
});
}
private void OnKeyBindingChanged(KeyBindingsChangedMessage message)
{
LoadKeyBindings();
// Should be scheduled no matter if we're in the UI thread.
ApplicationView.RunOnDispatcherThread(CreateMenuViewModel);
}
private void OnCommandHistoryChanged(CommandHistoryChangedMessage message)
{
// Should be scheduled no matter if we're in the UI thread.
ApplicationView.RunOnDispatcherThread(CreateMenuViewModel);
}
private void SelectTabNumber(int tabNumber)
{
if (tabNumber < Terminals.Count)
@@ -619,12 +601,7 @@ namespace FluentTerminal.App.ViewModels
SelectedTerminal = Terminals[previousIndex];
}
private Task ShowAbout()
{
return _dialogService.ShowAboutDialogAsync();
}
private void ShowSettings()
public void ShowSettings()
{
ShowSettingsRequested?.Invoke(this, EventArgs.Empty);
}
@@ -636,7 +613,7 @@ namespace FluentTerminal.App.ViewModels
public void OnWindowKeyDown(int key, bool control, bool alt, bool shift, bool meta)
{
var binding = _keyBindings.FirstOrDefault(b =>
var binding = _keyBindings.Values.SelectMany(bs => bs).FirstOrDefault(b =>
b.Key == key && b.Ctrl == control && b.Alt == alt && b.Shift == shift && b.Meta == meta);
if (binding != null)
@@ -644,5 +621,166 @@ namespace FluentTerminal.App.ViewModels
_keyboardCommandService.SendCommand(binding.Command);
}
}
#region App menu
private const int RecentItemsMaxCount = 10;
private AppMenuViewModel _menuViewModel;
private RelayCommand _newRemoteTabCommand;
private RelayCommand _newQuickTabCommand;
private RelayCommand _settingsCommand;
private RelayCommand _aboutCommand;
public AppMenuViewModel MenuViewModel
{
get => _menuViewModel;
private set
{
// Changing app menu view model only if it's actually different than previous.
if (!value.EquivalentTo(_menuViewModel))
{
Set(ref _menuViewModel, value);
}
}
}
private void InitializeAppMenu()
{
_newRemoteTabCommand = new RelayCommand(async () => await AddSshProfileAsync(NewTerminalLocation.Tab));
_newQuickTabCommand = new RelayCommand(async () => await AddQuickLaunchProfileAsync(NewTerminalLocation.Tab));
_settingsCommand = new RelayCommand(ShowSettings);
_aboutCommand = new RelayCommand(async () => await _dialogService.ShowAboutDialogAsync());
CreateMenuViewModel();
}
private void CreateMenuViewModel()
{
var tabItem = new MenuItemViewModel(I18N.TranslateWithFallback("NewTab.Text", "New tab"),
AddLocalShellCommand,
I18N.TranslateWithFallback("NewTab_Description", "Opens default profile in a new tab."),
icon: 57609 /*(int) Symbol.Add*/);
if (_keyBindings.TryGetValue(nameof(Command.NewTab), out var keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding tabKeyBindings &&
_acceleratorKeyValidator.Valid(tabKeyBindings.Key))
{
LoadKeyBindingsFromModel(tabItem, tabKeyBindings);
}
else
{
tabItem.KeyBinding = null;
}
var remoteTabItem = new MenuItemViewModel(I18N.TranslateWithFallback("NewSshTab.Text", "New remote tab"),
_newRemoteTabCommand,
I18N.TranslateWithFallback("NewSshTab_Description", "Opens a new SSH or Mosh session in a new tab."),
icon: 57609 /*(int) Symbol.Add*/);
if (_keyBindings.TryGetValue(nameof(Command.NewSshTab), out keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding remoteTabKeyBinding &&
_acceleratorKeyValidator.Valid(remoteTabKeyBinding.Key))
{
LoadKeyBindingsFromModel(remoteTabItem, remoteTabKeyBinding);
}
else
{
remoteTabItem.KeyBinding = null;
}
var quickTab = new MenuItemViewModel(I18N.TranslateWithFallback("NewQuickTab.Text", "New quick tab"),
_newQuickTabCommand,
I18N.TranslateWithFallback("NewQuickTab_Description",
"Opens \"Quick Launch\" dialog and starts session in a new tab."),
icon: 57609 /*(int) Symbol.Add*/);
if (_keyBindings.TryGetValue(nameof(Command.NewCustomCommandTab), out keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding quickTabKeyBinding &&
_acceleratorKeyValidator.Valid(quickTabKeyBinding.Key))
{
LoadKeyBindingsFromModel(quickTab, quickTabKeyBinding);
}
else
{
quickTab.KeyBinding = null;
}
var settings = new MenuItemViewModel(I18N.TranslateWithFallback("Settings.Text", "Settings"),
_settingsCommand, I18N.TranslateWithFallback("Settings_Description", "Opens settings window."),
icon: 57621 /*(int) Symbol.Setting*/);
if (_keyBindings.TryGetValue(nameof(Command.ShowSettings), out keyBindings) &&
keyBindings.FirstOrDefault() is KeyBinding settingsKeyBinding &&
_acceleratorKeyValidator.Valid(settingsKeyBinding.Key))
{
LoadKeyBindingsFromModel(settings, settingsKeyBinding);
}
else
{
settings.KeyBinding = null;
}
var recent = new ExpandableMenuItemViewModel(I18N.TranslateWithFallback("Recent.Text", "Recent"),
GetRecentMenuItems(), I18N.TranslateWithFallback("Recent_Description", "Recently opened sessions."),
icon: "\uF738" /*Segoe MDL2 Assets Glyph property*/);
var about = new MenuItemViewModel(I18N.TranslateWithFallback("AboutDialog.Title", "About"), _aboutCommand,
I18N.TranslateWithFallback("About_Description", "Basic info about the app."),
icon: "\uE946" /*Segoe MDL2 Assets Glyph property*/);
var appMenuViewModel = new AppMenuViewModel(new MenuItemViewModelBase[]
{
tabItem,
remoteTabItem,
quickTab,
settings,
recent,
about,
});
MenuViewModel = appMenuViewModel;
}
private ObservableCollection<MenuItemViewModel> GetRecentMenuItems() =>
new ObservableCollection<MenuItemViewModel>(_commandHistoryService
.GetHistoryRecentFirst(top: RecentItemsMaxCount).Select(CommandToMenuItem));
private MenuItemViewModel CommandToMenuItem(ExecutedCommand command)
{
var itemCommand = new RelayCommand(async () => await AddProfileAsync(command.ShellProfile),
keepTargetAlive: true);
var keyBinding = command.ShellProfile.KeyBindings?.FirstOrDefault() is KeyBinding kb
? new MenuItemKeyBindingViewModel(kb)
: null;
return new MenuItemViewModel(command.Value, itemCommand, keyBinding: keyBinding);
}
private void LoadKeyBindingsFromModel(MenuItemViewModel menuItemViewModel, KeyBinding keyBinding)
{
if (!_acceleratorKeyValidator.Valid(keyBinding.Key))
{
menuItemViewModel.KeyBinding = null;
return;
}
if (menuItemViewModel.KeyBinding == null)
{
menuItemViewModel.KeyBinding = new MenuItemKeyBindingViewModel(keyBinding);
}
else
{
menuItemViewModel.KeyBinding.Key = keyBinding.Key;
menuItemViewModel.KeyBinding.Ctrl = keyBinding.Ctrl;
menuItemViewModel.KeyBinding.Alt = keyBinding.Alt;
menuItemViewModel.KeyBinding.Shift = keyBinding.Shift;
menuItemViewModel.KeyBinding.Windows = keyBinding.Meta;
}
}
#endregion App menu
}
}