SystemTrayMenu/UserInterface/Menu.xaml.cs
Peter Kirmeier 36cbd9f633 Keep using Window Resources for SVG images
App Resources are not loaded consistently by the Window designer
Also updating SVG image colors would not work without changing resource loading.
2022-12-04 22:03:57 +01:00

1253 lines
47 KiB
C#

// <copyright file="Menu.xaml.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace SystemTrayMenu.UserInterface
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using SystemTrayMenu.Business;
using SystemTrayMenu.DataClasses;
using SystemTrayMenu.DllImports;
using SystemTrayMenu.Properties;
using SystemTrayMenu.Utilities;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
/// <summary>
/// Logic of Menu window.
/// </summary>
public partial class Menu : Window
{
private const int CornerRadius = 10;
private static readonly RoutedEvent FadeInEvent = EventManager.RegisterRoutedEvent(
nameof(FadeIn), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
private static readonly RoutedEvent FadeOutEvent = EventManager.RegisterRoutedEvent(
nameof(FadeOut), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Menu));
#if TODO // SEARCH
public const string RowFilterShowAll = "[SortIndex] LIKE '%0%'";
#endif
private bool isFading;
private bool directionToRight;
private bool mouseDown;
private Point lastLocation;
#if TODO // SEARCH
private bool isSetSearchText;
private bool dgvHeightSet;
#endif
private bool isClosed = false; // TODO WPF Replace Forms wrapper
private DispatcherTimer timerUpdateIcons = new DispatcherTimer(DispatcherPriority.Render, Dispatcher.CurrentDispatcher);
internal Menu(string title, int level, MenuDataDirectoryState directoryState)
{
timerUpdateIcons.Tick += TimerUpdateIcons_Tick;
Closed += (_, _) =>
{
timerUpdateIcons.Stop();
isClosed = true; // TODO WPF Replace Forms wrapper
};
InitializeComponent();
Level = level;
if (Level == 0)
{
// Use Main Menu DPI for all further calculations
Scaling.CalculateFactorByDpi(this);
}
if (title.Length > MenuDefines.LengthMax)
{
title = $"{title[..MenuDefines.LengthMax]}...";
}
txtTitle.Text = title;
foreach (FrameworkElement control in
new List<FrameworkElement>()
{
buttonMenuAlwaysOpen,
buttonOpenFolder,
buttonSettings,
buttonRestart,
pictureBoxSearch,
pictureBoxMenuAlwaysOpen,
pictureBoxOpenFolder,
pictureBoxSettings,
pictureBoxRestart,
pictureBoxLoading,
})
{
control.Width = Scaling.Scale(control.Width);
control.Height = Scaling.Scale(control.Height);
}
labelTitle.FontSize = Scaling.ScaleFontByPoints(8.25F);
textBoxSearch.FontSize = Scaling.ScaleFontByPoints(8.25F);
labelStatus.FontSize = Scaling.ScaleFontByPoints(7F);
dgv.FontSize = Scaling.ScaleFontByPoints(9F);
MouseDown += Menu_MouseDown;
MouseUp += Menu_MouseUp;
MouseMove += Menu_MouseMove;
SolidColorBrush foreColor = new(Colors.Black);
SolidColorBrush backColor = AppColors.Background.ToSolidColorBrush();
SolidColorBrush backColorSearch = AppColors.SearchField.ToSolidColorBrush();
SolidColorBrush backgroundBorder = AppColors.BackgroundBorder.ToSolidColorBrush();
if (Config.IsDarkMode())
{
foreColor = new (Colors.White);
backColor = AppColors.DarkModeBackground.ToSolidColorBrush();
backColorSearch = AppColors.DarkModeSearchField.ToSolidColorBrush();
backgroundBorder = AppColors.DarkModeBackgroundBorder.ToSolidColorBrush();
Resources["ic_fluent_svgColor"] = AppColors.SolidColorBrushFromString(Settings.Default.ColorDarkModeIcons);
}
else
{
Resources["ic_fluent_svgColor"] = AppColors.SolidColorBrushFromString(Settings.Default.ColorIcons);
}
labelTitle.Foreground = foreColor;
textBoxSearch.Foreground = foreColor;
dgv.Foreground = foreColor;
labelStatus.Foreground = MenuDefines.ColorIcons.ToSolidColorBrush();
windowFrame.BorderBrush = backgroundBorder;
windowFrame.Background = backColor;
searchPanel.Background = backColorSearch;
panelLine.Background = AppColors.Icons.ToSolidColorBrush();
dgv.GotFocus += (_, _) => FocusTextBox();
#if TODO // Misc MouseEvents
dgv.MouseEnter += ControlsMouseEnter;
labelTitle.MouseEnter += ControlsMouseEnter;
textBoxSearch.MouseEnter += ControlsMouseEnter;
pictureBoxOpenFolder.MouseEnter += ControlsMouseEnter;
pictureBoxMenuAlwaysOpen.MouseEnter += ControlsMouseEnter;
pictureBoxSettings.MouseEnter += ControlsMouseEnter;
pictureBoxRestart.MouseEnter += ControlsMouseEnter;
pictureBoxSearch.MouseEnter += ControlsMouseEnter;
tableLayoutPanelMenu.MouseEnter += ControlsMouseEnter;
tableLayoutPanelDgvAndScrollbar.MouseEnter += ControlsMouseEnter;
tableLayoutPanelBottom.MouseEnter += ControlsMouseEnter;
labelStatus.MouseEnter += ControlsMouseEnter;
void ControlsMouseEnter(object sender, EventArgs e)
{
MouseEnter?.Invoke();
}
dgv.MouseLeave += ControlsMouseLeave;
labelTitle.MouseLeave += ControlsMouseLeave;
textBoxSearch.MouseLeave += ControlsMouseLeave;
pictureBoxMenuAlwaysOpen.MouseLeave += ControlsMouseLeave;
pictureBoxOpenFolder.MouseLeave += ControlsMouseLeave;
pictureBoxSettings.MouseLeave += ControlsMouseLeave;
pictureBoxRestart.MouseLeave += ControlsMouseLeave;
pictureBoxSearch.MouseLeave += ControlsMouseLeave;
tableLayoutPanelMenu.MouseLeave += ControlsMouseLeave;
tableLayoutPanelDgvAndScrollbar.MouseLeave += ControlsMouseLeave;
tableLayoutPanelBottom.MouseLeave += ControlsMouseLeave;
labelStatus.MouseLeave += ControlsMouseLeave;
void ControlsMouseLeave(object sender, EventArgs e)
{
MouseLeave?.Invoke();
}
#endif
#if TODO // TOUCH
bool isTouchEnabled = NativeMethods.IsTouchEnabled();
if ((isTouchEnabled && Properties.Settings.Default.DragDropItemsEnabledTouch) ||
(!isTouchEnabled && Properties.Settings.Default.DragDropItemsEnabled))
{
AllowDrop = true;
DragEnter += DragDropHelper.DragEnter;
DragDrop += DragDropHelper.DragDrop;
}
#endif
Loaded += (sender, e) =>
{
NativeMethods.HideFromAltTab(this);
RaiseEvent(new(routedEvent: FadeInEvent));
};
Closed += (sender, e) =>
{
foreach (ListViewItemData item in dgv.Items)
{
item.data.SubMenu?.Close();
}
};
}
internal event Action? MenuScrolled;
#if TODO // Misc MouseEvents
internal new event Action MouseEnter;
internal new event Action MouseLeave;
#endif
internal event Action<Menu, Key, ModifierKeys>? CmdKeyProcessed;
#if TODO // Misc MouseEvents and TOUCH
internal event EventHandler<KeyPressEventArgs> KeyPressCheck;
#endif
internal event Action? SearchTextChanging;
#if TODO // SEARCH
internal event EventHandler<bool> SearchTextChanged;
#endif
internal event Action? UserDragsMenu;
internal event Action<ListView, int>? CellMouseEnter;
internal event Action<ListView, int>? CellMouseLeave;
internal event Action<ListView, int, MouseButtonEventArgs>? CellMouseDown;
internal event Action<ListView, int, MouseButtonEventArgs>? CellMouseUp;
internal event Action<ListView, int, MouseButtonEventArgs>? CellMouseClick;
internal event RoutedEventHandler FadeIn
{
add { AddHandler(FadeInEvent, value); }
remove { RemoveHandler(FadeInEvent, value); }
}
internal event RoutedEventHandler FadeOut
{
add { AddHandler(FadeOutEvent, value); }
remove { RemoveHandler(FadeOutEvent, value); }
}
internal enum StartLocation
{
Predecessor,
BottomLeft,
BottomRight,
TopRight,
}
public bool IsLoadingMenu { get; internal set; } // TODO State out of window
public bool IsDisposed => isClosed; // TODO WPF Replace Forms wrapper
public bool Disposing => isClosed; // TODO WPF Replace Forms wrapper
public System.Drawing.Point Location => new System.Drawing.Point((int)Left, (int)Top); // TODO WPF Replace Forms wrapper)
internal int Level { get; set; }
internal string? FolderPath { get; set; }
internal bool IsUsable => Visibility == Visibility.Visible && !isFading && !IsDisposed && !Disposing;
#if TODO // TOUCH
internal bool ScrollbarVisible { get; private set; }
private ListView tableLayoutPanelDgvAndScrollbar => dgv; // TODO WPF Remove and replace with dgv
#endif
internal void ResetSearchText()
{
textBoxSearch.Text = string.Empty;
if (dgv.Items.Count > 0)
{
dgv.ScrollIntoView(dgv.Items[0]);
}
}
internal void RefreshSearchText()
{
TextBoxSearch_TextChanged();
if (dgv.Items.Count > 0)
{
dgv.ScrollIntoView(dgv.Items[0]);
}
}
internal void FocusTextBox()
{
#if TODO // SEARCH
if (isSetSearchText)
{
isSetSearchText = false;
textBoxSearch.SelectAll();
textBoxSearch.Focus();
textBoxSearch.SelectionStart = textBoxSearch.Text.Length;
textBoxSearch.SelectionLength = 0;
}
else
{
textBoxSearch.SelectAll();
textBoxSearch.Focus();
}
#endif
}
internal void SetBehavior(MenuDataDirectoryState state)
{
if (!Config.ShowDirectoryTitleAtTop)
{
txtTitle.Visibility = Visibility.Hidden;
}
if (!Config.ShowSearchBar)
{
searchPanel.Visibility = Visibility.Collapsed;
}
if (!(Config.ShowCountOfElementsBelow || state != MenuDataDirectoryState.Valid))
{
// Hide status when neither config is set nor an error message must be shown
labelStatus.Visibility = Visibility.Collapsed;
}
if (!Config.ShowFunctionKeyOpenFolder)
{
buttonOpenFolder.Visibility = Visibility.Collapsed;
}
if (!Config.ShowFunctionKeyPinMenu)
{
buttonMenuAlwaysOpen.Visibility = Visibility.Collapsed;
}
if (!Config.ShowFunctionKeySettings)
{
buttonSettings.Visibility = Visibility.Collapsed;
}
if (!Config.ShowFunctionKeyRestart)
{
buttonRestart.Visibility = Visibility.Collapsed;
}
if (Level == 0)
{
// Main Menu
textBoxSearch.TextChanged += (_, _) => TextBoxSearch_TextChanged();
}
else
{
// SubMenu
buttonSettings.Visibility = Visibility.Collapsed;
buttonRestart.Visibility = Visibility.Collapsed;
switch (state)
{
case MenuDataDirectoryState.Valid:
textBoxSearch.TextChanged += (_, _) => TextBoxSearch_TextChanged();
buttonMenuAlwaysOpen.Visibility = Visibility.Collapsed;
break;
case MenuDataDirectoryState.Empty:
searchPanel.Visibility = Visibility.Collapsed;
labelStatus.Content = Translator.GetText("Directory empty");
buttonMenuAlwaysOpen.Visibility = Visibility.Collapsed;
break;
case MenuDataDirectoryState.NoAccess:
searchPanel.Visibility = Visibility.Collapsed;
labelStatus.Content = Translator.GetText("Directory inaccessible");
buttonMenuAlwaysOpen.Visibility = Visibility.Collapsed;
break;
case MenuDataDirectoryState.Undefined:
IsLoadingMenu = true;
labelStatus.Content = Translator.GetText("loading");
buttonMenuAlwaysOpen.Visibility = Visibility.Visible;
buttonOpenFolder.Visibility = Visibility.Collapsed;
// Todo: use embedded resources that we can assign image in XAML already
pictureBoxLoading.Source = SystemTrayMenu.Resources.StaticResources.LoadingIcon.ToImageSource();
pictureBoxLoading.Visibility = Visibility.Visible;
break;
default:
break;
}
}
}
internal string GetSearchText()
{
return textBoxSearch.Text;
}
internal void SetSearchText(string userSearchText)
{
if (!string.IsNullOrEmpty(userSearchText))
{
textBoxSearch.Text = userSearchText + "*";
#if TODO // SEARCH
isSetSearchText = true;
#endif
}
}
internal bool IsMouseOn()
{
Point mousePos = NativeMethods.Screen.CursorPosition;
bool isMouseOn = Visibility == Visibility.Visible &&
mousePos.X >= 0 && mousePos.X < Width &&
mousePos.Y >= 0 && mousePos.Y < Height;
return isMouseOn;
}
internal ListView? GetDataGridView() // TODO WPF Replace Forms wrapper
{
return dgv;
}
internal void ShowWithFadeOrTransparent(bool formActiveFormIsMenu)
{
if (formActiveFormIsMenu)
{
ShowWithFade();
}
else
{
ShowTransparent();
}
}
internal void ShowWithFade() => Fading_Show(false);
internal void ShowTransparent() => Fading_Show(true);
internal void Fading_Show(bool transparency)
{
timerUpdateIcons.Start();
if (Level == 0)
{
Activate();
}
else
{
ShowActivated = false;
}
Opacity = 0D;
Show();
if (Settings.Default.UseFading)
{
isFading = true;
if (transparency)
{
// TODO: FADING: Instead setting of opacity 100% only go up to 80% (Temporarily go to 100% as well)
RaiseEvent(new(routedEvent: FadeInEvent));
}
else
{
RaiseEvent(new(routedEvent: FadeInEvent));
}
}
else
{
Opacity = transparency ? 0.80D : 1D;
FadeIn_Completed(this, new());
}
}
internal void HideWithFade()
{
if (Settings.Default.UseFading)
{
isFading = true;
// TODO: FADING: Instead starting at opacity 100% it should start with 80% due to transparency setting
RaiseEvent(new(routedEvent: FadeOutEvent));
}
else
{
FadeOut_Completed(this, new());
}
}
internal void TimerUpdateIconsStart()
{
timerUpdateIcons.Start();
}
/// <summary>
/// Update the position and size of the menu.
/// </summary>
/// <param name="bounds">Screen coordinates where the menu is allowed to be drawn in.</param>
/// <param name="menuPredecessor">Predecessor menu (when available).</param>
/// <param name="startLocation">Defines where the first menu is drawn (when no predecessor is set).</param>
/// <param name="isCustomLocationOutsideOfScreen">isCustomLocationOutsideOfScreen.</param>
internal void AdjustSizeAndLocation(
Rect bounds,
Menu menuPredecessor,
StartLocation startLocation,
bool isCustomLocationOutsideOfScreen)
{
// Update the height and width
AdjustDataGridViewHeight(menuPredecessor, bounds.Height);
AdjustDataGridViewWidth();
bool useCustomLocation = Properties.Settings.Default.UseCustomLocation || lastLocation.X > 0;
bool changeDirectionWhenOutOfBounds = true;
if (menuPredecessor != null)
{
// Ignore start as we use predecessor
startLocation = StartLocation.Predecessor;
}
else if (useCustomLocation && !isCustomLocationOutsideOfScreen)
{
// Do not adjust location again because Cursor.Postion changed
if (Tag != null)
{
return;
}
// Use this menu as predecessor and overwrite location with CustomLocation
menuPredecessor = this;
Tag = new RowData();
Left = Properties.Settings.Default.CustomLocationX;
Top = Properties.Settings.Default.CustomLocationY;
directionToRight = true;
startLocation = StartLocation.Predecessor;
changeDirectionWhenOutOfBounds = false;
}
else if (Properties.Settings.Default.AppearAtMouseLocation)
{
// Do not adjust location again because Cursor.Postion changed
if (Tag != null)
{
return;
}
// Use this menu as predecessor and overwrite location with Cursor.Postion
menuPredecessor = this;
Tag = new RowData();
var position = Mouse.GetPosition(this);
Left = position.X;
Top = position.Y - labelTitle.Height;
directionToRight = true;
startLocation = StartLocation.Predecessor;
changeDirectionWhenOutOfBounds = false;
}
if (IsLoaded)
{
AdjustSizeAndLocationInternal();
}
else
{
// Layout cannot be calculated during loading, postpone this event
// TODO: Make sure lampa capture is registered only once
Loaded += (_, _) => AdjustSizeAndLocationInternal();
}
void AdjustSizeAndLocationInternal()
{
// Calculate X position
double x;
switch (startLocation)
{
case StartLocation.Predecessor:
double scaling = Math.Round(Scaling.Factor, 0, MidpointRounding.AwayFromZero);
directionToRight = menuPredecessor!.directionToRight; // try keeping same direction
if (directionToRight)
{
x = menuPredecessor.Location.X + menuPredecessor.Width - scaling;
if (changeDirectionWhenOutOfBounds &&
bounds.X + bounds.Width <= x + Width - scaling)
{
x = menuPredecessor.Location.X - Width + scaling;
if (x < bounds.X &&
menuPredecessor.Location.X + menuPredecessor.Width < bounds.X + bounds.Width &&
bounds.X + (bounds.Width / 2) > menuPredecessor.Location.X + (Width / 2))
{
x = bounds.X + bounds.Width - Width + scaling;
}
else
{
if (x < bounds.X)
{
x = bounds.X;
}
directionToRight = !directionToRight;
}
}
}
else
{
x = menuPredecessor.Location.X - Width + scaling;
if (changeDirectionWhenOutOfBounds &&
x < bounds.X)
{
x = menuPredecessor.Location.X + menuPredecessor.Width - scaling;
if (x + Width > bounds.X + bounds.Width &&
menuPredecessor.Location.X > bounds.X &&
bounds.X + (bounds.Width / 2) < menuPredecessor.Location.X + (Width / 2))
{
x = bounds.X;
}
else
{
if (x + Width > bounds.X + bounds.Width)
{
x = bounds.X + bounds.Width - Width + scaling;
}
directionToRight = !directionToRight;
}
}
}
break;
case StartLocation.BottomLeft:
x = bounds.X;
directionToRight = true;
break;
case StartLocation.TopRight:
case StartLocation.BottomRight:
default:
x = bounds.Width - Width;
directionToRight = false;
break;
}
// X position for click, remove width of this menu as it is used as predecessor
if (menuPredecessor == this && directionToRight)
{
x -= Width;
}
if (Level != 0 &&
!Properties.Settings.Default.AppearNextToPreviousMenu &&
menuPredecessor != null && menuPredecessor.Width > Properties.Settings.Default.OverlappingOffsetPixels)
{
if (directionToRight)
{
x = x - menuPredecessor.Width + Properties.Settings.Default.OverlappingOffsetPixels;
}
else
{
x = x + menuPredecessor.Width - Properties.Settings.Default.OverlappingOffsetPixels;
}
}
// Calculate Y position
double y;
switch (startLocation)
{
case StartLocation.Predecessor:
RowData trigger = (RowData)Tag;
ListView dgv = menuPredecessor!.GetDataGridView() !;
// Set position on same height as the selected row from predecessor
y = menuPredecessor.Location.Y;
if (dgv.Items.Count > trigger.RowIndex)
{
// When item is not found, it might be invalidated due to resizing or moving
// After updating the layout the location should be available again.
// It also makes sure all height and location information is up to date
menuPredecessor.UpdateLayout();
// When scrolled, we have to reduce the index number as we calculate based on visual tree
int startIndex = 0;
double offset = 0D;
ScrollViewer? scrollViewer = (VisualTreeHelper.GetChild(dgv, 0) as Decorator)?.Child as ScrollViewer;
if (scrollViewer != null)
{
startIndex = (int)scrollViewer.VerticalOffset;
if (trigger.RowIndex < startIndex)
{
// calculate position above starting point
for (int i = trigger.RowIndex; i < startIndex; i++)
{
ListViewItem? item = dgv.FindVisualChildOfType<ListViewItem>(i);
if (item != null)
{
offset -= item.ActualHeight;
}
}
}
}
if (startIndex < trigger.RowIndex)
{
// calculate position below starting point
for (int i = startIndex; i < trigger.RowIndex; i++)
{
ListViewItem? item = dgv.FindVisualChildOfType<ListViewItem>(i);
if (item != null)
{
offset += item.ActualHeight;
}
}
}
if (offset < 0)
{
// Do not allow to show window higher than previous window
offset = 0;
}
else
{
double offsetList = menuPredecessor.GetRelativeChildPositionTo(dgv).Y;
offsetList += dgv.ActualHeight;
if (offsetList < offset)
{
// Do not allow to show window below last entry position of list
offset = offsetList;
}
}
y += (int)offset;
}
if (searchPanel.Visibility == Visibility.Collapsed)
{
y += menuPredecessor.searchPanel.ActualHeight;
}
// Move vertically when out of bounds
if (bounds.Y + bounds.Height < y + Height)
{
y = bounds.Y + bounds.Height - Height;
}
else if (y < bounds.Y)
{
y = bounds.Y;
}
break;
case StartLocation.TopRight:
y = bounds.Y;
break;
case StartLocation.BottomLeft:
case StartLocation.BottomRight:
default:
y = bounds.Height - Height;
break;
}
// Update position
Left = x;
Top = y;
if (Properties.Settings.Default.RoundCorners)
{
windowFrame.CornerRadius = new CornerRadius(CornerRadius);
}
// Keep its size when once created.
SizeToContent = SizeToContent.Manual;
}
}
internal void ResetHeight()
{
if (IsLoaded)
{
// TODO: WPF Check if this "reset" works
SizeToContent = SizeToContent.WidthAndHeight;
UpdateLayout();
SizeToContent = SizeToContent.Manual;
#if TODO // SEARCH
dgvHeightSet = false;
#endif
}
}
internal void SetCounts(int foldersCount, int filesCount)
{
int filesAndFoldersCount = foldersCount + filesCount;
string elements = filesAndFoldersCount == 1 ? "element" : "elements";
labelStatus.Content = $"{filesAndFoldersCount} {Translator.GetText(elements)}";
}
private void FadeIn_Completed(object sender, EventArgs e)
{
isFading = false;
}
private void FadeOut_Completed(object sender, EventArgs e)
{
isFading = false;
Hide();
}
private void HandlePreviewKeyDown(object sender, KeyEventArgs e)
{
searchPanel.Visibility = Visibility.Visible;
ModifierKeys modifiers = Keyboard.Modifiers;
switch (e.Key)
{
case Key.F4:
if (modifiers != ModifierKeys.Alt)
{
return;
}
break;
case Key.F:
if (modifiers != ModifierKeys.Control)
{
return;
}
break;
case Key.Tab:
if ((modifiers != ModifierKeys.Shift) && (modifiers != ModifierKeys.None))
{
return;
}
break;
case Key.Enter:
case Key.Home:
case Key.End:
case Key.Up:
case Key.Down:
case Key.Left:
case Key.Right:
case Key.Escape:
case Key.Apps:
if (modifiers != ModifierKeys.None)
{
return;
}
break;
default:
return;
}
CmdKeyProcessed?.Invoke(this, e.Key, modifiers);
e.Handled = true;
}
private void AdjustDataGridViewHeight(Menu menuPredecessor, double screenHeightMax)
{
double factor = Properties.Settings.Default.RowHeighteInPercentage / 100f;
if (NativeMethods.IsTouchEnabled())
{
factor = Properties.Settings.Default.RowHeighteInPercentageTouch / 100f;
}
if (menuPredecessor == null)
{
if (dgv.Tag == null && dgv.Items.Count > 0)
{
// dgv.AutoResizeRows(); slightly incorrect depending on dpi
// 100% = 20 instead 21
// 125% = 23 instead 27, 150% = 28 instead 32
// 175% = 33 instead 37, 200% = 35 instead 42
// #418 use 21 as default and scale it manually
double rowHeightDefault = 21.24f * Scaling.FactorByDpi;
Resources["RowHeight"] = (double)(int)((rowHeightDefault * factor * Scaling.Factor) + 0.5);
dgv.Tag = true;
}
}
else
{
// Take over the height from predecessor menu
Resources["RowHeight"] = menuPredecessor.Resources["RowHeight"];
dgv.Tag = true;
}
#if TODO // SEARCH
if (!dgvHeightSet && dgvHeightByItems > 0 && dgvHeightMax > 0)
{
#endif
double heightMaxByOptions = Scaling.Factor * Scaling.FactorByDpi *
450f * (Properties.Settings.Default.HeightMaxInPercent / 100f);
MaxHeight = Math.Min(screenHeightMax, heightMaxByOptions);
#if TODO // SEARCH
dgvHeightSet = true;
}
#endif
#if TODO // SEARCH and TOUCH
if (dgvHeightByItems > dgvHeightMax)
{
ScrollbarVisible = true;
}
else
{
ScrollbarVisible = false;
}
#endif
}
private void AdjustDataGridViewWidth()
{
if (!string.IsNullOrEmpty(textBoxSearch.Text))
{
return;
}
double factorIconSizeInPercent = Properties.Settings.Default.IconSizeInPercent / 100f;
// IcoWidth 100% = 21px, 175% is 33, +3+2 is padding from ColumnIcon
double icoWidth = (16 * Scaling.FactorByDpi) + 5;
Resources["ColumnIconWidth"] = (double)(int)((icoWidth * factorIconSizeInPercent * Scaling.Factor) + 0.5);
double renderedMaxWidth = 0D;
foreach (ListViewItemData item in dgv.Items)
{
double renderedWidth = new FormattedText(
item.ColumnText,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(dgv.FontFamily, dgv.FontStyle, dgv.FontWeight, dgv.FontStretch),
dgv.FontSize,
dgv.Foreground,
VisualTreeHelper.GetDpi(this).PixelsPerDip).Width;
if (renderedWidth > renderedMaxWidth)
{
renderedMaxWidth = renderedWidth;
}
}
Resources["ColumnTextWidth"] = Math.Min(
renderedMaxWidth,
(double)(Scaling.Factor * Scaling.FactorByDpi * 400f * (Properties.Settings.Default.WidthMaxInPercent / 100f)));
}
private void HandleScrollChanged(object sender, ScrollChangedEventArgs e)
{
if (IsLoaded)
{
MenuScrolled?.Invoke();
}
}
#if TODO // Misc MouseEvents and TOUCH
private void TextBoxSearch_KeyPress(object sender, KeyPressEventArgs e)
{
KeyPressCheck?.Invoke(sender, e);
}
#endif
private void TextBoxSearch_TextChanged()
{
SearchTextChanging?.Invoke();
string? userPattern = textBoxSearch.Text?.Replace("%", " ").Replace("*", " ").ToLower();
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(dgv.ItemsSource);
if (string.IsNullOrEmpty(userPattern))
{
view.Filter = null;
}
else
{
// Instead implementing in-string wildcards, simply split into multiple search patters
view.Filter = (object item) =>
{
// Look for each space separated string if it is part of an entries text (case insensitive)
ListViewItemData row = (ListViewItemData)item;
foreach (string pattern in userPattern.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
{
if (!row.ColumnText.ToLower().Contains(pattern))
{
return false;
}
}
return true;
};
}
#if TODO // SEARCH
DataTable data = (DataTable)dgv.DataSource;
string columnSortIndex = "SortIndex";
if (string.IsNullOrEmpty(userPattern))
{
foreach (DataRow row in data.Rows)
{
RowData rowData = (RowData)row[2];
if (rowData.IsAddionalItem && Properties.Settings.Default.ShowOnlyAsSearchResult)
{
row[columnSortIndex] = 99;
}
else
{
row[columnSortIndex] = 0;
}
}
data.DefaultView.Sort = string.Empty;
data.AcceptChanges();
}
else
{
foreach (DataRow row in data.Rows)
{
if (row[1].ToString().StartsWith(
searchString,
StringComparison.InvariantCultureIgnoreCase))
{
row[columnSortIndex] = 0;
}
else
{
row[columnSortIndex] = 1;
}
}
data.DefaultView.Sort = columnSortIndex;
}
int foldersCount = 0;
int filesCount = 0;
bool anyIconNotUpdated = false;
foreach (DataGridViewRow row in dgv.Rows)
{
RowData rowData = (RowData)row.Cells[2].Value;
if (!string.IsNullOrEmpty(userPattern) ||
!(rowData.IsAddionalItem && Properties.Settings.Default.ShowOnlyAsSearchResult))
{
rowData.RowIndex = row.Index;
if (rowData.ContainsMenu)
{
foldersCount++;
}
else
{
filesCount++;
}
if (rowData.IconLoading)
{
anyIconNotUpdated = true;
}
}
}
SetCounts(foldersCount, filesCount);
SearchTextChanged.Invoke(this, string.IsNullOrEmpty(userPattern));
if (anyIconNotUpdated)
{
timerUpdateIcons.Start();
}
if (dgv.Rows.Count > 0)
{
dgv.FirstDisplayedScrollingRowIndex = 0;
}
#endif
}
#if TODO // Misc MouseEvents and BorderColors
private void PictureBoxOpenFolder_Paint(object sender, PaintEventArgs e)
{
PictureBox pictureBox = (PictureBox)sender;
if (pictureBox.Tag != null && (bool)pictureBox.Tag)
{
Rectangle rowBounds = new(0, 0, pictureBox.Width, pictureBox.Height);
ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid);
}
}
#endif
private void PictureBoxOpenFolder_Click(object sender, RoutedEventArgs e)
{
Menus.OpenFolder(FolderPath);
}
#if TODO // BorderColors
private void PictureBoxMenuAlwaysOpen_Paint(object sender, PaintEventArgs e)
{
PictureBox pictureBox = (PictureBox)sender;
if (pictureBox.Tag != null && (bool)pictureBox.Tag)
{
Rectangle rowBounds = new(0, 0, pictureBox.Width, pictureBox.Height);
ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid);
}
}
#endif
private void PictureBoxMenuAlwaysOpen_Click(object sender, RoutedEventArgs e)
{
if (Config.AlwaysOpenByPin = !Config.AlwaysOpenByPin)
{
pictureBoxMenuAlwaysOpen.Source = (DrawingImage)Resources["ic_fluent_pin_48_filledDrawingImage"];
}
else
{
pictureBoxMenuAlwaysOpen.Source = (DrawingImage)Resources["ic_fluent_pin_48_regularDrawingImage"];
}
}
#if TODO // BorderColors
private void PictureBoxSettings_Paint(object sender, PaintEventArgs e)
{
PictureBox pictureBox = (PictureBox)sender;
if (pictureBox.Tag != null && (bool)pictureBox.Tag)
{
Rectangle rowBounds = new(0, 0, pictureBox.Width, pictureBox.Height);
ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid);
}
}
#endif
private void PictureBoxSettings_MouseClick(object sender, RoutedEventArgs e)
{
SettingsWindow.ShowSingleInstance();
}
#if TODO // BorderColors
private void PictureBoxRestart_Paint(object sender, PaintEventArgs e)
{
PictureBox pictureBox = (PictureBox)sender;
if (pictureBox.Tag != null && (bool)pictureBox.Tag)
{
Rectangle rowBounds = new(0, 0, pictureBox.Width, pictureBox.Height);
ControlPaint.DrawBorder(e.Graphics, rowBounds, MenuDefines.ColorSelectedItemBorder, ButtonBorderStyle.Solid);
}
}
#endif
private void PictureBoxRestart_MouseClick(object sender, RoutedEventArgs e)
{
AppRestart.ByMenuButton();
}
private void TimerUpdateIcons_Tick(object? sender, EventArgs e)
{
int iconsToUpdate = 0;
foreach (Menu.ListViewItemData row in dgv.Items)
{
RowData rowData = row.data;
rowData.RowIndex = dgv.Items.IndexOf(row);
if (rowData.IconLoading)
{
iconsToUpdate++;
row.ColumnIcon = rowData.ReadIcon(false).ToImageSource();
}
}
if (iconsToUpdate < 1)
{
timerUpdateIcons.Stop();
}
else
{
((CollectionView)CollectionViewSource.GetDefaultView(dgv.ItemsSource)).Refresh();
}
}
private void Menu_MouseDown(object sender, MouseButtonEventArgs e)
{
if (Level == 0)
{
mouseDown = true;
lastLocation = NativeMethods.Screen.CursorPosition;
UserDragsMenu?.Invoke();
}
}
private void Menu_MouseMove(object sender, MouseEventArgs e)
{
if (mouseDown)
{
Point mousePos = NativeMethods.Screen.CursorPosition;
Left = Left + mousePos.X - lastLocation.X;
Top = Top + mousePos.Y - lastLocation.Y;
lastLocation = mousePos;
Properties.Settings.Default.CustomLocationX = (int)Left;
Properties.Settings.Default.CustomLocationY = (int)Top;
}
}
private void Menu_MouseUp(object sender, MouseButtonEventArgs e)
{
mouseDown = false;
if (Properties.Settings.Default.UseCustomLocation)
{
if (!SettingsWindow.IsOpen())
{
Properties.Settings.Default.Save();
}
}
}
private void ListViewItem_MouseEnter(object sender, MouseEventArgs e)
{
CellMouseEnter?.Invoke(dgv, dgv.IndexOfSenderItem((ListViewItem)sender));
}
private void ListViewItem_MouseLeave(object sender, MouseEventArgs e)
{
CellMouseLeave?.Invoke(dgv, dgv.IndexOfSenderItem((ListViewItem)sender));
}
private void ListViewItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
CellMouseDown?.Invoke(dgv, dgv.IndexOfSenderItem((ListViewItem)sender), e);
}
private void ListViewItem_MouseUp(object sender, MouseButtonEventArgs e)
{
CellMouseUp?.Invoke(dgv, dgv.IndexOfSenderItem((ListViewItem)sender), e);
}
private void ListViewxItem_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
CellMouseClick?.Invoke(dgv, dgv.IndexOfSenderItem((ListViewItem)sender), e);
}
/// <summary>
/// Type for ListView items.
/// </summary>
internal class ListViewItemData
{
public ListViewItemData(ImageSource columnIcon, string columnText, RowData rowData, int sortIndex)
{
ColumnIcon = columnIcon;
ColumnText = columnText;
data = rowData;
SortIndex = sortIndex;
}
public ImageSource ColumnIcon { get; set; }
public string ColumnText { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Benennungsstile", Justification = "Temporarily retained for compatibility reasons")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Temporarily retained for compatibility reasons")]
public RowData data { get; set; }
public int SortIndex { get; set; }
}
private void textBoxSearch_TextInput(object sender, TextCompositionEventArgs e)
{
// TODO WPF
}
}
}