UI: OS File Operations Within File Browser #104531

Merged
Harley Acheson merged 8 commits from Harley/blender:FileOperations into main 2023-04-18 20:39:42 +02:00
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,
Harley marked this conversation as resolved Outdated

Make this a function in fileops.c with a switch, since:

  • This should not be part of the public API
  • It's Windows specific
  • Using a switch ensures there is a compile warning/error when this goes out of sync with FileExternalOperation
Make this a function in `fileops.c` with a switch, since: * This should not be part of the public API * It's Windows specific * Using a switch ensures there is a compile warning/error when this goes out of sync with `FileExternalOperation`
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)
{
Harley marked this conversation as resolved Outdated

Replace FILE_MAXDIR with FILE_MAX everywhere in this patch, since these are not just directory paths.

Replace `FILE_MAXDIR` with `FILE_MAX` everywhere in this patch, since these are not just directory paths.
#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")) {

String comparison like this does not work in C.

String comparison like this does not work in C.
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"},
Harley marked this conversation as resolved Outdated

This needs to be static and have a better name. Or better, move it to BLI and leave platform specific code out of editors/ as much as possible.

This needs to be static and have a better name. Or better, move it to BLI and leave platform specific code out of editors/ as much as possible.
{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;
Harley marked this conversation as resolved Outdated

Can we report an error when the operation fails? For example due to permissions.

Can we report an error when the operation fails? For example due to permissions.
}
#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)
{
Harley marked this conversation as resolved Outdated

"shell execute" sounds like a named based on a Windows implementation detail, can this be more generic? Something about file operations.

"shell execute" sounds like a named based on a Windows implementation detail, can this be more generic? Something about file operations.

Don't use default, it means there will be no warnings when someone forgets to update this function. Instead add return "" add the end of the function outside the switch.

Don't use `default`, it means there will be no warnings when someone forgets to update this function. Instead add `return ""` add the end of the function outside the switch.
const char *description = "";
RNA_enum_description(file_external_operation, RNA_enum_get(ptr, "operation"), &description);
Harley marked this conversation as resolved Outdated

I don't think we need print.

I don't think we need print.
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;
}

Can you use BLI_file_external_operation_supported instead of this? Might also make sense to give BLI_file_external_operation_supported a parameter to indicate if it's for a directory or file.

Can you use `BLI_file_external_operation_supported` instead of this? Might also make sense to give `BLI_file_external_operation_supported` a parameter to indicate if it's for a directory or file.
SpaceFile *sfile = CTX_wm_space_file(C);
FileSelectParams *params = ED_fileselect_get_active_params(sfile);
Harley marked this conversation as resolved Outdated

It seems to use the first selected entry now to determine this, when there may be both a file and directory selected. Is that the standard behavior in file browsers, or can we do something to be more consistent with them?

It seems to use the first selected entry now to determine this, when there may be both a file and directory selected. Is that the standard behavior in file browsers, or can we do something to be more consistent with them?
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);
}