From c00c8b1b3786e3f162b6ba4ed786638a230b598b Mon Sep 17 00:00:00 2001 From: Guillermo Venegas Date: Tue, 12 Dec 2023 18:46:12 +0100 Subject: [PATCH] IO: Add support for multiple drag-n-drop files There are operators in Blender that allow the user to import multiple files at the same time, however this functionality is only implemented when importing with blender's file browser, drag and drop files only takes first selected file. The patch adds support for drag and drop multiple files. Notes: * The files are filtered according to the extension of the first selected file. * Not all operators that import files support importing multiple files, so they will still import one. * Changes must be made to allow importers to read all file paths. This will be used in upcoming drag & drop import. Pull Request: https://projects.blender.org/blender/blender/pulls/107230 --- .../editors/interface/interface_drag.cc | 2 +- source/blender/editors/screen/screen_ops.cc | 2 +- .../blender/editors/space_clip/space_clip.cc | 2 +- .../editors/space_console/space_console.cc | 2 +- .../blender/editors/space_file/space_file.cc | 2 +- .../editors/space_image/space_image.cc | 2 +- .../blender/editors/space_node/space_node.cc | 2 +- .../space_sequencer/sequencer_drag_drop.cc | 4 +- .../blender/editors/space_text/space_text.cc | 2 +- .../editors/space_view3d/space_view3d.cc | 2 +- source/blender/windowmanager/CMakeLists.txt | 8 ++ source/blender/windowmanager/WM_api.hh | 28 ++++-- source/blender/windowmanager/WM_types.hh | 11 ++- .../windowmanager/intern/wm_dragdrop.cc | 68 ++++++++++++-- .../windowmanager/intern/wm_dragdrop_test.cc | 93 +++++++++++++++++++ .../blender/windowmanager/intern/wm_window.cc | 17 ++-- 16 files changed, 212 insertions(+), 35 deletions(-) create mode 100644 source/blender/windowmanager/intern/wm_dragdrop_test.cc diff --git a/source/blender/editors/interface/interface_drag.cc b/source/blender/editors/interface/interface_drag.cc index 9a9516ef5b2..10800a2b34d 100644 --- a/source/blender/editors/interface/interface_drag.cc +++ b/source/blender/editors/interface/interface_drag.cc @@ -64,7 +64,7 @@ void UI_but_drag_set_path(uiBut *but, const char *path) if (but->dragflag & UI_BUT_DRAGPOIN_FREE) { WM_drag_data_free(but->dragtype, but->dragpoin); } - but->dragpoin = WM_drag_create_path_data(path); + but->dragpoin = WM_drag_create_path_data(blender::Span(&path, 1)); but->dragflag |= UI_BUT_DRAGPOIN_FREE; } diff --git a/source/blender/editors/screen/screen_ops.cc b/source/blender/editors/screen/screen_ops.cc index d252ec011a4..5730fc22f9d 100644 --- a/source/blender/editors/screen/screen_ops.cc +++ b/source/blender/editors/screen/screen_ops.cc @@ -5944,7 +5944,7 @@ static bool blend_file_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * static void blend_file_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { /* copy drag path to properties */ - RNA_string_set(drop->ptr, "filepath", WM_drag_get_path(drag)); + RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag)); } void ED_keymap_screen(wmKeyConfig *keyconf) diff --git a/source/blender/editors/space_clip/space_clip.cc b/source/blender/editors/space_clip/space_clip.cc index fff564cc7ac..1fc04ba3ba7 100644 --- a/source/blender/editors/space_clip/space_clip.cc +++ b/source/blender/editors/space_clip/space_clip.cc @@ -536,7 +536,7 @@ static void clip_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) PointerRNA itemptr; char dir[FILE_MAX], file[FILE_MAX]; - BLI_path_split_dir_file(WM_drag_get_path(drag), dir, sizeof(dir), file, sizeof(file)); + BLI_path_split_dir_file(WM_drag_get_single_path(drag), dir, sizeof(dir), file, sizeof(file)); RNA_string_set(drop->ptr, "directory", dir); diff --git a/source/blender/editors/space_console/space_console.cc b/source/blender/editors/space_console/space_console.cc index aeee63af694..9bce3be7877 100644 --- a/source/blender/editors/space_console/space_console.cc +++ b/source/blender/editors/space_console/space_console.cc @@ -174,7 +174,7 @@ static bool path_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve static void path_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { char pathname[FILE_MAX + 2]; - SNPRINTF(pathname, "\"%s\"", WM_drag_get_path(drag)); + SNPRINTF(pathname, "\"%s\"", WM_drag_get_single_path(drag)); RNA_string_set(drop->ptr, "text", pathname); } diff --git a/source/blender/editors/space_file/space_file.cc b/source/blender/editors/space_file/space_file.cc index fed15290320..424e9e43850 100644 --- a/source/blender/editors/space_file/space_file.cc +++ b/source/blender/editors/space_file/space_file.cc @@ -797,7 +797,7 @@ static bool filepath_drop_poll(bContext *C, wmDrag *drag, const wmEvent * /*even static void filepath_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { - RNA_string_set(drop->ptr, "filepath", WM_drag_get_path(drag)); + RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag)); } /* region dropbox definition */ diff --git a/source/blender/editors/space_image/space_image.cc b/source/blender/editors/space_image/space_image.cc index 64cc8b79774..ecca3d2dab2 100644 --- a/source/blender/editors/space_image/space_image.cc +++ b/source/blender/editors/space_image/space_image.cc @@ -268,7 +268,7 @@ static bool image_drop_poll(bContext *C, wmDrag *drag, const wmEvent *event) static void image_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { /* copy drag path to properties */ - RNA_string_set(drop->ptr, "filepath", WM_drag_get_path(drag)); + RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag)); } /* area+region dropbox definition */ diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index c1321dd723a..db74db961f4 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -897,7 +897,7 @@ static void node_id_path_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) return; } - const char *path = WM_drag_get_path(drag); + const char *path = WM_drag_get_single_path(drag); if (path) { RNA_string_set(drop->ptr, "filepath", path); RNA_struct_property_unset(drop->ptr, "name"); diff --git a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc index 38b23e7748c..8718d241688 100644 --- a/source/blender/editors/space_sequencer/sequencer_drag_drop.cc +++ b/source/blender/editors/space_sequencer/sequencer_drag_drop.cc @@ -249,7 +249,7 @@ static void sequencer_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) return; } - const char *path = WM_drag_get_path(drag); + const char *path = WM_drag_get_single_path(drag); /* Path dropped. */ if (path) { if (RNA_struct_find_property(drop->ptr, "filepath")) { @@ -335,7 +335,7 @@ static void get_drag_path(const bContext *C, wmDrag *drag, char r_path[FILE_MAX] BLI_path_abs(r_path, BKE_main_blendfile_path_from_global()); } else { - BLI_strncpy(r_path, WM_drag_get_path(drag), FILE_MAX); + BLI_strncpy(r_path, WM_drag_get_single_path(drag), FILE_MAX); } } diff --git a/source/blender/editors/space_text/space_text.cc b/source/blender/editors/space_text/space_text.cc index 938ac4bd5d2..45fde446df3 100644 --- a/source/blender/editors/space_text/space_text.cc +++ b/source/blender/editors/space_text/space_text.cc @@ -316,7 +316,7 @@ static bool text_drop_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*eve static void text_drop_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *drop) { /* copy drag path to properties */ - RNA_string_set(drop->ptr, "filepath", WM_drag_get_path(drag)); + RNA_string_set(drop->ptr, "filepath", WM_drag_get_single_path(drag)); } static bool text_drop_paste_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/) diff --git a/source/blender/editors/space_view3d/space_view3d.cc b/source/blender/editors/space_view3d/space_view3d.cc index 4cba9425ce4..dc7bd693d00 100644 --- a/source/blender/editors/space_view3d/space_view3d.cc +++ b/source/blender/editors/space_view3d/space_view3d.cc @@ -893,7 +893,7 @@ static void view3d_id_path_drop_copy(bContext *C, wmDrag *drag, wmDropBox *drop) RNA_struct_property_unset(drop->ptr, "filepath"); return; } - const char *path = WM_drag_get_path(drag); + const char *path = WM_drag_get_single_path(drag); if (path) { RNA_string_set(drop->ptr, "filepath", path); RNA_struct_property_unset(drop->ptr, "image"); diff --git a/source/blender/windowmanager/CMakeLists.txt b/source/blender/windowmanager/CMakeLists.txt index 6737ddeb6fd..4b5e04c43c8 100644 --- a/source/blender/windowmanager/CMakeLists.txt +++ b/source/blender/windowmanager/CMakeLists.txt @@ -210,3 +210,11 @@ blender_add_lib_nolist(bf_windowmanager "${SRC}" "${INC}" "${INC_SYS}" "${LIB}") # RNA_prototypes.h add_dependencies(bf_windowmanager bf_rna) + +if(WITH_GTESTS) + set(TEST_SRC + intern/wm_dragdrop_test.cc + ) + include(GTestTesting) + blender_add_test_lib(bf_wm_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB}") +endif() diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index f060c912da1..88d3bb3c59c 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -1424,16 +1424,32 @@ const ListBase *WM_drag_asset_list_get(const wmDrag *drag); const char *WM_drag_get_item_name(wmDrag *drag); -/* Path drag and drop. */ +/* Paths drag and drop. */ /** - * \param path: The path to drag. Value will be copied into the drag data so the passed string may - * be destructed. + * \param paths: The paths to drag. Values will be copied into the drag data so the passed strings + * may be destructed. */ -wmDragPath *WM_drag_create_path_data(const char *path); -const char *WM_drag_get_path(const wmDrag *drag); +wmDragPath *WM_drag_create_path_data(blender::Span paths); +/* If #drag contains path data, returns the first path int he path list. */ +const char *WM_drag_get_single_path(const wmDrag *drag); +/* If #drag contains path data, returns the first path in the path list that maches a + * a `file_type`.*/ +/* + * \param drag: The drag that could contain drag path data. + * \param file_type: `eFileSel_File_Types` bit flag + */ +const char *WM_drag_get_single_path(const wmDrag *drag, int file_type); +blender::Span WM_drag_get_paths(const wmDrag *drag); +/* If #drag contains path data, returns if any file path match a `file_type`.*/ +/* + * \param drag: The drag that could contain drag path data. + * \param file_type: `eFileSel_File_Types` bit flag + */ +bool WM_drag_has_path_file_type(const wmDrag *drag, int file_type); /** * Note that even though the enum return type uses bit-flags, this should never have multiple - * type-bits set, so `ELEM()` like comparison is possible. + * type-bits set, so `ELEM()` like comparison is possible. To check all paths or to do a bit-flag + * check use `WM_drag_has_path_file_type(drag,file_type)` instead. */ int /* eFileSel_File_Types */ WM_drag_get_path_file_type(const wmDrag *drag); diff --git a/source/blender/windowmanager/WM_types.hh b/source/blender/windowmanager/WM_types.hh index 90a89188ba8..f0a3076a293 100644 --- a/source/blender/windowmanager/WM_types.hh +++ b/source/blender/windowmanager/WM_types.hh @@ -111,6 +111,7 @@ struct wmWindowManager; #include "BLI_compiler_attrs.h" #include "BLI_utildefines.h" +#include "BLI_vector.hh" #include "DNA_listBase.h" #include "DNA_uuid_types.h" #include "DNA_vec_types.h" @@ -1179,10 +1180,12 @@ struct wmDragAssetListItem { }; struct wmDragPath { - char *path; - /* Note that even though the enum type uses bit-flags, this should never have multiple type-bits - * set, so `ELEM()` like comparison is possible. */ - int file_type; /* eFileSel_File_Types */ + blender::Vector paths; + /* File type of each path in #paths. */ + blender::Vector file_types; /* eFileSel_File_Types */ + /* Bit flag of file types in #paths. */ + int file_types_bit_flag; /* eFileSel_File_Types */ + std::string tooltip; }; struct wmDragGreasePencilLayer { diff --git a/source/blender/windowmanager/intern/wm_dragdrop.cc b/source/blender/windowmanager/intern/wm_dragdrop.cc index 9eb739e7c20..a0f94fce155 100644 --- a/source/blender/windowmanager/intern/wm_dragdrop.cc +++ b/source/blender/windowmanager/intern/wm_dragdrop.cc @@ -60,6 +60,7 @@ #include "wm_event_system.h" #include "wm_window.hh" +#include /* ****************************************************** */ static ListBase dropboxes = {nullptr, nullptr}; @@ -762,29 +763,80 @@ const ListBase *WM_drag_asset_list_get(const wmDrag *drag) return &drag->asset_items; } -wmDragPath *WM_drag_create_path_data(const char *path) +wmDragPath *WM_drag_create_path_data(blender::Span paths) { + BLI_assert(!paths.is_empty()); wmDragPath *path_data = MEM_new("wmDragPath"); - path_data->path = BLI_strdup(path); - path_data->file_type = ED_path_extension_type(path); + + for (const char *path : paths) { + path_data->paths.append(path); + path_data->file_types_bit_flag |= ED_path_extension_type(path); + path_data->file_types.append(ED_path_extension_type(path)); + } + + path_data->tooltip = path_data->paths[0]; + + if (path_data->paths.size() > 1) { + std::string path_count = std::to_string(path_data->paths.size()); + path_data->tooltip = fmt::format(TIP_("Dragging {} files"), path_count); + } + return path_data; } static void wm_drag_free_path_data(wmDragPath **path_data) { - MEM_freeN((*path_data)->path); MEM_delete(*path_data); *path_data = nullptr; } -const char *WM_drag_get_path(const wmDrag *drag) +const char *WM_drag_get_single_path(const wmDrag *drag) { if (drag->type != WM_DRAG_PATH) { return nullptr; } const wmDragPath *path_data = static_cast(drag->poin); - return path_data->path; + return path_data->paths[0].c_str(); +} + +const char *WM_drag_get_single_path(const wmDrag *drag, int file_type) +{ + if (drag->type != WM_DRAG_PATH) { + return nullptr; + } + const wmDragPath *path_data = static_cast(drag->poin); + auto const file_types = path_data->file_types; + + auto itr = std::find_if( + file_types.begin(), file_types.end(), [file_type](const int file_fype_test) { + return file_fype_test & file_type; + }); + + if (itr == file_types.end()) { + return nullptr; + } + const int index = itr - file_types.begin(); + return path_data->paths[index].c_str(); +} + +bool WM_drag_has_path_file_type(const wmDrag *drag, int file_type) +{ + if (drag->type != WM_DRAG_PATH) { + return false; + } + const wmDragPath *path_data = static_cast(drag->poin); + return bool(path_data->file_types_bit_flag & file_type); +} + +blender::Span WM_drag_get_paths(const wmDrag *drag) +{ + if (drag->type != WM_DRAG_PATH) { + return blender::Span(); + } + + const wmDragPath *path_data = static_cast(drag->poin); + return path_data->paths.as_span(); } int WM_drag_get_path_file_type(const wmDrag *drag) @@ -794,7 +846,7 @@ int WM_drag_get_path_file_type(const wmDrag *drag) } const wmDragPath *path_data = static_cast(drag->poin); - return path_data->file_type; + return path_data->file_types[0]; } /* ************** draw ***************** */ @@ -848,7 +900,7 @@ const char *WM_drag_get_item_name(wmDrag *drag) } case WM_DRAG_PATH: { const wmDragPath *path_drag_data = static_cast(drag->poin); - return path_drag_data->path; + return path_drag_data->tooltip.c_str(); } case WM_DRAG_NAME: return static_cast(drag->poin); diff --git a/source/blender/windowmanager/intern/wm_dragdrop_test.cc b/source/blender/windowmanager/intern/wm_dragdrop_test.cc new file mode 100644 index 00000000000..215cef0d140 --- /dev/null +++ b/source/blender/windowmanager/intern/wm_dragdrop_test.cc @@ -0,0 +1,93 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: Apache-2.0 */ + +#include "testing/testing.h" + +/* #eFileSel_File_Types. */ +#include "DNA_space_types.h" + +#include "WM_api.hh" +#include "WM_types.hh" + +namespace blender::tests { + +TEST(wm_drag, wmDragPath) +{ + { + /** + * NOTE: `WM_drag_create_path_data` gets the `file_type` from the first path in `paths` and + * only needs its extension, so there is no need to describe a full path here that can have a + * different format on Windows or Linux. However callers must ensure that they are valid paths. + */ + blender::Vector paths{"text_file.txt"}; + wmDragPath *path_data = WM_drag_create_path_data(paths); + blender::Vector expected_file_paths{"text_file.txt"}; + + EXPECT_EQ(path_data->paths.size(), 1); + EXPECT_EQ(path_data->tooltip, "text_file.txt"); + EXPECT_EQ(path_data->paths, expected_file_paths); + + /** Test `wmDrag` path data getters. */ + wmDrag drag; + drag.type = WM_DRAG_PATH; + drag.poin = path_data; + EXPECT_STREQ(WM_drag_get_single_path(&drag), "text_file.txt"); + EXPECT_EQ(WM_drag_get_path_file_type(&drag), FILE_TYPE_TEXT); + EXPECT_EQ(WM_drag_get_paths(&drag), expected_file_paths.as_span()); + EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT), "text_file.txt"); + EXPECT_EQ(WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER), nullptr); + EXPECT_TRUE( + WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE)); + EXPECT_FALSE(WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE)); + MEM_delete(path_data); + } + { + blender::Vector paths = {"blender.blend", "text_file.txt", "image.png"}; + wmDragPath *path_data = WM_drag_create_path_data(paths); + blender::Vector expected_file_paths = { + "blender.blend", "text_file.txt", "image.png"}; + + EXPECT_EQ(path_data->paths.size(), 3); + EXPECT_EQ(path_data->tooltip, "Dragging 3 files"); + EXPECT_EQ(path_data->paths, expected_file_paths); + + /** Test `wmDrag` path data getters. */ + wmDrag drag; + drag.type = WM_DRAG_PATH; + drag.poin = path_data; + EXPECT_STREQ(WM_drag_get_single_path(&drag), "blender.blend"); + EXPECT_EQ(WM_drag_get_path_file_type(&drag), FILE_TYPE_BLENDER); + EXPECT_EQ(WM_drag_get_paths(&drag), expected_file_paths.as_span()); + EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER), "blender.blend"); + EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_IMAGE), "image.png"); + EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT), "text_file.txt"); + EXPECT_STREQ( + WM_drag_get_single_path(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE), + "blender.blend"); + EXPECT_STREQ(WM_drag_get_single_path(&drag, FILE_TYPE_TEXT | FILE_TYPE_IMAGE), + "text_file.txt"); + EXPECT_EQ(WM_drag_get_single_path(&drag, FILE_TYPE_ASSET), nullptr); + EXPECT_TRUE( + WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_TEXT | FILE_TYPE_IMAGE)); + EXPECT_TRUE(WM_drag_has_path_file_type(&drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE)); + EXPECT_TRUE(WM_drag_has_path_file_type(&drag, FILE_TYPE_IMAGE)); + EXPECT_FALSE(WM_drag_has_path_file_type(&drag, FILE_TYPE_ASSET)); + MEM_delete(path_data); + } + { + /** Test `wmDrag` path data getters when the drag type is different to `WM_DRAG_PATH`. */ + wmDrag drag; + drag.type = WM_DRAG_COLOR; + EXPECT_EQ(WM_drag_get_single_path(&drag), nullptr); + EXPECT_EQ(WM_drag_get_path_file_type(&drag), 0); + EXPECT_EQ(WM_drag_get_paths(&drag).size(), 0); + EXPECT_EQ(WM_drag_get_single_path( + &drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE | FILE_TYPE_TEXT | FILE_TYPE_ASSET), + nullptr); + EXPECT_FALSE(WM_drag_has_path_file_type( + &drag, FILE_TYPE_BLENDER | FILE_TYPE_IMAGE | FILE_TYPE_TEXT | FILE_TYPE_ASSET)); + } +} + +} // namespace blender::tests diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 9ac93aa8bc5..53a01f6a6e9 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -15,6 +15,8 @@ #include #include +#include "CLG_log.h" + #include "DNA_listBase.h" #include "DNA_screen_types.h" #include "DNA_windowmanager_types.h" @@ -1649,14 +1651,17 @@ static bool ghost_event_proc(GHOST_EventHandle ghost_event, GHOST_TUserDataPtr C if (ddd->dataType == GHOST_kDragnDropTypeFilenames) { const GHOST_TStringArray *stra = static_cast(ddd->data); - for (int a = 0; a < stra->count; a++) { - printf("drop file %s\n", stra->strings[a]); - /* try to get icon type from extension */ - int icon = ED_file_extension_icon((char *)stra->strings[a]); - wmDragPath *path_data = WM_drag_create_path_data((char *)stra->strings[a]); + if (stra->count) { + CLOG_INFO(WM_LOG_EVENTS, 1, "Drop %d files:", stra->count); + for (const char *path : blender::Span((char **)stra->strings, stra->count)) { + CLOG_INFO(WM_LOG_EVENTS, 1, "%s", path); + } + /* Try to get icon type from extension of the first path. */ + int icon = ED_file_extension_icon((char *)stra->strings[0]); + wmDragPath *path_data = WM_drag_create_path_data( + blender::Span((char **)stra->strings, stra->count)); WM_event_start_drag(C, icon, WM_DRAG_PATH, path_data, 0.0, WM_DRAG_NOP); /* Void pointer should point to string, it makes a copy. */ - break; /* only one drop element supported now */ } }