DVX_GUI/dvxshell/shellDesktop.c

490 lines
12 KiB
C

// shellDesktop.c — Program Manager window for DVX Shell
//
// Displays a grid of available DXE apps from the apps/ directory.
// Double-click or Enter launches an app. Includes Task Manager (Ctrl+Esc).
#include "shellApp.h"
#include "dvxDialog.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
// ============================================================
// Constants
// ============================================================
#define MAX_DXE_FILES 64
#define MAX_PATH_LEN 260
#define PM_GRID_COLS 4
#define PM_BTN_W 100
#define PM_BTN_H 24
// Menu command IDs
#define CMD_RUN 100
#define CMD_EXIT 101
#define CMD_CASCADE 200
#define CMD_TILE 201
#define CMD_TILE_H 202
#define CMD_TILE_V 203
#define CMD_ABOUT 300
#define CMD_TASK_MGR 301
// ============================================================
// Module state
// ============================================================
typedef struct {
char name[SHELL_APP_NAME_MAX]; // display name (filename without .app)
char path[MAX_PATH_LEN]; // full path
} DxeEntryT;
static AppContextT *sCtx = NULL;
static WindowT *sPmWindow = NULL;
static WidgetT *sStatusLabel = NULL;
static DxeEntryT sDxeFiles[MAX_DXE_FILES];
static int32_t sDxeCount = 0;
// ============================================================
// Prototypes
// ============================================================
static void buildPmWindow(void);
static void onAppButtonClick(WidgetT *w);
static void onPmClose(WindowT *win);
static void onPmMenu(WindowT *win, int32_t menuId);
static void scanAppsDir(void);
static void showAboutDialog(void);
static void updateStatusText(void);
// Task Manager
static WindowT *sTmWindow = NULL;
static WidgetT *sTmListBox = NULL;
static void buildTaskManager(void);
static void onTmClose(WindowT *win);
static void onTmEndTask(WidgetT *w);
static void onTmSwitchTo(WidgetT *w);
static void refreshTaskList(void);
// ============================================================
// Static functions (alphabetical)
// ============================================================
static void buildPmWindow(void) {
int32_t screenW = sCtx->display.width;
int32_t screenH = sCtx->display.height;
int32_t winW = 440;
int32_t winH = 340;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 4;
sPmWindow = dvxCreateWindow(sCtx, "Program Manager", winX, winY, winW, winH, true);
if (!sPmWindow) {
return;
}
sPmWindow->appId = 0;
sPmWindow->onClose = onPmClose;
sPmWindow->onMenu = onPmMenu;
// Menu bar
MenuBarT *menuBar = wmAddMenuBar(sPmWindow);
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
wmAddMenuItem(windowMenu, "&Tile", CMD_TILE);
wmAddMenuItem(windowMenu, "Tile &Horizontally", CMD_TILE_H);
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
wmAddMenuItem(helpMenu, "&About DVX Shell...", CMD_ABOUT);
wmAddMenuSeparator(helpMenu);
wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR);
// Widget tree
WidgetT *root = wgtInitWindow(sCtx, sPmWindow);
// App button grid in a frame
WidgetT *appFrame = wgtFrame(root, "Applications");
appFrame->weight = 100;
if (sDxeCount == 0) {
WidgetT *lbl = wgtLabel(appFrame, "(No applications found in apps/ directory)");
(void)lbl;
} else {
// Build rows of buttons
int32_t row = 0;
WidgetT *hbox = NULL;
for (int32_t i = 0; i < sDxeCount; i++) {
if (i % PM_GRID_COLS == 0) {
hbox = wgtHBox(appFrame);
hbox->spacing = wgtPixels(8);
row++;
}
WidgetT *btn = wgtButton(hbox, sDxeFiles[i].name);
btn->prefW = wgtPixels(PM_BTN_W);
btn->prefH = wgtPixels(PM_BTN_H);
btn->userData = &sDxeFiles[i];
btn->onDblClick = onAppButtonClick;
btn->onClick = onAppButtonClick;
}
(void)row;
}
// Status bar
WidgetT *statusBar = wgtStatusBar(root);
sStatusLabel = wgtLabel(statusBar, "");
sStatusLabel->weight = 100;
updateStatusText();
dvxFitWindow(sCtx, sPmWindow);
}
static void buildTaskManager(void) {
if (sTmWindow) {
// Already open — just raise it
for (int32_t i = 0; i < sCtx->stack.count; i++) {
if (sCtx->stack.windows[i] == sTmWindow) {
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, i);
wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
break;
}
}
return;
}
int32_t screenW = sCtx->display.width;
int32_t screenH = sCtx->display.height;
int32_t winW = 300;
int32_t winH = 280;
int32_t winX = (screenW - winW) / 2;
int32_t winY = (screenH - winH) / 3;
sTmWindow = dvxCreateWindow(sCtx, "Task Manager", winX, winY, winW, winH, true);
if (!sTmWindow) {
return;
}
sTmWindow->appId = 0;
sTmWindow->onClose = onTmClose;
WidgetT *root = wgtInitWindow(sCtx, sTmWindow);
// List box of running apps
sTmListBox = wgtListBox(root);
sTmListBox->weight = 100;
sTmListBox->prefH = wgtPixels(160);
// Button row
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
btnRow->spacing = wgtPixels(8);
WidgetT *switchBtn = wgtButton(btnRow, "Switch To");
switchBtn->onClick = onTmSwitchTo;
switchBtn->prefW = wgtPixels(90);
WidgetT *endBtn = wgtButton(btnRow, "End Task");
endBtn->onClick = onTmEndTask;
endBtn->prefW = wgtPixels(90);
refreshTaskList();
dvxFitWindow(sCtx, sTmWindow);
}
static void onAppButtonClick(WidgetT *w) {
DxeEntryT *entry = (DxeEntryT *)w->userData;
if (!entry) {
return;
}
shellLoadApp(sCtx, entry->path);
updateStatusText();
}
static void onPmClose(WindowT *win) {
(void)win;
// Confirm exit
int32_t result = dvxMessageBox(sCtx, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION);
if (result == ID_YES) {
sCtx->running = false;
}
}
static void onPmMenu(WindowT *win, int32_t menuId) {
(void)win;
switch (menuId) {
case CMD_RUN:
{
FileFilterT filters[] = {
{ "Applications (*.app)", "*.app" },
{ "All Files (*.*)", "*.*" }
};
char path[MAX_PATH_LEN];
if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sCtx, path);
updateStatusText();
}
}
break;
case CMD_EXIT:
onPmClose(sPmWindow);
break;
case CMD_CASCADE:
dvxCascadeWindows(sCtx);
break;
case CMD_TILE:
dvxTileWindows(sCtx);
break;
case CMD_TILE_H:
dvxTileWindowsH(sCtx);
break;
case CMD_TILE_V:
dvxTileWindowsV(sCtx);
break;
case CMD_ABOUT:
showAboutDialog();
break;
case CMD_TASK_MGR:
buildTaskManager();
break;
}
}
static void onTmClose(WindowT *win) {
sTmListBox = NULL;
sTmWindow = NULL;
dvxDestroyWindow(sCtx, win);
}
static void onTmEndTask(WidgetT *w) {
(void)w;
if (!sTmListBox) {
return;
}
int32_t sel = wgtListBoxGetSelected(sTmListBox);
if (sel < 0) {
return;
}
// Map list index to app ID
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
shellForceKillApp(sCtx, app);
refreshTaskList();
updateStatusText();
return;
}
idx++;
}
}
}
static void onTmSwitchTo(WidgetT *w) {
(void)w;
if (!sTmListBox) {
return;
}
int32_t sel = wgtListBoxGetSelected(sTmListBox);
if (sel < 0) {
return;
}
// Map list index to app ID, find topmost window for that app
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
if (idx == sel) {
// Find the topmost window for this app
for (int32_t j = sCtx->stack.count - 1; j >= 0; j--) {
WindowT *win = sCtx->stack.windows[j];
if (win->appId == i) {
if (win->minimized) {
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win);
}
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);
wmSetFocus(&sCtx->stack, &sCtx->dirty, sCtx->stack.count - 1);
return;
}
}
return;
}
idx++;
}
}
}
static void refreshTaskList(void) {
if (!sTmListBox) {
return;
}
static const char *items[SHELL_MAX_APPS];
static char names[SHELL_MAX_APPS][SHELL_APP_NAME_MAX + 16];
int32_t count = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
const char *state = app->hasMainLoop ? "task" : "callback";
snprintf(names[count], sizeof(names[count]), "%s [%s]", app->name, state);
items[count] = names[count];
count++;
}
}
wgtListBoxSetItems(sTmListBox, items, count);
}
static void scanAppsDir(void) {
DIR *dir = opendir("apps");
if (!dir) {
shellLog("Shell: apps/ directory not found");
return;
}
sDxeCount = 0;
struct dirent *ent;
while ((ent = readdir(dir)) != NULL && sDxeCount < MAX_DXE_FILES) {
int32_t len = strlen(ent->d_name);
if (len < 5) {
continue;
}
// Check for .app extension (case-insensitive)
const char *ext = ent->d_name + len - 4;
if (strcmp(ext, ".app") != 0 && strcmp(ext, ".APP") != 0) {
continue;
}
DxeEntryT *entry = &sDxeFiles[sDxeCount];
// Name = filename without extension
int32_t nameLen = len - 4;
if (nameLen >= SHELL_APP_NAME_MAX) {
nameLen = SHELL_APP_NAME_MAX - 1;
}
memcpy(entry->name, ent->d_name, nameLen);
entry->name[nameLen] = '\0';
// Capitalize first letter for display
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
entry->name[0] -= 32;
}
snprintf(entry->path, sizeof(entry->path), "apps/%s", ent->d_name);
sDxeCount++;
}
closedir(dir);
shellLog("Shell: found %ld app(s)", (long)sDxeCount);
}
static void showAboutDialog(void) {
dvxMessageBox(sCtx, "About DVX Shell",
"DVX Shell 1.0\n\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.",
MB_OK | MB_ICONINFO);
}
static void updateStatusText(void) {
if (!sStatusLabel) {
return;
}
static char buf[64];
int32_t count = shellRunningAppCount();
if (count == 0) {
snprintf(buf, sizeof(buf), "No applications running");
} else if (count == 1) {
snprintf(buf, sizeof(buf), "1 application running");
} else {
snprintf(buf, sizeof(buf), "%ld applications running", (long)count);
}
wgtSetText(sStatusLabel, buf);
}
// ============================================================
// Public API
// ============================================================
void shellDesktopInit(AppContextT *ctx) {
sCtx = ctx;
scanAppsDir();
buildPmWindow();
}
void shellDesktopUpdateStatus(void) {
updateStatusText();
// Also refresh task manager if open
if (sTmWindow) {
refreshTaskList();
}
}
void shellTaskManager(AppContextT *ctx) {
sCtx = ctx;
buildTaskManager();
}