/** \file * Magic Lantern GUI */ /* * Copyright (C) 2009 Trammell Hudson * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "dryos.h" #include "version.h" #include "bmp.h" #include "gui.h" #include "config.h" #include "property.h" #include "lens.h" #include "font.h" #include "menu.h" #include "beep.h" #include "zebra.h" #include "focus.h" #include "menuhelp.h" #include "console.h" #include "debug.h" #include "lvinfo.h" #ifdef CONFIG_QEMU #define GUIMODE_ML_MENU 0 #endif #define CONFIG_MENU_ICONS //~ #define CONFIG_MENU_DIM_HACKS #undef SUBMENU_DEBUG_JUNKIE #define DOUBLE_BUFFERING 1 //~ #define MENU_KEYHELP_Y_POS (menu_lv_transparent_mode ? 425 : 430) #define MENU_HELP_Y_POS 435 #define MENU_HELP_Y_POS_2 458 #define MENU_WARNING_Y_POS (menu_lv_transparent_mode ? 425 : 458) #define MENU_BG_COLOR_HEADER_FOOTER 42 extern int bmp_color_scheme; #define MENU_BAR_COLOR (bmp_color_scheme ? COLOR_LIGHT_BLUE : COLOR_BLUE) #ifdef CONFIG_MENU_ICONS #define SUBMENU_OFFSET 48 #define MENU_OFFSET 38 #else #define SUBMENU_OFFSET 30 #define MENU_OFFSET 20 #endif #define MY_MENU_NAME "MyMenu" static struct menu * my_menu; static int my_menu_dirty = 0; /* modified settings menu */ #define MOD_MENU_NAME "Modified" static struct menu * mod_menu; static int mod_menu_dirty = 1; //for vscroll #define MENU_LEN 11 /* int sem_line = 0; int take_semapore_dbg(int sem, int timeout, int line) { bmp_printf(FONT_LARGE, 0, 0, "take: %d (%d) ", sem_line, line); int ans = take_semaphore(sem, timeout); sem_line = line; bmp_printf(FONT_LARGE, 0, 0, "take: OK "); return ans; } #define TAKE_SEMAPHORE(sem, timeout) take_semapore_dbg(sem, timeout, __LINE__) #define GIVE_SEMAPHORE(sem) { sem_line = 0; give_semaphore(sem); } */ static struct semaphore * menu_sem; extern struct semaphore * gui_sem; static struct semaphore * menu_redraw_sem; static int menu_damage; static int menu_shown = false; static int menu_lv_transparent_mode; // for ISO, kelvin... static int config_dirty = 0; static int menu_flags_save_dirty = 0; static int menu_flags_load_dirty = 1; //~ static int menu_hidden_should_display_help = 0; static int menu_zebras_mirror_dirty = 0; // to clear zebras from mirror (avoids display artifacts if, for example, you enable false colors in menu, then you disable them, and preview LV) int menu_help_active = 0; // also used in menuhelp.c int menu_redraw_blocked = 0; // also used in flexinfo static int menu_redraw_cancel = 0; static int submenu_level = 0; static int edit_mode = 0; static int customize_mode = 0; static int advanced_mode = 0; /* cached value; only for submenus for now */ static int caret_position = 0; #define SUBMENU_OR_EDIT (submenu_level || edit_mode) #define EDIT_OR_TRANSPARENT (edit_mode || menu_lv_transparent_mode) static CONFIG_INT("menu.junkie", junkie_mode, 0); //~ static CONFIG_INT("menu.set", set_action, 2); //~ static CONFIG_INT("menu.start.my", start_in_my_menu, 0); static int is_customize_selected(); extern void CancelDateTimer(); #define CAN_HAVE_PICKBOX(entry) ((entry)->max > (entry)->min && (((entry)->max - (entry)->min < 15) || (entry)->choices) && IS_ML_PTR((entry)->priv)) #define SHOULD_HAVE_PICKBOX(entry) ((entry)->max > (entry)->min + 1 && (entry)->max - (entry)->min < 10 && IS_ML_PTR((entry)->priv)) #define IS_BOOL(entry) (((entry)->max - (entry)->min == 1 && IS_ML_PTR((entry)->priv)) || (entry->icon_type == IT_BOOL)) #define IS_ACTION(entry) ((entry)->icon_type == IT_ACTION || (entry)->icon_type == IT_SUBMENU) #define SHOULD_USE_EDIT_MODE(entry) (!IS_BOOL(entry) && !IS_ACTION(entry)) #define HAS_SINGLE_ITEM_SUBMENU(entry) ((entry)->children && !(entry)->children[0].next && !(entry)->children[0].prev && !MENU_IS_EOL(entry->children)) #define IS_SINGLE_ITEM_SUBMENU_ENTRY(entry) (!(entry)->next && !(entry)->prev) static int can_be_turned_off(struct menu_entry * entry) { return (IS_BOOL(entry) && entry->icon_type != IT_DICE) || entry->icon_type == IT_PERCENT_OFF || entry->icon_type == IT_PERCENT_LOG_OFF || entry->icon_type == IT_DICE_OFF || entry->icon_type == IT_SUBMENU; } #define HAS_HIDDEN_FLAG(entry) ((entry)->hidden) #define HAS_JHIDDEN_FLAG(entry) ((entry)->jhidden) #define HAS_SHIDDEN_FLAG(entry) ((entry)->shidden) // this is *never* displayed #define HAS_STARRED_FLAG(entry) ((entry)->starred) // in junkie mode, this is only displayed in MyMenu (implicit hiding from main menus) #define HAS_CURRENT_HIDDEN_FLAG(entry) ( \ (!junkie_mode && HAS_HIDDEN_FLAG(entry)) || \ (junkie_mode && HAS_JHIDDEN_FLAG(entry)) ) // junkie mode, entry present in my menu, hide it from normal menu #define IMPLICIT_MY_MENU_HIDING(entry) \ (junkie_mode && HAS_STARRED_FLAG(entry)) static int is_visible(struct menu_entry * entry) { return ( !(HAS_CURRENT_HIDDEN_FLAG(entry) || IMPLICIT_MY_MENU_HIDING(entry)) || customize_mode || junkie_mode==2 ) && ( !HAS_SHIDDEN_FLAG(entry) ) && ( advanced_mode || !entry->advanced || entry->selected || config_var_was_changed(entry->priv) ) ; } static int g_submenu_width = 0; //~ #define g_submenu_width 720 static int redraw_flood_stop = 0; #define MENU_REDRAW 1 static int hist_countdown = 3; // histogram is slow, so draw it less often int is_submenu_or_edit_mode_active() { return gui_menu_shown() && SUBMENU_OR_EDIT; } int get_menu_edit_mode() { return edit_mode; } //~ static CONFIG_INT("menu.transparent", semitransparent, 0); static CONFIG_INT("menu.first", menu_first_by_icon, ICON_i); void menu_set_dirty() { menu_damage = 1; } int is_menu_help_active() { return gui_menu_shown() && menu_help_active; } static void select_menu_by_icon(int icon); static void menu_help_go_to_selected_entry(struct menu * menu); //~ static void menu_init( void ); static void menu_show_version(void); static struct menu * get_current_submenu(); static struct menu * get_selected_menu(); static void menu_make_sure_selection_is_valid(); static void config_menu_load_flags(); static int guess_submenu_enabled(struct menu_entry * entry); static void menu_draw_icon(int x, int y, int type, intptr_t arg, int warn); // private static struct menu_entry * entry_find_by_name(const char* name, const char* entry_name); static struct menu_entry * get_selected_entry(struct menu * menu); static void submenu_display(struct menu * submenu); static void start_redraw_flood(); static struct menu * menu_find_by_name(const char * name, int icon); void menu_toggle_submenu(); extern int gui_state; void menu_enable_lv_transparent_mode() { if (lv) menu_lv_transparent_mode = 1; menu_damage = 1; } void menu_disable_lv_transparent_mode() { menu_lv_transparent_mode = 0; } int menu_active_but_hidden() { return gui_menu_shown() && ( menu_lv_transparent_mode ); } int menu_active_and_not_hidden() { return gui_menu_shown() && !( menu_lv_transparent_mode && hist_countdown < 2 ); } static void draw_version( void ) { bmp_printf( FONT( FONT_SMALL, COLOR_WHITE, COLOR_BLUE ), 0, 0, "Magic Lantern Firmware version %s (%s)\nBuilt on%s by %s\n%s", build_version, build_id, build_date, build_user, "http://www.magiclantern.fm/" ); /* int y = 200; struct config * config = global_config; bmp_printf( FONT_SMALL, 0, y, "Config: %x", (unsigned) global_config ); y += font_small.height; while( config ) { bmp_printf( FONT_SMALL, 0, y, "'%s' => '%s'", config->name, config->value ); config = config->next; y += font_small.height; } */ } #ifdef CONFIG_RELEASE_BUILD int beta_should_warn() { return 0; } #else CONFIG_INT("beta.warn", beta_warn, 0); static int get_beta_timestamp() { #ifdef CONFIG_QEMU return 1; #endif struct tm now; LoadCalendarFromRTC(&now); return now.tm_mday; } static int beta_should_warn() { int t = get_beta_timestamp(); return beta_warn != t; } static void beta_set_warned() { unsigned t = get_beta_timestamp(); beta_warn = t; } #endif static struct menu_entry customize_menu[] = { { .name = "Customize Menus", .priv = &customize_mode, .max = 1, //~ .choices = CHOICES("OFF", "MyMenu items", "Hide items"), } }; static int is_customize_selected(struct menu * menu) // argument is optional, just for speedup { struct menu_entry * selected_entry = get_selected_entry(menu); if (selected_entry == &customize_menu[0]) return 1; return 0; } #define MY_MENU_ENTRY \ { \ .hidden = 1, \ .jhidden = 1, \ }, static MENU_UPDATE_FUNC(menu_placeholder_unused_update) { info->custom_drawing = CUSTOM_DRAW_THIS_ENTRY; // do not draw it at all if (entry->selected && !junkie_mode) bmp_printf(FONT(FONT_LARGE, 45, COLOR_BLACK), 250, info->y, "(empty)"); } static struct menu_entry my_menu_placeholders[] = { MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY }; /* static struct menu_entry menu_prefs[] = { { .name = "Menu Preferences", .select = menu_open_submenu, .children = (struct menu_entry[]) { { .name = "Start in MyMenu", .priv = &start_in_my_menu, .max = 1, .help = "Go to My Menu every time you open ML menu.", }, { .name = "SET action", .priv = &set_action, .max = 2, .choices = (const char *[]) {"Pickbox", "Toggle", "Auto"}, .help = "Choose the behavior of SET key in ML menu:", .help2 = "Pickbox: SET shows a list of choices, select and confirm.\n" "Toggle: SET will toggle ON/OFF or increment current value.\n" "Auto: SET toggles ON/OFF items, pickbox for 3+ choices.", }, MENU_EOL, }, } }; */ /* todo: use dynamic entries, like in file_man */ static struct menu_entry mod_menu_placeholders[] = { MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY MY_MENU_ENTRY }; void customize_menu_init() { menu_add("Prefs", customize_menu, COUNT(customize_menu)); // this is added at the end, after all the others my_menu = menu_find_by_name( MY_MENU_NAME,ICON_ML_MYMENU ); menu_add(MY_MENU_NAME, my_menu_placeholders, COUNT(my_menu_placeholders)); mod_menu = menu_find_by_name(MOD_MENU_NAME, ICON_ML_MODIFIED); menu_add(MOD_MENU_NAME, mod_menu_placeholders, COUNT(mod_menu_placeholders)); } static struct menu * menus; struct menu * menu_get_root() { return menus; } // ISO 3 R10": 10, 12, 15, 20, 25, 30, 40, 50, 60, 80, 100 /* static int round_to_R10(int val) { if (val < 0) return -round_to_R10(-val); int mag = 1; while (val >= 30) { val /= 10; mag *= 10; } if (val <= 6) {} else if (val <= 8) val = 8; else if (val <= 11) val = 10; else if (val <= 13) val = 12; else if (val <= 17) val = 15; else if (val <= 22) val = 20; else if (val <= 27) val = 25; else val = 30; return val * mag; }*/ // ISO 3 R20": 10, 11, 12, 14, 15, 18, 20, 22, 25, 28, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90, 100 static int round_to_R20(int val) { if (val < 0) return -round_to_R20(-val); int mag = 1; while (val >= 60) { val /= 10; mag *= 10; } if (val <= 12) {} else if (val <= 14) val = 14; else if (val <= 16) val = 15; else if (val <= 19) val = 18; else if (val <= 21) val = 20; else if (val <= 23) val = 22; else if (val <= 26) val = 25; else if (val <= 29) val = 28; else if (val <= 32) val = 30; else if (val <= 37) val = 35; else if (val <= 42) val = 40; else if (val <= 47) val = 45; else if (val <= 52) val = 50; else if (val <= 57) val = 55; else val = 60; return val * mag; } static void menu_numeric_toggle_R20(int* val, int delta, int min, int max) { ASSERT(IS_ML_PTR(val)); int v = *val; if (v >= max && delta > 0) v = min; else if (v <= min && delta < 0) v = max; else { int v0 = round_to_R20(v); if (v0 != v && SGN(v0 - v) == SGN(delta)) // did we round in the correct direction? if so, stop here { *val = v0; return; } // slow, but works (fast enough for numbers like 5000) while (v0 == round_to_R20(v)) v += delta; v = COERCE(round_to_R20(v), min, max); } set_config_var_ptr(val, v); } static void menu_numeric_toggle_long_range(int* val, int delta, int min, int max) { ASSERT(IS_ML_PTR(val)); int v = *val; if (v >= max && delta > 0) v = min; else if (v <= min && delta < 0) v = max; else { int a = ABS(v + delta); int M = 1; if (a >= 20000) M = 1000; else if (a >= 10000) M = 500; else if (a >= 2000) M = 100; else if (a >= 1000) M = 50; else if (a >= 200) M = 10; else if (a >= 100) M = 5; v += delta; while (v % M) { if (v >= max) break; if (v <= min) break; v += delta; } } set_config_var_ptr(val, v); } /* for editing with caret */ static int get_delta(struct menu_entry * entry, int sign) { if(!EDIT_OR_TRANSPARENT) return sign; else if(entry->unit == UNIT_DEC) return sign * powi(10, caret_position); else if(entry->unit == UNIT_HEX) return sign * powi(16, caret_position); else if(entry->unit == UNIT_TIME) { if(caret_position == 2) return sign * 10; else if(caret_position == 4) return sign * 60; else if(caret_position == 5) return sign * 600; else if(caret_position == 7) return sign * 3600; else if(caret_position == 8) return sign * 36000; } return sign; } static int uses_caret_editing(struct menu_entry * entry) { return entry->select == 0 && /* caret editing requires its own toggle logic */ (entry->unit == UNIT_DEC || entry->unit == UNIT_HEX || entry->unit == UNIT_TIME); /* only these caret edit modes are supported */ } static int editing_with_caret(struct menu_entry * entry) { return EDIT_OR_TRANSPARENT && uses_caret_editing(entry); } static void caret_move(struct menu_entry * entry, int delta) { int max = (entry->unit == UNIT_HEX) ? log2i(MAX(ABS(entry->max),ABS(entry->min)))/4 : (entry->unit == UNIT_DEC) ? log10i(MAX(ABS(entry->max),ABS(entry->min))/2) : (entry->unit == UNIT_TIME) ? 7 : 0; menu_numeric_toggle(&caret_position, delta, 0, max); /* skip "h", "m" and "s" positions for time fields */ if(entry->unit == UNIT_TIME && (caret_position == 0 || caret_position == 3 || caret_position == 6)) { menu_numeric_toggle(&caret_position, delta, 0, max); } } void menu_numeric_toggle(int* val, int delta, int min, int max) { ASSERT(IS_ML_PTR(val)); set_config_var_ptr(val, MOD(*val - min + delta, max - min + 1) + min); } void menu_numeric_toggle_time(int * val, int delta, int min, int max) { int deltas[] = {1,5,15,30,60,300,900,1800,3600}; int i = 0; for(i = COUNT(deltas) - 1; i > 0; i--) if(deltas[i] * 4 <= (delta < 0 ? *val - 1 : *val)) break; delta *= deltas[i]; int new_val = (*val + delta) / delta * delta; if(new_val > max) new_val = min; if(new_val < min) new_val = max; set_config_var_ptr(val, new_val); } static void menu_numeric_toggle_fast(int* val, int delta, int min, int max, int is_time) { ASSERT(IS_ML_PTR(val)); static int prev_t = 0; static int prev_delta = 1000; int t = get_ms_clock_value(); if(is_time) menu_numeric_toggle_time(val, delta, min, max); else if (max - min > 20) { if (t - prev_t < 200 && prev_delta < 200) menu_numeric_toggle_R20(val, delta, min, max); else menu_numeric_toggle_long_range(val, delta, min, max); } else { set_config_var_ptr(val, MOD(*val - min + delta, max - min + 1) + min); } prev_delta = t - prev_t; prev_t = t; } static void entry_guess_icon_type(struct menu_entry * entry) { if (entry->icon_type == IT_AUTO) { if (entry->select == menu_open_submenu) { entry->icon_type = IT_SUBMENU; } else if (!IS_ML_PTR(entry->priv) || entry->select == run_in_separate_task) { entry->icon_type = IT_ACTION; } else if(entry->choices) { const char* first_choice = entry->choices[entry->min]; if (streq(first_choice, "OFF") || streq(first_choice, "Hide")) entry->icon_type = entry->max == 1 ? IT_BOOL : IT_DICE_OFF; else if (streq(first_choice, "ON")) entry->icon_type = IT_BOOL_NEG; else if (streq(first_choice, "Small")) entry->icon_type = IT_SIZE; else entry->icon_type = IT_DICE; } else if (entry->min != entry->max) { entry->icon_type = entry->max == 1 && entry->min == 0 ? IT_BOOL : entry->max - entry->min > 10 ? (entry->max * entry->min <= 0 ? IT_PERCENT_LOG_OFF : IT_PERCENT_LOG) : (entry->max * entry->min <= 0 ? IT_PERCENT_OFF : IT_PERCENT); } else entry->icon_type = IT_BOOL; } } static int entry_guess_enabled(struct menu_entry * entry) { if (entry->icon_type == IT_BOOL || entry->icon_type == IT_DICE_OFF || entry->icon_type == IT_PERCENT_OFF || entry->icon_type == IT_PERCENT_LOG_OFF) return MENU_INT(entry); else if (entry->icon_type == IT_BOOL_NEG) return !MENU_INT(entry); else if (entry->icon_type == IT_SUBMENU) return guess_submenu_enabled(entry); else return 1; } static int guess_submenu_enabled(struct menu_entry * entry) { if IS_ML_PTR(entry->priv) // if it has a priv field, use it as truth value for the entire group { return MENU_INT(entry); } else { // otherwise, look in the children submenus; if one is true, then submenu icon is drawn as "true" struct menu_entry * e = entry->children; for( ; e ; e = e->next ) { if (MENU_INT(e) && can_be_turned_off(e)) { return 1; } } return 0; } } static int log_percent(struct menu_entry * entry) { if (entry->min * entry->max < 0) // goes to both sizes? consider 50% in the middle { int v = MENU_INT(entry); if (v == 0) return 50; else if (v > 0) return 50 + log_length(v + 1) * 50 / log_length(entry->max + 1); else return 50 - log_length(-v + 1) * 50 / log_length(-entry->min + 1); } else { return log_length(SELECTED_INDEX(entry) + 1) * 100 / log_length(NUM_CHOICES(entry)); } } static void entry_draw_icon( struct menu_entry * entry, int x, int y, int enabled, int warn ) { switch (entry->icon_type) { case IT_BOOL: case IT_BOOL_NEG: menu_draw_icon(x, y, MNI_BOOL(enabled), 0, warn); break; case IT_ACTION: menu_draw_icon(x, y, MNI_ACTION, 0, warn); break; case IT_ALWAYS_ON: menu_draw_icon(x, y, MNI_AUTO, 0, warn); break; //~ case IT_SIZE: //~ if (!enabled) menu_draw_icon(x, y, MNI_OFF, 0, warn); //~ else menu_draw_icon(x, y, MNI_SIZE, SELECTED_INDEX(entry) | (NUM_CHOICES(entry) << 16), warn); //~ break; case IT_DICE: if (!enabled) menu_draw_icon(x, y, MNI_DICE_OFF, 0 | (NUM_CHOICES(entry) << 16), warn); else menu_draw_icon(x, y, MNI_DICE, SELECTED_INDEX(entry) | (NUM_CHOICES(entry) << 16), warn); break; case IT_DICE_OFF: if (!enabled) menu_draw_icon(x, y, MNI_DICE_OFF, 0 | (NUM_CHOICES(entry) << 16), warn); menu_draw_icon(x, y, MNI_DICE_OFF, SELECTED_INDEX(entry) | (NUM_CHOICES(entry) << 16), warn); break; case IT_PERCENT_OFF: { int p = SELECTED_INDEX(entry) * 100 / (NUM_CHOICES(entry)-1); if (!enabled) menu_draw_icon(x, y, MNI_PERCENT_OFF, p, warn); menu_draw_icon(x, y, MNI_PERCENT_ALLOW_OFF, p, warn); break; } case IT_PERCENT: //~ if (entry->min < 0) menu_draw_icon(x, y, MNI_PERCENT_PM, (CURRENT_VALUE & 0xFF) | ((entry->min & 0xFF) << 8) | ((entry->max & 0xFF) << 16), warn); menu_draw_icon(x, y, MNI_PERCENT, SELECTED_INDEX(entry) * 100 / (NUM_CHOICES(entry)-1), warn); break; case IT_PERCENT_LOG_OFF: { int p = log_percent(entry); if (!enabled) menu_draw_icon(x, y, MNI_PERCENT_OFF, p, warn); menu_draw_icon(x, y, MNI_PERCENT_ALLOW_OFF, p, warn); break; } case IT_PERCENT_LOG: { int p = log_percent(entry); menu_draw_icon(x, y, MNI_PERCENT, p, warn); break; } //~ case IT_NAMED_COLOR: //~ if (!enabled) menu_draw_icon(x, y, MNI_OFF, 0, warn); //~ else menu_draw_icon(x, y, MNI_NAMED_COLOR, (intptr_t) entry->choices[SELECTED_INDEX(entry)], warn); //~ break; case IT_DISABLE_SOME_FEATURE: menu_draw_icon(x, y, MENU_INT(entry) ? MNI_DISABLE : MNI_NEUTRAL, 0, warn); break; /* case IT_DISABLE_SOME_FEATURE_NEG: menu_draw_icon(x, y, MENU_INT(entry) ? MNI_NEUTRAL : MNI_DISABLE, 0, warn); break; case IT_REPLACE_SOME_FEATURE: menu_draw_icon(x, y, MENU_INT(entry) ? MNI_ON : MNI_NEUTRAL, 0, warn); break; */ case IT_SUBMENU: { int value = guess_submenu_enabled(entry); if (!enabled) value = 0; menu_draw_icon(x, y, MNI_SUBMENU, value, warn); break; } } } /* static struct menu_entry* menu_find_by_id_entry( struct menu_entry* root, uint32_t id ) { struct menu_entry * menu = root; for( ; menu ; menu = menu->next ) { if( menu->id == id ) { return menu; } if( menu->children ) { struct menu_entry * ch = menu_find_by_id_entry(menu->children, id); if (ch!=NULL) return ch; } } return NULL; } struct menu_entry * menu_find_by_id( uint32_t id ) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if( menu->id == id ) { return menu->children; } if( menu->children ) { struct menu_entry * ch = menu_find_by_id_entry(menu->children, id); if (ch!=NULL) return ch; } } return NULL; } */ static struct menu * menu_find_by_name( const char * name, int icon ) { take_semaphore( menu_sem, 0 ); struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if( streq( menu->name, name ) ) { if (icon && !menu->icon) menu->icon = icon; give_semaphore( menu_sem ); return menu; } // Stop just before we get to the end if( !menu->next ) break; } // Not found; create it struct menu * new_menu = malloc( sizeof(*new_menu) ); if( !new_menu ) { give_semaphore( menu_sem ); return NULL; } new_menu->name = name; new_menu->icon = icon; new_menu->prev = menu; new_menu->next = NULL; // Inserting at end new_menu->children = NULL; new_menu->submenu_width = 0; new_menu->submenu_height = 0; new_menu->split_pos = -16; new_menu->scroll_pos = 0; new_menu->advanced = 0; // menu points to the last entry or NULL if there are none if( menu ) { // We are adding to the end menu->next = new_menu; new_menu->selected = 0; } else { // This is the first one menus = new_menu; new_menu->selected = 1; } give_semaphore( menu_sem ); return new_menu; } static int get_menu_visible_count(struct menu * menu) { struct menu_entry * entry = menu->children; int n = 0; while( entry ) { if (is_visible(entry)) n ++; entry = entry->next; } return n; } static int get_menu_selected_pos(struct menu * menu) { struct menu_entry * entry = menu->children; int n = 0; while( entry ) { if (is_visible(entry)) { n ++; if (entry->selected) return n; } entry = entry->next; } return 0; } static int menu_has_visible_items(struct menu * menu) { if (junkie_mode) // hide Debug and Help { if ( streq(menu->name, "Debug") || streq(menu->name, "Help") || //~ streq(menu->name, "Scripts") || streq(menu->name, "Modules") || streq(menu->name, MOD_MENU_NAME) || 0) return 0; } struct menu_entry * entry = menu->children; while( entry ) { if (entry == &customize_menu[0]) // hide the Customize menu if everything else from Prefs is also hidden goto next; if (is_visible(entry)) { return 1; } next: entry = entry->next; } return 0; } static int are_there_any_visible_menus() { struct menu * menu = menus; while( menu ) { if (!IS_SUBMENU(menu) && menu_has_visible_items(menu)) { return 1; } menu = menu->next; } return 0; } #if defined(POSITION_INDEPENDENT) /* when compiling position independent, we have to fix all link-time addresses to load-time addresses */ void menu_fixup_pic(struct menu_entry * new_entry, int count) { struct menu_entry * main_ptr = new_entry; struct menu_entry * sub_ptr; while(count) { main_ptr->select = PIC_RESOLVE(main_ptr->select); //~ main_ptr->select_reverse = PIC_RESOLVE(main_ptr->select_reverse); main_ptr->select_Q = PIC_RESOLVE(main_ptr->select_Q); main_ptr->display = PIC_RESOLVE(main_ptr->display); main_ptr->help = PIC_RESOLVE(main_ptr->help); main_ptr->name = PIC_RESOLVE(main_ptr->name); main_ptr->priv = PIC_RESOLVE(main_ptr->priv); main_ptr->children = PIC_RESOLVE(main_ptr->children); main_ptr->choices = PIC_RESOLVE(main_ptr->choices); if(main_ptr->choices) { for(int pos = 0; pos <= main_ptr->max; pos++) { main_ptr->choices[pos] = PIC_RESOLVE(main_ptr->choices[pos]); } } if(main_ptr->children) { int entries = 0; while(main_ptr->children[entries].priv != MENU_EOL_PRIV) { entries++; } menu_fixup_pic(main_ptr->children, entries); } main_ptr++; count--; } } #endif static void menu_update_split_pos(struct menu * menu, struct menu_entry * entry) { // auto adjust width so that all things can be printed nicely // only "negative" numbers are auto-adjusted (if you override the width, you do so with a positive value) if (entry->name && menu->split_pos < 0)// && entry->priv) { menu->split_pos = -MAX(-menu->split_pos, bmp_string_width(FONT_LARGE, entry->name)/20 + 2); if (-menu->split_pos > 28) menu->split_pos = -28; } } static void placeholder_copy(struct menu_entry * dst, struct menu_entry * src) { /* keep linked list pointers and customization flags from the old entry */ void* next = dst->next; void* prev = dst->prev; int selected = dst->selected; int starred = dst->starred; int hidden = dst->hidden; int jhidden = dst->jhidden; /* also keep the name pointer, which will help when removing the menu and restoring the placeholder */ char* name = (char*) dst->name; memcpy(dst, src, sizeof(struct menu_entry)); dst->next = next; dst->prev = prev; dst->name = name; dst->selected = selected; dst->starred = starred; dst->hidden = hidden; dst->jhidden = jhidden; } /* if we find a placeholder entry, use it for changing the menu order */ /* todo: check interference with menu customization */ static void menu_update_placeholder(struct menu * menu, struct menu_entry * new_entry) { if (!menu) return; for (struct menu_entry * entry = menu->children; entry; entry = entry->next) { if (entry != new_entry && MENU_IS_PLACEHOLDER(entry) && streq(entry->name, new_entry->name)) { /* found, let's try to swap the entries */ placeholder_copy(entry, new_entry); entry->shidden = 0; new_entry->shidden = 1; if (entry->starred) my_menu_dirty = 1; /* warning: the unused entry is still kept in place, but hidden; important to delete? */ break; } } } void menu_add_base( const char * name, struct menu_entry * new_entry, int count, bool update_placeholders ) { #if defined(POSITION_INDEPENDENT) menu_fixup_pic(new_entry, count); #endif // There is nothing to display. Sounds crazy (but might result from ifdef's) if ( count == 0 ) return; // Walk the menu list to find a menu struct menu * menu = menu_find_by_name( name, 0); if( !menu ) return; menu_flags_load_dirty = 1; int count0 = count; // for submenus take_semaphore( menu_sem, 0 ); struct menu_entry * head = menu->children; if( !head ) { // First one -- insert it as the selected item head = menu->children = new_entry; //~ if (new_entry->id == 0) new_entry->id = menu_id_increment++; new_entry->next = NULL; new_entry->prev = NULL; new_entry->parent_menu = menu; new_entry->selected = 1; menu_update_split_pos(menu, new_entry); entry_guess_icon_type(new_entry); new_entry++; count--; } // Find the end of the entries on the menu already while( head->next ) head = head->next; for (int i = 0; i < count; i++) { new_entry->selected = 0; new_entry->next = NULL; new_entry->prev = head; new_entry->parent_menu = menu; head->next = new_entry; head = new_entry; menu_update_split_pos(menu, new_entry); entry_guess_icon_type(new_entry); if (update_placeholders) menu_update_placeholder(menu, new_entry); new_entry++; } give_semaphore( menu_sem ); // create submenus struct menu_entry * entry = head; for (int i = 0; i < count0; i++) { if (entry->children) { int count = 0; struct menu_entry * child = entry->children; while (!MENU_IS_EOL(child)) { child->depends_on |= entry->depends_on; // inherit dependencies child->works_best_in |= entry->works_best_in; count++; child++; } struct menu * submenu = menu_find_by_name( entry->name, ICON_ML_SUBMENU); if (submenu->children != entry->children) // submenu is reused, do not add it twice menu_add(entry->name, entry->children, count); submenu->submenu_width = entry->submenu_width; submenu->submenu_height = entry->submenu_height; /* ensure the "children" field always points to the very first item in the submenu */ /* (important when merging 2 submenus) */ while (entry->children->prev) entry->children = entry->children->prev; } entry = entry->prev; if (!entry) break; } } void menu_add( const char * name, struct menu_entry * new_entry, int count ) { // Update placeholders menu_add_base(name, new_entry, count, true); } static void menu_remove_entry(struct menu * menu, struct menu_entry * entry) { if (menu->children == entry) { menu->children = entry->next; } if (entry->prev) { // printf("link %s to %x\n", entry->prev->name, entry->next); entry->prev->next = entry->next; } if (entry->next) { // printf("link %s to %x\n", entry->next->name, entry->prev); entry->next->prev = entry->prev; } /* remove the submenu */ /* fixme: won't work with composite submenus */ if (entry->children) { struct menu * submenu = menu_find_by_name( entry->name, ICON_ML_SUBMENU); if (submenu) { // printf("unlink submenu %s\n", submenu->name); submenu->children = 0; } } /* look for placeholder */ for (struct menu_entry * placeholder = entry->prev; placeholder; placeholder = placeholder->prev) { if (streq(placeholder->name, entry->name)) { // printf("restore placeholder %s\n", entry->name); struct menu_entry restored_placeholder = MENU_PLACEHOLDER(placeholder->name); placeholder_copy(placeholder, &restored_placeholder); break; } } } void menu_remove( const char * name, struct menu_entry * old_entry, int count ) { if ( count == 0 ) return; struct menu * menu = menu_find_by_name( name, 0); if( !menu ) return; menu_flags_load_dirty = 1; int removed = 0; struct menu_entry * entry = menu->children; while(entry && removed < count) { if (entry >= old_entry && entry < old_entry + count) { menu_remove_entry(menu, entry); removed++; } entry = entry->next; } } static void dot(int x, int y, int color, int radius) { fill_circle(x+16, y+16, radius, color); } #ifdef CONFIG_MENU_ICONS static void maru(int x, int y, int color) { dot(x, y, color, 10); } static void batsu(int x, int y, int c) { int i; for (i = 1; i < 4; i++) { draw_line(x + 8 + i, y + 9, x + 21 + i, y + 22, c); draw_line(x + 21 + i, y + 9, x + 8 + i, y + 22, c); } } /* static void crossout(int x, int y, int color) { x += 16; y += 16; int r; for (r = 9; r < 10; r++) { int i = r-9; draw_circle(x, y, r, color); draw_circle(x + 1, y, r, color); draw_line(x + 5 + i, y - 5, x - 5 + i, y + 5, color); } } static void percent(int x, int y, int value, int fg, int bg) { int i; y -= 2; value = value * 28 / 100; for (i = 0; i < 28; i++) draw_line(x + 2 + i, y + 25, x + 2 + i, y + 25 - i/3 - 5, i <= value ? fg : bg ); } static void clockmeter(int x, int y, int value, int fg, int bg) { fill_circle(x+16, y+16, 10, bg); for (int a = 0; a <= value * 3600 / 100; a+=10) draw_angled_line(x+16, y+16, 10, a-900, fg); draw_circle(x+16, y+16, 10, fg); } static void clockmeter_half(int x, int y, int value, int fg, int bg) { int thr = value * 1800 / 100; for (int a = 1800; a >=0 ; a-=5) draw_angled_line(x+16, y+21, 12, a-1800, a <= thr ? fg : bg); } */ /* static void clockmeter_pm(int x, int y, uint32_t arg, int fg, int bg) { int value = (int8_t)(arg & 0xFF); int min = (int8_t)((arg>>8) & 0xFF); int max = (int8_t)((arg>>16) & 0xFF); int M = MAX(ABS(min), ABS(max)); int thr = value * 1800 / M; for (int a = 0; a <= 1800; a+=5) { int v = a * M / 1800; if (v > max) break; draw_angled_line(x+16, y+21, 12, a-1800, a <= thr ? fg : bg); } for (int a = 0; a >= -1800; a-=5) { int v = a * M / 1800; if (v < min) break; draw_angled_line(x+16, y+21, 12, a-1800, a >= thr ? fg : bg); } } */ static void playicon(int x, int y, int color) { int i; for (i = 7; i < 32-7; i++) { draw_line(x + 9, y + i, x + 23, y + 16, color); draw_line(x + 9, y + i, x + 23, y + 16, color); } } static void leftright_sign(int x, int y) { int i; for (i = 5; i < 32-5; i++) { draw_line(x + 3, y + i, x + 18 + 3, y + 16, COLOR_CYAN); draw_line(x + 3, y + i, x + 18 + 3, y + 16, COLOR_CYAN); draw_line(x - 3, y + i, x - 18 - 3, y + 16, COLOR_CYAN); draw_line(x - 3, y + i, x - 18 - 3, y + 16, COLOR_CYAN); } } /* void submenu_icon(int x, int y) { //~ int color = COLOR_WHITE; x -= MENU_OFFSET; //~ bmp_draw_rect(45, x+2, y+5, 32-3, 32-10+1); draw_line(x+20, y+28, x+30, y+28, COLOR_WHITE); for (int i = -2; i <= 2; i++) draw_line(x+26, y+28+i, x+30, y+28, COLOR_WHITE); //~ for (int r = 0; r < 2; r++) //~ { //~ draw_circle(x + 30, y + 28, r, color); //~ draw_circle(x + 23, y + 28, r, color); //~ draw_circle(x + 16, y + 28, r, color); //~ } } */ /* static void size_icon(int x, int y, int current, int nmax, int color) { dot(x, y, color, COERCE(current * (nmax > 2 ? 9 : 7) / (nmax-1) + 3, 1, 12)); } static void dice_icon(int x, int y, int current, int nmax, int color_on, int color_off) { #define C(i) (current == (i) ? color_on : color_off), (current == (i) ? 6 : 4) //~ x -= 40; //~ x += 16; y += 16; switch (nmax) { case 2: dot(x - 5, y + 5, C(0)+1); dot(x + 5, y - 5, C(1)+1); break; case 3: dot(x , y - 7, C(0)); dot(x - 7, y + 3, C(1)); dot(x + 7, y + 3, C(2)); break; case 4: dot(x - 6, y - 6, C(0)); dot(x + 6, y - 6, C(1)); dot(x - 6, y + 6, C(2)); dot(x + 6, y + 6, C(3)); break; case 5: dot(x, y, C(0)); dot(x - 8, y - 8, C(1)); dot(x + 8, y - 8, C(2)); dot(x + 8, y + 8, C(3)); dot(x - 8, y + 8, C(4)); break; case 6: dot(x - 10, y - 8, C(0)); dot(x , y - 8, C(1)); dot(x + 10, y - 8, C(2)); dot(x - 10, y + 8, C(3)); dot(x , y + 8, C(4)); dot(x + 10, y + 8, C(5)); break; case 7: dot(x - 10, y - 10, C(0)); dot(x , y - 10, C(1)); dot(x + 10, y - 10, C(2)); dot(x - 10, y + 10, C(3)); dot(x , y + 10, C(4)); dot(x + 10, y + 10, C(5)); dot(x , y , C(6)); break; case 8: dot(x - 10, y - 10, C(0)); dot(x , y - 10, C(1)); dot(x + 10, y - 10, C(2)); dot(x - 10, y + 10, C(3)); dot(x , y + 10, C(4)); dot(x + 10, y + 10, C(5)); dot(x - 5, y , C(6)); dot(x + 5, y , C(7)); break; case 9: dot(x - 10, y - 10, C(0)); dot(x , y - 10, C(1)); dot(x + 10, y - 10, C(2)); dot(x - 10, y , C(3)); dot(x , y , C(4)); dot(x + 10, y , C(5)); dot(x - 10, y + 10, C(6)); dot(x , y + 10, C(7)); dot(x + 10, y + 10, C(8)); break; default: size_icon(x, y, current, nmax, color_on); break; } #undef C } static void pizza_slice(int x, int y, int current, int nmax, int fg, int bg) { dot(x, y, bg, 10); int a0 = current * 3600 / nmax; int w = 3600 / nmax; for (int a = a0-w/2; a < a0+w/2; a += 10) { draw_angled_line(x+16, y+16, 10, a-900, fg); } }*/ /* static void slider_box(int x, int y, int w, int h, int c) { bmp_draw_rect_chamfer(c, x, y, w, h, 1, 0); bmp_fill(c, x+1, y+1, w-1, h-1); } static void hslider(int x, int y, int current, int nmax, int fg, int bg) { #define SW 30 #define SH 15 #define SO ((30-SH)/2) int w = MIN(SW / nmax, 10); int W = w * nmax; x += (SW-W)/2; for (int i = 0; i < nmax; i++) { int xc = x + i*w; slider_box(xc, y+SO, MAX(3, w-2), SH, bg); } int xc = x + current * w; slider_box(xc, y+SO, MAX(3, w-2), SH, fg); #undef SW #undef SH #undef SO } static void vslider(int x, int y, int current, int nmax, int fg, int bg) { #define SW 22 #define SH 16 #define SO ((30-SH)/2) if (nmax > 3) { y += (32-SW)/2; slider_box(x+SO, y, SH, SW, bg); int w = 8; int yc = y + COERCE(current, 0, nmax-1) * (SW-w) / (nmax-1); slider_box(x+SO, yc, SH, w, fg); } else { int w = MIN(SW / nmax, 10); int W = w * nmax; y += (32-W)/2; for (int i = 0; i < nmax; i++) { int yc = y + i*w; slider_box(x+SO, yc, SH, w-3, bg); } int yc = y + COERCE(current, 0, nmax-1) * w; slider_box(x+SO, yc, SH, w-3, fg); } #undef SW #undef SH #undef SO } #define slider vslider */ static void round_box(int c, int x, int y, int w, int h) { bmp_draw_rect_chamfer(c, x-1, y-1, w+2, h+2, 2, 0); bmp_draw_rect_chamfer(c, x, y, w, h, 1, 0); if (w >= 4) bmp_fill(c, x+1, y+1, w-1, h-1); } static void round_box_meter(int x, int y, int value, int fg, int bg) { value = COERCE(value, 0, 100); round_box(fg, x+8, y+8, 16, 16); int X = x+9 + 12 * value / 100; bmp_draw_rect(bg, X, y+10, 2, 12); } static void slider(int x, int y, int current, int nmax, int fg, int bg) { if (nmax >= 2) round_box_meter(x, y, current * 100 / (nmax-1), fg, bg); else round_box(fg, x+8, y+8, 16, 16); } static void submenu_only_icon(int x, int y, int color) { round_box(color, x+8, y+8, 16, 16); /* for (int r = 0; r < 3; r++) { draw_circle(x + 8, y + 10, r, color); draw_circle(x + 8, y + 16, r, color); draw_circle(x + 8, y + 22, r, color); draw_circle(x + 9, y + 10, r, color); draw_circle(x + 9, y + 16, r, color); draw_circle(x + 9, y + 22, r, color); } if (color == COLOR_GREEN1) color = COLOR_WHITE; bmp_draw_rect(color, x + 15, y + 10, 10, 1); bmp_draw_rect(color, x + 15, y + 16, 10, 1); bmp_draw_rect(color, x + 15, y + 22, 10, 1); */ } /* void color_icon(int x, int y, const char* color) { if (streq(color, "Red")) maru(x, y, COLOR_RED); else if (streq(color, "Green")) maru(x, y, COLOR_GREEN2); else if (streq(color, "Blue")) maru(x, y, COLOR_LIGHT_BLUE); else if (streq(color, "Cyan")) maru(x, y, COLOR_CYAN); else if (streq(color, "Magenta")) maru(x, y, 14); else if (streq(color, "Yellow")) maru(x, y, COLOR_YELLOW); else if (streq(color, "Orange")) maru(x, y, COLOR_ORANGE); else if (streq(color, "White")) maru(x, y, COLOR_WHITE); else if (streq(color, "Black")) maru(x, y, COLOR_WHITE); else if (streq(color, "Luma") || streq(color, "Luma Fast")) maru(x, y, 60); else if (streq(color, "RGB")) { dot(x, y - 7, COLOR_RED, 5); dot(x - 7, y + 3, COLOR_GREEN2, 5); dot(x + 7, y + 3, COLOR_LIGHT_BLUE, 5); } else if (streq(color, "ON")) maru(x, y, COLOR_GREEN1); else if (streq(color, "OFF")) maru(x, y, 40); else { dot(x, y - 7, COLOR_CYAN, 5); dot(x - 7, y + 3, COLOR_RED, 5); dot(x + 7, y + 3, COLOR_YELLOW, 5); } } */ #endif // CONFIG_MENU_ICONS static void FAST selection_bar_backend(int c, int black, int x0, int y0, int w, int h) { uint8_t* B = bmp_vram(); #ifdef CONFIG_VXWORKS c = D2V(c); black = D2V(black); #endif #define P(x,y) B[BM(x,y)] for (int y = y0; y < y0 + h; y++) { for (int x = x0; x < x0 + w; x++) { if (P(x,y) == black) P(x,y) = c; } } // use a shadow for better readability, especially for gray text for (int y = y0; y < y0 + h; y++) { for (int x = x0; x < x0 + w; x++) { if (P(x,y) != c && P(x,y) != black) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { if (P(x+dx,y+dy) == c) P(x+dx,y+dy) = black; } } } } } } #ifdef CONFIG_MENU_DIM_HACKS void FAST replace_color(int old, int new, int x0, int y0, int w, int h) { uint8_t* B = bmp_vram(); #ifdef CONFIG_VXWORKS old = D2V(old); new = D2V(new); #endif #define P(x,y) B[BM(x,y)] for (int y = y0; y < y0 + h; y++) { for (int x = x0; x < x0 + w; x++) { if (P(x,y) == old) P(x,y) = new; } } } void FAST dim_screen(int fg, int bg, int x0, int y0, int w, int h) { uint8_t* B = bmp_vram(); #ifdef CONFIG_VXWORKS bg = D2V(bg); fg = D2V(fg); #endif #define P(x,y) B[BM(x,y)] for (int y = y0; y < y0 + h; y++) { for (int x = x0; x < x0 + w; x++) { if (P(x,y) != bg) { if (P(x,y) >= COLOR_ALMOST_BLACK && P(x,y) < fg) continue; P(x,y) = fg; } } } } #endif // By default, icon type is MNI_BOOL(MENU_INT(entry)) // To override, call menu_draw_icon from the display functions // Icon is only drawn once for each menu item, even if this is called multiple times // Only the first call is executed static int icon_drawn = 0; static void menu_draw_icon(int x, int y, int type, intptr_t arg, int warn) { if (icon_drawn) return; icon_drawn = type; #ifdef CONFIG_MENU_ICONS x -= MENU_OFFSET; int color_on = warn ? COLOR_DARK_GREEN1_MOD : COLOR_GREEN1; int color_off = 45; int color_dis = warn ? 50 : COLOR_RED; int color_slider_fg = warn ? COLOR_DARK_CYAN1_MOD : COLOR_CYAN; int color_slider_bg = warn ? COLOR_BLACK : 45; int color_slider_off_fg = warn ? COLOR_DARK_GREEN1_MOD : COLOR_GREEN1; int color_action = warn ? 45 : COLOR_WHITE; switch(type) { case MNI_OFF: maru(x, y, color_off); return; case MNI_ON: maru(x, y, color_on); return; case MNI_DISABLE: batsu(x, y, color_dis); return; case MNI_NEUTRAL: maru(x, y, 55); return; case MNI_AUTO: slider(x, y, 0, 0, color_slider_fg, color_slider_bg); return; case MNI_PERCENT: round_box_meter(x, y, arg, color_slider_fg, color_slider_bg); return; case MNI_PERCENT_ALLOW_OFF: round_box_meter(x, y, arg, color_slider_off_fg, color_slider_bg); return; case MNI_PERCENT_OFF: round_box_meter(x, y, arg, color_off, color_off); return; //~ case MNI_PERCENT_PM: clockmeter_pm(x, y, arg, color_slider_fg, color_slider_bg); return; case MNI_ACTION: playicon(x, y, color_action); return; case MNI_DICE: { int i = arg & 0xFFFF; int N = arg >> 16; slider(x, y, i, N, color_slider_fg, color_slider_bg); return; } case MNI_DICE_OFF: { int i = arg & 0xFFFF; int N = arg >> 16; //~ maru(x, y, i ? color_on : color_off); return; //~ if (i == 0) dice_icon(x, y, i-1, N-1, 40, 40); //~ else dice_icon(x, y, i-1, N-1, COLOR_GREEN1, 50); if (i == 0) //maru(x, y, color_off); slider(x, y, i-1, N-1, color_off, color_off); else slider(x, y, i-1, N-1, color_slider_off_fg, color_slider_bg); return; return; } //~ case MNI_SIZE: //size_icon(x, y, arg & 0xFFFF, arg >> 16, color_slider_fg); return; //~ { //~ int i = arg & 0xFFFF; //~ int N = arg >> 16; //~ round_box_meter(x, y, i*100/(N-1), color_slider_fg, color_slider_bg); //~ return; //~ } //~ case MNI_NAMED_COLOR: //~ { //~ if (warn) maru(x, y, color_on); //~ else color_icon(x, y, (char *)arg); //~ return; //~ } case MNI_RECORD: maru(x, y, COLOR_RED); return; case MNI_SUBMENU: submenu_only_icon(x, y, arg ? color_on : color_off); return; } #endif } // if the help text contains more lines (separated by '\n'), display the i'th line // if line number is too high, display the first line static char* menu_help_get_line(const char* help, int line, char buf[MENU_MAX_HELP_LEN]) { char * p = strchr(help, '\n'); if (!p) return (char*) help; // help text contains a single line, no more fuss // help text contains more than one line, choose the i'th one int i = line; if (i < 0) i = 0; char* start = (char*) help; char* end = p; while (i > 0) // we need to skip some lines { if (*end == 0) // too many lines skipped? fall back to first line { start = (char*) help; end = p; break; } // there are more lines, skip to next one start = end + 1; end = strchr(start+1, '\n'); if (!end) end = (char*) help + strlen(help); i--; } // return the substring from "start" to "end" int len = MIN(MENU_MAX_HELP_LEN, end - start + 1); snprintf(buf, len, "%s", start); return buf; } static char* pickbox_string(struct menu_entry * entry, int i) { if (entry->choices) return (char*) entry->choices[i - entry->min]; if (entry->min == 0 && entry->max == 1) return i ? "ON" : "OFF"; // not configured; just use some reasonable defaults static char msg[20]; snprintf(msg, sizeof(msg), "%d", i); return msg; } static void pickbox_draw(struct menu_entry * entry, int x0, int y0) { int lo = entry->min; int hi = entry->max; int sel = SELECTED_INDEX(entry) + lo; int fnt = SHADOW_FONT(FONT(FONT_LARGE, COLOR_WHITE, COLOR_BLACK)); // don't draw too many items in the pickbox if (hi - lo > 10) { lo = MAX(lo, sel - (sel < hi ? 9 : 10)); hi = MIN(hi, lo + 10); } // compute the width of the pickbox (what's the longest string?) int w = 100; for (int i = lo; i <= hi; i++) { w = MAX(w, font_large.width * strlen(pickbox_string(entry, i))); } // don't draw the pickbox out of the screen int h = 32 * (hi-lo+1); /*#define SUBMENU_HINT_SUFFIX ": advanced..." if (entry->children) { h += 20; // has submenu, display a hint here w = MAX(w, font_med.width * (strlen(Q_BTN_NAME) + strlen(SUBMENU_HINT_SUFFIX))); }*/ if (y0 + h > 410) y0 = 410 - h; if (x0 + w > 700) x0 = 700 - w; w = 720-x0+16; // extend it till the right edge // draw the pickbox bmp_fill(45, x0-16, y0, w, h+1); for (int i = lo; i <= hi; i++) { int y = y0 + (i-lo) * 32; if (i == sel) selection_bar_backend(MENU_BAR_COLOR, 45, x0-16, y, w, 32); bmp_printf(fnt, x0, y, pickbox_string(entry, i)); } /* if (entry->children) bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_CYAN, 45)), x0, y0 + (hi-lo+1) * 32 + 5, "%s" SUBMENU_HINT_SUFFIX, Q_BTN_NAME );*/ } static void submenu_key_hint(int x, int y, int fg, int bg, int chr) { bmp_fill(bg, x+12, y+1, 25, 30); bfnt_draw_char(chr, x, y-5, fg, COLOR_BLACK); } static void menu_clean_footer() { int h = 50; if (is_menu_active("Help")) h = font_med.height * 3 + 2; int bgu = MENU_BG_COLOR_HEADER_FOOTER; bmp_fill(bgu, 0, 480-h, 720, h); } static int check_default_warnings(struct menu_entry * entry, char* warning) { warning[0] = 0; /* all submenu entries depend on the master entry, if any */ if (IS_SUBMENU(entry->parent_menu)) { struct menu_entry * parent_entry = entry_find_by_name(0, entry->parent_menu->name); if (parent_entry && IS_ML_PTR(parent_entry->priv)) { if (!MENU_INT(parent_entry)) { int is_plural = parent_entry->name[strlen(parent_entry->name)-1] == 's'; snprintf(warning, MENU_MAX_WARNING_LEN, "%s %s disabled.", parent_entry->name, is_plural ? "are" : "is"); return MENU_WARN_NOT_WORKING; } } } // default warnings if (DEPENDS_ON(DEP_GLOBAL_DRAW) && !get_global_draw()) snprintf(warning, MENU_MAX_WARNING_LEN, GDR_WARNING_MSG); else if (DEPENDS_ON(DEP_MOVIE_MODE) && !is_movie_mode()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature only works in movie mode."); else if (DEPENDS_ON(DEP_PHOTO_MODE) && is_movie_mode()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature only works in photo mode."); else if (DEPENDS_ON(DEP_LIVEVIEW) && !lv) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature only works in LiveView."); else if (DEPENDS_ON(DEP_NOT_LIVEVIEW) && lv) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature does not work in LiveView."); else if (DEPENDS_ON(DEP_AUTOFOCUS) && is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires autofocus enabled."); else if (DEPENDS_ON(DEP_MANUAL_FOCUS) && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires manual focus."); else if (DEPENDS_ON(DEP_CFN_AF_HALFSHUTTER) && cfn_get_af_button_assignment() != AF_BTN_HALFSHUTTER && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "Set AF to Half-Shutter from Canon menu (CFn / custom ctrl), or use MF."); #if !defined(CONFIG_NO_HALFSHUTTER_AF_IN_LIVEVIEW) else if (DEPENDS_ON(DEP_CFN_AF_BACK_BUTTON) && cfn_get_af_button_assignment() == AF_BTN_HALFSHUTTER && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "Set AF to back btn (*) from Canon menu (CFn / custom ctrl), or use MF."); #endif else if (DEPENDS_ON(DEP_EXPSIM) && lv && !lv_luma_is_accurate()) snprintf(warning, MENU_MAX_WARNING_LEN, EXPSIM_WARNING_MSG); //~ else if (DEPENDS_ON(DEP_NOT_EXPSIM) && lv && lv_luma_is_accurate()) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires ExpSim disabled."); else if (DEPENDS_ON(DEP_MANUAL_FOCUS) && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires manual focus."); else if (DEPENDS_ON(DEP_CHIPPED_LENS) && !lens_info.name[0]) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires a chipped (electronic) lens."); else if (DEPENDS_ON(DEP_M_MODE) && shooting_mode != SHOOTMODE_M) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires Manual (M) mode."); else if (DEPENDS_ON(DEP_MANUAL_ISO) && !lens_info.raw_iso) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature requires manual ISO."); else if (DEPENDS_ON(DEP_SOUND_RECORDING) && !sound_recording_enabled()) snprintf(warning, MENU_MAX_WARNING_LEN, (was_sound_recording_disabled_by_fps_override() && !fps_should_record_wav()) ? "Sound recording was disabled by FPS override." : "Sound recording is disabled. Enable it from Canon menu." ); else if (DEPENDS_ON(DEP_NOT_SOUND_RECORDING) && sound_recording_enabled()) snprintf(warning, MENU_MAX_WARNING_LEN, "Disable sound recording from Canon menu!"); if (warning[0]) return MENU_WARN_NOT_WORKING; if (entry->selected) // check recommendations { if (WORKS_BEST_IN(DEP_GLOBAL_DRAW) && !get_global_draw()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with GlobalDraw enabled."); else if (WORKS_BEST_IN(DEP_MOVIE_MODE) && !is_movie_mode()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best in movie mode."); else if (WORKS_BEST_IN(DEP_PHOTO_MODE) && is_movie_mode()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best in photo mode."); else if (WORKS_BEST_IN(DEP_LIVEVIEW) && !lv) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best in LiveView."); else if (WORKS_BEST_IN(DEP_NOT_LIVEVIEW) && lv) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best outside LiveView."); else if (WORKS_BEST_IN(DEP_AUTOFOCUS) && is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with autofocus enabled."); else if (WORKS_BEST_IN(DEP_MANUAL_FOCUS) && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with manual focus."); else if (WORKS_BEST_IN(DEP_CFN_AF_HALFSHUTTER) && cfn_get_af_button_assignment() != AF_BTN_HALFSHUTTER && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "Set AF to Half-Shutter from Canon menu (CFn / custom ctrl)."); #if !defined(CONFIG_NO_HALFSHUTTER_AF_IN_LIVEVIEW) else if (WORKS_BEST_IN(DEP_CFN_AF_BACK_BUTTON) && cfn_get_af_button_assignment() == AF_BTN_HALFSHUTTER && !is_manual_focus()) snprintf(warning, MENU_MAX_WARNING_LEN, "Set AF to back btn (*) from Canon menu (CFn / custom ctrl)."); #endif //~ else if (WORKS_BEST_IN(DEP_EXPSIM) && lv && !lv_luma_is_accurate()) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with ExpSim enabled."); //~ else if (WORKS_BEST_IN(DEP_NOT_EXPSIM) && lv && lv_luma_is_accurate()) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with ExpSim disabled."); //~ else if (WORKS_BEST_IN(DEP_CHIPPED_LENS) && !lens_info.name[0]) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with a chipped (electronic) lens."); else if (WORKS_BEST_IN(DEP_M_MODE) && shooting_mode != SHOOTMODE_M) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best in Manual (M) mode."); else if (WORKS_BEST_IN(DEP_MANUAL_ISO) && !lens_info.raw_iso) snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with manual ISO."); //~ else if (WORKS_BEST_IN(DEP_SOUND_RECORDING) && !sound_recording_enabled()) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with sound recording enabled."); //~ else if (WORKS_BEST_IN(DEP_NOT_SOUND_RECORDING) && sound_recording_enabled()) //~ snprintf(warning, MENU_MAX_WARNING_LEN, "This feature works best with sound recording disabled."); if (warning[0]) return MENU_WARN_ADVICE; } return MENU_WARN_NONE; } static void entry_default_display_info( struct menu_entry * entry, struct menu_display_info * info ) { static char name[MENU_MAX_NAME_LEN]; static char short_name[MENU_MAX_SHORT_NAME_LEN]; static char value[MENU_MAX_VALUE_LEN]; static char short_value[MENU_MAX_SHORT_VALUE_LEN]; static char help[MENU_MAX_HELP_LEN]; static char warning[MENU_MAX_WARNING_LEN]; static char rinfo[MENU_MAX_RINFO_LEN]; name[0] = 0; short_name[0] = 0; value[0] = 0; short_value[0] = 0; help[0] = 0; warning[0] = 0; rinfo[0] = 0; info->name = name; info->short_name = short_name; info->value = value; info->short_value = short_value; info->help = help; info->warning = warning; info->rinfo = rinfo; info->custom_drawing = 0; info->can_custom_draw = 1; info->icon = 0; info->icon_arg = 0; info->enabled = entry_guess_enabled(entry); info->warning_level = check_default_warnings(entry, warning); snprintf(name, sizeof(name), "%s", entry->name); /* for junkie mode, short_name will get copied, short_value is empty by default */ /*if(entry->short_name && strlen(entry->short_name)) { snprintf(short_name, sizeof(short_name), "%s", entry->short_name); }*/ if (entry->choices && SELECTED_INDEX(entry) >= 0 && SELECTED_INDEX(entry) < NUM_CHOICES(entry)) { STR_APPEND(value, "%s", entry->choices[SELECTED_INDEX(entry)]); } else if (IS_ML_PTR(entry->priv) && entry->select != run_in_separate_task) { if (entry->min == 0 && entry->max == 1) { STR_APPEND(value, MENU_INT(entry) ? "ON" : "OFF"); } else { switch (entry->unit) { case UNIT_1_8_EV: case UNIT_x10: case UNIT_PERCENT_x10: { int v = MENU_INT(entry); int den = entry->unit == UNIT_1_8_EV ? 8 : 10; STR_APPEND(value, "%s%d", v < 0 ? "-" : "", ABS(v)/den); int r = (ABS(v)%den)*10/den; if (r) STR_APPEND(value, ".%d", r); STR_APPEND(value, "%s", entry->unit == UNIT_1_8_EV ? " EV" : entry->unit == UNIT_PERCENT_x10 ? "%" : "" ); break; } case UNIT_PERCENT: { STR_APPEND(value, "%d%%", MEM(entry->priv)); break; } case UNIT_ISO: { if (!MEM(entry->priv)) { STR_APPEND(value, "Auto"); } else { STR_APPEND(value, "%d", raw2iso(MEM(entry->priv))); } break; } case UNIT_DEC: { if(edit_mode) { char* zero_pad = "00000000"; STR_APPEND(value, "%s%d", (zero_pad + COERCE(8-(caret_position - log10i(MEM(entry->priv))),0,8)), MEM(entry->priv)); } else { STR_APPEND(value, "%d", MEM(entry->priv)); } break; } case UNIT_HEX: { if(edit_mode) { char* zero_pad = "00000000"; STR_APPEND(value, "0x%s%x", (zero_pad + COERCE(8-(caret_position - log2i(MEM(entry->priv))/4),0,8)), MEM(entry->priv)); } else { STR_APPEND(value, "0x%x", MEM(entry->priv)); } break; } case UNIT_TIME: { if(MEM(entry->priv) / 3600 > 0 || (entry->selected && caret_position > 5)) { STR_APPEND(value,"%dh%02dm%02ds", MEM(entry->priv) / 3600, MEM(entry->priv) / 60 % 60, MEM(entry->priv) % 60); } else if((entry->selected && caret_position > 4)) { STR_APPEND(value,"%02dm%02ds", MEM(entry->priv) / 60, MEM(entry->priv) % 60); } else if(MEM(entry->priv) / 60 > 0 || (entry->selected && caret_position > 2)) { STR_APPEND(value,"%dm%02ds", MEM(entry->priv) / 60, MEM(entry->priv) % 60); } else if((entry->selected && caret_position > 1)) { STR_APPEND(value,"%02ds", MEM(entry->priv) % 60); } else { STR_APPEND(value,"%ds", MEM(entry->priv)); } break; } default: { STR_APPEND(value, "%d", MEM(entry->priv)); break; } } } } } static inline int get_customize_color() { return COLOR_DARK_ORANGE_MOD; } static void display_customize_marker(struct menu_entry * entry, int x, int y) { // star marker if (entry->starred) bfnt_draw_char(ICON_ML_MYMENU, x, y-4, COLOR_GREEN1, COLOR_BLACK); // hidden marker else if (HAS_CURRENT_HIDDEN_FLAG(entry)) batsu(x+4, y, junkie_mode ? COLOR_ORANGE : COLOR_RED); } static void print_help_line(int color, int x, int y, char* msg) { int fnt = FONT(FONT_MED, color, MENU_BG_COLOR_HEADER_FOOTER); int len = bmp_string_width(fnt, msg); if (len > 720 - 2*x) { /* squeeze long lines (if the help text is just a bit too long) */ /* TODO: detect if squeezing fails and beep or print the help line in red; requires changes in the font backend */ fnt |= FONT_ALIGN_JUSTIFIED | FONT_TEXT_WIDTH(720 - 2*x); } bmp_printf( fnt, x, y, "%s", msg ); } static void entry_print( int x, int y, int w, int h, struct menu_entry * entry, struct menu_display_info * info, int in_submenu ) { int w0 = w; int fnt = MENU_FONT; if (info->warning_level == MENU_WARN_NOT_WORKING) fnt = MENU_FONT_GRAY; if (submenu_level && !in_submenu) fnt = MENU_FONT_GRAY; int use_small_font = 0; int x_font_offset = 0; int y_font_offset = (h - (int)font_large.height) / 2; int not_at_home = !entry->parent_menu->selected && /* is it in some dynamic menu? (not in its original place) */ !submenu_level && /* hack: submenus are not marked as "selected", so we can't have dynamic submenus for now */ 1; /* do not show right-side info in dynamic menus (looks a little tidier) */ if (not_at_home) info->rinfo[0] = 0; if ( not_at_home && /* special display to show where it's coming from */ IS_SUBMENU(entry->parent_menu) && /* if it's from a top-level menu, it's obvious where it's coming from */ !edit_mode && /* show unmodified entry when editing */ 1) { /* use a smaller font */ use_small_font = 1; x_font_offset = 28; fnt = (fnt & ~FONT_MASK) | FONT_MED_LARGE; y_font_offset = (h - (int)fontspec_font(fnt)->height) / 2; if (my_menu->selected) /* in My Menu, we will include the submenu name in the original entry */ { /* how much space we have to print our stuff? (we got some extra because of the smaller font) */ int max_len = w; int current_len = bmp_string_width(fnt, info->name); int extra_len = bmp_strlen_clipped(fnt, entry->parent_menu->name, max_len - current_len - 50); /* try to modify the name to show where it's coming from */ char new_name[100]; new_name[0] = 0; if (extra_len >= 5) { /* we have some space to show the menu where the original entry is coming from */ /* (or at least some part of it) */ snprintf(new_name, MIN(extra_len + 1, sizeof(new_name)), "%s", entry->parent_menu->name); STR_APPEND(new_name, " - "); } /* print the original name */ STR_APPEND(new_name, "%s", info->name); /* if it's too long, add some dots */ if ((int)strlen(new_name) > max_len) { new_name[max_len-1] = new_name[max_len-2] = new_name[max_len-3] = '.'; new_name[max_len] = 0; } bmp_printf( fnt, x, y + y_font_offset, new_name ); /* don't indent */ x_font_offset = 0; /* don't print the name in the normal way */ goto skip_name; } } bmp_printf( fnt, x + x_font_offset, y + y_font_offset, info->name ); skip_name: // debug if (0) bmp_printf(FONT_SMALL, x, y, "name(%s)(%d) value(%s)(%d)", info->name, strlen(info->name), info->value, strlen(info->value)); if (info->enabled == 0) fnt = MENU_FONT_GRAY; if (use_small_font) fnt = (fnt & ~FONT_MASK) | FONT_MED_LARGE; // far right end int x_end = in_submenu ? x + g_submenu_width - SUBMENU_OFFSET : 717; int char_width = fontspec_font(fnt)->width; w = MAX(w, bmp_string_width(fnt, info->name) + char_width); // both submenu marker and value? make sure they don't overlap if (entry->icon_type == IT_SUBMENU && info->value[0]) w += 2 * char_width; // value string too big? move it to the left int val_width = bmp_string_width(fnt, info->value); int end = w + val_width; int wmax = x_end - x; // right-justified info field? int rlen = bmp_string_width(fnt, info->rinfo); int rinfo_x = x_end - rlen - 35; if (rlen) wmax -= rlen + char_width + 35; // no right info? then make sure there's room for the Q symbol else if (entry->children && !in_submenu && !menu_lv_transparent_mode && (entry->priv || entry->select)) { wmax -= 35; } if (end > wmax) w -= (end - wmax); int xval = x + w; if (entry->selected && editing_with_caret(entry) && caret_position >= (int)strlen(info->value)) { bmp_fill(COLOR_WHITE, xval, y + fontspec_font(fnt)->height - 4, char_width, 2); xval += char_width * (caret_position - strlen(info->value) + 1); } // print value field bmp_printf( fnt, xval, y + y_font_offset, "%s", info->value ); if(entry->selected && editing_with_caret(entry) && caret_position < (int)strlen(info->value) && strlen(info->value) > 0) { int w1 = bmp_string_width(fnt, (info->value + strlen(info->value) - caret_position)); int w2 = bmp_string_width(fnt, (info->value + strlen(info->value) - caret_position - 1)); bmp_fill(COLOR_WHITE, xval + val_width - w2, y + fontspec_font(fnt)->height - 4, w2 - w1, 2); } // print right-justified info, if any if (info->rinfo[0]) { bmp_printf( MENU_FONT_GRAY, rinfo_x, y + y_font_offset, "%s", info->rinfo ); } int y_icon_offset = (h - 32) / 2 - 1; if (entry->icon_type == IT_SUBMENU ) { // Forward sign for submenus that open with SET submenu_key_hint( xval-18 - (info->value[0] ? font_large.width*2 : 0), y + y_icon_offset, info->warning_level == MENU_WARN_NOT_WORKING ? MENU_FONT_GRAY : 60, COLOR_BLACK, ICON_ML_FORWARD ); } else if (entry->children && !SUBMENU_OR_EDIT && !menu_lv_transparent_mode) { // Q sign for selected item, if submenu opens with Q // Discrete placeholder for non-selected item if (entry->selected) submenu_key_hint(720-40, y + y_icon_offset, COLOR_WHITE, COLOR_BLACK, ICON_ML_Q_FORWARD); else submenu_key_hint(720-35, y + y_icon_offset, 40, COLOR_BLACK, ICON_ML_FORWARD); } // selection bar params int xl = x - 5 + x_font_offset; int xc = x - 5 + x_font_offset; if ((in_submenu || edit_mode) && info->value[0]) xc = x + w - 15; // selection bar if (entry->selected) { int color_left = 45; int color_right = MENU_BAR_COLOR; if (junkie_mode && !in_submenu) color_left = color_right = COLOR_BLACK; if (customize_mode) { color_left = color_right = get_customize_color(); } selection_bar_backend(color_left, COLOR_BLACK, xl, y, xc-xl, h-1); selection_bar_backend(color_right, COLOR_BLACK, xc, y, x_end-xc, h-1); // use a pickbox if possible if (edit_mode && CAN_HAVE_PICKBOX(entry)) { int px = x + w0; pickbox_draw(entry, px, y); } } // display help if (entry->selected && !menu_lv_transparent_mode) { char help1_buf[MENU_MAX_HELP_LEN]; char help2_buf[MENU_MAX_HELP_LEN]; int help_color = 70; /* overriden help will go in first free slot */ char* help1 = (char*)entry->help; if (entry->help) { /* if there are multiple help lines, pick the one that matches current choice */ help1 = menu_help_get_line(entry->help, SELECTED_INDEX(entry), help1_buf); } else { /* no help1? put overriden help (via MENU_SET_HELP) here */ help1 = info->help; } if (help1) { print_help_line(help_color, 10, MENU_HELP_Y_POS, help1); } char* help2 = 0; if (help1 != info->help) { /* help1 already used for something else? * put overriden help (via MENU_SET_HELP) here */ help2 = info->help; } else if (entry->help2) { /* pick help line according to selected choice */ help2 = menu_help_get_line(entry->help2, SELECTED_INDEX(entry), help2_buf); } if (!help2 || !help2[0]) // default help just list the choices { int num = NUM_CHOICES(entry); if (num > 2 && num < 10) { help2_buf[0] = 0; for (int i = entry->min; i <= entry->max; i++) { int len = bmp_string_width(FONT_MED, help2); if (len > 700) break; STR_APPEND(help2_buf, "%s%s", pickbox_string(entry, i), i < entry->max ? " / " : "."); } help_color = 50; help2 = help2_buf; } } /* only show the second help line if there are no audio meters */ if (help2 && !audio_meters_are_drawn()) { print_help_line(help_color, 10, MENU_HELP_Y_POS_2, help2); } } // if there's a warning message set, display it if (entry->selected && info->warning[0]) { int warn_color = info->warning_level == MENU_WARN_INFO ? COLOR_GREEN1 : info->warning_level == MENU_WARN_ADVICE ? COLOR_YELLOW : info->warning_level == MENU_WARN_NOT_WORKING ? COLOR_ORANGE : COLOR_WHITE; int warn_y = audio_meters_are_drawn() ? MENU_HELP_Y_POS : MENU_WARNING_Y_POS; bmp_fill(MENU_BG_COLOR_HEADER_FOOTER, 10, warn_y, 720, font_med.height); print_help_line(warn_color, 10, warn_y, info->warning); } /* from now on, we'll draw the icon only, which should be shifted */ x += x_font_offset; y += y_icon_offset - 1; // customization markers if (customize_mode) { display_customize_marker(entry, x - 44, y); return; // do not display icons } // warning icon, if any int warn = (info->warning_level == MENU_WARN_NOT_WORKING); // overriden icon has the highest priority if (info->icon) menu_draw_icon(x, y, info->icon, info->icon_arg, warn); if (entry->icon_type == IT_BOOL) menu_draw_icon(x, y, info->enabled ? MNI_ON : MNI_OFF, 0, warn); entry_draw_icon(entry, x, y, info->enabled, warn); } static void menu_post_display() { char* cfg_preset = get_config_preset_name(); if (cfg_preset && !submenu_level) { bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_GRAY(40), COLOR_BLACK)) | FONT_ALIGN_RIGHT, 715, 480-50-font_med.height+4, "%s", cfg_preset ); } if (!CURRENT_DIALOG_MAYBE) { // we can't use the scrollwheel // and you need to be careful because you will change shooting settings while recording! bfnt_draw_char(ICON_MAINDIAL, 680, 395, MENU_WARNING_COLOR, MENU_BG_COLOR_HEADER_FOOTER); draw_line(720, 405, 680, 427, MENU_WARNING_COLOR); draw_line(720, 406, 680, 428, MENU_WARNING_COLOR); } // display help about how to customize the menu if (customize_mode) { bmp_printf( FONT(FONT_MED, get_customize_color(), MENU_BG_COLOR_HEADER_FOOTER), 5, MENU_HELP_Y_POS_2, //~ CUSTOMIZE_MODE_HIDING ? "Press SET to show/hide items you don't use. " : //~ CUSTOMIZE_MODE_MYMENU ? "Press SET to choose your favorite items for MyMenu. " : "" "Press SET to choose MyMenu items or hide what you don't use." ); } } static int menu_entry_process( struct menu * menu, struct menu_entry * entry, int x, int y, int h, int only_selected ) { // fill in default text, warning checks etc static struct menu_display_info info; entry_default_display_info(entry, &info); info.x = x; info.y = y; info.x_val = x + 20 * ABS(menu->split_pos); info.can_custom_draw = menu != my_menu && menu != mod_menu && !menu_lv_transparent_mode; // display icon (only the first icon is drawn) icon_drawn = 0; if ((!menu_lv_transparent_mode && !only_selected) || entry->selected) { // should we override some things? if (entry->update) { /* in edit mode with caret, we will not allow the update function to override the entry value */ char default_value[MENU_MAX_VALUE_LEN]; if (editing_with_caret(entry)) snprintf(default_value, MENU_MAX_VALUE_LEN, "%s", info.value); entry->update(entry, &info); if (editing_with_caret(entry)) snprintf(info.value, MENU_MAX_VALUE_LEN, "%s", default_value); } // menu->update asked to draw the entire screen by itself? stop drawing right now if (info.custom_drawing == CUSTOM_DRAW_THIS_MENU) return 0; if (info.custom_drawing == CUSTOM_DRAW_DO_NOT_DRAW) menu_redraw_cancel = 1; // print the menu on the screen if (info.custom_drawing == CUSTOM_DRAW_DISABLE) entry_print(info.x, info.y, info.x_val - x, h, entry, &info, IS_SUBMENU(menu)); } return 1; } static void dyn_menu_add_entry(struct menu * dyn_menu, struct menu_entry * entry, struct menu_entry * dyn_entry) { // copy most things from old menu structure to this one // except for some essential things :P void* next = dyn_entry->next; void* prev = dyn_entry->prev; int selected = dyn_entry->selected; memcpy(dyn_entry, entry, sizeof(struct menu_entry)); dyn_entry->next = next; dyn_entry->prev = prev; dyn_entry->selected = selected; dyn_entry->shidden = entry->shidden; dyn_entry->hidden = 0; dyn_entry->jhidden = 0; dyn_entry->starred = 0; // update split position menu_update_split_pos(dyn_menu, dyn_entry); } static int my_menu_select_func(struct menu_entry * entry) { return entry->starred ? 1 : 0; } static struct menu_entry * mod_menu_selected_entry = 0; static int mod_menu_select_func(struct menu_entry * entry) { if (config_var_was_changed(entry->priv)) return 1; /* don't delete currently selected entry */ if (entry == mod_menu_selected_entry) return 1; /* anything from submenu was changed? */ if (entry->children) { struct menu * submenu = menu_find_by_name(entry->name, ICON_ML_SUBMENU); if (submenu) { struct menu_entry * e = submenu->children; for(; e ; e = e->next) { if (mod_menu_select_func(e)) return 1; } } } return 0; } #define DYN_MENU_DO_NOT_EXPAND_SUBMENUS 0 #define DYN_MENU_EXPAND_ALL_SUBMENUS 1 #define DYN_MENU_EXPAND_ONLY_ACTIVE_SUBMENUS 2 static int dyn_menu_rebuild(struct menu * dyn_menu, int (*select_func)(struct menu_entry * entry), struct menu_entry * placeholders, int max_placeholders, int expand_submenus) { dyn_menu->split_pos = -20; int i = 0; struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if (menu == my_menu || menu == mod_menu) continue; if (IS_SUBMENU(menu)) continue; struct menu_entry * entry = menu->children; for(; entry ; entry = entry->next) { if (entry->shidden) continue; if (select_func(entry)) { if (i >= max_placeholders) // too many items in our dynamic menu return 0; // whoops dyn_menu_add_entry(dyn_menu, entry, &placeholders[i]); i++; } // any submenu? if (entry->children) { int should_expand = expand_submenus == DYN_MENU_EXPAND_ALL_SUBMENUS || (expand_submenus == DYN_MENU_EXPAND_ONLY_ACTIVE_SUBMENUS && !(IS_ML_PTR(entry->priv) && !MENU_INT(entry))); if (!should_expand) continue; struct menu * submenu = menu_find_by_name(entry->name, ICON_ML_SUBMENU); if (submenu) { struct menu_entry * e = submenu->children; for(; e ; e = e->next) { if (select_func(e)) { if (i >= max_placeholders) // too many items in our dynamic menu return 0; // whoops dyn_menu_add_entry(dyn_menu, e, &placeholders[i]); i++; } } } } } } for ( ; i < max_placeholders; i++) { struct menu_entry * dyn_entry = &(placeholders[i]); dyn_entry->shidden = 1; dyn_entry->hidden = 1; dyn_entry->jhidden = 1; dyn_entry->name = 0; dyn_entry->priv = 0; dyn_entry->select = 0; dyn_entry->select_Q = 0; dyn_entry->update = menu_placeholder_unused_update; } return 1; // success } static int my_menu_rebuild() { my_menu_dirty = 0; return dyn_menu_rebuild(my_menu, my_menu_select_func, my_menu_placeholders, COUNT(my_menu_placeholders), DYN_MENU_EXPAND_ALL_SUBMENUS); } static int mod_menu_rebuild() { /* don't be so aggressive with updates (they are a bit CPU-intensive) */ static int last_update = -1; if (!should_run_polling_action(200, &last_update)) return 1; mod_menu_dirty = 0; mod_menu_selected_entry = get_selected_entry(mod_menu); mod_menu_selected_entry = entry_find_by_name(mod_menu_selected_entry->parent_menu->name, mod_menu_selected_entry->name); int ok = dyn_menu_rebuild(mod_menu, mod_menu_select_func, mod_menu_placeholders, COUNT(mod_menu_placeholders), DYN_MENU_EXPAND_ONLY_ACTIVE_SUBMENUS); /* make sure the selection doesn't move because of updating */ if (mod_menu->selected) { select_menu_by_name(MOD_MENU_NAME, mod_menu_selected_entry->name); } return ok; } static void menu_display( struct menu * menu, int x, int y, int only_selected ) { struct menu_entry * entry = menu->children; //hide upper menu for vscroll int pos = get_menu_selected_pos(menu); int num_visible = get_menu_visible_count(menu); int target_height = 370; if (is_menu_active("Help")) target_height -= 20; if (is_menu_active("Focus")) target_height -= 70; int natural_height = num_visible * font_large.height; int ideal_num_items = target_height / font_large.height; /* if the menu items does not exceed max count by too much (e.g. 12 instead of 11), * prefer to squeeze them vertically in order to avoid scrolling. */ /* but if we can't avoid scrolling, don't squeeze */ if (num_visible > ideal_num_items + 1) { num_visible = ideal_num_items; natural_height = num_visible * font_large.height; /* leave some space for the scroll indicators */ target_height -= submenu_level ? 16 : 12; y += submenu_level ? 4 : 2; } else /* we can fit everything */ { menu->scroll_pos = 0; } int extra_spacing = (target_height - natural_height); /* don't stretch too much */ extra_spacing = MIN(extra_spacing, 2 * num_visible); /* use Bresenham line-drawing algorithm to divide space evenly with integer-only math */ /* http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#Algorithm_with_Integer_Arithmetic */ /* up to 3 pixels of spacing per row */ /* x: from 0 to (num_visible-1)*3 */ /* y: space accumulated (from 0 to extra_spacing) */ int dx = (num_visible-1)*3; int dy = ABS(extra_spacing); dy = MIN(dy, dx); int D = 2*dy - dx; int scroll_pos = menu->scroll_pos; // how many menu entries to skip scroll_pos = MAX(scroll_pos, pos - num_visible); scroll_pos = MIN(scroll_pos, pos - 1); menu->scroll_pos = scroll_pos; for(int i=0;inext; entry = entry->next; } if (scroll_pos > 0) { for (int i = -13; i <= 13; i++) draw_line(360 - i, y + 8 - 12, 360, y - 12, MENU_BAR_COLOR); } //<== vscroll if (!menu_lv_transparent_mode) menu_clean_footer(); for (int i = 0; i < num_visible && entry; ) { if (is_visible(entry)) { /* how much extra spacing for this menu entry? */ /* (Bresenham step) */ int local_spacing = 0; for (int i = 0; i < 3; i++) { if (D > 0) { local_spacing += SGN(extra_spacing); D = D + (2*dy - 2*dx); } else { D = D + 2*dy; } } // display current entry int ok = menu_entry_process(menu, entry, x, y, font_large.height + local_spacing, only_selected); // entry asked for custom draw? stop here if (!ok) goto end; // move down for next item y += font_large.height + local_spacing; i++; } entry = entry->next; } int more_entries = 0; while (entry) { if (is_visible(entry)) more_entries++; entry = entry->next; } if (more_entries) { y += 10; for (int i = -13; i <= 13; i++) draw_line(360 - i, y - 8, 360, y, MENU_BAR_COLOR); } end: // all menus displayed, now some extra stuff menu_post_display(); } static int startswith(char* str, char* prefix) { char* s = str; char* p = prefix; for (; *p; s++,p++) if (*s != *p) return 0; return 1; } static inline int islovowel(char c) { if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') return 1; return 0; } static char* junkie_get_shortname(struct menu_display_info * info, int fnt, int maxlen) { static char tmp[30]; static char sname[20]; memset(sname, 0, sizeof(sname)); if (info->short_name[0]) { snprintf(tmp, sizeof(tmp), "%s", info->short_name); } else { // skip some common words int skip = 0; if (startswith(info->name, "Movie")) skip = 5; else if (startswith(info->name, "Magic")) skip = 5; else if (startswith(info->name, "Focus")) skip = 5; else if (startswith(info->name, "Expo")) skip = 4; else if (startswith(info->name, "Advanced")) skip = 8; else if (startswith(info->name, "LV Display")) skip = 10; else if (startswith(info->name, "LV")) skip = 2; else if (startswith(info->name, "ML")) skip = 2; // keep the first letter from the skipped word char abbr[4] = ""; if (skip > 2) { abbr[0] = info->name[0]; abbr[1] = 0; } snprintf(tmp, sizeof(tmp), "%s%s", abbr, info->name + skip); } int N = strlen(tmp); int char_width = fontspec_font(fnt)->width; int i,j; for (i = 0, j = 0; i < COUNT(sname)-1 && j < N && bmp_string_width(fnt, sname) < maxlen - char_width; j++) { char c = tmp[j]; if (c == ' ') { tmp[j+1] = toupper(tmp[j+1]); continue; } if (c == '.') continue; if (c == '(') break; if (maxlen < 3*char_width && islower(c)) continue; sname[i] = c; i++; } return sname; } static char* junkie_get_shortvalue(struct menu_display_info * info, int fnt, int maxlen) { static char tmp[30]; static char svalue[20]; memset(svalue, 0, sizeof(svalue)); if (info->short_value[0]) { snprintf(tmp, sizeof(tmp), "%s", info->short_value); } else { // skip some common words int skip = 0; if (startswith(info->value, "ON,")) skip = 3; if (startswith(info->value, "Press")) skip = 5; if (startswith(info->value, "up to ")) skip = 6; if (startswith(info->value, "Photo,")) skip = 6; snprintf(tmp, sizeof(tmp), "%s", info->value + skip); } int N = strlen(tmp); int char_width = fontspec_font(fnt)->width; int i,j; for (i = 0, j = 0; i < COUNT(svalue)-1 && j < N && bmp_string_width(fnt, svalue) < maxlen - char_width; j++) { char c = tmp[j]; if (c == ' ') continue; if (c == '(') break; svalue[i] = c; i++; } return svalue; } static char* junkie_get_shorttext(struct menu_display_info * info, int fnt, int maxlen) { // print name or value? if (streq(info->value, "ON") || streq(info->value, "Default") || startswith(info->value, "OFF") || streq(info->value, "Normal") || (!info->value[0] && !info->short_value[0])) { // ON/OFF is obvious by color; print just the name return junkie_get_shortname(info, fnt, maxlen); } else // print value only { char* svalue = junkie_get_shortvalue(info, fnt, maxlen); int len = bmp_string_width(fnt, svalue); int char_width = fontspec_font(fnt)->width; if (maxlen - len >= char_width * 4) // still plenty of space? try to print part of name too { static char nv[30]; char* sname = junkie_get_shortname(info, fnt, maxlen - len - 1); if (bmp_string_width(fnt, sname) >= char_width * 2) { snprintf(nv, sizeof(nv), "%s %s", sname, svalue); return nv; } } return svalue; } } static void entry_print_junkie( int x, int y, int w, int h, struct menu_entry * entry, struct menu_display_info * info, int menu_selected ) { int sel = menu_selected && entry->selected; int fg = 65; int bg = 45; if (info->warning_level == MENU_WARN_NOT_WORKING) { if (info->enabled) { bg = can_be_turned_off(entry) ? COLOR_DARK_GREEN1_MOD : COLOR_DARK_CYAN1_MOD; fg = COLOR_BLACK; } else { bg = 45; fg = COLOR_BLACK; } } else if (info->enabled) { bg = can_be_turned_off(entry) ? COLOR_GREEN1 : COLOR_DARK_CYAN2_MOD; fg = COLOR_BLACK; if (customize_mode) bg = 60; } w -= 2; x += 1; h -= 1; if (sel) // display the full selected entry normally { entry_print(MENU_OFFSET, 390, 10, font_large.height, entry, info, 0); // brighten the selection if (bg == COLOR_GREEN1) bg = COLOR_GREEN2; else if (bg == COLOR_DARK_GREEN1_MOD) bg = COLOR_DARK_GREEN2_MOD; else if (bg == COLOR_DARK_CYAN1_MOD) bg = COLOR_DARK_CYAN2_MOD; else if (bg == COLOR_DARK_CYAN2_MOD) bg = COLOR_CYAN; else if (bg == 45) bg = 50; if (fg == 65) fg = COLOR_WHITE; } int fnt = FONT(FONT_MED, fg, bg); if (h > 30 && w > 130) // we can use large font when we have 5 or fewer tabs fnt = FONT(FONT_LARGE, fg, bg); int maxlen = (w - 8); bmp_fill(bg, x+2, y+2, w-4, h-4); //~ bmp_draw_rect(bg, x+2, y+2, w-4, h-4); char* shorttext = junkie_get_shorttext(info, fnt, maxlen); bmp_printf( fnt, x + (w - bmp_string_width(fnt, shorttext)) / 2 + 2, y + (h - fontspec_height(fnt)) / 2, "%s", shorttext ); // selection bar params // selection bar int selc = sel ? COLOR_WHITE : COLOR_BLACK; //menu_selected ? COLOR_BLUE : entry->selected ? COLOR_BLACK : COLOR_BLACK; bmp_draw_rect_chamfer(selc, x, y, w, h, 3, 0); bmp_draw_rect_chamfer(selc, x+1, y+1, w-2, h-2, 2, 1); bmp_draw_rect_chamfer(selc, x+2, y+2, w-4, h-4, 2, 1); bmp_draw_rect_chamfer(COLOR_BLACK, x+3, y+3, w-6, h-6, 2, 1); //~ draw_line(x, y+h+1, x+w, y+h+1, selc); // round corners /* bmp_putpixel(x+3, y+3, selc); bmp_putpixel(x+3, y+4, selc); bmp_putpixel(x+4, y+3, selc); bmp_putpixel(x+w-3, y+3, selc); bmp_putpixel(x+w-3, y+4, selc); bmp_putpixel(x+w-4, y+3, selc); bmp_putpixel(x+3, y+h-3, selc); bmp_putpixel(x+3, y+h-4, selc); bmp_putpixel(x+4, y+h-3, selc); bmp_putpixel(x+w-3, y+h-3, selc); bmp_putpixel(x+w-3, y+h-4, selc); bmp_putpixel(x+w-4, y+h-3, selc); */ // customization markers if (customize_mode) { display_customize_marker(entry, x + w - 35, y); } } static int menu_entry_process_junkie( struct menu * menu, struct menu_entry * entry, int x, int y, int w, int h ) { // fill in default text, warning checks etc static struct menu_display_info info; entry_default_display_info(entry, &info); info.x = 0; info.y = 0; info.x_val = 0; info.can_custom_draw = 0; // display icon (only the first icon is drawn) icon_drawn = 0; //~ if ((!menu_lv_transparent_mode && !only_selected) || entry->selected) { // should we override some things? if (entry->update) entry->update(entry, &info); // menu->update asked to draw the entire screen by itself? stop drawing right now if (info.custom_drawing == CUSTOM_DRAW_THIS_MENU) return 0; if (info.custom_drawing == CUSTOM_DRAW_DO_NOT_DRAW) menu_redraw_cancel = 1; // print the menu on the screen if (info.custom_drawing == CUSTOM_DRAW_DISABLE) entry_print_junkie(x, y, w, h, entry, &info, menu->selected); } return 1; } static void menu_entry_move( struct menu * menu, int direction ); static int junkie_get_selection_y(struct menu * menu, int* h) { int num = get_menu_visible_count(menu); int space_left = 330; *h = space_left / num; int y = 0; struct menu_entry * entry = menu->children; while( entry ) { if (is_visible(entry)) { // move down for next item int dh = space_left / num; if (entry->selected) // found! return y + dh/2; y += dh; space_left -= dh; num--; } entry = entry->next; } return 0; } static int junkie_selection_pos_y = 10; static void junkie_update_selection_pos(struct menu * menu) { int h; int y = junkie_get_selection_y(menu, &h); int steps = (ABS(junkie_selection_pos_y - y) + h/2) / h; int dir = SGN(junkie_selection_pos_y - y); for (int i = 0; i < steps; i++) menu_entry_move(menu, dir); } static void junkie_sync_selection() { struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if (!menu->selected && !IS_SUBMENU(menu)) { junkie_update_selection_pos(menu); } } } static void menu_display_junkie( struct menu * menu, int x, int y, int w ) { int num = get_menu_visible_count(menu); int h = 330 / num; int space_left = 330; if (!menu_lv_transparent_mode && menu->selected) menu_clean_footer(); struct menu_entry * entry = menu->children; while( entry ) { if (is_visible(entry)) { //~ int h = font_large.height - 3; // display current entry int ok = menu_entry_process_junkie(menu, entry, x, y, w, h); // entry asked for custom draw? stop here if (!ok) break; //~ if (entry->warning_level == MENU_WARN_NOT_WORKING) //~ continue; // move down for next item int dh = space_left / num; y += dh; space_left -= dh; num--; } entry = entry->next; } // all menus displayed, now some extra stuff menu_post_display(); } static void show_hidden_items(struct menu * menu, int force_clear) { // show any items that may be hidden if (!menu_lv_transparent_mode) { char hidden_msg[100]; snprintf(hidden_msg, sizeof(hidden_msg), "Hidden: "); int hidden_count = 0; struct menu_entry * entry = menu->children; while( entry ) { if (HAS_HIDDEN_FLAG(entry) && entry->name) { if (hidden_count) { STR_APPEND(hidden_msg, ", "); } int len = strlen(hidden_msg); STR_APPEND(hidden_msg, "%s", entry->name); while (isspace(hidden_msg[strlen(hidden_msg)-1])) hidden_msg[strlen(hidden_msg)-1] = '\0'; while (ispunct(hidden_msg[strlen(hidden_msg)-1])) hidden_msg[strlen(hidden_msg)-1] = '\0'; hidden_msg[MIN(len+15, (int)sizeof(hidden_msg))] = '\0'; hidden_count++; } entry = entry->next; } STR_APPEND(hidden_msg, customize_mode ? "." : " (Prefs->Customize)."); unsigned maxlen = bmp_strlen_clipped(FONT_MED, hidden_msg, 700); if (strlen(hidden_msg) > maxlen) { hidden_msg[maxlen-1] = hidden_msg[maxlen-2] = hidden_msg[maxlen-3] = '.'; hidden_msg[maxlen] = '\0'; } int hidden_pos_y = 410; if (is_menu_active("Help")) hidden_pos_y -= font_med.height; if (hidden_count) { bmp_fill(COLOR_BLACK, 0, hidden_pos_y, 720, 19); bmp_printf( SHADOW_FONT(FONT(FONT_MED, customize_mode ? MENU_WARNING_COLOR : COLOR_ORANGE , MENU_BG_COLOR_HEADER_FOOTER)), 10, hidden_pos_y, hidden_msg ); } } } static void show_vscroll(struct menu * parent){ if (edit_mode) return; int pos = get_menu_selected_pos(parent); int max = get_menu_visible_count(parent); int menu_len = MENU_LEN; if(max > menu_len + 1){ int y_lo = 44; int h = submenu_level ? 378 : 385; int size = (h - y_lo) * menu_len / max; int y = y_lo + ((h - size) * (pos-1) / (max-1)); int x = MIN(360 + g_submenu_width/2, 720-3); if (submenu_level) x -= 6; bmp_fill(COLOR_BLACK, x-2, y_lo, 6, h); bmp_fill(MENU_BAR_COLOR, x, y, 3, size); } } static void menus_display( struct menu * menu, int orig_x, int y ) { g_submenu_width = 720; if (my_menu_dirty) my_menu_rebuild(); if (mod_menu_dirty) mod_menu_rebuild(); struct menu * submenu = 0; if (submenu_level) submenu = get_current_submenu(); advanced_mode = submenu ? submenu->advanced : 1; if (junkie_mode) junkie_sync_selection(); #ifdef SUBMENU_DEBUG_JUNKIE struct menu * junkie_sub = 0; if (junkie_mode == 2) { struct menu_entry * entry = get_selected_entry(0); if (entry && entry->children) junkie_sub = menu_find_by_name(entry->name, 0); } #endif take_semaphore( menu_sem, 0 ); // will override them only if rack focus items are selected reset_override_zoom_buttons(); // how many tabs should we display? we should know in order to adjust the spacing between them // keep the conditions in sync with the next loop int num_tabs = 0; for(struct menu * tmp_menu = menu ; tmp_menu ; tmp_menu = tmp_menu->next ) { if (!menu_has_visible_items(tmp_menu) && !tmp_menu->selected) continue; // empty menu if (IS_SUBMENU(tmp_menu)) continue; num_tabs++; } int x = orig_x + junkie_mode ? 2 : 150; int icon_spacing = junkie_mode ? 716 / num_tabs : (720 - 150) / num_tabs; int bgs = COLOR_BLACK; int bgu = MENU_BG_COLOR_HEADER_FOOTER; int fgu = COLOR_GRAY(35); int fgs = COLOR_WHITE; if (customize_mode) fgs = get_customize_color(); bmp_fill(bgu, orig_x, y, 720, 42); //~ bmp_fill(fgu, orig_x, y+42, 720, 2); for( ; menu ; menu = menu->next ) { if (!menu_has_visible_items(menu) && !menu->selected) continue; // empty menu if (IS_SUBMENU(menu)) continue; int fg = menu->selected ? fgs : fgu; int bg = menu->selected ? bgs : bgu; if (!menu_lv_transparent_mode) { if (menu->selected) bmp_fill(bg, x-1, y+2, icon_spacing+3, 38); int icon_char = menu->icon ? menu->icon : menu->name[0]; int icon_width = bfnt_char_get_width(icon_char); int x_ico = x + (icon_spacing - icon_width) / 2 + 1; bfnt_draw_char(icon_char, x_ico, y + 2, fg, bg); if (menu->selected) { //~ bmp_printf(FONT_MED, 720 - strlen(menu->name)*font_med.width, 50, menu->name); //~ else if (!junkie_mode) bmp_printf(FONT(FONT_CANON, fg, bg), 5, y, "%s", menu->name); int x1 = x - 1; int x2 = x1 + icon_spacing + 2; //~ draw_line(x1, y+42-4, x1, y+5, fgu); //~ draw_line(x2, y+42-4, x2, y+5, fgu); //~ draw_line(x1-1, y+42-4, x1-1, y+5, fgu); //~ draw_line(x2+1, y+42-4, x2+1, y+5, fgu); //~ draw_line(x1+4, y+1, x2-4, y+1, fgu); //~ draw_line(x1+4, y, x2-4, y, fgu); draw_line(x1-1, y+40, x2+1, y+40, bgs); draw_line(x1-2, y+41, x2+2, y+41, bgs); draw_line(x1-3, y+42, x2+3, y+42, bgs); draw_line(x1-4, y+43, x2+4, y+43, bgs); //~ draw_line(x1-4, y+42, x1, y+42-4, fgu); //~ draw_line(x2+4, y+42, x2, y+42-4, fgu); //~ draw_line(x1-4, y+41, x1, y+41-4, fgu); //~ draw_line(x2+4, y+41, x2, y+41-4, fgu); //~ draw_line(x1, y+5, x1+4, y+1, fgu); //~ draw_line(x2, y+5, x2-4, y+1, fgu); //~ draw_line(x1, y+4, x1+4, y, fgu); //~ draw_line(x2, y+4, x2-4, y, fgu); draw_line(x1, y+2, x1, y+3, bgu); draw_line(x1+1, y+2, x1+1, y+2, bgu); draw_line(x2, y+2, x2, y+3, bgu); draw_line(x2-1, y+2, x2-1, y+2, bgu); } x += icon_spacing; } if (submenu) continue; if (junkie_mode && !edit_mode && !menu_lv_transparent_mode) { struct menu * mn = menu; #ifdef SUBMENU_DEBUG_JUNKIE if (junkie_sub && mn == my_menu) mn = junkie_sub; #endif menu_display_junkie( mn, x - icon_spacing, y + 55, icon_spacing ); } else if( menu->selected) { menu_display( menu, orig_x + MENU_OFFSET, y + 55, edit_mode ? 1 : 0 ); show_vscroll(menu); show_hidden_items(menu, 0); } } // debug //~ if (junkie_mode) //~ draw_line(0, 55 + junkie_selection_pos_y, 720, 55 + junkie_selection_pos_y, COLOR_BLUE); if (submenu) { //~ dim_screen(43, COLOR_BLACK, 0, 45, 720, 480-45-50); submenu_display(submenu); show_vscroll(submenu); } give_semaphore( menu_sem ); } /* static void implicit_submenu_display() { struct menu * menu = get_selected_menu(); menu_display( menu, MENU_OFFSET, 55, 1 ); } */ static int submenu_default_height(int count) { return MIN(422, count * (font_large.height + 2) + 40 + 50 - (count > 7 ? 30 : 0)); /* body + titlebar + padding - smaller padding for large submenus */ } static void submenu_display(struct menu * submenu) { if (!submenu) return; int count = get_menu_visible_count(submenu); int h = submenu->submenu_height ? submenu->submenu_height : submenu_default_height(count); int w = submenu->submenu_width ? submenu->submenu_width : 600; // submenu promoted to pickbox? expand the pickbox by default if (IS_SINGLE_ITEM_SUBMENU_ENTRY(submenu->children)) { w = 720; int num_choices = submenu->children[0].max - submenu->children[0].min; if (CAN_HAVE_PICKBOX(submenu->children)) { h = MAX(h, submenu_default_height(num_choices)+7); } } w = MIN(w, 720-10); g_submenu_width = w; int bx = (720 - w)/2; int by = (480 - h)/2 - 30; by = MAX(by, 3); // submenu header if ( (IS_SINGLE_ITEM_SUBMENU_ENTRY(submenu->children) && edit_mode) // promoted submenu || (!menu_lv_transparent_mode && !edit_mode) ) { w = 720-2*bx; bmp_fill(MENU_BG_COLOR_HEADER_FOOTER, bx, by, w, 40); bmp_fill(COLOR_BLACK, bx, by + 40, w, h-40); bmp_printf(FONT(FONT_CANON, COLOR_WHITE, 40), bx + 15, by+2, "%s", submenu->name); for (int i = 0; i < 5; i++) bmp_draw_rect(45, bx-i, by-i, w+i*2, h+i*2); /* gradient experiments for (int i = 0; i < 3; i++) bmp_draw_rect(38 + i, bx-i, by-i, w+i*2, h+i*2); for (int i = 3; i < 7; i++) bmp_draw_rect(42, bx-i, by-i, w+i*2, h+i*2); for (int i = 7; i < 10; i++) bmp_draw_rect(48-i, bx-i, by-i, w+i*2, h+i*2); for (int i = 10; i < 15; i++) bmp_draw_rect(COLOR_BLACK, bx-i, by-i, w+i*2, h+i*2); */ submenu_key_hint(720-bx-45, by+5, COLOR_WHITE, MENU_BG_COLOR_HEADER_FOOTER, ICON_ML_Q_BACK); } /* titlebar + padding difference for large submenus */ menu_display(submenu, bx + SUBMENU_OFFSET, by + 40 + (count > 7 ? 10 : 25), edit_mode ? 1 : 0); show_hidden_items(submenu, 1); } static void menu_entry_showhide_toggle( struct menu * menu, struct menu_entry * entry ) { if( !entry ) return; if (junkie_mode) entry->jhidden = !entry->jhidden; else entry->hidden = !entry->hidden; } // this can fail if there are too many starred items (1=success, 0=fail) static int menu_entry_star_toggle( struct menu_entry * entry ) { if( !entry ) return 0; entry->starred = !entry->starred; menu_flags_save_dirty = 1; int ok = my_menu_rebuild(); if (!ok) { entry->starred = 0; my_menu_rebuild(); return 0; } return 1; } // normal -> starred -> hidden static void menu_entry_customize_toggle( struct menu * menu ) { struct menu_entry * entry = get_selected_entry(menu); if (!entry) return; if (menu == my_menu) // special case { // lookup the corresponding entry in normal menus, and toggle that one instead char* name = (char*) entry->name; // trick so we don't find the same menu entry->name = 0; // (this menu will be rebuilt anyway, so... no big deal) entry = entry_find_by_name(entry->parent_menu->name, name); if (!entry) { beep(); return; } if (!entry->starred) return; menu_entry_star_toggle(entry); // should not fail return; } if (entry->starred && HAS_CURRENT_HIDDEN_FLAG(entry)) // both flags active, abnormal { menu_entry_showhide_toggle(menu, entry); // keep the star flag } if (!entry->starred && !HAS_CURRENT_HIDDEN_FLAG(entry)) // normal -> starred { int ok = menu_entry_star_toggle(entry); if (!ok) menu_entry_showhide_toggle(menu, entry); // too many starred items? just hide } else if (entry->starred && !HAS_CURRENT_HIDDEN_FLAG(entry)) // starred -> hidden { menu_entry_star_toggle(entry); // should not fail menu_entry_showhide_toggle(menu, entry); } else if (!entry->starred && HAS_CURRENT_HIDDEN_FLAG(entry)) // hidden -> normal { menu_entry_showhide_toggle(menu, entry); } menu_flags_save_dirty = 1; my_menu_dirty = 1; menu_make_sure_selection_is_valid(); } static void menu_entry_select( struct menu * menu, int mode // 0 = increment, 1 = decrement, 2 = Q, 3 = SET ) { if( !menu ) return; struct menu_entry * entry = get_selected_entry(menu); if( !entry ) { /* empty submenu? go back */ menu_lv_transparent_mode = edit_mode = 0; submenu_level = MAX(submenu_level - 1, 0); return; } // don't perform actions on empty items (can happen on empty submenus) if (!is_visible(entry)) { edit_mode = 0; submenu_level = MAX(submenu_level - 1, 0); menu_lv_transparent_mode = 0; return; } if(mode == 1) // decrement { if (entry->select) { /* custom select function? use it */ entry->select( entry->priv, -1); } else if IS_ML_PTR(entry->priv) { /* .priv is a variable? in edit mode, increment according to caret_position, otherwise use exponential R20 toggle */ /* exception: hex fields are never fast-toggled */ if (editing_with_caret(entry) || (entry->unit == UNIT_HEX)) menu_numeric_toggle(entry->priv, get_delta(entry,-1), entry->min, entry->max); else menu_numeric_toggle_fast(entry->priv, -1, entry->min, entry->max, entry->unit == UNIT_TIME); } } else if (mode == 2) // Q { bool promotable_to_pickbox = HAS_SINGLE_ITEM_SUBMENU(entry) && SHOULD_USE_EDIT_MODE(entry->children); if (menu_lv_transparent_mode) { menu_lv_transparent_mode = 0; } else if (edit_mode) { edit_mode = 0; submenu_level = MAX(submenu_level - 1, 0); } else if ( entry->select_Q ) entry->select_Q( entry->priv, 1); // caution: entry may now be a dangling pointer else menu_toggle_submenu(); // submenu with a single entry? promote it as pickbox if (submenu_level && promotable_to_pickbox) edit_mode = 1; } else if (mode == 3) // SET { /* if (set_action == 0) // pickbox { if (entry->icon_type != IT_SUBMENU) edit_mode = !edit_mode; else if( entry->select ) entry->select( entry->priv, 1); else edit_mode = !edit_mode; } else if (set_action == 1) // toggle { if (edit_mode) edit_mode = 0; else if( entry->select ) entry->select( entry->priv, 1); else if IS_ML_PTR(entry->priv) menu_numeric_toggle_fast(entry->priv, 1, entry->min, entry->max); } else */ { if (submenu_level && edit_mode && IS_SINGLE_ITEM_SUBMENU_ENTRY(entry)) { edit_mode = 0; submenu_level = MAX(submenu_level - 1, 0); } else if (edit_mode) edit_mode = 0; else if (menu_lv_transparent_mode && entry->icon_type != IT_ACTION) menu_lv_transparent_mode = 0; else if (entry->edit_mode == EM_MANY_VALUES) edit_mode = !edit_mode; else if (entry->edit_mode == EM_MANY_VALUES_LV && lv) menu_lv_transparent_mode = !menu_lv_transparent_mode; else if (entry->edit_mode == EM_MANY_VALUES_LV && !lv) edit_mode = !edit_mode; else if (SHOULD_USE_EDIT_MODE(entry)) edit_mode = !edit_mode; else if (entry->select) entry->select( entry->priv, 1); else if IS_ML_PTR(entry->priv) menu_numeric_toggle_fast(entry->priv, 1, entry->min, entry->max, entry->unit == UNIT_TIME); } } else // increment (same logic as decrement) { if( entry->select ) { entry->select( entry->priv, 1); } else if (IS_ML_PTR(entry->priv)) { if (editing_with_caret(entry) || (entry->unit == UNIT_HEX)) menu_numeric_toggle(entry->priv, get_delta(entry,1), entry->min, entry->max); else menu_numeric_toggle_fast(entry->priv, 1, entry->min, entry->max, entry->unit == UNIT_TIME); } } if(entry->unit == UNIT_TIME && edit_mode && caret_position == 0) caret_position = 1; config_dirty = 1; mod_menu_dirty = 1; } /** Scroll side to side in the list of menus */ static void menu_move( struct menu * menu, int direction ) { //~ menu_damage = 1; if( !menu ) return; take_semaphore( menu_sem, 0 ); // Deselect the current one menu->selected = 0; do { if( direction < 0 ) { if( menu->prev ) menu = menu->prev; else { // Go to the last one while( menu->next ) menu = menu->next; } } else { if( menu->next ) menu = menu->next; else { // Go to the first one while( menu->prev ) menu = menu->prev; } } } while ((IS_SUBMENU(menu)) || /* always skip submenus */ (!menu_has_visible_items(menu) && are_there_any_visible_menus())); /* skip empty menus */ // Select the new one (which might be the same) menu->selected = 1; menu_first_by_icon = menu->icon; /* rebuild the modified settings menu */ mod_menu_dirty = 1; give_semaphore( menu_sem ); } /** Scroll up or down in the currently displayed menu */ static void menu_entry_move( struct menu * menu, int direction ) { if( !menu ) return; take_semaphore( menu_sem, 0 ); if (!menu_has_visible_items(menu)) { give_semaphore( menu_sem ); return; } struct menu_entry * entry = menu->children; for( ; entry ; entry = entry->next ) { if( entry->selected ) break; } // Nothing selected? if( !entry ) { give_semaphore( menu_sem ); return; } // Deslect the current one entry->selected = 0; do { if( direction < 0 ) { // First and moving up? if( entry->prev ){ entry = entry->prev; }else { // Go to the last one while( entry->next ) entry = entry->next; } } else { // Last and moving down? if( entry->next ){ entry = entry->next; }else { // Go to the first one while( entry->prev ) entry = entry->prev; } } } while (!is_visible(entry) && menu_has_visible_items(menu)); /* skip hidden items */ // Select the new one, which might be the same as the old one entry->selected = 1; if (!menu_lv_transparent_mode) { /* reset caret_position */ /* don't reset it when LV is behind our menu, since in this case we usually want to edit many similar fields */ caret_position = entry->unit == UNIT_TIME ? 1 : 0; } give_semaphore( menu_sem ); if (junkie_mode && menu->selected) { int unused; junkie_selection_pos_y = junkie_get_selection_y(menu, &unused); } } // Make sure we will not display an empty menu // If the menu or the selection is empty, move back and forth to restore a valid selection static void menu_make_sure_selection_is_valid() { struct menu * menu = get_selected_menu(); if (submenu_level) { struct menu * main_menu = menu; menu = get_current_submenu(); if (!menu) menu = main_menu; // no submenu, operate on same item } // current menu has any valid items in current mode? if (!menu_has_visible_items(menu)) { if (submenu_level) return; // empty submenu menu_move(menu, -1); menu = get_selected_menu(); menu_move(menu, 1); menu = get_selected_menu(); } // currently selected menu entry is visible? struct menu_entry * entry = get_selected_entry(menu); if (!entry) return; if (entry->selected && !is_visible(entry)) { menu_entry_move(menu, -1); menu_entry_move(menu, 1); } } /*static void menu_select_current(int reverse) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) if( menu->selected ) break; menu_entry_select(menu,reverse); }*/ CONFIG_INT("menu.upside.down", menu_upside_down, 0); static void menu_redraw_do() { menu_damage = 0; //~ g_submenu_width = 720; if (!DISPLAY_IS_ON) return; if (sensor_cleaning) return; if (gui_state == GUISTATE_MENUDISP) return; if (menu_help_active) { menu_help_redraw(); menu_damage = 0; } else { if (!lv) menu_lv_transparent_mode = 0; if (menu_lv_transparent_mode && edit_mode) edit_mode = 0; if (DOUBLE_BUFFERING) { // draw to mirror buffer to avoid flicker //~ bmp_idle_copy(0); // no need, drawing is fullscreen anyway bmp_draw_to_idle(1); } /* int z = zebra_should_run(); if (menu_zebras_mirror_dirty && !z) { clear_zebras_from_mirror(); menu_zebras_mirror_dirty = 0; }*/ if (menu_lv_transparent_mode) { bmp_fill( 0, 0, 0, 720, 480 ); /* if (z) { if (prev_z) copy_zebras_from_mirror(); else cropmark_clear_cache(); // will clear BVRAM mirror and reset cropmarks menu_zebras_mirror_dirty = 1; } */ if (hist_countdown == 0 && !should_draw_zoom_overlay()) draw_histogram_and_waveform(0); // too slow else hist_countdown--; } else { bmp_fill(COLOR_BLACK, 0, 40, 720, 400 ); } //~ prev_z = z; menu_make_sure_selection_is_valid(); menus_display( menus, 0, 0 ); if (!menu_lv_transparent_mode && !SUBMENU_OR_EDIT && !junkie_mode) { if (is_menu_active("Help")) menu_show_version(); } if (menu_lv_transparent_mode) { draw_ml_topbar(); draw_ml_bottombar(); bfnt_draw_char(ICON_ML_Q_BACK, 680, -5, COLOR_WHITE, COLOR_BLACK); } if (beta_should_warn()) draw_beta_warning(); #ifdef CONFIG_CONSOLE console_draw_from_menu(); #endif if (DOUBLE_BUFFERING) { // copy image to main buffer bmp_draw_to_idle(0); if (menu_redraw_cancel) { /* maybe next time */ menu_redraw_cancel = 0; } else { int screen_layout = get_screen_layout(); if (hdmi_code == 2) // copy at a smaller scale to fit the screen { if (screen_layout == SCREENLAYOUT_16_10) bmp_zoom(bmp_vram(), bmp_vram_idle(), 360, 150, /* 128 div */ 143, /* 128 div */ 169); else if (screen_layout == SCREENLAYOUT_16_9) bmp_zoom(bmp_vram(), bmp_vram_idle(), 360, 165, /* 128 div */ 143, /* 128 div */ 185); else { if (menu_upside_down) bmp_flip(bmp_vram(), bmp_vram_idle(), 0); else bmp_idle_copy(1,0); } } else if (EXT_MONITOR_RCA) bmp_zoom(bmp_vram(), bmp_vram_idle(), 360, 200, /* 128 div */ 135, /* 128 div */ 135); else { if (menu_upside_down) bmp_flip(bmp_vram(), bmp_vram_idle(), 0); else bmp_idle_copy(1,0); } } //~ bmp_idle_clear(); } //~ update_stuff(); lens_display_set_dirty(); } bmp_on(); #ifdef FEATURE_COLOR_SCHEME if (!bmp_color_scheme) { // adjust some colors for better contrast alter_bitmap_palette_entry(COLOR_DARK_GREEN1_MOD, COLOR_GREEN1, 100, 100); alter_bitmap_palette_entry(COLOR_DARK_GREEN2_MOD, COLOR_GREEN1, 200, 200); alter_bitmap_palette_entry(COLOR_GREEN2, COLOR_GREEN2, 300, 256); //~ alter_bitmap_palette_entry(COLOR_ORANGE, COLOR_ORANGE, 160, 160); alter_bitmap_palette_entry(COLOR_DARK_ORANGE_MOD, COLOR_ORANGE, 160, 160); alter_bitmap_palette_entry(COLOR_DARK_CYAN1_MOD, COLOR_CYAN, 60, 60); alter_bitmap_palette_entry(COLOR_DARK_CYAN2_MOD, COLOR_CYAN, 128, 128); // alter_bitmap_palette_entry(COLOR_DARK_YELLOW_MOD, COLOR_YELLOW, 128, 128); if (RECORDING) alter_bitmap_palette_entry(COLOR_BLACK, COLOR_BG, 256, 256); } #endif #ifdef CONFIG_VXWORKS set_ml_palette(); #endif } void menu_benchmark() { SetGUIRequestMode(1); msleep(1000); int t0 = get_ms_clock_value(); for (int i = 0; i < 500; i++) { menu_redraw_do(); bmp_printf(FONT_MED, 0, 0, "%d%% ", i/5); } int t1 = get_ms_clock_value(); clrscr(); NotifyBox(20000, "Elapsed time: %d ms", t1 - t0); } static int menu_ensure_canon_dialog() { #ifndef CONFIG_VXWORKS if (CURRENT_DIALOG_MAYBE != GUIMODE_ML_MENU && CURRENT_DIALOG_MAYBE != DLG_PLAY) { if (redraw_flood_stop) { // Canon dialog timed out? #if defined(CONFIG_MENU_TIMEOUT_FIX) // force dialog change when canon dialog times out (EOSM, 6D etc) // don't try more often than once per second static int aux = 0; if (should_run_polling_action(1000, &aux)) { start_redraw_flood(); SetGUIRequestMode(GUIMODE_ML_MENU); } #else return 0; #endif } } #endif return 1; } static struct msg_queue * menu_redraw_queue = 0; static void menu_redraw_task() { menu_redraw_queue = (struct msg_queue *) msg_queue_create("menu_redraw_mq", 1); TASK_LOOP { /* this loop will only receive redraw messages */ int msg; int err = msg_queue_receive(menu_redraw_queue, (struct event**)&msg, 500); if (err) continue; if (gui_menu_shown()) { if (get_halfshutter_pressed()) { /* close menu on half-shutter */ /* (the event is not always caught by the key handler) */ gui_stop_menu(); continue; } /* make sure to check the canon dialog even if drawing is blocked * (for scripts and such that piggyback the ML menu) */ if (!menu_ensure_canon_dialog()) { /* didn't work, close ML menu */ gui_stop_menu(); continue; } if (!menu_redraw_blocked) { menu_redraw_do(); } } } } TASK_CREATE( "menu_redraw_task", menu_redraw_task, 0, 0x1a, 0x8000 ); void menu_redraw() { if (!DISPLAY_IS_ON) return; if (ml_shutdown_requested) return; if (menu_help_active) bmp_draw_request_stop(); if (menu_redraw_queue) msg_queue_post(menu_redraw_queue, MENU_REDRAW); } static void menu_redraw_full() { if (!DISPLAY_IS_ON) return; if (ml_shutdown_requested) return; if (menu_help_active) bmp_draw_request_stop(); if (menu_redraw_queue) msg_queue_post(menu_redraw_queue, MENU_REDRAW); } static struct menu * get_selected_menu() { struct menu * menu = menus; for( ; menu ; menu = menu->next ) if( menu->selected ) break; return menu; } static struct menu_entry * get_selected_entry(struct menu * menu) // argument is optional, just for speedup { if (!menu) { menu = menus; for( ; menu ; menu = menu->next ) if( menu->selected ) break; } struct menu_entry * entry = menu->children; for( ; entry ; entry = entry->next ) if( entry->selected ) return entry; return 0; } static struct menu * get_current_submenu() { struct menu_entry * entry = get_selected_entry(0); if (!entry) return 0; for(int level = submenu_level; level > 1; level--) { for(entry = entry->children ; entry ; entry = entry->next ) { if( entry->selected ) break; } if(!entry) break; } if (entry && entry->children) return menu_find_by_name(entry->name, 0); // no submenu, fall back to edit mode submenu_level--; edit_mode = 1; return 0; } static int keyrepeat = 0; static int keyrep_countdown = 4; static int keyrep_ack = 0; int handle_ml_menu_keyrepeat(struct event * event) { //~ if (menu_shown || arrow_keys_shortcuts_active()) { switch(event->param) { case BGMT_PRESS_LEFT: case BGMT_PRESS_RIGHT: case BGMT_PRESS_UP: case BGMT_PRESS_DOWN: #ifdef BGMT_PRESS_UP_LEFT case BGMT_PRESS_UP_LEFT: case BGMT_PRESS_UP_RIGHT: case BGMT_PRESS_DOWN_LEFT: case BGMT_PRESS_DOWN_RIGHT: #endif if (keyrepeat && event->param != keyrepeat) keyrepeat = 0; else keyrepeat = event->param; break; #ifdef BGMT_UNPRESS_UDLR case BGMT_UNPRESS_UDLR: #else case BGMT_UNPRESS_LEFT: case BGMT_UNPRESS_RIGHT: case BGMT_UNPRESS_UP: case BGMT_UNPRESS_DOWN: #endif keyrepeat = 0; keyrep_countdown = 4; keyrep_ack = 0; break; } } return 1; } void keyrepeat_ack(int button_code) // also for arrow shortcuts { keyrep_ack = (button_code == keyrepeat); } #ifdef CONFIG_TOUCHSCREEN int handle_ml_menu_touch(struct event * event) { int button_code = event->param; switch (button_code) { case BGMT_TOUCH_1_FINGER: fake_simple_button(BGMT_Q); return 0; case BGMT_TOUCH_2_FINGER: fake_simple_button(BGMT_TRASH); return 0; case BGMT_UNTOUCH_1_FINGER: case BGMT_UNTOUCH_2_FINGER: return 0; default: return 1; } return 1; } #endif int handle_ml_menu_keys(struct event * event) { if (menu_shown || arrow_keys_shortcuts_active()) handle_ml_menu_keyrepeat(event); if (!menu_shown) return 1; if (!DISPLAY_IS_ON) if (event->param != BGMT_PRESS_HALFSHUTTER) return 1; // on some cameras, scroll events may arrive grouped; we can't handle it, so split into individual events if (handle_scrollwheel_fast_clicks(event)==0) return 0; // rack focus may override some menu keys if (handle_rack_focus_menu_overrides(event)==0) return 0; if (beta_should_warn()) { if (event->param == BGMT_PRESS_SET || event->param == BGMT_MENU || event->param == BGMT_TRASH || event->param == BGMT_PLAY || event->param == BGMT_PRESS_HALFSHUTTER || event->param == BGMT_PRESS_UP || event->param == BGMT_PRESS_DOWN || event->param == BGMT_PRESS_LEFT || event->param == BGMT_PRESS_RIGHT || event->param == BGMT_WHEEL_UP || event->param == BGMT_WHEEL_DOWN || event->param == BGMT_WHEEL_LEFT || event->param == BGMT_WHEEL_RIGHT ) #ifndef CONFIG_RELEASE_BUILD //gives compiling errors on 5DC { beta_set_warned(); menu_redraw(); } #endif if (event->param != BGMT_PRESS_HALFSHUTTER) return 0; } // Find the selected menu (should be cached?) struct menu * menu = get_selected_menu(); struct menu * main_menu = menu; if (submenu_level) { main_menu = menu; menu = get_current_submenu(); if (!menu) menu = main_menu; // no submenu, operate on same item } int button_code = event->param; #if defined(CONFIG_60D) || defined(CONFIG_600D) || defined(CONFIG_7D) // Q not working while recording, use INFO instead if (button_code == BGMT_INFO && RECORDING) button_code = BGMT_Q; #endif int menu_needs_full_redraw = 0; // if true, do not allow quick redraws switch( button_code ) { case BGMT_MENU: { if (SUBMENU_OR_EDIT || menu_lv_transparent_mode || menu_help_active) { submenu_level = 0; edit_mode = 0; menu_lv_transparent_mode = 0; menu_help_active = 0; } else { // double click will go to "extra junkie" mode (nothing hidden) static int last_t = 0; int t = get_ms_clock_value(); if (t > last_t && t < last_t + 300) junkie_mode = !junkie_mode*2; else junkie_mode = !junkie_mode; last_t = t; } break; } #ifdef BGMT_PRESS_UP_LEFT case BGMT_PRESS_UP_LEFT: case BGMT_PRESS_UP_RIGHT: case BGMT_PRESS_DOWN_LEFT: case BGMT_PRESS_DOWN_RIGHT: return 0; // ignore diagonal buttons #endif case BGMT_PRESS_HALFSHUTTER: // If they press the shutter halfway //~ menu_close(); redraw_flood_stop = 1; give_semaphore(gui_sem); return 1; #if !defined(CONFIG_500D) && !defined(CONFIG_5DC) // LV is Q case BGMT_LV: if (!lv) return 1; // else fallthru #endif case BGMT_PRESS_ZOOMIN_MAYBE: if (lv) menu_lv_transparent_mode = !menu_lv_transparent_mode; else edit_mode = !edit_mode; menu_damage = 1; menu_help_active = 0; break; case BGMT_PRESS_UP: if (edit_mode && !menu_lv_transparent_mode) { struct menu_entry * entry = get_selected_entry(menu); if(entry && uses_caret_editing(entry)) { menu_entry_select( menu, 0 ); break; } } case BGMT_WHEEL_UP: if (menu_help_active) { menu_help_prev_page(); break; } if (edit_mode && !menu_lv_transparent_mode) menu_entry_select( menu, 1 ); else { menu_entry_move( menu, -1 ); if (menu_lv_transparent_mode) menu_needs_full_redraw = 1; } break; case BGMT_PRESS_DOWN: if (edit_mode && !menu_lv_transparent_mode) { struct menu_entry * entry = get_selected_entry(menu); if(entry && uses_caret_editing(entry)) { menu_entry_select( menu, 1 ); break; } } case BGMT_WHEEL_DOWN: if (menu_help_active) { menu_help_next_page(); break; } if (edit_mode && !menu_lv_transparent_mode) menu_entry_select( menu, 0 ); else { menu_entry_move( menu, 1 ); if (menu_lv_transparent_mode) menu_needs_full_redraw = 1; } break; case BGMT_PRESS_RIGHT: if(EDIT_OR_TRANSPARENT) { struct menu_entry * entry = get_selected_entry(menu); if(entry && uses_caret_editing(entry)) { caret_move(entry, -1); menu_damage = 1; break; } } case BGMT_WHEEL_RIGHT: menu_damage = 1; if (menu_help_active) { menu_help_next_page(); break; } if (SUBMENU_OR_EDIT || menu_lv_transparent_mode) menu_entry_select( menu, 0 ); else { menu_move( menu, 1 ); menu_lv_transparent_mode = 0; menu_needs_full_redraw = 1; } //~ menu_hidden_should_display_help = 0; break; case BGMT_PRESS_LEFT: if(EDIT_OR_TRANSPARENT) { struct menu_entry * entry = get_selected_entry(menu); if(entry && uses_caret_editing(entry)) { caret_move(entry, 1); menu_damage = 1; break; } } case BGMT_WHEEL_LEFT: menu_damage = 1; if (menu_help_active) { menu_help_prev_page(); break; } if (SUBMENU_OR_EDIT || menu_lv_transparent_mode) menu_entry_select( menu, 1 ); else { menu_move( menu, -1 ); menu_lv_transparent_mode = 0; menu_needs_full_redraw = 1; } //~ menu_hidden_should_display_help = 0; break; case BGMT_UNPRESS_SET: return 0; // block Canon menu redraws #if defined(CONFIG_7D) case BGMT_JOY_CENTER: #endif case BGMT_PRESS_SET: if (menu_help_active) // pel, don't touch this! { menu_help_active = 0; break; } else if (customize_mode && !is_customize_selected(menu)) { menu_entry_customize_toggle(menu); } else { menu_entry_select( menu, 3 ); // "SET" select menu_needs_full_redraw = 1; } //~ menu_damage = 1; //~ menu_hidden_should_display_help = 0; break; case BGMT_INFO: menu_help_active = !menu_help_active; menu_lv_transparent_mode = 0; if (menu_help_active) menu_help_go_to_selected_entry(main_menu); menu_needs_full_redraw = 1; //~ menu_damage = 1; //~ menu_hidden_should_display_help = 0; break; case BGMT_PLAY: if (menu_help_active) { menu_help_active = 0; /* menu_damage = 1; */ break; } menu_entry_select( menu, 1 ); // decrement menu_needs_full_redraw = 1; //~ menu_damage = 1; //~ menu_hidden_should_display_help = 0; break; #ifdef CONFIG_TOUCHSCREEN case BGMT_TOUCH_1_FINGER: case BGMT_TOUCH_2_FINGER: case BGMT_UNTOUCH_1_FINGER: case BGMT_UNTOUCH_2_FINGER: return handle_ml_menu_touch(event); #endif #ifdef BGMT_RATE case BGMT_RATE: #endif #if defined(BGMT_Q) case BGMT_Q: #endif #ifdef BGMT_Q_ALT case BGMT_Q_ALT: #endif //~ #ifdef BGMT_JOY_CENTER //~ case BGMT_JOY_CENTER: //~ #endif #if defined(CONFIG_5D2) || defined(CONFIG_7D) case BGMT_PICSTYLE: #endif #ifdef CONFIG_50D case BGMT_FUNC: //~ case BGMT_LV: #endif #ifdef CONFIG_500D case BGMT_LV: #endif #ifdef CONFIG_5DC case BGMT_JUMP: case BGMT_PRESS_DIRECT_PRINT: #endif case MLEV_JOYSTICK_LONG: if (menu_help_active) { menu_help_active = 0; /* menu_damage = 1; */ break; } menu_entry_select( menu, 2 ); // Q action select menu_needs_full_redraw = 1; //~ menu_damage = 1; //~ menu_hidden_should_display_help = 0; break; default: /*DebugMsg( DM_MAGIC, 3, "%s: unknown event %08x? %08x %08x %x08", __func__, event, arg2, arg3, arg4 );*/ return 1; } // If we end up here, something has been changed. // Reset the timeout // if submenu mode was changed, force a full redraw static int prev_menu_mode = 0; int menu_mode = submenu_level | edit_mode*2 | menu_lv_transparent_mode*4 | customize_mode*8 | junkie_mode*16; if (menu_mode != prev_menu_mode) menu_needs_full_redraw = 1; prev_menu_mode = menu_mode; if (menu_needs_full_redraw) menu_redraw_full(); else menu_redraw(); keyrepeat_ack(button_code); hist_countdown = 3; return 0; } void menu_init( void ) { menus = NULL; menu_sem = create_named_semaphore( "menus", 1 ); gui_sem = create_named_semaphore( "gui", 0 ); menu_redraw_sem = create_named_semaphore( "menu_r", 1); struct menu * m = NULL; m = menu_find_by_name( "Audio", ICON_ML_AUDIO ); m = menu_find_by_name( "Expo", ICON_ML_EXPO ); m = menu_find_by_name( "Overlay", ICON_ML_OVERLAY ); m = menu_find_by_name( "Movie", ICON_ML_MOVIE ); m = menu_find_by_name( "Shoot", ICON_ML_SHOOT ); m = menu_find_by_name( "Focus", ICON_ML_FOCUS ); m = menu_find_by_name( "Display", ICON_ML_DISPLAY ); m = menu_find_by_name( "Prefs", ICON_ML_PREFS ); m = menu_find_by_name( "Scripts", ICON_ML_SCRIPT ); m = menu_find_by_name( "Games", ICON_ML_GAMES ); m = menu_find_by_name( "Modules", ICON_ML_MODULES ); if (m) m->split_pos = 12; m = menu_find_by_name( "Debug", ICON_ML_DEBUG ); m = menu_find_by_name( "Help", ICON_ML_INFO ); } /* CONFIG_INT("guimode.ml.menu", guimode_ml_menu, 2); static void guimode_ml_menu_print( void * priv, int x, int y, int selected ) { bmp_printf( selected ? MENU_FONT_SEL : MENU_FONT, x, y, "GUIMode for ML menu: %d", guimode_ml_menu ); } static void guimode_ml_menu_inc(void* priv) { guimode_ml_menu++; } static void guimode_ml_menu_dec(void* priv) { guimode_ml_menu--; } */ void gui_stop_menu( ) { if (gui_menu_shown()) give_semaphore(gui_sem); } void gui_open_menu( ) { if (!gui_menu_shown()) give_semaphore(gui_sem); } int FAST gui_menu_shown( void ) { return menu_shown; } static void open_canon_menu() { //~ while(1) //~ { fake_simple_button(BGMT_MENU); int i; for (i = 0; i < 10; i++) { if (MENU_MODE) return; msleep(100); } //~ } } // pump a few redraws quickly, to mask Canon's back menu void menu_redraw_flood() { if (!lv) msleep(100); else if (EXT_MONITOR_CONNECTED) msleep(300); for (int i = 0; i < 5; i++) { if (redraw_flood_stop) break; if (!menu_shown) break; canon_gui_enable_front_buffer(0); menu_redraw_full(); msleep(20); } msleep(50); redraw_flood_stop = 1; } static void start_redraw_flood() { redraw_flood_stop = 0; task_create("menu_redraw_flood", 0x1c, 0, menu_redraw_flood, 0); } static void piggyback_canon_menu() { #ifdef GUIMODE_ML_MENU int new_gui_mode = GUIMODE_ML_MENU; if (!new_gui_mode) return; if (sensor_cleaning) return; if (gui_state == GUISTATE_MENUDISP) return; NotifyBoxHide(); if (new_gui_mode != (int)CURRENT_DIALOG_MAYBE) { start_redraw_flood(); if (lv) bmp_off(); // mask out the underlying Canon menu :) SetGUIRequestMode(new_gui_mode); msleep(200); // bmp will be enabled after first redraw } #endif } static void close_canon_menu() { #ifdef GUIMODE_ML_MENU if (CURRENT_DIALOG_MAYBE == 0) return; if (sensor_cleaning) return; if (gui_state == GUISTATE_MENUDISP) return; if (lv) bmp_off(); // mask out the underlying Canon menu :) SetGUIRequestMode(0); msleep(100); // bitmap will be re-enabled in the caller #endif #ifdef CONFIG_5DC //~ forces the 5dc screen to turn off for ML menu. if (DISPLAY_IS_ON && !HALFSHUTTER_PRESSED) fake_simple_button(BGMT_MENU); msleep(50); #endif } static void menu_open() { if (menu_shown) return; // start in my menu, if configured /* if (start_in_my_menu) { struct menu * my_menu = menu_find_by_name(MY_MENU_NAME, 0); if (menu_has_visible_items(my_menu)) select_menu_by_icon(ICON_ML_MYMENU); } */ #ifdef CONFIG_5DC //~ forces the 5dc screen to turn on for ML menu. if (!DISPLAY_IS_ON) fake_simple_button(BGMT_MENU); msleep(50); #endif menu_lv_transparent_mode = 0; submenu_level = 0; edit_mode = 0; customize_mode = 0; menu_help_active = 0; keyrepeat = 0; menu_shown = 1; //~ menu_hidden_should_display_help = 0; if (lv) menu_zebras_mirror_dirty = 1; piggyback_canon_menu(); canon_gui_disable_front_buffer(0); if (lv && EXT_MONITOR_CONNECTED) clrscr(); CancelDateTimer(); menu_redraw_full(); } static void menu_close() { if (!menu_shown) return; menu_shown = false; customize_mode = 0; update_disp_mode_bits_from_params(); lens_focus_stop(); menu_lv_transparent_mode = 0; close_canon_menu(); canon_gui_enable_front_buffer(0); redraw(); if (lv) bmp_on(); } /* void show_welcome_screen() { if (menu_first_by_icon == ICON_i) // true on first startup { piggyback_canon_menu(); canon_gui_disable(); clrscr(); bmp_printf(FONT_LARGE, 50, 100, " Welcome to Magic Lantern! \n" " \n" "Press DELETE to open ML menu."); canon_gui_enable(); SetGUIRequestMode(0); } }*/ static void menu_task( void* unused ) { extern int ml_started; while (!ml_started) msleep(100); debug_menu_init(); int initial_mode = 0; // shooting mode when menu was opened (if changed, menu should close) select_menu_by_icon(menu_first_by_icon); menu_make_sure_selection_is_valid(); TASK_LOOP { int keyrepeat_active = keyrepeat && (menu_shown || arrow_keys_shortcuts_active()); int transparent_menu_magic_zoom = should_draw_zoom_overlay() && menu_lv_transparent_mode; int dt = (keyrepeat_active) ? /* repeat delay when holding a key */ COERCE(100 + keyrep_countdown*5, 20, 100) : /* otherwise (no keys held) */ (transparent_menu_magic_zoom ? 2000 : 500 ); int rc = take_semaphore( gui_sem, dt ); if( rc == 0 ) { /* menu toggle request */ if (menu_shown) { menu_close(); } else { menu_open(); initial_mode = shooting_mode; } } else { /* semaphore timeout - perform periodical checks (polling) */ if (keyrepeat_active) { if (keyrep_ack) { keyrep_countdown--; if (keyrep_countdown <= 0) { keyrep_ack = 0; fake_simple_button(keyrepeat); } } continue; } /* executed once at startup, * and whenever new menus appear/disappear */ if (menu_flags_load_dirty) { config_menu_load_flags(); menu_flags_load_dirty = 0; } if (menu_shown) { /* should we still display the menu? */ if (sensor_cleaning || initial_mode != shooting_mode || gui_state == GUISTATE_MENUDISP || (!DISPLAY_IS_ON && CURRENT_DIALOG_MAYBE != DLG_PLAY)) { /* close ML menu */ gui_stop_menu(); continue; } /* redraw either periodically (every 500ms), * or on request (menu_damage) */ if ((!menu_help_active && !menu_lv_transparent_mode) || menu_damage) { menu_redraw(); } } else { /* menu no longer displayed */ /* if we changed anything in the menu, save config */ extern int config_autosave; if (config_autosave && (config_dirty || menu_flags_save_dirty) && NOT_RECORDING && !ml_shutdown_requested) { config_save(); config_dirty = 0; menu_flags_save_dirty = 0; } } } } } static void menu_task_minimal( void* unused ) { select_menu_by_icon(menu_first_by_icon); TASK_LOOP { int rc = take_semaphore( gui_sem, 500 ); if( rc != 0 ) { // We woke up after 1 second continue; } //~ canon_gui_toggle(); //~ menu_shown = !canon_gui_disabled(); //~ extern void* test_dialog; if( !menu_shown ) { //~ menu_shown = true; menu_open(); } else { menu_close(); //~ menu_shown = false; } } } TASK_CREATE( "menu_task", menu_task, 0, 0x1a, 0x2000 ); //~ TASK_CREATE( "menu_task_minimal", menu_task_minimal, 0, 0x1a, 0x2000 ); int is_menu_entry_selected(char* menu_name, char* entry_name) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) if( menu->selected ) break; if (streq(menu->name, menu_name)) { struct menu_entry * entry = get_selected_entry(menu); if (!entry) return 0; return streq(entry->name, entry_name); } return 0; } int is_menu_selected(char* name) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) if( menu->selected ) break; return streq(menu->name, name); } int is_menu_active(char* name) { if (!menu_shown) return 0; if (menu_help_active) return 0; if (beta_should_warn()) return 0; return is_menu_selected(name); } void select_menu(char* name, int entry_index) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) { menu->selected = streq(menu->name, name); if (menu->selected) { struct menu_entry * entry = menu->children; int i; for(i = 0 ; entry ; entry = entry->next, i++ ) entry->selected = (i == entry_index); } } //~ menu_damage = 1; } void select_menu_by_name(char* name, const char* entry_name) { struct menu * menu_that_was_selected = 0; int entry_was_selected = 0; struct menu * menu = menus; for( ; menu ; menu = menu->next ) { menu->selected = streq(menu->name, name) && !menu_that_was_selected; if (menu->selected) menu_that_was_selected = menu; if (menu->selected) { struct menu_entry * entry = menu->children; int i; for(i = 0 ; entry ; entry = entry->next, i++ ) { entry->selected = streq(entry->name, entry_name) && !entry_was_selected; if (entry->selected) entry_was_selected = 1; } } } if (!menu_that_was_selected) { // menu not found, just select the first one one menus->selected = 1; menu_that_was_selected = menus; } if (!entry_was_selected) { // entry not found if (menu_that_was_selected && menu_that_was_selected->children) { menu_that_was_selected->children->selected = 1; } } //~ menu_damage = 1; } static struct menu_entry * entry_find_by_name(const char* name, const char* entry_name) { struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if (!name || streq(menu->name, name)) { struct menu_entry * entry = menu->children; int i; for(i = 0 ; entry ; entry = entry->next, i++ ) { if (streq(entry->name, entry_name)) { return entry; } } } } return 0; } static void hide_menu_by_name(char* name, char* entry_name) { struct menu * menu = menu_find_by_name(name, 0); struct menu_entry * entry = entry_find_by_name(name, entry_name); if (menu && entry) { entry->hidden = 1; } } static void jhide_menu_by_name(char* name, char* entry_name) { struct menu * menu = menu_find_by_name(name, 0); struct menu_entry * entry = entry_find_by_name(name, entry_name); if (menu && entry) { entry->jhidden = 1; } } static void star_menu_by_name(char* name, char* entry_name) { struct menu_entry * entry = entry_find_by_name(name, entry_name); if (entry) { entry->starred = 1; } } static void select_menu_by_icon(int icon) { take_semaphore(menu_sem, 0); struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if (menu->icon == icon) // found! { struct menu * menu = menus; for( ; menu ; menu = menu->next ) menu->selected = menu->icon == icon; break; } } give_semaphore(menu_sem); } static void menu_help_go_to_selected_entry( struct menu * menu ) { if( !menu ) return; struct menu_entry * entry = get_selected_entry(menu); if (!entry) return; menu_help_go_to_label((char*) entry->name, 0); give_semaphore(menu_sem); } static void menu_show_version(void) { big_bmp_printf(FONT(FONT_MED, 60, MENU_BG_COLOR_HEADER_FOOTER), 10, 480 - font_med.height * 3, "Magic Lantern version : %s\n" "Mercurial changeset : %s\n" "Built on %s by %s.", build_version, build_id, build_date, build_user); } #ifdef CONFIG_JOY_CENTER_ACTIONS static int joystick_pressed = 0; static int joystick_longpress = 0; static int joy_center_action_disabled = 0; /* called from GUI timers */ static void joystick_longpress_check() { if (joy_center_action_disabled) { return; } if (joystick_pressed) { joystick_longpress++; delayed_call(100, joystick_longpress_check, 0); } //~ bmp_printf(FONT_MED, 50, 50, "%d ", joystick_longpress); if (joystick_longpress == 5) { /* long press opens ML menu or submenus */ fake_simple_button(MLEV_JOYSTICK_LONG); /* make sure it won't re-trigger */ joystick_longpress++; } else if (joystick_longpress < 2 && !joystick_pressed && gui_menu_shown()) { /* short press in menu => do a regular SET */ fake_simple_button(BGMT_PRESS_SET); fake_simple_button(BGMT_UNPRESS_UDLR); } } #endif #ifdef CONFIG_EOSM static int erase_pressed = 0; static int erase_longpress = 0; /* called from GUI timers */ static void erase_longpress_check() { if (erase_pressed) { erase_longpress++; delayed_call(100, erase_longpress_check, 0); } //~ bmp_printf(FONT_MED, 50, 50, "%d ", erase_longpress); if (erase_longpress == 5) { /* long press opens ML menu */ fake_simple_button(BGMT_TRASH); /* make sure it won't re-trigger */ erase_longpress++; } else if (erase_longpress <= 2 && !erase_pressed) { /* short press => do a regular "down/erase" */ fake_simple_button(BGMT_PRESS_DOWN); fake_simple_button(BGMT_UNPRESS_DOWN); } } #endif // this should work on most cameras int handle_ml_menu_erase(struct event * event) { if (dofpreview) return 1; // don't open menu when DOF preview is locked if (event->param == BGMT_TRASH || #ifdef CONFIG_TOUCHSCREEN event->param == BGMT_TOUCH_2_FINGER || #endif 0) { if (gui_state == GUISTATE_IDLE || (gui_menu_shown() && !beta_should_warn())) { give_semaphore( gui_sem ); return 0; } } if (event->param == MLEV_JOYSTICK_LONG && !gui_menu_shown()) { /* some cameras will trigger the Q menu (with photo settings) from a joystick press, others will do nothing */ if (gui_state == GUISTATE_IDLE || gui_state == GUISTATE_QMENU) { give_semaphore( gui_sem ); return 0; } } #ifdef CONFIG_JOY_CENTER_ACTIONS /* also trigger menu by a long joystick press */ if (event->param == BGMT_JOY_CENTER) { if (joy_center_action_disabled) { return gui_menu_shown() ? 0 : 1; } if (is_submenu_or_edit_mode_active()) { /* in submenus, a short press goes back to main menu (since you can edit with left and right) */ fake_simple_button(MLEV_JOYSTICK_LONG); return 0; } else if (gui_state == GUISTATE_IDLE || gui_state == GUISTATE_QMENU || gui_menu_shown()) { /* if we can make use of a long joystick press, check it */ joystick_pressed = 1; joystick_longpress = 0; delayed_call(100, joystick_longpress_check, 0); if (gui_menu_shown()) return 0; } } else if (event->param == BGMT_UNPRESS_UDLR) { joystick_pressed = 0; joy_center_action_disabled = 0; } else if (event->param == BGMT_PRESS_LEFT || event->param == BGMT_PRESS_RIGHT || event->param == BGMT_PRESS_DOWN || event->param == BGMT_PRESS_UP || event->param == BGMT_PRESS_UP_LEFT || event->param == BGMT_PRESS_UP_RIGHT || event->param == BGMT_PRESS_DOWN_LEFT || event->param == BGMT_PRESS_DOWN_RIGHT) { joy_center_action_disabled = 1; } #endif #ifdef CONFIG_EOSM /* also trigger menu by a long press on ERASE (DOWN) */ if (event->param == BGMT_PRESS_DOWN) { if (gui_state == GUISTATE_IDLE && !gui_menu_shown() && !IS_FAKE(event)) { erase_pressed = 1; erase_longpress = 0; delayed_call(100, erase_longpress_check, 0); return 0; } } else if (event->param == BGMT_UNPRESS_DOWN) { erase_pressed = 0; } #endif return 1; } void menu_open_submenu(struct menu_entry * entry) { submenu_level++; edit_mode = 0; menu_lv_transparent_mode = 0; } void menu_close_submenu() { submenu_level = MAX(submenu_level - 1, 0); //make sure we don't go negative edit_mode = 0; menu_lv_transparent_mode = 0; } void menu_toggle_submenu() { if (!edit_mode || submenu_level) { if(submenu_level == 0) submenu_level++; else submenu_level--; } edit_mode = 0; menu_lv_transparent_mode = 0; } int handle_quick_access_menu_items(struct event * event) { #ifdef BGMT_Q // quick access to some menu items #ifdef BGMT_Q_ALT if (event->param == BGMT_Q_ALT && !gui_menu_shown()) #else if (event->param == BGMT_Q && !gui_menu_shown()) #endif { #ifdef ISO_ADJUSTMENT_ACTIVE if (ISO_ADJUSTMENT_ACTIVE) #else if (0) #endif { select_menu("Expo", 0); give_semaphore( gui_sem ); return 0; } #ifdef CURRENT_DIALOG_MAYBE_2 else if (CURRENT_DIALOG_MAYBE_2 == DLG2_FOCUS_MODE) #else else if (CURRENT_DIALOG_MAYBE == DLG_FOCUS_MODE) #endif { select_menu("Focus", 0); give_semaphore( gui_sem ); return 0; } } #endif return 1; } static int menu_get_flags(struct menu_entry * entry) { return entry->starred + entry->hidden*2 + entry->jhidden*4; } static void menu_set_flags(char* menu_name, char* entry_name, int flags) { if (flags & 1) star_menu_by_name(menu_name, entry_name); if (flags & 2) hide_menu_by_name(menu_name, entry_name); if (flags & 4) jhide_menu_by_name(menu_name, entry_name); } #define CFG_APPEND(fmt, ...) ({ lastlen = snprintf(cfg + cfglen, CFG_SIZE - cfglen, fmt, ## __VA_ARGS__); cfglen += lastlen; }) #define CFG_SIZE 32768 static void menu_save_flags(char* filename) { char* cfg = fio_malloc(CFG_SIZE); cfg[0] = '\0'; int cfglen = 0; int lastlen = 0; struct menu * menu = menus; for( ; menu ; menu = menu->next ) { if (menu == my_menu) continue; if (menu == mod_menu) continue; struct menu_entry * entry = menu->children; int i; for(i = 0 ; entry ; entry = entry->next, i++ ) { if (!entry->name) continue; if (!entry->name[0]) continue; int flags = menu_get_flags(entry); if (flags) { CFG_APPEND("%d %s\\%s\n", flags, menu->name, entry->name); } } } FILE * file = FIO_CreateFile(filename); if (!file) goto end; FIO_WriteFile(file, cfg, strlen(cfg)); FIO_CloseFile( file ); end: fio_free(cfg); } static void menu_load_flags(char* filename) { int size = 0; char* buf = (char*)read_entire_file(filename , &size); if (!size) return; if (!buf) return; int prev = -1; int sep = 0; for (int i = 0; i < size; i++) { if (buf[i] == '\\') sep = i; else if (buf[i] == '\n') { //~ NotifyBox(2000, "%d %d %d ", prev, sep, i); if (prev < sep-2 && sep < i-2) { buf[i] = 0; buf[sep] = 0; char* menu_name = &buf[prev+3]; char* entry_name = &buf[sep+1]; int flags = buf[prev+1] - '0'; //~ NotifyBox(2000, "%s -> %s", menu_name, entry_name); msleep(2000); menu_set_flags(menu_name, entry_name, flags); } prev = i; } } fio_free(buf); } static void config_menu_load_flags() { char menu_config_file[0x80]; snprintf(menu_config_file, sizeof(menu_config_file), "%sMENU.CFG", get_config_dir()); menu_load_flags(menu_config_file); my_menu_dirty = 1; } void config_menu_save_flags() { if (!menu_flags_save_dirty) return; char menu_config_file[0x80]; snprintf(menu_config_file, sizeof(menu_config_file), "%sMENU.CFG", get_config_dir()); menu_save_flags(menu_config_file); } /*void menu_save_all_items_dbg() { char* cfg = fio_malloc(CFG_SIZE); cfg[0] = '\0'; int unnamed = 0; struct menu * menu = menus; for( ; menu ; menu = menu->next ) { struct menu_entry * entry = menu->children; int i; for(i = 0 ; entry ; entry = entry->next, i++ ) { CFG_APPEND("%s\\%s\n", menu->name, entry->name); if (strlen(entry->name) == 0 || strlen(menu->name) == 0) unnamed++; } } FILE * file = FIO_CreateFile( "ML/LOGS/MENUS.LOG" ); if (!file) return; FIO_WriteFile(file, cfg, strlen(cfg)); FIO_CloseFile( file ); NotifyBox(5000, "Menu items: %d unnamed.", unnamed); end: fio_free(cfg); }*/ int menu_get_value_from_script(const char* name, const char* entry_name) { struct menu_entry * entry = entry_find_by_name(name, entry_name); if (!entry) { printf("Menu not found: %s -> %s\n", name, entry->name); return 0; } return CURRENT_VALUE; } char* menu_get_str_value_from_script(const char* name, const char* entry_name) { struct menu_entry * entry = entry_find_by_name(name, entry_name); if (!entry) { printf("Menu not found: %s -> %s\n", name, entry->name); return 0; } // this won't work with ML menu on (race condition) static struct menu_display_info info; entry_default_display_info(entry, &info); if (entry->update) entry->update(entry, &info); return info.value; } int menu_set_str_value_from_script(const char* name, const char* entry_name, char* value, int value_int) { struct menu_entry * entry = entry_find_by_name(name, entry_name); if (!entry) { printf("Menu not found: %s -> %s\n", name, entry->name); return 0; } // we will need exclusive access to menu_display_info take_semaphore(menu_sem, 0); // if it doesn't seem to cycle, cancel earlier char first[MENU_MAX_VALUE_LEN]; char last[MENU_MAX_VALUE_LEN]; snprintf(first, sizeof(first), "%s", menu_get_str_value_from_script(name, entry_name)); snprintf(last, sizeof(last), "%s", menu_get_str_value_from_script(name, entry_name)); for (int i = 0; i < 500; i++) // keep cycling until we get the desired value (or until it repeats the same value) { char* current = menu_get_str_value_from_script(name, entry_name); if (streq(current, value)) goto ok; // success!! if (startswith(current, value) && !isdigit(current[strlen(value)])) goto ok; // accept 3500 instead of 3500K, or ON instead of ON,blahblah, but not 160 instead of 1600 if (IS_ML_PTR(entry->priv) && CURRENT_VALUE == value_int) goto ok; // also success! if (i > 0 && streq(current, last)) // value not changing? stop here { printf("Value not changing: %s.\n", current); break; } if (i > 0 && streq(current, first)) // back to first value? stop here break; // for debugging, print this always if (i > 50 && i % 10 == 0) // it's getting fishy, maybe it's good to show some progress printf("menu_set_str('%s', '%s', '%s'): trying %s (%d), was %s...\n", name, entry_name, value, current, CURRENT_VALUE, last); snprintf(last, sizeof(last), "%s", current); if (entry->select) entry->select( entry->priv, 1); else if IS_ML_PTR(entry->priv) menu_numeric_toggle_long_range(entry->priv, 1, entry->min, entry->max); else break; msleep(20); // we may need to wait for property handlers to update } printf("Could not set value '%s' for menu %s -> %s\n", value, name, entry_name); give_semaphore(menu_sem); return 0; // boo :( ok: give_semaphore(menu_sem); return 1; // :) } int menu_set_value_from_script(const char* name, const char* entry_name, int value) { struct menu_entry * entry = entry_find_by_name(name, entry_name); if (!entry) { printf("Menu not found: %s -> %s\n", name, entry->name); return 0; } if( entry->select ) // special item, we need some heuristics { // we'll just cycle until either the displayed value or priv field looks alright char value_str[10]; snprintf(value_str, sizeof(value_str), "%d", value); return menu_set_str_value_from_script(name, entry_name, value_str, value); } else if IS_ML_PTR(entry->priv) // numeric item, just set it { *(int*)(entry->priv) = value; return 1; // success! } else // unknown { printf("Cannot set value for %s -> %s\n", name, entry->name); return 0; // boo :( } } /* returns 1 if the backend is ready to use, 0 if caller should call this one again to re-check */ int menu_request_image_backend() { static int last_guimode_request = 0; int t = get_ms_clock_value(); if (CURRENT_DIALOG_MAYBE != DLG_PLAY) { if (t > last_guimode_request + 1000) { SetGUIRequestMode(DLG_PLAY); last_guimode_request = t; } /* not ready, please retry */ return 0; } if (t > last_guimode_request + 500 && DISPLAY_IS_ON) { if (get_yuv422_vram()->vram) { /* ready to draw on the YUV buffer! */ clrscr(); return 1; } else { /* something might be wrong */ yuv422_buffer_check(); } } /* not yet ready, please retry */ return 0; } MENU_SELECT_FUNC(menu_advanced_toggle) { struct menu * menu = get_selected_menu(); struct menu * main_menu = menu; if (submenu_level) { main_menu = menu; menu = get_current_submenu(); if (!menu) menu = main_menu; // no submenu, operate on same item } advanced_mode = menu->advanced = !menu->advanced; menu->scroll_pos = 0; } MENU_UPDATE_FUNC(menu_advanced_update) { MENU_SET_NAME(advanced_mode ? "Simple..." : "Advanced..."); MENU_SET_ICON(IT_ACTION, 0); MENU_SET_HELP(advanced_mode ? "Back to 'beginner' mode." : "Advanced options for experts. Use with care."); } #ifdef CONFIG_QEMU void qemu_menu_screenshots() { /* hack to bypass ML checks */ CURRENT_DIALOG_MAYBE = 1; /* hack to avoid picture style warning */ lens_info.picstyle = 1; /* get a screenshot of the initial "welcome" screen, then hide it */ menu_redraw_do(); call("dispcheck"); beta_set_warned(); while(1) { /* get a screenshot of the current menu */ menu_redraw_do(); call("dispcheck"); /* cycle through menus, until the first menu gets selected again */ menu_move(get_selected_menu(), 1); if (menus->selected) break; } call("shutdown"); while(1); } #endif