This is not dynamic: it only happens when the dir is added to the list--automatically for recent files, and by the user for bookmarks. Entries can then be manually renamed like other dirs. They will keep the same name if the language is changed afterwards. Reviewed By: mont29 Differential Revision: https://developer.blender.org/D15629
1237 lines
38 KiB
C
1237 lines
38 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later
|
|
* Copyright 2001-2002 NaN Holding BV. All rights reserved. */
|
|
|
|
/** \file
|
|
* \ingroup spfile
|
|
*/
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_blenlib.h"
|
|
#include "BLI_ghash.h"
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "BKE_appdir.h"
|
|
|
|
#include "ED_fileselect.h"
|
|
|
|
#ifdef WIN32
|
|
/* Need to include windows.h so _WIN32_IE is defined. */
|
|
# include <windows.h>
|
|
/* For SHGetSpecialFolderPath, has to be done before BLI_winstuff
|
|
* because 'near' is disabled through BLI_windstuff. */
|
|
# include "BLI_winstuff.h"
|
|
# include <shlobj.h>
|
|
# include <shlwapi.h>
|
|
#endif
|
|
|
|
#include "UI_interface_icons.h"
|
|
#include "UI_resources.h"
|
|
#include "WM_api.h"
|
|
#include "WM_types.h"
|
|
|
|
#ifdef __APPLE__
|
|
# include <Carbon/Carbon.h>
|
|
#endif /* __APPLE__ */
|
|
|
|
#ifdef __linux__
|
|
# include "BLI_fileops_types.h"
|
|
# include <mntent.h>
|
|
#endif
|
|
|
|
#include "fsmenu.h" /* include ourselves */
|
|
|
|
/* FSMENU HANDLING */
|
|
|
|
typedef struct FSMenu {
|
|
FSMenuEntry *fsmenu_system;
|
|
FSMenuEntry *fsmenu_system_bookmarks;
|
|
FSMenuEntry *fsmenu_bookmarks;
|
|
FSMenuEntry *fsmenu_recent;
|
|
FSMenuEntry *fsmenu_other;
|
|
} FSMenu;
|
|
|
|
static FSMenu *g_fsmenu = NULL;
|
|
|
|
FSMenu *ED_fsmenu_get(void)
|
|
{
|
|
if (!g_fsmenu) {
|
|
g_fsmenu = MEM_callocN(sizeof(struct FSMenu), "fsmenu");
|
|
}
|
|
return g_fsmenu;
|
|
}
|
|
|
|
struct FSMenuEntry *ED_fsmenu_get_category(struct FSMenu *fsmenu, FSMenuCategory category)
|
|
{
|
|
FSMenuEntry *fsm_head = NULL;
|
|
|
|
switch (category) {
|
|
case FS_CATEGORY_SYSTEM:
|
|
fsm_head = fsmenu->fsmenu_system;
|
|
break;
|
|
case FS_CATEGORY_SYSTEM_BOOKMARKS:
|
|
fsm_head = fsmenu->fsmenu_system_bookmarks;
|
|
break;
|
|
case FS_CATEGORY_BOOKMARKS:
|
|
fsm_head = fsmenu->fsmenu_bookmarks;
|
|
break;
|
|
case FS_CATEGORY_RECENT:
|
|
fsm_head = fsmenu->fsmenu_recent;
|
|
break;
|
|
case FS_CATEGORY_OTHER:
|
|
fsm_head = fsmenu->fsmenu_other;
|
|
break;
|
|
}
|
|
return fsm_head;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name XDG User Directory Support (Unix)
|
|
*
|
|
* Generic Unix, Use XDG when available, otherwise fallback to the home directory.
|
|
* \{ */
|
|
|
|
/**
|
|
* Look for `user-dirs.dirs`, where localized or custom user folders are defined,
|
|
* and store their paths in a GHash.
|
|
*/
|
|
static GHash *fsmenu_xdg_user_dirs_parse(const char *home)
|
|
{
|
|
/* Add to the default for variable, equals & quotes. */
|
|
char l[128 + FILE_MAXDIR];
|
|
FILE *fp;
|
|
|
|
/* Check if the config file exists. */
|
|
{
|
|
char filepath[FILE_MAX];
|
|
const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
|
|
if (xdg_config_home != NULL) {
|
|
BLI_path_join(filepath, sizeof(filepath), xdg_config_home, "user-dirs.dirs", NULL);
|
|
}
|
|
else {
|
|
BLI_path_join(filepath, sizeof(filepath), home, ".config", "user-dirs.dirs", NULL);
|
|
}
|
|
fp = BLI_fopen(filepath, "r");
|
|
if (!fp) {
|
|
return NULL;
|
|
}
|
|
}
|
|
/* By default there are 8 paths. */
|
|
GHash *xdg_map = BLI_ghash_str_new_ex(__func__, 8);
|
|
while (fgets(l, sizeof(l), fp) != NULL) { /* read a line */
|
|
|
|
/* Avoid inserting invalid values. */
|
|
if (STRPREFIX(l, "XDG_")) {
|
|
char *l_value = strchr(l, '=');
|
|
if (l_value != NULL) {
|
|
*l_value = '\0';
|
|
l_value++;
|
|
|
|
BLI_str_rstrip(l_value);
|
|
const uint l_value_len = strlen(l_value);
|
|
if ((l_value[0] == '"') && (l_value_len > 0) && (l_value[l_value_len - 1] == '"')) {
|
|
l_value[l_value_len - 1] = '\0';
|
|
l_value++;
|
|
|
|
char l_value_expanded[FILE_MAX];
|
|
char *l_value_final = l_value;
|
|
|
|
/* This is currently the only variable used.
|
|
* Based on the 'user-dirs.dirs' man page,
|
|
* there is no need to resolve arbitrary environment variables. */
|
|
if (STRPREFIX(l_value, "$HOME" SEP_STR)) {
|
|
BLI_path_join(l_value_expanded, sizeof(l_value_expanded), home, l_value + 6, NULL);
|
|
l_value_final = l_value_expanded;
|
|
}
|
|
|
|
BLI_ghash_insert(xdg_map, BLI_strdup(l), BLI_strdup(l_value_final));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
return xdg_map;
|
|
}
|
|
|
|
static void fsmenu_xdg_user_dirs_free(GHash *xdg_map)
|
|
{
|
|
if (xdg_map != NULL) {
|
|
BLI_ghash_free(xdg_map, MEM_freeN, MEM_freeN);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add fsmenu entry for system folders on linux.
|
|
* - Check if a path is stored in the #GHash generated from `user-dirs.dirs`.
|
|
* - If not, check for a default path in `$HOME`.
|
|
*
|
|
* \param key: Use `user-dirs.dirs` format "XDG_EXAMPLE_DIR"
|
|
* \param default_path: Directory name to check in $HOME, also used for the menu entry name.
|
|
*/
|
|
static void fsmenu_xdg_insert_entry(GHash *xdg_map,
|
|
struct FSMenu *fsmenu,
|
|
const char *key,
|
|
const char *default_path,
|
|
int icon,
|
|
const char *home)
|
|
{
|
|
char xdg_path_buf[FILE_MAXDIR];
|
|
const char *xdg_path = xdg_map ? BLI_ghash_lookup(xdg_map, key) : NULL;
|
|
if (xdg_path == NULL) {
|
|
BLI_path_join(xdg_path_buf, sizeof(xdg_path_buf), home, default_path, NULL);
|
|
xdg_path = xdg_path_buf;
|
|
}
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, xdg_path, N_(default_path), icon, FS_INSERT_LAST);
|
|
}
|
|
|
|
/** \} */
|
|
|
|
void ED_fsmenu_set_category(struct FSMenu *fsmenu, FSMenuCategory category, FSMenuEntry *fsm_head)
|
|
{
|
|
switch (category) {
|
|
case FS_CATEGORY_SYSTEM:
|
|
fsmenu->fsmenu_system = fsm_head;
|
|
break;
|
|
case FS_CATEGORY_SYSTEM_BOOKMARKS:
|
|
fsmenu->fsmenu_system_bookmarks = fsm_head;
|
|
break;
|
|
case FS_CATEGORY_BOOKMARKS:
|
|
fsmenu->fsmenu_bookmarks = fsm_head;
|
|
break;
|
|
case FS_CATEGORY_RECENT:
|
|
fsmenu->fsmenu_recent = fsm_head;
|
|
break;
|
|
case FS_CATEGORY_OTHER:
|
|
fsmenu->fsmenu_other = fsm_head;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int ED_fsmenu_get_nentries(struct FSMenu *fsmenu, FSMenuCategory category)
|
|
{
|
|
FSMenuEntry *fsm_iter;
|
|
int count = 0;
|
|
|
|
for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter; fsm_iter = fsm_iter->next) {
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
FSMenuEntry *ED_fsmenu_get_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
|
|
{
|
|
FSMenuEntry *fsm_iter;
|
|
|
|
for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
|
|
fsm_iter = fsm_iter->next) {
|
|
idx--;
|
|
}
|
|
|
|
return fsm_iter;
|
|
}
|
|
|
|
char *ED_fsmenu_entry_get_path(struct FSMenuEntry *fsentry)
|
|
{
|
|
return fsentry->path;
|
|
}
|
|
|
|
void ED_fsmenu_entry_set_path(struct FSMenuEntry *fsentry, const char *path)
|
|
{
|
|
if ((!fsentry->path || !path || !STREQ(path, fsentry->path)) && (fsentry->path != path)) {
|
|
char tmp_name[FILE_MAXFILE];
|
|
|
|
MEM_SAFE_FREE(fsentry->path);
|
|
|
|
fsentry->path = (path && path[0]) ? BLI_strdup(path) : NULL;
|
|
|
|
BLI_join_dirfile(tmp_name,
|
|
sizeof(tmp_name),
|
|
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
|
|
BLENDER_BOOKMARK_FILE);
|
|
fsmenu_write_file(ED_fsmenu_get(), tmp_name);
|
|
}
|
|
}
|
|
|
|
int ED_fsmenu_entry_get_icon(struct FSMenuEntry *fsentry)
|
|
{
|
|
return (fsentry->icon) ? fsentry->icon : ICON_FILE_FOLDER;
|
|
}
|
|
|
|
void ED_fsmenu_entry_set_icon(struct FSMenuEntry *fsentry, const int icon)
|
|
{
|
|
fsentry->icon = icon;
|
|
}
|
|
|
|
static void fsmenu_entry_generate_name(struct FSMenuEntry *fsentry, char *name, size_t name_size)
|
|
{
|
|
int offset = 0;
|
|
int len = name_size;
|
|
|
|
if (BLI_path_name_at_index(fsentry->path, -1, &offset, &len)) {
|
|
/* use as size */
|
|
len += 1;
|
|
}
|
|
|
|
BLI_strncpy(name, &fsentry->path[offset], MIN2(len, name_size));
|
|
if (!name[0]) {
|
|
name[0] = '/';
|
|
name[1] = '\0';
|
|
}
|
|
}
|
|
|
|
char *ED_fsmenu_entry_get_name(struct FSMenuEntry *fsentry)
|
|
{
|
|
if (fsentry->name[0]) {
|
|
return fsentry->name;
|
|
}
|
|
|
|
/* Here we abuse fsm_iter->name, keeping first char NULL. */
|
|
char *name = fsentry->name + 1;
|
|
size_t name_size = sizeof(fsentry->name) - 1;
|
|
|
|
fsmenu_entry_generate_name(fsentry, name, name_size);
|
|
return name;
|
|
}
|
|
|
|
void ED_fsmenu_entry_set_name(struct FSMenuEntry *fsentry, const char *name)
|
|
{
|
|
if (!STREQ(name, fsentry->name)) {
|
|
char tmp_name[FILE_MAXFILE];
|
|
size_t tmp_name_size = sizeof(tmp_name);
|
|
|
|
fsmenu_entry_generate_name(fsentry, tmp_name, tmp_name_size);
|
|
if (!name[0] || STREQ(tmp_name, name)) {
|
|
/* reset name to default behavior. */
|
|
fsentry->name[0] = '\0';
|
|
}
|
|
else {
|
|
BLI_strncpy(fsentry->name, name, sizeof(fsentry->name));
|
|
}
|
|
|
|
BLI_join_dirfile(tmp_name,
|
|
sizeof(tmp_name),
|
|
BKE_appdir_folder_id_create(BLENDER_USER_CONFIG, NULL),
|
|
BLENDER_BOOKMARK_FILE);
|
|
fsmenu_write_file(ED_fsmenu_get(), tmp_name);
|
|
}
|
|
}
|
|
|
|
void fsmenu_entry_refresh_valid(struct FSMenuEntry *fsentry)
|
|
{
|
|
if (fsentry->path && fsentry->path[0]) {
|
|
#ifdef WIN32
|
|
/* XXX Special case, always consider those as valid.
|
|
* Thanks to Windows, which can spend five seconds to perform a mere stat() call on those paths
|
|
* See T43684. */
|
|
const char *exceptions[] = {"A:\\", "B:\\", NULL};
|
|
const size_t exceptions_len[] = {strlen(exceptions[0]), strlen(exceptions[1]), 0};
|
|
int i;
|
|
|
|
for (i = 0; exceptions[i]; i++) {
|
|
if (STRCASEEQLEN(fsentry->path, exceptions[i], exceptions_len[i])) {
|
|
fsentry->valid = true;
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
fsentry->valid = BLI_is_dir(fsentry->path);
|
|
}
|
|
else {
|
|
fsentry->valid = false;
|
|
}
|
|
}
|
|
|
|
short fsmenu_can_save(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
|
|
{
|
|
FSMenuEntry *fsm_iter;
|
|
|
|
for (fsm_iter = ED_fsmenu_get_category(fsmenu, category); fsm_iter && idx;
|
|
fsm_iter = fsm_iter->next) {
|
|
idx--;
|
|
}
|
|
|
|
return fsm_iter ? fsm_iter->save : 0;
|
|
}
|
|
|
|
void fsmenu_insert_entry(struct FSMenu *fsmenu,
|
|
FSMenuCategory category,
|
|
const char *path,
|
|
const char *name,
|
|
int icon,
|
|
FSMenuInsert flag)
|
|
{
|
|
const uint path_len = strlen(path);
|
|
BLI_assert(path_len > 0);
|
|
if (path_len == 0) {
|
|
return;
|
|
}
|
|
const bool has_trailing_slash = (path[path_len - 1] == SEP);
|
|
FSMenuEntry *fsm_prev;
|
|
FSMenuEntry *fsm_iter;
|
|
FSMenuEntry *fsm_head;
|
|
|
|
fsm_head = ED_fsmenu_get_category(fsmenu, category);
|
|
fsm_prev = fsm_head; /* this is odd and not really correct? */
|
|
|
|
for (fsm_iter = fsm_head; fsm_iter; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
|
|
if (fsm_iter->path) {
|
|
/* Compare, with/without the trailing slash in 'path'. */
|
|
const int cmp_ret = BLI_path_ncmp(path, fsm_iter->path, path_len);
|
|
if (cmp_ret == 0 && STREQ(fsm_iter->path + path_len, has_trailing_slash ? "" : SEP_STR)) {
|
|
if (flag & FS_INSERT_FIRST) {
|
|
if (fsm_iter != fsm_head) {
|
|
fsm_prev->next = fsm_iter->next;
|
|
fsm_iter->next = fsm_head;
|
|
ED_fsmenu_set_category(fsmenu, category, fsm_iter);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if ((flag & FS_INSERT_SORTED) && cmp_ret < 0) {
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
/* if we're bookmarking this, file should come
|
|
* before the last separator, only automatically added
|
|
* current dir go after the last separator. */
|
|
if (flag & FS_INSERT_SAVE) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
fsm_iter = MEM_mallocN(sizeof(*fsm_iter), "fsme");
|
|
if (has_trailing_slash) {
|
|
fsm_iter->path = BLI_strdup(path);
|
|
}
|
|
else {
|
|
fsm_iter->path = BLI_strdupn(path, path_len + 1);
|
|
fsm_iter->path[path_len] = SEP;
|
|
fsm_iter->path[path_len + 1] = '\0';
|
|
}
|
|
fsm_iter->save = (flag & FS_INSERT_SAVE) != 0;
|
|
|
|
/* If entry is also in another list, use that icon and maybe name. */
|
|
/* On macOS we get icons and names for System Bookmarks from the FS_CATEGORY_OTHER list. */
|
|
if (ELEM(category, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT)) {
|
|
|
|
const FSMenuCategory cats[] = {
|
|
FS_CATEGORY_OTHER,
|
|
FS_CATEGORY_SYSTEM,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
FS_CATEGORY_BOOKMARKS,
|
|
};
|
|
int i = ARRAY_SIZE(cats);
|
|
if (category == FS_CATEGORY_BOOKMARKS) {
|
|
i--;
|
|
}
|
|
|
|
while (i--) {
|
|
FSMenuEntry *tfsm = ED_fsmenu_get_category(fsmenu, cats[i]);
|
|
for (; tfsm; tfsm = tfsm->next) {
|
|
if (STREQ(tfsm->path, fsm_iter->path)) {
|
|
icon = tfsm->icon;
|
|
if (tfsm->name[0] && (!name || !name[0])) {
|
|
name = DATA_(tfsm->name);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (tfsm) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (name && name[0]) {
|
|
BLI_strncpy(fsm_iter->name, name, sizeof(fsm_iter->name));
|
|
}
|
|
else {
|
|
fsm_iter->name[0] = '\0';
|
|
}
|
|
|
|
ED_fsmenu_entry_set_icon(fsm_iter, icon);
|
|
|
|
if (flag & FS_INSERT_NO_VALIDATE) {
|
|
fsm_iter->valid = true;
|
|
}
|
|
else {
|
|
fsmenu_entry_refresh_valid(fsm_iter);
|
|
}
|
|
|
|
if (fsm_prev) {
|
|
if (flag & FS_INSERT_FIRST) {
|
|
fsm_iter->next = fsm_head;
|
|
ED_fsmenu_set_category(fsmenu, category, fsm_iter);
|
|
}
|
|
else {
|
|
fsm_iter->next = fsm_prev->next;
|
|
fsm_prev->next = fsm_iter;
|
|
}
|
|
}
|
|
else {
|
|
fsm_iter->next = fsm_head;
|
|
ED_fsmenu_set_category(fsmenu, category, fsm_iter);
|
|
}
|
|
}
|
|
|
|
void fsmenu_remove_entry(struct FSMenu *fsmenu, FSMenuCategory category, int idx)
|
|
{
|
|
FSMenuEntry *fsm_prev = NULL;
|
|
FSMenuEntry *fsm_iter;
|
|
FSMenuEntry *fsm_head;
|
|
|
|
fsm_head = ED_fsmenu_get_category(fsmenu, category);
|
|
|
|
for (fsm_iter = fsm_head; fsm_iter && idx; fsm_prev = fsm_iter, fsm_iter = fsm_iter->next) {
|
|
idx--;
|
|
}
|
|
|
|
if (fsm_iter) {
|
|
/* you should only be able to remove entries that were
|
|
* not added by default, like windows drives.
|
|
* also separators (where path == NULL) shouldn't be removed */
|
|
if (fsm_iter->save && fsm_iter->path) {
|
|
|
|
/* remove fsme from list */
|
|
if (fsm_prev) {
|
|
fsm_prev->next = fsm_iter->next;
|
|
}
|
|
else {
|
|
fsm_head = fsm_iter->next;
|
|
ED_fsmenu_set_category(fsmenu, category, fsm_head);
|
|
}
|
|
/* free entry */
|
|
MEM_freeN(fsm_iter->path);
|
|
MEM_freeN(fsm_iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fsmenu_write_file(struct FSMenu *fsmenu, const char *filepath)
|
|
{
|
|
FSMenuEntry *fsm_iter = NULL;
|
|
char fsm_name[FILE_MAX];
|
|
int nwritten = 0;
|
|
|
|
FILE *fp = BLI_fopen(filepath, "w");
|
|
if (!fp) {
|
|
return;
|
|
}
|
|
|
|
fprintf(fp, "[Bookmarks]\n");
|
|
for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_BOOKMARKS); fsm_iter;
|
|
fsm_iter = fsm_iter->next) {
|
|
if (fsm_iter->path && fsm_iter->save) {
|
|
fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
|
|
if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
|
|
fprintf(fp, "!%s\n", fsm_iter->name);
|
|
}
|
|
fprintf(fp, "%s\n", fsm_iter->path);
|
|
}
|
|
}
|
|
fprintf(fp, "[Recent]\n");
|
|
for (fsm_iter = ED_fsmenu_get_category(fsmenu, FS_CATEGORY_RECENT);
|
|
fsm_iter && (nwritten < FSMENU_RECENT_MAX);
|
|
fsm_iter = fsm_iter->next, nwritten++) {
|
|
if (fsm_iter->path && fsm_iter->save) {
|
|
fsmenu_entry_generate_name(fsm_iter, fsm_name, sizeof(fsm_name));
|
|
if (fsm_iter->name[0] && !STREQ(fsm_iter->name, fsm_name)) {
|
|
fprintf(fp, "!%s\n", fsm_iter->name);
|
|
}
|
|
fprintf(fp, "%s\n", fsm_iter->path);
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
void fsmenu_read_bookmarks(struct FSMenu *fsmenu, const char *filepath)
|
|
{
|
|
char line[FILE_MAXDIR];
|
|
char name[FILE_MAXFILE];
|
|
FSMenuCategory category = FS_CATEGORY_BOOKMARKS;
|
|
FILE *fp;
|
|
|
|
fp = BLI_fopen(filepath, "r");
|
|
if (!fp) {
|
|
return;
|
|
}
|
|
|
|
name[0] = '\0';
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL) { /* read a line */
|
|
if (STRPREFIX(line, "[Bookmarks]")) {
|
|
category = FS_CATEGORY_BOOKMARKS;
|
|
}
|
|
else if (STRPREFIX(line, "[Recent]")) {
|
|
category = FS_CATEGORY_RECENT;
|
|
}
|
|
else if (line[0] == '!') {
|
|
int len = strlen(line);
|
|
if (len > 0) {
|
|
if (line[len - 1] == '\n') {
|
|
line[len - 1] = '\0';
|
|
}
|
|
BLI_strncpy(name, line + 1, sizeof(name));
|
|
}
|
|
}
|
|
else {
|
|
int len = strlen(line);
|
|
if (len > 0) {
|
|
if (line[len - 1] == '\n') {
|
|
line[len - 1] = '\0';
|
|
}
|
|
/* don't do this because it can be slow on network drives,
|
|
* having a bookmark from a drive that's ejected or so isn't
|
|
* all _that_ bad */
|
|
#if 0
|
|
if (BLI_exists(line))
|
|
#endif
|
|
{
|
|
fsmenu_insert_entry(fsmenu, category, line, name, ICON_FILE_FOLDER, FS_INSERT_SAVE);
|
|
}
|
|
}
|
|
/* always reset name. */
|
|
name[0] = '\0';
|
|
}
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
#ifdef WIN32
|
|
/* Add a Windows known folder path to the System list. */
|
|
static void fsmenu_add_windows_folder(struct FSMenu *fsmenu,
|
|
FSMenuCategory category,
|
|
REFKNOWNFOLDERID rfid,
|
|
const char *name,
|
|
const int icon,
|
|
FSMenuInsert flag)
|
|
{
|
|
LPWSTR pPath;
|
|
char line[FILE_MAXDIR];
|
|
if (SHGetKnownFolderPath(rfid, 0, NULL, &pPath) == S_OK) {
|
|
BLI_strncpy_wchar_as_utf8(line, pPath, FILE_MAXDIR);
|
|
CoTaskMemFree(pPath);
|
|
fsmenu_insert_entry(fsmenu, category, line, name, icon, flag);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void fsmenu_read_system(struct FSMenu *fsmenu, int read_bookmarks)
|
|
{
|
|
char line[FILE_MAXDIR];
|
|
#ifdef WIN32
|
|
/* Add the drive names to the listing */
|
|
{
|
|
wchar_t wline[FILE_MAXDIR];
|
|
__int64 tmp;
|
|
char tmps[4], *name;
|
|
|
|
tmp = GetLogicalDrives();
|
|
|
|
for (int i = 0; i < 26; i++) {
|
|
if ((tmp >> i) & 1) {
|
|
tmps[0] = 'A' + i;
|
|
tmps[1] = ':';
|
|
tmps[2] = '\\';
|
|
tmps[3] = '\0';
|
|
name = NULL;
|
|
|
|
/* Skip over floppy disks A & B. */
|
|
if (i > 1) {
|
|
/* Friendly volume descriptions without using SHGetFileInfoW (T85689). */
|
|
BLI_strncpy_wchar_from_utf8(wline, tmps, 4);
|
|
IShellFolder *desktop;
|
|
if (SHGetDesktopFolder(&desktop) == S_OK) {
|
|
PIDLIST_RELATIVE volume;
|
|
if (desktop->lpVtbl->ParseDisplayName(
|
|
desktop, NULL, NULL, wline, NULL, &volume, NULL) == S_OK) {
|
|
STRRET volume_name;
|
|
volume_name.uType = STRRET_WSTR;
|
|
if (desktop->lpVtbl->GetDisplayNameOf(
|
|
desktop, volume, SHGDN_FORADDRESSBAR, &volume_name) == S_OK) {
|
|
wchar_t *volume_name_wchar;
|
|
if (StrRetToStrW(&volume_name, volume, &volume_name_wchar) == S_OK) {
|
|
BLI_strncpy_wchar_as_utf8(line, volume_name_wchar, FILE_MAXDIR);
|
|
name = line;
|
|
CoTaskMemFree(volume_name_wchar);
|
|
}
|
|
}
|
|
CoTaskMemFree(volume);
|
|
}
|
|
desktop->lpVtbl->Release(desktop);
|
|
}
|
|
}
|
|
if (name == NULL) {
|
|
name = tmps;
|
|
}
|
|
|
|
int icon = ICON_DISK_DRIVE;
|
|
switch (GetDriveType(tmps)) {
|
|
case DRIVE_REMOVABLE:
|
|
icon = ICON_EXTERNAL_DRIVE;
|
|
break;
|
|
case DRIVE_CDROM:
|
|
icon = ICON_DISC;
|
|
break;
|
|
case DRIVE_FIXED:
|
|
case DRIVE_RAMDISK:
|
|
icon = ICON_DISK_DRIVE;
|
|
break;
|
|
case DRIVE_REMOTE:
|
|
icon = ICON_NETWORK_DRIVE;
|
|
break;
|
|
}
|
|
|
|
fsmenu_insert_entry(fsmenu,
|
|
FS_CATEGORY_SYSTEM,
|
|
tmps,
|
|
name,
|
|
icon,
|
|
FS_INSERT_SORTED | FS_INSERT_NO_VALIDATE);
|
|
}
|
|
}
|
|
|
|
/* Get Special Folder Locations. */
|
|
if (read_bookmarks) {
|
|
|
|
/* These items are shown in System List. */
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Profile,
|
|
N_("Home"),
|
|
ICON_HOME,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Desktop,
|
|
N_("Desktop"),
|
|
ICON_DESKTOP,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Documents,
|
|
N_("Documents"),
|
|
ICON_DOCUMENTS,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Downloads,
|
|
N_("Downloads"),
|
|
ICON_IMPORT,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Music,
|
|
N_("Music"),
|
|
ICON_FILE_SOUND,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Pictures,
|
|
N_("Pictures"),
|
|
ICON_FILE_IMAGE,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Videos,
|
|
N_("Videos"),
|
|
ICON_FILE_MOVIE,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_Fonts,
|
|
N_("Fonts"),
|
|
ICON_FILE_FONT,
|
|
FS_INSERT_LAST);
|
|
fsmenu_add_windows_folder(fsmenu,
|
|
FS_CATEGORY_SYSTEM_BOOKMARKS,
|
|
&FOLDERID_SkyDrive,
|
|
N_("OneDrive"),
|
|
ICON_URL,
|
|
FS_INSERT_LAST);
|
|
|
|
/* These items are just put in path cache for thumbnail views and if bookmarked. */
|
|
|
|
fsmenu_add_windows_folder(
|
|
fsmenu, FS_CATEGORY_OTHER, &FOLDERID_UserProfiles, NULL, ICON_COMMUNITY, FS_INSERT_LAST);
|
|
}
|
|
}
|
|
#elif defined(__APPLE__)
|
|
{
|
|
/* We store some known macOS system paths and corresponding icons
|
|
* and names in the FS_CATEGORY_OTHER (not displayed directly) category. */
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_OTHER, "/Library/Fonts/", N_("Fonts"), ICON_FILE_FONT, FS_INSERT_LAST);
|
|
fsmenu_insert_entry(fsmenu,
|
|
FS_CATEGORY_OTHER,
|
|
"/Applications/",
|
|
N_("Applications"),
|
|
ICON_FILE_FOLDER,
|
|
FS_INSERT_LAST);
|
|
|
|
const char *home = BLI_getenv("HOME");
|
|
if (home) {
|
|
# define FS_MACOS_PATH(path, name, icon) \
|
|
BLI_snprintf(line, sizeof(line), path, home); \
|
|
fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, line, name, icon, FS_INSERT_LAST);
|
|
|
|
FS_MACOS_PATH("%s/", NULL, ICON_HOME)
|
|
FS_MACOS_PATH("%s/Desktop/", N_("Desktop"), ICON_DESKTOP)
|
|
FS_MACOS_PATH("%s/Documents/", N_("Documents"), ICON_DOCUMENTS)
|
|
FS_MACOS_PATH("%s/Downloads/", N_("Downloads"), ICON_IMPORT)
|
|
FS_MACOS_PATH("%s/Movies/", N_("Movies"), ICON_FILE_MOVIE)
|
|
FS_MACOS_PATH("%s/Music/", N_("Music"), ICON_FILE_SOUND)
|
|
FS_MACOS_PATH("%s/Pictures/", N_("Pictures"), ICON_FILE_IMAGE)
|
|
FS_MACOS_PATH("%s/Library/Fonts/", N_("Fonts"), ICON_FILE_FONT)
|
|
|
|
# undef FS_MACOS_PATH
|
|
}
|
|
|
|
/* Get mounted volumes better method OSX 10.6 and higher, see:
|
|
* https://developer.apple.com/library/mac/#documentation/CoreFoundation/Reference/CFURLRef/Reference/reference.html
|
|
*/
|
|
|
|
/* We get all volumes sorted including network and do not relay
|
|
* on user-defined finder visibility, less confusing. */
|
|
|
|
CFURLRef cfURL = NULL;
|
|
CFURLEnumeratorResult result = kCFURLEnumeratorSuccess;
|
|
CFURLEnumeratorRef volEnum = CFURLEnumeratorCreateForMountedVolumes(
|
|
NULL, kCFURLEnumeratorSkipInvisibles, NULL);
|
|
|
|
while (result != kCFURLEnumeratorEnd) {
|
|
char defPath[FILE_MAX];
|
|
|
|
result = CFURLEnumeratorGetNextURL(volEnum, &cfURL, NULL);
|
|
if (result != kCFURLEnumeratorSuccess) {
|
|
continue;
|
|
}
|
|
|
|
CFURLGetFileSystemRepresentation(cfURL, false, (UInt8 *)defPath, FILE_MAX);
|
|
|
|
/* Get name of the volume. */
|
|
char name[FILE_MAXFILE] = "";
|
|
CFStringRef nameString = NULL;
|
|
CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeLocalizedNameKey, &nameString, NULL);
|
|
if (nameString != NULL) {
|
|
CFStringGetCString(nameString, name, sizeof(name), kCFStringEncodingUTF8);
|
|
CFRelease(nameString);
|
|
}
|
|
|
|
/* Set icon for regular, removable or network drive. */
|
|
int icon = ICON_DISK_DRIVE;
|
|
CFBooleanRef localKey = NULL;
|
|
CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsLocalKey, &localKey, NULL);
|
|
if (localKey != NULL) {
|
|
if (!CFBooleanGetValue(localKey)) {
|
|
icon = ICON_NETWORK_DRIVE;
|
|
}
|
|
else {
|
|
CFBooleanRef ejectableKey = NULL;
|
|
CFURLCopyResourcePropertyForKey(cfURL, kCFURLVolumeIsEjectableKey, &ejectableKey, NULL);
|
|
if (ejectableKey != NULL) {
|
|
if (CFBooleanGetValue(ejectableKey)) {
|
|
icon = ICON_EXTERNAL_DRIVE;
|
|
}
|
|
CFRelease(ejectableKey);
|
|
}
|
|
}
|
|
CFRelease(localKey);
|
|
}
|
|
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM, defPath, name[0] ? name : NULL, icon, FS_INSERT_SORTED);
|
|
}
|
|
|
|
CFRelease(volEnum);
|
|
|
|
/* kLSSharedFileListFavoriteItems is deprecated, but available till macOS 10.15.
|
|
* Will have to find a new method to sync the Finder Favorites with File Browser. */
|
|
# pragma GCC diagnostic push
|
|
# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
|
/* Finally get user favorite places */
|
|
if (read_bookmarks) {
|
|
UInt32 seed;
|
|
LSSharedFileListRef list = LSSharedFileListCreate(
|
|
NULL, kLSSharedFileListFavoriteItems, NULL);
|
|
CFArrayRef pathesArray = LSSharedFileListCopySnapshot(list, &seed);
|
|
CFIndex pathesCount = CFArrayGetCount(pathesArray);
|
|
|
|
for (CFIndex i = 0; i < pathesCount; i++) {
|
|
LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)CFArrayGetValueAtIndex(
|
|
pathesArray, i);
|
|
|
|
CFURLRef cfURL = NULL;
|
|
OSErr err = LSSharedFileListItemResolve(itemRef,
|
|
kLSSharedFileListNoUserInteraction |
|
|
kLSSharedFileListDoNotMountVolumes,
|
|
&cfURL,
|
|
NULL);
|
|
if (err != noErr || !cfURL) {
|
|
continue;
|
|
}
|
|
|
|
CFStringRef pathString = CFURLCopyFileSystemPath(cfURL, kCFURLPOSIXPathStyle);
|
|
|
|
if (pathString == NULL ||
|
|
!CFStringGetCString(pathString, line, sizeof(line), kCFStringEncodingUTF8)) {
|
|
continue;
|
|
}
|
|
|
|
/* Exclude "all my files" as it makes no sense in blender file-selector. */
|
|
/* Exclude "airdrop" if wlan not active as it would show "" ) */
|
|
if (!strstr(line, "myDocuments.cannedSearch") && (*line != '\0')) {
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, line, NULL, ICON_FILE_FOLDER, FS_INSERT_LAST);
|
|
}
|
|
|
|
CFRelease(pathString);
|
|
CFRelease(cfURL);
|
|
}
|
|
|
|
CFRelease(pathesArray);
|
|
CFRelease(list);
|
|
}
|
|
# pragma GCC diagnostic pop
|
|
}
|
|
#else
|
|
/* unix */
|
|
{
|
|
const char *home = BLI_getenv("HOME");
|
|
|
|
if (read_bookmarks && home) {
|
|
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, home, N_("Home"), ICON_HOME, FS_INSERT_LAST);
|
|
|
|
/* Follow the XDG spec, check if these are available. */
|
|
GHash *xdg_map = fsmenu_xdg_user_dirs_parse(home);
|
|
|
|
struct {
|
|
const char *key;
|
|
const char *default_path;
|
|
BIFIconID icon;
|
|
} xdg_items[] = {
|
|
{"XDG_DESKTOP_DIR", "Desktop", ICON_DESKTOP},
|
|
{"XDG_DOCUMENTS_DIR", "Documents", ICON_DOCUMENTS},
|
|
{"XDG_DOWNLOAD_DIR", "Downloads", ICON_IMPORT},
|
|
{"XDG_VIDEOS_DIR", "Videos", ICON_FILE_MOVIE},
|
|
{"XDG_PICTURES_DIR", "Pictures", ICON_FILE_IMAGE},
|
|
{"XDG_MUSIC_DIR", "Music", ICON_FILE_SOUND},
|
|
};
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(xdg_items); i++) {
|
|
fsmenu_xdg_insert_entry(
|
|
xdg_map, fsmenu, xdg_items[i].key, xdg_items[i].default_path, xdg_items[i].icon, home);
|
|
}
|
|
|
|
fsmenu_xdg_user_dirs_free(xdg_map);
|
|
}
|
|
|
|
{
|
|
int found = 0;
|
|
# ifdef __linux__
|
|
/* loop over mount points */
|
|
struct mntent *mnt;
|
|
FILE *fp;
|
|
|
|
fp = setmntent(MOUNTED, "r");
|
|
if (fp == NULL) {
|
|
fprintf(stderr, "could not get a list of mounted file-systems\n");
|
|
}
|
|
else {
|
|
while ((mnt = getmntent(fp))) {
|
|
if (STRPREFIX(mnt->mnt_dir, "/boot")) {
|
|
/* Hide share not usable to the user. */
|
|
continue;
|
|
}
|
|
if (!STRPREFIX(mnt->mnt_fsname, "/dev")) {
|
|
continue;
|
|
}
|
|
if (STRPREFIX(mnt->mnt_fsname, "/dev/loop")) {
|
|
/* The dev/loop* entries are SNAPS used by desktop environment
|
|
* (Gnome) no need for them to show up in the list. */
|
|
continue;
|
|
}
|
|
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM, mnt->mnt_dir, NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
|
|
|
|
found = 1;
|
|
}
|
|
if (endmntent(fp) == 0) {
|
|
fprintf(stderr, "could not close the list of mounted file-systems\n");
|
|
}
|
|
}
|
|
/* Check gvfs shares. */
|
|
const char *const xdg_runtime_dir = BLI_getenv("XDG_RUNTIME_DIR");
|
|
if (xdg_runtime_dir != NULL) {
|
|
struct direntry *dirs;
|
|
char name[FILE_MAX];
|
|
BLI_join_dirfile(name, sizeof(name), xdg_runtime_dir, "gvfs/");
|
|
const uint dirs_num = BLI_filelist_dir_contents(name, &dirs);
|
|
for (uint i = 0; i < dirs_num; i++) {
|
|
if (dirs[i].type & S_IFDIR) {
|
|
const char *dirname = dirs[i].relname;
|
|
if (dirname[0] != '.') {
|
|
/* Dir names contain a lot of unwanted text.
|
|
* Assuming every entry ends with the share name */
|
|
const char *label = strstr(dirname, "share=");
|
|
if (label != NULL) {
|
|
/* Move pointer so "share=" is trimmed off
|
|
* or use full dirname as label. */
|
|
const char *label_test = label + 6;
|
|
label = *label_test ? label_test : dirname;
|
|
}
|
|
BLI_snprintf(line, sizeof(line), "%s%s", name, dirname);
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM, line, label, ICON_NETWORK_DRIVE, FS_INSERT_SORTED);
|
|
found = 1;
|
|
}
|
|
}
|
|
}
|
|
BLI_filelist_free(dirs, dirs_num);
|
|
}
|
|
# endif
|
|
|
|
/* fallback */
|
|
if (!found) {
|
|
fsmenu_insert_entry(
|
|
fsmenu, FS_CATEGORY_SYSTEM, "/", NULL, ICON_DISK_DRIVE, FS_INSERT_SORTED);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(WIN32) || defined(__APPLE__)
|
|
/* Quiet warnings. */
|
|
UNUSED_VARS(fsmenu_xdg_insert_entry, fsmenu_xdg_user_dirs_parse, fsmenu_xdg_user_dirs_free);
|
|
#endif
|
|
|
|
/* For all platforms, we add some directories from User Preferences to
|
|
* the FS_CATEGORY_OTHER category so that these directories
|
|
* have the appropriate icons when they are added to the Bookmarks. */
|
|
#define FS_UDIR_PATH(dir, icon) \
|
|
if (BLI_strnlen(dir, 3) > 2) { \
|
|
fsmenu_insert_entry(fsmenu, FS_CATEGORY_OTHER, dir, NULL, icon, FS_INSERT_LAST); \
|
|
}
|
|
|
|
FS_UDIR_PATH(U.fontdir, ICON_FILE_FONT)
|
|
FS_UDIR_PATH(U.textudir, ICON_FILE_IMAGE)
|
|
FS_UDIR_PATH(U.pythondir, ICON_FILE_SCRIPT)
|
|
FS_UDIR_PATH(U.sounddir, ICON_FILE_SOUND)
|
|
FS_UDIR_PATH(U.tempdir, ICON_TEMP)
|
|
|
|
#undef FS_UDIR_PATH
|
|
}
|
|
|
|
static void fsmenu_free_category(struct FSMenu *fsmenu, FSMenuCategory category)
|
|
{
|
|
FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
|
|
|
|
while (fsm_iter) {
|
|
FSMenuEntry *fsm_next = fsm_iter->next;
|
|
|
|
if (fsm_iter->path) {
|
|
MEM_freeN(fsm_iter->path);
|
|
}
|
|
MEM_freeN(fsm_iter);
|
|
|
|
fsm_iter = fsm_next;
|
|
}
|
|
}
|
|
|
|
void fsmenu_refresh_system_category(struct FSMenu *fsmenu)
|
|
{
|
|
fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM);
|
|
ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM, NULL);
|
|
|
|
fsmenu_free_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
|
|
ED_fsmenu_set_category(fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS, NULL);
|
|
|
|
/* Add all entries to system category */
|
|
fsmenu_read_system(fsmenu, true);
|
|
}
|
|
|
|
static void fsmenu_free_ex(FSMenu **fsmenu)
|
|
{
|
|
if (*fsmenu != NULL) {
|
|
fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM);
|
|
fsmenu_free_category(*fsmenu, FS_CATEGORY_SYSTEM_BOOKMARKS);
|
|
fsmenu_free_category(*fsmenu, FS_CATEGORY_BOOKMARKS);
|
|
fsmenu_free_category(*fsmenu, FS_CATEGORY_RECENT);
|
|
fsmenu_free_category(*fsmenu, FS_CATEGORY_OTHER);
|
|
MEM_freeN(*fsmenu);
|
|
}
|
|
|
|
*fsmenu = NULL;
|
|
}
|
|
|
|
void fsmenu_free(void)
|
|
{
|
|
fsmenu_free_ex(&g_fsmenu);
|
|
}
|
|
|
|
static void fsmenu_copy_category(struct FSMenu *fsmenu_dst,
|
|
struct FSMenu *fsmenu_src,
|
|
const FSMenuCategory category)
|
|
{
|
|
FSMenuEntry *fsm_dst_prev = NULL, *fsm_dst_head = NULL;
|
|
FSMenuEntry *fsm_src_iter = ED_fsmenu_get_category(fsmenu_src, category);
|
|
|
|
for (; fsm_src_iter != NULL; fsm_src_iter = fsm_src_iter->next) {
|
|
FSMenuEntry *fsm_dst = MEM_dupallocN(fsm_src_iter);
|
|
if (fsm_dst->path != NULL) {
|
|
fsm_dst->path = MEM_dupallocN(fsm_dst->path);
|
|
}
|
|
|
|
if (fsm_dst_prev != NULL) {
|
|
fsm_dst_prev->next = fsm_dst;
|
|
}
|
|
else {
|
|
fsm_dst_head = fsm_dst;
|
|
}
|
|
fsm_dst_prev = fsm_dst;
|
|
}
|
|
|
|
ED_fsmenu_set_category(fsmenu_dst, category, fsm_dst_head);
|
|
}
|
|
|
|
static FSMenu *fsmenu_copy(FSMenu *fsmenu)
|
|
{
|
|
FSMenu *fsmenu_copy = MEM_dupallocN(fsmenu);
|
|
|
|
fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM);
|
|
fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_SYSTEM_BOOKMARKS);
|
|
fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_BOOKMARKS);
|
|
fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_RECENT);
|
|
fsmenu_copy_category(fsmenu_copy, fsmenu_copy, FS_CATEGORY_OTHER);
|
|
|
|
return fsmenu_copy;
|
|
}
|
|
|
|
int fsmenu_get_active_indices(struct FSMenu *fsmenu, enum FSMenuCategory category, const char *dir)
|
|
{
|
|
FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, category);
|
|
int i;
|
|
|
|
for (i = 0; fsm_iter; fsm_iter = fsm_iter->next, i++) {
|
|
if (BLI_path_cmp(dir, fsm_iter->path) == 0) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/* Thanks to some bookmarks sometimes being network drives that can have tens of seconds of delay
|
|
* before being defined as unreachable by the OS, we need to validate the bookmarks in an async
|
|
* job...
|
|
*/
|
|
static void fsmenu_bookmark_validate_job_startjob(
|
|
void *fsmenuv,
|
|
/* Cannot be const, this function implements wm_jobs_start_callback.
|
|
* NOLINTNEXTLINE: readability-non-const-parameter. */
|
|
short *stop,
|
|
short *do_update,
|
|
float *UNUSED(progress))
|
|
{
|
|
FSMenu *fsmenu = fsmenuv;
|
|
|
|
int categories[] = {
|
|
FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
|
|
|
|
for (size_t i = ARRAY_SIZE(categories); i--;) {
|
|
FSMenuEntry *fsm_iter = ED_fsmenu_get_category(fsmenu, categories[i]);
|
|
for (; fsm_iter; fsm_iter = fsm_iter->next) {
|
|
if (*stop) {
|
|
return;
|
|
}
|
|
/* Note that we do not really need atomics primitives or thread locks here, since this only
|
|
* sets one short, which is assumed to be 'atomic'-enough for us here. */
|
|
fsmenu_entry_refresh_valid(fsm_iter);
|
|
*do_update = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fsmenu_bookmark_validate_job_update(void *fsmenuv)
|
|
{
|
|
FSMenu *fsmenu_job = fsmenuv;
|
|
|
|
int categories[] = {
|
|
FS_CATEGORY_SYSTEM, FS_CATEGORY_SYSTEM_BOOKMARKS, FS_CATEGORY_BOOKMARKS, FS_CATEGORY_RECENT};
|
|
|
|
for (size_t i = ARRAY_SIZE(categories); i--;) {
|
|
FSMenuEntry *fsm_iter_src = ED_fsmenu_get_category(fsmenu_job, categories[i]);
|
|
FSMenuEntry *fsm_iter_dst = ED_fsmenu_get_category(ED_fsmenu_get(), categories[i]);
|
|
for (; fsm_iter_dst != NULL; fsm_iter_dst = fsm_iter_dst->next) {
|
|
while (fsm_iter_src != NULL && !STREQ(fsm_iter_dst->path, fsm_iter_src->path)) {
|
|
fsm_iter_src = fsm_iter_src->next;
|
|
}
|
|
if (fsm_iter_src == NULL) {
|
|
return;
|
|
}
|
|
fsm_iter_dst->valid = fsm_iter_src->valid;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fsmenu_bookmark_validate_job_end(void *fsmenuv)
|
|
{
|
|
/* In case there would be some dangling update... */
|
|
fsmenu_bookmark_validate_job_update(fsmenuv);
|
|
}
|
|
|
|
static void fsmenu_bookmark_validate_job_free(void *fsmenuv)
|
|
{
|
|
FSMenu *fsmenu = fsmenuv;
|
|
fsmenu_free_ex(&fsmenu);
|
|
}
|
|
|
|
static void fsmenu_bookmark_validate_job_start(wmWindowManager *wm)
|
|
{
|
|
wmJob *wm_job;
|
|
FSMenu *fsmenu_job = fsmenu_copy(g_fsmenu);
|
|
|
|
/* setup job */
|
|
wm_job = WM_jobs_get(
|
|
wm, wm->winactive, wm, "Validating Bookmarks...", 0, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
|
|
WM_jobs_customdata_set(wm_job, fsmenu_job, fsmenu_bookmark_validate_job_free);
|
|
WM_jobs_timer(wm_job, 0.01, NC_SPACE | ND_SPACE_FILE_LIST, NC_SPACE | ND_SPACE_FILE_LIST);
|
|
WM_jobs_callbacks(wm_job,
|
|
fsmenu_bookmark_validate_job_startjob,
|
|
NULL,
|
|
fsmenu_bookmark_validate_job_update,
|
|
fsmenu_bookmark_validate_job_end);
|
|
|
|
/* start the job */
|
|
WM_jobs_start(wm, wm_job);
|
|
}
|
|
|
|
static void fsmenu_bookmark_validate_job_stop(wmWindowManager *wm)
|
|
{
|
|
WM_jobs_kill_type(wm, wm, WM_JOB_TYPE_FSMENU_BOOKMARK_VALIDATE);
|
|
}
|
|
|
|
void fsmenu_refresh_bookmarks_status(wmWindowManager *wm, FSMenu *fsmenu)
|
|
{
|
|
BLI_assert(fsmenu == ED_fsmenu_get());
|
|
UNUSED_VARS_NDEBUG(fsmenu);
|
|
|
|
fsmenu_bookmark_validate_job_stop(wm);
|
|
fsmenu_bookmark_validate_job_start(wm);
|
|
}
|