Add patterned rename and Move to, and keep settings, selection, and queue speed from resetting.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -68,7 +68,14 @@
|
||||
<TextBlock Text="Extension" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||
<CheckBox Content="Change extension" IsChecked="{Binding ChangeExtension}" Margin="0,0,0,8"/>
|
||||
<TextBox Text="{Binding NewExtension, UpdateSourceTrigger=PropertyChanged}"
|
||||
IsEnabled="{Binding ChangeExtension}"/>
|
||||
IsEnabled="{Binding ChangeExtension}" Margin="0,0,0,16"/>
|
||||
|
||||
<TextBlock Text="Name pattern" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||
<ComboBox IsEditable="True" ItemsSource="{Binding PatternChoices}"
|
||||
Text="{Binding NamePattern, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,8"/>
|
||||
<Button Content="Save pattern" Height="28" Command="{Binding SavePatternCommand}" Margin="0,0,0,8"/>
|
||||
<TextBlock TextWrapping="Wrap" Foreground="{DynamicResource FgMuted}" FontSize="12"
|
||||
Text="Optional. Replaces the current name. Placeholders: {Artist} {Title} {Album} {Track} {Year} {Genre} {CreatedDate} {TakenDate} {ModifiedDate} {Width} {Height} {Name} {Extension} {Parent} {Project} {Counter}. Dates accept {TakenDate:yyyyMMdd}."/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
<ListView Grid.Column="2" ItemsSource="{Binding Rows}"
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
<MenuItem Header="Rename" Click="OnCtxRename"/>
|
||||
<MenuItem Header="Batch rename…" Click="OnBatchRename"
|
||||
Visibility="{Binding ShowBatchRename, Converter={StaticResource BoolVis}}"/>
|
||||
<MenuItem Header="Tags…" Click="OnTags"
|
||||
Visibility="{Binding ShowTags, Converter={StaticResource BoolVis}}"/>
|
||||
<MenuItem Header="Move to…" Click="OnMoveTo"
|
||||
Visibility="{Binding ShowMoveTo, Converter={StaticResource BoolVis}}"/>
|
||||
<MenuItem Header="Run profile" Tag="RunProfileMenu"
|
||||
IsEnabled="{Binding ShowRunProfile}">
|
||||
<MenuItem Header="No profiles yet" IsEnabled="False"/>
|
||||
@@ -149,6 +153,10 @@
|
||||
</MenuItem>
|
||||
<MenuItem Header="_File Operations">
|
||||
<MenuItem Header="_Batch rename…" Click="OnBatchRename"/>
|
||||
<MenuItem Header="_Tags…" Click="OnTags"
|
||||
IsEnabled="{Binding ShowTags}"/>
|
||||
<MenuItem Header="_Move to…" Click="OnMoveTo"
|
||||
IsEnabled="{Binding ShowMoveTo}"/>
|
||||
<MenuItem Header="_Undo last rename batch" Command="{Binding UndoRenameBatchCommand}"
|
||||
IsEnabled="{Binding CanUndoRenameBatch}"/>
|
||||
<Separator/>
|
||||
|
||||
@@ -22,6 +22,10 @@ public partial class MainWindow : Window
|
||||
private bool _dragPending;
|
||||
private MouseButton _dragButton;
|
||||
private FolderItemViewModel? _dragItem;
|
||||
private ListView? _dragList;
|
||||
private FolderItemViewModel[] _dragSelection = [];
|
||||
private bool _dragFromMultiSelect;
|
||||
private bool _syncingSelection;
|
||||
private readonly ListMarquee _marquee = new();
|
||||
private bool _suppressItemContextMenu;
|
||||
private bool _incomingRightDrag;
|
||||
@@ -577,12 +581,17 @@ public partial class MainWindow : Window
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (sender is not ListView { IsVisible: true } list)
|
||||
if (_syncingSelection || sender is not ListView { IsVisible: true } list)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ActivatePaneFromList(list);
|
||||
if (_dragFromMultiSelect && list == _dragList && _dragSelection.Length > 1)
|
||||
{
|
||||
RestoreListSelection(list, _dragSelection);
|
||||
}
|
||||
|
||||
if (_clickRenameItem is not null && !list.SelectedItems.Contains(_clickRenameItem))
|
||||
{
|
||||
CancelClickRename();
|
||||
@@ -597,6 +606,26 @@ public partial class MainWindow : Window
|
||||
Vm.RefreshCloudActions();
|
||||
}
|
||||
|
||||
private void RestoreListSelection(ListView list, IReadOnlyList<FolderItemViewModel> items)
|
||||
{
|
||||
_syncingSelection = true;
|
||||
try
|
||||
{
|
||||
list.SelectedItems.Clear();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (list.Items.Contains(item))
|
||||
{
|
||||
list.SelectedItems.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_syncingSelection = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPaneFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ListView list)
|
||||
@@ -620,11 +649,35 @@ public partial class MainWindow : Window
|
||||
|
||||
private void OnListMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (IsInsideInlineRenameBox(e.OriginalSource as DependencyObject))
|
||||
{
|
||||
_dragPending = false;
|
||||
_marquee.Disarm();
|
||||
CancelClickRename();
|
||||
return;
|
||||
}
|
||||
|
||||
_suppressItemContextMenu = false;
|
||||
_dragStart = e.GetPosition(null);
|
||||
_dragPending = true;
|
||||
_dragButton = e.ChangedButton;
|
||||
_dragList = sender as ListView;
|
||||
_dragItem = HitTestFolderItem(sender as DependencyObject, e.GetPosition((IInputElement)sender));
|
||||
_dragSelection = _dragList is not null
|
||||
? _dragList.SelectedItems.OfType<FolderItemViewModel>().ToArray()
|
||||
: [];
|
||||
var additive = (Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Shift)) != 0;
|
||||
_dragFromMultiSelect = !additive
|
||||
&& _dragItem is not null
|
||||
&& _dragSelection.Length > 1
|
||||
&& Array.IndexOf(_dragSelection, _dragItem) >= 0;
|
||||
if (_dragFromMultiSelect && e.ChangedButton == MouseButton.Left && _dragList is not null)
|
||||
{
|
||||
e.Handled = true;
|
||||
_dragList.Focus();
|
||||
_dragList.CaptureMouse();
|
||||
}
|
||||
|
||||
if (sender is ListView list
|
||||
&& _dragItem is null
|
||||
&& ListMarquee.IsBackground(e.OriginalSource as DependencyObject))
|
||||
@@ -800,6 +853,13 @@ public partial class MainWindow : Window
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsInsideInlineRenameBox(e.OriginalSource as DependencyObject)
|
||||
|| _dragItem is { IsRenaming: true })
|
||||
{
|
||||
_dragPending = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = e.GetPosition(null);
|
||||
if (Math.Abs(pos.X - _dragStart.X) < SystemParameters.MinimumHorizontalDragDistance
|
||||
&& Math.Abs(pos.Y - _dragStart.Y) < SystemParameters.MinimumVerticalDragDistance)
|
||||
@@ -809,18 +869,13 @@ public partial class MainWindow : Window
|
||||
|
||||
_dragPending = false;
|
||||
CancelClickRename();
|
||||
var selected = Vm.ActivePane.SelectedItems.Select(i => i.FullPath).ToList();
|
||||
IReadOnlyList<string> paths;
|
||||
if (_dragItem is not null && (selected.Count == 0
|
||||
|| selected.TrueForAll(p => !p.Equals(_dragItem.FullPath, StringComparison.OrdinalIgnoreCase))))
|
||||
if (_dragList is { IsMouseCaptured: true })
|
||||
{
|
||||
paths = [_dragItem.FullPath];
|
||||
}
|
||||
else
|
||||
{
|
||||
paths = selected;
|
||||
_dragList.ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
var paths = DragPaths();
|
||||
_dragFromMultiSelect = false;
|
||||
if (paths.Count == 0)
|
||||
{
|
||||
return;
|
||||
@@ -878,11 +933,42 @@ public partial class MainWindow : Window
|
||||
}
|
||||
|
||||
base.OnPreviewMouseUp(e);
|
||||
if (e.ChangedButton == _dragButton)
|
||||
if (e.ChangedButton != _dragButton)
|
||||
{
|
||||
_dragPending = false;
|
||||
_marquee.Disarm();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dragFromMultiSelect && _dragPending && _dragList is not null && _dragItem is not null)
|
||||
{
|
||||
_dragFromMultiSelect = false;
|
||||
_dragList.SelectedItems.Clear();
|
||||
_dragList.SelectedItem = _dragItem;
|
||||
}
|
||||
|
||||
_dragPending = false;
|
||||
_dragFromMultiSelect = false;
|
||||
_marquee.Disarm();
|
||||
if (_dragList is { IsMouseCaptured: true })
|
||||
{
|
||||
_dragList.ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<string> DragPaths()
|
||||
{
|
||||
if (_dragFromMultiSelect && _dragSelection.Length > 0)
|
||||
{
|
||||
return _dragSelection.Select(i => i.FullPath).ToList();
|
||||
}
|
||||
|
||||
var selected = Vm.ActivePane.SelectedItems.Select(i => i.FullPath).ToList();
|
||||
if (_dragItem is not null && (selected.Count == 0
|
||||
|| selected.TrueForAll(p => !p.Equals(_dragItem.FullPath, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
return [_dragItem.FullPath];
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
private bool CompleteMarqueeMouseUp(MouseButtonEventArgs e)
|
||||
@@ -1293,6 +1379,36 @@ public partial class MainWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTags(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var vm = Vm.CreateTagRenameViewModel();
|
||||
if (vm is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new TagRenameWindow(vm) { Owner = this };
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
Vm.Footer = "Tag or rename work queued.";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMoveTo(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var vm = Vm.CreateMoveToViewModel();
|
||||
if (vm is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var dlg = new MoveToWindow(vm) { Owner = this };
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
Vm.Footer = "Move queued.";
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnExtractHere(object sender, RoutedEventArgs e)
|
||||
=> await Vm.ExtractSelectedAsync(null).ConfigureAwait(true);
|
||||
|
||||
|
||||
66
src/Explorer.App/MoveToWindow.xaml
Normal file
66
src/Explorer.App/MoveToWindow.xaml
Normal file
@@ -0,0 +1,66 @@
|
||||
<Window x:Class="Explorer.App.MoveToWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Move to"
|
||||
Icon="pack://application:,,,/Assets/explorer-workbench.ico"
|
||||
Height="560" Width="820"
|
||||
MinHeight="420" MinWidth="640"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource Bg}" Foreground="{DynamicResource Fg}">
|
||||
<DockPanel Margin="16">
|
||||
<DockPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
|
||||
<Button DockPanel.Dock="Right" Content="Cancel" MinWidth="88" Height="32" IsCancel="True" Margin="8,0,0,0"/>
|
||||
<Button DockPanel.Dock="Right" Content="Queue" MinWidth="88" Height="32" IsDefault="True"
|
||||
Command="{Binding QueueCommand}" IsEnabled="{Binding CanQueue}"/>
|
||||
<TextBlock Text="{Binding Status}" VerticalAlignment="Center" Foreground="{DynamicResource FgMuted}" TextWrapping="Wrap"/>
|
||||
</DockPanel>
|
||||
<StackPanel DockPanel.Dock="Top" Margin="0,0,0,12">
|
||||
<TextBlock Text="Destination pattern" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||
<Grid Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="PatternBox" MinHeight="32" VerticalContentAlignment="Center" Padding="8,4"
|
||||
Text="{Binding Pattern, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<Button Grid.Column="1" Content="Browse…" MinWidth="88" Height="32" Click="OnBrowse" Margin="8,0,0,0"/>
|
||||
<Button Grid.Column="2" Content="Save" MinWidth="72" Height="32" Command="{Binding SavePatternCommand}" Margin="8,0,0,0"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,0,0,8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Text="Recent" VerticalAlignment="Center" Margin="0,0,8,0" Foreground="{DynamicResource FgMuted}"/>
|
||||
<ComboBox Grid.Column="1" MinHeight="28" ItemsSource="{Binding PatternChoices}"
|
||||
SelectedItem="{Binding SelectedChoice}" SelectedIndex="-1"/>
|
||||
</Grid>
|
||||
<ItemsControl ItemsSource="{Binding Tokens}" Margin="0,0,0,8">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapPanel/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Content="{Binding}" MinWidth="0" Height="26" Padding="8,0" Margin="0,0,8,8"
|
||||
Click="OnInsertToken" Tag="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<TextBlock TextWrapping="Wrap" Foreground="{DynamicResource FgMuted}" FontSize="12"
|
||||
Text="Type a full UNC or drive path, then click a token to insert it at the caret. Example: \\10.0.0.31\media\movies\%filename_noext% creates a folder named after the file and places the file inside it. %year% / %month% use last-write time. Online-only cloud files are skipped."/>
|
||||
</StackPanel>
|
||||
<ListView ItemsSource="{Binding Rows}"
|
||||
Background="{DynamicResource Panel}" Foreground="{DynamicResource Fg}">
|
||||
<ListView.View>
|
||||
<GridView>
|
||||
<GridViewColumn Header="Action" Width="70" DisplayMemberBinding="{Binding Action}"/>
|
||||
<GridViewColumn Header="Source" Width="200" DisplayMemberBinding="{Binding Detail}"/>
|
||||
<GridViewColumn Header="Destination" Width="460" DisplayMemberBinding="{Binding Path}"/>
|
||||
</GridView>
|
||||
</ListView.View>
|
||||
</ListView>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
69
src/Explorer.App/MoveToWindow.xaml.cs
Normal file
69
src/Explorer.App/MoveToWindow.xaml.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Explorer.Presentation.ViewModels;
|
||||
|
||||
namespace Explorer.App;
|
||||
|
||||
public partial class MoveToWindow : Window
|
||||
{
|
||||
public MoveToWindow(MoveToViewModel vm)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
vm.CloseRequested += (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// not shown as a dialog
|
||||
}
|
||||
|
||||
Close();
|
||||
};
|
||||
Loaded += (_, _) =>
|
||||
{
|
||||
PatternBox.CaretIndex = PatternBox.Text?.Length ?? 0;
|
||||
PatternBox.Focus();
|
||||
};
|
||||
}
|
||||
|
||||
private void OnBrowse(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new Microsoft.Win32.OpenFolderDialog
|
||||
{
|
||||
Title = "Move to",
|
||||
Multiselect = false
|
||||
};
|
||||
if (picker.ShowDialog(this) != true || string.IsNullOrWhiteSpace(picker.FolderName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DataContext is MoveToViewModel vm)
|
||||
{
|
||||
vm.Pattern = picker.FolderName;
|
||||
}
|
||||
|
||||
PatternBox.CaretIndex = PatternBox.Text?.Length ?? 0;
|
||||
PatternBox.Focus();
|
||||
}
|
||||
|
||||
private void OnInsertToken(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button { Tag: string token } || DataContext is not MoveToViewModel vm)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var text = PatternBox.Text ?? vm.Pattern ?? "";
|
||||
var caret = Math.Clamp(PatternBox.CaretIndex, 0, text.Length);
|
||||
var slash = caret > 0 && text[caret - 1] != '\\' ? "\\" : "";
|
||||
var insert = slash + token;
|
||||
vm.Pattern = text.Insert(caret, insert);
|
||||
PatternBox.Focus();
|
||||
PatternBox.CaretIndex = caret + insert.Length;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.AdvancedSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Advanced" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
|
||||
@@ -10,7 +10,7 @@ public partial class AdvancedSettingsPage : UserControl
|
||||
|
||||
private void OnBrowseGit(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is SettingsDraft draft
|
||||
if (SettingsPageContext.DraftOf(this) is { } draft
|
||||
&& SettingsPathBrowse.TryPick(
|
||||
Window.GetWindow(this),
|
||||
"Git executable",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.AppearanceSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Appearance" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.FileOperationsSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StackPanel>
|
||||
<TextBlock Text="File Operations" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
|
||||
@@ -10,7 +10,7 @@ public partial class FileOperationsSettingsPage : UserControl
|
||||
|
||||
private void OnBrowseSevenZip(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is SettingsDraft draft
|
||||
if (SettingsPageContext.DraftOf(this) is { } draft
|
||||
&& SettingsPathBrowse.TryPick(
|
||||
Window.GetWindow(this),
|
||||
"7-Zip executable",
|
||||
@@ -24,7 +24,7 @@ public partial class FileOperationsSettingsPage : UserControl
|
||||
|
||||
private void OnBrowseFfmpeg(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DataContext is SettingsDraft draft
|
||||
if (SettingsPageContext.DraftOf(this) is { } draft
|
||||
&& SettingsPathBrowse.TryPick(
|
||||
Window.GetWindow(this),
|
||||
"FFmpeg executable",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.GeneralSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StackPanel>
|
||||
<TextBlock Text="General" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.IndexingSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:sys="clr-namespace:System;assembly=System.Runtime">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Indexing" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
@@ -31,16 +31,23 @@
|
||||
Text="The host watches Windows idle time even if this window is closed. Conservative defaults wait 10 minutes and prefer AC power."/>
|
||||
<TextBlock Text="Idle for" Foreground="{DynamicResource FgMuted}" Margin="0,0,0,8"
|
||||
IsEnabled="{Binding BackgroundMaintenanceWhenIdle}"/>
|
||||
<DockPanel LastChildFill="False" Margin="0,0,0,14">
|
||||
<ComboBox Width="80" HorizontalAlignment="Left"
|
||||
ItemsSource="{Binding IdleThresholdChoices}"
|
||||
SelectedItem="{Binding IdleMaintenanceMinutes}"
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,14">
|
||||
<ComboBox MinWidth="88" Width="88"
|
||||
SelectedItem="{Binding IdleMaintenanceMinutes, Mode=TwoWay}"
|
||||
IsEnabled="{Binding BackgroundMaintenanceWhenIdle}"
|
||||
AutomationProperties.Name="Idle threshold in minutes"/>
|
||||
AutomationProperties.Name="Idle threshold in minutes">
|
||||
<ComboBox.ItemsSource>
|
||||
<x:Array Type="sys:Int32">
|
||||
<sys:Int32>5</sys:Int32>
|
||||
<sys:Int32>10</sys:Int32>
|
||||
<sys:Int32>30</sys:Int32>
|
||||
</x:Array>
|
||||
</ComboBox.ItemsSource>
|
||||
</ComboBox>
|
||||
<TextBlock Text="minutes" VerticalAlignment="Center" Margin="10,0,0,0"
|
||||
Foreground="{DynamicResource FgMuted}"
|
||||
IsEnabled="{Binding BackgroundMaintenanceWhenIdle}"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
<CheckBox Margin="0,0,0,6"
|
||||
Content="Only run expensive maintenance on AC power"
|
||||
IsChecked="{Binding IdleMaintenanceAcOnly}"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<UserControl x:Class="Explorer.App.Settings.Pages.NavigationSettingsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
DataContext="{Binding Draft, RelativeSource={RelativeSource AncestorType=Window}}">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Navigation" Style="{StaticResource SettingsHeading}"
|
||||
AutomationProperties.HeadingLevel="Level1"/>
|
||||
|
||||
11
src/Explorer.App/Settings/SettingsPageContext.cs
Normal file
11
src/Explorer.App/Settings/SettingsPageContext.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Explorer.App.Settings;
|
||||
|
||||
internal static class SettingsPageContext
|
||||
{
|
||||
public static SettingsDraft? DraftOf(FrameworkElement page)
|
||||
=> page.DataContext as SettingsDraft
|
||||
?? (page.DataContext as SettingsShell)?.Draft
|
||||
?? (Window.GetWindow(page) as SettingsWindow)?.Draft;
|
||||
}
|
||||
@@ -63,9 +63,7 @@
|
||||
BorderThickness="1" Padding="16">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled"
|
||||
KeyboardNavigation.TabIndex="1">
|
||||
<ContentControl Content="{Binding SelectedCategory.Page}"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Top"/>
|
||||
<Grid x:Name="PageHost" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@@ -12,16 +12,46 @@ public partial class SettingsWindow : Window
|
||||
private readonly SettingsDraft _draft;
|
||||
private readonly string _originalTheme;
|
||||
|
||||
public SettingsDraft Draft => _draft;
|
||||
|
||||
public SettingsWindow(MainViewModel vm)
|
||||
{
|
||||
InitializeComponent();
|
||||
_vm = vm;
|
||||
var prefs = vm.CurrentPreferences();
|
||||
_originalTheme = prefs.Theme;
|
||||
_draft = SettingsDraft.From(prefs);
|
||||
_draft.PropertyChanged += OnDraftChanged;
|
||||
DataContext = new SettingsShell(_draft, SettingsCatalog.Create());
|
||||
Closed += (_, _) => _draft.PropertyChanged -= OnDraftChanged;
|
||||
var shell = new SettingsShell(_draft, SettingsCatalog.Create());
|
||||
DataContext = shell;
|
||||
InitializeComponent();
|
||||
shell.PropertyChanged += OnShellChanged;
|
||||
ShowSelectedPage();
|
||||
Closed += (_, _) =>
|
||||
{
|
||||
_draft.PropertyChanged -= OnDraftChanged;
|
||||
shell.PropertyChanged -= OnShellChanged;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnShellChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName is nameof(SettingsShell.SelectedCategory) or null)
|
||||
{
|
||||
ShowSelectedPage();
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSelectedPage()
|
||||
{
|
||||
PageHost.Children.Clear();
|
||||
if (DataContext is not SettingsShell shell)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var page = shell.SelectedCategory.Page;
|
||||
PageHost.Children.Add(page);
|
||||
page.DataContext = _draft;
|
||||
}
|
||||
|
||||
private void OnDraftChanged(object? sender, PropertyChangedEventArgs e)
|
||||
|
||||
50
src/Explorer.App/TagRenameWindow.xaml
Normal file
50
src/Explorer.App/TagRenameWindow.xaml
Normal file
@@ -0,0 +1,50 @@
|
||||
<Window x:Class="Explorer.App.TagRenameWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Tags"
|
||||
Icon="pack://application:,,,/Assets/explorer-workbench.ico"
|
||||
Height="640" Width="980"
|
||||
MinHeight="480" MinWidth="760"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Background="{DynamicResource Bg}" Foreground="{DynamicResource Fg}">
|
||||
<DockPanel Margin="16">
|
||||
<DockPanel DockPanel.Dock="Bottom" Margin="0,12,0,0">
|
||||
<Button DockPanel.Dock="Right" Content="Cancel" MinWidth="88" Height="32" IsCancel="True" Margin="8,0,0,0"/>
|
||||
<Button DockPanel.Dock="Right" Content="Queue rename" MinWidth="120" Height="32"
|
||||
Command="{Binding QueueRenameCommand}" IsEnabled="{Binding CanQueueRename}" Margin="8,0,0,0"/>
|
||||
<Button DockPanel.Dock="Right" Content="Queue tags" MinWidth="110" Height="32" IsDefault="True"
|
||||
Command="{Binding QueueTagsCommand}" IsEnabled="{Binding CanQueueTags}"/>
|
||||
<TextBlock Text="{Binding Status}" VerticalAlignment="Center" Foreground="{DynamicResource FgMuted}" TextWrapping="Wrap"/>
|
||||
</DockPanel>
|
||||
<DockPanel DockPanel.Dock="Top" Margin="0,0,0,12">
|
||||
<TextBlock DockPanel.Dock="Top" Text="Filename pattern" FontWeight="SemiBold" Margin="0,0,0,8"/>
|
||||
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" Margin="8,0,0,0">
|
||||
<Button Content="Save pattern" MinWidth="110" Height="28" Command="{Binding SavePatternCommand}" Margin="0,0,8,0"/>
|
||||
<Button Content="Filename → tags" MinWidth="130" Height="28" Command="{Binding ApplyFilenameToTagsCommand}" Margin="0,0,8,0"/>
|
||||
<Button Content="Tags → filename" MinWidth="130" Height="28" Command="{Binding ApplyTagsToNamesCommand}"/>
|
||||
</StackPanel>
|
||||
<ComboBox IsEditable="True" ItemsSource="{Binding PatternChoices}"
|
||||
Text="{Binding Pattern, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
</DockPanel>
|
||||
<TextBlock DockPanel.Dock="Top" Margin="0,0,0,12" TextWrapping="Wrap" Foreground="{DynamicResource FgMuted}" FontSize="12"
|
||||
Text="Edit tags, then Queue tags to write them into the files (ID3 on audio, EXIF on photos). Tags → filename fills New name from the pattern; Queue rename applies those names. Online-only cloud files are skipped. {Project} is the Git repo or parent folder."/>
|
||||
<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column" GridLinesVisibility="Horizontal"
|
||||
Background="{DynamicResource Panel}" Foreground="{DynamicResource Fg}"
|
||||
BorderBrush="{DynamicResource Stroke}" RowBackground="{DynamicResource Panel}"
|
||||
AlternatingRowBackground="{DynamicResource Panel}"
|
||||
HorizontalGridLinesBrush="{DynamicResource Stroke}">
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="File" Binding="{Binding FileName}" Width="180" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Artist" Binding="{Binding Artist, UpdateSourceTrigger=PropertyChanged}" Width="120"/>
|
||||
<DataGridTextColumn Header="Title" Binding="{Binding Title, UpdateSourceTrigger=PropertyChanged}" Width="160"/>
|
||||
<DataGridTextColumn Header="Album" Binding="{Binding Album, UpdateSourceTrigger=PropertyChanged}" Width="140"/>
|
||||
<DataGridTextColumn Header="Track" Binding="{Binding Track, UpdateSourceTrigger=PropertyChanged}" Width="60"/>
|
||||
<DataGridTextColumn Header="Year" Binding="{Binding Year, UpdateSourceTrigger=PropertyChanged}" Width="60"/>
|
||||
<DataGridTextColumn Header="Genre" Binding="{Binding Genre, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
|
||||
<DataGridTextColumn Header="New name" Binding="{Binding ProposedName}" Width="180" IsReadOnly="True"/>
|
||||
<DataGridTextColumn Header="Status" Binding="{Binding Status}" Width="120" IsReadOnly="True"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
26
src/Explorer.App/TagRenameWindow.xaml.cs
Normal file
26
src/Explorer.App/TagRenameWindow.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Windows;
|
||||
using Explorer.Presentation.ViewModels;
|
||||
|
||||
namespace Explorer.App;
|
||||
|
||||
public partial class TagRenameWindow : Window
|
||||
{
|
||||
public TagRenameWindow(TagRenameViewModel vm)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
vm.CloseRequested += (_, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// not shown as a dialog
|
||||
}
|
||||
|
||||
Close();
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user