1 /* 2 Uses the Context Menu system to implement user configured command menus. 3 4 Files with a .menu name extension found in the users config directory are 5 considered user menu files. Each .menu file defines a new menu displayed 6 in a standard EL window container. The window is always on top and can be 7 moved around by the optional title bar. A standard right-click context menu 8 controls window options. These options include a run-time reload function so you 9 can update the menus without restarting the client. Left clicking a particular 10 menu name opens the menu. The window options and position are saved 11 between sessions in the cfg file. The use of user menus is enabled/disabled 12 by an option in the config window. 13 14 The .menu files are flat text files so they are easy to create and edit outside 15 the client. The first line in a .menu file is used as the menu name in the 16 container window. Each of the remaining lines are new lines for the menu. Lines 17 for the menus have an associated set of commands that are executed when 18 that line is selected. Valid commands are anything you can enter at the user 19 command prompt; #commands, text for chat channels & PM, %setting etc. In 20 addition, commands can be URLs which will be directly opened in your browser. 21 22 Each menu line in a .menu file has two or more fields, the field separator is 23 "||". The first field is the text for the menu line, the remaining field or 24 fields are the associated commands. 25 26 Commands can prompt for user input. If the command contains text inside "<>", 27 e.g. "some text <more text> some other text" then the text inside the "<>" will 28 be used as a prompt and the "<...>" replaced by the text entered by the user. 29 You can include as many input prompts as you wish. 30 31 32 Author bluap/pjbroad May 2009 33 */ 34 35 // An example: 36 /* 37 Example 38 Show Server Stats||#stats 39 Show Invasion Monsters Count||#il 40 Show Day, Date & Time||#day||#date||#time 41 Show Day, Date & Time with 5 second delay||#user_menu_wait_time_ms 5000||#day||#date||#time||#user_menu_wait_time_ms 42 || 43 Say local hi||hi 44 Stats On||%show_stats_in_hud=1 45 Stats Off||%show_stats_in_hud=0 46 Channel Hi||@hi 47 || 48 Open Readonly Storage||#sto 49 Guild Info...||#guild_info <Guild Name> 50 || 51 Player Info...||#open_url http://game.eternal-lands.com/view_user.php?user=<Player Name> 52 BBC News||#open_url http://news.bbc.co.uk/ 53 */ 54 55 56 /* To Do: 57 - tidy up how the container window creates its context menu 58 - restore context help text 59 - split Container class, possibly into window/container classes 60 - virtical/horizontal option 61 - option auto hide down to icon 62 - option hide/show from clicking an icon - handles at ends 63 - make the menu prettier 64 - use a subtle text fade once the mouse is no longer over 65 - parameters should be separate (static ?) class or vars so do not create container usless needed 66 - tear off windows - sounds a lot of work..... 67 68 */ 69 70 71 #include <iostream> 72 #include <fstream> 73 #include <string> 74 #include <vector> 75 #include <queue> 76 #ifdef WINDOWS 77 #include "io.h" 78 #else 79 #include <glob.h> 80 #endif 81 82 #include <SDL.h> 83 #include <SDL_thread.h> 84 85 #include "chat.h" 86 #include "command_queue.hpp" 87 #include "context_menu.h" 88 #include "elconfig.h" 89 #include "elwindows.h" 90 #include "errors.h" 91 #include "font.h" 92 #include "gamewin.h" 93 #include "gl_init.h" 94 #include "hud.h" 95 #include "hud_indicators.h" 96 #include "icon_window.h" 97 #include "init.h" 98 #include "io/elpathwrapper.h" 99 #ifdef JSON_FILES 100 #include "json_io.h" 101 #endif 102 #include "translate.h" 103 #include "user_menus.h" 104 105 namespace UserMenus 106 { 107 // 108 // A single user menu, constructed from a menu file. Contains 109 // one or more Line objects. 110 // 111 class Menu 112 { 113 public: 114 Menu(const std::string &file_name); 115 ~Menu(void); get_name(void) const116 const std::string & get_name(void) const { return menu_name; } get_name_width(void) const117 int get_name_width(void) const { return menu_name_width; } recalc_name_width(float zoom)118 int recalc_name_width(float zoom) 119 { 120 menu_name_width = eternal_lands::FontManager::get_instance() 121 .line_width(UI_FONT, reinterpret_cast<const unsigned char*>(menu_name.c_str()), 122 menu_name.length(), zoom); 123 return menu_name_width; 124 } get_cm_id(void) const125 size_t get_cm_id(void) const { return cm_menu_id; } action(int option,CommandQueue::Queue & cq) const126 void action(int option, CommandQueue::Queue &cq) const { lines[option]->action(cq); }; 127 private: 128 size_t cm_menu_id; 129 int menu_name_width; 130 std::string menu_name; 131 std::vector<CommandQueue::Line *> lines; 132 }; 133 134 135 // 136 // A singleton class for the user menu window, contains the 137 // menu objects and implements the window callbacks. 138 // 139 class Container 140 { 141 public: 142 void destroy(void); 143 void open_window(void); close_window(void)144 void close_window(void) { command_queue.clear(); if (win_id >= 0) hide_window(win_id); } 145 void set_options(int win_x, int win_y, int options); 146 void get_options(int *win_x, int *win_y, int *options) const; 147 #ifdef JSON_FILES 148 void read_options(const char *dict_name); 149 void write_options(const char *dict_name) const; 150 #endif 151 static Container * get_instance(void); action_handler(window_info * win,int widget_id,int mx,int my,int option)152 static int action_handler(window_info *win, int widget_id, int mx, int my, int option) { return get_instance()->action(widget_id, option); } pre_show_handler(window_info * win,int widget_id,int mx,int my,window_info * cm_win)153 static void pre_show_handler(window_info *win, int widget_id, int mx, int my, window_info *cm_win) { get_instance()->pre_show(win, widget_id, mx, my, cm_win); } 154 155 private: 156 Container(void); 157 int win_id; 158 int win_width; 159 size_t current_mouseover_menu; 160 bool mouse_over_window; 161 bool reload_menus; 162 size_t context_id; 163 bool window_used; 164 int title_on; 165 int background_on; 166 int border_on; 167 int use_small_font; 168 int include_datadir; 169 bool auto_include_datadir; 170 int just_echo; 171 int win_x_pos; 172 int win_y_pos; 173 std::vector<Menu *> menus; 174 CommandQueue::Queue command_queue; 175 int name_sep; 176 int window_x_pad, window_y_pad; 177 int standard_window_position; 178 179 void update_standard_window_position(window_info *win); set_title_state(window_info * win,bool title_state)180 void set_title_state(window_info *win, bool title_state) { title_on = (title_state) ?1:0; set_win_flag(&win->flags, ELW_TITLE_BAR, title_on); } 181 void reload(window_info *win); 182 void recalc_win_width(window_info *win); 183 int display(window_info *win); 184 int action(size_t active_menu, int option); 185 void pre_show(window_info *win, int widget_id, int mx, int my, window_info *cm_win); 186 int click(window_info *win, int mx, Uint32 flags); 187 int ui_scale_changed(window_info *win); 188 int font_changed(window_info *win, eternal_lands::FontManager::Category cat); 189 size_t get_mouse_over_menu(window_info *win, int mx); 190 void delete_menus(void); 191 int context(window_info *win, int widget_id, int mx, int my, int option); 192 void set_win_flag(Uint32 *flags, Uint32 flag, int state); mouseover(window_info * win,int mx)193 void mouseover(window_info *win, int mx) { mouse_over_window = true; current_mouseover_menu = get_mouse_over_menu(win, mx); } get_height(window_info * win) const194 int get_height(window_info *win) const { return ((use_small_font) ?win->small_font_len_y :win->default_font_len_y) + 2 * window_y_pad; } calc_actual_width(window_info * win,int width) const195 int calc_actual_width(window_info *win, int width) const 196 { return (int)(0.5 + (use_small_font ? win->current_scale_small : win->current_scale) * width); } 197 display_handler(window_info * win)198 static int display_handler(window_info *win) { return get_instance()->display(win); } mouseover_handler(window_info * win,int mx,int my)199 static int mouseover_handler(window_info *win, int mx, int my) { get_instance()->mouseover(win, mx); return 0; } click_handler(window_info * win,int mx,int my,Uint32 flags)200 static int click_handler(window_info *win, int mx, int my, Uint32 flags) { return get_instance()->click(win, mx, flags); } ui_scale_handler(window_info * win)201 static int ui_scale_handler(window_info *win) { return get_instance()->ui_scale_changed(win); } font_change_handler(window_info * win,font_cat cat)202 static int font_change_handler(window_info *win, font_cat cat) { return get_instance()->font_changed(win, cat); } context_handler(window_info * win,int widget_id,int mx,int my,int option)203 static int context_handler(window_info *win, int widget_id, int mx, int my, int option){ return get_instance()->context(win, widget_id, mx, my, option); } 204 205 enum { STND_POS_NONE = 0, STND_POS_TOP_LEFT, STND_POS_TOP_CENTRE, STND_POS_TOP_RIGHT, STND_POS_BOTTOM_LEFT, STND_POS_BOTTOM_CENTRE, STND_POS_BOTTON_RIGHT, STND_POS_LAST }; 206 enum { CM_MOVEWIN=1, CM_FIXPOS, CM_CHANGEPOS, CM_BACKGND, CM_BORDER, CM_FONT, CM_STANDMENU, CM_SEP1, CM_SHOWCMD, CM_SEP2, CM_RELOAD, CM_DISABLE }; 207 }; 208 209 210 // 211 // Construct the Menu given a filepath and name. 212 // Menu(const std::string & file_name)213 Menu::Menu(const std::string &file_name) 214 : cm_menu_id(CM_INIT_VALUE), menu_name_width(0) 215 { 216 std::ifstream in(file_name.c_str()); 217 if (!in) 218 { 219 LOG_ERROR("%s: Failed to open [%s]\n", __FILE__, file_name.c_str() ); 220 return; 221 } 222 223 // the first line is the menu name, a menu without a name is not a menu 224 getline(in, menu_name); 225 if (menu_name.empty()) 226 { 227 LOG_ERROR("%s: Failed while reading [%s]\n", __FILE__, file_name.c_str() ); 228 in.close(); 229 return; 230 } 231 menu_name_width = get_string_width_zoom((const unsigned char*)menu_name.c_str(), 232 UI_FONT, 1.0); 233 234 // read each line after the menu name line, creating a menu Line object for each 235 std::string line; 236 while (getline(in, line)) 237 { 238 // lines starting with ## are ignored - like a comment 239 if ((!line.empty()) && (line.substr(0,2) != "##")) 240 lines.push_back(new CommandQueue::Line(line)); 241 } 242 in.close(); 243 244 // no lines, no menu 245 if (lines.empty()) 246 return; 247 248 // create a context menu for the menu 249 std::string menu_text; 250 for (size_t i=0; i<lines.size(); i++) 251 menu_text += lines[i]->get_text() + "\n"; 252 cm_menu_id = cm_create(menu_text.c_str(), Container::action_handler); 253 cm_set_pre_show_handler(cm_menu_id, Container::pre_show_handler); 254 255 } // end Menu() 256 257 258 // 259 // destruct a Menu, insuring the lines are deleted 260 // ~Menu(void)261 Menu::~Menu(void) 262 { 263 if (cm_valid(cm_menu_id)) 264 cm_destroy(cm_menu_id); 265 for (size_t i=0; i<lines.size(); i++) 266 delete lines[i]; 267 } 268 269 270 // 271 // constructor for Container, just initialises attributes 272 // Container(void)273 Container::Container(void) : win_id(-1), win_width(0), current_mouseover_menu(0), mouse_over_window(false), 274 reload_menus(false), context_id(CM_INIT_VALUE), window_used(false), title_on(1), background_on(1), 275 border_on(1), use_small_font(0), include_datadir(1), auto_include_datadir(true), just_echo(0), win_x_pos(100), 276 win_y_pos(100), name_sep(10), window_x_pad(8), window_y_pad(2), standard_window_position(STND_POS_NONE) 277 { 278 } 279 280 281 // 282 // destroy the window, menus and lines 283 // destroy(void)284 void Container::destroy(void) 285 { 286 delete_menus(); 287 if (cm_valid(context_id)) 288 cm_destroy(context_id); 289 context_id = CM_INIT_VALUE; 290 destroy_window(win_id); 291 } 292 293 294 // 295 // create the window for the user menus, or display it if already created 296 // open_window(void)297 void Container::open_window(void) 298 { 299 if (win_id >= 0) 300 { 301 show_window(win_id); 302 select_window(win_id); 303 return; 304 } 305 306 Uint32 win_flags = ELW_USE_UISCALE|ELW_SHOW_LAST|ELW_DRAGGABLE|ELW_SHOW|ELW_TITLE_NAME|ELW_ALPHA_BORDER|ELW_SWITCHABLE_OPAQUE; 307 308 set_win_flag(&win_flags, ELW_TITLE_BAR, title_on); 309 set_win_flag(&win_flags, ELW_USE_BACKGROUND, background_on); 310 set_win_flag(&win_flags, ELW_USE_BORDER, border_on); 311 window_used = true; 312 313 win_id = create_window(um_window_title_str, -1, 0, win_x_pos, win_y_pos, 0, 0, win_flags); 314 if (win_id < 0 || win_id >= windows_list.num_windows) 315 return; 316 317 set_window_handler(win_id, ELW_HANDLER_DISPLAY, (int (*)())&display_handler ); 318 set_window_handler(win_id, ELW_HANDLER_MOUSEOVER, (int (*)())&mouseover_handler ); 319 set_window_handler(win_id, ELW_HANDLER_CLICK, (int (*)())&click_handler ); 320 set_window_handler(win_id, ELW_HANDLER_UI_SCALE, (int (*)())&ui_scale_handler ); 321 set_window_handler(win_id, ELW_HANDLER_FONT_CHANGE, (int (*)())&font_change_handler); 322 323 // the build-in context menu is only available if we have a title so always recreate 324 325 if (cm_valid(windows_list.window[win_id].cm_id)) 326 { 327 cm_destroy(windows_list.window[win_id].cm_id); 328 windows_list.window[win_id].cm_id = CM_INIT_VALUE; 329 } 330 331 context_id = cm_create(cm_title_menu_str, NULL); 332 cm_bool_line(context_id, 1, &windows_list.window[win_id].opaque, NULL); 333 cm_bool_line(context_id, 2, &windows_on_top, "windows_on_top"); 334 cm_add(context_id, cm_user_menu_str, context_handler); 335 cm_add_window(context_id, win_id); 336 337 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_MOVEWIN, &title_on, NULL); 338 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_FIXPOS, &standard_window_position, NULL); 339 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_BACKGND, &background_on, NULL); 340 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_BORDER, &border_on, NULL); 341 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_FONT, &use_small_font, NULL); 342 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_STANDMENU, &include_datadir, NULL); 343 cm_bool_line(context_id, ELW_CM_MENU_LEN+CM_SHOWCMD, &just_echo, NULL); 344 345 ui_scale_changed(&windows_list.window[win_id]); 346 reload(&windows_list.window[win_id]); 347 348 } // Container::open_window() 349 350 351 // 352 // return current window position and option values, normally for saving in the cfg file 353 // get_options(int * win_x,int * win_y,int * options) const354 void Container::get_options(int *win_x, int *win_y, int *options) const 355 { 356 if(win_id >= 0) 357 { 358 *win_x = windows_list.window[win_id].cur_x; 359 *win_y = windows_list.window[win_id].cur_y; 360 } 361 else 362 { 363 *win_x = win_x_pos; 364 *win_y = win_y_pos; 365 } 366 *options = (window_used) ?1: 0; 367 *options |= title_on << 1; 368 *options |= border_on << 2; 369 *options |= use_small_font << 3; 370 *options |= include_datadir << 4; 371 *options |= (background_on ^ 1) << 5; // added later so will not get set for existing users but we want it on by default 372 *options |= standard_window_position << 6; // three bits 373 } 374 375 376 // 377 // set window position and option, normally from the cfg file 378 // set_options(int win_x,int win_y,int options)379 void Container::set_options(int win_x, int win_y, int options) 380 { 381 if ((window_used = options & 1)) 382 { 383 title_on = (options >> 1) & 1; 384 border_on = (options >> 2) & 1; 385 use_small_font = (options >> 3) & 1; 386 include_datadir = (options >> 4) & 1; 387 background_on = ((options >> 5) & 1) ^ 1; 388 standard_window_position = (options >> 6) & 7; // three bits 389 win_x_pos = win_x; 390 win_y_pos = win_y; 391 } 392 } 393 394 395 #ifdef JSON_FILES 396 // 397 // Write window position and option values, normally for saving in the cfg file 398 // write_options(const char * dict_name) const399 void Container::write_options(const char *dict_name) const 400 { 401 if(win_id >= 0) 402 { 403 json_cstate_set_int(dict_name, "pos_x", windows_list.window[win_id].cur_x); 404 json_cstate_set_int(dict_name, "pos_y", windows_list.window[win_id].cur_y); 405 } 406 else 407 { 408 json_cstate_set_int(dict_name, "pos_x", win_x_pos); 409 json_cstate_set_int(dict_name, "pos_y", win_y_pos); 410 } 411 json_cstate_set_bool(dict_name, "window_used", window_used); 412 json_cstate_set_bool(dict_name, "title_on", title_on); 413 json_cstate_set_bool(dict_name, "border_on", border_on); 414 json_cstate_set_bool(dict_name, "use_small_font", use_small_font); 415 json_cstate_set_bool(dict_name, "include_datadir", include_datadir); 416 json_cstate_set_bool(dict_name, "background_off", background_on ^ 1); 417 json_cstate_set_int(dict_name, "standard_window_position", standard_window_position); 418 } 419 420 421 // 422 // Read window position and option, normally from the cfg file 423 // read_options(const char * dict_name)424 void Container::read_options(const char *dict_name) 425 { 426 if ((window_used = json_cstate_get_bool(dict_name, "window_used", 0))) 427 { 428 title_on = json_cstate_get_bool(dict_name, "title_on", 0); 429 border_on = json_cstate_get_bool(dict_name, "border_on", 0); 430 use_small_font = json_cstate_get_bool(dict_name, "use_small_font", 0); 431 include_datadir = json_cstate_get_bool(dict_name, "include_datadir", 0); 432 background_on = json_cstate_get_bool(dict_name, "background_off", 0) ^ 1; 433 standard_window_position = json_cstate_get_int(dict_name, "standard_window_position", 0); 434 win_x_pos = json_cstate_get_int(dict_name, "pos_x", 0); 435 win_y_pos = json_cstate_get_int(dict_name, "pos_y", 0); 436 } 437 } 438 #endif 439 440 441 // 442 // the window display callback 443 // display(window_info * win)444 int Container::display(window_info *win) 445 { 446 // if the menus need reloading, try it now 447 if (reload_menus) 448 reload(win); 449 450 // update the command queue 451 command_queue.process(just_echo); 452 453 // a reload may have changed the window size 454 if ((win_width != win->len_x) || (get_height(win) != win->len_y)) 455 { 456 recalc_win_width(win); 457 resize_window (win->window_id, win_width, get_height(win)); 458 } 459 460 update_standard_window_position(win); 461 462 // enable the title bar if the window ends up off screen - resolution change perhaps 463 if ((standard_window_position == STND_POS_NONE) && ((win->cur_x + 20 > window_width) || (win->cur_y + 10 > window_height))) 464 set_title_state(win, true); 465 466 int curr_x = window_x_pad; 467 468 // if there are no menus, fill the window with a suitable message 469 if (menus.empty()) 470 { 471 if (mouse_over_window) 472 glColor3fv(gui_color); 473 else 474 glColor3fv(gui_dull_color); 475 if (use_small_font) 476 draw_string_small_zoomed(curr_x, window_y_pad, (const unsigned char *)um_no_menus_str, 1, win->current_scale); 477 else 478 draw_string_zoomed(curr_x, window_y_pad, (const unsigned char *)um_no_menus_str, 1, win->current_scale ); 479 mouse_over_window = false; 480 return 1; 481 } 482 483 // find the menu index of an already open menu window 484 size_t open_menu = menus.size(); 485 size_t open_cm = cm_window_shown(); 486 if (open_cm != CM_INIT_VALUE) 487 { 488 for (size_t i=0; i<menus.size(); i++) 489 if (menus[i]->get_cm_id() == open_cm) 490 open_menu = i; 491 } 492 493 // draw the menu name text, hightlighting if it is open or if the mouse is over it 494 for (size_t i=0; i<menus.size(); i++) 495 { 496 if ((current_mouseover_menu == i) || (open_menu == i)) 497 glColor3f(1.0f,1.0f,1.0f); 498 else if (mouse_over_window) 499 glColor3fv(gui_color); 500 else 501 glColor3fv(gui_dull_color); 502 if (use_small_font) 503 draw_string_small_zoomed(curr_x, window_y_pad, (const unsigned char *)menus[i]->get_name().c_str(), 1, win->current_scale); 504 else 505 draw_string_zoomed(curr_x, window_y_pad, (const unsigned char *)menus[i]->get_name().c_str(), 1, win->current_scale); 506 curr_x += calc_actual_width(win, menus[i]->get_name_width()) + name_sep; 507 } 508 509 // make sure next time we will not highlight a menu name if the mouse is not in the window 510 current_mouseover_menu = menus.size(); 511 mouse_over_window = false; 512 513 return 1; 514 515 } // end Container::display() 516 517 518 // 519 // open the menu if the name is clicked 520 // click(window_info * win,int mx,Uint32 flags)521 int Container::click(window_info *win, int mx, Uint32 flags) 522 { 523 if ((flags & ELW_LEFT_MOUSE) && 524 ((current_mouseover_menu = get_mouse_over_menu(win, mx)) < menus.size()) && 525 cm_valid(menus[current_mouseover_menu]->get_cm_id())) 526 { 527 cm_show_direct(menus[current_mouseover_menu]->get_cm_id(), win_id, current_mouseover_menu); 528 return 1; 529 } 530 return 0; 531 } 532 533 534 // 535 // Called when the UI is scaled 536 // ui_scale_changed(window_info * win)537 int Container::ui_scale_changed(window_info *win) 538 { 539 window_y_pad = static_cast<int>(0.5 + 2 * win->current_scale); 540 window_x_pad = 4 * window_y_pad; 541 name_sep = static_cast<int>(0.5 + 10 * win->current_scale); 542 recalc_win_width(win); 543 resize_window(win->window_id, win_width, get_height(win)); 544 return 1; 545 } 546 547 548 // 549 // Called when font settings are changed 550 // font_changed(window_info * win,eternal_lands::FontManager::Category cat)551 int Container::font_changed(window_info *win, eternal_lands::FontManager::Category cat) 552 { 553 if (cat != win->font_category) 554 return 0; 555 ui_scale_changed(win); 556 return 1; 557 } 558 559 560 // 561 // common callback fuction for context menu, line selection 562 // action(size_t active_menu,int option)563 int Container::action(size_t active_menu, int option) 564 { 565 if (active_menu < menus.size()) 566 menus[active_menu]->action(option, command_queue); 567 return 1; 568 } 569 570 571 // 572 // adjust the menu window position to be more menu like 573 // pre_show(window_info * win,int widget_id,int mx,int my,window_info * cm_win)574 void Container::pre_show(window_info *win, int widget_id, int mx, int my, window_info *cm_win) 575 { 576 size_t curr_menu = (size_t)widget_id; 577 if (win == NULL || cm_win == NULL || !(curr_menu<menus.size())) 578 { 579 std::cerr << __FUNCTION__ << ": Problem with input win=" << win << " widget=" << widget_id 580 << " mx=" << mx << " my=" << my << " cmwin=" << cm_win << std::endl; 581 return; 582 } 583 584 // get the menu name x offset in the menu window 585 int x_offset = window_x_pad; 586 for (size_t i=0; i<curr_menu && i<menus.size(); i++) 587 x_offset += calc_actual_width(win, menus[i]->get_name_width()) + name_sep; 588 589 // see what fits x: position under the menu name, or hard at the right or hard at the left 590 int new_x_pos = win->cur_x + x_offset; 591 if (new_x_pos + cm_win->len_x > window_width) 592 new_x_pos = window_width - cm_win->len_x; 593 if (new_x_pos < 0) 594 new_x_pos = 0; 595 596 // see what fits y: position below the window menu, or position above the window menu 597 int new_y_pos = win->cur_y; 598 if (new_y_pos + win->len_y + cm_win->len_y > window_height) 599 new_y_pos -= cm_win->len_y; 600 else 601 new_y_pos += win->len_y; 602 603 move_window(cm_win->window_id, -1, 0, new_x_pos, new_y_pos); 604 } 605 606 607 // 608 // the "evil" singleton mechanism 609 // get_instance(void)610 Container * Container::get_instance(void) 611 { 612 static Container um; 613 static SDL_threadID creation_thread = SDL_ThreadID(); 614 if (SDL_ThreadID() != creation_thread) 615 std::cerr << __FUNCTION__ << ": Danger W.R.! User menus call by non-creator thread." << std::endl; 616 return &um; 617 } 618 619 620 // 621 // load, or reload, the menu files 622 // reload(window_info * win)623 void Container::reload(window_info *win) 624 { 625 // if a context menu is currently showing, do not reload yet 626 if (cm_window_shown() != CM_INIT_VALUE) 627 { 628 reload_menus = true; 629 return; 630 } 631 reload_menus = false; 632 633 delete_menus(); 634 635 std::vector<std::string> filelist; 636 637 std::vector<std::string> search_paths; 638 if (include_datadir) 639 search_paths.push_back(std::string(datadir)); 640 search_paths.push_back(std::string(get_path_config())); 641 642 for (size_t i=0; i<search_paths.size(); i++) 643 { 644 std::string glob_path = search_paths[i] + "*.menu"; 645 // find all the menu files and build a list of path+filenames for later 646 #ifdef WINDOWS 647 struct _finddata_t c_file; 648 intptr_t hFile; 649 if ((hFile = _findfirst(glob_path.c_str(), &c_file)) != static_cast<intptr_t>(-1)) 650 { 651 do 652 filelist.push_back(search_paths[i] + std::string(c_file.name)); 653 while (_findnext(hFile, &c_file) == 0); 654 _findclose(hFile); 655 } 656 #else // phew! it's a real operating system 657 glob_t glob_res; 658 if (glob(glob_path.c_str(), 0, NULL, &glob_res)==0) 659 { 660 for (size_t i=0; i<glob_res.gl_pathc; i++) 661 filelist.push_back(std::string(glob_res.gl_pathv[i])); 662 globfree(&glob_res); 663 } 664 #endif 665 } 666 667 // process all the menu files, creating menu objects for each valid file 668 for (size_t i=0; i<filelist.size(); i++) 669 { 670 Menu *umb = new Menu(filelist[i]); 671 if (umb->get_name().empty()) 672 delete umb; 673 else 674 menus.push_back(umb); 675 } 676 677 current_mouseover_menu = menus.size(); 678 recalc_win_width(win); 679 680 // if there are no user menus but we're not trying standard menus... 681 // unless already controlled this session, enable standard menus and reload 682 if (menus.empty() && !include_datadir && auto_include_datadir) 683 { 684 include_datadir = 1; 685 reload(win); 686 } 687 688 } // end Container::reload() 689 690 691 // 692 // Calculates the window width, which changes with the zoom 693 // recalc_win_width(window_info * win)694 void Container::recalc_win_width(window_info *win) 695 { 696 // if there are no menus, use the size of the message for the window width 697 if (menus.empty()) 698 { 699 win_width = 2 * window_x_pad + calc_actual_width(win, get_string_width_zoom((const unsigned char*)um_no_menus_str, win->font_category, 1.0)); 700 return; 701 } 702 703 // otherwise, calculate the width from the widths of all the menus names 704 win_width = 2 * window_x_pad + name_sep * (menus.size() - 1); 705 for (size_t i=0; i<menus.size(); i++) 706 win_width += calc_actual_width(win, menus[i]->recalc_name_width(1.0)); 707 } 708 709 710 // 711 // If the mouse is over a menu name and there are no context windows 712 // open then return the menu index, otherwise return the number of menus. 713 // 714 // Side effects: 715 // If one of the menus is open and the mouse is over a different menu name, 716 // the current menu is closed and the one with the mouse over, opened. 717 // get_mouse_over_menu(window_info * win,int mx)718 size_t Container::get_mouse_over_menu(window_info *win, int mx) 719 { 720 // if the mouse is over a menu name, get the menus[] index 721 size_t mouse_over = menus.size(); 722 int name_end_x = window_x_pad; 723 for (size_t i=0; i<menus.size(); i++) 724 { 725 name_end_x += calc_actual_width(win, menus[i]->get_name_width()) + name_sep; 726 if (mx < name_end_x-name_sep/2) 727 { 728 mouse_over = i; 729 break; 730 } 731 } 732 733 // if not over a menu name 734 if (mouse_over == menus.size()) 735 return menus.size(); 736 737 // get the id of any open context menu, return with result of mouse index if none 738 size_t open_cm = cm_window_shown(); 739 if (open_cm == CM_INIT_VALUE) 740 return mouse_over; 741 742 // a context menu is open, if it is one of our menus and the mouse is over another 743 // close the current menu and open the one the mouse is over 744 for (size_t i=0; i<menus.size(); i++) 745 if (menus[i]->get_cm_id() == open_cm) 746 { 747 if (i != mouse_over) 748 { 749 cm_post_show_check(1); 750 cm_show_direct(menus[mouse_over]->get_cm_id(), win_id, mouse_over); 751 } 752 break; 753 } 754 755 return menus.size(); 756 } 757 758 759 // 760 // prepare for a menu reload or exit 761 // delete_menus(void)762 void Container::delete_menus(void) 763 { 764 for (size_t i=0; i<menus.size(); i++) 765 delete menus[i]; 766 menus.clear(); 767 } 768 769 770 // 771 // change a window property bit flag 772 // set_win_flag(Uint32 * flags,Uint32 flag,int state)773 void Container::set_win_flag(Uint32 *flags, Uint32 flag, int state) 774 { 775 if (state) 776 *flags |= flag; 777 else 778 *flags &= ~flag; 779 } 780 781 782 // 783 // Handle possitioning the window in one of the fixed locations. 784 // update_standard_window_position(window_info * win)785 void Container::update_standard_window_position(window_info *win) 786 { 787 if (title_on) 788 { 789 if (standard_window_position != STND_POS_NONE) 790 standard_window_position = STND_POS_NONE; 791 return; 792 } 793 794 int wanted_x = 0, wanted_y = 0, tmp_end = 0; 795 796 switch (standard_window_position) 797 { 798 case STND_POS_TOP_LEFT: 799 wanted_x = get_tabbed_chat_end_x() + window_x_pad; 800 wanted_y = 0; 801 break; 802 case STND_POS_TOP_CENTRE: 803 tmp_end = get_tabbed_chat_end_x(); 804 wanted_x = tmp_end + ( window_width - hud_x - get_fps_default_width() - tmp_end - win->len_x) / 2; 805 wanted_y = 0; 806 break; 807 case STND_POS_TOP_RIGHT: 808 wanted_x = window_width - hud_x - get_fps_default_width() - win->len_x - window_x_pad; 809 wanted_y = 0; 810 break; 811 case STND_POS_BOTTOM_LEFT: 812 wanted_x = window_x_pad + get_icons_win_active_len(); 813 wanted_y = window_height - win->len_y; 814 break; 815 case STND_POS_BOTTOM_CENTRE: 816 tmp_end = get_icons_win_active_len(); 817 wanted_x = tmp_end + (window_width - hud_x - get_hud_indicators_default_width() - tmp_end - win->len_x) / 2; 818 wanted_y = window_height - win->len_y; 819 break; 820 case STND_POS_BOTTON_RIGHT: 821 wanted_x = window_width - hud_x - get_hud_indicators_default_width() - win->len_x - window_x_pad; 822 wanted_y = window_height - win->len_y; 823 break; 824 default: 825 return; 826 } 827 828 if ((win->cur_x != wanted_x) || (win->cur_y != wanted_y)) 829 { 830 move_window(win->window_id, -1, 0, wanted_x, wanted_y); 831 if ((win->cur_x != wanted_x) || (win->cur_y != wanted_y)) 832 { 833 set_title_state(win, true); 834 standard_window_position = STND_POS_NONE; 835 } 836 } 837 } 838 839 840 // 841 // handler window content menu options 842 // context(window_info * win,int widget_id,int mx,int my,int option)843 int Container::context(window_info *win, int widget_id, int mx, int my, int option) 844 { 845 if (option<ELW_CM_MENU_LEN) 846 return cm_title_handler(win, widget_id, mx, my, option); 847 848 switch (option) 849 { 850 case ELW_CM_MENU_LEN+CM_MOVEWIN: 851 { 852 set_win_flag(&win->flags, ELW_TITLE_BAR, title_on); 853 if (win->cur_y == win->title_height) 854 move_window(win->window_id, -1, 0, win->cur_x, 0); 855 else if (win->cur_y == 0) 856 move_window(win->window_id, -1, 0, win->cur_x, win->title_height); 857 break; 858 } 859 case ELW_CM_MENU_LEN+CM_FIXPOS: 860 if (standard_window_position != STND_POS_NONE) 861 { 862 set_title_state(win, false); 863 update_standard_window_position(win); 864 } 865 break; 866 case ELW_CM_MENU_LEN+CM_CHANGEPOS: 867 set_title_state(win, false); 868 if (++standard_window_position >= STND_POS_LAST) 869 standard_window_position = STND_POS_TOP_LEFT; 870 break; 871 case ELW_CM_MENU_LEN+CM_BACKGND: set_win_flag(&win->flags, ELW_USE_BACKGROUND, background_on); break; 872 case ELW_CM_MENU_LEN+CM_BORDER: set_win_flag(&win->flags, ELW_USE_BORDER, border_on); break; 873 case ELW_CM_MENU_LEN+CM_FONT: recalc_win_width(win); break; 874 case ELW_CM_MENU_LEN+CM_STANDMENU: auto_include_datadir = false; reload(win); break; 875 case ELW_CM_MENU_LEN+CM_RELOAD: reload(win); break; 876 case ELW_CM_MENU_LEN+CM_DISABLE: toggle_user_menus(&enable_user_menus); break; 877 } 878 879 return 1; 880 } 881 882 } // end UserMenus namespace 883 884 885 // 886 // External Interface functions 887 // 888 extern "C" 889 { 890 int enable_user_menus = 0; 891 int ready_for_user_menus = 0; 892 set_options_user_menus(int win_x,int win_y,int options)893 void set_options_user_menus(int win_x, int win_y, int options) 894 { 895 UserMenus::Container::get_instance()->set_options(win_x, win_y, options); 896 } 897 get_options_user_menus(int * win_x,int * win_y,int * options)898 void get_options_user_menus(int *win_x, int *win_y, int *options) 899 { 900 UserMenus::Container::get_instance()->get_options(win_x, win_y, options); 901 } 902 903 #ifdef JSON_FILES read_options_user_menus(const char * dict_name)904 void read_options_user_menus(const char *dict_name) 905 { 906 UserMenus::Container::get_instance()->read_options(dict_name); 907 } 908 write_options_user_menus(const char * dict_name)909 void write_options_user_menus(const char *dict_name) 910 { 911 UserMenus::Container::get_instance()->write_options(dict_name); 912 } 913 #endif 914 toggle_user_menus(int * enable)915 void toggle_user_menus(int *enable) 916 { 917 *enable = !*enable; 918 set_var_unsaved("enable_user_menus", INI_FILE_VAR); 919 if (!ready_for_user_menus) 920 return; 921 if (*enable) 922 UserMenus::Container::get_instance()->open_window(); 923 else 924 UserMenus::Container::get_instance()->close_window(); 925 } 926 display_user_menus(void)927 void display_user_menus(void) 928 { 929 if (ready_for_user_menus) 930 UserMenus::Container::get_instance()->open_window(); 931 } 932 destroy_user_menus(void)933 void destroy_user_menus(void) { UserMenus::Container::get_instance()->destroy(); } 934 } 935