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 */ } }