feat: move Files to its own process; add window server messaging (multi-format clipboard, drag and drop), undo/redo, sidebar, breadcrumbs, thumbnails, file type icons, New menu and proper text fields
This commit is contained in:
1 parent
07191b1286
commit
d3e49a925d
66 files changed
+6855
-905
No files matched your search
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* navigation.cpp
|
||||
* Navigation and file opening logic for the Files file manager
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "filemanager_internal.hpp"
|
||||
|
||||
namespace filemanager {
|
||||
|
||||
static void filemanager_store_location(FileManagerLocation* location,
|
||||
const char* path,
|
||||
FileManagerVirtualViewKind virtual_view) {
|
||||
if (!location) return;
|
||||
montauk::strncpy(location->path, path ? path : "", sizeof(location->path));
|
||||
location->virtual_view = virtual_view;
|
||||
}
|
||||
|
||||
static bool filemanager_location_matches_current(const FileManagerState* fm,
|
||||
const FileManagerLocation& location) {
|
||||
return fm
|
||||
&& location.virtual_view == fm->virtual_view
|
||||
&& montauk::streq(location.path, fm->current_path);
|
||||
}
|
||||
|
||||
static void filemanager_restore_location(FileManagerState* fm, const FileManagerLocation& location) {
|
||||
montauk::strncpy(fm->current_path, location.path, sizeof(fm->current_path));
|
||||
|
||||
if (location.virtual_view != FM_VIRTUAL_VIEW_NONE) {
|
||||
filemanager_read_virtual_view(fm, location.virtual_view);
|
||||
} else if (location.path[0] == '\0') {
|
||||
filemanager_read_drives(fm);
|
||||
} else {
|
||||
filemanager_read_dir(fm);
|
||||
}
|
||||
}
|
||||
|
||||
void filemanager_push_history(FileManagerState* fm) {
|
||||
if (!fm) return;
|
||||
|
||||
if (fm->history_count > 0 && fm->history_pos >= 0) {
|
||||
if (filemanager_location_matches_current(fm, fm->history[fm->history_pos])) return;
|
||||
}
|
||||
|
||||
if (fm->history_pos < fm->history_count - 1) {
|
||||
fm->history_count = fm->history_pos + 1;
|
||||
}
|
||||
|
||||
if (fm->history_count >= 16) {
|
||||
for (int i = 1; i < 16; i++) {
|
||||
fm->history[i - 1] = fm->history[i];
|
||||
}
|
||||
fm->history_count = 15;
|
||||
fm->history_pos = 14;
|
||||
}
|
||||
|
||||
fm->history_pos++;
|
||||
filemanager_store_location(&fm->history[fm->history_pos], fm->current_path, fm->virtual_view);
|
||||
fm->history_count = fm->history_pos + 1;
|
||||
}
|
||||
|
||||
void filemanager_navigate(FileManagerState* fm, const char* name) {
|
||||
int path_len = montauk::slen(fm->current_path);
|
||||
if (path_len > 0 && fm->current_path[path_len - 1] != '/') {
|
||||
str_append(fm->current_path, "/", 256);
|
||||
}
|
||||
str_append(fm->current_path, name, 256);
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_go_up(FileManagerState* fm) {
|
||||
if (filemanager_is_computer_view(fm)) return;
|
||||
|
||||
// The Trash lives under a hidden folder of home; its parent is Computer,
|
||||
// where its icon is.
|
||||
if (filemanager_is_virtual_view(fm) || filemanager_is_trash_view(fm)) {
|
||||
filemanager_read_drives(fm);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
int len = montauk::slen(fm->current_path);
|
||||
|
||||
// At drive root (e.g. "0:/" or "10:/") -- go to drives view
|
||||
bool is_drive_root = false;
|
||||
if (len >= 3 && fm->current_path[len - 1] == '/') {
|
||||
int colon = -1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (fm->current_path[i] == ':') { colon = i; break; }
|
||||
}
|
||||
if (colon >= 0 && colon + 2 == len) is_drive_root = true;
|
||||
}
|
||||
if (is_drive_root) {
|
||||
filemanager_read_drives(fm);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len > 0 && fm->current_path[len - 1] == '/') {
|
||||
fm->current_path[len - 1] = '\0';
|
||||
len--;
|
||||
}
|
||||
|
||||
int last_slash = -1;
|
||||
for (int i = len - 1; i >= 0; i--) {
|
||||
if (fm->current_path[i] == '/') { last_slash = i; break; }
|
||||
}
|
||||
if (last_slash >= 0) {
|
||||
if (last_slash > 0 && fm->current_path[last_slash - 1] == ':') {
|
||||
fm->current_path[last_slash + 1] = '\0';
|
||||
} else {
|
||||
fm->current_path[last_slash] = '\0';
|
||||
}
|
||||
}
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_navigate_to_history(FileManagerState* fm) {
|
||||
if (!fm || fm->history_pos < 0 || fm->history_pos >= fm->history_count) return;
|
||||
filemanager_restore_location(fm, fm->history[fm->history_pos]);
|
||||
}
|
||||
|
||||
void filemanager_go_back(FileManagerState* fm) {
|
||||
if (fm->history_pos <= 0) return;
|
||||
fm->history_pos--;
|
||||
filemanager_navigate_to_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_go_forward(FileManagerState* fm) {
|
||||
if (fm->history_pos >= fm->history_count - 1) return;
|
||||
fm->history_pos++;
|
||||
filemanager_navigate_to_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_go_to_path(FileManagerState* fm, const char* path) {
|
||||
if (!fm) return;
|
||||
if (!path || !path[0]) {
|
||||
filemanager_go_home(fm);
|
||||
return;
|
||||
}
|
||||
if (path != fm->current_path) montauk::strncpy(fm->current_path, path, sizeof(fm->current_path));
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_open_trash(FileManagerState* fm) {
|
||||
if (!fm || !filemanager_trash_ensure(fm->desktop)) return;
|
||||
if (!filemanager_trash_files_dir(fm->desktop, fm->current_path, sizeof(fm->current_path)))
|
||||
return;
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_go_home(FileManagerState* fm) {
|
||||
filemanager_read_drives(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_refresh(FileManagerState* fm) {
|
||||
if (!fm) return;
|
||||
// Drives and bookmarks may have come and gone.
|
||||
filemanager_sidebar_rebuild(fm);
|
||||
if (filemanager_is_virtual_view(fm)) {
|
||||
filemanager_read_virtual_view(fm, fm->virtual_view);
|
||||
} else if (filemanager_is_computer_view(fm)) {
|
||||
filemanager_read_drives(fm);
|
||||
} else {
|
||||
filemanager_read_dir(fm);
|
||||
}
|
||||
}
|
||||
|
||||
void filemanager_start_pathbar(FileManagerState* fm) {
|
||||
fm->pathbar_editing = true;
|
||||
const char* path = filemanager_is_computer_view(fm) || filemanager_is_virtual_view(fm)
|
||||
? "" : fm->current_path;
|
||||
// All selected, so typing replaces the path and the arrows keep it.
|
||||
filemanager_field_set(fm->pathbar, path, sizeof(fm->pathbar.buf), 8, 0, montauk::slen(path));
|
||||
}
|
||||
|
||||
void filemanager_cancel_pathbar(FileManagerState* fm) {
|
||||
fm->pathbar_editing = false;
|
||||
}
|
||||
|
||||
// True if `path` begins with a "N:/" volume prefix (one or more digits then ":/").
|
||||
static bool filemanager_has_volume_prefix(const char* path) {
|
||||
int i = 0;
|
||||
while (path[i] >= '0' && path[i] <= '9') i++;
|
||||
return i > 0 && path[i] == ':' && path[i + 1] == '/';
|
||||
}
|
||||
|
||||
// Resolve a user-typed path into a fully-qualified "N:/..." path. Paths without
|
||||
// a volume number (e.g. "/os" or "os") are drive-relative; the kernel resolves
|
||||
// them against drive 0, but filemanager_read_dir strips a drive-root-relative
|
||||
// prefix, so an unqualified path would leave the returned basenames with their
|
||||
// full sub-path. Qualify it here so the prefix stripping matches. The drive of
|
||||
// the current location is preferred, falling back to drive 0.
|
||||
static void filemanager_qualify_path(FileManagerState* fm, char* out, int out_max) {
|
||||
const char* in = fm->pathbar.buf;
|
||||
if (filemanager_has_volume_prefix(in)) {
|
||||
montauk::strcpy(out, in);
|
||||
return;
|
||||
}
|
||||
|
||||
char drive[8] = "0";
|
||||
if (filemanager_has_volume_prefix(fm->current_path)) {
|
||||
int i = 0;
|
||||
while (fm->current_path[i] >= '0' && fm->current_path[i] <= '9' && i < 6) {
|
||||
drive[i] = fm->current_path[i];
|
||||
i++;
|
||||
}
|
||||
drive[i] = '\0';
|
||||
}
|
||||
|
||||
montauk::strcpy(out, drive);
|
||||
str_append(out, ":", out_max);
|
||||
if (in[0] != '/') str_append(out, "/", out_max);
|
||||
str_append(out, in, out_max);
|
||||
}
|
||||
|
||||
void filemanager_commit_pathbar(FileManagerState* fm) {
|
||||
fm->pathbar_editing = false;
|
||||
if (filemanager_field_len(fm->pathbar) == 0) {
|
||||
filemanager_read_drives(fm);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
filemanager_qualify_path(fm, fm->current_path, sizeof(fm->current_path));
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
|
||||
void filemanager_open_entry(FileManagerState* fm, int idx) {
|
||||
if (idx < 0 || idx >= fm->entry_count) return;
|
||||
|
||||
if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_SPECIAL_DIR) {
|
||||
DesktopState* ds = fm->desktop;
|
||||
if (ds && ds->home_dir[0] != '\0') {
|
||||
montauk::strcpy(fm->current_path, ds->home_dir);
|
||||
int plen = montauk::slen(fm->current_path);
|
||||
if (plen > 0 && fm->current_path[plen - 1] != '/')
|
||||
str_append(fm->current_path, "/", 256);
|
||||
str_append(fm->current_path, fm->entry_names[idx], 256);
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_APPS_ROOT) {
|
||||
filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_APPS);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_computer_view(fm)
|
||||
&& fm->entry_types[idx] == FM_ENTRY_SYSTEM_CONFIGURATION_ROOT) {
|
||||
filemanager_read_virtual_view(fm, FM_VIRTUAL_VIEW_SYSTEM_CONFIGURATION);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_virtual_view(fm) && fm->entry_types[idx] == FM_ENTRY_VIRTUAL_ITEM) {
|
||||
DesktopState* ds = fm->desktop;
|
||||
const FileManagerVirtualEntry& entry = fm->virtual_entries[idx];
|
||||
if (ds && entry.desktop_item_index >= 0
|
||||
&& entry.desktop_item_index < ds->desktop_item_count) {
|
||||
desktop_launch_item(ds, &ds->desktop_items[entry.desktop_item_index]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_HOME) {
|
||||
DesktopState* ds = fm->desktop;
|
||||
if (ds && ds->home_dir[0] != '\0') {
|
||||
montauk::strcpy(fm->current_path, ds->home_dir);
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_TRASH) {
|
||||
filemanager_open_trash(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (filemanager_is_computer_view(fm) && fm->entry_types[idx] == FM_ENTRY_DRIVE) {
|
||||
int d = fm->drive_indices[idx];
|
||||
char dpath[8];
|
||||
if (d < 10) {
|
||||
dpath[0] = '0' + d; dpath[1] = ':'; dpath[2] = '/'; dpath[3] = '\0';
|
||||
} else {
|
||||
dpath[0] = '1'; dpath[1] = '0' + (d - 10); dpath[2] = ':'; dpath[3] = '/'; dpath[4] = '\0';
|
||||
}
|
||||
montauk::strcpy(fm->current_path, dpath);
|
||||
filemanager_read_dir(fm);
|
||||
filemanager_push_history(fm);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fm->is_dir[idx]) {
|
||||
filemanager_navigate(fm, fm->entry_names[idx]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the file with its default app (see associations.cpp).
|
||||
DesktopState* ds = fm->desktop;
|
||||
const char* name = fm->entry_names[idx];
|
||||
char fullpath[512];
|
||||
filemanager_build_fullpath(fullpath, 512, fm->current_path, name);
|
||||
if (str_ends_with(name, ".elf")) {
|
||||
// An installed app's binary starts the way the launcher would start
|
||||
// it; any other program runs with Files' own (narrower) authority.
|
||||
int item = files_host::find_item_by_binary(ds, fullpath);
|
||||
if (item >= 0) desktop_launch_item(ds, &ds->desktop_items[item]);
|
||||
else desktop_spawn_app(ds, fullpath);
|
||||
return;
|
||||
}
|
||||
|
||||
char ext[16];
|
||||
file_extension(name, ext, sizeof(ext));
|
||||
int app = filemanager_assoc_default(ds, ext);
|
||||
if (app >= 0 && filemanager_launch_with(ds, app, fullpath)) return;
|
||||
|
||||
// Nothing claims this type: text still goes straight to the Text Editor
|
||||
// (READMEs, Makefiles, odd extensions); anything else asks.
|
||||
if (filemanager_file_looks_textual(fullpath)) {
|
||||
int editor = files_host::find_item_by_binary(ds, "0:/apps/texteditor/texteditor.elf");
|
||||
if (editor < 0 || !filemanager_launch_with(ds, editor, fullpath))
|
||||
desktop_spawn_app(ds, "0:/apps/texteditor/texteditor.elf", fullpath);
|
||||
return;
|
||||
}
|
||||
filemanager_show_open_with(fm, idx);
|
||||
}
|
||||
|
||||
} // namespace filemanager
|
||||
Reference in new issue
Block a user