// 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 #include #include #include #include // ============================================================ // 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; }