diff --git a/MultiTerm.Core/Common/NewlineSeparatorType.cs b/MultiTerm.Core/Common/NewlineSeparatorType.cs new file mode 100644 index 0000000..30030ab --- /dev/null +++ b/MultiTerm.Core/Common/NewlineSeparatorType.cs @@ -0,0 +1,30 @@ +using System.ComponentModel; + +namespace MultiTerm.Core.Common; + +public enum NewlineSeparatorType +{ + /// + /// No newline separator. + /// + [Description("None")] + None, + + /// + /// Carriage return newline separator. + /// + [Description("CR")] + CR, + + /// + /// Linefeed newline separator. + /// + [Description("LF")] + LF, + + /// + /// Carriage return and linefeed as newline separator. + /// + [Description("CR+LF")] + CR_LF +} diff --git a/MultiTerm.Core/ViewModel/ShellViewModel.cs b/MultiTerm.Core/ViewModel/ShellViewModel.cs index 770fe1a..0d32acb 100644 --- a/MultiTerm.Core/ViewModel/ShellViewModel.cs +++ b/MultiTerm.Core/ViewModel/ShellViewModel.cs @@ -1,6 +1,9 @@ using Common.StartupHelpers; using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using MultiTerm.Core.Common; using System.Collections.ObjectModel; +using System.Runtime.CompilerServices; namespace MultiTerm.Core.ViewModel; @@ -17,6 +20,21 @@ public partial class ShellViewModel : ObservableObject [ObservableProperty] private ITerminalViewModel? selectedTerminalViewModel; + #region Settings Menu Bar + public IEnumerable NewlineSeparatorTypeValues + { + get { return Enum.GetValues(typeof(NewlineSeparatorType)).Cast(); } + } + + // TODO Initialize from File + [ObservableProperty] + private NewlineSeparatorType selectedReceiveNewlineSeparator = NewlineSeparatorType.None; + + // TODO Initialize from File + [ObservableProperty] + private NewlineSeparatorType selectedSendNewlineSeparator = NewlineSeparatorType.None; + #endregion + public ShellViewModel(IAbstractFactory sendReceiveViewModelFactory) { this.sendReceiveViewModelFactory = sendReceiveViewModelFactory; @@ -34,5 +52,11 @@ public partial class ShellViewModel : ObservableObject // add to collection and set as selected this.TerminalViewModels.Add(newTerminal); this.SelectedTerminalViewModel = newTerminal; - } + } + + partial void OnSelectedReceiveNewlineSeparatorChanged(NewlineSeparatorType value) + { + Console.WriteLine($"Changed to {value}"); + } + } diff --git a/MultiTerm.Wpf/Controls/MenuItemExtensions.cs b/MultiTerm.Wpf/Controls/MenuItemExtensions.cs new file mode 100644 index 0000000..c527315 --- /dev/null +++ b/MultiTerm.Wpf/Controls/MenuItemExtensions.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; + +namespace MultiTerm.Wpf.Controls +{ + /// + /// TODO ueberarbeiten + /// FROM: + /// https://stackoverflow.com/questions/3652688/mutually-exclusive-checkable-menu-items/11497189#11497189 + /// + public class MenuItemExtensions : MenuItem + { + private static MenuItem? previouslySelectedMenuItem = null; + public static Dictionary ElementToGroupNames = new Dictionary(); + + public static readonly DependencyProperty GroupNameProperty = + DependencyProperty.RegisterAttached("GroupName", + typeof(String), + typeof(MenuItemExtensions), + new PropertyMetadata(String.Empty, OnGroupNameChanged)); + + public static readonly RoutedEvent IsCheckedChangedEvent = + EventManager.RegisterRoutedEvent("IsCheckedChanged", + RoutingStrategy.Bubble, typeof(RoutedEventArgs), + typeof(MenuItemExtensions)); + + public event RoutedEventHandler IsCheckedChanged + { + add { this.AddHandler(IsCheckedChangedEvent, value); } + remove { this.RemoveHandler(IsCheckedChangedEvent, value); } + } + + public static void SetGroupName(MenuItem element, String value) + { + element.SetValue(GroupNameProperty, value); + } + + public static String GetGroupName(MenuItem element) + { + return element.GetValue(GroupNameProperty).ToString(); + } + + private static void OnGroupNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + //Add an entry to the group name collection + var menuItem = d as MenuItem; + var parent = menuItem.Parent as MenuItem; + + if (menuItem != null) + { + String newGroupName = e.NewValue.ToString(); + String oldGroupName = e.OldValue.ToString(); + if (String.IsNullOrEmpty(newGroupName)) + { + //Removing the toggle button from grouping + RemoveCheckboxFromGrouping(menuItem); + } + else + { + //Switching to a new group + if (newGroupName != oldGroupName) + { + if (!String.IsNullOrEmpty(oldGroupName)) + { + //Remove the old group mapping + RemoveCheckboxFromGrouping(menuItem); + } + ElementToGroupNames.Add(menuItem, e.NewValue.ToString()); + menuItem.IsCheckable = true; + menuItem.Checked += MenuItemChecked; + } + } + } + } + + private static void RemoveCheckboxFromGrouping(MenuItem checkBox) + { + ElementToGroupNames.Remove(checkBox); + checkBox.Checked -= MenuItemChecked; + } + + + static void MenuItemChecked(object sender, RoutedEventArgs e) + { + var menuItem = e.OriginalSource as MenuItem; + foreach (var item in ElementToGroupNames) + { + // uncheck all other menu items in group + if (item.Key != menuItem && item.Value == GetGroupName(menuItem)) + { + item.Key.IsChecked = false; + } + } + // raise routed event + var menuItemExtensions = menuItem as MenuItemExtensions; + if(previouslySelectedMenuItem != null && previouslySelectedMenuItem != menuItem) + { + RoutedEventArgs args = new RoutedEventArgs(IsCheckedChangedEvent); + menuItemExtensions.RaiseEvent(args); + } + previouslySelectedMenuItem = menuItem; + } + } +} \ No newline at end of file diff --git a/MultiTerm.Wpf/Controls/SingleSelectSubMenu.cs b/MultiTerm.Wpf/Controls/SingleSelectSubMenu.cs new file mode 100644 index 0000000..dfc194f --- /dev/null +++ b/MultiTerm.Wpf/Controls/SingleSelectSubMenu.cs @@ -0,0 +1,124 @@ +using MultiTerm.Wpf.ValueConverters; +using System.Linq; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Controls; +using System; +using System.Printing; + +namespace MultiTerm.Wpf.Controls; + +public class SingleSelectSubMenu : MenuItem +{ + public static Dictionary RegisteredSubItemsAndParent = new Dictionary(); + + public static readonly DependencyProperty OptionsSourceProperty = + DependencyProperty.Register("OptionsSource", + typeof(IEnumerable), + typeof(SingleSelectSubMenu), + new PropertyMetadata(null, OnOptionsSourceChanged)); + + public static readonly DependencyProperty SelectedMenuItemProperty = + DependencyProperty.Register("SelectedMenuItem", + typeof(object), + typeof(SingleSelectSubMenu), + new PropertyMetadata(null, OnSelectedMenuItemPropertyChanged)); + + private static void OnSelectedMenuItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var sssm = d as SingleSelectSubMenu; + if (sssm != null) + { + if(e.NewValue != null) + { + // get menu item with same name from registered items + MenuItem selectedMenuItem = null; + foreach (var item in RegisteredSubItemsAndParent) + { + if(String.Compare(item.Key.Header.ToString(), e.NewValue.ToString(), true) == 0) + { + selectedMenuItem = item.Key; + break; + } + } + // var selectedMenuItem = RegisteredSubItemsAndParent.Where(x => x.Key.Header == e.NewValue).FirstOrDefault(); + if(selectedMenuItem != null) + { + selectedMenuItem.IsChecked = true; + //OnAnyItemChecked(selectedMenuItem, new RoutedEventArgs()); + } + } + } + } + + private static void OnOptionsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var sssm = d as SingleSelectSubMenu; + if(sssm != null) + { + if(e.NewValue != null) + { + var parent = sssm.Parent as MenuItem; + if(parent != null) + { + foreach (var item in (IEnumerable)e.NewValue) + { + var converter = new EnumDescriptionConverter(); + var newItem = new MenuItem() + { + Header = converter.Convert(item, typeof(object), null, CultureInfo.CurrentCulture), + IsCheckable = true + }; + newItem.Checked += OnAnyItemChecked; + RegisteredSubItemsAndParent.Add(newItem, sssm); + + parent.Items.Add(newItem); + Debug.WriteLine(item); + } + } + } + } + } + + private static void OnAnyItemChecked(object sender, RoutedEventArgs e) + { + var menuItem = sender as MenuItem; + if(menuItem != null) + { + Debug.WriteLine($"menuitem checked: {menuItem}"); + + // uncheck others + foreach (var item in RegisteredSubItemsAndParent) + { + if (item.Key != null && item.Key != menuItem) + { + item.Key.IsChecked = false; + } + } + + // set menu item for respective parent + RegisteredSubItemsAndParent[menuItem].SelectedMenuItem = menuItem; + } + } + + + [Bindable(true)] + public IEnumerable OptionsSource + { + get { return(IEnumerable)GetValue(OptionsSourceProperty); } + set { SetValue(OptionsSourceProperty, value); } + } + + + [Bindable(true)] + public object SelectedMenuItem + { + get { return GetValue(SelectedMenuItemProperty); } + set { SetValue (SelectedMenuItemProperty, value); } + } +} diff --git a/MultiTerm.Wpf/MainWindow.xaml b/MultiTerm.Wpf/MainWindow.xaml index 193837d..61f5f77 100644 --- a/MultiTerm.Wpf/MainWindow.xaml +++ b/MultiTerm.Wpf/MainWindow.xaml @@ -7,6 +7,8 @@ xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core" xmlns:v="clr-namespace:MultiTerm.Wpf.View" mc:Ignorable="d" - Title="MultiTerm" Height="450" Width="800"> + Height="900" Width="1600" + MinHeight="900" MinWidth="1600" + Title="MultiTerm"> diff --git a/MultiTerm.Wpf/ValueConverters/EnumDescriptionConverter.cs b/MultiTerm.Wpf/ValueConverters/EnumDescriptionConverter.cs new file mode 100644 index 0000000..758c6d9 --- /dev/null +++ b/MultiTerm.Wpf/ValueConverters/EnumDescriptionConverter.cs @@ -0,0 +1,60 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows.Data; + +namespace MultiTerm.Wpf.ValueConverters; + +public class EnumDescriptionConverter : IValueConverter +{ + private string GetEnumDescription(Enum enumObject) + { + FieldInfo fieldInfo = enumObject.GetType().GetField(enumObject.ToString()); + object[] attributeArray = fieldInfo.GetCustomAttributes(false); + if(attributeArray.Length == 0) + { + return enumObject.ToString(); + } + else + { + DescriptionAttribute attribute = attributeArray[0] as DescriptionAttribute; + return attribute.Description; + } + } + + /// + /// Convert from Data Source to Dependency Object type. + /// Here => Enum type to object. + /// + /// + /// + /// + /// + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + Enum myEnum = (Enum)value; + string description = this.GetEnumDescription(myEnum); + return description; + } + + /// + /// Convert from Dependency Object type to Data Source type. + /// Here => object to Enum type. + /// + /// + /// + /// + /// + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + int returnValue = 0; + if (parameter is Type) + { + returnValue = (int)Enum.Parse((Type)parameter, value.ToString()); + } + return returnValue; + } +} diff --git a/MultiTerm.Wpf/View/ShellView.xaml b/MultiTerm.Wpf/View/ShellView.xaml index 7b340e1..c27f56f 100644 --- a/MultiTerm.Wpf/View/ShellView.xaml +++ b/MultiTerm.Wpf/View/ShellView.xaml @@ -3,17 +3,74 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:MultiTerm.Wpf.View" + xmlns:sys="clr-namespace:System;assembly=System.Core" + xmlns:conv="clr-namespace:MultiTerm.Wpf.ValueConverters" + xmlns:controls="clr-namespace:MultiTerm.Wpf.Controls" xmlns:vm="clr-namespace:MultiTerm.Core.ViewModel;assembly=MultiTerm.Core" xmlns:v="clr-namespace:MultiTerm.Wpf.View" + xmlns:core_common="clr-namespace:MultiTerm.Core.Common;assembly=MultiTerm.Core" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> - + d:DesignHeight="600" d:DesignWidth="1200"> + + + + + + + + + + + + + + + + + + + + test + true + + + + + + + + + + + + + - + @@ -27,5 +84,5 @@ - + diff --git a/MultiTerm.Wpf/View/ShellView.xaml.cs b/MultiTerm.Wpf/View/ShellView.xaml.cs index fc1d7d0..91d19a7 100644 --- a/MultiTerm.Wpf/View/ShellView.xaml.cs +++ b/MultiTerm.Wpf/View/ShellView.xaml.cs @@ -1,4 +1,7 @@ -using MultiTerm.Core.ViewModel; +using MultiTerm.Core.Common; +using MultiTerm.Core.ViewModel; +using System; +using System.Linq; using System.Windows.Controls; namespace MultiTerm.Wpf.View; @@ -9,5 +12,21 @@ public partial class ShellView : UserControl { InitializeComponent(); this.DataContext = App.AppHost!.Services.GetService(typeof(ShellViewModel)); + this.PopulateSettingsNewlineSelectors(); + } + + private void PopulateSettingsNewlineSelectors() + { + //var types = Enum.GetValues(typeof(NewlineSeparatorType)).Cast(); + //foreach (var newlineSeparatorType in types) + //{ + // this.newlineReceiveMenuItem.Items.Add(new MenuItem() { Header = newlineSeparatorType }); + // this.newlineSendMenuItem.Items.Add(new MenuItem() { Header = newlineSeparatorType }); + //} + } + + private void MenuItem_IsCheckedChanged(object sender, System.Windows.RoutedEventArgs e) + { + Console.WriteLine("changed"); } }