Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Added support for bulk renaming items #15537

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/Files.App/Actions/FileSystem/RenameAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@ public RenameAction()
}

public Task ExecuteAsync(object? parameter = null)
{
context.ShellPage?.SlimContentPage?.ItemManipulationModel.StartRenameItem();
{
if (context.SelectedItems.Count == 1)
{
context.ShellPage?.SlimContentPage?.ItemManipulationModel.StartRenameItem();
yaira2 marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
context.ShellPage?.SlimContentPage?.ItemManipulationModel.StartRenameItems();
}

return Task.CompletedTask;
}

private bool IsSelectionValid()
{
return context.HasSelection && context.SelectedItems.Count == 1;
return context.HasSelection && context.SelectedItems.Count != 0;
}

private bool IsPageTypeValid()
Expand Down
7 changes: 7 additions & 0 deletions src/Files.App/Data/Models/ItemManipulationModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public sealed class ItemManipulationModel

public event EventHandler StartRenameItemInvoked;

public event EventHandler StartRenameItemsInvoked;

public event EventHandler<ListedItem> ScrollIntoViewInvoked;

public event EventHandler ScrollToTopInvoked;
Expand Down Expand Up @@ -101,6 +103,11 @@ public void StartRenameItem()
StartRenameItemInvoked?.Invoke(this, EventArgs.Empty);
}

public void StartRenameItems()
{
StartRenameItemsInvoked?.Invoke(this, EventArgs.Empty);
}

public void ScrollIntoView(ListedItem item)
{
ScrollIntoViewInvoked?.Invoke(this, item);
Expand Down
51 changes: 51 additions & 0 deletions src/Files.App/Helpers/UI/UIFilesystemHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,57 @@ public static async Task<bool> RenameFileItemAsync(ListedItem item, string newNa
return false;
}

public static async Task<bool> RenameFileItemsAsync(List<ListedItem> items, string newName, IShellPage associatedInstance, bool showExtensionDialog = true)
{
var tasks = items.Select(async (item, i) =>
{


if (item is AlternateStreamItem ads) // For alternate streams Name is not a substring ItemNameRaw
{
newName = item.ItemNameRaw.Replace(
item.Name.Substring(item.Name.LastIndexOf(':') + 1),
newName.Substring(newName.LastIndexOf(':') + 1),
StringComparison.Ordinal);
newName = $"{ads.MainStreamName}:{newName}";
}
else if (string.IsNullOrEmpty(item.Name))
{
newName = string.Concat(newName, item.FileExtension);
}
else
{
newName = item.ItemNameRaw.Replace(item.Name, newName, StringComparison.Ordinal);
}

if (item.ItemNameRaw == newName || string.IsNullOrEmpty(newName))
{
return true;
}

FilesystemItemType itemType = (item.PrimaryItemAttribute == StorageItemTypes.Folder) ? FilesystemItemType.Directory : FilesystemItemType.File;

ReturnResult renamed = await associatedInstance.FilesystemHelpers.RenameAsync(StorageHelpers.FromPathAndType(item.ItemPath, itemType), newName, NameCollisionOption.FailIfExists, true, showExtensionDialog);

if (renamed == ReturnResult.Success)
{
associatedInstance.ToolbarViewModel.CanGoForward = false;
await associatedInstance.RefreshIfNoWatcherExistsAsync();
return true;

}
else
{
return false;
}
});

var results = await Task.WhenAll(tasks);
return results.All(x => x);


}

public static async Task CreateFileFromDialogResultTypeAsync(AddItemDialogItemType itemType, ShellNewEntry? itemInfo, IShellPage associatedInstance)
{
await CreateFileFromDialogResultTypeForResult(itemType, itemInfo, associatedInstance);
Expand Down
95 changes: 92 additions & 3 deletions src/Files.App/Views/Layouts/BaseGroupableLayoutPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public abstract class BaseGroupableLayoutPage : BaseLayoutPage

protected override ItemsControl ItemsControl => ListViewBase;


// Constructor

public BaseGroupableLayoutPage() : base()
Expand Down Expand Up @@ -69,6 +70,7 @@ protected override void HookEvents()
ItemManipulationModel.RemoveSelectedItemInvoked += ItemManipulationModel_RemoveSelectedItemInvoked;
ItemManipulationModel.FocusSelectedItemsInvoked += ItemManipulationModel_FocusSelectedItemsInvoked;
ItemManipulationModel.StartRenameItemInvoked += ItemManipulationModel_StartRenameItemInvoked;
ItemManipulationModel.StartRenameItemsInvoked += ItemManipulationModel_StartRenameItemsInvoked;
ItemManipulationModel.ScrollIntoViewInvoked += ItemManipulationModel_ScrollIntoViewInvoked;
ItemManipulationModel.ScrollToTopInvoked += ItemManipulationModel_ScrollToTopInvoked;
ItemManipulationModel.RefreshItemThumbnailInvoked += ItemManipulationModel_RefreshItemThumbnail;
Expand All @@ -88,6 +90,7 @@ protected override void UnhookEvents()
ItemManipulationModel.RemoveSelectedItemInvoked -= ItemManipulationModel_RemoveSelectedItemInvoked;
ItemManipulationModel.FocusSelectedItemsInvoked -= ItemManipulationModel_FocusSelectedItemsInvoked;
ItemManipulationModel.StartRenameItemInvoked -= ItemManipulationModel_StartRenameItemInvoked;
ItemManipulationModel.StartRenameItemsInvoked -= ItemManipulationModel_StartRenameItemsInvoked;
ItemManipulationModel.ScrollIntoViewInvoked -= ItemManipulationModel_ScrollIntoViewInvoked;
ItemManipulationModel.ScrollToTopInvoked -= ItemManipulationModel_ScrollToTopInvoked;
ItemManipulationModel.RefreshItemThumbnailInvoked -= ItemManipulationModel_RefreshItemThumbnail;
Expand Down Expand Up @@ -209,6 +212,12 @@ protected virtual void ItemManipulationModel_StartRenameItemInvoked(object? send
StartRenameItem();
}

protected virtual void ItemManipulationModel_StartRenameItemsInvoked(object? sender, EventArgs e)
{
StartRenameItems();
}


protected virtual void ZoomIn(object? sender, GroupOption option)
{
if (option == GroupOption.None)
Expand Down Expand Up @@ -267,6 +276,54 @@ protected virtual void StartRenameItem(string itemNameTextBox)
IsRenamingItem = true;
}

protected virtual void StartRenameItems(string itemNameTextBox)
{

RenamingItems = SelectedItems; // Assume this method retrieves all selected items.
if (RenamingItems == null || RenamingItems.Count == 0)
return;
RenamingItem = RenamingItems.Last();
int extensionLength = RenamingItem.FileExtension?.Length ?? 0;

ListViewItem? listViewItem = ListViewBase.ContainerFromItem(RenamingItem) as ListViewItem;
if (listViewItem is null)
return;

TextBox? textBox = null;
TextBlock? textBlock = listViewItem.FindDescendant("ItemName") as TextBlock;
textBox = listViewItem.FindDescendant(itemNameTextBox) as TextBox;
if (textBox is null || textBlock is null)
return;
textBox!.Text = textBlock!.Text;
OldItemName = textBlock.Text;
textBlock.Visibility = Visibility.Collapsed;
textBox.Visibility = Visibility.Visible;

if (textBox.FindParent<Grid>() is null)
{
textBlock.Visibility = Visibility.Visible;
textBox.Visibility = Visibility.Collapsed;
return;
}

Grid.SetColumnSpan(textBox.FindParent<Grid>(), 8);


textBox.Focus(FocusState.Pointer);

textBox.LostFocus += RenameTextBox_LostFocus;
textBox.KeyDown += RenameTextBox_KeyDown;

int selectedTextLength = RenamingItem.Name.Length;

if (!RenamingItem.IsShortcut && UserSettingsService.FoldersSettingsService.ShowFileExtensions)
selectedTextLength -= extensionLength;

textBox.Select(0, selectedTextLength);
IsRenamingMultipleItems = true;

}

protected virtual async Task CommitRenameAsync(TextBox textBox)
{
EndRename(textBox);
Expand All @@ -275,20 +332,39 @@ protected virtual async Task CommitRenameAsync(TextBox textBox)
await UIFilesystemHelpers.RenameFileItemAsync(RenamingItem, newItemName, ParentShellPageInstance);
}

protected virtual async Task CommitMultipleRenameAsync(TextBox textBox)
{
EndRename(textBox);
string newItemName = textBox.Text.Trim().TrimEnd('.');
await UIFilesystemHelpers.RenameFileItemsAsync(RenamingItems, newItemName, ParentShellPageInstance);


}

protected virtual async void RenameTextBox_LostFocus(object sender, RoutedEventArgs e)
{
// This check allows the user to use the text box context menu without ending the rename
if (!(FocusManager.GetFocusedElement(MainWindow.Instance.Content.XamlRoot) is AppBarButton or Popup))
{
TextBox textBox = (TextBox)e.OriginalSource;
await CommitRenameAsync(textBox);
if (!IsRenamingMultipleItems)
{
await CommitRenameAsync(textBox);
}
else
{
await CommitMultipleRenameAsync(textBox);
}

}
}

// Methods

protected async void RenameTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
{
Console.WriteLine($"Key pressed: {e.Key}");
Console.WriteLine($"EnterKey : {VirtualKey.Enter}");
var textBox = (TextBox)sender;
var isShiftPressed = (PInvoke.GetKeyState((int)VirtualKey.Shift) & KEY_DOWN_MASK) != 0;

Expand All @@ -302,7 +378,10 @@ protected async void RenameTextBox_KeyDown(object sender, KeyRoutedEventArgs e)
break;
case VirtualKey.Enter:
textBox.LostFocus -= RenameTextBox_LostFocus;
await CommitRenameAsync(textBox);
if(!IsRenamingMultipleItems)
await CommitRenameAsync(textBox);
else
await CommitMultipleRenameAsync(textBox);
e.Handled = true;
break;
case VirtualKey.Up:
Expand All @@ -328,7 +407,14 @@ protected async void RenameTextBox_KeyDown(object sender, KeyRoutedEventArgs e)

if (textBox.Text != OldItemName)
{
await CommitRenameAsync(textBox);
if (!IsRenamingMultipleItems)
{
await CommitRenameAsync(textBox);
}
else
{
await CommitMultipleRenameAsync(textBox);
}
}
else
{
Expand All @@ -351,6 +437,7 @@ protected async void RenameTextBox_KeyDown(object sender, KeyRoutedEventArgs e)

protected bool TryStartRenameNextItem(ListedItem item)
{

var nextItemIndex = ListViewBase.Items.IndexOf(item) + NextRenameIndex;
NextRenameIndex = 0;

Expand All @@ -364,6 +451,8 @@ protected bool TryStartRenameNextItem(ListedItem item)
}

return false;


}

protected void SelectionCheckbox_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
Expand Down
10 changes: 10 additions & 0 deletions src/Files.App/Views/Layouts/BaseLayoutPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,15 @@ public bool AllowItemDrag
public IShellPage? ParentShellPageInstance { get; private set; }

public bool IsRenamingItem { get; set; }

public bool IsRenamingMultipleItems { get; set; }

public bool LockPreviewPaneContent { get; set; }

public ListedItem? RenamingItem { get; set; }

public List<ListedItem>? RenamingItems { get; set; }

public ListedItem? SelectedItem { get; private set; }

public string? OldItemName { get; set; }
Expand Down Expand Up @@ -1380,6 +1386,10 @@ virtual public void StartRenameItem()
{
}

virtual public void StartRenameItems()
{
}

public void CheckRenameDoubleClick(object clickedItem)
{
if (clickedItem is ListedItem item)
Expand Down
32 changes: 27 additions & 5 deletions src/Files.App/Views/Layouts/ColumnLayoutPage.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,13 @@ protected override void ItemManipulationModel_FocusSelectedItemsInvoked(object?

protected override void ItemManipulationModel_AddSelectedItemInvoked(object? sender, ListedItem e)
{
if (NextRenameIndex != 0 && TryStartRenameNextItem(e))
return;
if (!IsRenamingMultipleItems)
{
if (NextRenameIndex != 0 && TryStartRenameNextItem(e))
return;

FileList?.SelectedItems.Add(e);
FileList?.SelectedItems.Add(e);
}
}

protected override void ItemManipulationModel_RemoveSelectedItemInvoked(object? sender, ListedItem e)
Expand Down Expand Up @@ -196,9 +199,14 @@ override public void StartRenameItem()
StartRenameItem("ListViewTextBoxItemName");
}

public override void StartRenameItems()
{
StartRenameItems("ListViewTextBoxItemName");
}

private async void ItemNameTextBox_BeforeTextChanging(TextBox textBox, TextBoxBeforeTextChangingEventArgs args)
{
if (IsRenamingItem)
if (IsRenamingItem || IsRenamingMultipleItems)
{
await ValidateItemNameInputTextAsync(textBox, args, (showError) =>
{
Expand All @@ -212,6 +220,8 @@ protected override void EndRename(TextBox textBox)
{
FileNameTeachingTip.IsOpen = false;
IsRenamingItem = false;



// Unsubscribe from events
if (textBox is not null)
Expand All @@ -222,7 +232,18 @@ protected override void EndRename(TextBox textBox)

if (textBox is not null && textBox.Parent is not null)
{
ListViewItem? listViewItem = FileList.ContainerFromItem(RenamingItem) as ListViewItem;
ListViewItem? listViewItem;
if (!IsRenamingMultipleItems)
{
listViewItem = FileList.ContainerFromItem(RenamingItem) as ListViewItem;
}
else
{
ListedItem lastItem = RenamingItems.LastOrDefault();
if (lastItem is null)
return;
listViewItem = FileList.ContainerFromItem(lastItem) as ListViewItem;
}
if (listViewItem is null)
return;

Expand All @@ -233,6 +254,7 @@ protected override void EndRename(TextBox textBox)
textBox!.Visibility = Visibility.Collapsed;
textBlock!.Visibility = Visibility.Visible;
}

}

public override void ResetItemOpacity()
Expand Down
Loading
Loading