1 /*
2 Copyright (c) 2009-2010 Tero Lindeman (kometbomb)
3 
4 Permission is hereby granted, free of charge, to any person
5 obtaining a copy of this software and associated documentation
6 files (the "Software"), to deal in the Software without
7 restriction, including without limitation the rights to use,
8 copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following
11 conditions:
12 
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 OTHER DEALINGS IN THE SOFTWARE.
24 */
25 
26 #include "menu.h"
27 #include "gui/bevel.h"
28 #include "gfx/font.h"
29 #include "gui/view.h"
30 #include "shortcuts.h"
31 #include "bevdefs.h"
32 #include <string.h>
33 
34 #define SC_SIZE 64
35 
36 enum { ZONE, DRAW };
37 
38 static void (*menu_close_hook)(void)  = NULL;
39 static const Menu *current_menu = NULL;
40 static const Menu *current_menu_action = NULL;
41 static const KeyShortcut *shortcuts = NULL;
42 static GfxSurface *menu_gfx = NULL;
43 static const Font * menu_font, *shortcut_font, *header_font;
44 static const Font * menu_font_selected, *shortcut_font_selected, *header_font_selected;
45 
open_menu(const Menu * mainmenu,const Menu * action,void (* close_hook)(void),const KeyShortcut * _shortcuts,const Font * headerfont,const Font * headerfont_selected,const Font * menufont,const Font * menufont_selected,const Font * shortcutfont,const Font * shortcutfont_selected,GfxSurface * gfx)46 void open_menu(const Menu *mainmenu, const Menu *action, void (*close_hook)(void), const KeyShortcut *_shortcuts,
47 	const Font *headerfont, const Font *headerfont_selected,
48 	const Font *menufont, const Font *menufont_selected,
49 	const Font *shortcutfont, const Font *shortcutfont_selected, GfxSurface *gfx)
50 {
51 	current_menu = mainmenu;
52 	current_menu_action = action;
53 	menu_close_hook = close_hook;
54 	shortcuts = _shortcuts;
55 	menu_gfx = gfx;
56 	header_font = headerfont;
57 	header_font_selected = headerfont_selected;
58 	menu_font = menufont;
59 	menu_font_selected = menufont_selected;
60 	shortcut_font = shortcutfont;
61 	shortcut_font_selected = shortcutfont_selected;
62 }
63 
64 
get_current_menu()65 const Menu * get_current_menu()
66 {
67 	return current_menu;
68 }
69 
70 
get_current_menu_action()71 const Menu * get_current_menu_action()
72 {
73 	return current_menu_action;
74 }
75 
close_menu()76 void close_menu()
77 {
78 	if (current_menu_action == NULL)
79 	{
80 		if (menu_close_hook) menu_close_hook();
81 	}
82 	else
83 	{
84 		if (menu_close_hook) menu_close_hook();
85 		if (current_menu_action->action == MENU_CHECK || current_menu_action->action == MENU_CHECK_NOSET)
86 		{
87 			if (current_menu_action->action == MENU_CHECK) *(int*)(current_menu_action->p1) ^= CASTPTR(int,current_menu_action->p2);
88 
89 			if (current_menu_action->p3)
90 				((void *(*)(void*,void*,void*))(current_menu_action->p3))(0,0,0);
91 		}
92 		else
93 		{
94 			current_menu_action->action(current_menu_action->p1, current_menu_action->p2, current_menu_action->p3);
95 		}
96 
97 		current_menu = NULL;
98 		current_menu_action = NULL;
99 	}
100 }
101 
102 
get_menu_item_width(const Menu * item)103 static int get_menu_item_width(const Menu *item)
104 {
105 	return strlen(item->text) + 1;
106 }
107 
108 
get_shortcut_key(const Menu * item)109 static const char * get_shortcut_key(const Menu *item)
110 {
111 	if (!shortcuts) return NULL;
112 
113 	for (int i = 0 ; shortcuts[i].action ; ++i)
114 	{
115 		if (((item->action == MENU_CHECK || item->action == MENU_CHECK_NOSET) && (void*)shortcuts[i].action == item->p3) ||
116 			(shortcuts[i].action == item->action &&
117 			(void*)shortcuts[i].p1 == item->p1 &&
118 			(void*)shortcuts[i].p2 == item->p2 &&
119 			(void*)shortcuts[i].p3 == item->p3))
120 		{
121 			return get_shortcut_string(&shortcuts[i]);
122 		}
123 		else if (item->submenu)
124 		{
125 			return "�";
126 		}
127 	}
128 
129 	return NULL;
130 }
131 
132 
133 // Below is a two-pass combined event and drawing menu routine. It is two-pass (as opposed to other combined drawing
134 // handlers otherwhere in the project) so it can handle overlapping zones correctly. Otherwhere in the app there simply
135 // are no overlapping zones, menus however can overlap because of the cascading submenus etc.
136 
draw_submenu(GfxDomain * menu_dest,const SDL_Event * event,const Menu * items,const Menu * child,SDL_Rect * child_position,int pass)137 static void draw_submenu(GfxDomain *menu_dest, const SDL_Event *event, const Menu *items, const Menu *child, SDL_Rect *child_position, int pass)
138 {
139 	SDL_Rect area = { 0, 0, menu_dest->screen_w, shortcut_font->h + 4 + 1 };
140 	SDL_Rect r;
141 	const Font *font = NULL;
142 	int horiz = 0;
143 
144 	/* In short: this first iterates upwards the tree until it finds the main menu (FILE, SHOW etc.)
145 	Then it goes back level by level updating the collision/draw zones
146 	*/
147 
148 	if (items)
149 	{
150 		if (items[0].parent != NULL)
151 		{
152 			draw_submenu(menu_dest, event, items[0].parent, items, &area, pass);
153 
154 			font = menu_font;
155 
156 			area.w = area.h = 0;
157 
158 			const Menu * item = items;
159 
160 			for (; item->text ; ++item)
161 			{
162 				area.w = my_max(get_menu_item_width(item), area.w);
163 				if (item->text[0])
164 					area.h += font->h + 1;
165 				else
166 					area.h += SEPARATOR_HEIGHT;
167 			}
168 
169 			area.w = area.w * font->w;
170 			area.x += 3;
171 			area.y += 4;
172 
173 			area.w += SC_SIZE;
174 
175 			if (area.w + area.x > menu_dest->screen_w)
176 				area.x -= area.w + area.x - menu_dest->screen_w + 2;
177 
178 			copy_rect(&r, &area);
179 
180 			SDL_Rect bev;
181 			copy_rect(&bev, &area);
182 			adjust_rect(&bev, -6);
183 
184 			if (pass == DRAW) bevel(menu_dest, &bev, menu_gfx, BEV_MENU);
185 
186 			r.h = font->h + 1;
187 		}
188 		else
189 		{
190 			if (pass == DRAW) bevel(menu_dest, &area, menu_gfx, BEV_MENUBAR);
191 
192 			copy_rect(&r, &area);
193 			adjust_rect(&r, 2);
194 
195 			font = header_font;
196 
197 			horiz = 1;
198 
199 			r.h = font->h;
200 		}
201 
202 		const Menu * item = items;
203 
204 		for (; item->text ; ++item)
205 		{
206 			if (item->text[0])
207 			{
208 				if (horiz) font = header_font; else font = menu_font;
209 
210 				const char * sc_text = get_shortcut_key(item);
211 				int bg = 0;
212 
213 				if (horiz) r.w = font->w * get_menu_item_width(item) + 8;
214 
215 				if (event->type == SDL_MOUSEMOTION && pass == ZONE)
216 				{
217 					if ((event->button.x >= r.x) && (event->button.y >= r.y)
218 						&& (event->button.x < r.x + r.w) && (event->button.y < r.y + r.h))
219 					{
220 						if (item->submenu)
221 						{
222 							current_menu = item->submenu;
223 							current_menu_action = NULL;
224 							bg = 1;
225 						}
226 						else if (item->action)
227 						{
228 							current_menu_action = item;
229 							current_menu = items;
230 							bg = 1;
231 						}
232 					}
233 					else if (current_menu_action && item == current_menu_action)
234 					{
235 						current_menu_action = NULL;
236 					}
237 				}
238 
239 				if (item->submenu == child && child)
240 				{
241 					copy_rect(child_position, &r);
242 					if (horiz) child_position->y += r.h;
243 					else { child_position->x += r.w + 4; child_position->y -= 4; }
244 					bg = 1;
245 				}
246 
247 				int selected = 0;
248 
249 				if ((pass == DRAW) && (bg || (current_menu_action == item && current_menu_action)))
250 				{
251 					SDL_Rect bar;
252 					copy_rect(&bar, &r);
253 					adjust_rect(&bar, -1);
254 					bar.h --;
255 					bevel(menu_dest, &bar, menu_gfx, BEV_MENU_SELECTED);
256 
257 					font = horiz ? header_font_selected : menu_font_selected;
258 					selected = 1;
259 				}
260 
261 				if (pass == DRAW)
262 				{
263 					SDL_Rect text;
264 					copy_rect(&text, &r);
265 					text.x += font->w;
266 					text.w -= font->w;
267 					font_write(font, menu_dest, &text, item->text);
268 
269 					char tick_char[2] = { 0 };
270 
271 					if ((item->action == MENU_CHECK || item->action == MENU_CHECK_NOSET) && (*(int*)item->p1 & CASTPTR(int,item->p2)))
272 						*tick_char = '�';
273 					else if (item->flags & MENU_BULLET)
274 						*tick_char = '^';
275 
276 					if (tick_char[0] != 0)
277 					{
278 						SDL_Rect tick;
279 						copy_rect(&tick, &r);
280 						tick.y = r.h / 2 + r.y - shortcut_font->h / 2;
281 						font_write(selected ? shortcut_font_selected : shortcut_font, menu_dest, &tick, tick_char);
282 					}
283 				}
284 
285 				if (pass == DRAW && !horiz && sc_text)
286 				{
287 					r.x += r.w;
288 					int tmpw = r.w, tmpx = r.x, tmpy = r.y;
289 					r.w = SC_SIZE;
290 					r.x -= strlen(sc_text) * shortcut_font->w;
291 					r.y = r.h / 2 + r.y - shortcut_font->h / 2;
292 					font_write(selected ? shortcut_font_selected : shortcut_font, menu_dest, &r, sc_text);
293 					r.x = tmpx;
294 					r.y = tmpy;
295 					update_rect(&area, &r);
296 					r.w = tmpw;
297 				}
298 				else update_rect(&area, &r);
299 			}
300 			else
301 			{
302 				separator(menu_dest, &area, &r, menu_gfx, BEV_SEPARATOR);
303 			}
304 		}
305 	}
306 }
307 
308 
draw_menu(GfxDomain * dest,const SDL_Event * e)309 void draw_menu(GfxDomain *dest, const SDL_Event *e)
310 {
311 	draw_submenu(dest, e, current_menu, NULL, NULL, ZONE);
312 	draw_submenu(dest, e, current_menu, NULL, NULL, DRAW);
313 }
314