DVX_GUI/apps/notepad/notepad.c
2026-03-16 23:19:49 -05:00

340 lines
7.7 KiB
C

// notepad.c — Simple text editor DXE application (callback-only)
//
// Demonstrates a callback-only app with menus, text editing, and file I/O.
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "dvxWm.h"
#include "shellApp.h"
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define TEXT_BUF_SIZE 32768
#define CMD_NEW 100
#define CMD_OPEN 101
#define CMD_SAVE 102
#define CMD_SAVEAS 103
#define CMD_EXIT 104
#define CMD_CUT 200
#define CMD_COPY 201
#define CMD_PASTE 202
#define CMD_SELALL 203
// ============================================================
// Module state
// ============================================================
static DxeAppContextT *sCtx = NULL;
static WindowT *sWin = NULL;
static WidgetT *sTextArea = NULL;
static char sFilePath[260] = "";
static uint32_t sCleanHash = 0;
// ============================================================
// Prototypes
// ============================================================
int32_t appMain(DxeAppContextT *ctx);
static bool askSaveChanges(void);
static void doNew(void);
static void doOpen(void);
static void doSave(void);
static void doSaveAs(void);
static uint32_t hashText(const char *text);
static bool isDirty(void);
static void markClean(void);
static void onClose(WindowT *win);
static void onMenu(WindowT *win, int32_t menuId);
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Notepad",
.hasMainLoop = false,
.stackSize = 0,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================
// Dirty tracking
// ============================================================
static uint32_t hashText(const char *text) {
if (!text) {
return 0;
}
uint32_t h = 5381;
while (*text) {
h = ((h << 5) + h) ^ (uint8_t)*text;
text++;
}
return h;
}
static bool isDirty(void) {
const char *text = wgtGetText(sTextArea);
return hashText(text) != sCleanHash;
}
static void markClean(void) {
const char *text = wgtGetText(sTextArea);
sCleanHash = hashText(text);
}
// Returns true if it's OK to proceed (saved, discarded, or not dirty).
// Returns false if the user cancelled.
static bool askSaveChanges(void) {
if (!isDirty()) {
return true;
}
int32_t result = dvxMessageBox(sCtx->shellCtx, "Notepad", "Save changes?", MB_YESNOCANCEL | MB_ICONQUESTION);
if (result == ID_YES) {
doSave();
return true;
}
if (result == ID_NO) {
return true;
}
return false;
}
// ============================================================
// File operations
// ============================================================
static void doNew(void) {
if (!askSaveChanges()) {
return;
}
wgtSetText(sTextArea, "");
sFilePath[0] = '\0';
markClean();
dvxSetTitle(sCtx->shellCtx, sWin, "Untitled - Notepad");
}
static void doOpen(void) {
if (!askSaveChanges()) {
return;
}
FileFilterT filters[] = {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (!dvxFileDialog(sCtx->shellCtx, "Open", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
return;
}
FILE *f = fopen(path, "rb");
if (!f) {
dvxMessageBox(sCtx->shellCtx, "Error", "Could not open file.", MB_OK | MB_ICONERROR);
return;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
if (size >= TEXT_BUF_SIZE - 1) {
size = TEXT_BUF_SIZE - 2;
}
char *buf = (char *)malloc(size + 1);
if (buf) {
fread(buf, 1, size, f);
buf[size] = '\0';
wgtSetText(sTextArea, buf);
free(buf);
}
fclose(f);
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
markClean();
char title[300];
snprintf(title, sizeof(title), "%s - Notepad", sFilePath);
dvxSetTitle(sCtx->shellCtx, sWin, title);
}
static void doSave(void) {
if (sFilePath[0] == '\0') {
doSaveAs();
return;
}
const char *text = wgtGetText(sTextArea);
if (!text) {
return;
}
FILE *f = fopen(sFilePath, "wb");
if (!f) {
dvxMessageBox(sCtx->shellCtx, "Error", "Could not save file.", MB_OK | MB_ICONERROR);
return;
}
fwrite(text, 1, strlen(text), f);
fclose(f);
markClean();
}
static void doSaveAs(void) {
FileFilterT filters[] = {
{ "Text Files (*.txt)", "*.txt" },
{ "All Files (*.*)", "*.*" }
};
char path[260];
if (!dvxFileDialog(sCtx->shellCtx, "Save As", FD_SAVE, NULL, filters, 2, path, sizeof(path))) {
return;
}
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
doSave();
char title[300];
snprintf(title, sizeof(title), "%s - Notepad", sFilePath);
dvxSetTitle(sCtx->shellCtx, sWin, title);
}
// ============================================================
// Callbacks
// ============================================================
static void onClose(WindowT *win) {
if (!askSaveChanges()) {
return;
}
dvxDestroyWindow(sCtx->shellCtx, win);
sWin = NULL;
sTextArea = NULL;
}
static void onMenu(WindowT *win, int32_t menuId) {
(void)win;
switch (menuId) {
case CMD_NEW:
doNew();
break;
case CMD_OPEN:
doOpen();
break;
case CMD_SAVE:
doSave();
break;
case CMD_SAVEAS:
doSaveAs();
break;
case CMD_EXIT:
onClose(sWin);
break;
case CMD_CUT:
break;
case CMD_COPY:
break;
case CMD_PASTE:
break;
case CMD_SELALL:
break;
}
}
// ============================================================
// Entry point
// ============================================================
int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx;
AppContextT *ac = ctx->shellCtx;
int32_t screenW = ac->display.width;
int32_t screenH = ac->display.height;
int32_t winW = 480;
int32_t winH = 360;
int32_t winX = (screenW - winW) / 2 + 20;
int32_t winY = (screenH - winH) / 3 + 20;
sWin = dvxCreateWindow(ac, "Untitled - Notepad", winX, winY, winW, winH, true);
if (!sWin) {
return -1;
}
sWin->onClose = onClose;
sWin->onMenu = onMenu;
// Menu bar
MenuBarT *menuBar = wmAddMenuBar(sWin);
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&New", CMD_NEW);
wmAddMenuItem(fileMenu, "&Open...", CMD_OPEN);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "&Save", CMD_SAVE);
wmAddMenuItem(fileMenu, "Save &As...", CMD_SAVEAS);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
MenuT *editMenu = wmAddMenu(menuBar, "&Edit");
wmAddMenuItem(editMenu, "Cu&t\tCtrl+X", CMD_CUT);
wmAddMenuItem(editMenu, "&Copy\tCtrl+C", CMD_COPY);
wmAddMenuItem(editMenu, "&Paste\tCtrl+V", CMD_PASTE);
wmAddMenuSeparator(editMenu);
wmAddMenuItem(editMenu, "Select &All\tCtrl+A", CMD_SELALL);
// Widget tree
WidgetT *root = wgtInitWindow(ac, sWin);
sTextArea = wgtTextArea(root, TEXT_BUF_SIZE);
sTextArea->weight = 100;
sFilePath[0] = '\0';
markClean();
wgtInvalidate(root);
return 0;
}