1 #include "config.h"
2 
3 #include "building/type.h"
4 #include "core/hotkey_config.h"
5 #include "core/image_group.h"
6 #include "core/lang.h"
7 #include "core/string.h"
8 #include "graphics/generic_button.h"
9 #include "graphics/graphics.h"
10 #include "graphics/image.h"
11 #include "graphics/lang_text.h"
12 #include "graphics/panel.h"
13 #include "graphics/screen.h"
14 #include "graphics/scrollbar.h"
15 #include "graphics/text.h"
16 #include "graphics/window.h"
17 #include "translation/translation.h"
18 #include "window/config.h"
19 #include "window/hotkey_editor.h"
20 #include "window/plain_message_dialog.h"
21 
22 #define HOTKEY_HEADER -1
23 #define TR_NONE -1
24 #define GROUP_BUILDINGS 28
25 
26 #define NUM_VISIBLE_OPTIONS 14
27 #define NUM_BOTTOM_BUTTONS 3
28 
29 static void on_scroll(void);
30 
31 static void button_hotkey(int row, int is_alternative);
32 static void button_reset_defaults(int param1, int param2);
33 static void button_close(int save, int param2);
34 
35 static scrollbar_type scrollbar = { 580, 72, 352, on_scroll };
36 
37 typedef struct {
38     int action;
39     int name_translation;
40     int name_text_group;
41     int name_text_id;
42 } hotkey_widget;
43 
44 static hotkey_widget hotkey_widgets[] = {
45     {HOTKEY_HEADER, TR_HOTKEY_HEADER_ARROWS},
46     {HOTKEY_ARROW_UP, TR_HOTKEY_ARROW_UP},
47     {HOTKEY_ARROW_DOWN, TR_HOTKEY_ARROW_DOWN},
48     {HOTKEY_ARROW_LEFT, TR_HOTKEY_ARROW_LEFT},
49     {HOTKEY_ARROW_RIGHT, TR_HOTKEY_ARROW_RIGHT},
50     {HOTKEY_HEADER, TR_HOTKEY_HEADER_GLOBAL},
51     {HOTKEY_TOGGLE_FULLSCREEN, TR_HOTKEY_TOGGLE_FULLSCREEN},
52     {HOTKEY_CENTER_WINDOW, TR_HOTKEY_CENTER_WINDOW},
53     {HOTKEY_RESIZE_TO_640, TR_HOTKEY_RESIZE_TO_640},
54     {HOTKEY_RESIZE_TO_800, TR_HOTKEY_RESIZE_TO_800},
55     {HOTKEY_RESIZE_TO_1024, TR_HOTKEY_RESIZE_TO_1024},
56     {HOTKEY_SAVE_SCREENSHOT, TR_HOTKEY_SAVE_SCREENSHOT},
57     {HOTKEY_SAVE_CITY_SCREENSHOT, TR_HOTKEY_SAVE_CITY_SCREENSHOT},
58     {HOTKEY_LOAD_FILE, TR_HOTKEY_LOAD_FILE},
59     {HOTKEY_SAVE_FILE, TR_HOTKEY_SAVE_FILE},
60     {HOTKEY_HEADER, TR_HOTKEY_HEADER_CITY},
61     {HOTKEY_INCREASE_GAME_SPEED, TR_HOTKEY_INCREASE_GAME_SPEED},
62     {HOTKEY_DECREASE_GAME_SPEED, TR_HOTKEY_DECREASE_GAME_SPEED},
63     {HOTKEY_TOGGLE_PAUSE, TR_HOTKEY_TOGGLE_PAUSE},
64     {HOTKEY_CYCLE_LEGION, TR_HOTKEY_CYCLE_LEGION},
65     {HOTKEY_ROTATE_MAP_LEFT, TR_HOTKEY_ROTATE_MAP_LEFT},
66     {HOTKEY_ROTATE_MAP_RIGHT, TR_HOTKEY_ROTATE_MAP_RIGHT},
67     {HOTKEY_ROTATE_BUILDING, TR_HOTKEY_ROTATE_BUILDING},
68     {HOTKEY_ROTATE_BUILDING_BACK, TR_HOTKEY_ROTATE_BUILDING_BACK},
69     {HOTKEY_HEADER, TR_HOTKEY_HEADER_BUILD},
70     {HOTKEY_BUILD_CLONE, TR_HOTKEY_BUILD_CLONE},
71     {HOTKEY_COPY_BUILDING_SETTINGS, TR_HOTKEY_COPY_SETTINGS},
72     {HOTKEY_PASTE_BUILDING_SETTINGS, TR_HOTKEY_PASTE_SETTINGS},
73     {HOTKEY_BUILD_CLEAR_LAND, TR_NONE, 68, 21},
74     {HOTKEY_BUILD_VACANT_HOUSE, TR_NONE, 67, 7},
75     {HOTKEY_BUILD_ROAD, TR_NONE, GROUP_BUILDINGS, BUILDING_ROAD},
76     {HOTKEY_BUILD_PLAZA, TR_NONE, GROUP_BUILDINGS, BUILDING_PLAZA},
77     {HOTKEY_BUILD_GARDENS, TR_NONE, GROUP_BUILDINGS, BUILDING_GARDENS},
78     {HOTKEY_BUILD_PREFECTURE, TR_NONE, GROUP_BUILDINGS, BUILDING_PREFECTURE},
79     {HOTKEY_BUILD_ENGINEERS_POST, TR_NONE, GROUP_BUILDINGS, BUILDING_ENGINEERS_POST},
80     {HOTKEY_BUILD_DOCTOR, TR_NONE, GROUP_BUILDINGS, BUILDING_DOCTOR},
81     {HOTKEY_BUILD_GRANARY, TR_NONE, GROUP_BUILDINGS, BUILDING_GRANARY},
82     {HOTKEY_BUILD_WAREHOUSE, TR_NONE, GROUP_BUILDINGS, BUILDING_WAREHOUSE},
83     {HOTKEY_BUILD_MARKET, TR_NONE, GROUP_BUILDINGS, BUILDING_MARKET},
84     {HOTKEY_BUILD_WALL, TR_NONE, GROUP_BUILDINGS, BUILDING_WALL},
85     {HOTKEY_BUILD_GATEHOUSE, TR_NONE, GROUP_BUILDINGS, BUILDING_GATEHOUSE},
86     {HOTKEY_BUILD_RESERVOIR, TR_NONE, GROUP_BUILDINGS, BUILDING_RESERVOIR},
87     {HOTKEY_BUILD_AQUEDUCT, TR_NONE, GROUP_BUILDINGS, BUILDING_AQUEDUCT},
88     {HOTKEY_BUILD_FOUNTAIN, TR_NONE, GROUP_BUILDINGS, BUILDING_FOUNTAIN},
89     {HOTKEY_BUILD_ROADBLOCK, TR_NONE, GROUP_BUILDINGS, BUILDING_ROADBLOCK},
90     {HOTKEY_UNDO, TR_NONE, GROUP_BUILDINGS, 1},
91     {HOTKEY_HEADER, TR_HOTKEY_HEADER_ADVISORS},
92     {HOTKEY_SHOW_ADVISOR_LABOR, TR_HOTKEY_SHOW_ADVISOR_LABOR},
93     {HOTKEY_SHOW_ADVISOR_MILITARY, TR_HOTKEY_SHOW_ADVISOR_MILITARY},
94     {HOTKEY_SHOW_ADVISOR_IMPERIAL, TR_HOTKEY_SHOW_ADVISOR_IMPERIAL},
95     {HOTKEY_SHOW_ADVISOR_RATINGS, TR_HOTKEY_SHOW_ADVISOR_RATINGS},
96     {HOTKEY_SHOW_ADVISOR_TRADE, TR_HOTKEY_SHOW_ADVISOR_TRADE},
97     {HOTKEY_SHOW_ADVISOR_POPULATION, TR_HOTKEY_SHOW_ADVISOR_POPULATION},
98     {HOTKEY_SHOW_ADVISOR_HOUSING, TR_HOTKEY_SHOW_ADVISOR_HOUSING},
99     {HOTKEY_SHOW_ADVISOR_HEALTH, TR_HOTKEY_SHOW_ADVISOR_HEALTH},
100     {HOTKEY_SHOW_ADVISOR_EDUCATION, TR_HOTKEY_SHOW_ADVISOR_EDUCATION},
101     {HOTKEY_SHOW_ADVISOR_ENTERTAINMENT, TR_HOTKEY_SHOW_ADVISOR_ENTERTAINMENT},
102     {HOTKEY_SHOW_ADVISOR_RELIGION, TR_HOTKEY_SHOW_ADVISOR_RELIGION},
103     {HOTKEY_SHOW_ADVISOR_FINANCIAL, TR_HOTKEY_SHOW_ADVISOR_FINANCIAL},
104     {HOTKEY_SHOW_ADVISOR_CHIEF, TR_HOTKEY_SHOW_ADVISOR_CHIEF},
105     {HOTKEY_HEADER, TR_HOTKEY_HEADER_OVERLAYS},
106     {HOTKEY_TOGGLE_OVERLAY, TR_HOTKEY_TOGGLE_OVERLAY},
107     {HOTKEY_SHOW_OVERLAY_RELATIVE, TR_HOTKEY_SHOW_OVERLAY_RELATIVE},
108     {HOTKEY_SHOW_OVERLAY_WATER, TR_HOTKEY_SHOW_OVERLAY_WATER},
109     {HOTKEY_SHOW_OVERLAY_FIRE, TR_HOTKEY_SHOW_OVERLAY_FIRE},
110     {HOTKEY_SHOW_OVERLAY_DAMAGE, TR_HOTKEY_SHOW_OVERLAY_DAMAGE},
111     {HOTKEY_SHOW_OVERLAY_CRIME, TR_HOTKEY_SHOW_OVERLAY_CRIME},
112     {HOTKEY_SHOW_OVERLAY_PROBLEMS, TR_HOTKEY_SHOW_OVERLAY_PROBLEMS},
113     {HOTKEY_HEADER, TR_HOTKEY_HEADER_BOOKMARKS},
114     {HOTKEY_GO_TO_BOOKMARK_1, TR_HOTKEY_GO_TO_BOOKMARK_1},
115     {HOTKEY_GO_TO_BOOKMARK_2, TR_HOTKEY_GO_TO_BOOKMARK_2},
116     {HOTKEY_GO_TO_BOOKMARK_3, TR_HOTKEY_GO_TO_BOOKMARK_3},
117     {HOTKEY_GO_TO_BOOKMARK_4, TR_HOTKEY_GO_TO_BOOKMARK_4},
118     {HOTKEY_SET_BOOKMARK_1, TR_HOTKEY_SET_BOOKMARK_1},
119     {HOTKEY_SET_BOOKMARK_2, TR_HOTKEY_SET_BOOKMARK_2},
120     {HOTKEY_SET_BOOKMARK_3, TR_HOTKEY_SET_BOOKMARK_3},
121     {HOTKEY_SET_BOOKMARK_4, TR_HOTKEY_SET_BOOKMARK_4},
122     {HOTKEY_HEADER, TR_HOTKEY_HEADER_EDITOR},
123     {HOTKEY_EDITOR_TOGGLE_BATTLE_INFO, TR_HOTKEY_EDITOR_TOGGLE_BATTLE_INFO},
124 };
125 
126 #define HOTKEY_X_OFFSET_1 290
127 #define HOTKEY_X_OFFSET_2 430
128 #define HOTKEY_BTN_WIDTH 140
129 #define HOTKEY_BTN_HEIGHT 22
130 
131 static generic_button hotkey_buttons[] = {
132     {HOTKEY_X_OFFSET_1, 80 + 24 * 0, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 0, 0},
133     {HOTKEY_X_OFFSET_2, 80 + 24 * 0, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 0, 1},
134     {HOTKEY_X_OFFSET_1, 80 + 24 * 1, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 1, 0},
135     {HOTKEY_X_OFFSET_2, 80 + 24 * 1, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 1, 1},
136     {HOTKEY_X_OFFSET_1, 80 + 24 * 2, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 2, 0},
137     {HOTKEY_X_OFFSET_2, 80 + 24 * 2, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 2, 1},
138     {HOTKEY_X_OFFSET_1, 80 + 24 * 3, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 3, 0},
139     {HOTKEY_X_OFFSET_2, 80 + 24 * 3, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 3, 1},
140     {HOTKEY_X_OFFSET_1, 80 + 24 * 4, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 4, 0},
141     {HOTKEY_X_OFFSET_2, 80 + 24 * 4, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 4, 1},
142     {HOTKEY_X_OFFSET_1, 80 + 24 * 5, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 5, 0},
143     {HOTKEY_X_OFFSET_2, 80 + 24 * 5, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 5, 1},
144     {HOTKEY_X_OFFSET_1, 80 + 24 * 6, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 6, 0},
145     {HOTKEY_X_OFFSET_2, 80 + 24 * 6, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 6, 1},
146     {HOTKEY_X_OFFSET_1, 80 + 24 * 7, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 7, 0},
147     {HOTKEY_X_OFFSET_2, 80 + 24 * 7, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 7, 1},
148     {HOTKEY_X_OFFSET_1, 80 + 24 * 8, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 8, 0},
149     {HOTKEY_X_OFFSET_2, 80 + 24 * 8, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 8, 1},
150     {HOTKEY_X_OFFSET_1, 80 + 24 * 9, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 9, 0},
151     {HOTKEY_X_OFFSET_2, 80 + 24 * 9, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 9, 1},
152     {HOTKEY_X_OFFSET_1, 80 + 24 * 10, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 10, 0},
153     {HOTKEY_X_OFFSET_2, 80 + 24 * 10, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 10, 1},
154     {HOTKEY_X_OFFSET_1, 80 + 24 * 11, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 11, 0},
155     {HOTKEY_X_OFFSET_2, 80 + 24 * 11, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 11, 1},
156     {HOTKEY_X_OFFSET_1, 80 + 24 * 12, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 12, 0},
157     {HOTKEY_X_OFFSET_2, 80 + 24 * 12, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 12, 1},
158     {HOTKEY_X_OFFSET_1, 80 + 24 * 13, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 13, 0},
159     {HOTKEY_X_OFFSET_2, 80 + 24 * 13, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT, button_hotkey, button_none, 13, 1},
160 };
161 
162 static generic_button bottom_buttons[] = {
163     {230, 430, 180, 30, button_reset_defaults, button_none},
164     {415, 430, 100, 30, button_close, button_none, 0},
165     {520, 430, 100, 30, button_close, button_none, 1},
166 };
167 
168 static translation_key bottom_button_texts[] = {
169     TR_BUTTON_RESET_DEFAULTS,
170     TR_BUTTON_CANCEL,
171     TR_BUTTON_OK
172 };
173 
174 static struct {
175     int focus_button;
176     int bottom_focus_button;
177     hotkey_mapping mappings[HOTKEY_MAX_ITEMS][2];
178 } data;
179 
init(void)180 static void init(void)
181 {
182     scrollbar_init(&scrollbar, 0, sizeof(hotkey_widgets) / sizeof(hotkey_widget) - NUM_VISIBLE_OPTIONS);
183 
184     for (int i = 0; i < HOTKEY_MAX_ITEMS; i++) {
185         hotkey_mapping empty = { KEY_TYPE_NONE, KEY_MOD_NONE, i };
186 
187         const hotkey_mapping *mapping = hotkey_for_action(i, 0);
188         data.mappings[i][0] = mapping ? *mapping : empty;
189 
190         mapping = hotkey_for_action(i, 1);
191         data.mappings[i][1] = mapping ? *mapping : empty;
192     }
193 }
194 
draw_background(void)195 static void draw_background(void)
196 {
197     graphics_clear_screen(CANVAS_UI);
198 
199     window_draw_underlying_window();
200 
201     graphics_in_dialog();
202     outer_panel_draw(0, 0, 40, 30);
203 
204     text_draw_centered(translation_for(TR_HOTKEY_TITLE), 16, 16, 608, FONT_LARGE_BLACK, 0);
205 
206     text_draw_centered(translation_for(TR_HOTKEY_LABEL), HOTKEY_X_OFFSET_1, 55,
207         HOTKEY_BTN_WIDTH, FONT_NORMAL_BLACK, 0);
208     text_draw_centered(translation_for(TR_HOTKEY_ALTERNATIVE_LABEL), HOTKEY_X_OFFSET_2, 55,
209         HOTKEY_BTN_WIDTH, FONT_NORMAL_BLACK, 0);
210 
211     inner_panel_draw(20, 72, 35, 22);
212     int y_base = 80;
213     for (int i = 0; i < NUM_VISIBLE_OPTIONS; i++) {
214         hotkey_widget *widget = &hotkey_widgets[i + scrollbar.scroll_position];
215         int text_offset = y_base + 6 + 24 * i;
216         if (widget->action == HOTKEY_HEADER) {
217             text_draw(translation_for(widget->name_translation), 32, text_offset, FONT_NORMAL_WHITE, 0);
218         } else {
219             if (widget->name_translation != TR_NONE) {
220                 text_draw(translation_for(widget->name_translation),
221                     32, text_offset, FONT_NORMAL_GREEN, 0);
222             } else {
223                 lang_text_draw(widget->name_text_group, widget->name_text_id,
224                     32, text_offset, FONT_NORMAL_GREEN);
225             }
226 
227             const hotkey_mapping *mapping1 = &data.mappings[widget->action][0];
228             if (mapping1->key) {
229                 const uint8_t *keyname = key_combination_display_name(mapping1->key, mapping1->modifiers);
230                 graphics_set_clip_rectangle(HOTKEY_X_OFFSET_1, text_offset, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT);
231                 text_draw_centered(keyname, HOTKEY_X_OFFSET_1 + 3, text_offset,
232                     HOTKEY_BTN_WIDTH - 6, FONT_NORMAL_WHITE, 0);
233                 graphics_reset_clip_rectangle();
234             }
235 
236             const hotkey_mapping *mapping2 = &data.mappings[widget->action][1];
237             if (mapping2->key) {
238                 graphics_set_clip_rectangle(HOTKEY_X_OFFSET_2, text_offset, HOTKEY_BTN_WIDTH, HOTKEY_BTN_HEIGHT);
239                 const uint8_t *keyname = key_combination_display_name(mapping2->key, mapping2->modifiers);
240                 text_draw_centered(keyname, HOTKEY_X_OFFSET_2 + 3, text_offset,
241                     HOTKEY_BTN_WIDTH - 6, FONT_NORMAL_WHITE, 0);
242                 graphics_reset_clip_rectangle();
243             }
244         }
245     }
246 
247     for (int i = 0; i < NUM_BOTTOM_BUTTONS; i++) {
248         text_draw_centered(translation_for(bottom_button_texts[i]),
249             bottom_buttons[i].x, bottom_buttons[i].y + 9,
250             bottom_buttons[i].width, FONT_NORMAL_BLACK, 0);
251     }
252 
253     graphics_reset_dialog();
254 }
255 
draw_foreground(void)256 static void draw_foreground(void)
257 {
258     graphics_in_dialog();
259 
260     scrollbar_draw(&scrollbar);
261 
262     for (int i = 0; i < NUM_VISIBLE_OPTIONS; i++) {
263         hotkey_widget *widget = &hotkey_widgets[i + scrollbar.scroll_position];
264         if (widget->action != HOTKEY_HEADER) {
265             generic_button *btn = &hotkey_buttons[2 * i];
266             button_border_draw(btn->x, btn->y, btn->width, btn->height, data.focus_button == 1 + 2 * i);
267             btn++;
268             button_border_draw(btn->x, btn->y, btn->width, btn->height, data.focus_button == 2 + 2 * i);
269         }
270     }
271 
272     for (int i = 0; i < NUM_BOTTOM_BUTTONS; i++) {
273         button_border_draw(bottom_buttons[i].x, bottom_buttons[i].y,
274             bottom_buttons[i].width, bottom_buttons[i].height,
275             data.bottom_focus_button == i + 1);
276     }
277     graphics_reset_dialog();
278 }
279 
handle_input(const mouse * m,const hotkeys * h)280 static void handle_input(const mouse *m, const hotkeys *h)
281 {
282     const mouse *m_dialog = mouse_in_dialog(m);
283     if (scrollbar_handle_mouse(&scrollbar, m_dialog)) {
284         return;
285     }
286 
287     int handled = 0;
288     handled |= generic_buttons_handle_mouse(m_dialog, 0, 0,
289         hotkey_buttons, NUM_VISIBLE_OPTIONS * 2, &data.focus_button);
290     handled |= generic_buttons_handle_mouse(m_dialog, 0, 0,
291         bottom_buttons, NUM_BOTTOM_BUTTONS, &data.bottom_focus_button);
292     if (!handled && (m->right.went_up || h->escape_pressed)) {
293         window_go_back();
294     }
295 }
296 
hotkey_action_name_for(hotkey_action action)297 static const uint8_t *hotkey_action_name_for(hotkey_action action)
298 {
299     const uint8_t *name = 0;
300     for (int i = 0; i < NUM_VISIBLE_OPTIONS + scrollbar.max_scroll_position; i++) {
301         hotkey_widget *widget = &hotkey_widgets[i];
302         if (widget->action == action) {
303             if (widget->name_translation != TR_NONE) {
304                 name = translation_for(widget->name_translation);
305             } else {
306                 name = lang_get_string(widget->name_text_group, widget->name_text_id);
307             }
308             break;
309         }
310     }
311     return name;
312 }
313 
set_hotkey(hotkey_action action,int index,key_type key,key_modifier_type modifiers)314 static void set_hotkey(hotkey_action action, int index, key_type key, key_modifier_type modifiers)
315 {
316     int is_duplicate_hotkey = 0;
317     // check if new key combination already assigned to another action
318     if (key != KEY_TYPE_NONE) {
319         for (int test_action = 0; test_action < HOTKEY_MAX_ITEMS; test_action++) {
320             for (int test_index = 0; test_index < 2; test_index++) {
321                 if (data.mappings[test_action][test_index].key == key
322                     && data.mappings[test_action][test_index].modifiers == modifiers) {
323                     is_duplicate_hotkey = 1;
324                     // example explanation next "if" check:
325                     // "Fire overlay" already has hotkey "F" and user tries set same hotkey "F" again to "Fire overlay"
326                     // we must skip show warning window for better user experience
327                     if (!(test_action == action && test_index == index)) {
328                         window_plain_message_dialog_show_with_extra(
329                             TR_HOTKEY_DUPLICATE_TITLE, TR_HOTKEY_DUPLICATE_MESSAGE,
330                             hotkey_action_name_for(test_action));
331                     }
332                     break;
333                 }
334             }
335             if (is_duplicate_hotkey) {
336                 break;
337             }
338         }
339     }
340     if (!is_duplicate_hotkey) {
341         data.mappings[action][index].key = key;
342         data.mappings[action][index].modifiers = modifiers;
343     }
344 }
345 
button_hotkey(int row,int is_alternative)346 static void button_hotkey(int row, int is_alternative)
347 {
348     hotkey_widget *widget = &hotkey_widgets[row + scrollbar.scroll_position];
349     if (widget->action == HOTKEY_HEADER) {
350         return;
351     }
352     window_hotkey_editor_show(widget->action, is_alternative, set_hotkey);
353 }
354 
button_reset_defaults(int param1,int param2)355 static void button_reset_defaults(int param1, int param2)
356 {
357     for (int action = 0; action < HOTKEY_MAX_ITEMS; action++) {
358         for (int index = 0; index < 2; index++) {
359             data.mappings[action][index] = *hotkey_default_for_action(action, index);
360         }
361     }
362     window_invalidate();
363 }
364 
on_scroll(void)365 static void on_scroll(void)
366 {
367     window_invalidate();
368 }
369 
button_close(int save,int param2)370 static void button_close(int save, int param2)
371 {
372     if (!save) {
373         window_go_back();
374         return;
375     }
376     hotkey_config_clear();
377     for (int action = 0; action < HOTKEY_MAX_ITEMS; action++) {
378         for (int index = 0; index < 2; index++) {
379             if (data.mappings[action][index].key != KEY_TYPE_NONE) {
380                 hotkey_config_add_mapping(&data.mappings[action][index]);
381             }
382         }
383     }
384     hotkey_config_save();
385     window_go_back();
386 }
387 
window_hotkey_config_show(void)388 void window_hotkey_config_show(void)
389 {
390     window_type window = {
391         WINDOW_HOTKEY_CONFIG,
392         draw_background,
393         draw_foreground,
394         handle_input
395     };
396     init();
397     window_show(&window);
398 }
399