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

[PTRun] Drag and drop files #22409

Merged
merged 5 commits into from
Dec 9, 2022
Merged

[PTRun] Drag and drop files #22409

merged 5 commits into from
Dec 9, 2022

Conversation

daniel-richter
Copy link
Contributor

@daniel-richter daniel-richter commented Dec 1, 2022

Summary of the Pull Request

Support drag&drop to other application for files in result list.
Recording 2022-12-01 at 20 03 27

PR Checklist

  • Closes: [Run] Drag and drop files #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:

For unification, I had to rename Folder/SearchResult's FullPath to Path.

Validation Steps Performed

(See screen recording)

Recording 2022-12-01 at 19 59 50

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 1, 2022

Is this feature only for folder plugin? It might make sense to have it in the indexer plugin too.

@daniel-richter
Copy link
Contributor Author

As already stated above, this feature is available for the following plugins:

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 1, 2022

As already stated above, this feature is available for the following plugins:

But is it correct that the everything plugin needs to be updated to implement the new interface?

@daniel-richter
Copy link
Contributor Author

Yes. But I can't do it without having this changes in PTRun first. :-)

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 2, 2022

Do we have a check if the access to the path is denied. We should prevent a crash in this case.

@daniel-richter
Copy link
Contributor Author

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:

  1. 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 is DragDropEffects.Copy if some drop target accepted the drop or DragDropEffects.None if there wasn't.
    Maybe make the search results visible aggain if there was no drop?

  2. An alternative implementation to indicate file/folder search results (current proposal: a special type (IFileDropResult) for the Result.ContextData property) is to make the file/folder path a first class property of Result. Would that be more elgant?

  3. I have a working solution for adding the file thumbnail as drop image. Should I include that into this pull request?

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 2, 2022

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:

  1. 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 is DragDropEffects.Copy if some drop target accepted the drop or DragDropEffects.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.

  1. An alternative implementation to indicate file/folder search results (current proposal: a special type (IFileDropResult) for the Result.ContextData property) is to make the file/folder path a first class property of Result. Would that be more elgant?

Not sure. But it shouldn't be to complicated.

  1. 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?

@daniel-richter
Copy link
Contributor Author

daniel-richter commented Dec 2, 2022

no drag image with file thumbnail as drag image
Recording 2022-12-02 at 16 16 17 Recording 2022-12-02 at 16 18 16

Note: Displayingd the drag image is also the responsibility of the drop target. In the shown example it's Windows Explorer.

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 2, 2022

no drag image with file thumbnail as drag image
Recording 2022-12-02 at 16 16 17 Recording 2022-12-02 at 16 18 16

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.

@github-actions

This comment has been minimized.

@daniel-richter
Copy link
Contributor Author

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).

@htcfreek
Copy link
Collaborator

htcfreek commented Dec 5, 2022

@daniel-richter
When do we create the hBitmap? Can this make search or dragging slow?

@daniel-richter
Copy link
Contributor Author

daniel-richter commented Dec 5, 2022

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.

@github-actions

This comment has been minimized.

@daniel-richter
Copy link
Contributor Author

daniel-richter commented Dec 5, 2022

[...] 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.

Okay, found a solution in https://stackoverflow.com/a/2897325
Now, PowerLauncher.ViewModel.ResultViewModel.Image is (re)used for the drag image.

@jaimecbernardo jaimecbernardo self-requested a review December 9, 2022 11:01
Copy link
Collaborator

@jaimecbernardo jaimecbernardo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty nice feature, and it's working very well!
Thanks a lot for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants