From 694f792ee186f236036b487d63bfb15945a88082 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Tue, 18 Apr 2023 20:39:30 +0200 Subject: [PATCH] UI: OS File Operations Within File Browser Adds a submenu to the File Browser selected item context menu that allows opening the item or viewing the location in an OS browsing window. On Win32 also allows other actions like editing, searching, opening command prompt, etc. Pull Request: https://projects.blender.org/blender/blender/pulls/104531 --- scripts/startup/bl_ui/space_filebrowser.py | 1 + source/blender/blenlib/BLI_fileops.h | 27 ++ source/blender/blenlib/BLI_winstuff.h | 5 + source/blender/blenlib/intern/fileops.c | 65 +++++ source/blender/blenlib/intern/winstuff.c | 58 ++++ .../blender/editors/space_file/file_intern.h | 4 + source/blender/editors/space_file/file_ops.c | 254 ++++++++++++++++++ .../blender/editors/space_file/space_file.c | 2 + 8 files changed, 416 insertions(+) diff --git a/scripts/startup/bl_ui/space_filebrowser.py b/scripts/startup/bl_ui/space_filebrowser.py index 1a8063da420..e52d380f67e 100644 --- a/scripts/startup/bl_ui/space_filebrowser.py +++ b/scripts/startup/bl_ui/space_filebrowser.py @@ -538,6 +538,7 @@ class FILEBROWSER_MT_context_menu(FileBrowserMenu, Menu): layout.operator("file.next", text="Forward") layout.operator("file.parent", text="Go to Parent") layout.operator("file.refresh", text="Refresh") + layout.menu("FILEBROWSER_MT_operations_menu") layout.separator() diff --git a/source/blender/blenlib/BLI_fileops.h b/source/blender/blenlib/BLI_fileops.h index 87d0cd2b92d..eade4b63dcd 100644 --- a/source/blender/blenlib/BLI_fileops.h +++ b/source/blender/blenlib/BLI_fileops.h @@ -113,6 +113,33 @@ ENUM_OPERATORS(eFileAttributes, FILE_ATTR_HARDLINK); /** \} */ + +/* -------------------------------------------------------------------- */ +/** \name External File Operations + * \{ */ + +typedef enum FileExternalOperation { + FILE_EXTERNAL_OPERATION_OPEN = 0, + FILE_EXTERNAL_OPERATION_FOLDER_OPEN, + /* Following are Windows-only: */ + FILE_EXTERNAL_OPERATION_EDIT, + FILE_EXTERNAL_OPERATION_NEW, + FILE_EXTERNAL_OPERATION_FIND, + FILE_EXTERNAL_OPERATION_SHOW, + FILE_EXTERNAL_OPERATION_PLAY, + FILE_EXTERNAL_OPERATION_BROWSE, + FILE_EXTERNAL_OPERATION_PREVIEW, + FILE_EXTERNAL_OPERATION_PRINT, + FILE_EXTERNAL_OPERATION_INSTALL, + FILE_EXTERNAL_OPERATION_RUNAS, + FILE_EXTERNAL_OPERATION_PROPERTIES, + FILE_EXTERNAL_OPERATION_FOLDER_FIND, + FILE_EXTERNAL_OPERATION_FOLDER_CMD, +} FileExternalOperation; + +bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation); +bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation); + /* -------------------------------------------------------------------- */ /** \name Directories * \{ */ diff --git a/source/blender/blenlib/BLI_winstuff.h b/source/blender/blenlib/BLI_winstuff.h index 34f1b1a68f1..5ed64128a26 100644 --- a/source/blender/blenlib/BLI_winstuff.h +++ b/source/blender/blenlib/BLI_winstuff.h @@ -89,6 +89,11 @@ bool BLI_windows_register_blend_extension(bool background); void BLI_windows_get_default_root_dir(char root_dir[4]); int BLI_windows_get_executable_dir(char *str); +/* ShellExecute Helpers. */ + +bool BLI_windows_external_operation_supported(const char *filepath, const char *operation); +bool BLI_windows_external_operation_execute(const char *filepath, const char *operation); + #ifdef __cplusplus } #endif diff --git a/source/blender/blenlib/intern/fileops.c b/source/blender/blenlib/intern/fileops.c index 35aaeb3ac16..49ca3986a59 100644 --- a/source/blender/blenlib/intern/fileops.c +++ b/source/blender/blenlib/intern/fileops.c @@ -46,6 +46,71 @@ #include "BLI_sys_types.h" /* for intptr_t support */ #include "BLI_utildefines.h" +#ifdef WIN32 +/* Text string used as the "verb" for Windows shell operations. */ +static char *windows_operation_string(FileExternalOperation operation) +{ + switch (operation) { + case FILE_EXTERNAL_OPERATION_OPEN: + return "open"; + case FILE_EXTERNAL_OPERATION_FOLDER_OPEN: + return "open"; + case FILE_EXTERNAL_OPERATION_EDIT: + return "edit"; + case FILE_EXTERNAL_OPERATION_NEW: + return "new"; + case FILE_EXTERNAL_OPERATION_FIND: + return "find"; + case FILE_EXTERNAL_OPERATION_SHOW: + return "show"; + case FILE_EXTERNAL_OPERATION_PLAY: + return "play"; + case FILE_EXTERNAL_OPERATION_BROWSE: + return "browse"; + case FILE_EXTERNAL_OPERATION_PREVIEW: + return "preview"; + case FILE_EXTERNAL_OPERATION_PRINT: + return "print"; + case FILE_EXTERNAL_OPERATION_INSTALL: + return "install"; + case FILE_EXTERNAL_OPERATION_RUNAS: + return "runas"; + case FILE_EXTERNAL_OPERATION_PROPERTIES: + return "properties"; + case FILE_EXTERNAL_OPERATION_FOLDER_FIND: + return "find"; + case FILE_EXTERNAL_OPERATION_FOLDER_CMD: + return "cmd"; + } + BLI_assert_unreachable(); + return ""; +} +#endif + +bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation) +{ +#ifdef WIN32 + char *opstring = windows_operation_string(operation); + return BLI_windows_external_operation_supported(filepath, opstring); +#else + return false; +#endif +} + +bool BLI_file_external_operation_execute(const char *filepath, FileExternalOperation operation) +{ +#ifdef WIN32 + char *opstring = windows_operation_string(operation); + if (BLI_windows_external_operation_supported(filepath, opstring) && + BLI_windows_external_operation_execute(filepath, opstring)) { + return true; + } + return false; +#else + return false; +#endif +} + size_t BLI_file_zstd_from_mem_at_pos( void *buf, size_t len, FILE *file, size_t file_offset, int compression_level) { diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c index 3a574b60ae2..f8c9cc49371 100644 --- a/source/blender/blenlib/intern/winstuff.c +++ b/source/blender/blenlib/intern/winstuff.c @@ -11,10 +11,12 @@ # include # include # include +# include # include "MEM_guardedalloc.h" # define WIN32_SKIP_HKEY_PROTECTION /* Need to use HKEY. */ +# include "BLI_fileops.h" # include "BLI_path_util.h" # include "BLI_string.h" # include "BLI_utildefines.h" @@ -178,6 +180,62 @@ bool BLI_windows_register_blend_extension(const bool background) return true; } +/* Check the registry to see if there is an operation association to a file + * extension. Extension *should almost always contain a dot like ".txt", + * but this does allow querying non - extensions *like "Directory", "Drive", + * "AllProtocols", etc - anything in Classes with a "shell" branch. + */ +static bool BLI_windows_file_operation_is_registered(const char *extension, const char *operation) +{ + HKEY hKey; + HRESULT hr = AssocQueryKey(ASSOCF_INIT_IGNOREUNKNOWN, + ASSOCKEY_SHELLEXECCLASS, + (LPCTSTR)extension, + (LPCTSTR)operation, + &hKey); + if (SUCCEEDED(hr)) { + RegCloseKey(hKey); + return true; + } + return false; +} + +bool BLI_windows_external_operation_supported(const char *filepath, const char *operation) +{ + if (STREQ(operation, "open") || STREQ(operation, "properties")) { + return true; + } + + if (BLI_is_dir(filepath)) { + return BLI_windows_file_operation_is_registered("Directory", operation); + } + + const char *extension = BLI_path_extension(filepath); + return BLI_windows_file_operation_is_registered(extension, operation); +} + +bool BLI_windows_external_operation_execute(const char *filepath, char *operation) +{ + WCHAR wpath[FILE_MAX]; + if (conv_utf_8_to_16(filepath, wpath, ARRAY_SIZE(wpath)) != 0) { + return false; + } + + WCHAR woperation[FILE_MAX]; + if (conv_utf_8_to_16(operation, woperation, ARRAY_SIZE(woperation)) != 0) { + return false; + } + + SHELLEXECUTEINFOW shellinfo = {0}; + shellinfo.cbSize = sizeof(SHELLEXECUTEINFO); + shellinfo.fMask = SEE_MASK_INVOKEIDLIST; + shellinfo.lpVerb = woperation; + shellinfo.lpFile = wpath; + shellinfo.nShow = SW_SHOW; + + return ShellExecuteExW(&shellinfo); +} + void BLI_windows_get_default_root_dir(char root[4]) { char str[MAX_PATH + 1]; diff --git a/source/blender/editors/space_file/file_intern.h b/source/blender/editors/space_file/file_intern.h index 6fcc804443f..e11244d0214 100644 --- a/source/blender/editors/space_file/file_intern.h +++ b/source/blender/editors/space_file/file_intern.h @@ -66,6 +66,10 @@ void FILE_OT_bookmark_move(struct wmOperatorType *ot); void FILE_OT_reset_recent(wmOperatorType *ot); void FILE_OT_hidedot(struct wmOperatorType *ot); void FILE_OT_execute(struct wmOperatorType *ot); + +void FILE_OT_external_operation(struct wmOperatorType *ot); +void file_external_operations_menu_register(void); + /** * Variation of #FILE_OT_execute that accounts for some mouse specific handling. * Otherwise calls the same logic. diff --git a/source/blender/editors/space_file/file_ops.c b/source/blender/editors/space_file/file_ops.c index 16c6eba01e5..d22f654210b 100644 --- a/source/blender/editors/space_file/file_ops.c +++ b/source/blender/editors/space_file/file_ops.c @@ -19,6 +19,8 @@ #include "BKE_report.h" #include "BKE_screen.h" +#include "BLT_translation.h" + #ifdef WIN32 # include "BLI_winstuff.h" #endif @@ -1768,6 +1770,258 @@ bool file_draw_check_exists(SpaceFile *sfile) /** \} */ +/* -------------------------------------------------------------------- */ +/** \name External operations that can performed on files. + * \{ */ + +static const EnumPropertyItem file_external_operation[] = { + {FILE_EXTERNAL_OPERATION_OPEN, "OPEN", 0, "Open", "Open the file"}, + {FILE_EXTERNAL_OPERATION_FOLDER_OPEN, "FOLDER_OPEN", 0, "Open Folder", "Open the folder"}, + {FILE_EXTERNAL_OPERATION_EDIT, "EDIT", 0, "Edit", "Edit the file"}, + {FILE_EXTERNAL_OPERATION_NEW, "NEW", 0, "New", "Create a new file of this type"}, + {FILE_EXTERNAL_OPERATION_FIND, "FIND", 0, "Find File", "Search for files of this type"}, + {FILE_EXTERNAL_OPERATION_SHOW, "SHOW", 0, "Show", "Show this file"}, + {FILE_EXTERNAL_OPERATION_PLAY, "PLAY", 0, "Play", "Play this file"}, + {FILE_EXTERNAL_OPERATION_BROWSE, "BROWSE", 0, "Browse", "Browse this file"}, + {FILE_EXTERNAL_OPERATION_PREVIEW, "PREVIEW", 0, "Preview", "Preview this file"}, + {FILE_EXTERNAL_OPERATION_PRINT, "PRINT", 0, "Print", "Print this file"}, + {FILE_EXTERNAL_OPERATION_INSTALL, "INSTALL", 0, "Install", "Install this file"}, + {FILE_EXTERNAL_OPERATION_RUNAS, "RUNAS", 0, "Run As User", "Run as specific user"}, + {FILE_EXTERNAL_OPERATION_PROPERTIES, + "PROPERTIES", + 0, + "Properties", + "Show OS Properties for this item"}, + {FILE_EXTERNAL_OPERATION_FOLDER_FIND, + "FOLDER_FIND", + 0, + "Find in Folder", + "Search for items in this folder"}, + {FILE_EXTERNAL_OPERATION_FOLDER_CMD, + "CMD", + 0, + "Command Prompt Here", + "Open a command prompt here"}, + {0, NULL, 0, NULL, NULL}}; + +static int file_external_operation_exec(bContext *C, wmOperator *op) +{ + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath"); + char filepath[FILE_MAX]; + RNA_property_string_get(op->ptr, prop, filepath); + + const FileExternalOperation operation = (FileExternalOperation)RNA_enum_get(op->ptr, + "operation"); + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_WAIT); + +#ifdef WIN32 + if (BLI_file_external_operation_execute(filepath, operation)) { + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_FINISHED; + } +#else + wmOperatorType *ot = WM_operatortype_find("WM_OT_path_open", true); + PointerRNA op_props; + WM_operator_properties_create_ptr(&op_props, ot); + RNA_string_set(&op_props, "filepath", filepath); + if (WM_operator_name_call_ptr(C, ot, WM_OP_INVOKE_DEFAULT, &op_props, NULL) == + OPERATOR_FINISHED) { + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_FINISHED; + } +#endif + + BKE_reportf( + op->reports, RPT_ERROR, "Failure to perform exernal file operation on \"%s\"", filepath); + WM_cursor_set(CTX_wm_window(C), WM_CURSOR_DEFAULT); + return OPERATOR_CANCELLED; +} + +static char *file_external_operation_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(ot), + PointerRNA *ptr) +{ + const char *description = ""; + RNA_enum_description(file_external_operation, RNA_enum_get(ptr, "operation"), &description); + return BLI_strdup(description); +} + +void FILE_OT_external_operation(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "External File Operation"; + ot->idname = "FILE_OT_external_operation"; + ot->description = "Perform external operation on a file or folder"; + + /* api callbacks */ + ot->exec = file_external_operation_exec; + ot->get_description = file_external_operation_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER; /* No undo! */ + + /* properties */ + prop = RNA_def_string(ot->srna, "filepath", NULL, FILE_MAX, "File or folder path", ""); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + + RNA_def_enum(ot->srna, + "operation", + file_external_operation, + 0, + "Operation", + "Operation to perform on the file or path"); +} + +static void file_os_operations_menu_item(uiLayout *layout, + wmOperatorType *ot, + const char *path, + FileExternalOperation operation) +{ +#ifdef WIN32 + if (!BLI_file_external_operation_supported(path, operation)) { + return; + } +#else + if (!ELEM(operation, FILE_EXTERNAL_OPERATION_OPEN, FILE_EXTERNAL_OPERATION_FOLDER_OPEN)) { + return; + } +#endif + + const char *title = ""; + RNA_enum_name(file_external_operation, operation, &title); + + PointerRNA props_ptr; + uiItemFullO_ptr(layout, ot, title, ICON_NONE, NULL, WM_OP_INVOKE_DEFAULT, 0, &props_ptr); + RNA_string_set(&props_ptr, "filepath", path); + if (operation) { + RNA_enum_set(&props_ptr, "operation", operation); + } +} + +static void file_os_operations_menu_draw(const bContext *C_const, Menu *menu) +{ + bContext *C = (bContext *)C_const; + + /* File browsing only operator (not asset browsing). */ + if (!ED_operator_file_browsing_active(C)) { + return; + } + + SpaceFile *sfile = CTX_wm_space_file(C); + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + if (!sfile || !params) { + return; + } + + char dir[FILE_MAX_LIBEXTRA]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + return; + } + + int numfiles = filelist_files_ensure(sfile->files); + FileDirEntry *fileentry = NULL; + int num_selected = 0; + + for (int i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { + fileentry = filelist_file(sfile->files, i); + num_selected++; + } + } + + if (!fileentry || num_selected > 1) { + return; + } + + char path[FILE_MAX_LIBEXTRA]; + filelist_file_get_full_path(sfile->files, fileentry, path); + const char *root = filelist_dir(sfile->files); + + uiLayout *layout = menu->layout; + uiLayoutSetOperatorContext(layout, WM_OP_INVOKE_DEFAULT); + wmOperatorType *ot = WM_operatortype_find("FILE_OT_external_operation", true); + + if (fileentry->typeflag & FILE_TYPE_DIR) { + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FOLDER_OPEN); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FOLDER_CMD); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PROPERTIES); + } + else { + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_OPEN); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_EDIT); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_NEW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_FIND); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_SHOW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PLAY); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_BROWSE); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PREVIEW); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PRINT); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_INSTALL); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_RUNAS); + file_os_operations_menu_item(layout, ot, root, FILE_EXTERNAL_OPERATION_FOLDER_OPEN); + file_os_operations_menu_item(layout, ot, root, FILE_EXTERNAL_OPERATION_FOLDER_CMD); + file_os_operations_menu_item(layout, ot, path, FILE_EXTERNAL_OPERATION_PROPERTIES); + } +} + +static bool file_os_operations_menu_poll(const bContext *C_const, MenuType *UNUSED(mt)) +{ + bContext *C = (bContext *)C_const; + + /* File browsing only operator (not asset browsing). */ + if (!ED_operator_file_browsing_active(C)) { + return false; + } + + SpaceFile *sfile = CTX_wm_space_file(C); + FileSelectParams *params = ED_fileselect_get_active_params(sfile); + + if (sfile && params) { + char dir[FILE_MAX_LIBEXTRA]; + if (filelist_islibrary(sfile->files, dir, NULL)) { + return false; + } + + int numfiles = filelist_files_ensure(sfile->files); + int num_selected = 0; + for (int i = 0; i < numfiles; i++) { + if (filelist_entry_select_index_get(sfile->files, i, CHECK_ALL)) { + num_selected++; + } + } + + if (num_selected > 1) { + CTX_wm_operator_poll_msg_set(C, "More than one item is selected"); + } + else if (num_selected < 1) { + CTX_wm_operator_poll_msg_set(C, "No items are selected"); + } + else { + return true; + } + } + + return false; +} + + +void file_external_operations_menu_register(void) +{ + MenuType *mt; + + mt = MEM_callocN(sizeof(MenuType), "spacetype file menu file operations"); + strcpy(mt->idname, "FILEBROWSER_MT_operations_menu"); + strcpy(mt->label, N_("External")); + strcpy(mt->translation_context, BLT_I18NCONTEXT_DEFAULT_BPYRNA); + mt->draw = file_os_operations_menu_draw; + mt->poll = file_os_operations_menu_poll; + WM_menutype_add(mt); +} + +/** \} */ + /* -------------------------------------------------------------------- */ /** \name Execute File Window Operator * \{ */ diff --git a/source/blender/editors/space_file/space_file.c b/source/blender/editors/space_file/space_file.c index c58a9c67fc6..fe7179f8b8a 100644 --- a/source/blender/editors/space_file/space_file.c +++ b/source/blender/editors/space_file/space_file.c @@ -613,6 +613,7 @@ static void file_operatortypes(void) WM_operatortype_append(FILE_OT_start_filter); WM_operatortype_append(FILE_OT_edit_directory_path); WM_operatortype_append(FILE_OT_view_selected); + WM_operatortype_append(FILE_OT_external_operation); } /* NOTE: do not add .blend file reading on this level */ @@ -1063,6 +1064,7 @@ void ED_spacetype_file(void) art->draw = file_tools_region_draw; BLI_addhead(&st->regiontypes, art); file_tool_props_region_panels_register(art); + file_external_operations_menu_register(); BKE_spacetype_register(st); }