Find/Replace
This commit is contained in:
parent
289adb8c47
commit
0ef46ff6a0
5 changed files with 731 additions and 31 deletions
|
|
@ -17,8 +17,10 @@
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "shellApp.h"
|
#include "shellApp.h"
|
||||||
#include "widgetBox.h"
|
#include "widgetBox.h"
|
||||||
|
#include "widgetCheckbox.h"
|
||||||
#include "widgetImageButton.h"
|
#include "widgetImageButton.h"
|
||||||
#include "widgetLabel.h"
|
#include "widgetLabel.h"
|
||||||
|
#include "widgetRadio.h"
|
||||||
#include "widgetTextInput.h"
|
#include "widgetTextInput.h"
|
||||||
#include "widgetDropdown.h"
|
#include "widgetDropdown.h"
|
||||||
#include "widgetButton.h"
|
#include "widgetButton.h"
|
||||||
|
|
@ -87,6 +89,9 @@
|
||||||
#define CMD_PRJ_PROPS 138
|
#define CMD_PRJ_PROPS 138
|
||||||
#define CMD_WIN_PROJECT 137
|
#define CMD_WIN_PROJECT 137
|
||||||
#define CMD_HELP_ABOUT 140
|
#define CMD_HELP_ABOUT 140
|
||||||
|
#define CMD_FIND 141
|
||||||
|
#define CMD_REPLACE 142
|
||||||
|
#define CMD_FIND_NEXT 143
|
||||||
#define IDE_MAX_IMM 1024
|
#define IDE_MAX_IMM 1024
|
||||||
#define IDE_DESIGN_W 400
|
#define IDE_DESIGN_W 400
|
||||||
#define IDE_DESIGN_H 300
|
#define IDE_DESIGN_H 300
|
||||||
|
|
@ -145,6 +150,9 @@ static void onFormWinClose(WindowT *win);
|
||||||
static void onFormWinResize(WindowT *win, int32_t newW, int32_t newH);
|
static void onFormWinResize(WindowT *win, int32_t newW, int32_t newH);
|
||||||
static void onProjectWinClose(WindowT *win);
|
static void onProjectWinClose(WindowT *win);
|
||||||
static WindowT *getLastFocusWin(void);
|
static WindowT *getLastFocusWin(void);
|
||||||
|
static void closeFindDialog(void);
|
||||||
|
static bool findInProject(const char *needle, bool caseSensitive);
|
||||||
|
static void openFindDialog(bool showReplace);
|
||||||
static void handleEditCmd(int32_t cmd);
|
static void handleEditCmd(int32_t cmd);
|
||||||
static void handleFileCmd(int32_t cmd);
|
static void handleFileCmd(int32_t cmd);
|
||||||
static void handleProjectCmd(int32_t cmd);
|
static void handleProjectCmd(int32_t cmd);
|
||||||
|
|
@ -224,6 +232,20 @@ static char **sProcBufs = NULL; // stb_ds array: one buffer per procedure
|
||||||
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
|
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
|
||||||
static int32_t sEditorFileIdx = -1; // which project file owns sProcBufs (-1=none)
|
static int32_t sEditorFileIdx = -1; // which project file owns sProcBufs (-1=none)
|
||||||
|
|
||||||
|
// Find/Replace state
|
||||||
|
static char sFindText[256] = "";
|
||||||
|
static char sReplaceText[256] = "";
|
||||||
|
// Find/Replace dialog state (modeless)
|
||||||
|
static WindowT *sFindWin = NULL;
|
||||||
|
static WidgetT *sFindInput = NULL;
|
||||||
|
static WidgetT *sReplInput = NULL;
|
||||||
|
static WidgetT *sReplCheck = NULL;
|
||||||
|
static WidgetT *sBtnReplace = NULL;
|
||||||
|
static WidgetT *sBtnReplAll = NULL;
|
||||||
|
static WidgetT *sCaseCheck = NULL;
|
||||||
|
static WidgetT *sScopeGroup = NULL; // radio group: 0=Func, 1=Obj, 2=File, 3=Proj
|
||||||
|
static WidgetT *sDirGroup = NULL; // radio group: 0=Fwd, 1=Back
|
||||||
|
|
||||||
// Procedure table for Object/Event dropdowns
|
// Procedure table for Object/Event dropdowns
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char objName[64];
|
char objName[64];
|
||||||
|
|
@ -403,6 +425,8 @@ static void activateFile(int32_t fileIdx, IdeViewModeE view) {
|
||||||
sEditorFileIdx = fileIdx;
|
sEditorFileIdx = fileIdx;
|
||||||
sProject.activeFileIdx = fileIdx;
|
sProject.activeFileIdx = fileIdx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateDirtyIndicators();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -455,6 +479,8 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
if (sProjectWin) {
|
if (sProjectWin) {
|
||||||
sProjectWin->y = toolbarBottom() + 25;
|
sProjectWin->y = toolbarBottom() + 25;
|
||||||
sProjectWin->onClose = onProjectWinClose;
|
sProjectWin->onClose = onProjectWinClose;
|
||||||
|
sProjectWin->onMenu = onMenu;
|
||||||
|
sProjectWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
char title[300];
|
char title[300];
|
||||||
|
|
@ -536,6 +562,10 @@ static void buildWindow(void) {
|
||||||
wmAddMenuItem(editMenu, "Select &All\tCtrl+A", CMD_SELECT_ALL);
|
wmAddMenuItem(editMenu, "Select &All\tCtrl+A", CMD_SELECT_ALL);
|
||||||
wmAddMenuSeparator(editMenu);
|
wmAddMenuSeparator(editMenu);
|
||||||
wmAddMenuItem(editMenu, "&Delete\tDel", CMD_DELETE);
|
wmAddMenuItem(editMenu, "&Delete\tDel", CMD_DELETE);
|
||||||
|
wmAddMenuSeparator(editMenu);
|
||||||
|
wmAddMenuItem(editMenu, "&Find...\tCtrl+F", CMD_FIND);
|
||||||
|
wmAddMenuItem(editMenu, "Find &Next\tF3", CMD_FIND_NEXT);
|
||||||
|
wmAddMenuItem(editMenu, "&Replace...\tCtrl+H", CMD_REPLACE);
|
||||||
|
|
||||||
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
|
MenuT *runMenu = wmAddMenu(menuBar, "&Run");
|
||||||
wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN);
|
wmAddMenuItem(runMenu, "&Run\tF5", CMD_RUN);
|
||||||
|
|
@ -573,6 +603,9 @@ static void buildWindow(void) {
|
||||||
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
dvxAddAccel(accel, KEY_F7, 0, CMD_VIEW_CODE);
|
||||||
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
dvxAddAccel(accel, KEY_F7, ACCEL_SHIFT, CMD_VIEW_DESIGN);
|
||||||
dvxAddAccel(accel, 0x1B, 0, CMD_STOP);
|
dvxAddAccel(accel, 0x1B, 0, CMD_STOP);
|
||||||
|
dvxAddAccel(accel, 'F', ACCEL_CTRL, CMD_FIND);
|
||||||
|
dvxAddAccel(accel, 'H', ACCEL_CTRL, CMD_REPLACE);
|
||||||
|
dvxAddAccel(accel, KEY_F3, 0, CMD_FIND_NEXT);
|
||||||
sWin->accelTable = accel;
|
sWin->accelTable = accel;
|
||||||
|
|
||||||
WidgetT *tbRoot = wgtInitWindow(sAc, sWin);
|
WidgetT *tbRoot = wgtInitWindow(sAc, sWin);
|
||||||
|
|
@ -1129,6 +1162,8 @@ static void runCached(void) {
|
||||||
static void runModule(BasModuleT *mod) {
|
static void runModule(BasModuleT *mod) {
|
||||||
setStatus("Running...");
|
setStatus("Running...");
|
||||||
|
|
||||||
|
closeFindDialog();
|
||||||
|
|
||||||
// Hide IDE windows while the program runs
|
// Hide IDE windows while the program runs
|
||||||
bool hadFormWin = sFormWin && sFormWin->visible;
|
bool hadFormWin = sFormWin && sFormWin->visible;
|
||||||
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
||||||
|
|
@ -1676,6 +1711,8 @@ static void newProject(void) {
|
||||||
if (sProjectWin) {
|
if (sProjectWin) {
|
||||||
sProjectWin->y = toolbarBottom() + 25;
|
sProjectWin->y = toolbarBottom() + 25;
|
||||||
sProjectWin->onClose = onProjectWinClose;
|
sProjectWin->onClose = onProjectWinClose;
|
||||||
|
sProjectWin->onMenu = onMenu;
|
||||||
|
sProjectWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prjRebuildTree(&sProject);
|
prjRebuildTree(&sProject);
|
||||||
|
|
@ -1722,6 +1759,8 @@ static void openProject(void) {
|
||||||
if (sProjectWin) {
|
if (sProjectWin) {
|
||||||
sProjectWin->y = toolbarBottom() + 25;
|
sProjectWin->y = toolbarBottom() + 25;
|
||||||
sProjectWin->onClose = onProjectWinClose;
|
sProjectWin->onClose = onProjectWinClose;
|
||||||
|
sProjectWin->onMenu = onMenu;
|
||||||
|
sProjectWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
prjRebuildTree(&sProject);
|
prjRebuildTree(&sProject);
|
||||||
|
|
@ -1753,6 +1792,8 @@ static void closeProject(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeFindDialog();
|
||||||
|
|
||||||
if (sProject.dirty) {
|
if (sProject.dirty) {
|
||||||
prjSave(&sProject);
|
prjSave(&sProject);
|
||||||
}
|
}
|
||||||
|
|
@ -2040,6 +2081,8 @@ static void onCodeWinClose(WindowT *win) {
|
||||||
if (sLastFocusWin == win) {
|
if (sLastFocusWin == win) {
|
||||||
sLastFocusWin = NULL;
|
sLastFocusWin = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProjectMenuState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2342,6 +2385,466 @@ static void handleFileCmd(int32_t cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Find/Replace dialog (modeless)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ScopeFuncE,
|
||||||
|
ScopeObjE,
|
||||||
|
ScopeFileE,
|
||||||
|
ScopeProjE
|
||||||
|
} FindScopeE;
|
||||||
|
|
||||||
|
|
||||||
|
static FindScopeE getFindScope(void) {
|
||||||
|
if (!sScopeGroup) {
|
||||||
|
return ScopeProjE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t idx = wgtRadioGetIndex(sScopeGroup);
|
||||||
|
|
||||||
|
switch (idx) {
|
||||||
|
case 0: return ScopeFuncE;
|
||||||
|
case 1: return ScopeObjE;
|
||||||
|
case 2: return ScopeFileE;
|
||||||
|
default: return ScopeProjE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool getFindMatchCase(void) {
|
||||||
|
return sCaseCheck && wgtCheckboxIsChecked(sCaseCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool getFindForward(void) {
|
||||||
|
if (!sDirGroup) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wgtRadioGetIndex(sDirGroup) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool isReplaceEnabled(void) {
|
||||||
|
return sReplCheck && wgtCheckboxIsChecked(sReplCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onReplCheckChange(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
bool show = isReplaceEnabled();
|
||||||
|
|
||||||
|
if (sReplInput) { sReplInput->enabled = show; }
|
||||||
|
if (sBtnReplace) { sBtnReplace->enabled = show; }
|
||||||
|
if (sBtnReplAll) { sBtnReplAll->enabled = show; }
|
||||||
|
|
||||||
|
if (sFindWin) {
|
||||||
|
dvxInvalidateWindow(sAc, sFindWin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onFindNext(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
if (!sFindInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *needle = wgtGetText(sFindInput);
|
||||||
|
|
||||||
|
if (!needle || !needle[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(sFindText, sizeof(sFindText), "%s", needle);
|
||||||
|
|
||||||
|
FindScopeE scope = getFindScope();
|
||||||
|
bool caseSens = getFindMatchCase();
|
||||||
|
bool forward = getFindForward();
|
||||||
|
|
||||||
|
if (scope == ScopeFuncE && sEditor) {
|
||||||
|
// Search current procedure only
|
||||||
|
if (!wgtTextAreaFindNext(sEditor, sFindText, caseSens, forward)) {
|
||||||
|
setStatus("Not found.");
|
||||||
|
}
|
||||||
|
} else if (scope == ScopeObjE || scope == ScopeFileE || scope == ScopeProjE) {
|
||||||
|
if (!findInProject(sFindText, caseSens)) {
|
||||||
|
setStatus("Not found.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onReplace(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
if (!sEditor || !sFindInput || !sReplInput || !isReplaceEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *needle = wgtGetText(sFindInput);
|
||||||
|
const char *repl = wgtGetText(sReplInput);
|
||||||
|
|
||||||
|
if (!needle || !needle[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(sFindText, sizeof(sFindText), "%s", needle);
|
||||||
|
snprintf(sReplaceText, sizeof(sReplaceText), "%s", repl ? repl : "");
|
||||||
|
|
||||||
|
// If text is selected and matches the search, replace it, then find next
|
||||||
|
const char *edText = wgtGetText(sEditor);
|
||||||
|
|
||||||
|
if (edText) {
|
||||||
|
// TODO: replace current selection if it matches, then find next
|
||||||
|
// For now, just do find next
|
||||||
|
onFindNext(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onReplaceAll(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
if (!sFindInput || !sReplInput || !isReplaceEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *needle = wgtGetText(sFindInput);
|
||||||
|
const char *repl = wgtGetText(sReplInput);
|
||||||
|
|
||||||
|
if (!needle || !needle[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(sFindText, sizeof(sFindText), "%s", needle);
|
||||||
|
snprintf(sReplaceText, sizeof(sReplaceText), "%s", repl ? repl : "");
|
||||||
|
|
||||||
|
bool caseSens = getFindMatchCase();
|
||||||
|
FindScopeE scope = getFindScope();
|
||||||
|
int32_t totalCount = 0;
|
||||||
|
|
||||||
|
if (scope == ScopeFuncE && sEditor) {
|
||||||
|
totalCount = wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSens);
|
||||||
|
} else if (scope == ScopeProjE) {
|
||||||
|
stashCurrentFile();
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||||
|
activateFile(i, sProject.files[i].isForm ? ViewCodeE : ViewAutoE);
|
||||||
|
|
||||||
|
if (sEditor) {
|
||||||
|
totalCount += wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (scope == ScopeFileE && sEditor) {
|
||||||
|
// Replace in all procs of current file
|
||||||
|
int32_t procCount = (int32_t)arrlen(sProcBufs);
|
||||||
|
|
||||||
|
for (int32_t p = -1; p < procCount; p++) {
|
||||||
|
showProc(p);
|
||||||
|
|
||||||
|
if (sEditor) {
|
||||||
|
totalCount += wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (scope == ScopeObjE && sEditor && sObjDropdown) {
|
||||||
|
// Replace in all procs belonging to current object
|
||||||
|
int32_t objIdx = wgtDropdownGetSelected(sObjDropdown);
|
||||||
|
|
||||||
|
if (objIdx >= 0 && objIdx < (int32_t)arrlen(sObjItems)) {
|
||||||
|
const char *objName = sObjItems[objIdx];
|
||||||
|
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||||
|
|
||||||
|
for (int32_t p = 0; p < procCount; p++) {
|
||||||
|
if (strcasecmp(sProcTable[p].objName, objName) == 0) {
|
||||||
|
showProc(p);
|
||||||
|
|
||||||
|
if (sEditor) {
|
||||||
|
totalCount += wgtTextAreaReplaceAll(sEditor, sFindText, sReplaceText, caseSens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char statusBuf[64];
|
||||||
|
snprintf(statusBuf, sizeof(statusBuf), "%d replacement(s) made.", (int)totalCount);
|
||||||
|
setStatus(statusBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onFindClose(WindowT *win) {
|
||||||
|
(void)win;
|
||||||
|
closeFindDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onFindCloseBtn(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
closeFindDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void closeFindDialog(void) {
|
||||||
|
if (sFindWin) {
|
||||||
|
dvxDestroyWindow(sAc, sFindWin);
|
||||||
|
sFindWin = NULL;
|
||||||
|
sFindInput = NULL;
|
||||||
|
sReplInput = NULL;
|
||||||
|
sReplCheck = NULL;
|
||||||
|
sBtnReplace = NULL;
|
||||||
|
sBtnReplAll = NULL;
|
||||||
|
sCaseCheck = NULL;
|
||||||
|
sScopeGroup = NULL;
|
||||||
|
sDirGroup = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void openFindDialog(bool showReplace) {
|
||||||
|
if (sFindWin) {
|
||||||
|
// Already open — just toggle replace mode and raise
|
||||||
|
if (sReplCheck) {
|
||||||
|
wgtCheckboxSetChecked(sReplCheck, showReplace);
|
||||||
|
onReplCheckChange(sReplCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
dvxRaiseWindow(sAc, sFindWin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sFindWin = dvxCreateWindowCentered(sAc, "Find / Replace", 320, 210, false);
|
||||||
|
|
||||||
|
if (!sFindWin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sFindWin->onClose = onFindClose;
|
||||||
|
sFindWin->onMenu = onMenu;
|
||||||
|
sFindWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
|
|
||||||
|
WidgetT *root = wgtInitWindow(sAc, sFindWin);
|
||||||
|
root->spacing = wgtPixels(3);
|
||||||
|
|
||||||
|
// Find row
|
||||||
|
WidgetT *findRow = wgtHBox(root);
|
||||||
|
findRow->spacing = wgtPixels(4);
|
||||||
|
wgtLabel(findRow, "Find:");
|
||||||
|
sFindInput = wgtTextInput(findRow, 256);
|
||||||
|
sFindInput->weight = 100;
|
||||||
|
wgtSetText(sFindInput, sFindText);
|
||||||
|
|
||||||
|
// Replace checkbox + input
|
||||||
|
WidgetT *replRow = wgtHBox(root);
|
||||||
|
replRow->spacing = wgtPixels(4);
|
||||||
|
sReplCheck = wgtCheckbox(replRow, "Replace:");
|
||||||
|
wgtCheckboxSetChecked(sReplCheck, showReplace);
|
||||||
|
sReplCheck->onChange = onReplCheckChange;
|
||||||
|
sReplInput = wgtTextInput(replRow, 256);
|
||||||
|
sReplInput->weight = 100;
|
||||||
|
wgtSetText(sReplInput, sReplaceText);
|
||||||
|
|
||||||
|
// Options row: scope + direction + case
|
||||||
|
WidgetT *optRow = wgtHBox(root);
|
||||||
|
optRow->spacing = wgtPixels(8);
|
||||||
|
|
||||||
|
// Scope
|
||||||
|
WidgetT *scopeFrame = wgtFrame(optRow, "Scope");
|
||||||
|
WidgetT *scopeBox = wgtVBox(scopeFrame);
|
||||||
|
sScopeGroup = wgtRadioGroup(scopeBox);
|
||||||
|
wgtRadio(sScopeGroup, "Function");
|
||||||
|
wgtRadio(sScopeGroup, "Object");
|
||||||
|
wgtRadio(sScopeGroup, "File");
|
||||||
|
wgtRadio(sScopeGroup, "Project");
|
||||||
|
wgtRadioGroupSetSelected(sScopeGroup, 3); // Project
|
||||||
|
|
||||||
|
// Direction
|
||||||
|
WidgetT *dirFrame = wgtFrame(optRow, "Direction");
|
||||||
|
WidgetT *dirBox = wgtVBox(dirFrame);
|
||||||
|
sDirGroup = wgtRadioGroup(dirBox);
|
||||||
|
wgtRadio(sDirGroup, "Forward");
|
||||||
|
wgtRadio(sDirGroup, "Backward");
|
||||||
|
wgtRadioGroupSetSelected(sDirGroup, 0); // Forward
|
||||||
|
|
||||||
|
// Match Case
|
||||||
|
WidgetT *caseBox = wgtVBox(optRow);
|
||||||
|
sCaseCheck = wgtCheckbox(caseBox, "Match Case");
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
WidgetT *btnRow = wgtHBox(root);
|
||||||
|
btnRow->spacing = wgtPixels(4);
|
||||||
|
btnRow->align = AlignEndE;
|
||||||
|
|
||||||
|
WidgetT *btnFind = wgtButton(btnRow, "Find Next");
|
||||||
|
btnFind->onClick = onFindNext;
|
||||||
|
|
||||||
|
sBtnReplace = wgtButton(btnRow, "Replace");
|
||||||
|
sBtnReplace->onClick = onReplace;
|
||||||
|
|
||||||
|
sBtnReplAll = wgtButton(btnRow, "Replace All");
|
||||||
|
sBtnReplAll->onClick = onReplaceAll;
|
||||||
|
|
||||||
|
WidgetT *btnClose = wgtButton(btnRow, "Close");
|
||||||
|
btnClose->onClick = onFindCloseBtn;
|
||||||
|
|
||||||
|
// Set initial replace enable state
|
||||||
|
onReplCheckChange(sReplCheck);
|
||||||
|
|
||||||
|
dvxFitWindow(sAc, sFindWin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// findInProject -- search all project files for a text match.
|
||||||
|
// Starts from the current editor position in the current file,
|
||||||
|
// then continues through subsequent files, wrapping around.
|
||||||
|
// Opens the file and selects the match when found.
|
||||||
|
|
||||||
|
// showProcAndFind -- switch to a procedure, sync the dropdowns, and
|
||||||
|
// select the search match in the editor.
|
||||||
|
|
||||||
|
static bool showProcAndFind(int32_t procIdx, const char *needle, bool caseSensitive) {
|
||||||
|
showProc(procIdx);
|
||||||
|
|
||||||
|
// Sync the Object/Event dropdowns to match
|
||||||
|
if (procIdx >= 0 && procIdx < (int32_t)arrlen(sProcTable)) {
|
||||||
|
selectDropdowns(sProcTable[procIdx].objName, sProcTable[procIdx].evtName);
|
||||||
|
} else if (procIdx == -1 && sObjDropdown) {
|
||||||
|
wgtDropdownSetSelected(sObjDropdown, 0); // (General)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sEditor) {
|
||||||
|
return wgtTextAreaFindNext(sEditor, needle, caseSensitive, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Case-insensitive strstr replacement (strcasestr is a GNU extension)
|
||||||
|
static const char *findSubstrNoCase(const char *haystack, const char *needle, int32_t needleLen) {
|
||||||
|
for (; *haystack; haystack++) {
|
||||||
|
if (strncasecmp(haystack, needle, needleLen) == 0) {
|
||||||
|
return haystack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool findInProject(const char *needle, bool caseSensitive) {
|
||||||
|
if (!needle || !needle[0] || sProject.fileCount == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t needleLen = (int32_t)strlen(needle);
|
||||||
|
|
||||||
|
// Stash current editor state so all buffers are up-to-date
|
||||||
|
stashCurrentFile();
|
||||||
|
|
||||||
|
// Start from the active file, searching from after the current selection
|
||||||
|
int32_t startFile = sProject.activeFileIdx >= 0 ? sProject.activeFileIdx : 0;
|
||||||
|
int32_t startPos = 0;
|
||||||
|
|
||||||
|
// If the editor is open on the current file, try the current proc
|
||||||
|
// first (no wrap — returns false if no more matches ahead).
|
||||||
|
if (sEditor && sEditorFileIdx == startFile) {
|
||||||
|
if (wgtTextAreaFindNext(sEditor, needle, caseSensitive, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No more matches in current proc — search remaining procs
|
||||||
|
int32_t procCount = (int32_t)arrlen(sProcBufs);
|
||||||
|
|
||||||
|
for (int32_t p = sCurProcIdx + 1; p < procCount; p++) {
|
||||||
|
if (!sProcBufs[p]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *found = caseSensitive ? strstr(sProcBufs[p], needle) : findSubstrNoCase(sProcBufs[p], needle, needleLen);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
showProcAndFind(p, needle, caseSensitive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search General section if we started in a proc
|
||||||
|
if (sCurProcIdx >= 0 && sGeneralBuf) {
|
||||||
|
const char *found = caseSensitive ? strstr(sGeneralBuf, needle) : findSubstrNoCase(sGeneralBuf, needle, needleLen);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
showProcAndFind(-1, needle, caseSensitive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search procs before the current one (wrap within file)
|
||||||
|
for (int32_t p = 0; p < sCurProcIdx && p < procCount; p++) {
|
||||||
|
if (!sProcBufs[p]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *found = caseSensitive ? strstr(sProcBufs[p], needle) : findSubstrNoCase(sProcBufs[p], needle, needleLen);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
showProcAndFind(p, needle, caseSensitive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next file
|
||||||
|
startFile = (startFile + 1) % sProject.fileCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search remaining files, proc by proc.
|
||||||
|
// startFile was advanced past the current file if we already searched it.
|
||||||
|
int32_t filesToSearch = sProject.fileCount;
|
||||||
|
|
||||||
|
if (sEditor && sEditorFileIdx >= 0) {
|
||||||
|
filesToSearch--; // skip the file we already searched above
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t attempt = 0; attempt < filesToSearch; attempt++) {
|
||||||
|
int32_t fileIdx = (startFile + attempt) % sProject.fileCount;
|
||||||
|
|
||||||
|
// Activate the file to load its proc buffers
|
||||||
|
activateFile(fileIdx, sProject.files[fileIdx].isForm ? ViewCodeE : ViewAutoE);
|
||||||
|
|
||||||
|
// Search General section
|
||||||
|
if (sGeneralBuf) {
|
||||||
|
const char *found = caseSensitive ? strstr(sGeneralBuf, needle) : findSubstrNoCase(sGeneralBuf, needle, needleLen);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
showProcAndFind(-1, needle, caseSensitive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search each procedure
|
||||||
|
int32_t procCount = (int32_t)arrlen(sProcBufs);
|
||||||
|
|
||||||
|
for (int32_t p = 0; p < procCount; p++) {
|
||||||
|
if (!sProcBufs[p]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *found = caseSensitive ? strstr(sProcBufs[p], needle) : findSubstrNoCase(sProcBufs[p], needle, needleLen);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
showProcAndFind(p, needle, caseSensitive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void handleEditCmd(int32_t cmd) {
|
static void handleEditCmd(int32_t cmd) {
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case CMD_CUT:
|
case CMD_CUT:
|
||||||
|
|
@ -2372,6 +2875,22 @@ static void handleEditCmd(int32_t cmd) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_FIND:
|
||||||
|
openFindDialog(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_FIND_NEXT:
|
||||||
|
if (sFindText[0]) {
|
||||||
|
onFindNext(NULL);
|
||||||
|
} else {
|
||||||
|
openFindDialog(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CMD_REPLACE:
|
||||||
|
openFindDialog(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2465,6 +2984,8 @@ static void handleWindowCmd(int32_t cmd) {
|
||||||
|
|
||||||
if (sToolboxWin) {
|
if (sToolboxWin) {
|
||||||
sToolboxWin->y = toolbarBottom();
|
sToolboxWin->y = toolbarBottom();
|
||||||
|
sToolboxWin->onMenu = onMenu;
|
||||||
|
sToolboxWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -2475,6 +2996,8 @@ static void handleWindowCmd(int32_t cmd) {
|
||||||
|
|
||||||
if (sPropsWin) {
|
if (sPropsWin) {
|
||||||
sPropsWin->y = toolbarBottom();
|
sPropsWin->y = toolbarBottom();
|
||||||
|
sPropsWin->onMenu = onMenu;
|
||||||
|
sPropsWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -3431,6 +3954,8 @@ static void switchToDesign(void) {
|
||||||
|
|
||||||
if (sToolboxWin) {
|
if (sToolboxWin) {
|
||||||
sToolboxWin->y = toolbarBottom();
|
sToolboxWin->y = toolbarBottom();
|
||||||
|
sToolboxWin->onMenu = onMenu;
|
||||||
|
sToolboxWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3439,6 +3964,8 @@ static void switchToDesign(void) {
|
||||||
|
|
||||||
if (sPropsWin) {
|
if (sPropsWin) {
|
||||||
sPropsWin->y = toolbarBottom();
|
sPropsWin->y = toolbarBottom();
|
||||||
|
sPropsWin->onMenu = onMenu;
|
||||||
|
sPropsWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3502,11 +4029,15 @@ static void showCodeWindow(void) {
|
||||||
WidgetT *dropdownRow = wgtHBox(codeRoot);
|
WidgetT *dropdownRow = wgtHBox(codeRoot);
|
||||||
dropdownRow->spacing = wgtPixels(4);
|
dropdownRow->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
wgtLabel(dropdownRow, "Object:");
|
||||||
|
|
||||||
sObjDropdown = wgtDropdown(dropdownRow);
|
sObjDropdown = wgtDropdown(dropdownRow);
|
||||||
sObjDropdown->weight = 100;
|
sObjDropdown->weight = 100;
|
||||||
sObjDropdown->onChange = onObjDropdownChange;
|
sObjDropdown->onChange = onObjDropdownChange;
|
||||||
wgtDropdownSetItems(sObjDropdown, NULL, 0);
|
wgtDropdownSetItems(sObjDropdown, NULL, 0);
|
||||||
|
|
||||||
|
wgtLabel(dropdownRow, "Function:");
|
||||||
|
|
||||||
sEvtDropdown = wgtDropdown(dropdownRow);
|
sEvtDropdown = wgtDropdown(dropdownRow);
|
||||||
sEvtDropdown->weight = 100;
|
sEvtDropdown->weight = 100;
|
||||||
sEvtDropdown->onChange = onEvtDropdownChange;
|
sEvtDropdown->onChange = onEvtDropdownChange;
|
||||||
|
|
@ -3523,6 +4054,8 @@ static void showCodeWindow(void) {
|
||||||
|
|
||||||
// onChange is set after initial content is loaded by the caller
|
// onChange is set after initial content is loaded by the caller
|
||||||
// (navigateToEventSub, onPrjFileClick, etc.) to prevent false dirty marking.
|
// (navigateToEventSub, onPrjFileClick, etc.) to prevent false dirty marking.
|
||||||
|
|
||||||
|
updateProjectMenuState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3645,6 +4178,10 @@ static void updateProjectMenuState(void) {
|
||||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_REMOVE, hasProject);
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_PRJ_REMOVE, hasProject);
|
||||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE, hasProject);
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE, hasProject);
|
||||||
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, hasProject);
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_SAVE_ALL, hasProject);
|
||||||
|
|
||||||
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND, hasProject);
|
||||||
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_FIND_NEXT, hasProject);
|
||||||
|
wmMenuItemSetEnabled(sWin->menuBar, CMD_REPLACE, hasProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3681,11 +4218,19 @@ static void updateDirtyIndicators(void) {
|
||||||
if (sCodeWin) {
|
if (sCodeWin) {
|
||||||
bool codeDirty = false;
|
bool codeDirty = false;
|
||||||
|
|
||||||
if (sProject.activeFileIdx >= 0) {
|
const char *codeFile = "";
|
||||||
|
|
||||||
|
if (sEditorFileIdx >= 0 && sEditorFileIdx < sProject.fileCount) {
|
||||||
|
codeDirty = sProject.files[sEditorFileIdx].modified;
|
||||||
|
codeFile = sProject.files[sEditorFileIdx].path;
|
||||||
|
} else if (sProject.activeFileIdx >= 0) {
|
||||||
codeDirty = sProject.files[sProject.activeFileIdx].modified;
|
codeDirty = sProject.files[sProject.activeFileIdx].modified;
|
||||||
|
codeFile = sProject.files[sProject.activeFileIdx].path;
|
||||||
}
|
}
|
||||||
|
|
||||||
dvxSetTitle(sAc, sCodeWin, codeDirty ? "Code *" : "Code");
|
char codeTitle[DVX_MAX_PATH + 16];
|
||||||
|
snprintf(codeTitle, sizeof(codeTitle), "Code - %s%s", codeFile, codeDirty ? " *" : "");
|
||||||
|
dvxSetTitle(sAc, sCodeWin, codeTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Design window title
|
// Design window title
|
||||||
|
|
|
||||||
|
|
@ -2279,10 +2279,6 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) {
|
||||||
ctx->sysMenu.popupX = win->x + CHROME_BORDER_WIDTH;
|
ctx->sysMenu.popupX = win->x + CHROME_BORDER_WIDTH;
|
||||||
ctx->sysMenu.popupY = win->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT;
|
ctx->sysMenu.popupY = win->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT;
|
||||||
|
|
||||||
if (win->menuBar) {
|
|
||||||
ctx->sysMenu.popupY += CHROME_MENU_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t maxW = 0;
|
int32_t maxW = 0;
|
||||||
|
|
||||||
for (int32_t i = 0; i < ctx->sysMenu.itemCount; i++) {
|
for (int32_t i = 0; i < ctx->sysMenu.itemCount; i++) {
|
||||||
|
|
|
||||||
|
|
@ -1473,20 +1473,11 @@ static void int9Handler(void) {
|
||||||
|
|
||||||
|
|
||||||
void platformKeyUpInit(void) {
|
void platformKeyUpInit(void) {
|
||||||
if (sKeyUpInstalled) {
|
// INT 9 hook disabled pending investigation of keyboard corruption.
|
||||||
return;
|
// Key-up events are not available until this is fixed.
|
||||||
}
|
(void)sOldInt9;
|
||||||
|
(void)sNewInt9;
|
||||||
_go32_dpmi_get_protected_mode_interrupt_vector(9, &sOldInt9);
|
(void)int9Handler;
|
||||||
|
|
||||||
sNewInt9.pm_offset = (unsigned long)int9Handler;
|
|
||||||
sNewInt9.pm_selector = _go32_my_cs();
|
|
||||||
|
|
||||||
// Chain: our handler runs first, then DJGPP automatically
|
|
||||||
// calls the original handler via an IRET wrapper.
|
|
||||||
_go32_dpmi_chain_protected_mode_interrupt_vector(9, &sNewInt9);
|
|
||||||
|
|
||||||
sKeyUpInstalled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ typedef struct {
|
||||||
} TextAreaDataT;
|
} TextAreaDataT;
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <strings.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#define TEXTAREA_BORDER 2
|
#define TEXTAREA_BORDER 2
|
||||||
|
|
@ -165,6 +166,8 @@ static void widgetTextAreaDragSelect(WidgetT *w, WidgetT *root, int32_t vx, int3
|
||||||
static void widgetTextAreaOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
static void widgetTextAreaOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||||
static int32_t wordBoundaryLeft(const char *buf, int32_t pos);
|
static int32_t wordBoundaryLeft(const char *buf, int32_t pos);
|
||||||
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
|
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
|
||||||
|
bool wgtTextAreaFindNext(WidgetT *w, const char *needle, bool caseSensitive, bool forward);
|
||||||
|
int32_t wgtTextAreaReplaceAll(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive);
|
||||||
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
||||||
|
|
||||||
// sCursorBlinkOn is defined in widgetCore.c (shared state)
|
// sCursorBlinkOn is defined in widgetCore.c (shared state)
|
||||||
|
|
@ -2674,6 +2677,163 @@ void wgtTextAreaSetUseTabChar(WidgetT *w, bool useChar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool wgtTextAreaFindNext(WidgetT *w, const char *needle, bool caseSensitive, bool forward) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId || !needle || !needle[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
int32_t needleLen = strlen(needle);
|
||||||
|
|
||||||
|
if (needleLen > ta->len) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current cursor byte position
|
||||||
|
int32_t cursorByte = textAreaCursorToOff(ta->buf, ta->len, ta->cursorRow, ta->cursorCol);
|
||||||
|
|
||||||
|
// Search forward from cursor+1 (or backward from cursor-1).
|
||||||
|
// No wrap-around: returns false if the end/start of the buffer
|
||||||
|
// is reached without finding a match.
|
||||||
|
int32_t startPos = forward ? cursorByte + 1 : cursorByte - 1;
|
||||||
|
int32_t searchLen = ta->len - needleLen + 1;
|
||||||
|
|
||||||
|
if (searchLen <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t count = forward ? (searchLen - startPos) : (startPos + 1);
|
||||||
|
|
||||||
|
if (count <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t attempt = 0; attempt < count; attempt++) {
|
||||||
|
int32_t pos;
|
||||||
|
|
||||||
|
if (forward) {
|
||||||
|
pos = startPos + attempt;
|
||||||
|
} else {
|
||||||
|
pos = startPos - attempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos < 0 || pos >= searchLen) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool match;
|
||||||
|
if (caseSensitive) {
|
||||||
|
match = (memcmp(ta->buf + pos, needle, needleLen) == 0);
|
||||||
|
} else {
|
||||||
|
match = (strncasecmp(ta->buf + pos, needle, needleLen) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
// Select the found text
|
||||||
|
ta->selAnchor = pos;
|
||||||
|
ta->selCursor = pos + needleLen;
|
||||||
|
|
||||||
|
// Move cursor to start of match (forward) or end of match (backward)
|
||||||
|
int32_t cursorOff = forward ? pos : pos + needleLen;
|
||||||
|
textAreaOffToRowCol(ta->buf, cursorOff, &ta->cursorRow, &ta->cursorCol);
|
||||||
|
ta->desiredCol = ta->cursorCol;
|
||||||
|
|
||||||
|
// Scroll to show the match
|
||||||
|
AppContextT *ctx = wgtGetContext(w);
|
||||||
|
if (ctx) {
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
int32_t gutterW = textAreaGutterWidth(w, font);
|
||||||
|
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W - gutterW;
|
||||||
|
int32_t visCols = innerW / font->charWidth;
|
||||||
|
int32_t innerH = w->h - TEXTAREA_BORDER * 2;
|
||||||
|
int32_t visRows = innerH / font->charHeight;
|
||||||
|
if (visCols < 1) { visCols = 1; }
|
||||||
|
if (visRows < 1) { visRows = 1; }
|
||||||
|
textAreaEnsureVisible(w, visRows, visCols);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t wgtTextAreaReplaceAll(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive) {
|
||||||
|
if (!w || w->type != sTextAreaTypeId || !needle || !needle[0] || !replacement) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextAreaDataT *ta = (TextAreaDataT *)w->data;
|
||||||
|
int32_t needleLen = strlen(needle);
|
||||||
|
int32_t replLen = strlen(replacement);
|
||||||
|
int32_t delta = replLen - needleLen;
|
||||||
|
int32_t count = 0;
|
||||||
|
|
||||||
|
if (needleLen > ta->len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save undo before any modifications
|
||||||
|
textEditSaveUndo(ta->buf, ta->len, textAreaCursorToOff(ta->buf, ta->len, ta->cursorRow, ta->cursorCol), ta->undoBuf, &ta->undoLen, &ta->undoCursor, ta->bufSize);
|
||||||
|
|
||||||
|
int32_t pos = 0;
|
||||||
|
while (pos + needleLen <= ta->len) {
|
||||||
|
bool match;
|
||||||
|
if (caseSensitive) {
|
||||||
|
match = (memcmp(ta->buf + pos, needle, needleLen) == 0);
|
||||||
|
} else {
|
||||||
|
match = (strncasecmp(ta->buf + pos, needle, needleLen) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
// Check if replacement fits in buffer
|
||||||
|
if (ta->len + delta >= ta->bufSize) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shift text after match to make room (or close gap)
|
||||||
|
if (delta != 0) {
|
||||||
|
memmove(ta->buf + pos + replLen, ta->buf + pos + needleLen, ta->len - pos - needleLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy replacement in
|
||||||
|
memcpy(ta->buf + pos, replacement, replLen);
|
||||||
|
ta->len += delta;
|
||||||
|
ta->buf[ta->len] = '\0';
|
||||||
|
count++;
|
||||||
|
pos += replLen;
|
||||||
|
} else {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0) {
|
||||||
|
// Clear selection
|
||||||
|
ta->selAnchor = -1;
|
||||||
|
ta->selCursor = -1;
|
||||||
|
|
||||||
|
// Clamp cursor if it's past the end
|
||||||
|
int32_t cursorOff = textAreaCursorToOff(ta->buf, ta->len, ta->cursorRow, ta->cursorCol);
|
||||||
|
if (cursorOff > ta->len) {
|
||||||
|
textAreaOffToRowCol(ta->buf, ta->len, &ta->cursorRow, &ta->cursorCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
ta->desiredCol = ta->cursorCol;
|
||||||
|
textAreaDirtyCache(w);
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DXE registration
|
// DXE registration
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -2691,6 +2851,8 @@ static const struct {
|
||||||
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
||||||
void (*setTabWidth)(WidgetT *w, int32_t width);
|
void (*setTabWidth)(WidgetT *w, int32_t width);
|
||||||
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
||||||
|
bool (*findNext)(WidgetT *w, const char *needle, bool caseSensitive, bool forward);
|
||||||
|
int32_t (*replaceAll)(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive);
|
||||||
} sApi = {
|
} sApi = {
|
||||||
.create = wgtTextInput,
|
.create = wgtTextInput,
|
||||||
.password = wgtPasswordInput,
|
.password = wgtPasswordInput,
|
||||||
|
|
@ -2702,7 +2864,9 @@ static const struct {
|
||||||
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers,
|
.setShowLineNumbers = wgtTextAreaSetShowLineNumbers,
|
||||||
.setCaptureTabs = wgtTextAreaSetCaptureTabs,
|
.setCaptureTabs = wgtTextAreaSetCaptureTabs,
|
||||||
.setTabWidth = wgtTextAreaSetTabWidth,
|
.setTabWidth = wgtTextAreaSetTabWidth,
|
||||||
.setUseTabChar = wgtTextAreaSetUseTabChar
|
.setUseTabChar = wgtTextAreaSetUseTabChar,
|
||||||
|
.findNext = wgtTextAreaFindNext,
|
||||||
|
.replaceAll = wgtTextAreaReplaceAll
|
||||||
};
|
};
|
||||||
|
|
||||||
// Per-type APIs for the designer
|
// Per-type APIs for the designer
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ typedef struct {
|
||||||
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
void (*setCaptureTabs)(WidgetT *w, bool capture);
|
||||||
void (*setTabWidth)(WidgetT *w, int32_t width);
|
void (*setTabWidth)(WidgetT *w, int32_t width);
|
||||||
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
void (*setUseTabChar)(WidgetT *w, bool useChar);
|
||||||
|
bool (*findNext)(WidgetT *w, const char *needle, bool caseSensitive, bool forward);
|
||||||
|
int32_t (*replaceAll)(WidgetT *w, const char *needle, const char *replacement, bool caseSensitive);
|
||||||
} TextInputApiT;
|
} TextInputApiT;
|
||||||
|
|
||||||
static inline const TextInputApiT *dvxTextInputApi(void) {
|
static inline const TextInputApiT *dvxTextInputApi(void) {
|
||||||
|
|
@ -44,5 +46,7 @@ static inline const TextInputApiT *dvxTextInputApi(void) {
|
||||||
#define wgtTextAreaSetCaptureTabs(w, capture) dvxTextInputApi()->setCaptureTabs(w, capture)
|
#define wgtTextAreaSetCaptureTabs(w, capture) dvxTextInputApi()->setCaptureTabs(w, capture)
|
||||||
#define wgtTextAreaSetTabWidth(w, width) dvxTextInputApi()->setTabWidth(w, width)
|
#define wgtTextAreaSetTabWidth(w, width) dvxTextInputApi()->setTabWidth(w, width)
|
||||||
#define wgtTextAreaSetUseTabChar(w, useChar) dvxTextInputApi()->setUseTabChar(w, useChar)
|
#define wgtTextAreaSetUseTabChar(w, useChar) dvxTextInputApi()->setUseTabChar(w, useChar)
|
||||||
|
#define wgtTextAreaFindNext(w, needle, caseSens, fwd) dvxTextInputApi()->findNext(w, needle, caseSens, fwd)
|
||||||
|
#define wgtTextAreaReplaceAll(w, needle, repl, caseSens) dvxTextInputApi()->replaceAll(w, needle, repl, caseSens)
|
||||||
|
|
||||||
#endif // WIDGET_TEXTINPUT_H
|
#endif // WIDGET_TEXTINPUT_H
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue