diff options
6 files changed, 217 insertions, 8 deletions
diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py index 4a285ea2bad..0728cbf8f9f 100644 --- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py +++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py @@ -1793,6 +1793,8 @@ def km_file_browser(params): {"properties": [("data_path", 'space_data.params.show_hidden')]}), ("file.directory_new", {"type": 'I', "value": 'PRESS'}, {"properties": [("confirm", False)]}), + ("file.delete", {"type": 'X', "value": 'PRESS'}, None), + ("file.delete", {"type": 'DEL', "value": 'PRESS'}, None), ("file.smoothscroll", {"type": 'TIMER1', "value": 'ANY', "any": True}, None), ("file.bookmark_add", {"type": 'B', "value": 'PRESS', "ctrl": True}, None), ("file.filenum", {"type": 'NUMPAD_PLUS', "value": 'PRESS'}, diff --git a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py index 7427db6ebe7..c5c1bfa7f7b 100644 --- a/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py +++ b/release/scripts/presets/keyconfig/keymap_data/industry_compatible_data.py @@ -1177,10 +1177,13 @@ def km_file_browser(params): ("file.next", {"type": 'RIGHT_ARROW', "value": 'PRESS', "alt": True}, None), ("file.next", {"type": 'RIGHT_ARROW', "value": 'PRESS', "ctrl": True}, None), ("file.refresh", {"type": 'R', "value": 'PRESS', "ctrl": True}, None), + ("file.previous", {"type": 'BACK_SPACE', "value": 'PRESS'}, None), + ("file.next", {"type": 'BACK_SPACE', "value": 'PRESS', "shift": True}, None), ("wm.context_toggle", {"type": 'H', "value": 'PRESS'}, {"properties": [("data_path", 'space_data.params.show_hidden')]}), ("file.directory_new", {"type": 'I', "value": 'PRESS'}, {"properties": [("confirm", False)]}), + ("file.delete", {"type": 'DEL', "value": 'PRESS'}, None), ("file.smoothscroll", {"type": 'TIMER1', "value": 'ANY', "any": True}, None), ("wm.context_toggle", {"type": 'T', "value": 'PRESS'}, {"properties": [("data_path", 'space_data.show_region_toolbar')]}), diff --git a/release/scripts/startup/bl_ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index a322b96f9dd..e1097e8d512 100644 --- a/release/scripts/startup/bl_ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -469,7 +469,12 @@ class FILEBROWSER_MT_context_menu(Menu): layout.separator() layout.operator("file.rename", text="Rename") - # layout.operator("file.delete") + sub = layout.row() + sub.operator_context = 'EXEC_DEFAULT' + sub.operator("file.delete", text="Delete") + + layout.separator() + sub = layout.row() sub.operator_context = 'EXEC_DEFAULT' sub.operator("file.directory_new", text="New Folder") @@ -503,5 +508,6 @@ classes = ( if __name__ == "__main__": # only for live edit. from bpy.utils import register_class + for cls in classes: register_class(cls) diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index d78f167a8fd..bdf7588291f 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -50,6 +50,7 @@ int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(); int BLI_copy(const char *path, const char *to) ATTR_NONNULL(); int BLI_rename(const char *from, const char *to) ATTR_NONNULL(); int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL(); +int BLI_delete_soft(const char *path, const char **error_message) ATTR_NONNULL(); #if 0 /* Unused */ int BLI_move(const char *path, const char *to) ATTR_NONNULL(); int BLI_create_symlink(const char *path, const char *to) ATTR_NONNULL(); diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 99149f5ea42..3a45989fb63 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -33,16 +33,24 @@ #include "zlib.h" #ifdef WIN32 +# include <windows.h> +# include <shellapi.h> +# include <shobjidl.h> # include <io.h> # include "BLI_winstuff.h" # include "BLI_fileops_types.h" # include "utf_winfunc.h" # include "utfconv.h" #else +# if defined(__APPLE__) +# include <CoreFoundation/CoreFoundation.h> +# include <objc/runtime.h> +# include <objc/message.h> +# endif # include <sys/param.h> # include <dirent.h> # include <unistd.h> -# include <sys/stat.h> +# include <sys/wait.h> #endif #include "MEM_guardedalloc.h" @@ -288,6 +296,64 @@ int BLI_access(const char *filename, int mode) return uaccess(filename, mode); } +static bool delete_soft(const wchar_t *path_16, const char **error_message) +{ + /* Deletes file or directory to recycling bin. The latter moves all contained files and + * directories recursively to the recycling bin as well. */ + IFileOperation *pfo; + IShellItem *pSI; + + HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + + if (FAILED(hr)) { + *error_message = "Failed to initialize COM"; + goto error_1; + } + + hr = CoCreateInstance( + &CLSID_FileOperation, NULL, CLSCTX_ALL, &IID_IFileOperation, (void **)&pfo); + if (FAILED(hr)) { + *error_message = "Failed to create FileOperation instance"; + goto error_2; + } + + /* Flags for deletion: + * FOF_ALLOWUNDO: Enables moving file to recycling bin. + * FOF_SILENT: Don't show progress dialog box. + * FOF_WANTNUKEWARNING: Show dialog box if file can't be moved to recycling bin. */ + hr = pfo->lpVtbl->SetOperationFlags(pfo, FOF_ALLOWUNDO | FOF_SILENT | FOF_WANTNUKEWARNING); + + if (FAILED(hr)) { + *error_message = "Failed to set operation flags"; + goto error_2; + } + + hr = SHCreateItemFromParsingName(path_16, NULL, &IID_IShellItem, (void **)&pSI); + if (FAILED(hr)) { + *error_message = "Failed to parse path"; + goto error_2; + } + + hr = pfo->lpVtbl->DeleteItem(pfo, pSI, NULL); + if (FAILED(hr)) { + *error_message = "Failed to prepare delete operation"; + goto error_2; + } + + hr = pfo->lpVtbl->PerformOperations(pfo); + + if (FAILED(hr)) { + *error_message = "Failed to delete file or directory"; + } + +error_2: + pfo->lpVtbl->Release(pfo); + CoUninitialize(); /* Has to be uninitialized when CoInitializeEx returns either S_OK or S_FALSE + */ +error_1: + return FAILED(hr); +} + static bool delete_unique(const char *path, const bool dir) { bool err; @@ -370,6 +436,24 @@ int BLI_delete(const char *file, bool dir, bool recursive) return err; } +/** + * Moves the files or directories to the recycling bin. + */ +int BLI_delete_soft(const char *file, const char **error_message) +{ + int err; + + BLI_assert(!BLI_path_is_rel(file)); + + UTF16_ENCODE(file); + + err = delete_soft(file_16, error_message); + + UTF16_UN_ENCODE(file); + + return err; +} + /* Not used anywhere! */ # if 0 int BLI_move(const char *file, const char *to) @@ -720,6 +804,100 @@ static int delete_single_file(const char *from, const char *UNUSED(to)) return RecursiveOp_Callback_OK; } +# ifdef __APPLE__ +static int delete_soft(const char *file, const char **error_message) +{ + int ret = -1; + + Class NSAutoreleasePoolClass = objc_getClass("NSAutoreleasePool"); + SEL allocSel = sel_registerName("alloc"); + SEL initSel = sel_registerName("init"); + id poolAlloc = ((id(*)(Class, SEL))objc_msgSend)(NSAutoreleasePoolClass, allocSel); + id pool = ((id(*)(id, SEL))objc_msgSend)(poolAlloc, initSel); + + Class NSStringClass = objc_getClass("NSString"); + SEL stringWithUTF8StringSel = sel_registerName("stringWithUTF8String:"); + id pathString = ((id(*)(Class, SEL, const char *))objc_msgSend)( + NSStringClass, stringWithUTF8StringSel, file); + + Class NSFileManagerClass = objc_getClass("NSFileManager"); + SEL defaultManagerSel = sel_registerName("defaultManager"); + id fileManager = ((id(*)(Class, SEL))objc_msgSend)(NSFileManagerClass, defaultManagerSel); + + Class NSURLClass = objc_getClass("NSURL"); + SEL fileURLWithPathSel = sel_registerName("fileURLWithPath:"); + id nsurl = ((id(*)(Class, SEL, id))objc_msgSend)(NSURLClass, fileURLWithPathSel, pathString); + + SEL trashItemAtURLSel = sel_registerName("trashItemAtURL:resultingItemURL:error:"); + BOOL deleteSuccessful = ((BOOL(*)(id, SEL, id, id, id))objc_msgSend)( + fileManager, trashItemAtURLSel, nsurl, nil, nil); + + if (deleteSuccessful) { + ret = 0; + } + else { + *error_message = "The Cocoa API call to delete file or directory failed"; + } + + SEL drainSel = sel_registerName("drain"); + ((void (*)(id, SEL))objc_msgSend)(pool, drainSel); + + return ret; +} +# else +static int delete_soft(const char *file, const char **error_message) +{ + const char *args[5]; + const char *process_failed; + + char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP"); + char *xdg_session_desktop = getenv("XDG_SESSION_DESKTOP"); + + if ((xdg_current_desktop != NULL && strcmp(xdg_current_desktop, "KDE") == 0) || + (xdg_session_desktop != NULL && strcmp(xdg_session_desktop, "KDE") == 0)) { + args[0] = "kioclient5"; + args[1] = "move"; + args[2] = file; + args[3] = "trash:/"; + args[4] = NULL; + process_failed = "kioclient5 reported failure"; + } + else { + args[0] = "gio"; + args[1] = "trash"; + args[2] = file; + args[3] = NULL; + process_failed = "gio reported failure"; + } + + int pid = fork(); + + if (pid != 0) { + /* Parent process */ + int wstatus = 0; + + waitpid(pid, &wstatus, 0); + + if (!WIFEXITED(wstatus)) { + *error_message = + "Blender may not support moving files or directories to trash on your system."; + return -1; + } + else if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus)) { + *error_message = process_failed; + return -1; + } + + return 0; + } + + execvp(args[0], (char **)args); + + *error_message = "Forking process failed."; + return -1; /* This should only be reached if execvp fails and stack isn't replaced. */ +} +# endif + FILE *BLI_fopen(const char *filename, const char *mode) { BLI_assert(!BLI_path_is_rel(filename)); @@ -770,6 +948,19 @@ int BLI_delete(const char *file, bool dir, bool recursive) } /** + * Soft deletes the specified file or directory (depending on dir) by moving the files to the + * recycling bin, optionally doing recursive delete of directory contents. + * + * \return zero on success (matching 'remove' behavior). + */ +int BLI_delete_soft(const char *file, const char **error_message) +{ + BLI_assert(!BLI_path_is_rel(file)); + + return delete_soft(file, error_message); +} + +/** * Do the two paths denote the same file-system object? */ static bool check_the_same(const char *path_a, const char *path_b) diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index b4b51de302d..5ea95383892 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -2504,23 +2504,29 @@ int file_delete_exec(bContext *C, wmOperator *op) int numfiles = filelist_files_ensure(sfile->files); int i; + const char *error_message = NULL; bool report_error = false; errno = 0; for (i = 0; i < numfiles; i++) { if (filelist_entry_select_index_get(sfile->files, i, CHECK_FILES)) { file = filelist_file(sfile->files, i); BLI_make_file_string(BKE_main_blendfile_path(bmain), str, sfile->params->dir, file->relpath); - if (BLI_delete(str, false, false) != 0 || BLI_exists(str)) { + if (BLI_delete_soft(str, &error_message) != 0 || BLI_exists(str)) { report_error = true; } } } if (report_error) { - BKE_reportf(op->reports, - RPT_ERROR, - "Could not delete file: %s", - errno ? strerror(errno) : "unknown error"); + if (error_message != NULL) { + BKE_reportf(op->reports, RPT_ERROR, "Could not delete file or directory: %s", error_message); + } + else { + BKE_reportf(op->reports, + RPT_ERROR, + "Could not delete file or directory: %s", + errno ? strerror(errno) : "unknown error"); + } } ED_fileselect_clear(wm, sa, sfile); @@ -2533,7 +2539,7 @@ void FILE_OT_delete(struct wmOperatorType *ot) { /* identifiers */ ot->name = "Delete Selected Files"; - ot->description = "Delete selected files"; + ot->description = "Move selected files to the trash or recycle bin"; ot->idname = "FILE_OT_delete"; /* api callbacks */ |