Skip to content

Feature: Added option to show check boxes next to file names #11017

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

Merged
merged 35 commits into from
Jan 19, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f7deab7
Update DetailsLayout to new multi select behavior
marcelwgn Jan 15, 2023
4f395d9
Disable multi select for Column layout
marcelwgn Jan 15, 2023
098fb01
Fix issue with rectangle selection not updating checkboxes
marcelwgn Jan 15, 2023
91daaec
Update grid based layouts
marcelwgn Jan 15, 2023
80c03e9
Switch to preference for multi selection
marcelwgn Jan 15, 2023
7f689b5
Merge branch 'main' into dev/refactor-multi-select
marcelwgn Jan 15, 2023
129f9d5
Format xaml
yaira2 Jan 15, 2023
2f2469f
Move multiselect checkbox to folders settings
marcelwgn Jan 15, 2023
08f3af3
Revert formatting
marcelwgn Jan 15, 2023
c93b89c
Fix thumbnails not loading
marcelwgn Jan 15, 2023
7d7f20c
Update Folders.xaml
yaira2 Jan 15, 2023
e0401f0
Update src/Files.App/Strings/en-US/Resources.resw
yaira2 Jan 15, 2023
96cbd64
Update Folders.xaml
yaira2 Jan 15, 2023
f150ed9
CR feedback
marcelwgn Jan 15, 2023
a0bb218
Merge branch 'dev/refactor-multi-select' of https://github.com/chingu…
marcelwgn Jan 15, 2023
c253678
Merge branch 'main' into dev/refactor-multi-select
marcelwgn Jan 15, 2023
873b63e
Merge branch 'main' into dev/refactor-multi-select
marcelwgn Jan 16, 2023
3f4d7a9
Update checkbox loading
marcelwgn Jan 16, 2023
43c881c
Merge branch 'dev/refactor-multi-select' of https://github.com/chingu…
marcelwgn Jan 16, 2023
74b2595
Merge branch 'main' into dev/refactor-multi-select
yaira2 Jan 16, 2023
41fe9bf
Update DetailsLayoutBrowser.xaml
yaira2 Jan 16, 2023
f5eeab9
Update DetailsLayoutBrowser.xaml
yaira2 Jan 16, 2023
a24eb04
Revert file changes
marcelwgn Jan 16, 2023
e37917f
Merge branch 'dev/refactor-multi-select' of https://github.com/chingu…
marcelwgn Jan 16, 2023
2581616
Fix issue with selection rectangle
marcelwgn Jan 16, 2023
00340d4
CR feedback
marcelwgn Jan 16, 2023
7926512
Update DetailsLayoutBrowser.xaml
yaira2 Jan 16, 2023
4bedac6
Merge branch 'main' into dev/refactor-multi-select
yaira2 Jan 18, 2023
851fb06
Update DetailsLayoutBrowser.xaml.cs
yaira2 Jan 18, 2023
02e21ec
Merge branch 'main' into dev/refactor-multi-select
yaira2 Jan 18, 2023
37bb1a6
CR feedback
marcelwgn Jan 19, 2023
9825a0f
Merge branch 'dev/refactor-multi-select' of https://github.com/chingu…
marcelwgn Jan 19, 2023
d55b063
Merge branch 'main' into dev/refactor-multi-select
marcelwgn Jan 19, 2023
9d6d7ab
CR feedback
marcelwgn Jan 19, 2023
8d6ba1d
Merge branch 'dev/refactor-multi-select' of https://github.com/chingu…
marcelwgn Jan 19, 2023
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
7 changes: 0 additions & 7 deletions src/Files.App/DataModels/AppModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,6 @@ public bool IsPasteEnabled
set => SetProperty(ref isPasteEnabled, value);
}

private bool multiselectEnabled;
public bool MultiselectEnabled
{
get => multiselectEnabled;
set => SetProperty(ref multiselectEnabled, value);
}

private bool isQuickLookAvailable;
public bool IsQuickLookAvailable
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ public bool ShowFileTagsSection
set => Set(value);
}

public bool ShowSelectionCheckboxes
{
get => Get(false);
set => Set(value);
}

protected override void RaiseOnSettingChangedEvent(object sender, SettingChangedEventArgs e)
{
switch (e.SettingName)
Expand All @@ -196,6 +202,7 @@ protected override void RaiseOnSettingChangedEvent(object sender, SettingChanged
case nameof(ShowNetworkDrivesSection):
case nameof(ShowWslSection):
case nameof(ShowFileTagsSection):
case nameof(ShowSelectionCheckboxes):
Analytics.TrackEvent($"Set {e.SettingName} to {e.NewValue}");
break;
}
Expand Down
8 changes: 0 additions & 8 deletions src/Files.App/UserControls/InnerNavigationToolbar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,6 @@
ToolTipService.ToolTip="{helpers:ResourceString Name=NavToolbarSelectionOptions/ToolTipService/ToolTip}">
<AppBarButton.Flyout>
<MenuFlyout Placement="Bottom">
<ToggleMenuFlyoutItem
x:Name="MultiselectMFI"
IsChecked="{x:Bind AppModel.MultiselectEnabled, Mode=TwoWay}"
Text="{helpers:ResourceString Name=NavToolbarMultiselect/Text}">
<MenuFlyoutItem.Icon>
<FontIcon Glyph="&#xE762;" />
</MenuFlyoutItem.Icon>
</ToggleMenuFlyoutItem>
<MenuFlyoutItem
x:Name="SelectAllMFI"
Command="{x:Bind ViewModel.SelectAllContentPageItemsCommand, Mode=OneWay}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private void RectangleSelection_PointerMoved(object sender, PointerRoutedEventAr
if (prevSelectedItemsDrag is null || !prevSelectedItemsDrag.SequenceEqual(currentSelectedItemsDrag))
{
// Trigger SelectionChanged event if the selection has changed
selectionChanged(sender, null);
selectionChanged(sender, new SelectionChangedEventArgs(currentSelectedItemsDrag, new List<object>()));
prevSelectedItemsDrag = currentSelectedItemsDrag;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,19 @@ public bool IsDualPaneEnabled
}
}

public bool ShowSelectionCheckboxes
{
get => UserSettingsService.PreferencesSettingsService.ShowSelectionCheckboxes;
set
{
if (value != UserSettingsService.PreferencesSettingsService.ShowSelectionCheckboxes)
{
UserSettingsService.PreferencesSettingsService.ShowSelectionCheckboxes = value;
OnPropertyChanged();
}
}
}

public bool AlwaysOpenDualPaneInNewTab
{
get => UserSettingsService.PreferencesSettingsService.AlwaysOpenDualPaneInNewTab;
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/Views/LayoutModes/ColumnViewBase.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
RightTapped="FileList_RightTapped"
ScrollViewer.IsScrollInertiaEnabled="True"
SelectionChanged="FileList_SelectionChanged"
SelectionMode="{x:Bind AppModel.MultiselectEnabled, Mode=OneWay, Converter={StaticResource BoolToSelectionModeConverter}}"
SelectionMode="Extended"
Tapped="FileList_ItemTapped">

<ListView.ItemContainerTransitions>
Expand Down
838 changes: 427 additions & 411 deletions src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml

Large diffs are not rendered by default.

139 changes: 109 additions & 30 deletions src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI.UI;
using Files.App.Converters;
using Files.App.EventArguments;
using Files.App.Filesystem;
using Files.App.Helpers;
Expand All @@ -8,6 +10,7 @@
using Files.App.UserControls;
using Files.App.UserControls.Selection;
using Files.App.ViewModels;
using Files.Backend.Services.Settings;
using Files.Shared.Enums;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
Expand Down Expand Up @@ -62,9 +65,13 @@ public double MaxWidthForRenameTextbox

public ScrollViewer? ContentScroller { get; private set; }

public IPreferencesSettingsService Preferences { get; private set; }

public DetailsLayoutBrowser() : base()
{
Preferences = Ioc.Default.GetRequiredService<IUserSettingsService>().PreferencesSettingsService;
InitializeComponent();

this.DataContext = this;

var selectionRectangle = RectangleSelection.Create(FileList, SelectionRectangle, FileList_SelectionChanged);
Expand Down Expand Up @@ -310,6 +317,23 @@ private async void FileList_SelectionChanged(object sender, SelectionChangedEven
{
await QuickLookHelpers.ToggleQuickLook(ParentShellPageInstance, true);
}


// If the selection is not all items, uncheck the select all checkbox and vice versa
SelectAllItemsCheckbox.IsChecked = SelectedItems.Count == FileList.Items.Count;

if (e != null)
{
foreach (var item in e.AddedItems)
{
SetCheckboxSelectionState(item);
}

foreach (var item in e.RemovedItems)
{
SetCheckboxSelectionState(item);
}
}
}

override public void StartRenameItem()
Expand Down Expand Up @@ -544,8 +568,11 @@ private void FileList_ItemTapped(object sender, TappedRoutedEventArgs e)
if (item is null)
return;
// Skip code if the control or shift key is pressed or if the user is using multiselect
if (ctrlPressed || shiftPressed || AppModel.MultiselectEnabled)
if (ctrlPressed || shiftPressed || Preferences.ShowSelectionCheckboxes)
{
e.Handled = true;
return;
}

// Check if the setting to open items with a single click is turned on
if (item is not null
Expand Down Expand Up @@ -579,7 +606,7 @@ private void FileList_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
if ((e.OriginalSource as FrameworkElement)?.DataContext is ListedItem item
&& !UserSettingsService.FoldersSettingsService.OpenItemsWithOneClick)
{
_ = NavigationHelpers.OpenSelectedItems(ParentShellPageInstance, false);
_ = NavigationHelpers.OpenPath(item.ItemPath, ParentShellPageInstance);
}
else
{
Expand Down Expand Up @@ -608,6 +635,8 @@ private void StackPanel_Loaded(object sender, RoutedEventArgs e)
item = VisualTreeHelper.GetParent(item);
if (item is ListViewItem itemContainer)
itemContainer.ContextFlyout = ItemContextMenuFlyout;

SelectAllItemsCheckbox.IsChecked = FileList.SelectedItems.Count == FileList.Items.Count;
}

private void Grid_PointerPressed(object sender, PointerRoutedEventArgs e)
Expand All @@ -632,16 +661,16 @@ private void GridSplitter_PreviewKeyUp(object sender, KeyRoutedEventArgs e)

private void UpdateColumnLayout()
{
ColumnsViewModel.IconColumn.UserLength = new GridLength(Column1.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.NameColumn.UserLength = new GridLength(Column2.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.TagColumn.UserLength = new GridLength(Column3.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.OriginalPathColumn.UserLength = new GridLength(Column4.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateDeletedColumn.UserLength = new GridLength(Column5.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateModifiedColumn.UserLength = new GridLength(Column6.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateCreatedColumn.UserLength = new GridLength(Column7.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.ItemTypeColumn.UserLength = new GridLength(Column8.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.SizeColumn.UserLength = new GridLength(Column9.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.StatusColumn.UserLength = new GridLength(Column10.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.IconColumn.UserLength = new GridLength(Column2.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.NameColumn.UserLength = new GridLength(Column3.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.TagColumn.UserLength = new GridLength(Column4.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.OriginalPathColumn.UserLength = new GridLength(Column5.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateDeletedColumn.UserLength = new GridLength(Column6.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateModifiedColumn.UserLength = new GridLength(Column7.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.DateCreatedColumn.UserLength = new GridLength(Column8.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.ItemTypeColumn.UserLength = new GridLength(Column9.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.SizeColumn.UserLength = new GridLength(Column10.ActualWidth, GridUnitType.Pixel);
ColumnsViewModel.StatusColumn.UserLength = new GridLength(Column11.ActualWidth, GridUnitType.Pixel);
}

private void RootGrid_SizeChanged(object? sender, SizeChangedEventArgs? e)
Expand Down Expand Up @@ -696,14 +725,15 @@ private void ResizeColumnToFit(int columnToResize)

var maxItemLength = columnToResize switch
{
1 => FileList.Items.Cast<ListedItem>().Select(x => x.Name?.Length ?? 0).Max(), // file name column
2 => FileList.Items.Cast<ListedItem>().Select(x => x.FileTagsUI?.FirstOrDefault()?.TagName?.Length ?? 0).Max(), // file tag column
3 => FileList.Items.Cast<ListedItem>().Select(x => (x as RecycleBinItem)?.ItemOriginalPath?.Length ?? 0).Max(), // original path column
4 => FileList.Items.Cast<ListedItem>().Select(x => (x as RecycleBinItem)?.ItemDateDeleted?.Length ?? 0).Max(), // date deleted column
5 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemDateModified?.Length ?? 0).Max(), // date modified column
6 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemDateCreated?.Length ?? 0).Max(), // date created column
7 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemType?.Length ?? 0).Max(), // item type column
8 => FileList.Items.Cast<ListedItem>().Select(x => x.FileSize?.Length ?? 0).Max(), // item size column
1 => 40, // Check all items columns
2 => FileList.Items.Cast<ListedItem>().Select(x => x.Name?.Length ?? 0).Max(), // file name column
3 => FileList.Items.Cast<ListedItem>().Select(x => x.FileTagsUI?.FirstOrDefault()?.TagName?.Length ?? 0).Max(), // file tag column
4 => FileList.Items.Cast<ListedItem>().Select(x => (x as RecycleBinItem)?.ItemOriginalPath?.Length ?? 0).Max(), // original path column
5 => FileList.Items.Cast<ListedItem>().Select(x => (x as RecycleBinItem)?.ItemDateDeleted?.Length ?? 0).Max(), // date deleted column
6 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemDateModified?.Length ?? 0).Max(), // date modified column
7 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemDateCreated?.Length ?? 0).Max(), // date created column
8 => FileList.Items.Cast<ListedItem>().Select(x => x.ItemType?.Length ?? 0).Max(), // item type column
9 => FileList.Items.Cast<ListedItem>().Select(x => x.FileSize?.Length ?? 0).Max(), // item size column
_ => 20 // cloud status column
};

Expand All @@ -712,19 +742,19 @@ private void ResizeColumnToFit(int columnToResize)
if (maxItemLength == 0)
return;

var columnSizeToFit = new[] { 9 }.Contains(columnToResize) ? maxItemLength : MeasureTextColumnEstimate(columnToResize, 5, maxItemLength);
if (columnSizeToFit > 0)
var columnSizeToFit = new[] { 10 }.Contains(columnToResize) ? maxItemLength : MeasureTextColumnEstimate(columnToResize, 5, maxItemLength);
if (columnSizeToFit > 1)
{
var column = columnToResize switch
{
1 => ColumnsViewModel.NameColumn,
2 => ColumnsViewModel.TagColumn,
3 => ColumnsViewModel.OriginalPathColumn,
4 => ColumnsViewModel.DateDeletedColumn,
5 => ColumnsViewModel.DateModifiedColumn,
6 => ColumnsViewModel.DateCreatedColumn,
7 => ColumnsViewModel.ItemTypeColumn,
8 => ColumnsViewModel.SizeColumn,
2 => ColumnsViewModel.NameColumn,
3 => ColumnsViewModel.TagColumn,
4 => ColumnsViewModel.OriginalPathColumn,
5 => ColumnsViewModel.DateDeletedColumn,
6 => ColumnsViewModel.DateModifiedColumn,
7 => ColumnsViewModel.DateCreatedColumn,
8 => ColumnsViewModel.ItemTypeColumn,
9 => ColumnsViewModel.SizeColumn,
_ => ColumnsViewModel.StatusColumn
};

Expand Down Expand Up @@ -791,5 +821,54 @@ private void SetDetailsColumnsAsDefault_Click(object sender, RoutedEventArgs e)
{
FolderSettings.SetDefaultLayoutPreferences(ColumnsViewModel);
}

private void SelectAllCheckbox_Checked(object sender, RoutedEventArgs e)
{
FileList.SelectAll();
}

private void SelectAllCheckbox_Unchecked(object sender, RoutedEventArgs e)
{
// We should only unselect all items if the user clicked the checkbox
// We determine this by checking if all items were selected
if (SelectedItems.Count == FileList.Items.Count)
{
FileList.SelectedItem = null;
}
}

private void ItemSelected_Checked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.DataContext is ListedItem item && !FileList.SelectedItems.Contains(item))
{
FileList.SelectedItems.Add(item);
}
}

private void ItemSelected_Unchecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox checkBox && checkBox.DataContext is ListedItem item && FileList.SelectedItems.Contains(item))
{
FileList.SelectedItems.Remove(item);
}
}

private new void FileList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
SetCheckboxSelectionState(args.Item, args.ItemContainer as ListViewItem);
}

private void SetCheckboxSelectionState(object item, ListViewItem? lviContainer = null)
{
var container = lviContainer ?? FileList.ContainerFromItem(item) as ListViewItem;
if (container is not null)
{
var checkbox = container.FindDescendant("SelectionCheckbox") as CheckBox;
if (checkbox is not null)
{
checkbox.IsChecked = FileList.SelectedItems.Contains(item);
}
}
}
}
}
Loading