many code-cleanups. merged leveleditor patch from Ricardo Cruz. Fixed bugs. many...
[supertux.git] / src / menu.c
1 /*
2   menu.c
3   
4   Super Tux - Menu
5   
6   by Tobias Glaesser
7   tobi.web@gmx.de
8   http://www.newbreedsoftware.com/supertux/
9   
10   December 20, 2003 - December 30, 2003
11 */
12
13 #ifdef LINUX
14 #include <pwd.h>
15 #include <sys/types.h>
16 #include <ctype.h>
17 #endif
18
19 #include <stdlib.h>
20 #include <stdio.h>
21 #include <string.h>
22
23 #include "defines.h"
24 #include "globals.h"
25 #include "menu.h"
26 #include "screen.h"
27 #include "setup.h"
28 #include "sound.h"
29 #include "scene.h"
30 #include "leveleditor.h"
31 #include "timer.h"
32 #include "high_scores.h"
33
34 /* (global) menu variables */
35 int menuaction;
36 int show_menu;
37 int menu_change;
38 texture_type checkbox, checkbox_checked, back;
39
40 menu_type main_menu, game_menu, options_menu, leveleditor_menu, highscore_menu, load_game_menu, save_game_menu;
41 menu_type* current_menu, * last_menu;
42
43 /* input implementation variables */
44 int delete_character;
45 char mn_input_char;
46
47 /* Set the current menu */
48 void menu_set_current(menu_type* pmenu)
49 {
50   if(pmenu != current_menu)
51     {
52       menu_change = YES;
53       last_menu = current_menu;
54       current_menu = pmenu;
55     }
56 }
57
58 /* Return a pointer to a new menu item */
59 menu_item_type* menu_item_create(int kind, char *text, int init_toggle, void* target_menu)
60 {
61   menu_item_type *pnew_item = (menu_item_type*) malloc(sizeof(menu_item_type));
62   pnew_item->kind = kind;
63   pnew_item->text = (char*) malloc(sizeof(char) * (strlen(text) + 1));
64   strcpy(pnew_item->text,text);
65   if(kind == MN_TOGGLE)
66     pnew_item->toggled = init_toggle;
67   else
68     pnew_item->toggled = NO;
69   pnew_item->target_menu = target_menu;
70   pnew_item->input = NULL;
71   return pnew_item;
72 }
73
74 void menu_item_change_text(menu_item_type* pmenu_item, char *text)
75 {
76   if(text)
77     {
78       free(pmenu_item->text);
79       pmenu_item->text = (char*) malloc(sizeof(char )*(strlen(text)+1));
80       strcpy(pmenu_item->text,text);
81     }
82 }
83
84 /* Free a menu and all its items */
85 void menu_free(menu_type* pmenu)
86 {
87   int i;
88   if(pmenu->num_items != 0 && pmenu->item != NULL)
89     {
90       for(i = 0; i < pmenu->num_items; ++i)
91         {
92           free(pmenu->item[i].text);
93           free(pmenu->item[i].input);
94         }
95       free(pmenu->item);
96     }
97 }
98
99 /* Initialize a menu */
100 void menu_init(menu_type* pmenu)
101 {
102   pmenu->num_items = 0;
103   pmenu->active_item = 0;
104   pmenu->item = NULL;
105 }
106
107 /* Add an item to a menu */
108 void menu_additem(menu_type* pmenu, menu_item_type* pmenu_item)
109 {
110   ++pmenu->num_items;
111   pmenu->item = (menu_item_type*) realloc(pmenu->item, sizeof(menu_item_type) * pmenu->num_items);
112   memcpy(&pmenu->item[pmenu->num_items-1],pmenu_item,sizeof(menu_item_type));
113   free(pmenu_item);
114 }
115
116 /* Process actions done on the menu */
117 void menu_action(menu_type* pmenu)
118 {
119   int i;
120
121   if(pmenu->num_items != 0 && pmenu->item != NULL)
122     {
123       switch(menuaction)
124         {
125         case MN_UP:
126           if(pmenu->active_item > 0)
127             --pmenu->active_item;
128           else
129             pmenu->active_item = pmenu->num_items-1;
130           break;
131         case MN_DOWN:
132           if(pmenu->active_item < pmenu->num_items-1)
133             ++pmenu->active_item;
134           else
135             pmenu->active_item = 0;
136           break;
137         case MN_HIT:
138           if(pmenu->item[pmenu->active_item].kind == MN_GOTO && pmenu->item[pmenu->active_item].target_menu != NULL)
139             menu_set_current((menu_type*)pmenu->item[pmenu->active_item].target_menu);
140           else if(pmenu->item[pmenu->active_item].kind == MN_TOGGLE)
141             {
142               pmenu->item[pmenu->active_item].toggled = !pmenu->item[pmenu->active_item].toggled;
143               menu_change = YES;
144             }
145           else if(pmenu->item[pmenu->active_item].kind == MN_ACTION || pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD)
146             {
147               pmenu->item[pmenu->active_item].toggled = YES;
148             }
149           else if(pmenu->item[pmenu->active_item].kind == MN_BACK)
150             {
151               if(last_menu != NULL)
152                 menu_set_current(last_menu);
153             }
154           break;
155         case MN_REMOVE:
156           if(pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD)
157             {
158               if(pmenu->item[pmenu->active_item].input != NULL)
159                 {
160                   i = strlen(pmenu->item[pmenu->active_item].input);
161
162                   while(delete_character > 0)   /* remove charactes */
163                     {
164                       pmenu->item[pmenu->active_item].input[i-1] = '\0';
165                       delete_character--;
166                     }
167                 }
168             }
169           break;
170         case MN_INPUT:
171           if(pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD)
172             {
173               if(pmenu->item[pmenu->active_item].input != NULL)
174                 {
175                   i = strlen(pmenu->item[pmenu->active_item].input);
176                   pmenu->item[pmenu->active_item].input = (char*) realloc(pmenu->item[pmenu->active_item].input,sizeof(char)*(i + 2));
177                   pmenu->item[pmenu->active_item].input[i] = mn_input_char;
178                   pmenu->item[pmenu->active_item].input[i+1] = '\0';
179                 }
180               else
181                 {
182                   pmenu->item[pmenu->active_item].input = (char*) malloc(2*sizeof(char));
183                   pmenu->item[pmenu->active_item].input[0] = mn_input_char;
184                   pmenu->item[pmenu->active_item].input[1] = '\0';
185                 }
186             }
187           break;
188         }
189     }
190
191   if(pmenu->item[pmenu->active_item].kind == MN_DEACTIVE || pmenu->item[pmenu->active_item].kind == MN_LABEL)
192     {
193       if(menuaction != MN_UP && menuaction != MN_DOWN)
194         menuaction = MN_DOWN;
195
196       if(pmenu->num_items > 1)
197         menu_action(pmenu);
198     }
199
200 }
201
202 /* Check, if the value of the active menu item has changed. */
203 int menu_check(menu_type* pmenu)
204 {
205   if(pmenu->num_items != 0 && pmenu->item != NULL)
206     {
207       if((pmenu->item[pmenu->active_item].kind == MN_ACTION || pmenu->item[pmenu->active_item].kind == MN_TEXTFIELD) && pmenu->item[pmenu->active_item].toggled == YES)
208         {
209           pmenu->item[pmenu->active_item].toggled = NO;
210           show_menu = 0;
211           return pmenu->active_item;
212         }
213       else if(pmenu->item[pmenu->active_item].kind == MN_TOGGLE || pmenu->item[pmenu->active_item].kind == MN_GOTO)
214         {
215           return pmenu->active_item;
216         }
217       else
218         return -1;
219     }
220   else
221     return -1;
222 }
223
224 /* Draw the current menu. */
225 void menu_draw(menu_type* pmenu)
226 {
227   int i, y, menu_height, menu_width;
228
229   /* The width of the menu has to be more than the width of the text with the most characters */
230   menu_width = 0;
231   for(i = 0; i < pmenu->num_items; ++i)
232     {
233       y = strlen(pmenu->item[i].text) + (pmenu->item[i].input ? strlen(pmenu->item[i].input) : 0);
234       if( y > menu_width )
235         {
236           menu_width = y;
237           if( pmenu->item[i].kind == MN_TOGGLE)
238             menu_width += 2;
239         }
240     }
241   menu_width = menu_width * 16 + 48;
242   menu_height = (pmenu->num_items) * 24;
243
244   /* Draw a transparent background */
245   fillrect(screen->w/2 - menu_width/2,screen->h/2-(((pmenu->num_items)*24)/2),menu_width,menu_height,150,150,150,100);
246
247   for(i = 0; i < pmenu->num_items; ++i)
248     {
249       if(pmenu->item[i].kind == MN_DEACTIVE)
250         {
251           text_drawf(&black_text,pmenu->item[i].text,0,(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
252         }
253       else if(pmenu->item[i].kind == MN_LABEL)
254         {
255           text_drawf(&gold_text,pmenu->item[i].text,0,(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
256           fillrect(screen->w/2 - menu_width/2,(i)*24 - menu_height/2 + 20 + screen->h /2,menu_width,2,190,190,190,100);
257         }
258       else if(pmenu->item[i].kind == MN_TEXTFIELD)
259         {
260           text_drawf(&gold_text,pmenu->item[i].input,(strlen(pmenu->item[i].text) * 16)/2,(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
261           if(i == pmenu->active_item)
262             {
263               text_drawf(&blue_text,pmenu->item[i].text,-((strlen(pmenu->item[i].input) * 16)/2),(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,3,NO_UPDATE);
264             }
265           else
266             {
267               text_drawf(&white_text,pmenu->item[i].text,-((strlen(pmenu->item[i].input) * 16)/2),(i)*24 - menu_height/2 +10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
268             }
269         }
270       else if(i == pmenu->active_item)
271         {
272           text_drawf(&blue_text,pmenu->item[i].text,0,(i)*24 - menu_height/2 + 10 ,A_HMIDDLE, A_VMIDDLE,3,NO_UPDATE);
273         }
274       else
275         {
276           text_drawf(&white_text,pmenu->item[i].text,0,(i)*24 - menu_height/2 + 10,A_HMIDDLE, A_VMIDDLE,2,NO_UPDATE);
277         }
278       if(pmenu->item[i].kind == MN_TOGGLE)
279         {
280
281           if(pmenu->item[i].toggled == YES)
282             texture_draw(&checkbox_checked,screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2  + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8,NO_UPDATE);
283           else
284             texture_draw(&checkbox,screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2 + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 - 8,NO_UPDATE);
285         }
286       else if(pmenu->item[i].kind == MN_BACK)
287         {
288           texture_draw(&back,screen->w / 2 + (strlen(pmenu->item[i].text) * 16)/2  + 16,(i)*24 - menu_height/2 + 10 + screen->h / 2 -8,NO_UPDATE);
289         }
290     }
291 }
292
293 /* Reset/Set global defaults */
294 void menu_reset(void)
295 {
296   menu_change = NO;
297   show_menu = NO;
298   menuaction = -1;
299   current_menu = NULL;
300   last_menu = NULL;
301
302   delete_character = 0;
303   mn_input_char = '\0';
304 }
305
306 /* --- MENU --- */
307 /* Draw the current menu and execute the (menu)events */
308 void menu_process_current(void)
309 {
310   menu_change = NO;
311
312   if(current_menu != NULL)
313     {
314       menu_action(current_menu);
315       menu_draw(current_menu);
316     }
317
318   menuaction = -1;
319 }
320
321 /* Check for menu event */
322 void menu_event(SDL_keysym* keysym)
323 {
324   SDLKey key = keysym->sym;
325   SDLMod keymod;
326   keymod = SDL_GetModState();
327   char ch[2];
328
329   /* If the current unicode character is an ASCII character,
330      assign it to ch. */
331   if ( (keysym->unicode & 0xFF80) == 0 )
332     {
333       ch[0] = keysym->unicode & 0x7F;
334       ch[1] = '\0';
335     }
336   else
337     {
338       /* An International Character. */
339     }
340
341   switch(key)
342     {
343     case SDLK_UP:               /* Menu Up */
344       menuaction = MN_UP;
345       menu_change = YES;
346       break;
347     case SDLK_DOWN:             /* Menu Down */
348       menuaction = MN_DOWN;
349       menu_change = YES;
350       break;
351     case SDLK_SPACE:            /* Menu Hit */
352     case SDLK_RETURN:
353       menuaction = MN_HIT;
354       menu_change = YES;
355       break;
356     case SDLK_DELETE:
357     case SDLK_BACKSPACE:
358       menuaction = MN_REMOVE;
359       menu_change = YES;
360       delete_character++;
361       break;
362     default:
363       if( key >= SDLK_0 && key <= SDLK_9)
364         {
365           menuaction = MN_INPUT;
366           menu_change = YES;
367           mn_input_char = *ch;
368         }
369       else if( key >= SDLK_a && key <= SDLK_z )
370         {
371           menuaction = MN_INPUT;
372           menu_change = YES;
373           mn_input_char = *ch;
374         }
375       else
376         {
377           mn_input_char = '\0';
378         }
379       break;
380     }
381
382
383   /* FIXME: NO JOYSTICK SUPPORT */
384   /*#ifdef JOY_YES
385   else if (event.type == SDL_JOYBUTTONDOWN)
386    {
387       Joystick button: Continue:
388
389      done = 1;
390    }
391   #endif*/
392 }
393