feat: add common dialogs, i.e. open/save dialog

This commit is contained in:
2026-04-01 23:45:28 +02:00
parent b140825121
commit 19090d3ea5
15 changed files with 2164 additions and 79 deletions
+9 -4
View File
@@ -59,7 +59,7 @@ BINDIR := bin
PROGRAMS := $(notdir $(wildcard src/*)) PROGRAMS := $(notdir $(wildcard src/*))
# Programs with custom Makefiles (built separately). # Programs with custom Makefiles (built separately).
CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs
SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS))
# Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/. # Build targets: system programs go to bin/os/, apps go to bin/apps/<name>/.
@@ -81,9 +81,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt
# Common shared assets (wallpapers, etc.) # Common shared assets (wallpapers, etc.)
COMMONKEEP := $(BINDIR)/common/.keep COMMONKEEP := $(BINDIR)/common/.keep
.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl icons fonts bearssl libc tls libjpeg libjpegwrite install-apps .PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs icons fonts bearssl libc tls libjpeg libjpegwrite install-apps
all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP) all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator doom rpgdemo paint tcc lua screenshot texteditor mandelbrot printers printd printctl dialogs login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP)
# Build BearSSL static library (cross-compiled for freestanding x86_64). # Build BearSSL static library (cross-compiled for freestanding x86_64).
BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc)
@@ -261,8 +261,12 @@ printd: bearssl libc tls libjpegwrite
printctl: bearssl libc tls printctl: bearssl libc tls
$(MAKE) -C src/printctl $(MAKE) -C src/printctl
# Build shared dialogs app (depends on libc, bearssl, and tls through print helpers).
dialogs: bearssl libc tls
$(MAKE) -C src/dialogs
# Install app bundles (manifests, icons, data files) into bin/apps/<name>/. # Install app bundles (manifests, icons, data files) into bin/apps/<name>/.
install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator screenshot texteditor mandelbrot printers install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music video bluetooth terminal klog procmgr calculator screenshot texteditor mandelbrot printers dialogs
../scripts/install_apps.sh ../scripts/install_apps.sh
# Copy man pages into bin/man/ so mkramdisk.sh picks them up. # Copy man pages into bin/man/ so mkramdisk.sh picks them up.
@@ -329,3 +333,4 @@ clean:
$(MAKE) -C src/tcc clean $(MAKE) -C src/tcc clean
$(MAKE) -C src/printd clean $(MAKE) -C src/printd clean
$(MAKE) -C src/printctl clean $(MAKE) -C src/printctl clean
$(MAKE) -C src/dialogs clean
+324
View File
@@ -0,0 +1,324 @@
/*
* dialogs.hpp
* Shared modal dialog protocol and client helpers
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <stdio.h>
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <Api/Syscall.hpp>
namespace gui::dialogs {
inline constexpr const char* APP_BINARY = "0:/apps/dialogs/dialogs.elf";
inline constexpr const char* STORAGE_DIR = "0:/tmp/dialogs";
enum RequestKind : uint8_t {
REQUEST_KIND_FILE = 1,
REQUEST_KIND_PRINT = 2,
};
enum FileDialogMode : uint8_t {
FILE_DIALOG_OPEN = 1,
FILE_DIALOG_SAVE = 2,
};
struct Request {
uint8_t kind;
uint8_t mode;
char title[96];
char initial_path[256];
char suggested_name[128];
char source_path[256];
char job_name[128];
char result_path[256];
};
struct Result {
char status[16];
char path[256];
char job_id[64];
char message[160];
};
inline void safe_copy(char* dst, int dst_len, const char* src) {
if (!dst || dst_len <= 0) return;
if (!src) src = "";
montauk::strncpy(dst, src, dst_len - 1);
dst[dst_len - 1] = '\0';
}
inline void reset_request(Request* req) {
if (!req) return;
montauk::memset(req, 0, sizeof(Request));
}
inline void reset_result(Result* res) {
if (!res) return;
montauk::memset(res, 0, sizeof(Result));
}
inline void ensure_storage_dirs() {
montauk::fmkdir("0:/tmp");
montauk::fmkdir(STORAGE_DIR);
}
inline bool write_text_file(const char* path, const char* text) {
if (!path || !path[0] || !text) return false;
int fd = montauk::fcreate(path);
if (fd < 0) return false;
int len = montauk::slen(text);
bool ok = montauk::fwrite(fd, (const uint8_t*)text, 0, (uint64_t)len) >= 0;
montauk::close(fd);
return ok;
}
inline bool read_text_file(const char* path, char* out, int out_len) {
if (!out || out_len <= 0) return false;
out[0] = '\0';
if (!path || !path[0]) return false;
int fd = montauk::open(path);
if (fd < 0) return false;
uint64_t size = montauk::getsize(fd);
if ((int)size >= out_len) size = (uint64_t)(out_len - 1);
int got = montauk::read(fd, (uint8_t*)out, 0, size);
montauk::close(fd);
if (got < 0) return false;
out[got] = '\0';
return true;
}
inline bool line_value(const char* text, const char* key, char* out, int out_len) {
if (!text || !key || !out || out_len <= 0) return false;
int key_len = montauk::slen(key);
const char* p = text;
while (*p) {
const char* line = p;
while (*p && *p != '\n') p++;
int line_len = (int)(p - line);
if (*p == '\n') p++;
if (line_len <= key_len || line[key_len] != '=') continue;
bool match = true;
for (int i = 0; i < key_len; i++) {
if (line[i] != key[i]) {
match = false;
break;
}
}
if (!match) continue;
const char* value = line + key_len + 1;
int value_len = line_len - key_len - 1;
while (value_len > 0 && (value[value_len - 1] == '\r' || value[value_len - 1] == '\n'))
value_len--;
if (value_len >= out_len) value_len = out_len - 1;
montauk::memcpy(out, value, (uint64_t)value_len);
out[value_len] = '\0';
return true;
}
return false;
}
inline uint32_t parse_u32(const char* text) {
if (!text) return 0;
uint32_t value = 0;
for (int i = 0; text[i] >= '0' && text[i] <= '9'; i++) {
value = value * 10u + (uint32_t)(text[i] - '0');
}
return value;
}
inline bool write_request_file(const char* path, const Request* req) {
if (!path || !req) return false;
char text[1536];
int n = snprintf(text, sizeof(text),
"kind=%u\n"
"mode=%u\n"
"title=%s\n"
"initial_path=%s\n"
"suggested_name=%s\n"
"source_path=%s\n"
"job_name=%s\n"
"result_path=%s\n",
(unsigned)req->kind,
(unsigned)req->mode,
req->title,
req->initial_path,
req->suggested_name,
req->source_path,
req->job_name,
req->result_path);
if (n <= 0 || n >= (int)sizeof(text)) return false;
return write_text_file(path, text);
}
inline bool read_request_file(const char* path, Request* req) {
if (!path || !req) return false;
char text[1536];
if (!read_text_file(path, text, sizeof(text))) return false;
reset_request(req);
char value[256];
if (line_value(text, "kind", value, sizeof(value))) req->kind = (uint8_t)parse_u32(value);
if (line_value(text, "mode", value, sizeof(value))) req->mode = (uint8_t)parse_u32(value);
line_value(text, "title", req->title, sizeof(req->title));
line_value(text, "initial_path", req->initial_path, sizeof(req->initial_path));
line_value(text, "suggested_name", req->suggested_name, sizeof(req->suggested_name));
line_value(text, "source_path", req->source_path, sizeof(req->source_path));
line_value(text, "job_name", req->job_name, sizeof(req->job_name));
line_value(text, "result_path", req->result_path, sizeof(req->result_path));
return req->kind != 0 && req->result_path[0] != '\0';
}
inline bool write_result_file(const char* path, const Result* res) {
if (!path || !res) return false;
char text[1024];
int n = snprintf(text, sizeof(text),
"status=%s\n"
"path=%s\n"
"job_id=%s\n"
"message=%s\n",
res->status,
res->path,
res->job_id,
res->message);
if (n <= 0 || n >= (int)sizeof(text)) return false;
return write_text_file(path, text);
}
inline bool read_result_file(const char* path, Result* res) {
if (!path || !res) return false;
char text[1024];
if (!read_text_file(path, text, sizeof(text))) return false;
reset_result(res);
line_value(text, "status", res->status, sizeof(res->status));
line_value(text, "path", res->path, sizeof(res->path));
line_value(text, "job_id", res->job_id, sizeof(res->job_id));
line_value(text, "message", res->message, sizeof(res->message));
return res->status[0] != '\0';
}
inline void make_unique_paths(char* request_path, int request_path_len,
char* result_path, int result_path_len) {
static uint32_t seq = 0;
ensure_storage_dirs();
uint64_t now = montauk::get_milliseconds();
int pid = montauk::getpid();
uint32_t cur = ++seq;
snprintf(request_path, request_path_len, "%s/%d-%llu-%u.req",
STORAGE_DIR, pid, (unsigned long long)now, (unsigned)cur);
snprintf(result_path, result_path_len, "%s/%d-%llu-%u.res",
STORAGE_DIR, pid, (unsigned long long)now, (unsigned)cur);
}
inline bool run_request(const Request* req, Result* out_res, char* out_message, int out_message_len) {
if (out_message && out_message_len > 0) out_message[0] = '\0';
if (!req) {
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "invalid dialog request");
return false;
}
char request_path[256];
char result_path[256];
make_unique_paths(request_path, sizeof(request_path), result_path, sizeof(result_path));
Request req_copy = *req;
safe_copy(req_copy.result_path, sizeof(req_copy.result_path), result_path);
if (!write_request_file(request_path, &req_copy)) {
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "failed to write dialog request");
return false;
}
int pid = montauk::spawn(APP_BINARY, request_path);
if (pid < 0) {
montauk::fdelete(request_path);
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "failed to start dialog");
return false;
}
int proc = montauk::proc_open(pid);
if (proc >= 0) {
montauk::wait_handle(proc, Montauk::IPC_SIGNAL_EXITED);
montauk::close(proc);
} else {
montauk::waitpid(pid);
}
Result local_res = {};
bool ok = read_result_file(result_path, &local_res);
montauk::fdelete(request_path);
montauk::fdelete(result_path);
if (!ok) {
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, "dialog did not return a result");
return false;
}
if (out_res) *out_res = local_res;
if (out_message && out_message_len > 0) safe_copy(out_message, out_message_len, local_res.message);
return montauk::streq(local_res.status, "ok");
}
inline bool open_file(const char* title,
const char* initial_path,
char* out_path, int out_path_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_FILE;
req.mode = FILE_DIALOG_OPEN;
safe_copy(req.title, sizeof(req.title), title ? title : "Open");
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
Result res = {};
bool ok = run_request(&req, &res, out_message, out_message_len);
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
return ok;
}
inline bool save_file(const char* title,
const char* initial_path,
const char* suggested_name,
char* out_path, int out_path_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_FILE;
req.mode = FILE_DIALOG_SAVE;
safe_copy(req.title, sizeof(req.title), title ? title : "Save");
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
safe_copy(req.suggested_name, sizeof(req.suggested_name), suggested_name);
Result res = {};
bool ok = run_request(&req, &res, out_message, out_message_len);
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
return ok;
}
inline bool print_file(const char* title,
const char* source_path,
const char* job_name,
char* out_job_id, int out_job_id_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_PRINT;
safe_copy(req.title, sizeof(req.title), title ? title : "Print");
safe_copy(req.source_path, sizeof(req.source_path), source_path);
safe_copy(req.job_name, sizeof(req.job_name), job_name);
Result res = {};
bool ok = run_request(&req, &res, out_message, out_message_len);
if (ok && out_job_id && out_job_id_len > 0) safe_copy(out_job_id, out_job_id_len, res.job_id);
return ok;
}
} // namespace gui::dialogs
+79
View File
@@ -0,0 +1,79 @@
# Makefile for shared dialogs (standalone Window Server app) on MontaukOS
# Copyright (c) 2026 Daniel Hammer
MAKEFLAGS += -rR
.SUFFIXES:
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
CXX := $(TOOLCHAIN_PREFIX)g++
else
CXX := g++
endif
PROG_INC := ../../include
LINK_LD := ../../link.ld
BINDIR := ../../bin
OBJDIR := obj
LIBDIR := ../../lib
CXXFLAGS := \
-std=gnu++20 \
-g -O2 -pipe \
-Wall \
-Wextra \
-Wno-unused-parameter \
-Wno-unused-function \
-nostdinc \
-ffreestanding \
-fno-stack-protector \
-fno-stack-check \
-fno-PIC \
-fno-rtti \
-fno-exceptions \
-ffunction-sections \
-fdata-sections \
-m64 \
-march=x86-64 \
-msse \
-msse2 \
-mno-red-zone \
-mcmodel=small \
-MMD -MP \
-I $(PROG_INC) \
-isystem $(PROG_INC)/libc \
-I ../../lib/bearssl/inc \
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
LDFLAGS := \
-nostdlib \
-static \
-Wl,--build-id=none \
-Wl,--gc-sections \
-Wl,-m,elf_x86_64 \
-z max-page-size=0x1000 \
-T $(LINK_LD)
SRCS := main.cpp stb_truetype_impl.cpp font_data.cpp
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
TARGET := $(BINDIR)/apps/dialogs/dialogs.elf
LIBS := $(LIBDIR)/tls/libtls.a $(LIBDIR)/bearssl/libbearssl.a $(LIBDIR)/libc/liblibc.a
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) $(LINK_LD) Makefile $(LIBS)
mkdir -p $(BINDIR)/apps/dialogs
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
$(OBJDIR)/%.o: %.cpp Makefile
mkdir -p $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
-include $(OBJS:.o=.d)
clean:
rm -rf $(OBJDIR) $(TARGET)
+1
View File
@@ -0,0 +1 @@
#include "../desktop/font_data.cpp"
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
[app]
name = "Dialogs"
binary = "dialogs.elf"
icon = "system-file-manager.svg"
[menu]
category = "System"
visible = false
@@ -0,0 +1,32 @@
/*
* stb_truetype_impl.cpp
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
* Copyright (c) 2026 Daniel Hammer
*/
#include <cstdint>
#include <cstddef>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/stb_math.h>
#define STBTT_ifloor(x) ((int) stb_floor(x))
#define STBTT_iceil(x) ((int) stb_ceil(x))
#define STBTT_sqrt(x) stb_sqrt(x)
#define STBTT_pow(x,y) stb_pow(x,y)
#define STBTT_fmod(x,y) stb_fmod(x,y)
#define STBTT_cos(x) stb_cos(x)
#define STBTT_acos(x) stb_acos(x)
#define STBTT_fabs(x) stb_fabs(x)
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
#define STBTT_strlen(x) montauk::slen(x)
#define STBTT_assert(x) ((void)(x))
#define STB_TRUETYPE_IMPLEMENTATION
#include <gui/stb_truetype.h>
+63 -18
View File
@@ -5,6 +5,7 @@
*/ */
#include "pdfviewer.h" #include "pdfviewer.h"
#include <gui/dialogs.hpp>
#include <gui/standalone.hpp> #include <gui/standalone.hpp>
// ============================================================================ // ============================================================================
@@ -37,14 +38,15 @@ char g_status_msg[128] = {};
// Toolbar hit-testing constants // Toolbar hit-testing constants
// ============================================================================ // ============================================================================
// Toolbar buttons: Open | < > | - + // Toolbar buttons: Open | Print | < > | - +
static constexpr int TB_OPEN_X0 = 4, TB_OPEN_X1 = 40; static constexpr int TB_OPEN_X0 = 4, TB_OPEN_X1 = 40;
// separator at 48 static constexpr int TB_PRINT_X0 = 44, TB_PRINT_X1 = 80;
static constexpr int TB_PREV_X0 = 56, TB_PREV_X1 = 80; // separator at 88
static constexpr int TB_NEXT_X0 = 84, TB_NEXT_X1 = 108; static constexpr int TB_PREV_X0 = 96, TB_PREV_X1 = 120;
// separator at 116 static constexpr int TB_NEXT_X0 = 124, TB_NEXT_X1 = 148;
static constexpr int TB_ZOUT_X0 = 124, TB_ZOUT_X1 = 148; // separator at 156
static constexpr int TB_ZIN_X0 = 152, TB_ZIN_X1 = 176; static constexpr int TB_ZOUT_X0 = 164, TB_ZOUT_X1 = 188;
static constexpr int TB_ZIN_X0 = 192, TB_ZIN_X1 = 216;
// ============================================================================ // ============================================================================
// Helpers // Helpers
@@ -66,6 +68,15 @@ static void clamp_scroll() {
if (g_scroll_y > ms) g_scroll_y = ms; if (g_scroll_y > ms) g_scroll_y = ms;
} }
static const char* file_basename(const char* path) {
if (!path || !path[0]) return "";
const char* name = path;
for (int i = 0; path[i]; i++) {
if (path[i] == '/') name = path + i + 1;
}
return name;
}
static void go_to_page(int page) { static void go_to_page(int page) {
if (page < 0) page = 0; if (page < 0) page = 0;
if (page >= g_doc.page_count) page = g_doc.page_count - 1; if (page >= g_doc.page_count) page = g_doc.page_count - 1;
@@ -99,6 +110,40 @@ static void open_file(const char* path) {
} }
} }
static void open_inline_pathbar(const char* initial_text) {
g_pathbar_open = true;
if (!initial_text) initial_text = "";
str_cpy(g_pathbar_text, initial_text, sizeof(g_pathbar_text));
g_pathbar_len = str_len(g_pathbar_text);
g_pathbar_cursor = g_pathbar_len;
}
static void open_file_dialog() {
char path[256] = {};
if (dialogs::open_file("Open PDF", g_filepath, path, sizeof(path), g_status_msg, sizeof(g_status_msg))) {
open_file(path);
} else if (g_status_msg[0]) {
open_inline_pathbar(g_filepath);
}
}
static void print_file_dialog() {
if (!g_filepath[0]) {
str_cpy(g_status_msg, "Open a PDF before printing", sizeof(g_status_msg));
return;
}
char job_id[64] = {};
char msg[128] = {};
if (dialogs::print_file("Print PDF", g_filepath, file_basename(g_filepath),
job_id, sizeof(job_id), msg, sizeof(msg))) {
if (msg[0]) str_cpy(g_status_msg, msg, sizeof(g_status_msg));
else str_cpy(g_status_msg, "Print job queued", sizeof(g_status_msg));
} else if (msg[0]) {
str_cpy(g_status_msg, msg, sizeof(g_status_msg));
}
}
// ============================================================================ // ============================================================================
// Entry point // Entry point
// ============================================================================ // ============================================================================
@@ -126,9 +171,7 @@ extern "C" void _start() {
// Build window title // Build window title
char title[64] = "PDF Viewer"; char title[64] = "PDF Viewer";
if (g_filepath[0]) { if (g_filepath[0]) {
const char* fname = g_filepath; const char* fname = file_basename(g_filepath);
for (int i = 0; g_filepath[i]; i++)
if (g_filepath[i] == '/') fname = g_filepath + i + 1;
snprintf(title, 64, "%s - PDF Viewer", fname); snprintf(title, 64, "%s - PDF Viewer", fname);
} }
@@ -211,10 +254,12 @@ extern "C" void _start() {
} }
// Ctrl+O: open file // Ctrl+O: open file
else if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O' || key.ascii == 15)) { else if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O' || key.ascii == 15)) {
g_pathbar_open = true; open_file_dialog();
g_pathbar_text[0] = '\0'; redraw = true;
g_pathbar_len = 0; }
g_pathbar_cursor = 0; // Ctrl+P: print file
else if (key.ctrl && (key.ascii == 'p' || key.ascii == 'P' || key.ascii == 16)) {
print_file_dialog();
redraw = true; redraw = true;
} }
// Page Down / Right arrow: next page // Page Down / Right arrow: next page
@@ -288,10 +333,10 @@ extern "C" void _start() {
if (clicked && my < TOOLBAR_H && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_SIZE) { if (clicked && my < TOOLBAR_H && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_SIZE) {
if (mx >= TB_OPEN_X0 && mx < TB_OPEN_X1) { if (mx >= TB_OPEN_X0 && mx < TB_OPEN_X1) {
g_pathbar_open = true; open_file_dialog();
g_pathbar_text[0] = '\0'; redraw = true;
g_pathbar_len = 0; } else if (mx >= TB_PRINT_X0 && mx < TB_PRINT_X1) {
g_pathbar_cursor = 0; print_file_dialog();
redraw = true; redraw = true;
} else if (mx >= TB_PREV_X0 && mx < TB_PREV_X1 && g_doc.valid) { } else if (mx >= TB_PREV_X0 && mx < TB_PREV_X1 && g_doc.valid) {
go_to_page(g_current_page - 1); go_to_page(g_current_page - 1);
+2 -1
View File
@@ -180,8 +180,9 @@ void render(uint32_t* pixels) {
bx += 8; bx += 8;
}; };
// Open // Open / Print
tb_btn(36, false, "Open"); tb_btn(36, false, "Open");
tb_btn(36, false, "Print");
tb_sep(); tb_sep();
// Navigation // Navigation
+40 -28
View File
@@ -5,6 +5,7 @@
*/ */
#include "spreadsheet.h" #include "spreadsheet.h"
#include <gui/dialogs.hpp>
#include <gui/standalone.hpp> #include <gui/standalone.hpp>
// ============================================================================ // ============================================================================
@@ -131,6 +132,41 @@ bool hit_fill_handle(int mx, int my) {
my >= fhy - 3 && my <= fhy + FILL_HANDLE_SIZE + 3; my >= fhy - 3 && my <= fhy + FILL_HANDLE_SIZE + 3;
} }
static void open_inline_pathbar(bool save_mode, const char* initial_text) {
g_pathbar_open = true;
g_pathbar_save = save_mode;
if (!initial_text) initial_text = "";
str_cpy(g_pathbar_text, initial_text, sizeof(g_pathbar_text));
g_pathbar_len = str_len(g_pathbar_text);
g_pathbar_cursor = g_pathbar_len;
}
static void open_file_dialog() {
char path[256] = {};
char msg[128] = {};
if (dialogs::open_file("Open Spreadsheet", g_filepath, path, sizeof(path), msg, sizeof(msg))) {
load_file(path);
} else if (msg[0]) {
open_inline_pathbar(false, g_filepath);
}
}
static void save_file_dialog() {
if (g_filepath[0]) {
save_file();
return;
}
char path[256] = {};
char msg[128] = {};
if (dialogs::save_file("Save Spreadsheet", "", "untitled.mss", path, sizeof(path), msg, sizeof(msg))) {
str_cpy(g_filepath, path, 256);
save_file();
} else if (msg[0]) {
open_inline_pathbar(true, "untitled.mss");
}
}
bool handle_toolbar_click(int mx, int my) { bool handle_toolbar_click(int mx, int my) {
if (my >= TOOLBAR_H || my < TB_BTN_Y || my >= TB_BTN_Y + TB_BTN_SIZE) return false; if (my >= TOOLBAR_H || my < TB_BTN_Y || my >= TB_BTN_Y + TB_BTN_SIZE) return false;
@@ -140,22 +176,10 @@ bool handle_toolbar_click(int mx, int my) {
if (mx >= TB_OPEN_X0 && mx < TB_OPEN_X1) { if (mx >= TB_OPEN_X0 && mx < TB_OPEN_X1) {
if (g_editing) commit_edit(); if (g_editing) commit_edit();
g_pathbar_open = true; open_file_dialog();
g_pathbar_save = false;
g_pathbar_text[0] = '\0';
g_pathbar_len = 0;
g_pathbar_cursor = 0;
} else if (mx >= TB_SAVE_X0 && mx < TB_SAVE_X1) { } else if (mx >= TB_SAVE_X0 && mx < TB_SAVE_X1) {
if (g_editing) commit_edit(); if (g_editing) commit_edit();
if (g_filepath[0]) { save_file_dialog();
save_file();
} else {
g_pathbar_open = true;
g_pathbar_save = true;
str_cpy(g_pathbar_text, "0:/", 256);
g_pathbar_len = str_len(g_pathbar_text);
g_pathbar_cursor = g_pathbar_len;
}
} else if (mx >= TB_CUT_X0 && mx < TB_CUT_X1) { } else if (mx >= TB_CUT_X0 && mx < TB_CUT_X1) {
if (!g_editing) cut_selection(); if (!g_editing) cut_selection();
} else if (mx >= TB_COPY_X0 && mx < TB_COPY_X1) { } else if (mx >= TB_COPY_X0 && mx < TB_COPY_X1) {
@@ -556,24 +580,12 @@ extern "C" void _start() {
// Ctrl+S: save (or Save As if no path) // Ctrl+S: save (or Save As if no path)
else if (key.ctrl && (key.ascii == 's' || key.ascii == 'S' || key.ascii == 19)) { else if (key.ctrl && (key.ascii == 's' || key.ascii == 'S' || key.ascii == 19)) {
if (g_editing) commit_edit(); if (g_editing) commit_edit();
if (g_filepath[0]) { save_file_dialog();
save_file();
} else {
g_pathbar_open = true;
g_pathbar_save = true;
str_cpy(g_pathbar_text, "0:/", 256);
g_pathbar_len = str_len(g_pathbar_text);
g_pathbar_cursor = g_pathbar_len;
}
redraw = true; redraw = true;
} }
// Ctrl+O: open // Ctrl+O: open
else if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O' || key.ascii == 15) && !g_editing) { else if (key.ctrl && (key.ascii == 'o' || key.ascii == 'O' || key.ascii == 15) && !g_editing) {
g_pathbar_open = true; open_file_dialog();
g_pathbar_save = false;
g_pathbar_text[0] = '\0';
g_pathbar_len = 0;
g_pathbar_cursor = 0;
redraw = true; redraw = true;
} }
// Printable character: start editing or insert // Printable character: start editing or insert
+106 -18
View File
@@ -12,6 +12,7 @@
#include <gui/gui.hpp> #include <gui/gui.hpp>
#include <gui/truetype.hpp> #include <gui/truetype.hpp>
#include <gui/svg.hpp> #include <gui/svg.hpp>
#include <gui/dialogs.hpp>
#include <gui/stb_math.h> #include <gui/stb_math.h>
extern "C" { extern "C" {
@@ -87,6 +88,7 @@ int g_scroll_y = 0; // first visible line
int g_scroll_x = 0; // horizontal scroll in pixels int g_scroll_x = 0; // horizontal scroll in pixels
bool g_modified = false; bool g_modified = false;
bool g_cursor_moved = true; bool g_cursor_moved = true;
char g_status_msg[128] = {};
// File // File
char g_filepath[256] = {}; char g_filepath[256] = {};
@@ -434,24 +436,76 @@ static void ensure_cursor_visible(int visible_lines, int text_area_w) {
// File I/O // File I/O
// ============================================================================ // ============================================================================
static void load_file(const char* path) { static void set_status(const char* msg) {
if (!msg) msg = "";
montauk::strncpy(g_status_msg, msg, sizeof(g_status_msg) - 1);
g_status_msg[sizeof(g_status_msg) - 1] = '\0';
}
static bool looks_binary(const char* data, int len) {
if (!data || len <= 0) return false;
int control_count = 0;
for (int i = 0; i < len; i++) {
unsigned char ch = (unsigned char)data[i];
if (ch == 0) return true;
if (ch < 32 && ch != '\n' && ch != '\r' && ch != '\t' && ch != '\f')
control_count++;
}
return control_count * 20 > len;
}
static bool load_file(const char* path) {
int fd = montauk::open(path); int fd = montauk::open(path);
if (fd < 0) return; if (fd < 0) {
set_status("Failed to open file");
return false;
}
uint64_t size = montauk::getsize(fd); uint64_t size = montauk::getsize(fd);
if (size > (uint64_t)MAX_CAP) size = MAX_CAP; if (size > (uint64_t)MAX_CAP) {
montauk::close(fd);
set_status("File is too large for Text Editor");
return false;
}
if ((int)size >= g_buf_cap) { int read_len = (int)size;
int new_cap = (int)size + 1024; int temp_cap = read_len + 1;
if (temp_cap < 1024) temp_cap = 1024;
char* temp = (char*)montauk::malloc(temp_cap);
if (!temp) {
montauk::close(fd);
set_status("Out of memory while opening file");
return false;
}
int got = montauk::read(fd, (uint8_t*)temp, 0, size);
montauk::close(fd);
if (got < 0) {
montauk::mfree(temp);
set_status("Failed to read file");
return false;
}
temp[got] = '\0';
if (looks_binary(temp, got)) {
montauk::mfree(temp);
set_status("Binary files cannot be opened in Text Editor");
return false;
}
if (got >= g_buf_cap) {
int new_cap = got + 1024;
if (new_cap > MAX_CAP) new_cap = MAX_CAP; if (new_cap > MAX_CAP) new_cap = MAX_CAP;
g_buffer = (char*)montauk::realloc(g_buffer, new_cap); g_buffer = (char*)montauk::realloc(g_buffer, new_cap);
g_buf_cap = new_cap; g_buf_cap = new_cap;
} }
montauk::read(fd, (uint8_t*)g_buffer, 0, size); montauk::memcpy(g_buffer, temp, (uint64_t)(got + 1));
montauk::close(fd); montauk::mfree(temp);
g_buf_len = (int)size; g_buf_len = got;
g_cursor_pos = 0; g_cursor_pos = 0;
g_scroll_y = 0; g_scroll_y = 0;
g_scroll_x = 0; g_scroll_x = 0;
@@ -468,21 +522,28 @@ static void load_file(const char* path) {
montauk::strncpy(g_filename, path, 63); montauk::strncpy(g_filename, path, 63);
g_syntax_language = syn_detect_language(g_filepath); g_syntax_language = syn_detect_language(g_filepath);
clear_selection();
set_status("");
recompute_lines(); recompute_lines();
update_cursor_pos(); update_cursor_pos();
return true;
} }
static void save_file() { static void save_file() {
if (g_filepath[0] == '\0') return; if (g_filepath[0] == '\0') return;
int fd = montauk::fcreate(g_filepath); int fd = montauk::fcreate(g_filepath);
if (fd < 0) return; if (fd < 0) {
set_status("Failed to save file");
return;
}
montauk::fwrite(fd, (const uint8_t*)g_buffer, 0, g_buf_len); montauk::fwrite(fd, (const uint8_t*)g_buffer, 0, g_buf_len);
montauk::close(fd); montauk::close(fd);
g_modified = false; g_modified = false;
set_status("");
} }
// ============================================================================ // ============================================================================
@@ -734,10 +795,16 @@ static void render(uint32_t* pixels) {
// Left: filename // Left: filename
char status_left[128]; char status_left[128];
if (g_status_msg[0]) {
if (g_filename[0]) if (g_filename[0])
snprintf(status_left, 128, " %s%s", g_filename, g_modified ? " [modified]" : ""); snprintf(status_left, 128, " %s%s | %s", g_filename, g_modified ? " [modified]" : "", g_status_msg);
else else
snprintf(status_left, 128, " Untitled%s | %s", g_modified ? " [modified]" : "", g_status_msg);
} else if (g_filename[0]) {
snprintf(status_left, 128, " %s%s", g_filename, g_modified ? " [modified]" : "");
} else {
snprintf(status_left, 128, " Untitled%s", g_modified ? " [modified]" : ""); snprintf(status_left, 128, " Untitled%s", g_modified ? " [modified]" : "");
}
if (g_font) if (g_font)
g_font->draw_to_buffer(pixels, W, H, 4, sty, status_left, STATUS_TEXT, FONT_SIZE); g_font->draw_to_buffer(pixels, W, H, 4, sty, status_left, STATUS_TEXT, FONT_SIZE);
@@ -778,20 +845,41 @@ static void pathbar_confirm(int win_id) {
g_pathbar_open = false; g_pathbar_open = false;
} }
static void open_pathbar_open() { static void open_inline_pathbar(bool save_mode, const char* initial_text) {
g_pathbar_open = true; g_pathbar_open = true;
g_pathbar_save = false; g_pathbar_save = save_mode;
montauk::strncpy(g_pathbar_text, g_filepath, 255); if (!initial_text) initial_text = "";
montauk::strncpy(g_pathbar_text, initial_text, 255);
g_pathbar_len = montauk::slen(g_pathbar_text); g_pathbar_len = montauk::slen(g_pathbar_text);
g_pathbar_cursor = g_pathbar_len; g_pathbar_cursor = g_pathbar_len;
} }
static void open_pathbar_open() {
char path[256] = {};
char msg[128] = {};
if (dialogs::open_file("Open File", g_filepath, path, sizeof(path), msg, sizeof(msg))) {
load_file(path);
} else if (msg[0]) {
open_inline_pathbar(false, g_filepath);
}
}
static void open_pathbar_save() { static void open_pathbar_save() {
g_pathbar_open = true; char path[256] = {};
g_pathbar_save = true; const char* initial_path = g_filepath[0] ? g_filepath : "";
g_pathbar_text[0] = '\0'; const char* suggested_name = g_filename[0] ? g_filename : "untitled.txt";
g_pathbar_len = 0; char msg[128] = {};
g_pathbar_cursor = 0; if (dialogs::save_file("Save File", initial_path, suggested_name, path, sizeof(path), msg, sizeof(msg))) {
montauk::strncpy(g_filepath, path, 255);
const char* name = path;
for (int i = 0; path[i]; i++)
if (path[i] == '/') name = path + i + 1;
montauk::strncpy(g_filename, name, 63);
g_syntax_language = syn_detect_language(g_filepath);
save_file();
} else if (msg[0]) {
open_inline_pathbar(true, initial_path[0] ? initial_path : suggested_name);
}
} }
// ============================================================================ // ============================================================================
+19 -3
View File
@@ -5,6 +5,7 @@
*/ */
#include "wordprocessor.hpp" #include "wordprocessor.hpp"
#include <gui/dialogs.hpp>
static inline int wp_min_int(int a, int b) { static inline int wp_min_int(int a, int b) {
return a < b ? a : b; return a < b ? a : b;
@@ -1203,14 +1204,29 @@ void wp_set_filepath(WordProcessorState* wp, const char* path) {
montauk::strncpy(wp->filename, path, 63); montauk::strncpy(wp->filename, path, 63);
} }
void wp_open_save_pathbar(WordProcessorState* wp) { void wp_start_pathbar(WordProcessorState* wp, bool save_mode, const char* initial_text) {
if (!initial_text) initial_text = "";
wp->show_pathbar = true; wp->show_pathbar = true;
wp->pathbar_save_mode = true; wp->pathbar_save_mode = save_mode;
montauk::strncpy(wp->pathbar_text, wp->filepath, 255); montauk::strncpy(wp->pathbar_text, initial_text, 255);
wp->pathbar_text[255] = '\0';
wp->pathbar_len = montauk::slen(wp->pathbar_text); wp->pathbar_len = montauk::slen(wp->pathbar_text);
wp->pathbar_cursor = wp->pathbar_len; wp->pathbar_cursor = wp->pathbar_len;
} }
void wp_open_save_pathbar(WordProcessorState* wp) {
char path[256] = {};
const char* initial_path = wp->filepath[0] ? wp->filepath : "";
const char* suggested_name = wp->filename[0] ? wp->filename : "document.mwp";
char msg[160] = {};
if (dialogs::save_file("Save Document", initial_path, suggested_name, path, sizeof(path), msg, sizeof(msg))) {
wp_set_filepath(wp, path);
wp_save_file(wp);
} else if (msg[0]) {
wp_start_pathbar(wp, true, initial_path[0] ? initial_path : suggested_name);
}
}
static int wp_serialized_size(WordProcessorState* wp) { static int wp_serialized_size(WordProcessorState* wp) {
int size = 4 + 2 + 1 + 1 + 1 + 2 + 2; int size = 4 + 2 + 1 + 1 + 1 + 2 + 2;
for (int i = 0; i < wp->run_count; i++) for (int i = 0; i < wp->run_count; i++)
+7 -6
View File
@@ -5,14 +5,15 @@
*/ */
#include "wordprocessor.hpp" #include "wordprocessor.hpp"
#include <gui/dialogs.hpp>
static void wp_open_pathbar_for_open(WordProcessorState* wp) { static void wp_open_pathbar_for_open(WordProcessorState* wp) {
wp->show_pathbar = !wp->show_pathbar; char path[256] = {};
wp->pathbar_save_mode = false; char msg[160] = {};
if (wp->show_pathbar) { if (dialogs::open_file("Open Document", wp->filepath, path, sizeof(path), msg, sizeof(msg))) {
montauk::strncpy(wp->pathbar_text, wp->filepath, 255); wp_load_file(wp, path);
wp->pathbar_len = montauk::slen(wp->pathbar_text); } else if (msg[0]) {
wp->pathbar_cursor = wp->pathbar_len; wp_start_pathbar(wp, false, wp->filepath);
} }
} }
@@ -346,6 +346,7 @@ int wp_wrap_line_start(WordProcessorState* wp, int line_idx);
void wp_ensure_cursor_visible(WordProcessorState* wp, int view_h); void wp_ensure_cursor_visible(WordProcessorState* wp, int view_h);
void wp_set_filepath(WordProcessorState* wp, const char* path); void wp_set_filepath(WordProcessorState* wp, const char* path);
void wp_start_pathbar(WordProcessorState* wp, bool save_mode, const char* initial_text);
void wp_open_save_pathbar(WordProcessorState* wp); void wp_open_save_pathbar(WordProcessorState* wp);
void wp_save_file(WordProcessorState* wp); void wp_save_file(WordProcessorState* wp);
void wp_load_file(WordProcessorState* wp, const char* path); void wp_load_file(WordProcessorState* wp, const char* path);
+1
View File
@@ -35,6 +35,7 @@ APPS=(
"procmgr|apps/scalable/system-monitor.svg" "procmgr|apps/scalable/system-monitor.svg"
"calculator|apps/scalable/accessories-calculator.svg" "calculator|apps/scalable/accessories-calculator.svg"
"printers|devices/scalable/preferences-devices-printer.svg" "printers|devices/scalable/preferences-devices-printer.svg"
"dialogs|apps/scalable/system-file-manager.svg"
"rpgdemo|apps/scalable/utilities-terminal.svg" "rpgdemo|apps/scalable/utilities-terminal.svg"
"paint|apps/scalable/kolourpaint.svg" "paint|apps/scalable/kolourpaint.svg"
"screenshot|apps/scalable/gnome-screenshot.svg" "screenshot|apps/scalable/gnome-screenshot.svg"