1 /* Tower Toppler - Nebulus
2  * Copyright (C) 2000-2012  Andreas R�ver
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17  */
18 
19 #include "menusys.h"
20 
21 #include "string.h"
22 #include "screen.h"
23 #include "sprites.h"
24 #include "configuration.h"
25 #include "keyb.h"
26 
27 #include <ctype.h>
28 
29 static menubg_callback_proc menu_background_proc = NULL;
30 
set_men_bgproc(menubg_callback_proc proc)31 void set_men_bgproc(menubg_callback_proc proc) {
32   menu_background_proc = proc;
33 }
34 
new_menu_system(const char * title,menuopt_callback_proc pr,int molen,int ystart)35 _menusystem *new_menu_system(const char *title, menuopt_callback_proc pr, int molen, int ystart)
36 {
37   _menusystem *ms = new _menusystem;
38 
39   if (ms) {
40     memset(ms->title, '\0', MENUTITLELEN);
41     if (title) {
42       memcpy(ms->title, title, (strlen(title) < MENUTITLELEN) ? strlen(title) + 1 : (MENUTITLELEN-1));
43     }
44 
45     ms->numoptions = 0;
46     ms->moption = NULL;
47     ms->mstate = 0;
48     ms->mproc = pr;
49     ms->maxoptlen = molen;
50     ms->exitmenu = false;
51     ms->wraparound = false;
52     ms->curr_mtime = ms->hilited = 0;
53     ms->ystart = ystart;
54     ms->key = SDLK_UNKNOWN;
55     ms->mtime = ms->yhilitpos = ms->opt_steal_control = -1;
56     ms->timeproc = NULL;
57   }
58 
59   return ms;
60 }
61 
62 _menusystem *
add_menu_option(_menusystem * ms,const char * name,menuopt_callback_proc pr,SDLKey quickkey,menuoptflags flags,int state)63 add_menu_option(_menusystem *ms,
64                 const char *name,
65                 menuopt_callback_proc pr,
66                 SDLKey quickkey,
67                 menuoptflags flags,
68                 int state) {
69   _menuoption *tmp;
70   int olen = 0;
71 
72   if (!ms) return ms;
73 
74   tmp = new _menuoption[ms->numoptions+1];
75 
76   if (!tmp) return ms;
77 
78   memcpy(tmp, ms->moption, sizeof(_menuoption)*ms->numoptions);
79   delete [] ms->moption;
80 
81   memset(tmp[ms->numoptions].oname, '\0', MENUOPTIONLEN);
82 
83   /* if no name, but has callback proc, query name from it. */
84   if (!name && pr) name = (*pr) (NULL);
85 
86   if (name) {
87     olen = strlen(name);
88     memcpy(tmp[ms->numoptions].oname, name, (olen < MENUOPTIONLEN) ? olen + 1 : (MENUOPTIONLEN-1));
89   }
90 
91   tmp[ms->numoptions].oproc = pr;
92   tmp[ms->numoptions].ostate = state;
93   tmp[ms->numoptions].oflags = flags;
94   tmp[ms->numoptions].quickkey = quickkey;
95 
96   ms->moption = tmp;
97   ms->numoptions++;
98   if (name)
99     olen = scr_textlength(name);
100   else
101     olen = 0;
102   if (ms->maxoptlen < olen) ms->maxoptlen = olen;
103 
104   return ms;
105 }
106 
107 void
free_menu_system(_menusystem * ms)108 free_menu_system(_menusystem *ms)
109 {
110   if (!ms) return;
111 
112   delete [] ms->moption;
113   ms->numoptions = 0;
114   ms->mstate = 0;
115   ms->mproc = NULL;
116   delete ms;
117 }
118 
draw_background(_menusystem * ms)119 static void draw_background(_menusystem *ms)
120 {
121   while (!ms->mproc && ms->parent) ms = ms->parent;
122 
123   if (ms->mproc)
124     (*ms->mproc)(ms);
125   else if (menu_background_proc)
126     (*menu_background_proc) ();
127 }
128 
129 
130 void
draw_menu_system(_menusystem * ms,Uint16 dx,Uint16 dy)131 draw_menu_system(_menusystem *ms, Uint16 dx, Uint16 dy)
132 {
133   static int color_r = 0, color_g = 20, color_b = 70;
134 
135   if (!ms) return;
136 
137   int y, offs = 0, len, realy, minx, miny, maxx, maxy, scrlen,
138     newhilite = -1, yz, titlehei;
139   bool has_title = (ms->title) && (strlen(ms->title) != 0);
140 
141   if (ms->wraparound) {
142     if (ms->hilited < 0)
143       ms->hilited = ms->numoptions - 1;
144     else if (ms->hilited >= ms->numoptions)
145       ms->hilited = 0;
146   } else {
147     if (ms->hilited < 0)
148       ms->hilited = 0;
149     else if (ms->hilited >= ms->numoptions)
150       ms->hilited = ms->numoptions - 1;
151   }
152 
153   draw_background(ms);
154 
155   titlehei = 0;
156 
157   if (has_title) {
158     int pos = 0;
159     int start = 0;
160     int len = strlen(ms->title);
161 
162     while (pos <= len) {
163 
164       if ((ms->title[pos] == '\n') || (ms->title[pos] == 0)) {
165 
166         bool end = ms->title[pos] == 0;
167 
168         ms->title[pos] = 0;
169         scr_writetext_center(ms->ystart + titlehei * FONTHEI, ms->title + start);
170         titlehei ++;
171 
172         if (!end)
173           ms->title[pos] = '\n';
174 
175         start = pos + 1;
176       }
177       pos++;
178     }
179 
180     titlehei++;
181   }
182 
183 
184 
185   /* TODO: Calculate offs from ms->hilited.
186    * TODO: Put slider if more options than fits in screen.
187    */
188 
189   yz = ms->ystart + (titlehei) * FONTHEI;
190 
191   for (y = 0; (yz+y+1 < SCREENHEI) && (y+offs < ms->numoptions); y++) {
192     realy = yz + y * FONTHEI;
193     len = strlen(ms->moption[y+offs].oname);
194     scrlen = scr_textlength(ms->moption[y+offs].oname, len);
195     minx = (SCREENWID - scrlen) / 2;
196     miny = realy;
197     maxx = (SCREENWID + scrlen) / 2;
198     maxy = realy + FONTHEI;
199     if (len) {
200       if (dx >= minx && dx <= maxx && dy >= miny && dy <= maxy) {
201         newhilite = y + offs;
202         ms->curr_mtime = 0;
203       }
204       if (y + offs == ms->hilited) {
205         if (ms->yhilitpos == -1) {
206           ms->yhilitpos = miny;
207         } else {
208           if (ms->yhilitpos < miny) {
209             ms->yhilitpos += ((miny - ms->yhilitpos + 3) / 4)+1;
210             if (ms->yhilitpos > miny) ms->yhilitpos = miny;
211           } else if (ms->yhilitpos > miny) {
212             ms->yhilitpos -= ((ms->yhilitpos - miny + 3) / 4)+1;
213             if (ms->yhilitpos < miny) ms->yhilitpos = miny;
214           }
215         }
216         scr_putbar((SCREENWID - ms->maxoptlen - 8) / 2, ms->yhilitpos - 3,
217                    ms->maxoptlen + 8, FONTHEI + 3,
218                    color_r, color_g, color_b, (config.use_alpha_darkening())?128:255);
219       }
220     }
221   }
222 
223   maxy = y;
224 
225   for (y = 0; y < maxy; y++) {
226     if (strlen(ms->moption[y+offs].oname)) {
227       miny = ms->ystart + (y + titlehei)*FONTHEI;
228       if ((ms->moption[y+offs].oflags & MOF_LEFT))
229         scr_writetext((SCREENWID - ms->maxoptlen) / 2 + 4, miny,
230                       ms->moption[y+offs].oname);
231       else
232         if ((ms->moption[y+offs].oflags & MOF_RIGHT))
233           scr_writetext((SCREENWID + ms->maxoptlen) / 2 - 4
234                         - scr_textlength(ms->moption[y+offs].oname), miny,
235                         ms->moption[y+offs].oname);
236         else
237           scr_writetext_center(miny, ms->moption[y+offs].oname);
238     }
239   }
240 
241   if (newhilite >= 0) ms->hilited = newhilite;
242 
243   scr_color_ramp(&color_r, &color_g, &color_b);
244 
245   scr_swap();
246   dcl_wait();
247 }
248 
249 void
menu_system_caller(_menusystem * ms)250 menu_system_caller(_menusystem *ms)
251 {
252   const char *tmpbuf = (*ms->moption[ms->hilited].oproc) (ms);
253   if (tmpbuf) {
254     int olen = strlen(tmpbuf);
255     memset(ms->moption[ms->hilited].oname, '\0', MENUOPTIONLEN);
256     memcpy(ms->moption[ms->hilited].oname, tmpbuf,
257            (olen < MENUOPTIONLEN) ? olen + 1 : (MENUOPTIONLEN-1));
258     olen = scr_textlength(tmpbuf);
259     if (ms->maxoptlen < olen) ms->maxoptlen = olen;
260     ms->key = SDLK_UNKNOWN;
261   }
262 }
263 
264 
265 _menusystem *
run_menu_system(_menusystem * ms,_menusystem * parent)266 run_menu_system(_menusystem *ms, _menusystem *parent)
267 {
268   Uint16 x,y;
269   ttkey bttn;
270   bool stolen = false;
271 
272   if (!ms) return ms;
273 
274   ms->parent = parent;
275 
276   /* find the first option with text */
277   if (!strlen(ms->moption[ms->hilited].oname))
278     do {
279       ms->hilited = (ms->hilited + 1) % ms->numoptions;
280     } while (!strlen(ms->moption[ms->hilited].oname));
281 
282   (void)key_sdlkey();
283 
284   do {
285 
286     stolen = false;
287     bttn = no_key;
288     x = y = 0;
289 
290     ms->key = SDLK_UNKNOWN;
291 
292     if ((ms->curr_mtime++ >= ms->mtime) && ms->timeproc) {
293       (void) (*ms->timeproc) (ms);
294       ms->curr_mtime = 0;
295     }
296 
297     if ((ms->opt_steal_control >= 0) &&
298         (ms->opt_steal_control < ms->numoptions) &&
299         ms->moption[ms->opt_steal_control].oproc) {
300       ms->key = key_sdlkey();
301       ms->hilited = ms->opt_steal_control;
302       stolen = true;
303     } else {
304       if (!config.fullscreen() && !key_mouse(&x, &y, &bttn) && bttn)
305         ms->key = key_conv2sdlkey(bttn, false);
306       else ms->key = key_sdlkey();
307     }
308 
309     draw_menu_system(ms, x, y);
310 
311     if ((ms->key != SDLK_UNKNOWN) || stolen) {
312 
313       if (!stolen) {
314         ms->curr_mtime = 0;
315         for (int tmpz = 0; tmpz < ms->numoptions; tmpz++)
316           if (ms->moption[tmpz].quickkey == ms->key) {
317             ms->hilited = tmpz;
318             ms->key = SDLK_UNKNOWN;
319             break;
320           }
321       }
322 
323       if ((((ms->moption[ms->hilited].oflags & MOF_PASSKEYS)) || stolen) &&
324           (ms->moption[ms->hilited].oproc) && ((ms->key != SDLK_UNKNOWN) || stolen)) {
325         menu_system_caller(ms);
326       }
327       if (!stolen) {
328         switch (key_sdlkey2conv(ms->key, false)) {
329         case down_key:
330           if (ms->wraparound) {
331             do {
332               ms->hilited = (ms->hilited + 1) % ms->numoptions;
333             } while (!strlen(ms->moption[ms->hilited].oname));
334           } else {
335             if (ms->hilited < ms->numoptions) {
336               ms->hilited++;
337               if (!strlen(ms->moption[ms->hilited].oname)) ms->hilited++;
338             }
339           }
340           break;
341         case up_key:
342           if (ms->wraparound) {
343             do {
344               ms->hilited--;
345               if (ms->hilited < 0) ms->hilited = ms->numoptions - 1;
346             } while (!strlen(ms->moption[ms->hilited].oname));
347           } else {
348             int tmpz = ms->hilited;
349             if (ms->hilited > 0) {
350               do {
351                 if (ms->hilited < 0) {
352                   ms->hilited = tmpz;
353                   break;
354                 }
355                 ms->hilited--;
356               } while (!strlen(ms->moption[ms->hilited].oname));
357             }
358           }
359           break;
360         case fire_key:
361           if ((ms->hilited >= 0) && (ms->hilited < ms->numoptions) &&
362               ms->moption[ms->hilited].oproc) {
363             const char *tmpbuf = (*ms->moption[ms->hilited].oproc) (ms);
364             if (tmpbuf) {
365               int olen = strlen(tmpbuf);
366               memset(ms->moption[ms->hilited].oname, '\0', MENUOPTIONLEN);
367               memcpy(ms->moption[ms->hilited].oname, tmpbuf,
368                      (olen < MENUOPTIONLEN) ? olen + 1 : (MENUOPTIONLEN-1));
369               olen = scr_textlength(tmpbuf);
370               if (ms->maxoptlen < olen) ms->maxoptlen = olen;
371             }
372             break;
373           }
374         case break_key : ms->exitmenu = true; break;
375         default:
376           break;
377         }
378       }
379     }
380   } while (!ms->exitmenu);
381   return ms;
382 }
383 
men_info(char * s,long timeout,int fire)384 void men_info(char *s, long timeout, int fire) {
385   bool ende = false;
386   do {
387     if (menu_background_proc) (*menu_background_proc) ();
388     scr_writetext_center((SCREENHEI / 5), s);
389     if (fire)
390       scr_writetext_center((SCREENHEI / 5) + 2 * FONTHEI, (fire == 1) ? _("Press fire") : _("Press space"));
391     scr_swap();
392     dcl_wait();
393     if (timeout > 0) timeout--;
394     if (!timeout) ende = true;
395     else if ((fire == 2) && (key_chartyped() == ' ')) ende = true;
396     else if ((fire != 2) && key_keypressed(fire_key)) ende = true;
397   } while (!ende);
398   (void)key_sdlkey();
399 }
400 
401 static int input_box_cursor_state = 0;
402 
403 void
draw_input_box(int x,int y,int len,int cursor,char * txt)404 draw_input_box(int x, int y, int len, int cursor, char *txt)
405 {
406   static int col_r = 0, col_g = 200, col_b = 120;
407   int nlen = len, slen = len;
408   int arrows = 0;
409 
410   if ((len+3)*FONTMAXWID > SCREENWID)
411     nlen = (SCREENWID / FONTMAXWID) - 3;
412 
413   if (x < 0) x = (SCREENWID / 2) - nlen * (FONTMAXWID / 2);
414   if (x < 0) x = 0;
415   if (y < 0) y = (SCREENHEI / 2) - (FONTHEI / 2);
416 
417   scr_putbar(x, y, nlen * FONTMAXWID, FONTHEI, 0, 0, 0, (config.use_alpha_darkening())?128:255);
418 
419   if (scr_textlength(txt) >= nlen*FONTMAXWID) {
420     while ((cursor >= 0) &&
421            (scr_textlength(txt, cursor+(nlen/2)) >= (nlen)*FONTMAXWID)) {
422       cursor--;
423       txt++;
424       arrows = 1;
425     }
426   }
427   if (scr_textlength(txt) >= nlen*FONTMAXWID) {
428     arrows |= 2;
429     while ((slen > 0) && (scr_textlength(txt, slen) >= nlen*FONTMAXWID)) slen--;
430   }
431 
432   scr_writetext(x+1,y, txt, slen);
433 
434   if ((input_box_cursor_state & 4) && (cursor >= 0))
435     scr_putbar(x + scr_textlength(txt, cursor) + 1, y, FONTMINWID, FONTHEI,
436                col_r, col_g, col_b, (config.use_alpha_darkening())?128:255);
437   scr_putrect(x,y, nlen * FONTMAXWID, FONTHEI, col_r, col_g, col_b, 255);
438 
439   if ((arrows & 1)) scr_writetext(x-FONTMAXWID,y, "\x08"); //fontptrright
440   if ((arrows & 2)) scr_writetext(x+(nlen*FONTMAXWID),y, "\x06"); //fontptrleft
441 
442   input_box_cursor_state++;
443 
444   scr_color_ramp(&col_r, &col_g, &col_b);
445 }
446 
men_input(char * origs,int max_len,int xpos,int ypos,const char * allowed)447 bool men_input(char *origs, int max_len, int xpos, int ypos, const char *allowed) {
448   SDLKey sdlinp;
449   char inpc;
450   ttkey inptt;
451   static int pos = strlen(origs);
452   int ztmp;
453   static char s[256];
454   static bool copy_origs = true;
455   bool restore_origs = false;
456   bool ende = false;
457 
458   if ((strlen(origs) >= 256)) return true;
459 
460   if (copy_origs) {
461     strcpy(s, origs);
462     copy_origs = false;
463     pos = strlen(origs);
464   }
465 
466   (void)key_readkey();
467 
468   if (menu_background_proc) (*menu_background_proc) ();
469 
470   draw_input_box(xpos,ypos, max_len, pos, s);
471   scr_swap();
472   dcl_wait();
473 
474   key_keydatas(sdlinp, inptt, inpc);
475 
476   switch (sdlinp) {
477   case SDLK_RIGHT: if ((unsigned)pos < strlen(s)) pos++; break;
478   case SDLK_LEFT: if (pos > 0) pos--; break;
479   case SDLK_ESCAPE:if (strlen(s)) {
480     s[0] = '\0';
481     pos = 0;
482     restore_origs = false;
483   } else {
484     restore_origs = true;
485     ende = true;
486   }
487   break;
488   case SDLK_RETURN: restore_origs = false; copy_origs = true; ende = true;
489   break;
490   case SDLK_DELETE:
491     if (strlen(s) >= (unsigned)pos) {
492       for (ztmp = pos; ztmp < max_len-1; ztmp++) s[ztmp] = s[ztmp+1];
493       s[ztmp] = '\0';
494     }
495     break;
496   case SDLK_BACKSPACE:
497     if (pos > 0) {
498       if (pos <= max_len) {
499         for (ztmp = pos-1; ztmp < max_len-1; ztmp++) s[ztmp] = s[ztmp+1];
500         s[ztmp] = '\0';
501       }
502       pos--;
503     }
504     break;
505   default:
506     if (pos >= max_len || (inpc < ' ')) break;
507     if (allowed) {
508       if (!strchr(allowed, inpc)) {
509         if (strchr(allowed, toupper(inpc))) inpc = toupper(inpc);
510         else
511           if (strchr(allowed, tolower(inpc))) inpc = tolower(inpc);
512           else break;
513       }
514     } else {
515       if (inpc < ' ' || inpc > 'z') break;
516     }
517     if ((strlen(s) >= (unsigned)pos) &&
518         (strlen(s) < (unsigned)max_len)) {
519       for (ztmp = max_len-1; ztmp >= pos; ztmp--) s[ztmp+1] = s[ztmp];
520       s[pos] = inpc;
521       s[max_len] = '\0';
522       pos++;
523     }
524     break;
525   }
526   if (ende) {
527     if (!restore_origs) strcpy(origs, s);
528     s[0] = 0;
529     copy_origs = true;
530   } else {
531     copy_origs = false;
532   }
533   return ende;
534 }
535 
536 _menusystem *
set_menu_system_timeproc(_menusystem * ms,long t,menuopt_callback_proc pr)537 set_menu_system_timeproc(_menusystem *ms, long t, menuopt_callback_proc pr)
538 {
539   if (!ms) return ms;
540 
541   ms->timeproc = pr;
542   ms->mtime = t;
543 
544   return ms;
545 }
546 
547 static const char *
men_yn_option_yes(_menusystem * ms)548 men_yn_option_yes(_menusystem *ms)
549 {
550   if (ms) {
551     ms->mstate = 1;
552     ms->exitmenu = true;
553     return NULL;
554   } else return _("Yes");
555 }
556 
557 static const char *
men_yn_option_no(_menusystem * ms)558 men_yn_option_no(_menusystem *ms)
559 {
560   if (ms) {
561     ms->mstate = 0;
562     ms->exitmenu = true;
563     return NULL;
564   } else return _("No");
565 }
566 
men_yn(char * s,bool defchoice,menuopt_callback_proc pr)567 unsigned char men_yn(char *s, bool defchoice, menuopt_callback_proc pr) {
568   _menusystem *ms = new_menu_system(s, pr, 0, SCREENHEI / 5);
569 
570   bool doquit = false;
571 
572   if (!ms) return defchoice;
573 
574   ms = add_menu_option(ms, NULL, men_yn_option_no, SDLK_n);
575   ms = add_menu_option(ms, NULL, men_yn_option_yes, SDLK_y);
576 
577   ms->mstate = defchoice ? 1 : 0;
578 
579   ms = run_menu_system(ms, 0);
580 
581   doquit = (ms->mstate != 0);
582 
583   free_menu_system(ms);
584 
585   return ((doquit == 0) ? 0 : 1);
586 }
587 
588