// For licensing and usage information, read docs/release/winui_license.txt // MASTER //**************************************************************************** //============================================================ // // datamap.c - Win32 dialog and options bridge code // //============================================================ // standard windows headers #include #include #include #include // standard C headers // MAME/MAMEUI headers #include "mui_opts.h" #include "datamap.h" #include "winutf8.h" #include "corestr.h" #ifdef _MSC_VER #define snprintf _snprintf #endif //============================================================ // TYPE DEFINITIONS //============================================================ enum _control_type { CT_UNKNOWN, CT_BUTTON, CT_STATIC, CT_EDIT, CT_COMBOBOX, CT_TRACKBAR, CT_LISTVIEW }; typedef enum _control_type control_type; typedef struct _datamap_entry datamap_entry; struct _datamap_entry { // the basics about the entry int dlgitem; datamap_entry_type type; const char *option_name; // callbacks datamap_callback callbacks[DCT_COUNT]; get_option_name_callback get_option_name; // formats const char *int_format; const char *float_format; // trackbar options BOOL use_trackbar_options; float trackbar_min; float trackbar_max; float trackbar_increments; }; struct _datamap { int entry_count; datamap_entry entries[256]; // 256 options entries seems enough for now... }; typedef void (*datamap_default_callback)(datamap *map, HWND control, windows_options *opts, datamap_entry *entry, const char *option_name); //============================================================ // PROTOTYPES //============================================================ static datamap_entry *find_entry(datamap *map, int dlgitem); static control_type get_control_type(HWND control); static int control_operation(datamap *map, HWND dialog, windows_options *opts, datamap_entry *entry, datamap_callback_type callback_type); static void read_control(datamap *map, HWND control, windows_options *opts, datamap_entry *entry, const char *option_name); static void populate_control(datamap *map, HWND control, windows_options *opts, datamap_entry *entry, const char *option_name); static char *tztrim(float float_value); //============================================================ // datamap_create //============================================================ datamap *datamap_create(void) { datamap *map = (datamap *)malloc(sizeof(*map)); if (!map) return NULL; map->entry_count = 0; return map; } //============================================================ // datamap_free //============================================================ void datamap_free(datamap *map) { free(map); } //============================================================ // datamap_add //============================================================ void datamap_add(datamap *map, int dlgitem, datamap_entry_type type, const char *option_name) { // sanity check for too many entries if (!(map->entry_count < std::size(map->entries))) { printf("Datamap.cpp Line __LINE__ too many entries\n"); return; } // add entry to the datamap memset(&map->entries[map->entry_count], 0, sizeof(map->entries[map->entry_count])); map->entries[map->entry_count].dlgitem = dlgitem; map->entries[map->entry_count].type = type; map->entries[map->entry_count].option_name = option_name; map->entry_count++; } //============================================================ // datamap_set_callback //============================================================ void datamap_set_callback(datamap *map, int dlgitem, datamap_callback_type callback_type, datamap_callback callback) { datamap_entry *entry; assert(callback_type >= 0); assert(callback_type < DCT_COUNT); entry = find_entry(map, dlgitem); entry->callbacks[callback_type] = callback; } //============================================================ // datamap_set_option_name_callback //============================================================ void datamap_set_option_name_callback(datamap *map, int dlgitem, get_option_name_callback get_option_name) { datamap_entry *entry; entry = find_entry(map, dlgitem); entry->get_option_name = get_option_name; } //============================================================ // datamap_set_trackbar_range //============================================================ void datamap_set_trackbar_range(datamap *map, int dlgitem, float min, float max, float increments) { datamap_entry *entry = find_entry(map, dlgitem); entry->use_trackbar_options = TRUE; entry->trackbar_min = min; entry->trackbar_max = max; entry->trackbar_increments = increments; } //============================================================ // datamap_set_int_format //============================================================ void datamap_set_int_format(datamap *map, int dlgitem, const char *format) { datamap_entry *entry = find_entry(map, dlgitem); entry->int_format = format; } //============================================================ // datamap_set_float_format //============================================================ void datamap_set_float_format(datamap *map, int dlgitem, const char *format) { datamap_entry *entry = find_entry(map, dlgitem); entry->float_format = format; } //============================================================ // datamap_read_control //============================================================ BOOL datamap_read_control(datamap *map, HWND dialog, windows_options &opts, int dlgitem) { datamap_entry *entry = find_entry(map, dlgitem); return control_operation(map, dialog, &opts, entry, DCT_READ_CONTROL); } //============================================================ // datamap_read_all_controls //============================================================ void datamap_read_all_controls(datamap *map, HWND dialog, windows_options &opts) { for (int i = 0; i < map->entry_count; i++) control_operation(map, dialog, &opts, &map->entries[i], DCT_READ_CONTROL); } //============================================================ // datamap_populate_control //============================================================ void datamap_populate_control(datamap *map, HWND dialog, windows_options &opts, int dlgitem) { datamap_entry *entry = find_entry(map, dlgitem); control_operation(map, dialog, &opts, entry, DCT_POPULATE_CONTROL); } //============================================================ // datamap_populate_all_controls //============================================================ void datamap_populate_all_controls(datamap *map, HWND dialog, windows_options &opts) { for (int i = 0; i < map->entry_count; i++) control_operation(map, dialog, &opts, &map->entries[i], DCT_POPULATE_CONTROL); } //============================================================ // datamap_update_control //============================================================ void datamap_update_control(datamap *map, HWND dialog, windows_options &opts, int dlgitem) { datamap_entry *entry = find_entry(map, dlgitem); control_operation(map, dialog, &opts, entry, DCT_UPDATE_STATUS); } //============================================================ // datamap_update_all_controls //============================================================ void datamap_update_all_controls(datamap *map, HWND dialog, windows_options *opts) { for (int i = 0; i < map->entry_count; i++) control_operation(map, dialog, opts, &map->entries[i], DCT_UPDATE_STATUS); } //============================================================ // find_entry //============================================================ static datamap_entry *find_entry(datamap *map, int dlgitem) { for (int i = 0; i < map->entry_count; i++) if (map->entries[i].dlgitem == dlgitem) return &map->entries[i]; // should not reach here printf("Datamap.cpp line __LINE__ couldn't find an entry\n"); return NULL; } //============================================================ // get_control_type //============================================================ static control_type get_control_type(HWND control) { control_type type; TCHAR class_name[256]; GetClassName(control, class_name, std::size(class_name)); if (!_tcscmp(class_name, WC_BUTTON)) type = CT_BUTTON; else if (!_tcscmp(class_name, WC_STATIC)) type = CT_STATIC; else if (!_tcscmp(class_name, WC_EDIT)) type = CT_EDIT; else if (!_tcscmp(class_name, WC_COMBOBOX)) type = CT_COMBOBOX; else if (!_tcscmp(class_name, TRACKBAR_CLASS)) type = CT_TRACKBAR; else if (!_tcscmp(class_name, WC_LISTVIEW)) type = CT_LISTVIEW; else type = CT_UNKNOWN; return type; } //============================================================ // is_control_displayonly //============================================================ static BOOL is_control_displayonly(HWND control) { BOOL displayonly = false; switch(get_control_type(control)) { case CT_STATIC: displayonly = true; break; case CT_EDIT: displayonly = (GetWindowLong(control, GWL_STYLE) & ES_READONLY) ? true : false; break; default: displayonly = false; break; } if (!IsWindowEnabled(control)) displayonly = true; return displayonly; } //============================================================ // broadcast_changes //============================================================ static void broadcast_changes(datamap *map, HWND dialog, windows_options *opts, datamap_entry *entry, const char *option_name) { HWND other_control; const char *that_option_name; for (int i = 0; i < map->entry_count; i++) { // search for an entry with the same option_name, but is not the exact // same entry that_option_name = map->entries[i].option_name; if (map->entries[i].option_name && (&map->entries[i] != entry) && !strcmp(that_option_name, option_name)) { // we've found a control sharing the same option; populate it other_control = GetDlgItem(dialog, map->entries[i].dlgitem); if (other_control) populate_control(map, other_control, opts, &map->entries[i], that_option_name); } } } //============================================================ // control_operation //============================================================ static int control_operation(datamap *map, HWND dialog, windows_options *opts, datamap_entry *entry, datamap_callback_type callback_type) { static const datamap_default_callback default_callbacks[DCT_COUNT] = { read_control, populate_control, NULL }; int result = 0; const char *option_name; char option_name_buffer[64]; char option_value[1024] = {0, }; HWND control = GetDlgItem(dialog, entry->dlgitem); if (control) { // don't do anything if we're reading from a display-only control if ((callback_type != DCT_READ_CONTROL) || !is_control_displayonly(control)) { // figure out the option_name if (entry->get_option_name) { option_name_buffer[0] = '\0'; entry->get_option_name(map, dialog, control, option_name_buffer, std::size(option_name_buffer)); option_name = option_name_buffer; } else option_name = entry->option_name; // if reading, get the option value, solely for the purposes of comparison if ((callback_type == DCT_READ_CONTROL) && option_name) snprintf(option_value, std::size(option_value), "%s", opts->value(option_name)); if (entry->callbacks[callback_type]) { // use custom callback result = entry->callbacks[callback_type](map, dialog, control, opts, option_name); } else if (default_callbacks[callback_type] && option_name) { // use default callback default_callbacks[callback_type](map, control, opts, entry, option_name); } // the result is dependent on the type of control switch(callback_type) { case DCT_READ_CONTROL: // For callbacks that returned TRUE, do not broadcast_changes. if (!result) { // do a check to see if the control changed result = (option_name) && (strcmp(option_value, opts->value(option_name)) != 0); if (result) { // the value has changed; we may need to broadcast the change broadcast_changes(map, dialog, opts, entry, option_name); } } break; default: // do nothing break; } } } return result; } //============================================================ // trackbar_value_from_position //============================================================ static float trackbar_value_from_position(datamap_entry *entry, int position) { float position_f = position; if (entry->use_trackbar_options) position_f = (position_f * entry->trackbar_increments) + entry->trackbar_min; return position_f; } //============================================================ // trackbar_position_from_value //============================================================ static int trackbar_position_from_value(datamap_entry *entry, float value) { if (entry->use_trackbar_options) value = floor((value - entry->trackbar_min) / entry->trackbar_increments + 0.5); return (int) value; } //============================================================ // read_control //============================================================ static void read_control(datamap *map, HWND control, windows_options *opts, datamap_entry *entry, const char *option_name) { BOOL bool_value = 0; int int_value = 0; float float_value = 0; const char *string_value; int selected_index = 0; int trackbar_pos = 0; // use default read value behavior switch(get_control_type(control)) { case CT_BUTTON: //assert(entry->type == DM_BOOL); bool_value = Button_GetCheck(control); opts->set_value(option_name, bool_value, OPTION_PRIORITY_CMDLINE); break; case CT_COMBOBOX: selected_index = ComboBox_GetCurSel(control); if (selected_index >= 0) { switch(entry->type) { case DM_INT: int_value = (int) ComboBox_GetItemData(control, selected_index); opts->set_value(option_name, int_value, OPTION_PRIORITY_CMDLINE); break; case DM_STRING: string_value = (const char *) ComboBox_GetItemData(control, selected_index); opts->set_value(option_name, string_value ? string_value : "", OPTION_PRIORITY_CMDLINE); break; default: break; } } break; case CT_TRACKBAR: trackbar_pos = SendMessage(control, TBM_GETPOS, 0, 0); float_value = trackbar_value_from_position(entry, trackbar_pos); switch(entry->type) { case DM_INT: int_value = (int) float_value; if (int_value != opts->int_value(option_name)) { opts->set_value(option_name, int_value, OPTION_PRIORITY_CMDLINE); } break; case DM_FLOAT: // Use tztrim(float_value) or we get trailing zero's that break options_equal(). if (float_value != opts->float_value(option_name)) { opts->set_value(option_name, tztrim(float_value), OPTION_PRIORITY_CMDLINE); } break; default: break; } break; case CT_EDIT: // NYI break; case CT_STATIC: case CT_LISTVIEW: case CT_UNKNOWN: // non applicable break; } } //============================================================ // populate_control //============================================================ static void populate_control(datamap *map, HWND control, windows_options *opts, datamap_entry *entry, const char *option_name) { int i = 0; BOOL bool_value = 0; int int_value = 0; float float_value = 0; const char *string_value; const char *item_string; int selected_index = 0; char buffer[128]; int trackbar_range = 0; int trackbar_pos = 0; double trackbar_range_d = 0; // use default populate control value switch(get_control_type(control)) { case CT_BUTTON: assert(entry->type == DM_BOOL); bool_value = opts->bool_value(option_name); Button_SetCheck(control, bool_value); break; case CT_EDIT: case CT_STATIC: switch(entry->type) { case DM_STRING: string_value = opts->value(option_name); break; case DM_INT: int_value = opts->int_value(option_name); if (entry->int_format != NULL) snprintf(buffer, std::size(buffer), entry->int_format, int_value); else snprintf(buffer, std::size(buffer), "%d", int_value); string_value = buffer; break; case DM_FLOAT: float_value = opts->float_value(option_name); if (entry->float_format != NULL) snprintf(buffer, std::size(buffer), entry->float_format, float_value); else snprintf(buffer, std::size(buffer), "%f", float_value); string_value = buffer; break; default: string_value = ""; break; } if (string_value == NULL) string_value = ""; win_set_window_text_utf8(control, string_value); break; case CT_COMBOBOX: selected_index = 0; switch(entry->type) { case DM_INT: int_value = opts->int_value(option_name); for (i = 0; i < ComboBox_GetCount(control); i++) { if (int_value == (int) ComboBox_GetItemData(control, i)) { selected_index = i; break; } } break; case DM_STRING: string_value = opts->value(option_name); for (i = 0; i < ComboBox_GetCount(control); i++) { item_string = (const char *) ComboBox_GetItemData(control, i); if (!core_stricmp(string_value, item_string ? item_string : "")) { selected_index = i; break; } } break; default: break; } (void)ComboBox_SetCurSel(control, selected_index); break; case CT_TRACKBAR: // do we need to set the trackbar options? /* if (!entry->use_trackbar_options) { switch(options_get_range_type(opts, option_name)) { case OPTION_RANGE_NONE: // do nothing break; case OPTION_RANGE_INT: options_get_range_int(opts, option_name, &minval_int, &maxval_int); entry->use_trackbar_options = TRUE; entry->trackbar_min = minval_int; entry->trackbar_max = maxval_int; entry->trackbar_increments = 1; break; case OPTION_RANGE_FLOAT: options_get_range_float(opts, option_name, &minval_float, &maxval_float); entry->use_trackbar_options = TRUE; entry->trackbar_min = minval_float; entry->trackbar_max = maxval_float; entry->trackbar_increments = (float)0.05; break; } } */ // do we specify default options for this control? if so, we need to specify // the range if (entry->use_trackbar_options) { trackbar_range_d = floor(((entry->trackbar_max - entry->trackbar_min) / entry->trackbar_increments) + 0.5); trackbar_range = (int) trackbar_range_d; SendMessage(control, TBM_SETRANGEMIN, (WPARAM) FALSE, (LPARAM) 0); SendMessage(control, TBM_SETRANGEMAX, (WPARAM) FALSE, (LPARAM) trackbar_range); } switch(entry->type) { case DM_INT: int_value = opts->int_value(option_name); trackbar_pos = trackbar_position_from_value(entry, int_value); break; case DM_FLOAT: float_value = opts->float_value(option_name); trackbar_pos = trackbar_position_from_value(entry, float_value); break; default: trackbar_pos = 0; break; } SendMessage(control, TBM_SETPOS, (WPARAM) TRUE, (LPARAM) trackbar_pos); break; case CT_LISTVIEW: case CT_UNKNOWN: // non applicable break; } } // Return a string from a float value with trailing zeros removed. static char *tztrim(float float_value) { static char tz_string[20]; char float_string[20]; int i = 0; sprintf(float_string, "%f", float_value); char* ptr = float_string; // Copy before the '.' while (*ptr && *ptr != '.') tz_string[i++] = *ptr++; // add the '.' and the next digit if (*ptr == '.') { tz_string[i++] = *ptr++; tz_string[i++] = *ptr++; } // Keep copying until we hit a '0' while (*ptr && *ptr != '0') tz_string[i++] = *ptr++; // Null terminate tz_string[i] = '\0'; return tz_string; }