Major rewrite of scripting support:
[supertux.git] / src / gui / menu.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2004 Tobias Glaesser <tobi.web@gmx.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include <sys/types.h>
22 #include <ctype.h>
23
24 #include <iostream>
25 #include <sstream>
26 #include <cstdlib>
27 #include <cstdio>
28 #include <string>
29 #include <cassert>
30 #include <stdexcept>
31
32 #include "menu.hpp"
33 #include "mainloop.hpp"
34 #include "video/screen.hpp"
35 #include "video/drawing_context.hpp"
36 #include "gettext.hpp"
37 #include "math/vector.hpp"
38 #include "main.hpp"
39 #include "resources.hpp"
40 #include "control/joystickkeyboardcontroller.hpp"
41
42 static const int MENU_REPEAT_INITIAL = 400;
43 static const int MENU_REPEAT_RATE = 200;
44 static const int FLICK_CURSOR_TIME = 500;
45
46 extern SDL_Surface* screen;
47
48 std::vector<Menu*> Menu::last_menus;
49 Menu* Menu::current_ = 0;
50 Font* Menu::default_font;
51 Font* Menu::active_font;
52 Font* Menu::deactive_font;
53 Font* Menu::label_font;
54 Font* Menu::field_font;
55
56 /* just displays a Yes/No text that can be used to confirm stuff */
57 bool confirm_dialog(Surface *background, std::string text)
58 {
59   //Surface* cap_screen = Surface::CaptureScreen();
60   Menu* dialog = new Menu;
61   dialog->add_deactive(-1, text);
62   dialog->add_hl();
63   dialog->add_entry(true, _("Yes"));
64   dialog->add_entry(false, _("No"));
65   dialog->add_hl();
66   
67   Menu::set_current(dialog);
68
69   DrawingContext context;
70
71   // TODO make this a screen and not another mainloop...
72   while(true)
73     {
74       SDL_Event event;
75       while (SDL_PollEvent(&event)) {
76         if(event.type == SDL_QUIT)
77           main_loop->quit();
78         main_controller->process_event(event);
79         dialog->event(event);
80       }
81
82       if(background == NULL)
83         context.draw_gradient(Color(0.8, 0.95, 0.85), Color(0.8, 0.8, 0.8),
84                               LAYER_BACKGROUND0);
85       else
86         context.draw_surface(background, Vector(0,0), LAYER_BACKGROUND0);
87
88       dialog->draw(context);
89       dialog->update();
90
91       switch (dialog->check())
92         {
93         case true:
94           //delete cap_screen;
95           Menu::set_current(0);
96           delete dialog;
97           return true;
98           break;
99         case false:
100           //delete cap_screen;
101           Menu::set_current(0);
102           delete dialog;
103           return false;
104           break;
105         default:
106           break;
107         }
108
109       mouse_cursor->draw(context);
110       context.do_drawing();
111       SDL_Delay(25);
112     }
113
114   return false;
115 }
116
117 void
118 Menu::push_current(Menu* pmenu)
119 {
120   if (current_)
121     last_menus.push_back(current_);
122
123   current_ = pmenu;
124   current_->effect_ticks = SDL_GetTicks();
125 }
126
127 void
128 Menu::pop_current()
129 {
130   if (last_menus.size() >= 1) {
131     current_ = last_menus.back();
132     current_->effect_ticks = SDL_GetTicks();
133     last_menus.pop_back();
134   } else {
135     current_ = 0;
136   }
137 }
138
139 void
140 Menu::set_current(Menu* menu)
141 {
142   last_menus.clear();
143
144   if (menu)
145     menu->effect_ticks = SDL_GetTicks();
146
147   current_ = menu;
148   // just to be sure...
149   main_controller->reset();
150 }
151
152 MenuItem::MenuItem(MenuItemKind _kind, int _id)
153   : kind(_kind) , id(_id)
154 {
155   toggled = false;
156   selected = false;
157   target_menu = 0;
158 }
159
160 void
161 MenuItem::change_text(const  std::string& text_)
162 {
163   text = text_;
164 }
165
166 void
167 MenuItem::change_input(const  std::string& text_)
168 {
169   input = text_;
170 }
171
172 std::string MenuItem::get_input_with_symbol(bool active_item)
173 {
174   if(!active_item) {
175     input_flickering = true;
176   } else {
177     input_flickering = (SDL_GetTicks() / FLICK_CURSOR_TIME) % 2;
178   }
179
180   char str[1024];
181   if(input_flickering)
182     sprintf(str,"%s ",input.c_str());
183   else
184     sprintf(str,"%s_",input.c_str());
185
186   std::string string = str;
187
188   return string;
189 }
190
191 Menu::~Menu()
192 {
193   for(std::vector<MenuItem*>::iterator i = items.begin();
194       i != items.end(); ++i)
195     delete *i;
196 }
197
198 Menu::Menu()
199 {
200   hit_item = -1;
201   menuaction = MENU_ACTION_NONE;
202   delete_character = 0;
203   mn_input_char = '\0';
204
205   pos_x        = SCREEN_WIDTH/2;
206   pos_y        = SCREEN_HEIGHT/2;
207   arrange_left = 0;
208   active_item  = -1;
209
210   checkbox.reset(new Surface("images/engine/menu/checkbox-unchecked.png"));
211   checkbox_checked.reset(new Surface("images/engine/menu/checkbox-checked.png"));
212   back.reset(new Surface("images/engine/menu/arrow-back.png"));
213   arrow_left.reset(new Surface("images/engine/menu/arrow-left.png"));
214   arrow_right.reset(new Surface("images/engine/menu/arrow-right.png"));
215 }
216
217 void Menu::set_pos(float x, float y, float rw, float rh)
218 {
219   pos_x = x + get_width() * rw;
220   pos_y = y + get_height() * rh;
221 }
222
223 /* Add an item to a menu */
224 void
225 Menu::additem(MenuItem* item)
226 {
227   items.push_back(item);
228
229   /* If a new menu is being built, the active item shouldn't be set to
230    * something that isnt selectable. Set the active_item to the first
231    * selectable item added
232    */
233   if (active_item == -1
234       && item->kind != MN_HL 
235       && item->kind != MN_LABEL
236       && item->kind != MN_DEACTIVE) {
237     active_item = items.size() - 1;
238   }
239 }
240
241 void
242 Menu::add_hl()
243 {
244   additem(new MenuItem(MN_HL));
245 }
246
247 void
248 Menu::add_label(const std::string& text)
249 {
250   MenuItem* item = new MenuItem(MN_LABEL);
251   item->text = text;
252   additem(item);
253 }
254
255 void
256 Menu::add_controlfield(int id, const std::string& text,
257                 const std::string& mapping)
258 {
259   MenuItem* item = new MenuItem(MN_CONTROLFIELD, id);
260   item->change_text(text);
261         item->change_input(mapping);
262   additem(item);
263 }
264
265 void
266 Menu::add_entry(int id, const std::string& text)
267 {
268   MenuItem* item = new MenuItem(MN_ACTION, id);
269   item->text = text;
270   additem(item);
271 }
272
273 void
274 Menu::add_deactive(int id, const std::string& text)
275 {
276   MenuItem* item = new MenuItem(MN_DEACTIVE, id);
277   item->text = text;
278   additem(item);
279 }
280
281 void
282 Menu::add_toggle(int id, const std::string& text, bool toogled)
283 {
284   MenuItem* item = new MenuItem(MN_TOGGLE, id);
285   item->text = text;
286   item->toggled = toogled;
287   additem(item);
288 }
289
290 void
291 Menu::add_back(const std::string& text)
292 {
293   MenuItem* item = new MenuItem(MN_BACK);
294   item->text = text;
295   additem(item);
296 }
297
298 void
299 Menu::add_submenu(const std::string& text, Menu* submenu, int id)
300 {
301   MenuItem* item = new MenuItem(MN_GOTO, id);
302   item->text = text;
303   item->target_menu = submenu;
304   additem(item);
305 }
306
307 void
308 Menu::clear()
309 {
310   for(std::vector<MenuItem*>::iterator i = items.begin();
311       i != items.end(); ++i) {
312     delete *i;
313   }
314   items.clear();
315   active_item = -1;
316 }
317
318 /* Process actions done on the menu */
319 void
320 Menu::update()
321 {
322   /** check main input controller... */
323   Uint32 ticks = SDL_GetTicks();
324   if(main_controller->pressed(Controller::UP)) {
325     menuaction = MENU_ACTION_UP;
326     menu_repeat_ticks = ticks + MENU_REPEAT_INITIAL;
327   }
328   if(main_controller->hold(Controller::UP) && 
329       menu_repeat_ticks != 0 && ticks > menu_repeat_ticks) {
330     menuaction = MENU_ACTION_UP;
331     menu_repeat_ticks = ticks + MENU_REPEAT_RATE;
332   } 
333   if(main_controller->pressed(Controller::DOWN)) {
334     menuaction = MENU_ACTION_DOWN;
335     menu_repeat_ticks = ticks + MENU_REPEAT_INITIAL;
336   }
337   if(main_controller->hold(Controller::DOWN) && 
338       menu_repeat_ticks != 0 && ticks > menu_repeat_ticks) {
339     menuaction = MENU_ACTION_DOWN;
340     menu_repeat_ticks = ticks + MENU_REPEAT_RATE;
341   }
342   if(main_controller->pressed(Controller::JUMP)
343      || main_controller->pressed(Controller::ACTION)
344      || main_controller->pressed(Controller::MENU_SELECT)) {
345     menuaction = MENU_ACTION_HIT;
346   }
347   if(main_controller->pressed(Controller::PAUSE_MENU)) {
348     menuaction = MENU_ACTION_BACK;
349   }
350
351   hit_item = -1;
352   if(items.size() == 0)
353     return;
354   
355   int last_active_item = active_item;
356   switch(menuaction) {
357     case MENU_ACTION_UP:
358       do {
359         if (active_item > 0)
360           --active_item;
361         else
362           active_item = int(items.size())-1;
363       } while ((items[active_item]->kind == MN_HL 
364                 || items[active_item]->kind == MN_LABEL
365                 || items[active_item]->kind == MN_DEACTIVE)
366                && (active_item != last_active_item));
367       
368       break;
369       
370     case MENU_ACTION_DOWN:
371       do {
372         if(active_item < int(items.size())-1 )
373           ++active_item;
374         else
375           active_item = 0;
376       } while ((items[active_item]->kind == MN_HL
377                 || items[active_item]->kind == MN_LABEL
378                 || items[active_item]->kind == MN_DEACTIVE)
379                && (active_item != last_active_item));
380       
381       break;
382       
383     case MENU_ACTION_LEFT:
384       if(items[active_item]->kind == MN_STRINGSELECT) {
385         if(items[active_item]->selected > 0)
386           items[active_item]->selected--;
387         else
388           items[active_item]->selected = items[active_item]->list.size()-1;
389       }
390       break;
391       
392     case MENU_ACTION_RIGHT:
393       if(items[active_item]->kind == MN_STRINGSELECT) {
394         if(items[active_item]->selected+1 < items[active_item]->list.size())
395           items[active_item]->selected++;
396         else
397           items[active_item]->selected = 0;
398       }
399       break;
400       
401     case MENU_ACTION_HIT: {
402       hit_item = active_item;
403       switch (items[active_item]->kind) {
404         case MN_GOTO:
405           assert(items[active_item]->target_menu != 0);
406           Menu::push_current(items[active_item]->target_menu);
407           break;
408           
409         case MN_TOGGLE:
410           items[active_item]->toggled = !items[active_item]->toggled;
411           menu_action(items[active_item]);
412           break;
413           
414         case MN_CONTROLFIELD:
415           menu_action(items[active_item]);
416           break;
417           
418         case MN_ACTION:
419           menu_action(items[active_item]);
420           break;
421           
422         case MN_TEXTFIELD:
423         case MN_NUMFIELD:
424           menuaction = MENU_ACTION_DOWN;
425           update();
426           break;
427           
428         case MN_BACK:
429           Menu::pop_current();
430           break;
431         default:
432           break;
433       }
434       break;
435     }
436     
437     case MENU_ACTION_REMOVE:
438       if(items[active_item]->kind == MN_TEXTFIELD
439          || items[active_item]->kind == MN_NUMFIELD)
440       {
441         if(!items[active_item]->input.empty())
442         {
443           int i = items[active_item]->input.size();
444           
445           while(delete_character > 0)   /* remove charactes */
446           {
447             items[active_item]->input.resize(i-1);
448             delete_character--;
449           }
450         }
451       }
452       break;
453       
454     case MENU_ACTION_INPUT:
455       if(items[active_item]->kind == MN_TEXTFIELD
456          || (items[active_item]->kind == MN_NUMFIELD 
457              && mn_input_char >= '0' && mn_input_char <= '9'))
458       {
459         items[active_item]->input.push_back(mn_input_char);
460       }
461       break;
462       
463     case MENU_ACTION_BACK:
464       Menu::pop_current();
465       break;
466
467     case MENU_ACTION_NONE:
468       break;
469   }
470   menuaction = MENU_ACTION_NONE;
471
472   assert(active_item < int(items.size()));
473 }
474
475 int
476 Menu::check()
477 {
478   if (hit_item != -1)
479     return items[hit_item]->id;
480   else
481     return -1;
482 }
483
484 void
485 Menu::menu_action(MenuItem* )
486 {}
487
488 void
489 Menu::draw_item(DrawingContext& context, int index)
490 {
491   int menu_height = get_height();
492   int menu_width = get_width();  
493
494   MenuItem& pitem = *(items[index]);
495
496   int effect_offset = 0;
497   if(effect_ticks != 0) {
498     if(SDL_GetTicks() - effect_ticks > 500) {
499       effect_ticks = 0;
500     } else {
501       Uint32 effect_time = (500 - (SDL_GetTicks() - effect_ticks)) / 4;
502       effect_offset = (index % 2) ? effect_time : -effect_time;
503     }
504   }
505
506   Font* text_font = default_font;
507   float x_pos       = pos_x;
508   float y_pos       = pos_y + 24*index - menu_height/2 + 12 + effect_offset;
509   int shadow_size = 2;
510   int text_width  = int(text_font->get_text_width(pitem.text));
511   int input_width = int(text_font->get_text_width(pitem.input) + 10);
512   int list_width = 0;
513   if(pitem.list.size() > 0) {
514     list_width = (int) text_font->get_text_width(pitem.list[pitem.selected]);
515   }
516   
517   if (arrange_left)
518     x_pos += 24 - menu_width/2 + (text_width + input_width + list_width)/2;
519
520   if(index == active_item)
521     {
522       shadow_size = 3;
523       text_font = active_font;
524     }
525
526   switch (pitem.kind)
527     {
528     case MN_DEACTIVE:
529       {
530         context.draw_text(deactive_font, pitem.text,
531                           Vector(SCREEN_WIDTH/2, y_pos - int(deactive_font->get_height()/2)),
532                           CENTER_ALLIGN, LAYER_GUI);
533         break;
534       }
535
536     case MN_HL:
537       {
538         // TODO
539         float x = pos_x - menu_width/2;
540         float y = y_pos - 12 - effect_offset;
541         /* Draw a horizontal line with a little 3d effect */
542         context.draw_filled_rect(Vector(x, y + 6),
543                                  Vector(menu_width, 4),
544                                  Color(0.6f, 0.7f, 1.0f, 1.0f), LAYER_GUI);
545         context.draw_filled_rect(Vector(x, y + 6),
546                                  Vector(menu_width, 2),
547                                  Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI);
548         break;
549       }
550     case MN_LABEL:
551       {
552         context.draw_text(label_font, pitem.text,
553                           Vector(SCREEN_WIDTH/2, y_pos - int(label_font->get_height()/2)),
554                           CENTER_ALLIGN, LAYER_GUI);
555         break;
556       }
557     case MN_TEXTFIELD:
558     case MN_NUMFIELD:
559     case MN_CONTROLFIELD:
560       {
561         float width = text_width + input_width + 5;
562         float text_pos = SCREEN_WIDTH/2 - width/2;
563         float input_pos = text_pos + text_width + 10;
564
565         context.draw_filled_rect(
566           Vector(input_pos - 5, y_pos - 10),
567           Vector(input_width + 10, 20),
568           Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI-5);
569         context.draw_filled_rect(
570           Vector(input_pos - 4, y_pos - 9),
571           Vector(input_width + 8, 18),
572           Color(0, 0, 0, 0.5f), LAYER_GUI-4);
573
574         if(pitem.kind == MN_TEXTFIELD || pitem.kind == MN_NUMFIELD)
575           {
576             if(active_item == index)
577               context.draw_text(field_font,
578                                 pitem.get_input_with_symbol(true),
579                                 Vector(input_pos, y_pos - int(field_font->get_height()/2)),
580                                 LEFT_ALLIGN, LAYER_GUI);
581             else
582               context.draw_text(field_font,
583                                 pitem.get_input_with_symbol(false),
584                                 Vector(input_pos, y_pos - int(field_font->get_height()/2)),
585                                 LEFT_ALLIGN, LAYER_GUI);
586           }
587         else
588           context.draw_text(field_font, pitem.input,
589                             Vector(input_pos, y_pos - int(field_font->get_height()/2)),
590                             LEFT_ALLIGN, LAYER_GUI);
591
592         context.draw_text(text_font, pitem.text,
593                           Vector(text_pos, y_pos - int(text_font->get_height()/2)),
594                           LEFT_ALLIGN, LAYER_GUI);
595         break;
596       }
597     case MN_STRINGSELECT:
598       {
599         int list_pos_2 = list_width + 16;
600         int list_pos   = list_width/2;
601         int text_pos   = (text_width + 16)/2;
602
603         /* Draw arrows */
604         context.draw_surface(arrow_left.get(),
605                              Vector(x_pos - list_pos + text_pos - 17, y_pos - 8),
606                              LAYER_GUI);
607         context.draw_surface(arrow_right.get(),
608                              Vector(x_pos - list_pos + text_pos - 1 + list_pos_2, y_pos - 8),
609                              LAYER_GUI);
610
611         /* Draw input background */
612         context.draw_filled_rect(
613           Vector(x_pos - list_pos + text_pos - 1, y_pos - 10),
614           Vector(list_pos_2 + 2, 20),
615           Color(1.0f, 1.0f, 1.0f, 1.0f), LAYER_GUI - 4);
616         context.draw_filled_rect(
617           Vector(x_pos - list_pos + text_pos, y_pos - 9),
618           Vector(list_pos_2, 18),
619           Color(0, 0, 0, 0.5f), LAYER_GUI - 5);
620
621         context.draw_text(text_font, pitem.list[pitem.selected],
622                                  Vector(SCREEN_WIDTH/2 + text_pos, y_pos - int(text_font->get_height()/2)),
623                                  CENTER_ALLIGN, LAYER_GUI);
624         context.draw_text(text_font, pitem.text,
625                                  Vector(SCREEN_WIDTH/2  + list_pos_2/2, y_pos - int(text_font->get_height()/2)),
626                                  CENTER_ALLIGN, LAYER_GUI);
627         break;
628       }
629     case MN_BACK:
630       {
631         context.draw_text(text_font, pitem.text,
632                           Vector(SCREEN_WIDTH/2, y_pos - int(text_font->get_height()/2)),
633                           CENTER_ALLIGN, LAYER_GUI);
634         context.draw_surface(back.get(),
635                              Vector(x_pos + text_width/2  + 16, y_pos - 8),
636                              LAYER_GUI);
637         break;
638       }
639
640     case MN_TOGGLE:
641       {
642         context.draw_text(text_font, pitem.text,
643                           Vector(SCREEN_WIDTH/2, y_pos - (text_font->get_height()/2)),
644                           CENTER_ALLIGN, LAYER_GUI);
645
646         if(pitem.toggled)
647           context.draw_surface(checkbox_checked.get(),
648                                Vector(x_pos + (text_width+16)/2, y_pos - 8),
649                                LAYER_GUI + 1);
650         else
651           context.draw_surface(checkbox.get(),
652                                Vector(x_pos + (text_width+16)/2, y_pos - 8),
653                                LAYER_GUI + 1);
654         break;
655       }
656     case MN_ACTION:
657       context.draw_text(text_font, pitem.text,
658                         Vector(SCREEN_WIDTH/2, y_pos - int(text_font->get_height()/2)),
659                         CENTER_ALLIGN, LAYER_GUI);
660       break;
661
662     case MN_GOTO:
663       context.draw_text(text_font, pitem.text,
664                         Vector(SCREEN_WIDTH/2, y_pos - int(text_font->get_height()/2)),
665                         CENTER_ALLIGN, LAYER_GUI);
666       break;
667     }
668 }
669
670 int Menu::get_width() const
671   {
672     /* The width of the menu has to be more than the width of the text
673        with the most characters */
674     int menu_width = 0;
675     for(unsigned int i = 0; i < items.size(); ++i)
676       {
677         int w = items[i]->text.size() + items[i]->input.size() + 1;
678         if(w > menu_width)
679           {
680             menu_width = w;
681             if( items[i]->kind == MN_TOGGLE)
682               menu_width += 2;
683           }
684       }
685
686     return (menu_width * 16 + 24);
687   }
688
689 int Menu::get_height() const
690   {
691     return items.size() * 24;
692   }
693
694 /* Draw the current menu. */
695 void
696 Menu::draw(DrawingContext& context)
697 {
698   if(MouseCursor::current()) {
699     MouseCursor::current()->draw(context);
700   }
701   
702   int menu_height = get_height();
703   int menu_width = get_width();  
704
705   /* Draw a transparent background */
706   context.draw_filled_rect(
707     Vector(pos_x - menu_width/2, pos_y - 24*items.size()/2 - 10),
708     Vector(menu_width,menu_height + 20),
709     Color(0.6f, 0.7f, 0.8f, 0.5f), LAYER_GUI-10);
710
711   for(unsigned int i = 0; i < items.size(); ++i)
712     {
713       draw_item(context, i);
714     }
715 }
716
717 MenuItem&
718 Menu::get_item_by_id(int id)
719 {
720   for(std::vector<MenuItem*>::iterator i = items.begin();
721       i != items.end(); ++i) {
722     MenuItem& item = **i;
723     
724     if(item.id == id)
725       return item;
726   }
727
728   throw std::runtime_error("MenuItem not found");
729 }
730
731 const MenuItem&
732 Menu::get_item_by_id(int id) const
733 {
734   for(std::vector<MenuItem*>::const_iterator i = items.begin();
735       i != items.end(); ++i) {
736     const MenuItem& item = **i;
737     
738     if(item.id == id)
739       return item;
740   }
741
742   throw std::runtime_error("MenuItem not found");
743 }
744
745 int Menu::get_active_item_id()
746 {
747   return items[active_item]->id;
748 }
749
750 bool
751 Menu::is_toggled(int id) const
752 {
753   return get_item_by_id(id).toggled;
754 }
755
756 /* Check for menu event */
757 void
758 Menu::event(const SDL_Event& event)
759 {
760   if(effect_ticks != 0)
761     return;
762
763   switch(event.type) {
764     case SDL_MOUSEBUTTONDOWN:
765       {
766         int x = int(event.motion.x * float(SCREEN_WIDTH)/screen->w);
767         int y = int(event.motion.y * float(SCREEN_HEIGHT)/screen->h);
768
769         if(x > pos_x - get_width()/2 &&
770             x < pos_x + get_width()/2 &&
771             y > pos_y - get_height()/2 &&
772             y < pos_y + get_height()/2)
773           {
774             menuaction = MENU_ACTION_HIT;
775           }
776       }
777       break;
778
779     case SDL_MOUSEMOTION:
780       {
781         float x = event.motion.x * SCREEN_WIDTH/screen->w;
782         float y = event.motion.y * SCREEN_HEIGHT/screen->h;
783
784         if(x > pos_x - get_width()/2 &&
785             x < pos_x + get_width()/2 &&
786             y > pos_y - get_height()/2 &&
787             y < pos_y + get_height()/2)
788           {
789             int new_active_item 
790               = static_cast<int> ((y - (pos_y - get_height()/2)) / 24);
791           
792             /* only change the mouse focus to a selectable item */
793             if ((items[new_active_item]->kind != MN_HL)
794                 && (items[new_active_item]->kind != MN_LABEL)
795                 && (items[new_active_item]->kind != MN_DEACTIVE))
796               active_item = new_active_item;
797             
798             if(MouseCursor::current())
799               MouseCursor::current()->set_state(MC_LINK);
800           }
801         else
802         {
803           if(MouseCursor::current())
804             MouseCursor::current()->set_state(MC_NORMAL);
805         }
806       }
807       break;
808
809     default:
810       break;
811     }
812 }
813
814 void
815 Menu::set_active_item(int id)
816 {
817   for(size_t i = 0; i < items.size(); ++i) {
818     MenuItem* item = items[i];
819     if(item->id == id) {
820       active_item = i;
821       break;
822     }
823   }
824 }