feat: files app - render modified dates in Properties dialog

This commit is contained in:
2026-07-21 14:44:44 +02:00
parent de7edaf623
commit e9d8c66ff5
5 changed files with 81 additions and 16 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 4 #define MONTAUK_BUILD_NUMBER 6
+42
View File
@@ -158,6 +158,48 @@ inline void format_size(char* buf, uint64_t size) {
} }
} }
// ============================================================================
// Date/time formatting
// ============================================================================
// Format a UTC unix timestamp as a local "YYYY-MM-DD HH:MM" string.
// tz_offset_minutes shifts UTC to local time (pass montauk::gettz()). A zero
// or negative timestamp -- meaning the filesystem did not record a time -- is
// rendered as "--". buf must hold at least 17 bytes.
inline void format_datetime(char* buf, int buf_size, int64_t unix_utc, int tz_offset_minutes) {
if (buf_size <= 0) return;
if (unix_utc <= 0) { snprintf(buf, buf_size, "--"); return; }
int64_t t = unix_utc + (int64_t)tz_offset_minutes * 60;
if (t < 0) t = 0;
int64_t days = t / 86400;
int64_t rem = t % 86400;
int hour = (int)(rem / 3600);
int minute = (int)((rem % 3600) / 60);
int year = 1970;
for (;;) {
bool leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
int diy = leap ? 366 : 365;
if (days < diy) break;
days -= diy;
year++;
}
static const int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int month = 0;
for (; month < 12; month++) {
int dim = mdays[month];
if (month == 1 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) dim = 29;
if (days < dim) break;
days -= dim;
}
int day = (int)days + 1;
snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d", year, month + 1, day, hour, minute);
}
// ============================================================================ // ============================================================================
// Desktop builtins // Desktop builtins
// ============================================================================ // ============================================================================
@@ -70,6 +70,7 @@ struct FileManagerState {
char (*entry_names)[128]; char (*entry_names)[128];
int* entry_types; int* entry_types;
uint64_t* entry_sizes; // 64-bit: avoids overflow on files >= 2 GiB uint64_t* entry_sizes; // 64-bit: avoids overflow on files >= 2 GiB
int64_t* entry_mtimes; // last-modified time, UTC unix seconds (0 = unknown)
int entry_count; int entry_count;
int entry_cap; // allocated capacity of the per-entry arrays int entry_cap; // allocated capacity of the per-entry arrays
int selected; int selected;
@@ -30,6 +30,9 @@ bool filemanager_ensure_entry_capacity(FileManagerState* fm, int needed) {
uint64_t* sizes = (uint64_t*)montauk::realloc(fm->entry_sizes, (uint64_t)new_cap * sizeof(uint64_t)); uint64_t* sizes = (uint64_t*)montauk::realloc(fm->entry_sizes, (uint64_t)new_cap * sizeof(uint64_t));
if (!sizes) return false; if (!sizes) return false;
fm->entry_sizes = sizes; fm->entry_sizes = sizes;
int64_t* mtimes = (int64_t*)montauk::realloc(fm->entry_mtimes, (uint64_t)new_cap * sizeof(int64_t));
if (!mtimes) return false;
fm->entry_mtimes = mtimes;
bool* isdir = (bool*)montauk::realloc(fm->is_dir, (uint64_t)new_cap * sizeof(bool)); bool* isdir = (bool*)montauk::realloc(fm->is_dir, (uint64_t)new_cap * sizeof(bool));
if (!isdir) return false; if (!isdir) return false;
fm->is_dir = isdir; fm->is_dir = isdir;
@@ -81,6 +84,7 @@ void filemanager_free_arrays(FileManagerState* fm) {
montauk::mfree(fm->entry_names); fm->entry_names = nullptr; montauk::mfree(fm->entry_names); fm->entry_names = nullptr;
montauk::mfree(fm->entry_types); fm->entry_types = nullptr; montauk::mfree(fm->entry_types); fm->entry_types = nullptr;
montauk::mfree(fm->entry_sizes); fm->entry_sizes = nullptr; montauk::mfree(fm->entry_sizes); fm->entry_sizes = nullptr;
montauk::mfree(fm->entry_mtimes); fm->entry_mtimes = nullptr;
montauk::mfree(fm->is_dir); fm->is_dir = nullptr; montauk::mfree(fm->is_dir); fm->is_dir = nullptr;
montauk::mfree(fm->drive_indices); fm->drive_indices = nullptr; montauk::mfree(fm->drive_indices); fm->drive_indices = nullptr;
montauk::mfree(fm->drive_kinds); fm->drive_kinds = nullptr; montauk::mfree(fm->drive_kinds); fm->drive_kinds = nullptr;
@@ -125,6 +129,7 @@ static void filemanager_add_root_entry(FileManagerState* fm,
montauk::strncpy(fm->entry_names[idx], name, 63); montauk::strncpy(fm->entry_names[idx], name, 63);
fm->entry_types[idx] = type; fm->entry_types[idx] = type;
fm->entry_sizes[idx] = 0; fm->entry_sizes[idx] = 0;
fm->entry_mtimes[idx] = 0;
fm->is_dir[idx] = true; fm->is_dir[idx] = true;
fm->drive_indices[idx] = drive_index; fm->drive_indices[idx] = drive_index;
fm->drive_kinds[idx] = 0; fm->drive_kinds[idx] = 0;
@@ -143,6 +148,7 @@ static int filemanager_add_virtual_entry(FileManagerState* fm,
montauk::strncpy(fm->entry_names[idx], name, 63); montauk::strncpy(fm->entry_names[idx], name, 63);
fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP; fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP;
fm->entry_sizes[idx] = 0; fm->entry_sizes[idx] = 0;
fm->entry_mtimes[idx] = 0;
fm->is_dir[idx] = false; fm->is_dir[idx] = false;
fm->drive_indices[idx] = -1; fm->drive_indices[idx] = -1;
filemanager_reset_virtual_entry(&fm->virtual_entries[idx]); filemanager_reset_virtual_entry(&fm->virtual_entries[idx]);
@@ -322,9 +328,10 @@ void filemanager_read_dir(FileManagerState* fm) {
fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]); fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]);
// Get file size (64-bit: no truncation for files >= 2 GiB) // Fetch size + modified time in one stat call (works for files and
// directories). Filesystems without stat support leave both 0.
fm->entry_sizes[i] = 0; fm->entry_sizes[i] = 0;
if (!fm->is_dir[i]) { fm->entry_mtimes[i] = 0;
char fullpath[512]; char fullpath[512];
montauk::strcpy(fullpath, fm->current_path); montauk::strcpy(fullpath, fm->current_path);
int plen = montauk::slen(fullpath); int plen = montauk::slen(fullpath);
@@ -332,11 +339,11 @@ void filemanager_read_dir(FileManagerState* fm) {
str_append(fullpath, "/", 512); str_append(fullpath, "/", 512);
} }
str_append(fullpath, fm->entry_names[i], 512); str_append(fullpath, fm->entry_names[i], 512);
int fd = montauk::open(fullpath);
if (fd >= 0) { montauk::abi::FileStat stt;
fm->entry_sizes[i] = montauk::getsize(fd); if (montauk::stat(fullpath, &stt) == 0) {
montauk::close(fd); if (!fm->is_dir[i]) fm->entry_sizes[i] = stt.size;
} fm->entry_mtimes[i] = stt.mtime;
} }
} }
@@ -349,6 +356,7 @@ void filemanager_read_dir(FileManagerState* fm) {
char tmp_name[128]; char tmp_name[128];
int tmp_type = fm->entry_types[i]; int tmp_type = fm->entry_types[i];
uint64_t tmp_size = fm->entry_sizes[i]; uint64_t tmp_size = fm->entry_sizes[i];
int64_t tmp_mtime = fm->entry_mtimes[i];
bool tmp_isdir = fm->is_dir[i]; bool tmp_isdir = fm->is_dir[i];
montauk::strcpy(tmp_name, fm->entry_names[i]); montauk::strcpy(tmp_name, fm->entry_names[i]);
@@ -367,12 +375,14 @@ void filemanager_read_dir(FileManagerState* fm) {
montauk::strcpy(fm->entry_names[j + 1], fm->entry_names[j]); montauk::strcpy(fm->entry_names[j + 1], fm->entry_names[j]);
fm->entry_types[j + 1] = fm->entry_types[j]; fm->entry_types[j + 1] = fm->entry_types[j];
fm->entry_sizes[j + 1] = fm->entry_sizes[j]; fm->entry_sizes[j + 1] = fm->entry_sizes[j];
fm->entry_mtimes[j + 1] = fm->entry_mtimes[j];
fm->is_dir[j + 1] = fm->is_dir[j]; fm->is_dir[j + 1] = fm->is_dir[j];
j--; j--;
} }
montauk::strcpy(fm->entry_names[j + 1], tmp_name); montauk::strcpy(fm->entry_names[j + 1], tmp_name);
fm->entry_types[j + 1] = tmp_type; fm->entry_types[j + 1] = tmp_type;
fm->entry_sizes[j + 1] = tmp_size; fm->entry_sizes[j + 1] = tmp_size;
fm->entry_mtimes[j + 1] = tmp_mtime;
fm->is_dir[j + 1] = tmp_isdir; fm->is_dir[j + 1] = tmp_isdir;
} }
@@ -21,10 +21,12 @@ struct FilePropertiesDialogState {
char path[512]; char path[512];
char size[32]; char size[32];
char contains[32]; char contains[32];
char modified[24];
bool has_path; bool has_path;
bool has_size; bool has_size;
bool has_contains; bool has_contains;
bool has_modified;
bool owns_icon; bool owns_icon;
bool fallback_is_dir; bool fallback_is_dir;
SvgIcon icon; SvgIcon icon;
@@ -293,6 +295,14 @@ static void props_populate(FilePropertiesDialogState* st,
st->has_size = true; st->has_size = true;
} }
// Modified time (files and directories), when the filesystem recorded one.
if (!filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) &&
fm->entry_mtimes[idx] > 0) {
format_datetime(st->modified, sizeof(st->modified),
fm->entry_mtimes[idx], montauk::gettz());
st->has_modified = true;
}
if (props_entry_is_directory_like(fm, idx) && st->has_path) { if (props_entry_is_directory_like(fm, idx) && st->has_path) {
const char* names[256]; const char* names[256];
int count = montauk::readdir(st->path, names, 256); int count = montauk::readdir(st->path, names, 256);
@@ -368,6 +378,8 @@ static void props_on_draw(Window* win, Framebuffer& fb) {
y = props_draw_row(c, theme, pad, y, 82, "Size", st->size); y = props_draw_row(c, theme, pad, y, 82, "Size", st->size);
if (st->has_contains) if (st->has_contains)
y = props_draw_row(c, theme, pad, y, 82, "Contains", st->contains); y = props_draw_row(c, theme, pad, y, 82, "Contains", st->contains);
if (st->has_modified)
y = props_draw_row(c, theme, pad, y, 82, "Modified", st->modified);
Rect close = props_close_button_rect(c.w, c.h); Rect close = props_close_button_rect(c.w, c.h);
mtk::draw_button(c, close, "Close", mtk::BUTTON_PRIMARY, mtk::draw_button(c, close, "Close", mtk::BUTTON_PRIMARY,
@@ -932,7 +944,7 @@ void filemanager_show_properties(FileManagerState* fm, int target_idx) {
props_populate(st, fm, target_idx); props_populate(st, fm, target_idx);
int w = 420; int w = 420;
int h = 300; int h = 330;
int wx = 0; int wx = 0;
int wy = 0; int wy = 0;
props_center_dialog(ds, fm, w, h, &wx, &wy); props_center_dialog(ds, fm, w, h, &wx, &wy);