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
This commit is contained in:
Harley Acheson
2023-04-18 20:39:30 +02:00
parent 4edcae75aa
commit 694f792ee1
8 changed files with 416 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,10 +11,12 @@
# include <conio.h>
# include <stdio.h>
# include <stdlib.h>
# include <shlwapi.h>
# 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];

View File

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

View File

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

View File

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