File Browser: add back Delete, which now moves files to the trash

In Blender 2.7 delete would permanently delete files, now this function is back
but using more standard behavior.

This patch includes code contributed by Kris (Metricity).

Differential Revision: https://developer.blender.org/D4585
This commit is contained in:
Robert Guetzkow 2019-10-10 10:53:13 +02:00 committed by Brecht Van Lommel
parent 826db891ab
commit 8825250f5a
Notes: blender-bot 2024-01-16 18:05:25 +01:00
Referenced by issue #97338, Incorrect handling of reference counting for COM
Referenced by issue #88131, Blender Delete file | problem
Referenced by issue #64104, Make Delete in the File Browser use the system Trash
6 changed files with 217 additions and 8 deletions

View File

@ -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'},

View File

@ -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')]}),

View File

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

View File

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

View File

@ -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));
@ -769,6 +947,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?
*/

View File

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