From c3ce65c60b831684ca4755142631b6f5fbcd733b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dongmin=20Jang=20=28=EC=9E=A5=EB=8F=99=EB=AF=BC=29?= Date: Tue, 16 Jul 2024 17:44:59 +0900 Subject: [PATCH] Allow drag and drop to file opener --- Corathing.sln | 25 +++- .../Converters/NullToVisibilityConverter.cs | 4 - .../Corathing.UI.WPF/Corathing.UI.WPF.csproj | 11 +- src/Shared/Corathing.UI/Corathing.UI.csproj | 13 ++ .../AbstractFileStorageDataSourceContext.cs | 11 ++ ...s => LocalFileStorageDataSourceContext.cs} | 4 +- ... => LocalFileStorageDataSourceSelector.cs} | 4 +- .../FileOpeners/FileOpenerDataTemplates.xaml | 12 +- .../Widgets/FileOpeners/FileOpenerOption.cs | 7 + .../Widgets/FileOpeners/FileOpenerWidget.xaml | 106 ++++++++++++++++ .../FileOpeners/FileOpenerWidget.xaml.cs | 120 ++++++++++++++++++ .../FileOpeners/FileOpenerWidgetContext.cs | 58 ++++++++- 12 files changed, 350 insertions(+), 25 deletions(-) create mode 100644 src/Shared/Corathing.UI/Corathing.UI.csproj create mode 100644 src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/AbstractFileStorageDataSourceContext.cs rename src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/{FileStorageDataSourceContext.cs => LocalFileStorageDataSourceContext.cs} (88%) rename src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/{FileStorageDataSourceSelector.cs => LocalFileStorageDataSourceSelector.cs} (51%) create mode 100644 src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml create mode 100644 src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml.cs diff --git a/Corathing.sln b/Corathing.sln index 6d22f44..da7c772 100644 --- a/Corathing.sln +++ b/Corathing.sln @@ -51,7 +51,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9DC8 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Corathing.Dashboards.WPF.Sample", "src\Samples\Corathing.Dashboards.WPF.Sample\Corathing.Dashboards.WPF.Sample.csproj", "{CA61CC63-9DDE-4614-9288-1E1EFACB3FF7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Corathing.Contracts.Utils.UnitTests", "src\Shared\Corathing.Contracts.Utils.UnitTests\Corathing.Contracts.Utils.UnitTests.csproj", "{CB963293-F6CE-4685-B33E-8AE41948EB83}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Corathing.Contracts.Utils.UnitTests", "src\Shared\Corathing.Contracts.Utils.UnitTests\Corathing.Contracts.Utils.UnitTests.csproj", "{CB963293-F6CE-4685-B33E-8AE41948EB83}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Corathing.UI", "src\Shared\Corathing.UI\Corathing.UI.csproj", "{7995D746-109F-4438-8D5A-E897216AB867}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -377,6 +379,26 @@ Global {CB963293-F6CE-4685-B33E-8AE41948EB83}.Release|x64.Build.0 = Release|Any CPU {CB963293-F6CE-4685-B33E-8AE41948EB83}.Release|x86.ActiveCfg = Release|Any CPU {CB963293-F6CE-4685-B33E-8AE41948EB83}.Release|x86.Build.0 = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|ARM.ActiveCfg = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|ARM.Build.0 = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|ARM64.Build.0 = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|x64.ActiveCfg = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|x64.Build.0 = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|x86.ActiveCfg = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Debug|x86.Build.0 = Debug|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|Any CPU.Build.0 = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|ARM.ActiveCfg = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|ARM.Build.0 = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|ARM64.ActiveCfg = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|ARM64.Build.0 = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|x64.ActiveCfg = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|x64.Build.0 = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|x86.ActiveCfg = Release|Any CPU + {7995D746-109F-4438-8D5A-E897216AB867}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -395,6 +417,7 @@ Global {A9AE0DA4-AFE4-4D9A-A123-FA242BFA0C0C} = {5BF1289A-1427-4306-B2E1-51E0770B6B75} {55A4F80C-E7CB-48E6-A34D-BACBD470E0D4} = {5BF1289A-1427-4306-B2E1-51E0770B6B75} {CA61CC63-9DDE-4614-9288-1E1EFACB3FF7} = {9DC8886B-D4A5-43C1-9004-1EC87AA69E7C} + {7995D746-109F-4438-8D5A-E897216AB867} = {89CC8AAB-466A-47B6-98B9-93EB379F55A7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FC027A8C-0C58-494B-BE76-F403F0336A74} diff --git a/src/Shared/Corathing.Dashboards.WPF/Converters/NullToVisibilityConverter.cs b/src/Shared/Corathing.Dashboards.WPF/Converters/NullToVisibilityConverter.cs index 2fdc566..e906d87 100644 --- a/src/Shared/Corathing.Dashboards.WPF/Converters/NullToVisibilityConverter.cs +++ b/src/Shared/Corathing.Dashboards.WPF/Converters/NullToVisibilityConverter.cs @@ -12,8 +12,6 @@ namespace Corathing.Dashboards.WPF.Converters; /// public class NullToVisibilityConverter : IValueConverter { - #region Public Methods - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value == null ? Visibility.Collapsed : Visibility.Visible; @@ -23,6 +21,4 @@ public object ConvertBack(object value, Type targetType, object parameter, Cultu { throw new NotImplementedException(); } - - #endregion Public Methods } diff --git a/src/Shared/Corathing.UI.WPF/Corathing.UI.WPF.csproj b/src/Shared/Corathing.UI.WPF/Corathing.UI.WPF.csproj index 9021f4f..d08d4e2 100644 --- a/src/Shared/Corathing.UI.WPF/Corathing.UI.WPF.csproj +++ b/src/Shared/Corathing.UI.WPF/Corathing.UI.WPF.csproj @@ -23,13 +23,18 @@ - - - + + + + + + + + diff --git a/src/Shared/Corathing.UI/Corathing.UI.csproj b/src/Shared/Corathing.UI/Corathing.UI.csproj new file mode 100644 index 0000000..b8eca4e --- /dev/null +++ b/src/Shared/Corathing.UI/Corathing.UI.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/AbstractFileStorageDataSourceContext.cs b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/AbstractFileStorageDataSourceContext.cs new file mode 100644 index 0000000..a9ebaf1 --- /dev/null +++ b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/AbstractFileStorageDataSourceContext.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Corathing.Widgets.Basics.DataSources.FileStorages; + +public abstract class AbstractFileStorageDataSourceContext +{ +} diff --git a/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceContext.cs b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceContext.cs similarity index 88% rename from src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceContext.cs rename to src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceContext.cs index 1b01baf..d81eb5f 100644 --- a/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceContext.cs +++ b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceContext.cs @@ -12,7 +12,7 @@ namespace Corathing.Widgets.Basics.DataSources.FileStorages; [EntryCoraDataSource( - dataSourceType: typeof(FileStorageDataSourceContext), + dataSourceType: typeof(LocalFileStorageDataSourceContext), name: "Executable App", description: "Execute an executable app with the selected files.", defaultTitle: "DefaultApp" @@ -23,6 +23,6 @@ namespace Corathing.Widgets.Basics.DataSources.FileStorages; [EntryCoraDataSourceName(ApplicationLanguage.ko_KR, "실행 앱")] [EntryCoraDataSourceDescription(ApplicationLanguage.en_US, "Execute an executable app with the selected files")] [EntryCoraDataSourceDescription(ApplicationLanguage.ko_KR, "실행 앱을 통해 선택된 파일들을 실행")] -public class FileStorageDataSourceContext : DataSourceContext +public class LocalFileStorageDataSourceContext : DataSourceContext { } diff --git a/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceSelector.cs b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceSelector.cs similarity index 51% rename from src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceSelector.cs rename to src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceSelector.cs index d9d36d0..0b50392 100644 --- a/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/FileStorageDataSourceSelector.cs +++ b/src/Widgets/Corathing.Widgets.Basics/DataSources/FileStorages/LocalFileStorageDataSourceSelector.cs @@ -8,9 +8,9 @@ namespace Corathing.Widgets.Basics.DataSources.FileStorages; -public partial class FileStorageDataSourceSelector : DataSourceSelector +public partial class LocalFileStorageDataSourceSelector : DataSourceSelector { - public FileStorageDataSourceSelector(IServiceProvider services, Guid? guid = null) : base(services, guid) + public LocalFileStorageDataSourceSelector(IServiceProvider services, Guid? guid = null) : base(services, guid) { } } diff --git a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerDataTemplates.xaml b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerDataTemplates.xaml index 91d8c9b..9734ec5 100644 --- a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerDataTemplates.xaml +++ b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerDataTemplates.xaml @@ -18,17 +18,7 @@ - + diff --git a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerOption.cs b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerOption.cs index 2815082..e9a51e4 100644 --- a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerOption.cs +++ b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerOption.cs @@ -18,6 +18,13 @@ public enum FileOpenType Folders, } +public enum IconSourceType +{ + System, + Svg, + Image, +} + public class FileOpenerOption { public FileOpenType OpenType { get; set; } diff --git a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml new file mode 100644 index 0000000..03796b4 --- /dev/null +++ b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml @@ -0,0 +1,106 @@ + + + + + + + + + + diff --git a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml.cs b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml.cs new file mode 100644 index 0000000..243cd21 --- /dev/null +++ b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidget.xaml.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +using Microsoft.CodeAnalysis; +using Wpf.Ui.Controls; + +using static System.Windows.Forms.AxHost; + +using DataFormats = System.Windows.DataFormats; +using UserControl = System.Windows.Controls.UserControl; + +namespace Corathing.Widgets.Basics.Widgets.FileOpeners; + +/// +/// Interaction logic for FileOpenerWidget.xaml +/// +public partial class FileOpenerWidget : UserControl +{ + /// Identifies the dependency property. + public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register( + nameof(Symbol), + typeof(SymbolRegular), + typeof(FileOpenerWidget), + new PropertyMetadata(SymbolRegular.Empty) + ); + + /// + /// Gets or sets displayed . + /// + public SymbolRegular Symbol + { + get => (SymbolRegular)GetValue(SymbolProperty); + set => SetValue(SymbolProperty, value); + } + + /// + /// The edit mode property + /// + public static readonly DependencyProperty IsDraggingOverProperty = DependencyProperty.Register( + nameof(IsDraggingOver), + typeof(bool), + typeof(FileOpenerWidget), + new PropertyMetadata(false, (d, e) => ((FileOpenerWidget)d).DragOverEnabler())); + + /// + /// Gets or sets a value indicating whether the dashboard is in [edit mode]. + /// + /// true if [edit mode]; otherwise, false. + public bool IsDraggingOver + { + get => (bool)GetValue(IsDraggingOverProperty); + set => SetValue(IsDraggingOverProperty, value); + } + + bool _isDraggingLeaveProgress = false; + + public FileOpenerWidget() + { + InitializeComponent(); + } + + public void DragOverEnabler() + { + + } + + private void Button_FileOrFolder_PreviewDrop(object sender, System.Windows.DragEventArgs e) + { + if (DataContext is not FileOpenerWidgetContext context) + return; + + _isDraggingLeaveProgress = false; + IsDraggingOver = false; + + context.OnDrop(e); + } + + + private void Button_FileOrFolder_PreviewDragOver(object sender, System.Windows.DragEventArgs e) + { + _isDraggingLeaveProgress = false; + if (e.Data == null) + return; + + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) + return; + + if (e.Data.GetData(DataFormats.FileDrop) == null) + return; + + IsDraggingOver = true; + } + + private void Button_FileOrFolder_PreviewDragLeave(object sender, System.Windows.DragEventArgs e) + { + _isDraggingLeaveProgress = true; + Dispatcher.BeginInvoke(new Action(() => + { + if (_isDraggingLeaveProgress) IsDraggingOver = false; + })); + } + + private void Button_DragEnter(object sender, System.Windows.DragEventArgs e) + { + _isDraggingLeaveProgress = false; + } +} diff --git a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidgetContext.cs b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidgetContext.cs index b61f5f1..eedefeb 100644 --- a/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidgetContext.cs +++ b/src/Widgets/Corathing.Widgets.Basics/Widgets/FileOpeners/FileOpenerWidgetContext.cs @@ -3,9 +3,13 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using System.Windows.Media; +using System.Windows; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -15,6 +19,7 @@ using Corathing.Contracts.Services; using Corathing.Widgets.Basics.DataSources.ExecutableApps; using Microsoft.Extensions.DependencyInjection; +using DataFormats = System.Windows.DataFormats; namespace Corathing.Widgets.Basics.Widgets.FileOpeners; @@ -39,7 +44,10 @@ public partial class FileOpenerWidgetContext : WidgetContext private FileOpenType _openType = FileOpenType.Files; [ObservableProperty] - private string _icon; + private string? _icon; + + [ObservableProperty] + private ImageSource? _imageIcon; [ObservableProperty] private ObservableCollection? _filePaths; @@ -48,7 +56,7 @@ public partial class FileOpenerWidgetContext : WidgetContext private ObservableCollection? _folderPaths; [ObservableProperty] - private ExecutableAppDataSourceContext _executableAppDataSourceContext; + private ExecutableAppDataSourceContext? _executableAppDataSourceContext; public override void OnCreate(IServiceProvider services, WidgetState state) { @@ -67,6 +75,41 @@ public override void OnCreate(IServiceProvider services, WidgetState state) }); } + public void OnDrop(System.Windows.DragEventArgs e) + { + if (e.Data == null || State == null) + return; + + if (!e.Data.GetDataPresent(DataFormats.FileDrop)) + return; + + if (e.Data.GetData(DataFormats.FileDrop) is not string[] files || files.Length == 0) + return; + + var isDirectory = File.GetAttributes(files[0]).HasFlag(FileAttributes.Directory); + + if (State.CustomSettings is not FileOpenerOption option) + return; + + if (isDirectory) + { + option.OpenType = FileOpenType.Folders; + option.Folders = new List(); + option.Folders.AddRange(files.Where(file => + File.GetAttributes(file).HasFlag(FileAttributes.Directory))); + } + else + { + option.OpenType = FileOpenType.Files; + option.Files = new List(); + option.Files.AddRange(files.Where(file => + !File.GetAttributes(file).HasFlag(FileAttributes.Directory))); + ImageIcon = GetIcon(files[0]); + } + + ApplyState(State); + } + public override void OnStateChanged(WidgetState state) { if (state.CustomSettings is not FileOpenerOption option) @@ -146,4 +189,15 @@ public void Execute() } } } + + private static ImageSource? GetIcon(string fileName) + { + Icon? icon = System.Drawing.Icon.ExtractAssociatedIcon(fileName); + if (icon == null) + return null; + return System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon( + icon.Handle, + Int32Rect.Empty, + BitmapSizeOptions.FromEmptyOptions()); + } }