IO: Add support for multiple drag-n-drop files #107230

Merged
Brecht Van Lommel merged 20 commits from guishe/blender:dragndrop-files into main 2023-12-12 18:46:22 +01:00
16 changed files with 212 additions and 35 deletions

View File

@ -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;
}

View File

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

View File

@ -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);

View File

@ -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);
}

View File

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

View File

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

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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*/)

View File

@ -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");

View File

@ -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()

View File

@ -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<const char *> 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<std::string> 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);

View File

@ -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<std::string> paths;
/* File type of each path in #paths. */
blender::Vector<int> 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 {

View File

@ -60,6 +60,7 @@
#include "wm_event_system.h"
#include "wm_window.hh"
#include <fmt/format.h>
/* ****************************************************** */
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<const char *> paths)
{
BLI_assert(!paths.is_empty());
wmDragPath *path_data = MEM_new<wmDragPath>("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];
guishe marked this conversation as resolved
Review

No final point in our UI strings: "Dragging {} files"

No final point in our UI strings: `"Dragging {} files"`
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<const wmDragPath *>(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<const wmDragPath *>(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<const wmDragPath *>(drag->poin);
return bool(path_data->file_types_bit_flag & file_type);
}
blender::Span<std::string> WM_drag_get_paths(const wmDrag *drag)
{
if (drag->type != WM_DRAG_PATH) {
return blender::Span<std::string>();
}
const wmDragPath *path_data = static_cast<const wmDragPath *>(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<const wmDragPath *>(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<const wmDragPath *>(drag->poin);
return path_drag_data->path;
return path_drag_data->tooltip.c_str();
}
case WM_DRAG_NAME:
return static_cast<const char *>(drag->poin);

View File

@ -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"
brecht marked this conversation as resolved
Review

Doesn't seem to be needed.

Doesn't seem to be needed.

Needed by wmDrag.drop_state.ui_context, is a std::unique_ptr<bContextStore>

Needed by `wmDrag.drop_state.ui_context`, is a `std::unique_ptr<bContextStore>`
Review

While not a big deal, this include shouldn't be necessary. Committed e3b3399bcb so this can be removed now.

While not a big deal, this include shouldn't be necessary. Committed e3b3399bcb so this can be removed now.
#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<const char *> paths{"text_file.txt"};
wmDragPath *path_data = WM_drag_create_path_data(paths);
blender::Vector<std::string> 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<const char *> paths = {"blender.blend", "text_file.txt", "image.png"};
wmDragPath *path_data = WM_drag_create_path_data(paths);
blender::Vector<std::string> 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

View File

@ -15,6 +15,8 @@
#include <cstring>
#include <thread>
#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<const GHOST_TStringArray *>(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 */
}
}