wip: printing from word processor, word processor improvements

This commit is contained in:
2026-04-02 11:30:37 +02:00
parent 19090d3ea5
commit 516b53281e
13 changed files with 1379 additions and 126 deletions
+329 -53
View File
@@ -28,8 +28,8 @@ namespace {
constexpr int FILE_INIT_W = 720;
constexpr int FILE_INIT_H = 520;
constexpr int PRINT_INIT_W = 480;
constexpr int PRINT_INIT_H = 260;
constexpr int PRINT_INIT_W = 640;
constexpr int PRINT_INIT_H = 400;
constexpr int TOOLBAR_H = 32;
constexpr int PATHBAR_H = 32;
@@ -46,6 +46,7 @@ constexpr int GRID_PAD = 4;
constexpr int MAX_ENTRIES = 64;
constexpr int MAX_HISTORY = 16;
constexpr int MAX_DRIVES = 16;
constexpr int MAX_DISCOVERED_PRINTERS = 8;
constexpr int BUTTON_H = 30;
constexpr int BUTTON_W = 88;
@@ -137,10 +138,24 @@ struct PrintDialogState {
SvgIcon printer_icon;
char source_path[256];
char job_name[128];
char printer_uri[256];
char printer_name[128];
char status[160];
bool can_print;
struct PrinterRow {
char uri[256];
char name[128];
char status[128];
char seen_at[32];
bool ok;
bool is_default;
} printers[MAX_DISCOVERED_PRINTERS];
int printer_count;
int selected_printer;
uint32_t copies;
int queue_count;
int active_count;
bool daemon_running;
bool submit_mode;
bool source_ready;
char note[160];
char error[160];
int mouse_x;
int mouse_y;
};
@@ -173,7 +188,12 @@ struct FileDialogLayout {
};
struct PrintDialogLayout {
Rect content_rect;
Rect header_rect;
Rect printers_rect;
Rect detail_rect;
Rect copy_minus_btn;
Rect copy_value_rect;
Rect copy_plus_btn;
Rect cancel_btn;
Rect printers_btn;
Rect refresh_btn;
@@ -377,7 +397,13 @@ FileDialogLayout file_layout(const FileDialogState* st) {
PrintDialogLayout print_layout() {
PrintDialogLayout lo = {};
lo.content_rect = {0, 0, g_app.win.width, g_app.win.height};
lo.header_rect = {0, 0, 0, 0};
lo.printers_rect = {16, 16, 212, g_app.win.height - 94};
lo.detail_rect = {244, 16, g_app.win.width - 260, g_app.win.height - 94};
int copy_y = lo.detail_rect.y + 202;
lo.copy_minus_btn = {lo.detail_rect.x + 16, copy_y, 28, 28};
lo.copy_value_rect = {lo.copy_minus_btn.x + 36, copy_y, 104, 28};
lo.copy_plus_btn = {lo.copy_value_rect.x + lo.copy_value_rect.w + 8, copy_y, 28, 28};
int btn_y = g_app.win.height - BUTTON_H - 16;
lo.confirm_btn = {g_app.win.width - BUTTON_W - 16, btn_y, BUTTON_W, BUTTON_H};
lo.cancel_btn = {lo.confirm_btn.x - BUTTON_W - 8, btn_y, BUTTON_W, BUTTON_H};
@@ -386,10 +412,14 @@ PrintDialogLayout print_layout() {
return lo;
}
void dialog_finish(const char* status, const char* path, const char* job_id, const char* message) {
void dialog_finish(const char* status, const char* path, const char* job_id, const char* message,
const char* printer_uri = "", const char* printer_name = "", uint32_t copies = 0) {
safe_copy(g_app.result.status, sizeof(g_app.result.status), status);
safe_copy(g_app.result.path, sizeof(g_app.result.path), path);
safe_copy(g_app.result.job_id, sizeof(g_app.result.job_id), job_id);
safe_copy(g_app.result.printer_uri, sizeof(g_app.result.printer_uri), printer_uri);
safe_copy(g_app.result.printer_name, sizeof(g_app.result.printer_name), printer_name);
g_app.result.copies = copies;
safe_copy(g_app.result.message, sizeof(g_app.result.message), message);
dlg::write_result_file(g_app.request.result_path, &g_app.result);
g_app.running = false;
@@ -1080,35 +1110,149 @@ void draw_file_dialog() {
draw_action_button(c, lo.confirm_btn, confirm_label, confirm_enabled, lo.confirm_btn.contains(st->mouse_x, st->mouse_y), true);
}
void print_refresh(PrintDialogState* st) {
st->printer_uri[0] = '\0';
st->printer_name[0] = '\0';
st->status[0] = '\0';
st->can_print = false;
int count_dir_entries(const char* path) {
DIR* d = opendir(path);
if (!d) return 0;
if (!print::read_default_printer_uri(st->printer_uri, sizeof(st->printer_uri))) {
safe_copy(st->status, sizeof(st->status), "No default printer configured");
int count = 0;
struct dirent* ent = nullptr;
while ((ent = readdir(d)) != nullptr) {
if (ent->d_name[0] == '.') continue;
count++;
}
closedir(d);
return count;
}
void print_set_error(PrintDialogState* st, const char* msg) {
safe_copy(st->error, sizeof(st->error), msg);
}
void print_adjust_copies(PrintDialogState* st, int delta) {
if (!st) return;
int next = (int)st->copies + delta;
if (next < 1) next = 1;
if (next > 99) next = 99;
st->copies = (uint32_t)next;
}
void print_update_note(PrintDialogState* st) {
if (st->printer_count <= 0) {
safe_copy(st->note, sizeof(st->note), "No printers discovered from the userspace print spooler yet.");
return;
}
print::ProbeState probe = {};
if (print::load_printer_probe_state(&probe) && montauk::streq(probe.printer_uri, st->printer_uri)) {
if (probe.caps.printer_name[0]) safe_copy(st->printer_name, sizeof(st->printer_name), probe.caps.printer_name);
if (probe.message[0]) safe_copy(st->status, sizeof(st->status), probe.message);
if (!st->daemon_running) {
safe_copy(st->note, sizeof(st->note), "The print spooler is offline. Saved printers are still available.");
return;
}
if (!st->printer_name[0]) safe_copy(st->printer_name, sizeof(st->printer_name), "Default printer");
if (!st->status[0]) safe_copy(st->status, sizeof(st->status), "Uses the system print spooler");
int fd = montauk::open(st->source_path);
if (fd >= 0) {
montauk::close(fd);
st->can_print = true;
if (st->submit_mode) {
safe_copy(st->note, sizeof(st->note), "The selected document will be handed to the userspace print spooler.");
} else {
safe_copy(st->status, sizeof(st->status), "Source document is no longer available");
safe_copy(st->note, sizeof(st->note), "This app stores printer settings only for now. Document output wiring will be added later.");
}
}
int print_find_printer(const PrintDialogState* st, const char* uri) {
if (!st || !uri || !uri[0]) return -1;
for (int i = 0; i < st->printer_count; i++) {
if (montauk::streq(st->printers[i].uri, uri)) return i;
}
return -1;
}
void print_add_printer(PrintDialogState* st,
const char* uri,
const char* name,
const char* status,
const char* seen_at,
bool ok,
bool is_default) {
if (!st || !uri || !uri[0]) return;
int idx = print_find_printer(st, uri);
if (idx < 0) {
if (st->printer_count >= MAX_DISCOVERED_PRINTERS) return;
idx = st->printer_count++;
montauk::memset(&st->printers[idx], 0, sizeof(st->printers[idx]));
safe_copy(st->printers[idx].uri, sizeof(st->printers[idx].uri), uri);
}
if (name && name[0]) safe_copy(st->printers[idx].name, sizeof(st->printers[idx].name), name);
if (status && status[0]) safe_copy(st->printers[idx].status, sizeof(st->printers[idx].status), status);
if (seen_at && seen_at[0]) safe_copy(st->printers[idx].seen_at, sizeof(st->printers[idx].seen_at), seen_at);
st->printers[idx].ok = ok;
st->printers[idx].is_default = is_default;
}
Rect print_row_rect(const PrintDialogLayout& lo, int idx) {
return {lo.printers_rect.x + 8, lo.printers_rect.y + 28 + idx * 58, lo.printers_rect.w - 16, 52};
}
bool print_can_confirm(const PrintDialogState* st) {
if (!st || st->selected_printer < 0 || st->selected_printer >= st->printer_count) return false;
if (!st->submit_mode) return true;
return st->source_ready;
}
void print_refresh(PrintDialogState* st) {
if (!st) return;
char preferred_uri[256] = {};
if (st->selected_printer >= 0 && st->selected_printer < st->printer_count)
safe_copy(preferred_uri, sizeof(preferred_uri), st->printers[st->selected_printer].uri);
else if (g_app.request.printer_uri[0])
safe_copy(preferred_uri, sizeof(preferred_uri), g_app.request.printer_uri);
st->printer_count = 0;
st->selected_printer = -1;
st->error[0] = '\0';
st->queue_count = count_dir_entries(print::SPOOL_QUEUE_DIR);
st->active_count = count_dir_entries(print::SPOOL_ACTIVE_DIR);
int pid = -1;
st->daemon_running = print::daemon_is_running(&pid);
print::KnownPrinter known[MAX_DISCOVERED_PRINTERS];
int known_count = print::list_known_printers(known, MAX_DISCOVERED_PRINTERS);
for (int i = 0; i < known_count; i++) {
const char* status = known[i].status_message[0]
? known[i].status_message
: (known[i].ok ? "Ready for queued jobs" : "Saved printer");
print_add_printer(st, known[i].uri, known[i].display_name, status, known[i].last_seen, known[i].ok, known[i].is_default);
}
if (st->printer_count <= 0) {
char default_uri[256] = {};
bool have_default = print::read_default_printer_uri(default_uri, sizeof(default_uri));
print::ProbeState probe = {};
bool have_probe = print::load_printer_probe_state(&probe);
if (have_probe && probe.printer_uri[0]) {
const char* status = probe.message[0] ? probe.message
: (probe.caps.status_message[0] ? probe.caps.status_message
: (probe.ok ? "Ready for queued jobs" : "Saved printer"));
print_add_printer(st, probe.printer_uri, probe.caps.printer_name, status, probe.probed_at,
probe.ok, have_default && montauk::streq(default_uri, probe.printer_uri));
} else if (have_default) {
print_add_printer(st, default_uri, "Configured printer", "Saved by the print spooler", "", false, true);
}
}
if (preferred_uri[0]) st->selected_printer = print_find_printer(st, preferred_uri);
if (st->selected_printer < 0) {
for (int i = 0; i < st->printer_count; i++) {
if (st->printers[i].is_default) {
st->selected_printer = i;
break;
}
}
}
if (st->selected_printer < 0 && st->printer_count > 0) st->selected_printer = 0;
st->source_ready = !st->submit_mode || (st->source_path[0] && path_exists_via_open(st->source_path));
print_update_note(st);
}
void init_print_dialog(const dlg::Request& req) {
PrintDialogState* st = &g_app.print;
montauk::memset(st, 0, sizeof(*st));
@@ -1116,6 +1260,8 @@ void init_print_dialog(const dlg::Request& req) {
st->printer_icon = svg_load("0:/icons/printer-symbolic.svg", 24, 24, def);
safe_copy(st->source_path, sizeof(st->source_path), req.source_path);
safe_copy(st->job_name, sizeof(st->job_name), req.job_name);
st->submit_mode = req.mode == dlg::PRINT_DIALOG_SUBMIT;
st->copies = req.copies > 0 ? req.copies : 1;
print_refresh(st);
}
@@ -1124,44 +1270,132 @@ void draw_print_dialog() {
Canvas c = g_app.win.canvas();
c.fill(colors::WINDOW_BG);
PrintDialogLayout lo = print_layout();
const auto* selected = (st->selected_printer >= 0 && st->selected_printer < st->printer_count)
? &st->printers[st->selected_printer]
: nullptr;
c.fill_rect(0, 0, c.w, 54, Color::from_rgb(0xF7, 0xF7, 0xF7));
c.hline(0, 53, c.w, colors::BORDER);
if (st->printer_icon.pixels) c.icon(18, 15, st->printer_icon);
c.text(54, 16, st->printer_name[0] ? st->printer_name : "Print", colors::TEXT_COLOR);
c.fill_rounded_rect(lo.printers_rect.x, lo.printers_rect.y, lo.printers_rect.w, lo.printers_rect.h, 6, colors::WHITE);
c.rect(lo.printers_rect.x, lo.printers_rect.y, lo.printers_rect.w, lo.printers_rect.h, colors::BORDER);
c.text(lo.printers_rect.x + 12, lo.printers_rect.y + 8, "Printers", Color::from_rgb(0x77, 0x77, 0x77));
char source_line[256];
fit_text_end(path_basename(st->source_path), source_line, sizeof(source_line), c.w - 36);
c.text(18, 78, "Document", Color::from_rgb(0x77, 0x77, 0x77));
c.text(18, 100, source_line, colors::TEXT_COLOR);
if (st->printer_count <= 0) {
c.text(lo.printers_rect.x + 12, lo.printers_rect.y + 38, "No printers discovered", colors::TEXT_COLOR);
c.text(lo.printers_rect.x + 12, lo.printers_rect.y + 58, "Open Printers to configure one.", Color::from_rgb(0x77, 0x77, 0x77));
} else {
for (int i = 0; i < st->printer_count; i++) {
Rect row = print_row_rect(lo, i);
bool selected = i == st->selected_printer;
bool hovered = row.contains(st->mouse_x, st->mouse_y);
Color bg = selected ? colors::MENU_HOVER
: (hovered ? Color::from_rgb(0xF6, 0xF6, 0xF6) : colors::WHITE);
c.fill_rounded_rect(row.x, row.y, row.w, row.h, 6, bg);
c.rect(row.x, row.y, row.w, row.h, selected ? colors::ACCENT : Color::from_rgb(0xE4, 0xE4, 0xE4));
if (st->printer_icon.pixels) c.icon(row.x + 10, row.y + 14, st->printer_icon);
c.text(18, 136, "Printer", Color::from_rgb(0x77, 0x77, 0x77));
char printer_line[256];
fit_text_end(st->printer_uri[0] ? st->printer_uri : "Not configured", printer_line, sizeof(printer_line), c.w - 36);
c.text(18, 158, printer_line, colors::TEXT_COLOR);
char display[128];
if (st->printers[i].is_default && st->printers[i].name[0])
snprintf(display, sizeof(display), "%s (Default)", st->printers[i].name);
else if (st->printers[i].is_default)
snprintf(display, sizeof(display), "Default printer");
else
safe_copy(display, sizeof(display), st->printers[i].name[0] ? st->printers[i].name : st->printers[i].uri);
char status_line[160];
fit_text_end(st->status, status_line, sizeof(status_line), c.w - 36);
c.text(18, 190, status_line, st->can_print ? Color::from_rgb(0x66, 0x66, 0x66) : Color::from_rgb(0xC0, 0x33, 0x33));
char title[112];
fit_text_end(display, title, sizeof(title), row.w - 68);
c.text(row.x + 42, row.y + 9, title, colors::TEXT_COLOR);
char subtitle[256];
safe_copy(subtitle, sizeof(subtitle), st->printers[i].uri[0] ? st->printers[i].uri : "Saved printer");
fit_text_end(subtitle, title, sizeof(title), row.w - 68);
c.text(row.x + 42, row.y + 28, title, Color::from_rgb(0x77, 0x77, 0x77));
}
}
c.fill_rounded_rect(lo.detail_rect.x, lo.detail_rect.y, lo.detail_rect.w, lo.detail_rect.h, 6, colors::WHITE);
c.rect(lo.detail_rect.x, lo.detail_rect.y, lo.detail_rect.w, lo.detail_rect.h, colors::BORDER);
int dx = lo.detail_rect.x + 16;
int dy = lo.detail_rect.y + 18;
c.text(dx, dy, "Document", Color::from_rgb(0x77, 0x77, 0x77));
char line[256];
const char* job_label = st->job_name[0] ? st->job_name
: (st->source_path[0] ? path_basename(st->source_path) : "Untitled document");
fit_text_end(job_label, line, sizeof(line), lo.detail_rect.w - 32);
c.text(dx, dy + 20, line, colors::TEXT_COLOR);
dy += 68;
c.text(dx, dy, "Printer", Color::from_rgb(0x77, 0x77, 0x77));
if (selected) {
const char* title = selected->name[0] ? selected->name : selected->uri;
fit_text_end(title, line, sizeof(line), lo.detail_rect.w - 32);
c.text(dx, dy + 18, line, colors::TEXT_COLOR);
const char* subtitle = selected->uri[0] ? selected->uri : selected->status;
fit_text_end(subtitle, line, sizeof(line), lo.detail_rect.w - 32);
c.text(dx, dy + 36, line, Color::from_rgb(0x77, 0x77, 0x77));
} else {
c.text(dx, dy + 18, "No printer selected", Color::from_rgb(0x77, 0x77, 0x77));
}
dy += 86;
c.text(dx, dy, "Copies", Color::from_rgb(0x77, 0x77, 0x77));
draw_action_button(c, lo.copy_minus_btn, "-", st->copies > 1,
lo.copy_minus_btn.contains(st->mouse_x, st->mouse_y), false);
c.fill_rounded_rect(lo.copy_value_rect.x, lo.copy_value_rect.y, lo.copy_value_rect.w, lo.copy_value_rect.h, 4, Color::from_rgb(0xF6, 0xF6, 0xF6));
c.rect(lo.copy_value_rect.x, lo.copy_value_rect.y, lo.copy_value_rect.w, lo.copy_value_rect.h, colors::BORDER);
snprintf(line, sizeof(line), "%u %s", (unsigned)st->copies, st->copies == 1 ? "copy" : "copies");
c.text(lo.copy_value_rect.x + 10, lo.copy_value_rect.y + 7, line, colors::TEXT_COLOR);
draw_action_button(c, lo.copy_plus_btn, "+", st->copies < 99,
lo.copy_plus_btn.contains(st->mouse_x, st->mouse_y), false);
if (st->error[0]) {
char line[160];
fit_text_end(st->error, line, sizeof(line), lo.cancel_btn.x - 24);
c.text(16, lo.cancel_btn.y - 22, line, Color::from_rgb(0xC0, 0x33, 0x33));
}
draw_action_button(c, lo.refresh_btn, "Refresh", true, lo.refresh_btn.contains(st->mouse_x, st->mouse_y), false);
draw_action_button(c, lo.printers_btn, "Printers", true, lo.printers_btn.contains(st->mouse_x, st->mouse_y), false);
draw_action_button(c, lo.cancel_btn, "Cancel", true, lo.cancel_btn.contains(st->mouse_x, st->mouse_y), false);
draw_action_button(c, lo.confirm_btn, "Print", st->can_print && st->printer_uri[0], lo.confirm_btn.contains(st->mouse_x, st->mouse_y), true);
draw_action_button(c, lo.confirm_btn, "Print", print_can_confirm(st),
lo.confirm_btn.contains(st->mouse_x, st->mouse_y), true);
}
void print_finish_setup(PrintDialogState* st) {
if (!st || st->selected_printer < 0 || st->selected_printer >= st->printer_count) {
print_set_error(st, "Choose a printer to continue");
return;
}
const auto& printer = st->printers[st->selected_printer];
dialog_finish("ok", "", "", "Printer selected",
printer.uri, printer.name, st->copies);
}
void print_submit(PrintDialogState* st) {
if (!st || st->selected_printer < 0 || st->selected_printer >= st->printer_count) {
print_set_error(st, "Choose a printer to print");
return;
}
if (!st->source_ready) {
print_set_error(st, "Source document is unavailable");
return;
}
const auto& printer = st->printers[st->selected_printer];
char job_id[64] = {};
char err[160] = {};
const char* job_name = st->job_name[0] ? st->job_name : path_basename(st->source_path);
if (!print::submit_document_job(st->source_path, nullptr, job_name, job_id, sizeof(job_id), err, sizeof(err))) {
safe_copy(st->status, sizeof(st->status), err[0] ? err : "Failed to queue print job");
if (!print::submit_document_job(st->source_path, printer.uri, job_name,
job_id, sizeof(job_id), err, sizeof(err),
(int)(st->copies > 0 ? st->copies : 1))) {
print_set_error(st, err[0] ? err : "Failed to queue print job");
return;
}
char msg[160];
snprintf(msg, sizeof(msg), "Queued as %s", job_id);
dialog_finish("ok", "", job_id, msg);
dialog_finish("ok", "", job_id, msg, printer.uri, printer.name, st->copies);
}
void text_insert(char* buf, int* len, int* cursor, int max_len, char ch) {
@@ -1375,8 +1609,27 @@ void handle_print_mouse(const Montauk::WinEvent& ev) {
if (lo.cancel_btn.contains(me.x, me.y)) { dialog_cancel(); return; }
if (lo.refresh_btn.contains(me.x, me.y)) { print_refresh(st); return; }
if (lo.printers_btn.contains(me.x, me.y)) { montauk::spawn("0:/apps/printers/printers.elf"); return; }
if (lo.confirm_btn.contains(me.x, me.y) && st->can_print && st->printer_uri[0]) {
print_submit(st);
if (lo.copy_minus_btn.contains(me.x, me.y) && st->copies > 1) {
print_adjust_copies(st, -1);
st->error[0] = '\0';
return;
}
if (lo.copy_plus_btn.contains(me.x, me.y) && st->copies < 99) {
print_adjust_copies(st, +1);
st->error[0] = '\0';
return;
}
for (int i = 0; i < st->printer_count; i++) {
Rect row = print_row_rect(lo, i);
if (row.contains(me.x, me.y)) {
st->selected_printer = i;
st->error[0] = '\0';
return;
}
}
if (lo.confirm_btn.contains(me.x, me.y) && print_can_confirm(st)) {
if (st->submit_mode) print_submit(st);
else print_finish_setup(st);
}
}
@@ -1386,11 +1639,34 @@ void handle_print_key(const Montauk::KeyEvent& key) {
dialog_cancel();
return;
}
if ((key.ascii == '\n' || key.ascii == '\r') && g_app.print.can_print && g_app.print.printer_uri[0]) {
print_submit(&g_app.print);
if ((key.ascii == '\n' || key.ascii == '\r') && print_can_confirm(&g_app.print)) {
if (g_app.print.submit_mode) print_submit(&g_app.print);
else print_finish_setup(&g_app.print);
}
if (key.ctrl && (key.ascii == 'r' || key.ascii == 'R')) {
print_refresh(&g_app.print);
return;
}
if ((key.ascii == '+' || key.ascii == '=') && g_app.print.copies < 99) {
print_adjust_copies(&g_app.print, +1);
g_app.print.error[0] = '\0';
return;
}
if ((key.ascii == '-' || key.ascii == '_') && g_app.print.copies > 1) {
print_adjust_copies(&g_app.print, -1);
g_app.print.error[0] = '\0';
return;
}
if ((key.scancode == 0x48 || key.scancode == 0x4B) && g_app.print.printer_count > 0) {
if (g_app.print.selected_printer > 0) g_app.print.selected_printer--;
else g_app.print.selected_printer = 0;
g_app.print.error[0] = '\0';
return;
}
if ((key.scancode == 0x50 || key.scancode == 0x4D) && g_app.print.printer_count > 0) {
if (g_app.print.selected_printer < g_app.print.printer_count - 1) g_app.print.selected_printer++;
else g_app.print.selected_printer = g_app.print.printer_count - 1;
g_app.print.error[0] = '\0';
}
}