PowerToys
PowerToys copied to clipboard
[PTRun] Drag and drop files
Summary of the Pull Request
Support drag&drop to other application for files in result list.
PR Checklist
- [x] Closes: #4462
- [ ] Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
- [ ] Dev docs: Added/updated
Detailed Description of the Pull Request / Additional comments
To implement this behaviour, I added the following things:
- detect dragging an item from the suggestions list
- check whether this item represents a file/folder (something path-related)
- perform a DragDrop with path as FileDrop
- make related plugins (that return files/folders) mark their results as draggable file/folder
detect dragging an item from the suggestions list
extend src/modules/launcher/PowerLauncher/MainWindow.xaml.cs Save mouse position on mouse down and check aggainst system drag distance
private void SuggestionsList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_mouseDownPosition = e.GetPosition(null);
_mouseDownResultViewModel = ((FrameworkElement)e.OriginalSource).DataContext as ResultViewModel;
}
private void SuggestionsList_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && ...)
{
Vector dragDistance = _mouseDownPosition - e.GetPosition(null);
if (Math.Abs(dragDistance.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(dragDistance.Y) > SystemParameters.MinimumVerticalDragDistance)
{
...
}
}
}
check whether this item represents a file/folder (something path-related)
Since all plugins that return files/folders put their data objects containing the path information into Result.ContextData
, I decided to introduce an interface IFileDropResult
to indicate that a result contains a file/folder to be draggable.
src/modules/launcher/Wox.Plugin/Interfaces/IFileDropResult.cs
public interface IFileDropResult
{
public string Path { get; set; }
}
So we can check whether the result connected to the view model of the clicked result item implements this interface.
private void SuggestionsList_MouseMove(object sender, MouseEventArgs e)
{
if (... && _mouseDownResultViewModel?.Result?.ContextData is IFileDropResult fileDropResult)
{ ... }
}
perform a DragDrop with path as FileDrop
If so, the PTRun window will be hidden and the corresponding path of the dragged item will be made available for dragging as FileDrop:
_viewModel.Hide();
DataObject dataObject = new DataObject(DataFormats.FileDrop, new[] { fileDropResult.Path });
DragDrop.DoDragDrop(ListBox.SuggestionsList, dataObject, DragDropEffects.Copy);
make related plugins (that return files/folders) mark their results as draggable file/folder
The search result types of the following plugins now implement IFileDropResult
:
- Folder/SearchResult.cs
- Indexer/SearchHelper/SearchResult.cs
- (Everything/SearchResult.cs)
For unification, I had to rename Folder/SearchResult's FullPath
to Path
.
Validation Steps Performed
(See screen recording)
Is this feature only for folder plugin? It might make sense to have it in the indexer plugin too.
As already stated above, this feature is available for the following plugins:
- Folder/SearchResult.cs
- Indexer/SearchHelper/SearchResult.cs
- (Everything/SearchResult.cs)
As already stated above, this feature is available for the following plugins:
- Folder/SearchResult.cs
- Indexer/SearchHelper/SearchResult.cs
- (Everything/SearchResult.cs)
But is it correct that the everything plugin needs to be updated to implement the new interface?
Yes. But I can't do it without having this changes in PTRun first. :-)
Do we have a check if the access to the path is denied. We should prevent a crash in this case.
Do we have a check if the access to the path is denied. We should prevent a crash in this case.
Hm, not sure what to do... The Drag&Drop does not process "the file" in any way but only passes the path (string) as data to the shell. So DoDragDrop
should not fail, because what the target does with the path is the responsibility of the drop target. The transfer of the data (path as string) itself is successful if a target accepts the drop. A drop target application does not even have to open access the path but only process the provided path in some way (e.g. add to text).
Open questions:
-
Should return value of
DragDrop.DoDragDrop(ListBox.SuggestionsList, dataObject, DragDropEffects.Copy);
be checked in any way?DoDragDrop
will block the execution until drag&drop is finished - the result value isDragDropEffects.Copy
if some drop target accepted the drop orDragDropEffects.None
if there wasn't. Maybe make the search results visible aggain if there was no drop? -
An alternative implementation to indicate file/folder search results (current proposal: a special type (
IFileDropResult
) for theResult.ContextData
property) is to make the file/folder path a first class property ofResult
. Would that be more elgant? -
I have a working solution for adding the file thumbnail as drop image. Should I include that into this pull request?
Do we have a check if the access to the path is denied. We should prevent a crash in this case.
Hm, not sure what to do... The Drag&Drop does not process "the file" in any way but only passes the path (string) as data to the shell. So
DoDragDrop
should not fail, because what the target does with the path is the responsibility of the drop target. The transfer of the data (path as string) itself is successful if a target accepts the drop. A drop target application does not even have to open access the path but only process the provided path in some way (e.g. add to text).Open questions:
- Should return value of
DragDrop.DoDragDrop(ListBox.SuggestionsList, dataObject, DragDropEffects.Copy);
be checked in any way?DoDragDrop
will block the execution until drag&drop is finished - the result value isDragDropEffects.Copy
if some drop target accepted the drop orDragDropEffects.None
if there wasn't. Maybe make the search results visible aggain if there was no drop?
Regarding the error check: If we can't crash or hang I see no reason to handle something in our code.
- An alternative implementation to indicate file/folder search results (current proposal: a special type (
IFileDropResult
) for theResult.ContextData
property) is to make the file/folder path a first class property ofResult
. Would that be more elgant?
Not sure. But it shouldn't be to complicated.
- I have a working solution for adding the file thumbnail as drop image. Should I include that into this pull request?
Regarding the thumbnail: Do you have a screenshot of what you mean?
no drag image | with file thumbnail as drag image |
---|---|
![]() |
![]() |
Note: Displayingd the drag image is also the responsibility of the drop target. In the shown example it's Windows Explorer.
no drag image with file thumbnail as drag image Note: Displayingd the drag image is also the responsibility of the drop target. In the shown example it's Windows Explorer.
Looks good. Let's push it.
@check-spelling-bot Report
:red_circle: Please review
See the :open_file_folder: files view or the :scroll:action log for details.
Unrecognized words (1)
pshdi
To accept :heavy_check_mark: these unrecognized words as correct, run the following commands
... in a clone of the [email protected]:daniel-richter/PowerToys-FileDrop.git repository
on the main
branch (:information_source: how do I use this?):
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/v0.0.21/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/3603301370/attempts/1'
Available :books: dictionaries could cover words not in the :blue_book: dictionary
This includes both expected items (2138) from .github/actions/spell-check/expect.txt and unrecognized words (1)
Dictionary | Entries | Covers |
---|---|---|
cspell:cpp/src/cpp.txt | 30216 | 121 |
cspell:win32/src/win32.txt | 53509 | 118 |
cspell:python/src/python/python-lib.txt | 3873 | 30 |
cspell:php/php.txt | 2597 | 16 |
cspell:node/node.txt | 1768 | 14 |
cspell:typescript/typescript.txt | 1211 | 12 |
cspell:java/java.txt | 7642 | 11 |
cspell:python/src/python/python.txt | 453 | 10 |
cspell:r/src/r.txt | 808 | 8 |
cspell:python/src/common/extra.txt | 741 | 7 |
Consider adding them using (in .github/workflows/spelling2.yml
):
with:
extra_dictionaries:
cspell:cpp/src/cpp.txt
cspell:win32/src/win32.txt
cspell:python/src/python/python-lib.txt
cspell:php/php.txt
cspell:node/node.txt
cspell:typescript/typescript.txt
cspell:java/java.txt
cspell:python/src/python/python.txt
cspell:r/src/r.txt
cspell:python/src/common/extra.txt
To stop checking additional dictionaries, add:
with:
check_extra_dictionaries: ''
If the flagged items are :exploding_head: false positives
If items relate to a ...
-
binary file (or some other file you wouldn't want to check at all).
Please add a file path to the
excludes.txt
file matching the containing file.File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.
^
refers to the file's path from the root of the repository, so^README\.md$
would exclude README.md (on whichever branch you're using). -
well-formed pattern.
If you can write a pattern that would match it, try adding it to the
patterns.txt
file.Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
Looks good. Let's push it.
Done. Changed DoDragDrop
to:
_viewModel.Hide();
try
{
// DoDragDrop with file thumbnail as drag image
var dataObject = DragDataObject.FromFile(fileDropResult.Path);
IntPtr hBitmap = Image.WindowsThumbnailProvider.GetHBitmap(Path.GetFullPath(fileDropResult.Path), Constant.ThumbnailSize, Constant.ThumbnailSize, Image.ThumbnailOptions.None);
try
{
dataObject.SetDragImage(hBitmap, Constant.ThumbnailSize, Constant.ThumbnailSize);
DragDrop.DoDragDrop(ListBox.SuggestionsList, dataObject, DragDropEffects.Copy);
}
finally
{
Image.NativeMethods.DeleteObject(hBitmap);
}
}
catch
{
// DoDragDrop without drag image
IDataObject dataObject = new DataObject(DataFormats.FileDrop, new[] { fileDropResult.Path });
DragDrop.DoDragDrop(ListBox.SuggestionsList, dataObject, DragDropEffects.Copy);
}
I added src/modules/launcher/PowerLauncher/Helper/DragDataObject.cs with helper methods to deal with shell data objects (and drag images) and had to make Image.WindowsThumbnailProvider.GetHBitmap
public (because a HBitmap/IntPtr is needed for the thumbnail image).
@daniel-richter When do we create the hBitmap? Can this make search or dragging slow?
When do we create the hBitmap? Can this make search or dragging slow?
It's done before starting the drag&drop action (after clicking one result item and dragging the mouse the minumum drag distance). So drag&drop function won't affect searching in any way.
Creating the hBitmap could be slow, but the Folder and Indexer plugins will set the IconPath
to the file/folder, so the thumbnails will be loaded anyway. (The Everything plugin has a setting do diable preview.)
The thumbnails for search results will be cached (ImageSource Wox.Infrastructure.Image.ImageLoader.Load
). Also, the drag icon itself is already present in PowerLauncher.ViewModel.ResultViewModel.Image
resp. _mouseDownResultViewModel.Image
. But I couldn't find a nice way get an hBitmap from an ImageSource
typed object.
If that could be done, we could simply reuse the already loaded icon from the search result.
@check-spelling-bot Report
:red_circle: Please review
See the :open_file_folder: files view or the :scroll:action log for details.
Unrecognized words (2)
bgr weblog
Previously acknowledged words that are now absent
BGR :arrow_right:To accept :heavy_check_mark: these unrecognized words as correct and remove the previously acknowledged and now absent words, run the following commands
... in a clone of the [email protected]:daniel-richter/PowerToys-FileDrop.git repository
on the main
branch (:information_source: how do I use this?):
curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/v0.0.21/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/3621892784/attempts/1'
Available :books: dictionaries could cover words not in the :blue_book: dictionary
This includes both expected items (2138) from .github/actions/spell-check/expect.txt and unrecognized words (2)
Dictionary | Entries | Covers |
---|---|---|
cspell:cpp/src/cpp.txt | 30216 | 122 |
cspell:win32/src/win32.txt | 53509 | 117 |
cspell:python/src/python/python-lib.txt | 3873 | 30 |
cspell:php/php.txt | 2597 | 16 |
cspell:node/node.txt | 1768 | 14 |
cspell:typescript/typescript.txt | 1211 | 12 |
cspell:java/java.txt | 7642 | 11 |
cspell:python/src/python/python.txt | 453 | 10 |
cspell:r/src/r.txt | 808 | 8 |
cspell:python/src/common/extra.txt | 741 | 7 |
Consider adding them using (in .github/workflows/spelling2.yml
):
with:
extra_dictionaries:
cspell:cpp/src/cpp.txt
cspell:win32/src/win32.txt
cspell:python/src/python/python-lib.txt
cspell:php/php.txt
cspell:node/node.txt
cspell:typescript/typescript.txt
cspell:java/java.txt
cspell:python/src/python/python.txt
cspell:r/src/r.txt
cspell:python/src/common/extra.txt
To stop checking additional dictionaries, add:
with:
check_extra_dictionaries: ''
If the flagged items are :exploding_head: false positives
If items relate to a ...
-
binary file (or some other file you wouldn't want to check at all).
Please add a file path to the
excludes.txt
file matching the containing file.File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.
^
refers to the file's path from the root of the repository, so^README\.md$
would exclude README.md (on whichever branch you're using). -
well-formed pattern.
If you can write a pattern that would match it, try adding it to the
patterns.txt
file.Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.
[...] the drag icon itself is already present in
PowerLauncher.ViewModel.ResultViewModel.Image
resp._mouseDownResultViewModel.Image
. But I couldn't find a nice way get an hBitmap from anImageSource
typed object. If that could be done, we could simply reuse the already loaded icon from the search result.
Okay, found a solution in https://stackoverflow.com/a/2897325
Now, PowerLauncher.ViewModel.ResultViewModel.Image
is (re)used for the drag image.