2047 lines
65 KiB
C
2047 lines
65 KiB
C
// The MIT License (MIT)
|
|
//
|
|
// Copyright (C) 2026 Scott Duensing
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to
|
|
// deal in the Software without restriction, including without limitation the
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
|
|
// dialogsTest.c -- T4: modal dialogs driven through tDialogScript
|
|
//
|
|
// Every blocking dialog (message/input/int/choice/file) runs its own
|
|
// nested dvxUpdate loop, so each case installs an idle script that
|
|
// finds the modal window and drives it with queued input. A script
|
|
// may pump nested frames (tClick, tType, ...) from inside the idle
|
|
// callback; the busy stack below keeps a script from re-entering
|
|
// itself for the SAME modal while still letting it answer a nested
|
|
// dialog (confirmation boxes opened by the file dialog).
|
|
|
|
#include "dvxtest.h"
|
|
#include "dvxWm.h"
|
|
#include "dvxWgtP.h"
|
|
#include "box/box.h"
|
|
#include "button/button.h"
|
|
#include "canvas/canvas.h"
|
|
#include "dropdown/dropdown.h"
|
|
#include "listBox/listBox.h"
|
|
#include "slider/slider.h"
|
|
#include "spinner/spinner.h"
|
|
#include "textInput/textInpt.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <sys/stat.h>
|
|
|
|
#define SCREEN_W 640
|
|
#define SCREEN_H 480
|
|
#define SCREEN_W_LARGE 800
|
|
#define SCREEN_H_LARGE 600
|
|
#define SCREEN_BPP 32
|
|
|
|
#define SETTLE_FRAMES 3
|
|
#define SOUND_LOG_MAX 16
|
|
#define CENTER_TOLERANCE 1
|
|
#define BUSY_STACK_MAX 8
|
|
#define TEXT_MAX_LEN 64
|
|
#define SMALL_BUF_LEN 4
|
|
#define OUT_PATH_LEN 512
|
|
#define CUSTOM_LABEL_W 60
|
|
|
|
// Message box geometry mirrored from dvxDialog.c (not exported)
|
|
#define MSG_PADDING 8
|
|
#define MSG_MAX_WIDTH 320
|
|
#define MSG_MAX_WIDTH_FRAME (MSG_MAX_WIDTH + CHROME_TOTAL_SIDE * 2)
|
|
#define IB_WIDTH_FRAME (300 + CHROME_TOTAL_SIDE * 2) // dvxDialog.c IB_DIALOG_WIDTH + chrome
|
|
#define BUTTON_HEIGHT 24
|
|
#define LISTBOX_BORDER 2
|
|
#define SPINNER_BORDER 2
|
|
#define SPINNER_BTN_W 14
|
|
|
|
// Spinner fixture
|
|
#define SPIN_DEFAULT 5
|
|
#define SPIN_MIN 0
|
|
#define SPIN_MAX 10
|
|
#define SPIN_STEP 2
|
|
|
|
// Choice fixture
|
|
#define CHOICE_COUNT 4
|
|
#define CHOICE_DEFAULT 1
|
|
#define BUTTON_BOX_COUNT 4 // caseButtonBox's labels
|
|
#define BUTTON_BOX_ALL 2 // index of "Yes to All"
|
|
|
|
// Fixture tree entries: "..", "[sub]", a.txt, b.txt, c.bmp
|
|
#define FIXTURE_ENTRIES_ALL 5
|
|
#define FIXTURE_ENTRIES_TXT 4
|
|
#define FIXTURE_ENTRIES_BMP 3
|
|
#define FIXTURE_IDX_PARENT 0
|
|
#define FIXTURE_IDX_SUB 1
|
|
#define FIXTURE_IDX_ATXT 2
|
|
#define FIXTURE_SUB_ENTRIES 3 // "..", "[deep]", d.txt
|
|
|
|
// Color dialog fixture: the root's children are the palette, one row
|
|
// (label, slider, number box) per channel, the preview; a basic color's
|
|
// cell and where it sits relative to the palette's center.
|
|
#define COLOR_ROW_RED 1
|
|
#define COLOR_ROW_GREEN 2
|
|
#define COLOR_ROW_BLUE 3
|
|
#define COLOR_IDX_PREVIEW 4
|
|
#define COLOR_IDX_SLIDER 1
|
|
#define COLOR_IDX_SPINNER 2
|
|
#define COLOR_START 0x804020
|
|
#define COLOR_UNCHANGED 0x123456
|
|
#define COLOR_SWATCH_DX (-60) // cell (1, 1) center (36, 27) minus the palette center (96, 54)
|
|
#define COLOR_SWATCH_DY (-27)
|
|
#define COLOR_SWATCH_RGB 0xFFFF00 // basic color at (1, 1)
|
|
#define COLOR_GREEN_TEXT "200"
|
|
#define COLOR_KEYS_RGB 0xFFC820 // red End -> 255, green typed 200, blue from COLOR_START
|
|
#define COLOR_CHANNEL_MAX 255
|
|
#define COLOR_SHIFT_R 16
|
|
#define COLOR_SHIFT_G 8
|
|
#define COLOR_MASK 0xFF
|
|
#define BPP_8 8
|
|
#define BPP_15 15
|
|
#define BPP_16 16
|
|
|
|
#define SPIN_OVERRANGE_TEXT "999"
|
|
#define SPIN_UNDERRANGE_TEXT "-55"
|
|
#define BACKSPACE_CLEAR_COUNT 4
|
|
|
|
#define OTHER_WIN_X 20
|
|
#define OTHER_WIN_Y 20
|
|
#define OTHER_WIN_W 240
|
|
#define OTHER_WIN_H 160
|
|
|
|
// The runner bakes its own golden directory into libdvxtest.a, so the
|
|
// crop name walks from there to this suite's golden/ (see NEEDS in the
|
|
// T4 report: a per-suite golden dir setter).
|
|
#define GOLDEN_MSGBOX "msgBoxInfo32"
|
|
|
|
#define DBLCLICK_GAP_MS 10
|
|
#define SINGLE_CLICK_GAP_MS (MOUSE_DBLCLICK_DEFAULT_MS + 1)
|
|
|
|
|
|
// ============================================================
|
|
// Types
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
int32_t flags;
|
|
const char *labels[3];
|
|
int32_t ids[3];
|
|
int32_t count;
|
|
} ButtonSetT;
|
|
|
|
typedef struct {
|
|
const char *button; // caption to click on the outer dialog (NULL = none)
|
|
const char *nestedButton; // caption to click on any nested dialog
|
|
const char *typeText; // typed into the focused widget first (NULL = none)
|
|
bool pressEnter; // press Enter instead of clicking
|
|
bool pressEsc; // press Escape instead of clicking
|
|
bool closeGadget; // click the title-bar close gadget
|
|
bool abandon; // clear ctx->running
|
|
char title[MAX_TITLE_LEN];
|
|
int32_t calls;
|
|
int32_t winX;
|
|
int32_t winY;
|
|
int32_t winW;
|
|
int32_t winH;
|
|
int32_t contentH;
|
|
WindowT *modalSeen;
|
|
WindowT *prevModalAfterNested;
|
|
int32_t nestedResult;
|
|
char captured[DVX_MAX_PATH];
|
|
int32_t capturedInt;
|
|
int32_t capturedInt2;
|
|
const char *goldenName;
|
|
} ScriptT;
|
|
|
|
typedef struct {
|
|
WindowT *win;
|
|
WidgetT *btn;
|
|
WidgetT *input;
|
|
} OtherWindowT;
|
|
|
|
typedef enum {
|
|
ColorOpOkE, // OK as opened
|
|
ColorOpCancelE,
|
|
ColorOpEscE,
|
|
ColorOpSwatchE, // click a basic color, then OK
|
|
ColorOpKeysE // End on the focused Red slider, type into the Green box, Enter
|
|
} ColorOpE;
|
|
|
|
typedef struct {
|
|
ColorOpE op;
|
|
uint32_t start; // the color the dialog was opened with
|
|
int32_t calls;
|
|
bool previewOk; // the preview showed start
|
|
bool redFocused; // the Red slider had the focus
|
|
int32_t greenAfter; // Green slider after the typed number (keys op)
|
|
} ColorScriptT;
|
|
|
|
// caseCustom's onOk state: refuses the first `refuse` OKs, then copies
|
|
// the input's text.
|
|
typedef struct {
|
|
WidgetT *input;
|
|
int32_t calls;
|
|
int32_t refuse;
|
|
char text[TEXT_MAX_LEN];
|
|
} CustomOkT;
|
|
|
|
typedef enum {
|
|
FileOpCancelE,
|
|
FileOpEscE,
|
|
FileOpCloseE,
|
|
FileOpDblClickSubE,
|
|
FileOpTypePathE,
|
|
FileOpFilterNextE,
|
|
FileOpClickFileOpenE,
|
|
FileOpTypeNameE,
|
|
FileOpDblClickFileE,
|
|
FileOpSelectDirOpenE,
|
|
FileOpEnterInNameE,
|
|
FileOpDriveE
|
|
} FileOpE;
|
|
|
|
typedef struct {
|
|
FileOpE op;
|
|
bool acted; // step 0 done; next idle call records and cancels
|
|
const char *nestedButton;
|
|
const char *text;
|
|
char pathAfter[DVX_MAX_PATH];
|
|
char nameAfter[DVX_MAX_PATH];
|
|
int32_t itemsAfter;
|
|
int32_t itemsBefore;
|
|
int32_t driveIdx; // FileOpDriveE: the drive to pick (-1: none)
|
|
int32_t driveItems; // drive dropdown: its items, first label,
|
|
int32_t driveBefore; // and the selection before / after
|
|
int32_t driveAfter;
|
|
char driveLabel[MAX_TITLE_LEN];
|
|
bool stillModalAfter;
|
|
bool nestedSeen;
|
|
char nestedTitle[MAX_TITLE_LEN];
|
|
} FileScriptT;
|
|
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static void caseAbandon(void);
|
|
static void caseButtonBox(void);
|
|
static void caseChoice(void);
|
|
static void caseColorDialog(void);
|
|
static void caseCustom(void);
|
|
static void caseErrorInfoBox(void);
|
|
static void caseFileDialog(void);
|
|
static void caseGeometry(int32_t w, int32_t h);
|
|
static void caseGolden(void);
|
|
static void caseInputBox(void);
|
|
static void caseIntInputBox(void);
|
|
static void caseMessageBoxMatrix(void);
|
|
static void caseModality(void);
|
|
static void caseNested(void);
|
|
static void caseNestedSameType(void);
|
|
static void casePromptSave(void);
|
|
static void caseSounds(void);
|
|
static void caseWrapping(void);
|
|
static WidgetT *childAt(WidgetT *parent, int32_t idx);
|
|
static void clickCloseGadget(WindowT *win);
|
|
static void clickListRow(WidgetT *lb, int32_t row, bool dbl);
|
|
static void clickSpinnerArrow(WidgetT *sp, bool up);
|
|
static void colorScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
static void customEnd(WidgetT *w);
|
|
static bool customOk(WindowT *dlg, void *userCtx);
|
|
static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
static void fileSelectRow(WidgetT *lb, int32_t row);
|
|
static void fixtureTree(char *dir, int32_t dirSize);
|
|
static void nestedSameTypeScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
static void nestedScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
static OtherWindowT otherWindow(const char *title);
|
|
static bool scriptEnter(WindowT *modal);
|
|
static void scriptInit(ScriptT *s, const char *button);
|
|
static void scriptLeave(void);
|
|
static void scriptRecord(ScriptT *s, WindowT *modal);
|
|
static void soundRecord(DvxSoundE event, bool wait);
|
|
static void spinInit(ScriptT *s, const char *button);
|
|
static void spinScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
static void uniformScript(AppContextT *ctx, WindowT *modal, void *userCtx);
|
|
|
|
|
|
// ============================================================
|
|
// Module state
|
|
// ============================================================
|
|
|
|
static WindowT *sBusy[BUSY_STACK_MAX];
|
|
static int32_t sBusyDepth = 0;
|
|
|
|
static const ButtonSetT sButtonSets[] = {
|
|
{ MB_OK, { "OK", NULL, NULL }, { ID_OK, 0, 0 }, 1 },
|
|
{ MB_OKCANCEL, { "OK", "Cancel", NULL }, { ID_OK, ID_CANCEL, 0 }, 2 },
|
|
{ MB_YESNO, { "Yes", "No", NULL }, { ID_YES, ID_NO, 0 }, 2 },
|
|
{ MB_YESNOCANCEL, { "Yes", "No", "Cancel" }, { ID_YES, ID_NO, ID_CANCEL }, 3 },
|
|
{ MB_RETRYCANCEL, { "Retry", "Cancel", NULL }, { ID_RETRY, ID_CANCEL, 0 }, 2 },
|
|
};
|
|
|
|
static const int32_t sIcons[] = { 0, MB_ICONINFO, MB_ICONWARNING, MB_ICONERROR, MB_ICONQUESTION };
|
|
|
|
// The desktop sound of a message box with each of sIcons
|
|
static const DvxSoundE sIconSounds[] = { SoundDefaultE, SoundInfoE, SoundWarningE, SoundErrorE, SoundQuestionE };
|
|
|
|
// Desktop sounds asked for (the recording sound hook)
|
|
static DvxSoundE sSoundLog[SOUND_LOG_MAX];
|
|
static bool sSoundWait[SOUND_LOG_MAX];
|
|
static int32_t sSoundCount = 0;
|
|
|
|
static const char *sChoices[CHOICE_COUNT] = { "Alpha", "Beta", "Gamma", "Delta" };
|
|
|
|
static const FileFilterT sFiltersTxt[] = { { "Text Files (*.txt)" }, { "All Files (*.*)" } };
|
|
static const FileFilterT sFiltersMulti[] = { { "Text and Images (*.txt;*.bmp)" }, { "Bitmaps (*.bmp)" } };
|
|
|
|
|
|
// ============================================================
|
|
// Cases
|
|
// ============================================================
|
|
|
|
static void caseAbandon(void) {
|
|
tCase("abandon mid-loop leaves no state behind");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, NULL);
|
|
s.abandon = true;
|
|
tDialogScript(uniformScript, &s);
|
|
|
|
int32_t result = dvxMessageBox(tCtx(), "Abandon", "Going away", MB_OKCANCEL);
|
|
|
|
TEST_EQ(result, ID_CANCEL);
|
|
TEST_TRUE(!tCtx()->running);
|
|
TEST_PTR_EQ(tCtx()->modalWindow, NULL);
|
|
|
|
tCtx()->running = true;
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Abandon"), NULL);
|
|
TEST_EQ(tCtx()->stack.count, 0);
|
|
|
|
// A fresh dialog must work normally.
|
|
scriptInit(&s, "OK");
|
|
result = dvxMessageBox(tCtx(), "After", "Back again", MB_OKCANCEL);
|
|
TEST_EQ(result, ID_OK);
|
|
|
|
// Abandon a file dialog too (it owns heap entries).
|
|
char dir[DVX_MAX_PATH];
|
|
char out[OUT_PATH_LEN];
|
|
|
|
fixtureTree(dir, sizeof(dir));
|
|
scriptInit(&s, NULL);
|
|
s.abandon = true;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Gone", FD_OPEN, dir, sFiltersTxt, 2, out, sizeof(out)));
|
|
tCtx()->running = true;
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Gone"), NULL);
|
|
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// Caller's buttons: the index of the one clicked, Enter the first,
|
|
// Escape and the close gadget the last; bad arguments give -1.
|
|
static void caseButtonBox(void) {
|
|
static const char *const labels[BUTTON_BOX_COUNT] = { "&Yes", "&No", "Yes to &All", "&Cancel" };
|
|
|
|
tCase("button box");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, "Yes to All");
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "Replace it?", MB_ICONQUESTION, labels, BUTTON_BOX_COUNT), BUTTON_BOX_ALL);
|
|
TEST_STR_EQ(s.title, "Ask");
|
|
scriptInit(&s, NULL);
|
|
s.pressEnter = true;
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "Replace it?", 0, labels, BUTTON_BOX_COUNT), 0);
|
|
scriptInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "Replace it?", 0, labels, BUTTON_BOX_COUNT), BUTTON_BOX_COUNT - 1);
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "Replace it?", 0, labels, BUTTON_BOX_COUNT), BUTTON_BOX_COUNT - 1);
|
|
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "m", 0, labels, 0), -1);
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "m", 0, labels, DVX_BUTTON_BOX_MAX + 1), -1);
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", NULL, 0, labels, 1), -1);
|
|
TEST_EQ(dvxButtonBox(tCtx(), "Ask", "m", 0, NULL, 1), -1);
|
|
TEST_EQ(dvxButtonBox(NULL, "Ask", "m", 0, labels, 1), -1);
|
|
tDialogScript(NULL, NULL);
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Ask"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseChoice(void) {
|
|
tCase("choice dialog");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
int32_t idx = -1;
|
|
|
|
// Default index is highlighted and returned by OK.
|
|
scriptInit(&s, "OK");
|
|
TEST_TRUE(dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, CHOICE_DEFAULT, &idx));
|
|
TEST_EQ(idx, CHOICE_DEFAULT);
|
|
TEST_EQ(s.capturedInt, CHOICE_DEFAULT);
|
|
|
|
// Keyboard: Down twice from the default then OK.
|
|
scriptInit(&s, "OK");
|
|
s.capturedInt2 = 2;
|
|
idx = -1;
|
|
TEST_TRUE(dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, CHOICE_DEFAULT, &idx));
|
|
TEST_EQ(idx, CHOICE_DEFAULT + 2);
|
|
|
|
// Enter on the list accepts the highlighted item.
|
|
scriptInit(&s, NULL);
|
|
s.pressEnter = true;
|
|
idx = -1;
|
|
TEST_TRUE(dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, 0, &idx));
|
|
TEST_EQ(idx, 0);
|
|
|
|
// Double-click on the last row accepts it.
|
|
scriptInit(&s, NULL);
|
|
s.capturedInt2 = -(CHOICE_COUNT - 1) - 1;
|
|
idx = -1;
|
|
TEST_TRUE(dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, 0, &idx));
|
|
TEST_EQ(idx, CHOICE_COUNT - 1);
|
|
|
|
// Cancel, Escape and the close gadget all reject.
|
|
idx = 7;
|
|
scriptInit(&s, "Cancel");
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, CHOICE_DEFAULT, &idx));
|
|
TEST_EQ(idx, 7);
|
|
scriptInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, CHOICE_DEFAULT, &idx));
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, CHOICE_DEFAULT, &idx));
|
|
TEST_EQ(idx, 7);
|
|
|
|
// defaultIdx -1: the list box highlights its first item anyway.
|
|
scriptInit(&s, "OK");
|
|
TEST_TRUE(dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, -1, &idx));
|
|
TEST_EQ(idx, 0);
|
|
TEST_EQ(s.capturedInt, 0);
|
|
|
|
// Bad arguments
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, 0, 0, &idx));
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", NULL, CHOICE_COUNT, 0, &idx));
|
|
TEST_TRUE(!dvxChoiceDialog(tCtx(), "Pick", "Pick one:", sChoices, CHOICE_COUNT, 0, NULL));
|
|
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Pick"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// Custom dialogs: onOk may refuse (the dialog stays up and is answered
|
|
// again), sees the live widgets when it accepts, and is never called on
|
|
// Escape, Cancel or close; NULL onOk accepts at once.
|
|
static void caseColorDialog(void) {
|
|
tCase("color dialog: palette, sliders, number boxes, OK/Cancel at 8/15/16/32 bpp");
|
|
|
|
const int32_t bpps[] = { BPP_8, BPP_15, BPP_16, SCREEN_BPP };
|
|
|
|
for (int32_t i = 0; i < DVX_ARRAY_LEN(bpps); i++) {
|
|
tInit(SCREEN_W, SCREEN_H, bpps[i]);
|
|
|
|
ColorScriptT cs;
|
|
uint32_t rgb = COLOR_START;
|
|
|
|
memset(&cs, 0, sizeof(cs));
|
|
cs.op = ColorOpOkE;
|
|
cs.start = rgb;
|
|
tDialogScript(colorScript, &cs);
|
|
TEST_TRUE(dvxColorDialog(tCtx(), NULL, &rgb));
|
|
TEST_EQ(rgb, COLOR_START);
|
|
TEST_TRUE(cs.previewOk);
|
|
TEST_TRUE(cs.redFocused);
|
|
|
|
tShutdown();
|
|
}
|
|
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ColorScriptT cs;
|
|
uint32_t rgb = COLOR_START;
|
|
|
|
// A basic color picked with the mouse.
|
|
memset(&cs, 0, sizeof(cs));
|
|
cs.op = ColorOpSwatchE;
|
|
cs.start = rgb;
|
|
tDialogScript(colorScript, &cs);
|
|
TEST_TRUE(dvxColorDialog(tCtx(), "Pick", &rgb));
|
|
TEST_EQ(rgb, COLOR_SWATCH_RGB);
|
|
|
|
// Keyboard: End on the Red slider, a typed Green value, Enter.
|
|
rgb = COLOR_START;
|
|
memset(&cs, 0, sizeof(cs));
|
|
cs.op = ColorOpKeysE;
|
|
cs.start = rgb;
|
|
tDialogScript(colorScript, &cs);
|
|
TEST_TRUE(dvxColorDialog(tCtx(), "Pick", &rgb));
|
|
TEST_EQ(rgb, COLOR_KEYS_RGB);
|
|
TEST_EQ(cs.greenAfter, (COLOR_KEYS_RGB >> COLOR_SHIFT_G) & COLOR_MASK);
|
|
|
|
// Cancel and Escape leave the color alone.
|
|
rgb = COLOR_UNCHANGED;
|
|
memset(&cs, 0, sizeof(cs));
|
|
cs.op = ColorOpCancelE;
|
|
cs.start = rgb;
|
|
tDialogScript(colorScript, &cs);
|
|
TEST_TRUE(!dvxColorDialog(tCtx(), "Pick", &rgb));
|
|
memset(&cs, 0, sizeof(cs));
|
|
cs.op = ColorOpEscE;
|
|
cs.start = rgb;
|
|
tDialogScript(colorScript, &cs);
|
|
TEST_TRUE(!dvxColorDialog(tCtx(), "Pick", &rgb));
|
|
TEST_EQ(rgb, COLOR_UNCHANGED);
|
|
|
|
TEST_TRUE(!dvxColorDialog(tCtx(), "Pick", NULL));
|
|
TEST_TRUE(!dvxColorDialog(NULL, "Pick", &rgb));
|
|
|
|
tDialogScript(NULL, NULL);
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Pick"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseCustom(void) {
|
|
tCase("custom dialog");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
CustomOkT ok;
|
|
|
|
for (int32_t pass = 0; pass < 4; pass++) {
|
|
WindowT *dlg = dvxDialogCreate(tCtx(), "Custom", IB_WIDTH_FRAME);
|
|
|
|
TEST_TRUE(dlg != NULL);
|
|
|
|
if (!dlg) {
|
|
break;
|
|
}
|
|
|
|
memset(&ok, 0, sizeof(ok));
|
|
ok.input = dvxDialogAddField(dlg, "Name:", CUSTOM_LABEL_W, TEXT_MAX_LEN - 1, "value");
|
|
ok.refuse = 1;
|
|
TEST_TRUE(ok.input != NULL && tFindByText(dlg->widgetRoot, "Name:") == ok.input->parent->firstChild);
|
|
dvxDialogAddOkCancel(dlg, NULL, customOk, &ok);
|
|
|
|
switch (pass) {
|
|
case 0:
|
|
scriptInit(&s, "OK");
|
|
s.nestedButton = "OK";
|
|
TEST_TRUE(dvxDialogRun(tCtx(), dlg));
|
|
TEST_EQ(ok.calls, 2);
|
|
TEST_STR_EQ(ok.text, "value");
|
|
TEST_STR_EQ(s.title, "Custom");
|
|
break;
|
|
|
|
case 1:
|
|
scriptInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_TRUE(!dvxDialogRun(tCtx(), dlg));
|
|
TEST_EQ(ok.calls, 0);
|
|
break;
|
|
|
|
case 2:
|
|
scriptInit(&s, "Cancel");
|
|
TEST_TRUE(!dvxDialogRun(tCtx(), dlg));
|
|
TEST_EQ(ok.calls, 0);
|
|
break;
|
|
|
|
default:
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_TRUE(!dvxDialogRun(tCtx(), dlg));
|
|
TEST_EQ(ok.calls, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No onOk: OK accepts on the first press.
|
|
WindowT *plain = dvxDialogCreate(tCtx(), NULL, IB_WIDTH_FRAME);
|
|
|
|
TEST_TRUE(plain != NULL);
|
|
dvxDialogAddOkCancel(plain, "&Go", NULL, NULL);
|
|
scriptInit(&s, "Go");
|
|
TEST_TRUE(dvxDialogRun(tCtx(), plain));
|
|
|
|
// dvxDialogEnd from the caller's own button: accepting runs onOk,
|
|
// ending unaccepted does not.
|
|
for (int32_t accept = 0; accept < 2; accept++) {
|
|
WindowT *own = dvxDialogCreate(tCtx(), "Own", IB_WIDTH_FRAME);
|
|
|
|
TEST_TRUE(own != NULL);
|
|
|
|
if (!own) {
|
|
break;
|
|
}
|
|
|
|
memset(&ok, 0, sizeof(ok));
|
|
ok.input = dvxDialogAddField(own, "Name:", CUSTOM_LABEL_W, TEXT_MAX_LEN - 1, "own");
|
|
|
|
WidgetT *endBtn = wgtButton(own->widgetRoot, accept ? "&End" : "&Drop");
|
|
|
|
endBtn->userData = accept ? (void *)&ok : NULL;
|
|
endBtn->onClick = customEnd;
|
|
dvxDialogAddOkCancel(own, NULL, customOk, &ok);
|
|
scriptInit(&s, accept ? "End" : "Drop");
|
|
TEST_EQ(dvxDialogRun(tCtx(), own), accept == 1);
|
|
TEST_EQ(ok.calls, accept);
|
|
TEST_STR_EQ(ok.text, accept ? "own" : "");
|
|
}
|
|
|
|
dvxDialogEnd(NULL, true);
|
|
|
|
// Bad arguments are inert.
|
|
TEST_PTR_EQ(dvxDialogCreate(NULL, "x", IB_WIDTH_FRAME), NULL);
|
|
TEST_PTR_EQ(dvxDialogAddField(NULL, "x", CUSTOM_LABEL_W, TEXT_MAX_LEN - 1, NULL), NULL);
|
|
TEST_TRUE(!dvxDialogRun(tCtx(), NULL));
|
|
dvxDialogAddOkCancel(NULL, NULL, NULL, NULL);
|
|
tDialogScript(NULL, NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseErrorInfoBox(void) {
|
|
tCase("error/info box titles");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, "OK");
|
|
TEST_EQ(dvxErrorBox(tCtx(), NULL, "Boom"), ID_OK);
|
|
TEST_STR_EQ(s.title, "Error");
|
|
|
|
scriptInit(&s, "OK");
|
|
TEST_EQ(dvxErrorBox(tCtx(), "Custom", "Boom"), ID_OK);
|
|
TEST_STR_EQ(s.title, "Custom");
|
|
|
|
scriptInit(&s, "OK");
|
|
TEST_EQ(dvxInfoBox(tCtx(), NULL, "FYI"), ID_OK);
|
|
TEST_STR_EQ(s.title, "Information");
|
|
|
|
scriptInit(&s, "OK");
|
|
TEST_EQ(dvxInfoBox(tCtx(), "Note", "FYI"), ID_OK);
|
|
TEST_STR_EQ(s.title, "Note");
|
|
|
|
// Only one button exists; Cancel is absent.
|
|
scriptInit(&s, "Cancel");
|
|
s.nestedButton = "OK";
|
|
TEST_EQ(dvxInfoBox(tCtx(), "Note", "FYI"), ID_OK);
|
|
|
|
// NULL message / ctx are rejected without a window.
|
|
TEST_EQ(dvxMessageBox(tCtx(), "X", NULL, MB_OK), ID_CANCEL);
|
|
TEST_EQ(dvxMessageBox(NULL, "X", "m", MB_OK), ID_CANCEL);
|
|
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseFileDialog(void) {
|
|
tCase("file dialog");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
char dir[DVX_MAX_PATH];
|
|
char sub[DVX_MAX_PATH];
|
|
char expect[OUT_PATH_LEN];
|
|
char out[OUT_PATH_LEN];
|
|
FileScriptT f;
|
|
char real[PATH_MAX];
|
|
|
|
fixtureTree(dir, sizeof(dir));
|
|
|
|
if (!realpath(dir, real)) {
|
|
snprintf(real, sizeof(real), "%s", dir);
|
|
}
|
|
|
|
snprintf(sub, sizeof(sub), "%s/sub", real);
|
|
tDialogScript(fileScript, &f);
|
|
|
|
// Cancel / Escape / close gadget reject and leave outPath alone.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpCancelE;
|
|
strcpy(out, "untouched");
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(out, "untouched");
|
|
TEST_EQ(f.itemsBefore, FIXTURE_ENTRIES_ALL);
|
|
TEST_STR_EQ(f.pathAfter, real);
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpEscE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpCloseE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), NULL, FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Open"), NULL);
|
|
|
|
// Default titles
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpCancelE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), NULL, FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_PTR_EQ(tFindWindow("Save As"), NULL);
|
|
|
|
// Double-click on "[sub]" navigates; the dialog stays up.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpDblClickSubE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.pathAfter, sub);
|
|
TEST_TRUE(f.stillModalAfter);
|
|
TEST_EQ(f.itemsAfter, FIXTURE_SUB_ENTRIES);
|
|
|
|
// Typing a path + Enter in the path input navigates.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypePathE;
|
|
f.text = sub;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.pathAfter, sub);
|
|
TEST_TRUE(f.stillModalAfter);
|
|
|
|
// Drives: the host has one, "/", selected for the folder; picking it
|
|
// goes to its root, picking none does nothing.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpDriveE;
|
|
f.driveIdx = 0;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_EQ(f.driveItems, 1);
|
|
TEST_STR_EQ(f.driveLabel, "/");
|
|
TEST_EQ(f.driveBefore, 0);
|
|
TEST_EQ(f.driveAfter, 0);
|
|
TEST_STR_EQ(f.pathAfter, "/");
|
|
TEST_TRUE(f.stillModalAfter);
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpDriveE;
|
|
f.driveIdx = -1;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.pathAfter, real);
|
|
TEST_EQ(f.driveAfter, -1);
|
|
|
|
// Filters: single glob, then switching to the next filter.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpFilterNextE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, sFiltersTxt, 2, out, sizeof(out)));
|
|
TEST_EQ(f.itemsBefore, FIXTURE_ENTRIES_TXT);
|
|
TEST_EQ(f.itemsAfter, FIXTURE_ENTRIES_ALL);
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpFilterNextE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, sFiltersMulti, 2, out, sizeof(out)));
|
|
TEST_EQ(f.itemsBefore, FIXTURE_ENTRIES_ALL);
|
|
TEST_EQ(f.itemsAfter, FIXTURE_ENTRIES_BMP);
|
|
|
|
// Open: click a.txt (name input follows), then Open.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpClickFileOpenE;
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/a.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
TEST_STR_EQ(f.nameAfter, "a.txt");
|
|
|
|
// Double-click on a file accepts it.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpDblClickFileE;
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(out, expect);
|
|
|
|
// Enter in the filename input is the default button.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpEnterInNameE;
|
|
f.text = "b.txt";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/b.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
|
|
// Open of a missing file asks to create it: Yes accepts, No stays.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "zz.txt";
|
|
f.nestedButton = "Yes";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/zz.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
TEST_STR_EQ(f.nestedTitle, "Confirm Create");
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "zz.txt";
|
|
f.nestedButton = "No";
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_TRUE(f.stillModalAfter);
|
|
TEST_TRUE(f.nestedSeen);
|
|
|
|
// Save over an existing file asks to replace it.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "a.txt";
|
|
f.nestedButton = "Yes";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/a.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
TEST_STR_EQ(f.nestedTitle, "Confirm Save");
|
|
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "a.txt";
|
|
f.nestedButton = "No";
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_TRUE(f.stillModalAfter);
|
|
|
|
// Save of a new name without extension appends the filter's extension.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "fresh";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, sFiltersTxt, 2, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/fresh.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
TEST_TRUE(!f.nestedSeen);
|
|
|
|
// Save with a wildcard filter leaves the name alone.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "plain";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, &sFiltersTxt[1], 1, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/plain", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
|
|
// A name inside a missing folder: error box, then the dialog is
|
|
// still up.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "bad/name";
|
|
f.nestedButton = "OK";
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.nestedTitle, "Invalid Path");
|
|
TEST_TRUE(f.stillModalAfter);
|
|
|
|
// A name with a folder part navigates there and accepts the bare
|
|
// name in it (it used to be rejected as an invalid filename).
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "sub/new.txt";
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
snprintf(expect, sizeof(expect), "%s/sub/new.txt", dir);
|
|
TEST_STR_EQ(out, expect);
|
|
TEST_TRUE(!f.nestedSeen);
|
|
|
|
// An absolute path in the name box works the same way.
|
|
char absName[DVX_MAX_PATH];
|
|
snprintf(absName, sizeof(absName), "%s/sub/abs.txt", dir);
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = absName;
|
|
out[0] = '\0';
|
|
TEST_TRUE(dvxFileDialog(tCtx(), "Save It", FD_SAVE, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(out, absName);
|
|
|
|
// Selecting a directory and pressing Open navigates, never accepts.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpSelectDirOpenE;
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.pathAfter, sub);
|
|
TEST_TRUE(f.stillModalAfter);
|
|
|
|
// Typing a directory name and pressing Open navigates too.
|
|
memset(&f, 0, sizeof(f));
|
|
f.op = FileOpTypeNameE;
|
|
f.text = "sub";
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "Open It", FD_OPEN, dir, NULL, 0, out, sizeof(out)));
|
|
TEST_STR_EQ(f.pathAfter, sub);
|
|
TEST_TRUE(f.stillModalAfter);
|
|
TEST_TRUE(!f.nestedSeen);
|
|
|
|
// Bad arguments
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "X", FD_OPEN, dir, NULL, 0, NULL, 0));
|
|
TEST_TRUE(!dvxFileDialog(tCtx(), "X", FD_OPEN, dir, NULL, 0, out, 0));
|
|
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Open It"), NULL);
|
|
TEST_PTR_EQ(tFindWindow("Save It"), NULL);
|
|
TEST_PTR_EQ(tCtx()->modalWindow, NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseGeometry(int32_t w, int32_t h) {
|
|
tCase("dialogs are centered");
|
|
tInit(w, h, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
char dir[DVX_MAX_PATH];
|
|
char out[OUT_PATH_LEN];
|
|
char buf[TEXT_MAX_LEN];
|
|
int32_t val = 0;
|
|
int32_t idx = 0;
|
|
|
|
fixtureTree(dir, sizeof(dir));
|
|
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Geo", "Centered?", MB_OK | MB_ICONINFO);
|
|
TEST_TRUE(abs(s.winX + s.winW / 2 - w / 2) <= CENTER_TOLERANCE);
|
|
TEST_TRUE(abs(s.winY + s.winH / 2 - h / 2) <= CENTER_TOLERANCE);
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxInputBox(tCtx(), "Geo", "Prompt", NULL, buf, sizeof(buf));
|
|
TEST_TRUE(abs(s.winX + s.winW / 2 - w / 2) <= CENTER_TOLERANCE);
|
|
TEST_TRUE(abs(s.winY + s.winH / 2 - h / 2) <= CENTER_TOLERANCE);
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxIntInputBox(tCtx(), "Geo", "Prompt", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val);
|
|
TEST_TRUE(abs(s.winX + s.winW / 2 - w / 2) <= CENTER_TOLERANCE);
|
|
TEST_TRUE(abs(s.winY + s.winH / 2 - h / 2) <= CENTER_TOLERANCE);
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxChoiceDialog(tCtx(), "Geo", "Prompt", sChoices, CHOICE_COUNT, 0, &idx);
|
|
TEST_TRUE(abs(s.winX + s.winW / 2 - w / 2) <= CENTER_TOLERANCE);
|
|
TEST_TRUE(abs(s.winY + s.winH / 2 - h / 2) <= CENTER_TOLERANCE);
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxFileDialog(tCtx(), "Geo", FD_OPEN, dir, NULL, 0, out, sizeof(out));
|
|
TEST_TRUE(abs(s.winX + s.winW / 2 - w / 2) <= CENTER_TOLERANCE);
|
|
TEST_TRUE(abs(s.winY + s.winH / 2 - h / 2) <= CENTER_TOLERANCE);
|
|
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseGolden(void) {
|
|
tCase("message box golden crop (32bpp)");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, "OK");
|
|
s.goldenName = GOLDEN_MSGBOX;
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Golden", "Golden crop of a message box.", MB_OKCANCEL | MB_ICONINFO), ID_OK);
|
|
TEST_TRUE(s.calls >= 1);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseInputBox(void) {
|
|
tCase("input box");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
char buf[TEXT_MAX_LEN];
|
|
char small[SMALL_BUF_LEN];
|
|
|
|
// Default text is returned untouched by OK.
|
|
scriptInit(&s, "OK");
|
|
strcpy(buf, "old");
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Ask", "Name?", "default", buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "default");
|
|
TEST_STR_EQ(s.captured, "default");
|
|
|
|
// No default: empty string on OK.
|
|
scriptInit(&s, "OK");
|
|
strcpy(buf, "old");
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Ask", "Name?", NULL, buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "");
|
|
|
|
// Typed text (the input has focus) is appended and accepted by OK.
|
|
scriptInit(&s, "OK");
|
|
s.typeText = " Jr.";
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Ask", "Name?", "Scott", buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "Scott Jr.");
|
|
|
|
// Enter accepts, Escape cancels, close gadget cancels.
|
|
scriptInit(&s, NULL);
|
|
s.typeText = "typed";
|
|
s.pressEnter = true;
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Ask", "Name?", NULL, buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "typed");
|
|
|
|
scriptInit(&s, NULL);
|
|
s.typeText = "lost";
|
|
s.pressEsc = true;
|
|
strcpy(buf, "kept");
|
|
TEST_TRUE(!dvxInputBox(tCtx(), "Ask", "Name?", NULL, buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "kept");
|
|
|
|
scriptInit(&s, "Cancel");
|
|
TEST_TRUE(!dvxInputBox(tCtx(), "Ask", "Name?", "x", buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "kept");
|
|
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_TRUE(!dvxInputBox(tCtx(), "Ask", "Name?", "x", buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "kept");
|
|
|
|
// Buffer bound: result is truncated and terminated.
|
|
scriptInit(&s, "OK");
|
|
memset(small, 'Z', sizeof(small));
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Ask", "Name?", "longer than four", small, sizeof(small)));
|
|
TEST_STR_EQ(small, "lon");
|
|
|
|
// Default title when NULL
|
|
scriptInit(&s, "Cancel");
|
|
dvxInputBox(tCtx(), NULL, "Name?", NULL, buf, sizeof(buf));
|
|
TEST_STR_EQ(s.title, "Input");
|
|
|
|
// Bad arguments
|
|
TEST_TRUE(!dvxInputBox(tCtx(), "Ask", "Name?", NULL, NULL, 0));
|
|
TEST_TRUE(!dvxInputBox(tCtx(), "Ask", "Name?", NULL, buf, 0));
|
|
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Ask"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseIntInputBox(void) {
|
|
tCase("integer input box");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
int32_t val = -1;
|
|
|
|
// Default value through OK.
|
|
spinInit(&s, "OK");
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_DEFAULT);
|
|
|
|
// Up arrow twice (step 2), then the value is clamped at max on the third.
|
|
spinInit(&s, "OK");
|
|
s.capturedInt = 3;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_MAX);
|
|
|
|
spinInit(&s, "OK");
|
|
s.capturedInt = 1;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_DEFAULT + SPIN_STEP);
|
|
|
|
// Down arrows clamp at min.
|
|
spinInit(&s, "OK");
|
|
s.capturedInt = -4;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_MIN);
|
|
|
|
// Keyboard Up/Down on the focused spinner.
|
|
spinInit(&s, "OK");
|
|
s.capturedInt2 = 1;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_DEFAULT + SPIN_STEP);
|
|
|
|
// Typed out-of-range values are clamped on Enter (commit + default OK).
|
|
spinInit(&s, NULL);
|
|
s.typeText = SPIN_OVERRANGE_TEXT;
|
|
s.pressEnter = true;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, SPIN_MAX);
|
|
|
|
spinInit(&s, "OK");
|
|
s.typeText = SPIN_UNDERRANGE_TEXT;
|
|
TEST_TRUE(dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, -SPIN_MAX, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, -SPIN_MAX);
|
|
|
|
// Cancel / Escape / close leave the output alone.
|
|
val = 42;
|
|
spinInit(&s, "Cancel");
|
|
TEST_TRUE(!dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
spinInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_TRUE(!dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
spinInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_TRUE(!dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, &val));
|
|
TEST_EQ(val, 42);
|
|
|
|
TEST_TRUE(!dvxIntInputBox(tCtx(), "Int", "How many?", SPIN_DEFAULT, SPIN_MIN, SPIN_MAX, SPIN_STEP, NULL));
|
|
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Int"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseMessageBoxMatrix(void) {
|
|
tCase("message box button sets x icons");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
int32_t setCount = DVX_ARRAY_LEN(sButtonSets);
|
|
int32_t iconCount = DVX_ARRAY_LEN(sIcons);
|
|
|
|
for (int32_t si = 0; si < setCount; si++) {
|
|
const ButtonSetT *set = &sButtonSets[si];
|
|
|
|
for (int32_t ii = 0; ii < iconCount; ii++) {
|
|
int32_t flags = set->flags | sIcons[ii];
|
|
|
|
for (int32_t b = 0; b < set->count; b++) {
|
|
scriptInit(&s, set->labels[b]);
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Matrix", "Pick a button", flags), set->ids[b]);
|
|
TEST_EQ(s.calls, 1);
|
|
TEST_STR_EQ(s.title, "Matrix");
|
|
}
|
|
|
|
// Enter activates the first (default) button.
|
|
scriptInit(&s, NULL);
|
|
s.pressEnter = true;
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Matrix", "Enter", flags), set->ids[0]);
|
|
|
|
// Escape activates the last button (OK / Cancel / No).
|
|
scriptInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Matrix", "Escape", flags), set->ids[set->count - 1]);
|
|
|
|
// The close gadget always reports cancel.
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Matrix", "Close", flags), ID_CANCEL);
|
|
}
|
|
}
|
|
|
|
// Unknown button nibble falls back to a lone OK.
|
|
scriptInit(&s, "OK");
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Matrix", "Odd", MB_BUTTON_MASK), ID_OK);
|
|
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Matrix"), NULL);
|
|
TEST_EQ(tCtx()->stack.count, 0);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseModality(void) {
|
|
tCase("modal input gate and focus return");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
OtherWindowT o = otherWindow("Other");
|
|
TSpyT spy;
|
|
ScriptT s;
|
|
|
|
tSpyInit(&spy);
|
|
tSpyWidget(&spy, o.btn, "onClick");
|
|
tSpyWindow(&spy, o.win, "onFocus");
|
|
tSpyWindow(&spy, o.win, "onBlur");
|
|
TEST_TRUE(o.win->focused);
|
|
|
|
scriptInit(&s, "OK");
|
|
s.modalSeen = o.win; // script pokes this window while the modal is up
|
|
TEST_EQ(dvxMessageBox(tCtx(), "Modal", "Wait", MB_OK), ID_OK);
|
|
|
|
// Nothing reached the other window: no click, no typed text, no raise.
|
|
TEST_EQ(tSpyCount(&spy, "onClick"), 0);
|
|
TEST_STR_EQ(wgtGetText(o.input), "");
|
|
TEST_EQ(s.capturedInt, 1); // modal stayed on top after the clicks
|
|
TEST_EQ(s.capturedInt2, 1); // modal stayed focused after the clicks
|
|
|
|
// Focus came back to the previous window once the dialog closed.
|
|
tPump(1);
|
|
TEST_TRUE(o.win->focused);
|
|
TEST_EQ(tSpyCount(&spy, "onBlur"), 1);
|
|
TEST_EQ(tSpyCount(&spy, "onFocus"), 1);
|
|
TEST_PTR_EQ(tCtx()->modalWindow, NULL);
|
|
|
|
// With the dialog gone, the same clicks work again.
|
|
int32_t sx = 0;
|
|
int32_t sy = 0;
|
|
|
|
tWidgetCenter(o.btn, &sx, &sy);
|
|
tClick(sx, sy);
|
|
TEST_EQ(tSpyCount(&spy, "onClick"), 1);
|
|
|
|
tSpyFree(&spy);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseNested(void) {
|
|
tCase("nested dialog restores the previous modal");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
char buf[TEXT_MAX_LEN];
|
|
|
|
scriptInit(&s, "OK");
|
|
tDialogScript(nestedScript, &s);
|
|
TEST_TRUE(dvxInputBox(tCtx(), "Outer", "Name?", "outer", buf, sizeof(buf)));
|
|
TEST_STR_EQ(buf, "outer");
|
|
TEST_EQ(s.nestedResult, ID_YES);
|
|
TEST_PTR_EQ(s.prevModalAfterNested, s.modalSeen);
|
|
TEST_PTR_EQ(tCtx()->modalWindow, NULL);
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Outer"), NULL);
|
|
TEST_PTR_EQ(tFindWindow("Inner"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// Two message boxes alive at once (a second dvxMessageBox while the
|
|
// first one's loop runs) keep separate state: the inner answer must not
|
|
// end or overwrite the outer box.
|
|
static void caseNestedSameType(void) {
|
|
tCase("nested message boxes keep separate state");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, NULL);
|
|
tDialogScript(nestedSameTypeScript, &s);
|
|
|
|
int32_t outer = dvxMessageBox(tCtx(), "Outer", "Outer text", MB_OKCANCEL);
|
|
|
|
TEST_EQ(s.nestedResult, ID_YES);
|
|
TEST_EQ(outer, ID_CANCEL);
|
|
TEST_EQ(s.calls, 2);
|
|
TEST_PTR_EQ(tCtx()->modalWindow, NULL);
|
|
tPump(1);
|
|
TEST_PTR_EQ(tFindWindow("Outer"), NULL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void casePromptSave(void) {
|
|
tCase("prompt save");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
|
|
scriptInit(&s, "Yes");
|
|
TEST_EQ(dvxPromptSave(tCtx(), "Doc"), DVX_SAVE_YES);
|
|
TEST_STR_EQ(s.title, "Doc");
|
|
scriptInit(&s, "No");
|
|
TEST_EQ(dvxPromptSave(tCtx(), "Doc"), DVX_SAVE_NO);
|
|
scriptInit(&s, "Cancel");
|
|
TEST_EQ(dvxPromptSave(tCtx(), NULL), DVX_SAVE_CANCEL);
|
|
TEST_STR_EQ(s.title, "Save");
|
|
scriptInit(&s, NULL);
|
|
s.pressEsc = true;
|
|
TEST_EQ(dvxPromptSave(tCtx(), "Doc"), DVX_SAVE_CANCEL);
|
|
scriptInit(&s, NULL);
|
|
s.pressEnter = true;
|
|
TEST_EQ(dvxPromptSave(tCtx(), "Doc"), DVX_SAVE_YES);
|
|
scriptInit(&s, NULL);
|
|
s.closeGadget = true;
|
|
TEST_EQ(dvxPromptSave(tCtx(), "Doc"), DVX_SAVE_CANCEL);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// Message boxes play their icon's sound (dialogs no window sounds);
|
|
// windows play open (a frame later) and close; the API calls reach the
|
|
// hook.
|
|
static void caseSounds(void) {
|
|
tCase("desktop sounds: message boxes, windows, dvxBeep");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
dvxSetSoundHook(soundRecord);
|
|
|
|
ScriptT s;
|
|
|
|
for (int32_t i = 0; i < DVX_ARRAY_LEN(sIcons); i++) {
|
|
sSoundCount = 0;
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Sounds", "Listen", MB_OK | sIcons[i]);
|
|
tPump(1);
|
|
TEST_EQ(sSoundCount, 1);
|
|
TEST_EQ(sSoundLog[0], sIconSounds[i]);
|
|
TEST_TRUE(!sSoundWait[0]);
|
|
}
|
|
|
|
sSoundCount = 0;
|
|
|
|
WindowT *win = dvxCreateWindow(tCtx(), "Plain", OTHER_WIN_X, OTHER_WIN_Y, OTHER_WIN_W, OTHER_WIN_H, false);
|
|
|
|
TEST_EQ(sSoundCount, 0);
|
|
tPump(1);
|
|
TEST_EQ(sSoundCount, 1);
|
|
TEST_EQ(sSoundLog[0], SoundWindowOpenE);
|
|
tPump(1);
|
|
TEST_EQ(sSoundCount, 1);
|
|
dvxDestroyWindow(tCtx(), win);
|
|
TEST_EQ(sSoundCount, 2);
|
|
TEST_EQ(sSoundLog[1], SoundWindowCloseE);
|
|
|
|
// Opened and closed in one frame: only the close is heard.
|
|
sSoundCount = 0;
|
|
win = dvxCreateWindow(tCtx(), "Brief", OTHER_WIN_X, OTHER_WIN_Y, OTHER_WIN_W, OTHER_WIN_H, false);
|
|
dvxDestroyWindow(tCtx(), win);
|
|
tPump(1);
|
|
TEST_EQ(sSoundCount, 1);
|
|
TEST_EQ(sSoundLog[0], SoundWindowCloseE);
|
|
|
|
sSoundCount = 0;
|
|
dvxBeep();
|
|
dvxPlaySoundWait(SoundExitE);
|
|
TEST_EQ(sSoundCount, 2);
|
|
TEST_EQ(sSoundLog[0], SoundDefaultE);
|
|
TEST_EQ(sSoundLog[1], SoundExitE);
|
|
TEST_TRUE(sSoundWait[1]);
|
|
|
|
// Without a hook nothing happens.
|
|
dvxSetSoundHook(NULL);
|
|
sSoundCount = 0;
|
|
dvxBeep();
|
|
TEST_EQ(sSoundCount, 0);
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
static void caseWrapping(void) {
|
|
tCase("long message wrapping");
|
|
tInit(SCREEN_W, SCREEN_H, SCREEN_BPP);
|
|
|
|
ScriptT s;
|
|
int32_t lineH = tCtx()->font.charHeight;
|
|
int32_t baseH = 0;
|
|
|
|
// One line: contentH = lineH + 2*pad + button + 3*pad
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "short", MB_OK);
|
|
baseH = s.contentH;
|
|
TEST_EQ(baseH, lineH + MSG_PADDING * 2 + BUTTON_HEIGHT + MSG_PADDING * 3);
|
|
|
|
// 30 + 30 + 10 characters with spaces: greedy wrap at 38 columns -> 3 lines.
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb cccccccccc", MB_OK);
|
|
TEST_EQ((s.contentH - baseH) / lineH + 1, 3);
|
|
|
|
// Explicit newlines count as lines.
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "one\ntwo\nthree\nfour", MB_OK);
|
|
TEST_EQ((s.contentH - baseH) / lineH + 1, 4);
|
|
|
|
// With an icon the message area is at least the icon area tall.
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "short", MB_OK | MB_ICONWARNING);
|
|
TEST_TRUE(s.contentH > baseH);
|
|
|
|
// A 93-character word is broken at the 38-column limit (3 lines)
|
|
// and never widens the box.
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", MB_OK);
|
|
TEST_EQ((s.contentH - baseH) / lineH + 1, 3);
|
|
TEST_EQ(s.winW, MSG_MAX_WIDTH_FRAME);
|
|
|
|
// A line that ends exactly on a space fits whole: 20 + 1 + 17 = 38
|
|
// columns, then a 37-character word -> 2 lines (not 3).
|
|
scriptInit(&s, "OK");
|
|
dvxMessageBox(tCtx(), "Wrap", "aaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbb ccccccccccccccccccccccccccccccccccccc", MB_OK);
|
|
TEST_EQ((s.contentH - baseH) / lineH + 1, 2);
|
|
|
|
// Input prompts wrap too: '\n' starts a new label line and a long
|
|
// prompt never widens the dialog past the wrap width.
|
|
char buf[TEXT_MAX_LEN];
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxInputBox(tCtx(), "Prompt", "first\nsecond", NULL, buf, sizeof(buf));
|
|
TEST_TRUE(s.winW <= IB_WIDTH_FRAME);
|
|
|
|
int32_t twoLineH = s.contentH;
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxInputBox(tCtx(), "Prompt", "first", NULL, buf, sizeof(buf));
|
|
TEST_TRUE(twoLineH > s.contentH);
|
|
|
|
scriptInit(&s, "Cancel");
|
|
dvxInputBox(tCtx(), "Prompt", "a long prompt that goes on and on well beyond the width of any sensible input dialog box", NULL, buf, sizeof(buf));
|
|
TEST_TRUE(s.winW <= IB_WIDTH_FRAME);
|
|
|
|
tShutdown();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// Helpers
|
|
// ============================================================
|
|
|
|
static WidgetT *childAt(WidgetT *parent, int32_t idx) {
|
|
WidgetT *c = parent ? parent->firstChild : NULL;
|
|
|
|
while (c && idx > 0) {
|
|
c = c->nextSibling;
|
|
idx--;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
|
|
// Find the close gadget by scanning the title bar with the WM hit test.
|
|
// The gadget is Motif-style: one click opens the system menu, a
|
|
// double-click closes the window.
|
|
static void clickCloseGadget(WindowT *win) {
|
|
int32_t y = win->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT / 2;
|
|
|
|
for (int32_t x = win->x; x < win->x + win->w; x++) {
|
|
int32_t part = HIT_NONE;
|
|
int32_t idx = wmHitTest(&tCtx()->stack, x, y, &part);
|
|
|
|
if (idx >= 0 && tCtx()->stack.windows[idx] == win && part == HIT_CLOSE) {
|
|
tAdvanceClock(SINGLE_CLICK_GAP_MS);
|
|
tClick(x, y);
|
|
tAdvanceClock(DBLCLICK_GAP_MS);
|
|
tClick(x, y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
TEST_FAIL("close gadget not found");
|
|
}
|
|
|
|
|
|
// Click (or double-click, with a clock gap so the second press counts)
|
|
// on the given visible row of a list box.
|
|
static void clickListRow(WidgetT *lb, int32_t row, bool dbl) {
|
|
int32_t cx = 0;
|
|
int32_t cy = 0;
|
|
|
|
tWidgetCenter(lb, &cx, &cy);
|
|
|
|
int32_t x = cx - lb->w / 4;
|
|
int32_t y = cy - lb->h / 2 + LISTBOX_BORDER + row * tCtx()->font.charHeight + tCtx()->font.charHeight / 2;
|
|
|
|
tAdvanceClock(SINGLE_CLICK_GAP_MS);
|
|
tClick(x, y);
|
|
|
|
if (dbl) {
|
|
tAdvanceClock(DBLCLICK_GAP_MS);
|
|
tClick(x, y);
|
|
}
|
|
}
|
|
|
|
|
|
static void clickSpinnerArrow(WidgetT *sp, bool up) {
|
|
int32_t cx = 0;
|
|
int32_t cy = 0;
|
|
|
|
tWidgetCenter(sp, &cx, &cy);
|
|
|
|
int32_t x = cx + sp->w / 2 - SPINNER_BORDER - SPINNER_BTN_W / 2;
|
|
int32_t y = up ? cy - sp->h / 4 : cy + sp->h / 4;
|
|
|
|
tAdvanceClock(SINGLE_CLICK_GAP_MS);
|
|
tClick(x, y);
|
|
}
|
|
|
|
|
|
// Color dialog script: checks the opening state, then acts per op.
|
|
static void colorScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
ColorScriptT *cs = (ColorScriptT *)userCtx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
cs->calls++;
|
|
|
|
if (cs->calls > 1) {
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
WidgetT *root = modal->widgetRoot;
|
|
WidgetT *palette = childAt(root, 0);
|
|
WidgetT *redRow = childAt(root, COLOR_ROW_RED);
|
|
WidgetT *greenRow = childAt(root, COLOR_ROW_GREEN);
|
|
WidgetT *preview = childAt(root, COLOR_IDX_PREVIEW);
|
|
uint8_t r = (uint8_t)((cs->start >> COLOR_SHIFT_R) & COLOR_MASK);
|
|
uint8_t g = (uint8_t)((cs->start >> COLOR_SHIFT_G) & COLOR_MASK);
|
|
uint8_t b = (uint8_t)(cs->start & COLOR_MASK);
|
|
int32_t sx = 0;
|
|
int32_t sy = 0;
|
|
|
|
cs->previewOk = wgtCanvasGetPixel(preview, 0, 0) == packColor(&ctx->display, r, g, b);
|
|
cs->redFocused = wgtGetFocused() == childAt(redRow, COLOR_IDX_SLIDER);
|
|
|
|
switch (cs->op) {
|
|
case ColorOpOkE:
|
|
TEST_TRUE(tDialogClickButton(modal, "OK"));
|
|
break;
|
|
|
|
case ColorOpCancelE:
|
|
TEST_TRUE(tDialogClickButton(modal, "Cancel"));
|
|
break;
|
|
|
|
case ColorOpEscE:
|
|
tKeyEsc();
|
|
break;
|
|
|
|
case ColorOpSwatchE:
|
|
tWidgetCenter(palette, &sx, &sy);
|
|
tAdvanceClock(SINGLE_CLICK_GAP_MS);
|
|
tClick(sx + COLOR_SWATCH_DX, sy + COLOR_SWATCH_DY);
|
|
TEST_TRUE(tDialogClickButton(modal, "OK"));
|
|
break;
|
|
|
|
case ColorOpKeysE: {
|
|
WidgetT *greenBox = childAt(greenRow, COLOR_IDX_SPINNER);
|
|
|
|
tKey(KEY_END & ~KEY_EXT_FLAG, 0, 0);
|
|
TEST_EQ(wgtSpinnerGetValue(childAt(redRow, COLOR_IDX_SPINNER)), COLOR_CHANNEL_MAX);
|
|
wgtSetFocused(greenBox);
|
|
tKey(KEY_END & ~KEY_EXT_FLAG, 0, 0);
|
|
|
|
for (int32_t i = 0; i < BACKSPACE_CLEAR_COUNT; i++) {
|
|
tKeyBackspace();
|
|
}
|
|
|
|
tType(COLOR_GREEN_TEXT);
|
|
cs->greenAfter = wgtSliderGetValue(childAt(greenRow, COLOR_IDX_SLIDER));
|
|
tKeyEnter();
|
|
break;
|
|
}
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
// A button of the caller's own: ends the dialog, accepted when its
|
|
// userData is set.
|
|
static void customEnd(WidgetT *w) {
|
|
dvxDialogEnd(w->window, w->userData != NULL);
|
|
}
|
|
|
|
|
|
static bool customOk(WindowT *dlg, void *userCtx) {
|
|
CustomOkT *ok = (CustomOkT *)userCtx;
|
|
|
|
(void)dlg;
|
|
ok->calls++;
|
|
|
|
if (ok->calls <= ok->refuse) {
|
|
return false;
|
|
}
|
|
|
|
snprintf(ok->text, sizeof(ok->text), "%s", wgtGetText(ok->input));
|
|
return true;
|
|
}
|
|
|
|
|
|
// File dialog script. Layout: pathRow(label,input,label,drive
|
|
// dropdown), list, [filterRow(label,dropdown)], nameRow(label,input),
|
|
// buttons. Nested confirmation boxes (different title) are answered
|
|
// with nestedButton.
|
|
static void fileScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
FileScriptT *f = (FileScriptT *)userCtx;
|
|
|
|
(void)ctx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
bool outer = strcmp(modal->title, "Open It") == 0 || strcmp(modal->title, "Save It") == 0 || strcmp(modal->title, "Open") == 0 || strcmp(modal->title, "Save As") == 0;
|
|
|
|
if (!outer) {
|
|
f->nestedSeen = true;
|
|
snprintf(f->nestedTitle, sizeof(f->nestedTitle), "%s", modal->title);
|
|
TEST_TRUE(tDialogClickButton(modal, f->nestedButton ? f->nestedButton : "OK"));
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
WidgetT *root = modal->widgetRoot;
|
|
WidgetT *pathInput = childAt(childAt(root, 0), 1);
|
|
WidgetT *driveDd = childAt(childAt(root, 0), 3);
|
|
WidgetT *list = childAt(root, 1);
|
|
bool hasFilter = childAt(root, 4) != NULL;
|
|
WidgetT *filterDd = hasFilter ? childAt(childAt(root, 2), 1) : NULL;
|
|
WidgetT *nameInput = childAt(childAt(root, hasFilter ? 3 : 2), 1);
|
|
WidgetT *okBtn = childAt(childAt(root, hasFilter ? 4 : 3), 0);
|
|
int32_t sx = 0;
|
|
int32_t sy = 0;
|
|
|
|
TEST_TRUE(pathInput && driveDd && list && nameInput && okBtn);
|
|
|
|
if (!pathInput || !driveDd || !list || !nameInput || !okBtn) {
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
// Step 1: the dialog survived the action. The modal loop only
|
|
// notices 'done' after this frame, so the state is captured on the
|
|
// NEXT idle call rather than right after the action.
|
|
if (f->acted) {
|
|
f->stillModalAfter = true;
|
|
f->itemsAfter = dvxListBoxApi()->getItemCount(list);
|
|
f->driveAfter = wgtDropdownGetSelected(driveDd);
|
|
snprintf(f->pathAfter, sizeof(f->pathAfter), "%s", wgtGetText(pathInput));
|
|
tDialogClickButton(modal, "Cancel");
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
f->acted = true;
|
|
f->itemsBefore = dvxListBoxApi()->getItemCount(list);
|
|
f->driveItems = wgtDropdownGetItemCount(driveDd);
|
|
f->driveBefore = wgtDropdownGetSelected(driveDd);
|
|
snprintf(f->driveLabel, sizeof(f->driveLabel), "%s", wgtDropdownGetItem(driveDd, 0));
|
|
|
|
switch (f->op) {
|
|
case FileOpCancelE:
|
|
snprintf(f->pathAfter, sizeof(f->pathAfter), "%s", wgtGetText(pathInput));
|
|
tDialogClickButton(modal, "Cancel");
|
|
break;
|
|
|
|
case FileOpEscE:
|
|
tKeyEsc();
|
|
break;
|
|
|
|
case FileOpCloseE:
|
|
clickCloseGadget(modal);
|
|
break;
|
|
|
|
case FileOpDblClickSubE:
|
|
clickListRow(list, FIXTURE_IDX_SUB, true);
|
|
break;
|
|
|
|
case FileOpTypePathE:
|
|
tWidgetCenter(pathInput, &sx, &sy);
|
|
tClick(sx, sy);
|
|
wgtSetText(pathInput, "");
|
|
tType(f->text);
|
|
tKeyEnter();
|
|
break;
|
|
|
|
case FileOpFilterNextE:
|
|
wgtSetFocused(filterDd);
|
|
tKey(KEY_SCAN_DOWN, 0, 0);
|
|
break;
|
|
|
|
case FileOpClickFileOpenE:
|
|
fileSelectRow(list, FIXTURE_IDX_ATXT);
|
|
snprintf(f->nameAfter, sizeof(f->nameAfter), "%s", wgtGetText(nameInput));
|
|
tWidgetCenter(okBtn, &sx, &sy);
|
|
tClick(sx, sy);
|
|
break;
|
|
|
|
case FileOpDblClickFileE:
|
|
clickListRow(list, FIXTURE_IDX_ATXT, true);
|
|
break;
|
|
|
|
case FileOpTypeNameE:
|
|
tWidgetCenter(nameInput, &sx, &sy);
|
|
tClick(sx, sy);
|
|
tType(f->text);
|
|
tWidgetCenter(okBtn, &sx, &sy);
|
|
tClick(sx, sy);
|
|
break;
|
|
|
|
case FileOpEnterInNameE:
|
|
tWidgetCenter(nameInput, &sx, &sy);
|
|
tClick(sx, sy);
|
|
tType(f->text);
|
|
tKeyEnter();
|
|
break;
|
|
|
|
case FileOpSelectDirOpenE:
|
|
fileSelectRow(list, FIXTURE_IDX_SUB);
|
|
TEST_STR_EQ(wgtGetText(nameInput), "");
|
|
tWidgetCenter(okBtn, &sx, &sy);
|
|
tClick(sx, sy);
|
|
break;
|
|
|
|
case FileOpDriveE:
|
|
wgtSetFocused(driveDd);
|
|
wgtDropdownSetSelected(driveDd, f->driveIdx);
|
|
driveDd->onChange(driveDd);
|
|
break;
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
// Single-click a row with the double-click window expired first.
|
|
static void fileSelectRow(WidgetT *lb, int32_t row) {
|
|
clickListRow(lb, row, false);
|
|
TEST_EQ(wgtListBoxGetSelected(lb), row);
|
|
}
|
|
|
|
|
|
// Fixture: dir/{a.txt, b.txt, c.bmp, sub/{d.txt, deep/}}
|
|
static void fixtureTree(char *dir, int32_t dirSize) {
|
|
char path[DVX_MAX_PATH * 2];
|
|
|
|
snprintf(dir, dirSize, "%s/tree", tTempDir());
|
|
mkdir(dir, 0700);
|
|
snprintf(path, sizeof(path), "%s/sub", dir);
|
|
mkdir(path, 0700);
|
|
snprintf(path, sizeof(path), "%s/sub/deep", dir);
|
|
mkdir(path, 0700);
|
|
snprintf(path, sizeof(path), "%s/a.txt", dir);
|
|
tWriteFile(path, "a", 1);
|
|
snprintf(path, sizeof(path), "%s/b.txt", dir);
|
|
tWriteFile(path, "b", 1);
|
|
snprintf(path, sizeof(path), "%s/c.bmp", dir);
|
|
tWriteFile(path, "c", 1);
|
|
snprintf(path, sizeof(path), "%s/sub/d.txt", dir);
|
|
tWriteFile(path, "d", 1);
|
|
}
|
|
|
|
|
|
// Outer message box: open a second message box from the idle script,
|
|
// answer it Yes, then cancel the outer one.
|
|
static void nestedSameTypeScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
ScriptT *s = (ScriptT *)userCtx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
if (strcmp(modal->title, "Inner") == 0) {
|
|
tDialogClickButton(modal, "Yes");
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
// First visit opens the inner box; the outer box must still be up
|
|
// on a later frame, where it is cancelled.
|
|
s->calls++;
|
|
|
|
if (s->calls == 1) {
|
|
s->nestedResult = dvxMessageBox(ctx, "Inner", "Inner text", MB_YESNO);
|
|
TEST_PTR_EQ(ctx->modalWindow, modal);
|
|
} else {
|
|
tDialogClickButton(modal, "Cancel");
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
// Outer input box: open a nested message box from the idle script,
|
|
// answer it, and record which modal was restored afterwards.
|
|
static void nestedScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
ScriptT *s = (ScriptT *)userCtx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
if (strcmp(modal->title, "Inner") == 0) {
|
|
TEST_PTR_EQ(ctx->modalWindow, modal);
|
|
TEST_TRUE(modal->focused);
|
|
tDialogClickButton(modal, "Yes");
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
scriptRecord(s, modal);
|
|
|
|
if (s->calls == 1) {
|
|
s->nestedResult = dvxMessageBox(ctx, "Inner", "Nested?", MB_YESNO | MB_ICONQUESTION);
|
|
s->prevModalAfterNested = ctx->modalWindow;
|
|
TEST_TRUE(modal->focused);
|
|
tDialogClickButton(modal, s->button);
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
static OtherWindowT otherWindow(const char *title) {
|
|
OtherWindowT o;
|
|
|
|
memset(&o, 0, sizeof(o));
|
|
o.win = dvxCreateWindow(tCtx(), title, OTHER_WIN_X, OTHER_WIN_Y, OTHER_WIN_W, OTHER_WIN_H, false);
|
|
|
|
WidgetT *root = wgtInitWindow(tCtx(), o.win);
|
|
WidgetT *box = wgtVBox(root);
|
|
|
|
o.btn = wgtButton(box, "Press");
|
|
o.input = wgtTextInput(box, TEXT_MAX_LEN);
|
|
wgtSetName(o.btn, "press");
|
|
wgtSetName(o.input, "input");
|
|
wgtInvalidate(root);
|
|
tPump(SETTLE_FRAMES);
|
|
return o;
|
|
}
|
|
|
|
|
|
// Re-entrancy guard keyed by modal window: the same script may be
|
|
// invoked again for the same modal from a nested pump; refuse that, but
|
|
// let it run for a different (nested) modal.
|
|
static bool scriptEnter(WindowT *modal) {
|
|
for (int32_t i = 0; i < sBusyDepth; i++) {
|
|
if (sBusy[i] == modal) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (sBusyDepth >= BUSY_STACK_MAX) {
|
|
return false;
|
|
}
|
|
|
|
sBusy[sBusyDepth++] = modal;
|
|
return true;
|
|
}
|
|
|
|
|
|
static void scriptInit(ScriptT *s, const char *button) {
|
|
memset(s, 0, sizeof(*s));
|
|
s->button = button;
|
|
tDialogScript(uniformScript, s);
|
|
}
|
|
|
|
|
|
static void scriptLeave(void) {
|
|
if (sBusyDepth > 0) {
|
|
sBusyDepth--;
|
|
}
|
|
}
|
|
|
|
|
|
static void scriptRecord(ScriptT *s, WindowT *modal) {
|
|
s->calls++;
|
|
snprintf(s->title, sizeof(s->title), "%s", modal->title);
|
|
s->winX = modal->x;
|
|
s->winY = modal->y;
|
|
s->winW = modal->w;
|
|
s->winH = modal->h;
|
|
s->contentH = modal->contentH;
|
|
s->modalSeen = modal;
|
|
}
|
|
|
|
|
|
static void soundRecord(DvxSoundE event, bool wait) {
|
|
if (sSoundCount < SOUND_LOG_MAX) {
|
|
sSoundLog[sSoundCount] = event;
|
|
sSoundWait[sSoundCount] = wait;
|
|
sSoundCount++;
|
|
}
|
|
}
|
|
|
|
|
|
static void spinInit(ScriptT *s, const char *button) {
|
|
scriptInit(s, button);
|
|
tDialogScript(spinScript, s);
|
|
}
|
|
|
|
|
|
// Int input box: layout label, spinner, buttons. capturedInt = arrow
|
|
// clicks (+up / -down), capturedInt2 = keyboard Up presses, typeText =
|
|
// replace the text after End + backspaces.
|
|
static void spinScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
ScriptT *s = (ScriptT *)userCtx;
|
|
|
|
(void)ctx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
scriptRecord(s, modal);
|
|
|
|
if (s->calls > 1) {
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
WidgetT *spinner = childAt(modal->widgetRoot, 1);
|
|
|
|
TEST_PTR_EQ(wgtGetFocused(), spinner);
|
|
|
|
for (int32_t i = 0; i < s->capturedInt; i++) {
|
|
clickSpinnerArrow(spinner, true);
|
|
}
|
|
|
|
for (int32_t i = 0; i > s->capturedInt; i--) {
|
|
clickSpinnerArrow(spinner, false);
|
|
}
|
|
|
|
for (int32_t i = 0; i < s->capturedInt2; i++) {
|
|
tKey(KEY_SCAN_UP, 0, 0);
|
|
}
|
|
|
|
if (s->typeText) {
|
|
wgtSetFocused(spinner);
|
|
tKey(KEY_END & ~KEY_EXT_FLAG, 0, 0);
|
|
|
|
for (int32_t i = 0; i < BACKSPACE_CLEAR_COUNT; i++) {
|
|
tKeyBackspace();
|
|
}
|
|
|
|
tType(s->typeText);
|
|
}
|
|
|
|
if (s->pressEnter) {
|
|
tKeyEnter();
|
|
} else if (s->pressEsc) {
|
|
tKeyEsc();
|
|
} else if (s->closeGadget) {
|
|
clickCloseGadget(modal);
|
|
} else if (s->button) {
|
|
TEST_TRUE(tDialogClickButton(modal, s->button));
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
// Generic script for message/input/choice boxes and geometry checks.
|
|
// Nested dialogs (a different title than the first one seen) get
|
|
// nestedButton. For the choice dialog capturedInt2 > 0 presses Down
|
|
// that many times, < 0 double-clicks row (-capturedInt2 - 1).
|
|
static void uniformScript(AppContextT *ctx, WindowT *modal, void *userCtx) {
|
|
ScriptT *s = (ScriptT *)userCtx;
|
|
|
|
if (!scriptEnter(modal)) {
|
|
return;
|
|
}
|
|
|
|
if (s->calls > 0 && s->modalSeen != modal && s->nestedButton) {
|
|
tDialogClickButton(modal, s->nestedButton);
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
if (s->calls > 0 && s->modalSeen == modal) {
|
|
// Second visit to the same dialog (e.g. OK refused): fall back.
|
|
s->calls++;
|
|
|
|
if (s->nestedButton) {
|
|
tDialogClickButton(modal, s->nestedButton);
|
|
}
|
|
|
|
scriptLeave();
|
|
return;
|
|
}
|
|
|
|
// caseModality passes the other window in modalSeen before the
|
|
// first call; poke it before recording the modal.
|
|
WindowT *other = s->modalSeen;
|
|
|
|
scriptRecord(s, modal);
|
|
|
|
if (other) {
|
|
int32_t sx = 0;
|
|
int32_t sy = 0;
|
|
|
|
tWidgetCenter(tFind(other, "press"), &sx, &sy);
|
|
tClick(sx, sy);
|
|
tWidgetCenter(tFind(other, "input"), &sx, &sy);
|
|
tClick(sx, sy);
|
|
tType("nope");
|
|
tClick(other->x + other->w / 2, other->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT / 2);
|
|
tAltTab();
|
|
s->capturedInt = (ctx->stack.windows[ctx->stack.count - 1] == modal) ? 1 : 0;
|
|
s->capturedInt2 = modal->focused ? 1 : 0;
|
|
}
|
|
|
|
if (s->goldenName) {
|
|
// Park the cursor off the dialog so the crop holds only chrome,
|
|
// icon, text and buttons.
|
|
tMouseMove(0, 0);
|
|
tGoldenRect(s->goldenName, modal->x, modal->y, modal->w, modal->h);
|
|
}
|
|
|
|
// Choice dialog: record the highlighted row and optionally move it.
|
|
WidgetT *focus = wgtGetFocused();
|
|
|
|
if (focus && strcmp(modal->title, "Pick") == 0) {
|
|
s->capturedInt = wgtListBoxGetSelected(focus);
|
|
|
|
for (int32_t i = 0; i < s->capturedInt2; i++) {
|
|
tKey(KEY_SCAN_DOWN, 0, 0);
|
|
}
|
|
|
|
if (s->capturedInt2 < 0) {
|
|
clickListRow(focus, -s->capturedInt2 - 1, true);
|
|
}
|
|
}
|
|
|
|
// Input box: the text input owns focus; capture its default text.
|
|
if (focus && strcmp(modal->title, "Ask") == 0) {
|
|
snprintf(s->captured, sizeof(s->captured), "%s", wgtGetText(focus));
|
|
}
|
|
|
|
if (s->typeText) {
|
|
tType(s->typeText);
|
|
}
|
|
|
|
if (s->abandon) {
|
|
ctx->running = false;
|
|
} else if (s->pressEnter) {
|
|
tKeyEnter();
|
|
} else if (s->pressEsc) {
|
|
tKeyEsc();
|
|
} else if (s->closeGadget) {
|
|
clickCloseGadget(modal);
|
|
} else if (s->button) {
|
|
bool found = tDialogClickButton(modal, s->button);
|
|
|
|
if (!found && s->nestedButton) {
|
|
tDialogClickButton(modal, s->nestedButton);
|
|
}
|
|
}
|
|
|
|
scriptLeave();
|
|
}
|
|
|
|
|
|
int main(void) {
|
|
platformSetLogPath("dialogsTest.log");
|
|
tSuiteBegin("T4 dialogs");
|
|
|
|
tSetGoldenDir(DIALOGS_GOLDEN_DIR);
|
|
|
|
int32_t loaded = tProcessInit(DVXTEST_WIDGET_DIR);
|
|
|
|
printf("widgets loaded: %ld\n", (long)loaded);
|
|
TEST_TRUE(loaded > 0);
|
|
|
|
caseMessageBoxMatrix();
|
|
caseErrorInfoBox();
|
|
caseInputBox();
|
|
caseIntInputBox();
|
|
caseButtonBox();
|
|
caseChoice();
|
|
caseColorDialog();
|
|
caseCustom();
|
|
casePromptSave();
|
|
caseFileDialog();
|
|
caseModality();
|
|
caseSounds();
|
|
caseNested();
|
|
caseNestedSameType();
|
|
caseAbandon();
|
|
caseGeometry(SCREEN_W, SCREEN_H);
|
|
caseGeometry(SCREEN_W_LARGE, SCREEN_H_LARGE);
|
|
caseWrapping();
|
|
caseGolden();
|
|
|
|
return tSuiteEnd();
|
|
}
|