blob: d16f3a12d1823d418ad7915dd4490b4e12b750db [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif /* HAVE_LANGINFO_H */
#undef CAN_GLOB
#if defined(HAVE_GLOB)
#define CAN_GLOB
#include <glob.h>
#elif defined(__MINGW32__)
#define CAN_GLOB
#include <io.h>
#else /* glob: paradigm-specific global definitions */
#warning file globbing support not available on this platform
#endif /* glob: paradigm-specific global definitions */
#include "log.h"
#include "menu.h"
#include "strfmt.h"
#include "prefs.h"
#include "timing.h"
#include "parse.h"
#include "file.h"
typedef struct {
char *directory;
const char *extension;
const char *pattern;
char *initial;
char *current;
unsigned none:1;
#if defined(HAVE_GLOB)
glob_t glob;
#elif defined(__MINGW32__)
char **names;
int offset;
#endif /* glob: paradigm-specific field declarations */
char **paths;
int count;
unsigned char setting;
char *pathsArea[3];
} FileData;
typedef struct {
Menu *menu;
unsigned opened:1;
unsigned int total;
unsigned int visible;
} SubmenuData;
struct MenuStruct {
Menu *parent;
struct {
MenuItem *array;
unsigned int size;
unsigned int count;
unsigned int index;
} items;
unsigned int menuNumber;
unsigned int submenuCount;
MenuItem *activeItem;
char valueBuffer[0X20];
};
typedef struct {
int (*testItem) (const MenuItem *item);
int (*beginItem) (MenuItem *item);
void (*endItem) (MenuItem *item, int deallocating);
void (*activateItem) (MenuItem *item);
const char * (*getValue) (const MenuItem *item);
const char * (*getText) (const MenuItem *item);
const char * (*getComment) (const MenuItem *item);
} MenuItemMethods;
struct MenuItemStruct {
Menu *menu;
unsigned char *setting; /* pointer to current value */
const char *title; /* item name for presentation */
const char *subtitle; /* item name for presentation */
const MenuItemMethods *methods;
MenuItemTester *test; /* returns true if item should be presented */
MenuItemChanged *changed;
unsigned char minimum; /* lowest valid value */
unsigned char maximum; /* highest valid value */
unsigned char step; /* present only multiples of this value */
union {
const char *text;
const MenuString *strings;
FileData *files;
SubmenuData *submenu;
struct {
const char *unit;
NumericMenuItemFormatter *formatter;
} numeric;
struct {
MenuToolFunction *function;
} tool;
} data;
};
static inline const char *
getLocalizedText (const char *string) {
return (string && *string)? gettext(string): "";
}
static const char *
formatValue (Menu *menu, const char *format, ...) {
{
va_list arguments;
va_start(arguments, format);
vsnprintf(menu->valueBuffer, sizeof(menu->valueBuffer), format, arguments);
va_end(arguments);
}
return menu->valueBuffer;
}
Menu *
newMenu (void) {
Menu *menu;
if ((menu = malloc(sizeof(*menu)))) {
memset(menu, 0, sizeof(*menu));
menu->parent = NULL;
menu->items.array = NULL;
menu->items.size = 0;
menu->items.count = 0;
menu->items.index = 0;
menu->menuNumber = 0;
menu->submenuCount = 0;
menu->activeItem = NULL;
return menu;
} else {
logMallocError();
}
return NULL;
}
static int
beginMenuItem (MenuItem *item) {
return !item->methods->beginItem || item->methods->beginItem(item);
}
static void
endMenuItem (MenuItem *item, int deallocating) {
if (item->methods->endItem) item->methods->endItem(item, deallocating);
}
void
destroyMenu (Menu *menu) {
if (menu) {
if (menu->items.array) {
MenuItem *item = menu->items.array;
const MenuItem *end = item + menu->items.count;
while (item < end) endMenuItem(item++, 1);
free(menu->items.array);
}
free(menu);
}
}
unsigned int
getMenuNumber (const Menu *menu) {
return menu->menuNumber;
}
Menu *
getMenuParent (const Menu *menu) {
return menu->parent;
}
unsigned int
getMenuSize (const Menu *menu) {
return menu->items.count;
}
unsigned int
getMenuIndex (const Menu *menu) {
return menu->items.index;
}
MenuItem *
getMenuItem (Menu *menu, unsigned int index) {
return (index < menu->items.count)? &menu->items.array[index]: NULL;
}
static MenuItem *
getSelectedMenuItem (Menu *menu) {
return getMenuItem(menu, menu->items.index);
}
static int
testMenuItem (const MenuItem *item, int all) {
if (!item) return 0;
if (all) return 1;
if (item->methods->testItem && !item->methods->testItem(item)) return 0;
return !item->test || item->test();
}
int
isMenuItemSettable (const MenuItem *item) {
return !!item->setting;
}
int
isMenuItemAction (const MenuItem *item) {
return !!item->methods->activateItem;
}
int
isMenuItemVisible (const MenuItem *item) {
return testMenuItem(item, 0);
}
static inline int
testMenuItemActive (Menu *menu, unsigned int index) {
return testMenuItem(getMenuItem(menu, index), 0);
}
static inline int
testMenuItemVisible (Menu *menu, unsigned int index) {
return testMenuItem(getMenuItem(menu, index), prefs.showAllItems);
}
Menu *
getMenuItemMenu (const MenuItem *item) {
return item->menu;
}
unsigned int
getMenuItemIndex (const MenuItem *item) {
return item - item->menu->items.array;
}
const char *
getMenuItemTitle (const MenuItem *item) {
return getLocalizedText(item->title);
}
const char *
getMenuItemSubtitle (const MenuItem *item) {
return getLocalizedText(item->subtitle);
}
const char *
getMenuItemValue (const MenuItem *item) {
return item->methods->getValue? item->methods->getValue(item): "";
}
const char *
getMenuItemText (const MenuItem *item) {
return item->methods->getText? item->methods->getText(item): getMenuItemValue(item);
}
const char *
getMenuItemComment (const MenuItem *item) {
return item->methods->getComment? item->methods->getComment(item): "";
}
static MenuItem *
newMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
if (menu->items.count == menu->items.size) {
unsigned int newSize = menu->items.size? (menu->items.size << 1): 0X10;
MenuItem *newArray = realloc(menu->items.array, (newSize * sizeof(*newArray)));
if (!newArray) {
logMallocError();
return NULL;
}
menu->items.array = newArray;
menu->items.size = newSize;
}
{
MenuItem *item = getMenuItem(menu, menu->items.count++);
item->menu = menu;
item->setting = setting;
if (name) {
item->title = name->label;
item->subtitle = name->comment;
} else {
item->title = NULL;
item->subtitle = NULL;
}
item->methods = NULL;
item->test = NULL;
item->changed = NULL;
item->minimum = 0;
item->maximum = 0;
item->step = 1;
return item;
}
}
void
setMenuItemTester (MenuItem *item, MenuItemTester *handler) {
item->test = handler;
}
void
setMenuItemChanged (MenuItem *item, MenuItemChanged *handler) {
item->changed = handler;
}
static const char *
getValue_text (const MenuItem *item) {
return item->data.text;
}
static const MenuItemMethods menuItemMethods_text = {
.getValue = getValue_text
};
MenuItem *
newTextMenuItem (Menu *menu, const MenuString *name, const char *text) {
MenuItem *item = newMenuItem(menu, NULL, name);
if (item) {
item->methods = &menuItemMethods_text;
item->data.text = text;
}
return item;
}
static const char *
getValue_numeric (const MenuItem *item) {
Menu *menu = item->menu;
item->data.numeric.formatter(
menu, *item->setting,
menu->valueBuffer, sizeof(menu->valueBuffer)
);
return menu->valueBuffer;
}
static const char *
getComment_numeric (const MenuItem *item) {
return getLocalizedText(item->data.numeric.unit);
}
static const MenuItemMethods menuItemMethods_numeric = {
.getValue = getValue_numeric,
.getComment = getComment_numeric
};
static void
defaultNumericMenuItemFormatter (
Menu *menu, unsigned char value,
char *buffer, size_t size
) {
snprintf(buffer, size, "%u", value);
}
MenuItem *
newNumericMenuItem (
Menu *menu, unsigned char *setting, const MenuString *name,
unsigned char minimum, unsigned char maximum, unsigned char step,
const char *unit, NumericMenuItemFormatter *formatter
) {
if (!formatter) formatter = defaultNumericMenuItemFormatter;
MenuItem *item = newMenuItem(menu, setting, name);
if (item) {
item->methods = &menuItemMethods_numeric;
item->minimum = minimum;
item->maximum = maximum;
item->step = step;
item->data.numeric.unit = unit;
item->data.numeric.formatter = formatter;
}
return item;
}
static void
formatTime (Menu *menu, unsigned char time, char *buffer, size_t size) {
unsigned int milliseconds = PREFS2MSECS(time);
unsigned int seconds = milliseconds / MSECS_PER_SEC;
milliseconds %= MSECS_PER_SEC;
const char *decimalPoint;
#ifdef HAVE_NL_LANGINFO
decimalPoint = nl_langinfo(RADIXCHAR);
#else /* HAVE_NL_LANGINFO */
decimalPoint = NULL;
#endif /* HAVE_NL_LANGINFO */
if (!decimalPoint) decimalPoint = ".";
size_t end;
size_t decimalFrom;
size_t decimalTo;
STR_BEGIN(buffer, size);
STR_PRINTF("%u", seconds);
decimalFrom = STR_LENGTH;
STR_PRINTF("%s", decimalPoint);
decimalTo = STR_LENGTH;
STR_PRINTF("%03u", milliseconds);
end = STR_LENGTH;
STR_END;
while (buffer[--end] == '0');
if (++end == decimalTo) end = decimalFrom;
buffer[end] = 0;
}
MenuItem *
newTimeMenuItem (
Menu *menu, unsigned char *setting,
const MenuString *name
) {
return newNumericMenuItem(menu, setting, name, 10, 250, 10, strtext("seconds"), formatTime);
}
MenuItem *
newPercentMenuItem (
Menu *menu, unsigned char *setting,
const MenuString *name, unsigned char step
) {
return newNumericMenuItem(menu, setting, name, 0, 100, step, "%", NULL);
}
static const char *
getValue_strings (const MenuItem *item) {
const MenuString *strings = item->data.strings;
return getLocalizedText(strings[*item->setting - item->minimum].label);
}
static const char *
getComment_strings (const MenuItem *item) {
const MenuString *strings = item->data.strings;
return getLocalizedText(strings[*item->setting - item->minimum].comment);
}
static const MenuItemMethods menuItemMethods_strings = {
.getValue = getValue_strings,
.getComment = getComment_strings
};
static void
setMenuItemStrings (MenuItem *item, const MenuString *strings, unsigned char count) {
item->methods = &menuItemMethods_strings;
item->data.strings = strings;
item->minimum = 0;
item->maximum = count - 1;
item->step = 1;
}
MenuItem *
newStringsMenuItem (
Menu *menu, unsigned char *setting, const MenuString *name,
const MenuString *strings, unsigned char count
) {
MenuItem *item = newMenuItem(menu, setting, name);
if (item) {
setMenuItemStrings(item, strings, count);
}
return item;
}
MenuItem *
newBooleanMenuItem (Menu *menu, unsigned char *setting, const MenuString *name) {
static const MenuString strings[] = {
{.label=strtext("No")},
{.label=strtext("Yes")}
};
return newEnumeratedMenuItem(menu, setting, name, strings);
}
static int
qsortCompare_fileNames (const void *element1, const void *element2) {
const char *const *name1 = element1;
const char *const *name2 = element2;
return strcmp(*name1, *name2);
}
static int
beginItem_files (MenuItem *item) {
FileData *files = item->data.files;
int index;
files->paths = files->pathsArea;
files->count = ARRAY_COUNT(files->pathsArea) - 1;
files->paths[files->count] = NULL;
index = files->count;
#ifdef CAN_GLOB
{
#ifdef HAVE_FCHDIR
int originalDirectory = open(".", O_RDONLY);
if (originalDirectory != -1)
#else /* HAVE_FCHDIR */
char *originalDirectory = getWorkingDirectory();
if (originalDirectory)
#endif /* HAVE_FCHDIR */
{
if (chdir(files->directory) != -1) {
#if defined(HAVE_GLOB)
memset(&files->glob, 0, sizeof(files->glob));
files->glob.gl_offs = files->count;
if (glob(files->pattern, GLOB_DOOFFS, NULL, &files->glob) == 0) {
files->paths = files->glob.gl_pathv;
/* The behaviour of gl_pathc is inconsistent. Some implementations
* include the leading NULL pointers and some don't. Let's just
* figure it out the hard way by finding the trailing NULL.
*/
while (files->paths[files->count]) files->count += 1;
}
#elif defined(__MINGW32__)
struct _finddata_t findData;
long findHandle = _findfirst(files->pattern, &findData);
int allocated = files->count | 0XF;
files->offset = files->count;
files->names = malloc(allocated * sizeof(*files->names));
if (findHandle != -1) {
do {
if (files->count >= allocated) {
allocated = allocated * 2;
files->names = realloc(files->names, allocated * sizeof(*files->names));
}
files->names[files->count++] = strdup(findData.name);
} while (_findnext(findHandle, &findData) == 0);
_findclose(findHandle);
}
files->names = realloc(files->names, files->count * sizeof(*files->names));
files->paths = files->names;
#endif /* glob: paradigm-specific field initialization */
#ifdef HAVE_FCHDIR
if (fchdir(originalDirectory) == -1) logSystemError("fchdir");
#else /* HAVE_FCHDIR */
if (chdir(originalDirectory) == -1) logSystemError("chdir");
#endif /* HAVE_FCHDIR */
} else {
logMessage(LOG_ERR, "%s: %s: %s",
gettext("cannot set working directory"), files->directory, strerror(errno));
}
#ifdef HAVE_FCHDIR
close(originalDirectory);
#else /* HAVE_FCHDIR */
free(originalDirectory);
#endif /* HAVE_FCHDIR */
} else {
#ifdef HAVE_FCHDIR
logMessage(LOG_ERR, "%s: %s",
gettext("cannot open working directory"), strerror(errno));
#else /* HAVE_FCHDIR */
logMessage(LOG_ERR, "%s", gettext("cannot determine working directory"));
#endif /* HAVE_FCHDIR */
}
}
#endif /* CAN_GLOB */
qsort(&files->paths[index], files->count-index, sizeof(*files->paths), qsortCompare_fileNames);
if (files->none) files->paths[--index] = "";
files->paths[--index] = files->initial;
files->paths += index;
files->count -= index;
files->setting = 0;
for (index=1; index<files->count; index+=1) {
if (strcmp(files->paths[index], files->initial) == 0) {
files->paths += 1;
files->count -= 1;
break;
}
}
for (index=0; index<files->count; index+=1) {
if (strcmp(files->paths[index], files->current) == 0) {
files->setting = index;
break;
}
}
item->maximum = files->count - 1;
return 1;
}
static void
endItem_files (MenuItem *item, int deallocating) {
FileData *files = item->data.files;
if (files->current) free(files->current);
files->current = deallocating? NULL: strdup(files->paths[files->setting]);
#if defined(HAVE_GLOB)
if (files->glob.gl_pathc) {
for (int i=0; i<files->glob.gl_offs; i+=1) files->glob.gl_pathv[i] = NULL;
globfree(&files->glob);
files->glob.gl_pathc = 0;
}
#elif defined(__MINGW32__)
if (files->names) {
for (int i=files->offset; i<files->count; i+=1) free(files->names[i]);
free(files->names);
files->names = NULL;
}
#endif /* glob: paradigm-specific memory deallocation */
}
static const char *
getValue_files (const MenuItem *item) {
const FileData *files = item->data.files;
const char *path;
if (item == item->menu->activeItem) {
path = files->paths[files->setting];
} else {
path = files->current;
}
if (!path) path = "";
const char *name = locatePathName(path);
if (name == path) {
const char *extension = files->extension;
if (hasFileExtension(name, extension)) {
int length = strlen(path) - strlen(extension);
Menu *menu = item->menu;
snprintf(
menu->valueBuffer, sizeof(menu->valueBuffer),
"%.*s", length, name
);
path = menu->valueBuffer;
}
}
return path;
}
static const char *
getText_files (const MenuItem *item) {
return item->methods->getValue(item);
}
static const MenuItemMethods menuItemMethods_files = {
.beginItem = beginItem_files,
.endItem = endItem_files,
.getValue = getValue_files,
.getText = getText_files
};
MenuItem *
newFilesMenuItem (
Menu *menu, const MenuString *name,
const char *directory, const char *subdirectory, const char *extension,
const char *initial, int none
) {
FileData *files;
if ((files = malloc(sizeof(*files)))) {
memset(files, 0, sizeof(*files));
files->extension = extension;
files->none = !!none;
char *pattern;
{
const char *strings[] = {"*", extension};
pattern = joinStrings(strings, ARRAY_COUNT(strings));
}
if (pattern) {
files->pattern = pattern;
if ((files->initial = *initial? ensureFileExtension(initial, extension): strdup(""))) {
if ((files->current = strdup(files->initial))) {
if (subdirectory) {
files->directory = makePath(directory, subdirectory);
} else if (!(files->directory = strdup(directory))) {
logMallocError();
}
if (files->directory) {
MenuItem *item = newMenuItem(menu, &files->setting, name);
if (item) {
item->methods = &menuItemMethods_files;
item->data.files = files;
return item;
}
free(files->directory);
}
free(files->current);
} else {
logMallocError();
}
free(files->initial);
} else {
logMallocError();
}
free(pattern);
} else {
logMallocError();
}
free(files);
} else {
logMallocError();
}
return NULL;
}
static void
activateItem_tool (MenuItem *item) {
item->data.tool.function();
}
static const char *
getValue_tool (const MenuItem *item) {
return NULL;
}
static const MenuItemMethods menuItemMethods_tool = {
.activateItem = activateItem_tool,
.getValue = getValue_tool
};
MenuItem *
newToolMenuItem (Menu *menu, const MenuString *name, MenuToolFunction *function) {
MenuItem *item = newMenuItem(menu, NULL, name);
if (item) {
item->methods = &menuItemMethods_tool;
item->data.tool.function = function;
}
return item;
}
static MenuItem *
getParentMenuItem (const MenuItem *item) {
return getSelectedMenuItem(item->menu->parent);
}
static int
testItem_submenu (const MenuItem *item) {
return getMenuSize(item->data.submenu->menu) > 1;
}
static int
beginItem_submenu (MenuItem *item) {
item->data.submenu->visible = 0;
{
Menu *menu = item->data.submenu->menu;
unsigned int size = getMenuSize(menu);
for (unsigned int index=1; index<size; index+=1) {
if (testMenuItemActive(menu, index)) {
item->data.submenu->visible += 1;
}
}
item->data.submenu->total = size - 1;
}
return 1;
}
static void
endItem_submenu (MenuItem *item, int deallocating) {
if (deallocating) {
SubmenuData *submenu = item->data.submenu;
destroyMenu(submenu->menu);
free(submenu);
}
}
static void
activateItem_submenu (MenuItem *item) {
endMenuItem(item, 0);
item->data.submenu->opened = 1;
}
static const char *
getValue_submenu (const MenuItem *item) {
return "--->";
}
static const char *
getComment_submenu (const MenuItem *item) {
if (!prefs.showSubmenuSizes) return "";
{
const SubmenuData *submenu = item->data.submenu;
return prefs.showAllItems?
formatValue(item->menu, "%u", submenu->total):
formatValue(item->menu, "%u/%u", submenu->visible, submenu->total);
}
}
static const MenuItemMethods menuItemMethods_submenu = {
.testItem = testItem_submenu,
.beginItem = beginItem_submenu,
.endItem = endItem_submenu,
.activateItem = activateItem_submenu,
.getValue = getValue_submenu,
.getComment = getComment_submenu
};
static void
activateItem_close (MenuItem *item) {
item = getParentMenuItem(item);
item->data.submenu->opened = 0;
beginMenuItem(item);
}
static const char *
getValue_close (const MenuItem *item) {
return getLocalizedText(strtext("Close"));
}
static const char *
getComment_close (const MenuItem *item) {
return getMenuItemTitle(getParentMenuItem(item));
}
static const MenuItemMethods menuItemMethods_close = {
.activateItem = activateItem_close,
.getValue = getValue_close,
.getComment = getComment_close
};
Menu *
newSubmenuMenuItem (
Menu *menu, const MenuString *name
) {
SubmenuData *submenu;
if ((submenu = malloc(sizeof(*submenu)))) {
memset(submenu, 0, sizeof(*submenu));
if ((submenu->menu = newMenu())) {
static const MenuString closeName = {.label="<---"};
MenuItem *close;
if ((close = newMenuItem(submenu->menu, NULL, &closeName))) {
MenuItem *item;
if ((item = newMenuItem(menu, NULL, name))) {
submenu->menu->parent = menu;
submenu->opened = 0;
close->methods = &menuItemMethods_close;
item->methods = &menuItemMethods_submenu;
item->data.submenu = submenu;
while (1) {
menu->submenuCount += 1;
if (!menu->parent) break;
menu = menu->parent;
}
submenu->menu->menuNumber = menu->submenuCount;
return submenu->menu;
}
}
destroyMenu(submenu->menu);
}
free(submenu);
} else {
logMallocError();
}
return NULL;
}
void
changeMenuItem (MenuItem *item) {
Menu *menu = item->menu;
menu->items.index = getMenuItemIndex(item);
}
int
changeMenuItemPrevious (Menu *menu, int wrap) {
unsigned int index = menu->items.index;
if (index >= menu->items.count) return 0;
do {
if (!menu->items.index) {
if (!wrap) {
menu->items.index = index;
return 0;
}
menu->items.index = menu->items.count;
}
if (--menu->items.index == index) return 0;
} while (!testMenuItemVisible(menu, menu->items.index));
return 1;
}
int
changeMenuItemNext (Menu *menu, int wrap) {
unsigned int index = menu->items.index;
if (index >= menu->items.count) return 0;
do {
if (++menu->items.index == menu->items.count) {
if (!wrap) {
menu->items.index = index;
return 0;
}
menu->items.index = 0;
}
if (menu->items.index == index) return 0;
} while (!testMenuItemVisible(menu, menu->items.index));
return 1;
}
int
changeMenuItemFirst (Menu *menu) {
if (!menu->items.count) return 0;
menu->items.index = 0;
return testMenuItemVisible(menu, menu->items.index) || changeMenuItemNext(menu, 0);
}
int
changeMenuItemLast (Menu *menu) {
if (!menu->items.count) return 0;
menu->items.index = menu->items.count - 1;
return testMenuItemVisible(menu, menu->items.index) || changeMenuItemPrevious(menu, 0);
}
int
changeMenuItemIndex (Menu *menu, unsigned int index) {
if (index >= menu->items.count) return 0;
menu->items.index = index;
return 1;
}
static int
activateMenuItem (MenuItem *item) {
if (!item->methods->activateItem) return 0;
item->methods->activateItem(item);
return 1;
}
static int
adjustMenuSetting (const MenuItem *item, int (*adjust) (const MenuItem *item, int wrap), int wrap) {
unsigned char setting = *item->setting;
int count = item->maximum - item->minimum + 1;
do {
int ok = 0;
if (--count) {
if (adjust(item, wrap)) {
ok = 1;
}
}
if (!ok) {
*item->setting = setting;
return 0;
}
} while ((*item->setting % item->step) || (item->changed && !item->changed(item, *item->setting)));
return 1;
}
static int
decrementMenuSetting (const MenuItem *item, int wrap) {
if ((*item->setting)-- <= item->minimum) {
if (!wrap) return 0;
*item->setting = item->maximum;
}
return 1;
}
int
changeMenuSettingPrevious (Menu *menu, int wrap) {
MenuItem *item = getCurrentMenuItem(menu);
if (activateMenuItem(item)) return 1;
if (!item->setting) return 0;
return adjustMenuSetting(item, decrementMenuSetting, wrap);
}
static int
incrementMenuSetting (const MenuItem *item, int wrap) {
if ((*item->setting)++ >= item->maximum) {
if (!wrap) return 0;
*item->setting = item->minimum;
}
return 1;
}
int
changeMenuSettingNext (Menu *menu, int wrap) {
MenuItem *item = getCurrentMenuItem(menu);
if (activateMenuItem(item)) return 1;
if (!item->setting) return 0;
return adjustMenuSetting(item, incrementMenuSetting, wrap);
}
int
changeMenuSettingScaled (Menu *menu, unsigned int index, unsigned int count) {
MenuItem *item = getCurrentMenuItem(menu);
if (activateMenuItem(item)) return 1;
if (item->setting) {
unsigned char oldSetting = *item->setting;
if (item->methods->getValue == getValue_numeric) {
*item->setting = rescaleInteger(index, count-1, item->maximum-item->minimum) + item->minimum;
} else {
*item->setting = index % (item->maximum + 1);
}
if (!item->changed || item->changed(item, *item->setting)) return 1;
*item->setting = oldSetting;
}
return 0;
}
MenuItem *
getCurrentMenuItem (Menu *menu) {
MenuItem *newItem = getSelectedMenuItem(menu);
MenuItem *oldItem = menu->activeItem;
if (newItem != oldItem) {
if (oldItem) endMenuItem(oldItem, 0);
menu->activeItem = beginMenuItem(newItem)? newItem: NULL;
}
return newItem;
}
Menu *
getCurrentSubmenu (Menu *menu) {
while (1) {
MenuItem *item = getCurrentMenuItem(menu);
if (item->methods != &menuItemMethods_submenu) break;
if (!item->data.submenu->opened) break;
menu = item->data.submenu->menu;
}
return menu;
}