Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Portions of this file are copyright Rebirth contributors and licensed as
  3.  * described in COPYING.txt.
  4.  * Portions of this file are copyright Parallax Software and licensed
  5.  * according to the Parallax license below.
  6.  * See COPYING.txt for license details.
  7.  
  8. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
  9. SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
  10. END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
  11. ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
  12. IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
  13. SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
  14. FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
  15. CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
  16. AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
  17. COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
  18. */
  19.  
  20. /*
  21.  *
  22.  * Routines for menus.
  23.  *
  24.  */
  25.  
  26. #include <stdio.h>
  27. #include <cstdlib>
  28. #include <string.h>
  29. #include <stdarg.h>
  30. #include <ctype.h>
  31. #include <functional>
  32.  
  33. #include "pstypes.h"
  34. #include "dxxerror.h"
  35. #include "gr.h"
  36. #include "grdef.h"
  37. #include "window.h"
  38. #include "songs.h"
  39. #include "key.h"
  40. #include "mouse.h"
  41. #include "palette.h"
  42. #include "game.h"
  43. #include "text.h"
  44. #include "menu.h"
  45. #include "newmenu.h"
  46. #include "gamefont.h"
  47. #include "iff.h"
  48. #include "pcx.h"
  49. #include "u_mem.h"
  50. #include "mouse.h"
  51. #include "joy.h"
  52. #include "digi.h"
  53. #include "multi.h"
  54. #include "endlevel.h"
  55. #include "screens.h"
  56. #include "config.h"
  57. #include "player.h"
  58. #include "state.h"
  59. #include "newdemo.h"
  60. #include "kconfig.h"
  61. #include "strutil.h"
  62. #include "vers_id.h"
  63. #include "timer.h"
  64. #include "playsave.h"
  65. #include "automap.h"
  66. #include "rbaudio.h"
  67. #include "args.h"
  68. #if defined(DXX_BUILD_DESCENT_II)
  69. #include "args.h"
  70. #include "gamepal.h"
  71. #endif
  72.  
  73. #if DXX_USE_OGL
  74. #include "ogl_init.h"
  75. #endif
  76.  
  77. #include "compiler-range_for.h"
  78. #include "partial_range.h"
  79.  
  80. #define MAXDISPLAYABLEITEMS 14
  81. #define MAXDISPLAYABLEITEMSTINY 21
  82. #define MESSAGEBOX_TEXT_SIZE 2176  // How many characters in messagebox
  83. #define MAX_TEXT_WIDTH FSPACX(120) // How many pixels wide a input box can be
  84.  
  85. struct newmenu : embed_window_pointer_t
  86. {
  87.         int                             x,y,w,h;
  88.         short                   swidth, sheight;
  89.         // with these we check if resolution or fonts have changed so menu structure can be recreated
  90.         font_x_scale_proportion fntscalex;
  91.         font_y_scale_proportion fntscaley;
  92.         const char                      *title;
  93.         const char                      *subtitle;
  94.         unsigned                nitems;
  95.         int                             citem;
  96.         newmenu_item    *items;
  97.         int                             (*subfunction)(newmenu *menu,const d_event &event, void *userdata);
  98.         const char                      *filename;
  99.         int                             tiny_mode;
  100.         int                     tabs_flag;
  101.         int                             scroll_offset, max_displayable;
  102.         int                             all_text;               //set true if all text items
  103.         int                             is_scroll_box;   // Is this a scrolling box? Set to false at init
  104.         int                             max_on_menu;
  105.         int                             mouse_state, dblclick_flag;
  106.         int                             *rval;                  // Pointer to return value (for polling newmenus)
  107.         void                    *userdata;              // For whatever - like with window system
  108.         partial_range_t<newmenu_item *> item_range()
  109.         {
  110.                 return unchecked_partial_range(items, nitems);
  111.         }
  112. };
  113.  
  114. constexpr std::integral_constant<unsigned, NM_TYPE_INPUT> newmenu_item::input_specific_type::nm_type;
  115. constexpr std::integral_constant<unsigned, NM_TYPE_RADIO> newmenu_item::radio_specific_type::nm_type;
  116. constexpr std::integral_constant<unsigned, NM_TYPE_NUMBER> newmenu_item::number_specific_type::nm_type;
  117. constexpr std::integral_constant<unsigned, NM_TYPE_INPUT_MENU> newmenu_item::imenu_specific_type::nm_type;
  118. constexpr std::integral_constant<unsigned, NM_TYPE_SLIDER> newmenu_item::slider_specific_type::nm_type;
  119.  
  120. static grs_main_bitmap nm_background, nm_background1;
  121. static grs_subbitmap_ptr nm_background_sub;
  122.  
  123. void newmenu_free_background()  {
  124.         if (nm_background.bm_data)
  125.         {
  126.                 nm_background_sub.reset();
  127.         }
  128.         nm_background.reset();
  129.         nm_background1.reset();
  130. }
  131.  
  132. namespace dsx {
  133.  
  134. #if defined(DXX_BUILD_DESCENT_I)
  135. static const char *UP_ARROW_MARKER(const grs_font &, const grs_font &)
  136. {
  137.         return "+";  // 135
  138. }
  139.  
  140. static const char *DOWN_ARROW_MARKER(const grs_font &, const grs_font &)
  141. {
  142.         return "+";  // 136
  143. }
  144. #elif defined(DXX_BUILD_DESCENT_II)
  145. static const char *UP_ARROW_MARKER(const grs_font &cv_font, const grs_font &game_font)
  146. {
  147.         return &cv_font == &game_font ? "\202" : "\207";  // 135
  148. }
  149.  
  150. static const char *DOWN_ARROW_MARKER(const grs_font &cv_font, const grs_font &game_font)
  151. {
  152.         return &cv_font == &game_font ? "\200" : "\210";  // 136
  153. }
  154. #endif
  155.  
  156. // Draws the custom menu background pcx, if available
  157. static void nm_draw_background1(grs_canvas &canvas, const char * filename)
  158. {
  159.         if (filename != NULL)
  160.         {
  161.                 if (nm_background1.bm_data == NULL)
  162.                 {
  163.                         const auto pcx_error = pcx_read_bitmap(filename, nm_background1, gr_palette);
  164.                         if (pcx_error != pcx_result::SUCCESS)
  165.                                 return;
  166.                 }
  167.                 gr_palette_load( gr_palette );
  168.                 show_fullscr(canvas, nm_background1);
  169.         }
  170. #if defined(DXX_BUILD_DESCENT_II)
  171.         strcpy(last_palette_loaded,"");         //force palette load next time
  172. #endif
  173. }
  174. }
  175.  
  176. #define MENU_BACKGROUND_BITMAP_HIRES (PHYSFSX_exists("scoresb.pcx",1)?"scoresb.pcx":"scores.pcx")
  177. #define MENU_BACKGROUND_BITMAP_LORES (PHYSFSX_exists("scores.pcx",1)?"scores.pcx":"scoresb.pcx")
  178. #define MENU_BACKGROUND_BITMAP (HIRESMODE?MENU_BACKGROUND_BITMAP_HIRES:MENU_BACKGROUND_BITMAP_LORES)
  179.  
  180. // Draws the frame background for menus
  181. void nm_draw_background(grs_canvas &canvas, int x1, int y1, int x2, int y2)
  182. {
  183.         int w,h,init_sub=0;
  184.         static float BGScaleX=1,BGScaleY=1;
  185.         if (nm_background.bm_data == NULL)
  186.         {
  187.                 palette_array_t background_palette;
  188.                 const auto pcx_error = pcx_read_bitmap(MENU_BACKGROUND_BITMAP, nm_background,background_palette);
  189.                 if (pcx_error != pcx_result::SUCCESS)
  190.                         return;
  191.                 gr_remap_bitmap_good(nm_background, background_palette, -1, -1);
  192.                 BGScaleX=(static_cast<float>(SWIDTH)/nm_background.bm_w);
  193.                 BGScaleY=(static_cast<float>(SHEIGHT)/nm_background.bm_h);
  194.                 init_sub=1;
  195.         }
  196.  
  197.         if ( x1 < 0 ) x1 = 0;
  198.         if ( y1 < 0 ) y1 = 0;
  199.         if ( x2 > SWIDTH - 1) x2 = SWIDTH - 1;
  200.         if ( y2 > SHEIGHT - 1) y2 = SHEIGHT - 1;
  201.  
  202.         w = x2-x1;
  203.         h = y2-y1;
  204.  
  205.         if (w > SWIDTH) w = SWIDTH;
  206.         if (h > SHEIGHT) h = SHEIGHT;
  207.  
  208.         gr_palette_load( gr_palette );
  209.         {
  210.                 const auto &&tmp = gr_create_sub_canvas(canvas, x1, y1, w, h);
  211.                 show_fullscr(*tmp, nm_background); // show so we load all necessary data for the sub-bitmap
  212.         if (!init_sub && ((nm_background_sub->bm_w != w*((static_cast<float>(nm_background.bm_w))/SWIDTH)) || (nm_background_sub->bm_h != h*((static_cast<float>(nm_background.bm_h))/SHEIGHT))))
  213.         {
  214.                 init_sub=1;
  215.         }
  216.         if (init_sub)
  217.                 nm_background_sub = gr_create_sub_bitmap(nm_background,0,0,w*((static_cast<float>(nm_background.bm_w))/SWIDTH),h*((static_cast<float>(nm_background.bm_h))/SHEIGHT));
  218.         show_fullscr(*tmp, *nm_background_sub.get());
  219.         }
  220.  
  221.         gr_settransblend(canvas, 14, gr_blend::normal);
  222.         {
  223.                 const auto color = BM_XRGB(1, 1, 1);
  224.         for (w=5*BGScaleX;w>0;w--)
  225.                         gr_urect(canvas, x2 - w, y1 + w * (BGScaleY / BGScaleX), x2 - w, y2 - w * (BGScaleY / BGScaleX), color);//right edge
  226.         }
  227.         {
  228.                 const auto color = BM_XRGB(0, 0, 0);
  229.         for (h=5*BGScaleY;h>0;h--)
  230.                         gr_urect(canvas, x1 + h * (BGScaleX / BGScaleY), y2 - h, x2 - h * (BGScaleX / BGScaleY), y2 - h, color);//bottom edge
  231.         }
  232.         gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
  233. }
  234.  
  235. // Draw a left justfied string
  236. static void nm_string(grs_canvas &canvas, const int w1, int x, const int y, const char *const s, const int tabs_flag)
  237. {
  238.         if (!tabs_flag)
  239.         {
  240.                 const char *s1 = s;
  241.                 const char *p = nullptr;
  242.                 RAIIdmem<char[]> s2;
  243.                 if (w1 > 0 && (p = strchr(s, '\t')))
  244.                 {
  245.                         s2.reset(d_strdup(s));
  246.                         s1 = s2.get();
  247.                         *std::next(s2.get(), std::distance(s, p)) = '\0';
  248.                 }
  249.                 gr_string(canvas, *canvas.cv_font, x, y, s1);
  250.                 if (p)
  251.                 {
  252.                         int w, h;
  253.                         ++ p;
  254.                         gr_get_string_size(*canvas.cv_font, p, &w, &h, nullptr);
  255.                         gr_string(canvas, *canvas.cv_font, x + w1 - w, y, p, w, h);
  256.                 }
  257.                 return;
  258.         }
  259.         std::array<int, 6> XTabs = {{18, 90, 127, 165, 231, 256}};
  260.         const auto &&fspacx = FSPACX();
  261.         range_for (auto &i, XTabs)
  262.         {
  263.                 i = fspacx(i) + x;
  264.         }
  265.         unsigned t = 0;
  266.         char measure[2];
  267.         measure[1] = 0;
  268.         for (unsigned i = 0; const char c = s[i]; ++i)
  269.         {
  270.                 if (c == '\t')
  271.                 {
  272.                         x=XTabs[t];
  273.                         t++;
  274.                         continue;
  275.                 }
  276.                 measure[0] = c;
  277.                 int tx, th;
  278.                 gr_get_string_size(*canvas.cv_font, measure, &tx, &th, nullptr);
  279.                 gr_string(canvas, *canvas.cv_font, x, y, measure, tx, th);
  280.                 x+=tx;
  281.         }
  282. }
  283.  
  284. // Draw a slider and it's string
  285. static void nm_string_slider(grs_canvas &canvas, const int w1, const int x, const int y, char *const s)
  286. {
  287.         char *p,*s1;
  288.  
  289.         s1=NULL;
  290.  
  291.         p = strchr( s, '\t' );
  292.         if (p)  {
  293.                 *p = '\0';
  294.                 s1 = p+1;
  295.         }
  296.  
  297.         gr_string(canvas, *canvas.cv_font, x, y, s);
  298.  
  299.         if (p)  {
  300.                 int w, h;
  301.                 gr_get_string_size(*canvas.cv_font, s1, &w, &h, nullptr);
  302.                 gr_string(canvas, *canvas.cv_font, x + w1 - w, y, s1, w, h);
  303.  
  304.                 *p = '\t';
  305.         }
  306. }
  307.  
  308.  
  309. // Draw a left justfied string with black background.
  310. static void nm_string_black(grs_canvas &canvas, int w1, const int x, const int y, const char *const s)
  311. {
  312.         int w,h;
  313.         gr_get_string_size(*canvas.cv_font, s, &w, &h, nullptr);
  314.  
  315.         if (w1 == 0) w1 = w;
  316.  
  317.         const auto &&fspacx = FSPACX();
  318.         const auto &&fspacy = FSPACY();
  319.         {
  320.                 const uint8_t color = BM_XRGB(5, 5, 5);
  321.                 gr_rect(canvas, x - fspacx(2), y - fspacy(1), x + w1, y + h, color);
  322.         }
  323.         {
  324.                 const uint8_t color = BM_XRGB(2, 2, 2);
  325.                 gr_rect(canvas, x - fspacx(2), y - fspacy(1), x, y + h, color);
  326.         }
  327.         {
  328.                 const uint8_t color = BM_XRGB(0, 0, 0);
  329.                 gr_rect(canvas, x - fspacx(1), y - fspacy(1), x + w1 - fspacx(1), y + h, color);
  330.         }
  331.         gr_string(canvas, *canvas.cv_font, x, y, s, w, h);
  332. }
  333.  
  334.  
  335. // Draw a right justfied string
  336. static void nm_rstring(grs_canvas &canvas, int w1, int x, const int y, const char *const s)
  337. {
  338.         int w, h;
  339.         gr_get_string_size(*canvas.cv_font, s, &w, &h, nullptr);
  340.         x -= FSPACX(3);
  341.  
  342.         if (w1 == 0) w1 = w;
  343.         gr_string(canvas, *canvas.cv_font, x - w, y, s, w, h);
  344. }
  345.  
  346. static void nm_string_inputbox(grs_canvas &canvas, const int w, const int x, const int y, const char *text, const int current)
  347. {
  348.         int w1;
  349.  
  350.         // even with variable char widths and a box that goes over the whole screen, we maybe never get more than 75 chars on the line
  351.         if (strlen(text)>75)
  352.                 text+=strlen(text)-75;
  353.         while( *text )  {
  354.                 gr_get_string_size(*canvas.cv_font, text, &w1, nullptr, nullptr);
  355.                 if ( w1 > w-FSPACX(10) )
  356.                         text++;
  357.                 else
  358.                         break;
  359.         }
  360.         if ( *text == 0 )
  361.                 w1 = 0;
  362.  
  363.         nm_string_black(canvas, w, x, y, text);
  364.  
  365.         if ( current && timer_query() & 0x8000 )
  366.                 gr_string(canvas, *canvas.cv_font, x + w1, y, CURSOR_STRING);
  367. }
  368.  
  369. static void draw_item(grs_canvas &canvas, newmenu_item *item, int is_current, int tiny, int tabs_flag, int scroll_offset)
  370. {
  371.         if (tiny)
  372.         {
  373.                 int r, g, b;
  374.                 if (item->text[0]=='\t')
  375.                         r = g = b = 63;
  376.                 else if (is_current)
  377.                         r = 57, g = 49, b = 20;
  378.                 else
  379.                         r = g = 29, b = 47;
  380.                 gr_set_fontcolor(canvas, gr_find_closest_color_current(r, g, b), -1);
  381.         }
  382.         else
  383.         {
  384.                 gr_set_curfont(canvas, is_current?MEDIUM2_FONT:MEDIUM1_FONT);
  385.         }
  386.  
  387.         const int line_spacing = static_cast<int>(LINE_SPACING(*canvas.cv_font, *GAME_FONT));
  388.         switch( item->type )    {
  389.                 case NM_TYPE_SLIDER:
  390.                 {
  391.                         int i;
  392.                         auto &slider = item->slider();
  393.                         if (item->value < slider.min_value)
  394.                                 item->value = slider.min_value;
  395.                         if (item->value > slider.max_value)
  396.                                 item->value = slider.max_value;
  397.                         i = snprintf(item->saved_text.data(), item->saved_text.size(), "%s\t%s", item->text, SLIDER_LEFT);
  398.                         for (uint_fast32_t j = (slider.max_value - slider.min_value + 1); j--;)
  399.                         {
  400.                                 i += snprintf(item->saved_text.data() + i, item->saved_text.size() - i, "%s", SLIDER_MIDDLE);
  401.                         }
  402.                         i += snprintf(item->saved_text.data() + i, item->saved_text.size() - i, "%s", SLIDER_RIGHT);
  403.                         item->saved_text[item->value+1+strlen(item->text)+1] = SLIDER_MARKER[0];
  404.                         nm_string_slider(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->saved_text.data());
  405.                 }
  406.                         break;
  407.                 case NM_TYPE_INPUT_MENU:
  408.                         if (item->imenu().group == 0)
  409.                         {
  410.                         case NM_TYPE_TEXT:
  411.                         case NM_TYPE_MENU:
  412.                                 nm_string(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->text, tabs_flag);
  413.                                 break;
  414.                         }
  415.                         DXX_BOOST_FALLTHROUGH;
  416.                 case NM_TYPE_INPUT:
  417.                         nm_string_inputbox(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->text, is_current);
  418.                         break;
  419.                 case NM_TYPE_CHECK:
  420.                         nm_string(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->text, tabs_flag);
  421.                         nm_rstring(canvas, item->right_offset, item->x, item->y - (line_spacing * scroll_offset), item->value ? CHECKED_CHECK_BOX : NORMAL_CHECK_BOX);
  422.                         break;
  423.                 case NM_TYPE_RADIO:
  424.                         nm_string(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->text, tabs_flag);
  425.                         nm_rstring(canvas, item->right_offset, item->x, item->y - (line_spacing * scroll_offset), item->value ? CHECKED_RADIO_BOX : NORMAL_RADIO_BOX);
  426.                         break;
  427.                 case NM_TYPE_NUMBER:
  428.                 {
  429.                         char text[sizeof("-2147483647")];
  430.                         auto &number = item->number();
  431.                         if (item->value < number.min_value)
  432.                                 item->value = number.min_value;
  433.                         if (item->value > number.max_value)
  434.                                 item->value = number.max_value;
  435.                         nm_string(canvas, item->w, item->x, item->y - (line_spacing * scroll_offset), item->text, tabs_flag);
  436.                         snprintf(text, sizeof(text), "%d", item->value );
  437.                         nm_rstring(canvas, item->right_offset, item->x, item->y - (line_spacing * scroll_offset), text);
  438.                 }
  439.                         break;
  440.         }
  441. }
  442.  
  443. const char *Newmenu_allowed_chars=NULL;
  444.  
  445. //returns true if char is allowed
  446. static bool char_disallowed(char c)
  447. {
  448.         const char *p = Newmenu_allowed_chars;
  449.         if (!p)
  450.                 return false;
  451.         for (uint8_t a, b; (a = p[0]) && (b = p[1]); p += 2)
  452.         {
  453.                 if (likely(c >= a && c <= b))
  454.                         return false;
  455.         }
  456.         return true;
  457. }
  458.  
  459. static bool char_allowed(char c)
  460. {
  461.         return !char_disallowed(c);
  462. }
  463.  
  464. static void strip_end_whitespace( char * text )
  465. {
  466.         char *ns = text;
  467.         for (char c; (c = *text);)
  468.         {
  469.                 ++ text;
  470.                 if (!isspace(static_cast<unsigned>(c)))
  471.                         ns = text;
  472.         }
  473.         *ns = 0;
  474. }
  475.  
  476. int newmenu_do2(const char *const title, const char *const subtitle, const uint_fast32_t nitems, newmenu_item *const item, const newmenu_subfunction subfunction, void *const userdata, const int citem, const char *const filename)
  477. {
  478.         newmenu *menu;
  479.         bool exists = true;
  480.         int rval = -1;
  481.  
  482.         menu = newmenu_do3( title, subtitle, nitems, item, subfunction, userdata, citem, filename );
  483.  
  484.         if (!menu)
  485.                 return -1;
  486.         menu->rval = &rval;
  487.  
  488.         // Track to see when the window is freed
  489.         // Doing this way in case another window is opened on top without its own polling loop
  490.         menu->wind->track(&exists);
  491.  
  492.         // newmenu_do2 and simpler get their own event loop
  493.         // This is so the caller doesn't have to provide a callback that responds to EVENT_NEWMENU_SELECTED
  494.         while (exists)
  495.                 event_process();
  496.  
  497.         return rval;
  498. }
  499.  
  500. static void swap_menu_item_entries(newmenu_item &a, newmenu_item &b)
  501. {
  502.         using std::swap;
  503.         swap(a.text, b.text);
  504.         swap(a.value, b.value);
  505. }
  506.  
  507. template <typename T>
  508. static void move_menu_item_entry(T &&t, newmenu_item *const items, uint_fast32_t citem, uint_fast32_t boundary)
  509. {
  510.         if (citem == boundary)
  511.                 return;
  512.         auto a = &items[citem];
  513.         auto selected = std::make_pair(a->text, a->value);
  514.         for (; citem != boundary;)
  515.         {
  516.                 citem = t(citem, 1);
  517.                 auto &b = items[citem];
  518.                 a->text = b.text;
  519.                 a->value = b.value;
  520.                 a = &b;
  521.         }
  522.         a->text = selected.first;
  523.         a->value = selected.second;
  524. }
  525.  
  526. static int newmenu_save_selection_key(newmenu *menu, const d_event &event)
  527. {
  528.         auto k = event_key_get(event);
  529.         switch(k)
  530.         {
  531.                 case KEY_SHIFTED+KEY_UP:
  532.                         if (menu->citem > 0)
  533.                         {
  534.                                 auto &a = menu->items[menu->citem];
  535.                                 auto &b = menu->items[-- menu->citem];
  536.                                 swap_menu_item_entries(a, b);
  537.                         }
  538.                         break;
  539.                 case KEY_SHIFTED+KEY_DOWN:
  540.                         if (menu->citem < (menu->nitems - 1))
  541.                         {
  542.                                 auto &a = menu->items[menu->citem];
  543.                                 auto &b = menu->items[++ menu->citem];
  544.                                 swap_menu_item_entries(a, b);
  545.                         }
  546.                         break;
  547.                 case KEY_PAGEUP + KEY_SHIFTED:
  548.                         move_menu_item_entry(std::minus<uint_fast32_t>(), menu->items, menu->citem, 0);
  549.                         break;
  550.                 case KEY_PAGEDOWN + KEY_SHIFTED:
  551.                         move_menu_item_entry(std::plus<uint_fast32_t>(), menu->items, menu->citem, menu->nitems - 1);
  552.                         break;
  553.         }
  554.         return 0;
  555. }
  556.  
  557. static int newmenu_save_selection_handler(newmenu *menu, const d_event &event, const unused_newmenu_userdata_t *)
  558. {
  559.         switch(event.type)
  560.         {
  561.                 case EVENT_KEY_COMMAND:
  562.                         return newmenu_save_selection_key(menu, event);
  563.                 default:
  564.                         break;
  565.         }
  566.         return 0;
  567. }
  568.  
  569. // Basically the same as do2 but sets reorderitems flag for weapon priority menu a bit redundant to get lose of a global variable but oh well...
  570. void newmenu_doreorder( const char * title, const char * subtitle, uint_fast32_t nitems, newmenu_item * item)
  571. {
  572.         newmenu_do2(title, subtitle, nitems, item, newmenu_save_selection_handler, unused_newmenu_userdata, 0, nullptr);
  573. }
  574.  
  575. newmenu_item *newmenu_get_items(newmenu *menu)
  576. {
  577.         return menu->items;
  578. }
  579.  
  580. int newmenu_get_nitems(newmenu *menu)
  581. {
  582.         return menu->nitems;
  583. }
  584.  
  585. int newmenu_get_citem(newmenu *menu)
  586. {
  587.         return menu->citem;
  588. }
  589.  
  590. namespace {
  591.  
  592. struct step_down
  593. {
  594.         template <typename T>
  595.                 T operator()(T &t) const
  596.                 {
  597.                         return ++t;
  598.                 }
  599. };
  600.  
  601. struct step_up
  602. {
  603.         template <typename T>
  604.                 T operator()(T &t) const
  605.                 {
  606.                         return --t;
  607.                 }
  608. };
  609.  
  610. }
  611.  
  612. template <typename S, typename O>
  613. static void update_menu_position(newmenu &menu, newmenu_item *const stop, int_fast32_t amount, S step, O overflow)
  614. {
  615.         auto icitem = menu.citem;
  616.         auto pcitem = &menu.items[icitem];
  617.         do // count until we reached a non NM_TYPE_TEXT item and reached our amount
  618.         {
  619.                 if (pcitem == stop)
  620.                         break;
  621.                 step(icitem);
  622.                 step(pcitem);
  623.                 if (menu.is_scroll_box) // update scroll_offset as we go
  624.                 {
  625.                         if (overflow(icitem))
  626.                                 step(menu.scroll_offset);
  627.                 }
  628.         } while (-- amount > 0 || pcitem->type == NM_TYPE_TEXT);
  629.         menu.citem = icitem;
  630. }
  631.  
  632. static void newmenu_scroll(newmenu *const menu, const int amount)
  633. {
  634.         if (amount == 0) // nothing to do for us
  635.                 return;
  636.  
  637.         if (menu->all_text)
  638.         {
  639.                 menu->scroll_offset += amount;
  640.                 if (menu->scroll_offset < 0)
  641.                         menu->scroll_offset = 0;
  642.                 if (menu->max_on_menu+menu->scroll_offset > menu->nitems)
  643.                         menu->scroll_offset = menu->nitems-menu->max_on_menu;
  644.                 return;
  645.         }
  646.         const auto &range = menu->item_range();
  647.         const auto predicate = [](const newmenu_item &n) {
  648.                 return n.type != NM_TYPE_TEXT;
  649.         };
  650.         const auto first = std::find_if(range.begin(), range.end(), predicate);
  651.         if (first == range.end())
  652.                 return;
  653.         const auto rlast = std::find_if(range.rbegin(), std::reverse_iterator<newmenu_item *>(first), predicate).base();
  654.         /* `first == rlast` should not happen, since that would mean that
  655.          * there are no elements in `range` for which `predicate` is true.
  656.          * If there are no such elements, then `first == range.end()` should
  657.          * be true and the function would have returned above.
  658.          */
  659.         assert(first != rlast);
  660.         if (first == rlast) // nothing to do for us
  661.                 return;
  662.         const auto last = std::prev(rlast);
  663.         /* Exactly one element that satisfies `predicate` exists in `range`.
  664.          * Only elements that satisfy `predicate` can be selected, so the
  665.          * selection cannot be changed.
  666.          */
  667.         if (first == last)
  668.                 return;
  669.         auto citem = &menu->items[menu->citem];
  670.         if (citem == last && amount == 1) // if citem == last item and we want to go down one step, go to first item
  671.         {
  672.                 newmenu_scroll(menu, -menu->nitems);
  673.                 return;
  674.         }
  675.         if (citem == first && amount == -1) // if citem == first item and we want to go up one step, go to last item
  676.         {
  677.                 newmenu_scroll(menu, menu->nitems);
  678.                 return;
  679.         }
  680.  
  681.         if (amount > 0) // down the list
  682.         {
  683.                 const auto overflow = [menu](int icitem) {
  684.                         return icitem + 4 >= menu->max_on_menu + menu->scroll_offset && menu->scroll_offset < menu->nitems - menu->max_on_menu;
  685.                 };
  686.                 update_menu_position(*menu, last, amount, step_down(), overflow);
  687.         }
  688.         else if (amount < 0) // up the list
  689.         {
  690.                 const auto overflow = [menu](int icitem) {
  691.                         return icitem - 4 < menu->scroll_offset && menu->scroll_offset > 0;
  692.                 };
  693.                 update_menu_position(*menu, first, std::abs(amount), step_up(), overflow);
  694.         }
  695. }
  696.  
  697. static int nm_trigger_radio_button(newmenu &menu, newmenu_item &citem)
  698. {
  699.         citem.value = 1;
  700.         const auto cg = citem.radio().group;
  701.         range_for (auto &r, menu.item_range())
  702.         {
  703.                 if (&r != &citem && r.type == NM_TYPE_RADIO && r.radio().group == cg)
  704.                 {
  705.                         if (r.value)
  706.                         {
  707.                                 r.value = 0;
  708.                                 return 1;
  709.                         }
  710.                 }
  711.         }
  712.         return 0;
  713. }
  714.  
  715. static window_event_result newmenu_mouse(window *wind,const d_event &event, newmenu *menu, int button)
  716. {
  717.         int old_choice, mx=0, my=0, mz=0, x1 = 0, x2, y1, y2, changed = 0;
  718.         grs_canvas &menu_canvas = window_get_canvas(*wind);
  719.         grs_canvas &save_canvas = *grd_curcanv;
  720.  
  721.         switch (button)
  722.         {
  723.                 case MBTN_LEFT:
  724.                 {
  725.                         gr_set_current_canvas(menu_canvas);
  726.                         auto &canvas = *grd_curcanv;
  727.  
  728.                         old_choice = menu->citem;
  729.  
  730.                         const auto &&fspacx = FSPACX();
  731.                         if ((event.type == EVENT_MOUSE_BUTTON_DOWN) && !menu->all_text)
  732.                         {
  733.                                 mouse_get_pos(&mx, &my, &mz);
  734.                                 const int line_spacing = static_cast<int>(LINE_SPACING(*grd_curcanv->cv_font, *GAME_FONT));
  735.                                 for (int i = menu->scroll_offset; i < menu->max_on_menu + menu->scroll_offset; ++i)
  736.                                 {
  737.                                         x1 = canvas.cv_bitmap.bm_x + menu->items[i].x - fspacx(13) /*- menu->items[i].right_offset - 6*/;
  738.                                         x2 = x1 + menu->items[i].w + fspacx(13);
  739.                                         y1 = canvas.cv_bitmap.bm_y + menu->items[i].y - (line_spacing * menu->scroll_offset);
  740.                                         y2 = y1 + menu->items[i].h;
  741.                                         if (((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2))) {
  742.                                                 menu->citem = i;
  743.                                                 auto &citem = menu->items[menu->citem];
  744.                                                 switch (citem.type)
  745.                                                 {
  746.                                                         case NM_TYPE_CHECK:
  747.                                                                 citem.value = !citem.value;
  748.                                                                 changed = 1;
  749.                                                                 break;
  750.                                                         case NM_TYPE_RADIO:
  751.                                                                 changed = nm_trigger_radio_button(*menu, citem);
  752.                                                                 break;
  753.                                                         case NM_TYPE_TEXT:
  754.                                                                 menu->citem=old_choice;
  755.                                                                 menu->mouse_state=0;
  756.                                                                 break;
  757.                                                         case NM_TYPE_MENU:
  758.                                                         case NM_TYPE_INPUT:
  759.                                                         case NM_TYPE_NUMBER:
  760.                                                         case NM_TYPE_INPUT_MENU:
  761.                                                         case NM_TYPE_SLIDER:
  762.                                                                 break;
  763.                                                 }
  764.                                                 break;
  765.                                         }
  766.                                 }
  767.                         }
  768.  
  769.                         if ( menu->mouse_state ) {
  770.                                 mouse_get_pos(&mx, &my, &mz);
  771.  
  772.                                 // check possible scrollbar stuff first
  773.                                 const int line_spacing = static_cast<int>(LINE_SPACING(*grd_curcanv->cv_font, *GAME_FONT));
  774.                                 if (menu->is_scroll_box) {
  775.                                         int ScrollAllow=0;
  776.                                         static fix64 ScrollTime=0;
  777.                                         if (ScrollTime + F1_0/5 < timer_query())
  778.                                         {
  779.                                                 ScrollTime = timer_query();
  780.                                                 ScrollAllow = 1;
  781.                                         }
  782.  
  783.                                         if (menu->scroll_offset != 0) {
  784.                                                 int arrow_width, arrow_height;
  785.                                                 gr_get_string_size(*canvas.cv_font, UP_ARROW_MARKER(*canvas.cv_font, *GAME_FONT), &arrow_width, &arrow_height, nullptr);
  786.                                                 x1 = canvas.cv_bitmap.bm_x + BORDERX - fspacx(12);
  787.                                                 y1 = canvas.cv_bitmap.bm_y + menu->items[menu->scroll_offset].y - (line_spacing * menu->scroll_offset);
  788.                                                 x2 = x1 + arrow_width;
  789.                                                 y2 = y1 + arrow_height;
  790.                                                 if (((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2)) && ScrollAllow) {
  791.                                                         newmenu_scroll(menu, -1);
  792.                                                 }
  793.                                         }
  794.                                         if (menu->scroll_offset+menu->max_displayable<menu->nitems) {
  795.                                                 int arrow_width, arrow_height;
  796.                                                 gr_get_string_size(*canvas.cv_font, DOWN_ARROW_MARKER(*canvas.cv_font, *GAME_FONT), &arrow_width, &arrow_height, nullptr);
  797.                                                 x1 = canvas.cv_bitmap.bm_x + BORDERX - fspacx(12);
  798.                                                 y1 = canvas.cv_bitmap.bm_y + menu->items[menu->scroll_offset + menu->max_displayable - 1].y - (line_spacing * menu->scroll_offset);
  799.                                                 x2 = x1 + arrow_width;
  800.                                                 y2 = y1 + arrow_height;
  801.                                                 if (((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2)) && ScrollAllow) {
  802.                                                         newmenu_scroll(menu, 1);
  803.                                                 }
  804.                                         }
  805.                                 }
  806.  
  807.                                 for (int i = menu->scroll_offset; i < menu->max_on_menu + menu->scroll_offset; ++i)
  808.                                 {
  809.                                         x1 = canvas.cv_bitmap.bm_x + menu->items[i].x - fspacx(13);
  810.                                         x2 = x1 + menu->items[i].w + fspacx(13);
  811.                                         y1 = canvas.cv_bitmap.bm_y + menu->items[i].y - (line_spacing * menu->scroll_offset);
  812.                                         y2 = y1 + menu->items[i].h;
  813.  
  814.                                         if (((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2)) && (menu->items[i].type != NM_TYPE_TEXT) ) {
  815.                                                 menu->citem = i;
  816.                                                 auto &citem = menu->items[menu->citem];
  817.                                                 if (citem.type == NM_TYPE_SLIDER)
  818.                                                 {
  819.                                                         char slider_text[NM_MAX_TEXT_LEN+1], *p, *s1;
  820.  
  821.                                                         strcpy(slider_text, citem.saved_text);
  822.                                                         p = strchr(slider_text, '\t');
  823.                                                         if (p) {
  824.                                                                 *p = '\0';
  825.                                                                 s1 = p+1;
  826.                                                         }
  827.                                                         if (p) {
  828.                                                                 int slider_width, sleft_width, sright_width, smiddle_width;
  829.                                                                 gr_get_string_size(*canvas.cv_font, s1, &slider_width, nullptr, nullptr);
  830.                                                                 gr_get_string_size(*canvas.cv_font, SLIDER_LEFT, &sleft_width, nullptr, nullptr);
  831.                                                                 gr_get_string_size(*canvas.cv_font, SLIDER_RIGHT, &sright_width, nullptr, nullptr);
  832.                                                                 gr_get_string_size(*canvas.cv_font, SLIDER_MIDDLE, &smiddle_width, nullptr, nullptr);
  833.  
  834.                                                                 x1 = canvas.cv_bitmap.bm_x + citem.x + citem.w - slider_width;
  835.                                                                 x2 = x1 + slider_width + sright_width;
  836.                                                                 int new_value;
  837.                                                                 auto &slider = citem.slider();
  838.                                                                 if (
  839.                                                                         (mx > x1 && mx < x1 + sleft_width && (new_value = slider.min_value, true)) ||
  840.                                                                         (mx < x2 && mx > x2 - sright_width && (new_value = slider.max_value, true)) ||
  841.                                                                         (mx > x1 + sleft_width && mx < x2 - sright_width - sleft_width && (new_value = (mx - x1 - sleft_width) / ((slider_width - sleft_width - sright_width) / (slider.max_value - slider.min_value + 1)), true))
  842.                                                                 )
  843.                                                                         if (citem.value != new_value)
  844.                                                                         {
  845.                                                                                 citem.value = new_value;
  846.                                                                                 changed = 1;
  847.                                                                         }
  848.                                                                 *p = '\t';
  849.                                                         }
  850.                                                 }
  851.                                                 if (menu->citem == old_choice)
  852.                                                         break;
  853.                                                 if (citem.type == NM_TYPE_INPUT && menu->citem != old_choice)
  854.                                                         citem.value = -1;
  855.                                                 if ((old_choice>-1) && (menu->items[old_choice].type==NM_TYPE_INPUT_MENU) && (old_choice!=menu->citem)) {
  856.                                                         menu->items[old_choice].imenu().group = 0;
  857.                                                         strcpy(menu->items[old_choice].text, menu->items[old_choice].saved_text );
  858.                                                         menu->items[old_choice].value = -1;
  859.                                                 }
  860.                                                 break;
  861.                                         }
  862.                                 }
  863.                         }
  864.  
  865.                         if ((event.type == EVENT_MOUSE_BUTTON_UP) && !menu->all_text && (menu->citem != -1) && (menu->items[menu->citem].type == NM_TYPE_MENU) )
  866.                         {
  867.                                 mouse_get_pos(&mx, &my, &mz);
  868.                                 const int line_spacing = static_cast<int>(LINE_SPACING(*grd_curcanv->cv_font, *GAME_FONT));
  869.                                 for (int i = menu->scroll_offset; i < menu->max_on_menu + menu->scroll_offset; ++i)
  870.                                 {
  871.                                         x1 = canvas.cv_bitmap.bm_x + menu->items[i].x - fspacx(13);
  872.                                         x2 = x1 + menu->items[i].w + fspacx(13);
  873.                                         y1 = canvas.cv_bitmap.bm_y + menu->items[i].y - (line_spacing * menu->scroll_offset);
  874.                                         y2 = y1 + menu->items[i].h;
  875.                                         if (((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2))) {
  876.                                                         // Tell callback, allow staying in menu
  877.                                                         const d_select_event selected{menu->citem};
  878.                                                         if (menu->subfunction && (*menu->subfunction)(menu, selected, menu->userdata))
  879.                                                                 return window_event_result::handled;
  880.  
  881.                                                         if (menu->rval)
  882.                                                                 *menu->rval = menu->citem;
  883.                                                         gr_set_current_canvas(save_canvas);
  884.                                                         return window_event_result::close;
  885.                                         }
  886.                                 }
  887.                         }
  888.  
  889.                         if (event.type == EVENT_MOUSE_BUTTON_UP && menu->citem > -1)
  890.                         {
  891.                                 auto &citem = menu->items[menu->citem];
  892.                                 if (citem.type == NM_TYPE_INPUT_MENU && citem.imenu().group == 0)
  893.                                 {
  894.                                         citem.imenu().group = 1;
  895.                                         if (!d_stricmp(citem.saved_text, TXT_EMPTY))
  896.                                         {
  897.                                                 citem.text[0] = 0;
  898.                                                 citem.value = -1;
  899.                                         } else {
  900.                                                 strip_end_whitespace(citem.text);
  901.                                         }
  902.                                 }
  903.                         }
  904.  
  905.                         gr_set_current_canvas(save_canvas);
  906.  
  907.                         if (changed && menu->subfunction)
  908.                         {
  909.                                 (*menu->subfunction)(menu, d_change_event{menu->citem}, menu->userdata);
  910.                         }
  911.                         break;
  912.                 }
  913.                 case MBTN_RIGHT:
  914.                         if (menu->mouse_state)
  915.                         {
  916.                                 if (!(menu->citem > -1))
  917.                                         return window_event_result::close;
  918.                                 auto &citem = menu->items[menu->citem];
  919.                                 if (citem.type == NM_TYPE_INPUT_MENU && citem.imenu().group == 1)
  920.                                 {
  921.                                         citem.imenu().group = 0;
  922.                                         strcpy(citem.text, citem.saved_text);
  923.                                         citem.value = -1;
  924.                                 } else {
  925.                                         return window_event_result::close;
  926.                                 }
  927.                         }
  928.                         break;
  929.                 case MBTN_Z_UP:
  930.                         if (menu->mouse_state)
  931.                                 newmenu_scroll(menu, -1);
  932.                         break;
  933.                 case MBTN_Z_DOWN:
  934.                         if (menu->mouse_state)
  935.                                 newmenu_scroll(menu, 1);
  936.                         break;
  937.         }
  938.  
  939.         return window_event_result::ignored;
  940. }
  941.  
  942. static window_event_result newmenu_key_command(window *, const d_event &event, newmenu *menu)
  943. {
  944.         int k = event_key_get(event);
  945.         int old_choice;
  946.         int changed = 0;
  947.         window_event_result rval = window_event_result::handled;
  948.  
  949.         if (keyd_pressed[KEY_NUMLOCK])
  950.         {
  951.                 switch( k )
  952.                 {
  953.                         case KEY_PAD0: k = KEY_0;  break;
  954.                         case KEY_PAD1: k = KEY_1;  break;
  955.                         case KEY_PAD2: k = KEY_2;  break;
  956.                         case KEY_PAD3: k = KEY_3;  break;
  957.                         case KEY_PAD4: k = KEY_4;  break;
  958.                         case KEY_PAD5: k = KEY_5;  break;
  959.                         case KEY_PAD6: k = KEY_6;  break;
  960.                         case KEY_PAD7: k = KEY_7;  break;
  961.                         case KEY_PAD8: k = KEY_8;  break;
  962.                         case KEY_PAD9: k = KEY_9;  break;
  963.                         case KEY_PADPERIOD: k = KEY_PERIOD; break;
  964.                 }
  965.         }
  966.  
  967.         old_choice = menu->citem;
  968.         auto &citem = menu->items[menu->citem];
  969.  
  970.         switch( k )     {
  971.                 case KEY_HOME:
  972.                 case KEY_PAD7:
  973.                         newmenu_scroll(menu, -menu->nitems);
  974.                         break;
  975.                 case KEY_END:
  976.                 case KEY_PAD1:
  977.                         newmenu_scroll(menu, menu->nitems);
  978.                         break;
  979.                 case KEY_TAB + KEY_SHIFTED:
  980.                 case KEY_UP:
  981.                 case KEY_PAGEUP:
  982.                 case KEY_PAD8:
  983.                         newmenu_scroll(menu, k == KEY_PAGEUP ? -10 : -1);
  984.                         if (citem.type == NM_TYPE_INPUT && menu->citem != old_choice)
  985.                                 citem.value = -1;
  986.                         if ((old_choice>-1) && (menu->items[old_choice].type==NM_TYPE_INPUT_MENU) && (old_choice!=menu->citem)) {
  987.                                 menu->items[old_choice].imenu().group=0;
  988.                                 strcpy(menu->items[old_choice].text, menu->items[old_choice].saved_text );
  989.                                 menu->items[old_choice].value = -1;
  990.                         }
  991.                         break;
  992.                 case KEY_TAB:
  993.                 case KEY_DOWN:
  994.                 case KEY_PAGEDOWN:
  995.                 case KEY_PAD2:
  996.                         newmenu_scroll(menu, k == KEY_PAGEDOWN ? 10 : 1);
  997.                         if (citem.type == NM_TYPE_INPUT && menu->citem != old_choice)
  998.                                 citem.value = -1;
  999.                         if ( (old_choice>-1) && (menu->items[old_choice].type==NM_TYPE_INPUT_MENU) && (old_choice!=menu->citem))        {
  1000.                                 menu->items[old_choice].imenu().group=0;
  1001.                                 strcpy(menu->items[old_choice].text, menu->items[old_choice].saved_text );
  1002.                                 menu->items[old_choice].value = -1;
  1003.                         }
  1004.                         break;
  1005.                 case KEY_SPACEBAR:
  1006.                         if ( menu->citem > -1 ) {
  1007.                                 switch (citem.type)
  1008.                                 {
  1009.                                         case NM_TYPE_TEXT:
  1010.                                         case NM_TYPE_NUMBER:
  1011.                                         case NM_TYPE_SLIDER:
  1012.                                         case NM_TYPE_MENU:
  1013.                                         case NM_TYPE_INPUT:
  1014.                                         case NM_TYPE_INPUT_MENU:
  1015.                                                 break;
  1016.                                         case NM_TYPE_CHECK:
  1017.                                                 citem.value = !citem.value;
  1018.                                                 changed = 1;
  1019.                                                 break;
  1020.                                         case NM_TYPE_RADIO:
  1021.                                                 changed = nm_trigger_radio_button(*menu, citem);
  1022.                                                 break;
  1023.                                 }
  1024.                         }
  1025.                         break;
  1026.  
  1027.                 case KEY_ENTER:
  1028.                 case KEY_PADENTER:
  1029.                         if (menu->citem > -1 && citem.type == NM_TYPE_INPUT_MENU && citem.imenu().group == 0)
  1030.                         {
  1031.                                 citem.imenu().group = 1;
  1032.                                 if (!d_stricmp(citem.saved_text, TXT_EMPTY))
  1033.                                 {
  1034.                                         citem.text[0] = 0;
  1035.                                         citem.value = -1;
  1036.                                 } else {
  1037.                                         strip_end_whitespace(citem.text);
  1038.                                 }
  1039.                         } else
  1040.                         {
  1041.                                 if (citem.type == NM_TYPE_INPUT_MENU)
  1042.                                         citem.imenu().group = 0;        // go out of editing mode
  1043.  
  1044.                                 // Tell callback, allow staying in menu
  1045.                                 const d_select_event selected{menu->citem};
  1046.                                 if (menu->subfunction && (*menu->subfunction)(menu, selected, menu->userdata))
  1047.                                         return window_event_result::handled;
  1048.  
  1049.                                 if (menu->rval)
  1050.                                         *menu->rval = menu->citem;
  1051.                                 return window_event_result::close;
  1052.                         }
  1053.                         break;
  1054.  
  1055.                 case KEY_ESC:
  1056.                         if (menu->citem > -1 && citem.type == NM_TYPE_INPUT_MENU && citem.imenu().group == 1)
  1057.                         {
  1058.                                 citem.imenu().group = 0;
  1059.                                 strcpy(citem.text, citem.saved_text);
  1060.                                 citem.value = -1;
  1061.                         } else {
  1062.                                 return window_event_result::close;
  1063.                         }
  1064.                         break;
  1065.                 default:
  1066.                         rval = window_event_result::ignored;
  1067.                         break;
  1068.         }
  1069.  
  1070.         if ( menu->citem > -1 ) {
  1071.                 // Alerting callback of every keypress for NM_TYPE_INPUT. Alternatively, just respond to EVENT_NEWMENU_SELECTED
  1072.                 if ((citem.type == NM_TYPE_INPUT || (citem.type == NM_TYPE_INPUT_MENU && citem.imenu().group == 1)) && (old_choice == menu->citem) )    {
  1073.                         if ( k==KEY_LEFT || k==KEY_BACKSP || k==KEY_PAD4 )      {
  1074.                                 if (citem.value == -1) citem.value = strlen(citem.text);
  1075.                                 if (citem.value > 0)
  1076.                                         citem.value--;
  1077.                                 citem.text[citem.value] = 0;
  1078.  
  1079.                                 if (citem.type == NM_TYPE_INPUT)
  1080.                                         changed = 1;
  1081.                                 rval = window_event_result::handled;
  1082.                         }
  1083.                         else if (citem.value < citem.input_or_menu()->text_len)
  1084.                         {
  1085.                                 auto ascii = key_ascii();
  1086.                                 if (ascii < 255)
  1087.                                 {
  1088.                                         if (citem.value == -1) {
  1089.                                                 citem.value = 0;
  1090.                                         }
  1091.                                         if (char_allowed(ascii) || (ascii == ' ' && char_allowed(ascii = '_')))
  1092.                                         {
  1093.                                                 citem.text[citem.value++] = ascii;
  1094.                                                 citem.text[citem.value] = 0;
  1095.  
  1096.                                                 if (citem.type == NM_TYPE_INPUT)
  1097.                                                         changed = 1;
  1098.                                         }
  1099.                                 }
  1100.                         }
  1101.                 }
  1102.                 else if ((citem.type != NM_TYPE_INPUT) && (citem.type != NM_TYPE_INPUT_MENU) )
  1103.                 {
  1104.                         auto ascii = key_ascii();
  1105.                         if (ascii < 255 ) {
  1106.                                 int choice1 = menu->citem;
  1107.                                 ascii = toupper(ascii);
  1108.                                 do {
  1109.                                         int i,ch;
  1110.                                         choice1++;
  1111.                                         if (choice1 >= menu->nitems )
  1112.                                                 choice1=0;
  1113.  
  1114.                                         for (i=0;(ch=menu->items[choice1].text[i])!=0 && ch==' ';i++);
  1115.  
  1116.                                         if ( ( (menu->items[choice1].type==NM_TYPE_MENU) ||
  1117.                                                   (menu->items[choice1].type==NM_TYPE_CHECK) ||
  1118.                                                   (menu->items[choice1].type==NM_TYPE_RADIO) ||
  1119.                                                   (menu->items[choice1].type==NM_TYPE_NUMBER) ||
  1120.                                                   (menu->items[choice1].type==NM_TYPE_SLIDER) )
  1121.                                                 && (ascii==toupper(ch)) )
  1122.                                         {
  1123.                                                 k = 0;
  1124.                                                 menu->citem = choice1;
  1125.                                         }
  1126.  
  1127.                                         while (menu->citem+4>=menu->max_on_menu+menu->scroll_offset && menu->scroll_offset < menu->nitems-menu->max_on_menu)
  1128.                                                 menu->scroll_offset++;
  1129.                                         while (menu->citem-4<menu->scroll_offset && menu->scroll_offset > 0)
  1130.                                                 menu->scroll_offset--;
  1131.  
  1132.                                 } while (choice1 != menu->citem );
  1133.                         }
  1134.                 }
  1135.  
  1136.                 if (const auto ns = citem.number_or_slider())
  1137.                 {
  1138.                         switch( k ) {
  1139.                                 case KEY_LEFT:
  1140.                                 case KEY_PAD4:
  1141.                                         citem.value -= 1;
  1142.                                         changed = 1;
  1143.                                         rval = window_event_result::handled;
  1144.                                         break;
  1145.                                 case KEY_RIGHT:
  1146.                                 case KEY_PAD6:
  1147.                                         citem.value++;
  1148.                                         changed = 1;
  1149.                                         rval = window_event_result::handled;
  1150.                                         break;
  1151.                                 case KEY_SPACEBAR:
  1152.                                         citem.value += 10;
  1153.                                         changed = 1;
  1154.                                         rval = window_event_result::handled;
  1155.                                         break;
  1156.                                 case KEY_BACKSP:
  1157.                                         citem.value -= 10;
  1158.                                         changed = 1;
  1159.                                         rval = window_event_result::handled;
  1160.                                         break;
  1161.                         }
  1162.  
  1163.                         auto &min_value = ns->min_value;
  1164.                         if (citem.value < min_value)
  1165.                                 citem.value = min_value;
  1166.                         auto &max_value = ns->max_value;
  1167.                         if (citem.value > max_value)
  1168.                                 citem.value = max_value;
  1169.                 }
  1170.  
  1171.         }
  1172.  
  1173.         if (changed && menu->subfunction)
  1174.         {
  1175.                 (*menu->subfunction)(menu, d_change_event{menu->citem}, menu->userdata);
  1176.         }
  1177.  
  1178.         return rval;
  1179. }
  1180.  
  1181. namespace dsx {
  1182. static void newmenu_create_structure( newmenu *menu )
  1183. {
  1184.         int aw, tw, th, twidth,right_offset;
  1185.         int nmenus, nothers;
  1186.         grs_canvas &save_canvas = *grd_curcanv;
  1187.         gr_set_default_canvas();
  1188.         auto &canvas = *grd_curcanv;
  1189.  
  1190.         tw = th = 0;
  1191.  
  1192.         if ( menu->title )      {
  1193.                 int string_width, string_height;
  1194.                 auto &huge_font = *HUGE_FONT;
  1195.                 gr_get_string_size(huge_font, menu->title, &string_width, &string_height, nullptr);
  1196.                 tw = string_width;
  1197.                 th = string_height;
  1198.         }
  1199.         if ( menu->subtitle )   {
  1200.                 int string_width, string_height;
  1201.                 auto &medium3_font = *MEDIUM3_FONT;
  1202.                 gr_get_string_size(medium3_font, menu->subtitle, &string_width, &string_height, nullptr);
  1203.                 if (string_width > tw )
  1204.                         tw = string_width;
  1205.                 th += string_height;
  1206.         }
  1207.  
  1208.         th += FSPACY(5);                //put some space between titles & body
  1209.  
  1210.         auto &cv_font = *(menu->tiny_mode ? GAME_FONT : MEDIUM1_FONT).get();
  1211.  
  1212.         menu->w = aw = 0;
  1213.         menu->h = th;
  1214.         nmenus = nothers = 0;
  1215.  
  1216.         const auto &&fspacx = FSPACX();
  1217.         const auto &&fspacy = FSPACY();
  1218.         // Find menu height & width (store in w,h)
  1219.         range_for (auto &i, menu->item_range())
  1220.         {
  1221.                 i.y = menu->h;
  1222.                 int string_width, string_height, average_width;
  1223.                 gr_get_string_size(cv_font, i.text, &string_width, &string_height, &average_width);
  1224.                 i.right_offset = 0;
  1225.  
  1226.                 i.saved_text[0] = '\0';
  1227.  
  1228.                 if (i.type == NM_TYPE_SLIDER)
  1229.                 {
  1230.                         int index,w1;
  1231.                         nothers++;
  1232.                         index = snprintf (i.saved_text.data(), i.saved_text.size(), "%s", SLIDER_LEFT);
  1233.                         auto &slider = i.slider();
  1234.                         for (uint_fast32_t j = (slider.max_value - slider.min_value + 1); j--;)
  1235.                         {
  1236.                                 index += snprintf(i.saved_text.data() + index, i.saved_text.size() - index, "%s", SLIDER_MIDDLE);
  1237.                         }
  1238.                         index += snprintf(i.saved_text.data() + index, i.saved_text.size() - index, "%s", SLIDER_RIGHT);
  1239.                         gr_get_string_size(cv_font, i.saved_text.data(), &w1, nullptr, nullptr);
  1240.                         string_width += w1 + aw;
  1241.                 }
  1242.  
  1243.                 if (i.type == NM_TYPE_MENU)
  1244.                 {
  1245.                         nmenus++;
  1246.                 }
  1247.  
  1248.                 if (i.type == NM_TYPE_CHECK)
  1249.                 {
  1250.                         int w1;
  1251.                         nothers++;
  1252.                         gr_get_string_size(cv_font, NORMAL_CHECK_BOX, &w1, nullptr, nullptr);
  1253.                         i.right_offset = w1;
  1254.                         gr_get_string_size(cv_font, CHECKED_CHECK_BOX, &w1, nullptr, nullptr);
  1255.                         if (w1 > i.right_offset)
  1256.                                 i.right_offset = w1;
  1257.                 }
  1258.  
  1259.                 if (i.type == NM_TYPE_RADIO)
  1260.                 {
  1261.                         int w1;
  1262.                         nothers++;
  1263.                         gr_get_string_size(cv_font, NORMAL_RADIO_BOX, &w1, nullptr, nullptr);
  1264.                         i.right_offset = w1;
  1265.                         gr_get_string_size(cv_font, CHECKED_RADIO_BOX, &w1, nullptr, nullptr);
  1266.                         if (w1 > i.right_offset)
  1267.                                 i.right_offset = w1;
  1268.                 }
  1269.  
  1270.                 if (i.type == NM_TYPE_NUMBER)
  1271.                 {
  1272.                         int w1;
  1273.                         char test_text[20];
  1274.                         nothers++;
  1275.                         auto &number = i.number();
  1276.                         snprintf(test_text, sizeof(test_text), "%d", number.max_value);
  1277.                         gr_get_string_size(cv_font, test_text, &w1, nullptr, nullptr);
  1278.                         i.right_offset = w1;
  1279.                         snprintf(test_text, sizeof(test_text), "%d", number.min_value);
  1280.                         gr_get_string_size(cv_font, test_text, &w1, nullptr, nullptr);
  1281.                         if (w1 > i.right_offset)
  1282.                                 i.right_offset = w1;
  1283.                 }
  1284.  
  1285.                 if (const auto input_or_menu = i.input_or_menu())
  1286.                 {
  1287.                         i.saved_text.copy_if(i.text);
  1288.                         const auto text_len = input_or_menu->text_len;
  1289.                         string_width = text_len * fspacx(8) + text_len;
  1290.                         if (i.type == NM_TYPE_INPUT && string_width > MAX_TEXT_WIDTH)
  1291.                                 string_width = MAX_TEXT_WIDTH;
  1292.  
  1293.                         i.value = -1;
  1294.                         if (i.type == NM_TYPE_INPUT_MENU)
  1295.                         {
  1296.                                 i.imenu().group = 0;
  1297.                                 nmenus++;
  1298.                         }
  1299.                         else
  1300.                         {
  1301.                                 nothers++;
  1302.                         }
  1303.                 }
  1304.  
  1305.                 i.w = string_width;
  1306.                 i.h = string_height;
  1307.  
  1308.                 if ( string_width > menu->w )
  1309.                         menu->w = string_width;         // Save maximum width
  1310.                 if ( average_width > aw )
  1311.                         aw = average_width;
  1312.                 menu->h += string_height + fspacy(1);           // Find the height of all strings
  1313.         }
  1314.  
  1315.         if (menu->nitems > menu->max_on_menu)
  1316.         {
  1317.                 menu->is_scroll_box=1;
  1318.                 menu->h = th + (LINE_SPACING(cv_font, *GAME_FONT) * menu->max_on_menu);
  1319.                 menu->max_displayable=menu->max_on_menu;
  1320.  
  1321.                 // if our last citem was > menu->max_on_menu, make sure we re-scroll when we call this menu again
  1322.                 if (menu->citem > menu->max_on_menu-4)
  1323.                 {
  1324.                         menu->scroll_offset = menu->citem - (menu->max_on_menu-4);
  1325.                         if (menu->scroll_offset + menu->max_on_menu > menu->nitems)
  1326.                                 menu->scroll_offset = menu->nitems - menu->max_on_menu;
  1327.                 }
  1328.         }
  1329.         else
  1330.         {
  1331.                 menu->is_scroll_box=0;
  1332.                 menu->max_on_menu = menu->nitems;
  1333.         }
  1334.  
  1335.         right_offset=0;
  1336.  
  1337.         range_for (auto &i, menu->item_range())
  1338.         {
  1339.                 i.w = menu->w;
  1340.                 if (right_offset < i.right_offset)
  1341.                         right_offset = i.right_offset;
  1342.         }
  1343.  
  1344.         menu->w += right_offset;
  1345.  
  1346.         twidth = 0;
  1347.         if ( tw > menu->w )     {
  1348.                 twidth = ( tw - menu->w )/2;
  1349.                 menu->w = tw;
  1350.         }
  1351.  
  1352.         // Find min point of menu border
  1353.         menu->w += BORDERX*2;
  1354.         menu->h += BORDERY*2;
  1355.  
  1356.         menu->x = (canvas.cv_bitmap.bm_w - menu->w) / 2;
  1357.         menu->y = (canvas.cv_bitmap.bm_h - menu->h) / 2;
  1358.  
  1359.         if ( menu->x < 0 ) menu->x = 0;
  1360.         if ( menu->y < 0 ) menu->y = 0;
  1361.  
  1362.         nm_draw_background1(canvas, menu->filename);
  1363.  
  1364.         // Update all item's x & y values.
  1365.         range_for (auto &i, menu->item_range())
  1366.         {
  1367.                 i.x = BORDERX + twidth + right_offset;
  1368.                 i.y += BORDERY;
  1369.                 if (i.type == NM_TYPE_RADIO) {
  1370.                         // find first marked one
  1371.                         newmenu_item *fm = nullptr;
  1372.                         range_for (auto &j, menu->item_range())
  1373.                         {
  1374.                                 if (j.type == NM_TYPE_RADIO && j.radio().group == i.radio().group) {
  1375.                                         if (!fm && j.value)
  1376.                                                 fm = &j;
  1377.                                         j.value = 0;
  1378.                                 }
  1379.                         }
  1380.                         (fm ? *fm : i).value = 1;
  1381.                 }
  1382.         }
  1383.  
  1384.         if (menu->citem != -1)
  1385.         {
  1386.                 if (menu->citem < 0 ) menu->citem = 0;
  1387.                 if (menu->citem > menu->nitems-1 ) menu->citem = menu->nitems-1;
  1388.  
  1389.                 menu->dblclick_flag = 1;
  1390.                 uint_fast32_t i = 0;
  1391.                 while ( menu->items[menu->citem].type==NM_TYPE_TEXT )   {
  1392.                         menu->citem++;
  1393.                         i++;
  1394.                         if (menu->citem >= menu->nitems ) {
  1395.                                 menu->citem=0;
  1396.                         }
  1397.                         if (i > menu->nitems ) {
  1398.                                 menu->citem=0;
  1399.                                 menu->all_text=1;
  1400.                                 break;
  1401.                         }
  1402.                 }
  1403.         }
  1404.  
  1405.         menu->mouse_state = 0;
  1406.         menu->swidth = SWIDTH;
  1407.         menu->sheight = SHEIGHT;
  1408.         menu->fntscalex = FNTScaleX;
  1409.         menu->fntscaley = FNTScaleY;
  1410.         gr_set_current_canvas(save_canvas);
  1411. }
  1412.  
  1413. static window_event_result newmenu_draw(window *wind, newmenu *menu)
  1414. {
  1415.         grs_canvas &menu_canvas = window_get_canvas(*wind);
  1416.         grs_canvas &save_canvas = *grd_curcanv;
  1417.         int th = 0, ty, sx, sy;
  1418.         int i;
  1419.  
  1420.         if (menu->swidth != SWIDTH || menu->sheight != SHEIGHT || menu->fntscalex != FNTScaleX || menu->fntscaley != FNTScaleY)
  1421.         {
  1422.                 newmenu_create_structure ( menu );
  1423.                 {
  1424.                         gr_init_sub_canvas(menu_canvas, grd_curscreen->sc_canvas, menu->x, menu->y, menu->w, menu->h);
  1425.                 }
  1426.         }
  1427.  
  1428.         gr_set_default_canvas();
  1429.         nm_draw_background1(*grd_curcanv, menu->filename);
  1430.         if (menu->filename == NULL)
  1431.         {
  1432.                 const auto mx = menu->x;
  1433.                 const auto my = menu->y;
  1434.                 auto ex = mx;
  1435.                 if (menu->is_scroll_box)
  1436.                         ex -= FSPACX(5);
  1437.                 nm_draw_background(*grd_curcanv, ex, my, mx + menu->w, my + menu->h);
  1438.         }
  1439.  
  1440.         gr_set_current_canvas( menu_canvas );
  1441.  
  1442.         ty = BORDERY;
  1443.  
  1444.         if ( menu->title )      {
  1445.                 gr_set_fontcolor(*grd_curcanv, BM_XRGB(31, 31, 31), -1);
  1446.                 int string_width, string_height;
  1447.                 auto &huge_font = *HUGE_FONT;
  1448.                 gr_get_string_size(huge_font, menu->title, &string_width, &string_height, nullptr);
  1449.                 th = string_height;
  1450.                 gr_string(*grd_curcanv, huge_font, 0x8000, ty, menu->title, string_width, string_height);
  1451.         }
  1452.  
  1453.         if ( menu->subtitle )   {
  1454.                 gr_set_fontcolor(*grd_curcanv, BM_XRGB(21, 21, 21), -1);
  1455.                 auto &medium3_font = *MEDIUM3_FONT;
  1456.                 gr_string(*grd_curcanv, medium3_font, 0x8000, ty + th, menu->subtitle);
  1457.         }
  1458.  
  1459.         gr_set_curfont(*grd_curcanv, menu->tiny_mode?GAME_FONT:MEDIUM1_FONT);
  1460.  
  1461.         // Redraw everything...
  1462.         for (i=menu->scroll_offset; i<menu->max_displayable+menu->scroll_offset; i++ )
  1463.         {
  1464.                 draw_item(*grd_curcanv, &menu->items[i], (i==menu->citem && !menu->all_text),menu->tiny_mode, menu->tabs_flag, menu->scroll_offset);
  1465.         }
  1466.  
  1467.         if (menu->is_scroll_box)
  1468.         {
  1469.                 auto &cv_font = *(menu->tiny_mode ? GAME_FONT : MEDIUM2_FONT);
  1470.  
  1471.                 const int line_spacing = static_cast<int>(LINE_SPACING(cv_font, *GAME_FONT));
  1472.                 sy = menu->items[menu->scroll_offset].y - (line_spacing * menu->scroll_offset);
  1473.                 const auto &&fspacx = FSPACX();
  1474.                 sx = BORDERX - fspacx(12);
  1475.  
  1476.                 gr_string(*grd_curcanv, cv_font, sx, sy, menu->scroll_offset ? UP_ARROW_MARKER(cv_font, *GAME_FONT) : "  ");
  1477.  
  1478.                 sy = menu->items[menu->scroll_offset + menu->max_displayable - 1].y - (line_spacing * menu->scroll_offset);
  1479.                 sx = BORDERX - fspacx(12);
  1480.  
  1481.                 gr_string(*grd_curcanv, cv_font, sx, sy, (menu->scroll_offset + menu->max_displayable < menu->nitems) ? DOWN_ARROW_MARKER(*grd_curcanv->cv_font, *GAME_FONT) : "  ");
  1482.         }
  1483.  
  1484.                 if (menu->subfunction)
  1485.                         (*menu->subfunction)(menu, d_event{EVENT_NEWMENU_DRAW}, menu->userdata);
  1486.  
  1487.         gr_set_current_canvas(save_canvas);
  1488.  
  1489.         return window_event_result::handled;
  1490. }
  1491.  
  1492. static window_event_result newmenu_handler(window *wind,const d_event &event, newmenu *menu)
  1493. {
  1494. #if DXX_MAX_JOYSTICKS // Pierre-Marie Baty -- missing preprocessor condition
  1495.         if (joy_translate_menu_key(event))
  1496.                 return window_event_result::handled;
  1497. #endif
  1498.  
  1499.         if (menu->subfunction)
  1500.         {
  1501.                 int rval = (*menu->subfunction)(menu, event, menu->userdata);
  1502.  
  1503. #if 0   // No current instances of the subfunction closing the window itself (which is preferred)
  1504.                 // Enable when all subfunctions return a window_event_result
  1505.                 if (rval == window_event_result::deleted)
  1506.                         return rval;    // some subfunction closed the window: bail!
  1507. #endif
  1508.  
  1509.                 if (rval)
  1510.                 {
  1511.                         if (rval < -1)
  1512.                         {
  1513.                                 if (menu->rval)
  1514.                                         *menu->rval = rval;
  1515.                                 return window_event_result::close;
  1516.                         }
  1517.  
  1518.                         return window_event_result::handled;            // event handled
  1519.                 }
  1520.         }
  1521.  
  1522.         switch (event.type)
  1523.         {
  1524.                 case EVENT_WINDOW_ACTIVATED:
  1525.                         game_flush_inputs();
  1526.                         event_toggle_focus(0);
  1527.                         key_toggle_repeat(1);
  1528.                         break;
  1529.  
  1530.                 case EVENT_WINDOW_DEACTIVATED:
  1531.                         //event_toggle_focus(1);        // No cursor recentering
  1532.                         key_toggle_repeat(1);
  1533.                         menu->mouse_state = 0;
  1534.                         break;
  1535.  
  1536.                 case EVENT_MOUSE_BUTTON_DOWN:
  1537.                 case EVENT_MOUSE_BUTTON_UP:
  1538.                 {
  1539.                         int button = event_mouse_get_button(event);
  1540.                         menu->mouse_state = event.type == EVENT_MOUSE_BUTTON_DOWN;
  1541.                         return newmenu_mouse(wind, event, menu, button);
  1542.                 }
  1543.  
  1544.                 case EVENT_KEY_COMMAND:
  1545.                         return newmenu_key_command(wind, event, menu);
  1546.                 case EVENT_IDLE:
  1547.                         if (!(Game_mode & GM_MULTI && Game_wind))
  1548.                                 timer_delay2(CGameArg.SysMaxFPS);
  1549.                         break;
  1550.                 case EVENT_WINDOW_DRAW:
  1551.                         return newmenu_draw(wind, menu);
  1552.                 case EVENT_WINDOW_CLOSE:
  1553.                         delete menu;
  1554.                         break;
  1555.  
  1556.                 default:
  1557.                         break;
  1558.         }
  1559.         return window_event_result::ignored;
  1560. }
  1561.  
  1562. newmenu *newmenu_do4( const char * title, const char * subtitle, uint_fast32_t nitems, newmenu_item * item, newmenu_subfunction subfunction, void *userdata, int citem, const char * filename, int TinyMode, int TabsFlag )
  1563. {
  1564.         newmenu *menu = new newmenu{};
  1565.         menu->citem = citem;
  1566.         menu->scroll_offset = 0;
  1567.         menu->all_text = 0;
  1568.         menu->is_scroll_box = 0;
  1569.         menu->max_on_menu = TinyMode?MAXDISPLAYABLEITEMSTINY:MAXDISPLAYABLEITEMS;
  1570.         menu->dblclick_flag = 0;
  1571.         menu->title = title;
  1572.         menu->subtitle = subtitle;
  1573.         menu->nitems = nitems;
  1574.         menu->subfunction = subfunction;
  1575.         menu->items = item;
  1576.         menu->filename = filename;
  1577.         menu->tiny_mode = TinyMode;
  1578.         menu->tabs_flag = TabsFlag;
  1579.         menu->rval = NULL;              // Default to not returning a value - respond to EVENT_NEWMENU_SELECTED instead
  1580.         menu->userdata = userdata;
  1581.  
  1582.         newmenu_free_background();
  1583.  
  1584.         if (nitems < 1 )
  1585.         {
  1586.                 delete menu;
  1587.                 return NULL;
  1588.         }
  1589.  
  1590.         menu->max_displayable=nitems;
  1591.  
  1592.         //set_screen_mode(SCREEN_MENU); //hafta set the screen mode here or fonts might get changed/freed up if screen res changes
  1593.  
  1594.         newmenu_create_structure(menu);
  1595.  
  1596.         // Create the basic window
  1597.         const auto wind = window_create(grd_curscreen->sc_canvas, menu->x, menu->y, menu->w, menu->h, newmenu_handler, menu);
  1598.        
  1599.         if (!wind)
  1600.         {
  1601.                 delete menu;
  1602.                 return NULL;
  1603.         }
  1604.         return menu;
  1605. }
  1606. }
  1607.  
  1608. int (vnm_messagebox_aN)(const char *title, const nm_messagebox_tie &tie, const char *format, ...)
  1609. {
  1610.         va_list args;
  1611.         char nm_text[MESSAGEBOX_TEXT_SIZE];
  1612.         va_start(args, format);
  1613.         vsnprintf(nm_text,sizeof(nm_text),format,args);
  1614.         va_end(args);
  1615.         return nm_messagebox_str(title, tie, nm_text);
  1616. }
  1617.  
  1618. int nm_messagebox_str(const char *title, const nm_messagebox_tie &tie, const char *str)
  1619. {
  1620.         newmenu_item items[nm_messagebox_tie::maximum_arity];
  1621.         for (unsigned i=0; i < tie.count(); ++i) {
  1622.                 const char *s = tie.string(i);
  1623.                 nm_set_item_menu(items[i], s);
  1624.         }
  1625.         return newmenu_do( title, str, tie.count(), items, unused_newmenu_subfunction, unused_newmenu_userdata );
  1626. }
  1627.  
  1628. // Example listbox callback function...
  1629. // int lb_callback( int * citem, int *nitems, char * items[], int *keypress )
  1630. // {
  1631. //      int i;
  1632. //
  1633. //      if ( *keypress = KEY_CTRLED+KEY_D )     {
  1634. //              if ( *nitems > 1 )      {
  1635. //                      PHYSFS_delete(items[*citem]);     // Delete the file
  1636. //                      for (i=*citem; i<*nitems-1; i++ )       {
  1637. //                              items[i] = items[i+1];
  1638. //                      }
  1639. //                      *nitems = *nitems - 1;
  1640. //                      d_free( items[*nitems] );
  1641. //                      items[*nitems] = NULL;
  1642. //                      return 1;       // redraw;
  1643. //              }
  1644. //                      *keypress = 0;
  1645. //      }
  1646. //      return 0;
  1647. // }
  1648.  
  1649. #define LB_ITEMS_ON_SCREEN 8
  1650.  
  1651. struct listbox : embed_window_pointer_t
  1652. {
  1653.         struct marquee
  1654.         {
  1655.                 class deleter : std::default_delete<fix64[]>
  1656.                 {
  1657.                 public:
  1658.                         void operator()(marquee *const m) const
  1659.                         {
  1660.                                 static_assert(std::is_trivially_destructible<marquee>::value, "marquee destructor not called");
  1661.                                 std::default_delete<fix64[]>::operator()(reinterpret_cast<fix64 *>(m));
  1662.                         }
  1663.                 };
  1664.                 using ptr = std::unique_ptr<marquee, deleter>;
  1665.                 static ptr allocate(const unsigned maxchars)
  1666.                 {
  1667.                         const unsigned max_bytes = maxchars + 1 + sizeof(marquee);
  1668.                         auto pf = std::make_unique<fix64[]>(1 + (max_bytes / sizeof(fix64)));
  1669.                         auto pm = ptr(new(pf.get()) marquee(maxchars));
  1670.                         pf.release();
  1671.                         return pm;
  1672.                 }
  1673.                 marquee(const unsigned mc) :
  1674.                         maxchars(mc)
  1675.                 {
  1676.                 }
  1677.                 fix64 lasttime; // to scroll text if string does not fit in box
  1678.                 const unsigned maxchars;
  1679.                 int pos = 0, scrollback = 0;
  1680.                 char text[0];   /* must be last */
  1681.         };
  1682.         const char *title;
  1683.         const char **item;
  1684.         int allow_abort_flag;
  1685.         listbox_subfunction_t<void> listbox_callback;
  1686.         unsigned nitems;
  1687.         unsigned items_on_screen;
  1688.         int citem, first_item;
  1689.         int box_w, height, box_x, box_y, title_height;
  1690.         short swidth, sheight;
  1691.         // with these we check if resolution or fonts have changed so listbox structure can be recreated
  1692.         font_x_scale_proportion fntscalex;
  1693.         font_y_scale_proportion fntscaley;
  1694.         int mouse_state;
  1695.         marquee::ptr marquee;
  1696.         void *userdata;
  1697. };
  1698.  
  1699. window *listbox_get_window(listbox *const lb)
  1700. {
  1701.         return lb->wind;
  1702. }
  1703.  
  1704. const char **listbox_get_items(listbox *lb)
  1705. {
  1706.         return lb->item;
  1707. }
  1708.  
  1709. int listbox_get_citem(listbox *lb)
  1710. {
  1711.         return lb->citem;
  1712. }
  1713.  
  1714. void listbox_delete_item(listbox *lb, int item)
  1715. {
  1716.         Assert(item >= 0);
  1717.  
  1718.         const auto nitems = lb->nitems;
  1719.         if (nitems <= 0)
  1720.                 return;
  1721.         if (item < nitems - 1)
  1722.         {
  1723.                 auto &items = lb->item;
  1724.                 std::rotate(&items[item], &items[item + 1], &items[nitems]);
  1725.         }
  1726.         -- lb->nitems;
  1727.         if (lb->citem >= lb->nitems)
  1728.                 lb->citem = lb->nitems ? lb->nitems - 1 : 0;
  1729. }
  1730.  
  1731. static void update_scroll_position(listbox *lb)
  1732. {
  1733.         if (lb->citem<0)
  1734.                 lb->citem = 0;
  1735.  
  1736.         if (lb->citem>=lb->nitems)
  1737.                 lb->citem = lb->nitems-1;
  1738.  
  1739.         if (lb->citem< lb->first_item)
  1740.                 lb->first_item = lb->citem;
  1741.  
  1742.         if (lb->citem >= lb->items_on_screen)
  1743.         {
  1744.                 if (lb->first_item <= lb->citem - lb->items_on_screen)
  1745.                         lb->first_item = lb->citem - lb->items_on_screen + 1;
  1746.         }
  1747.  
  1748.         if (lb->nitems <= lb->items_on_screen)
  1749.                 lb->first_item = 0;
  1750.  
  1751.         if (lb->nitems >= lb->items_on_screen)
  1752.         {
  1753.                 if (lb->first_item > lb->nitems - lb->items_on_screen)
  1754.                         lb->first_item = lb->nitems - lb->items_on_screen;
  1755.         }
  1756.         if (lb->first_item < 0 ) lb->first_item = 0;
  1757. }
  1758.  
  1759. static window_event_result listbox_mouse(window *, const d_event &event, listbox *lb, int button)
  1760. {
  1761.         switch (button)
  1762.         {
  1763.                 case MBTN_LEFT:
  1764.                 {
  1765.                         if (lb->mouse_state)
  1766.                         {
  1767.                                 int mx, my, mz;
  1768.                                 mouse_get_pos(&mx, &my, &mz);
  1769.                                 const int x1 = lb->box_x;
  1770.                                 if (mx <= x1)
  1771.                                         break;
  1772.                                 const int x2 = x1 + lb->box_w;
  1773.                                 if (mx >= x2)
  1774.                                         break;
  1775.                                 const int by = lb->box_y;
  1776.                                 if (my <= by)
  1777.                                         break;
  1778.                                 const int my_relative_by = my - by;
  1779.                                 const auto &&line_spacing = LINE_SPACING(*MEDIUM3_FONT, *GAME_FONT);
  1780.                                 if (line_spacing < 1)
  1781.                                         break;
  1782.                                 const int idx_relative_first_visible_item = my_relative_by / line_spacing;
  1783.                                 const auto first_visible_item = lb->first_item;
  1784.                                 const auto nitems = lb->nitems;
  1785.                                 const auto last_visible_item = std::min(first_visible_item + lb->items_on_screen, nitems);
  1786.                                 if (idx_relative_first_visible_item >= last_visible_item)
  1787.                                         break;
  1788.                                 const int px_within_item = my_relative_by % static_cast<int>(line_spacing);
  1789.                                 const int h = gr_get_string_height(*MEDIUM3_FONT, 1);
  1790.                                 if (px_within_item >= h)
  1791.                                         break;
  1792.                                 const int idx_absolute_item = idx_relative_first_visible_item + first_visible_item;
  1793.                                 if (idx_absolute_item >= nitems)
  1794.                                         break;
  1795.                                 lb->citem = idx_absolute_item;
  1796.                         }
  1797.                         else if (event.type == EVENT_MOUSE_BUTTON_UP)
  1798.                         {
  1799.                                 int h;
  1800.  
  1801.                                 if (lb->citem < 0)
  1802.                                         return window_event_result::ignored;
  1803.  
  1804.                                 int mx, my, mz;
  1805.                                 mouse_get_pos(&mx, &my, &mz);
  1806.                                 gr_get_string_size(*MEDIUM3_FONT, lb->item[lb->citem], nullptr, &h, nullptr);
  1807.                                 const int x1 = lb->box_x;
  1808.                                 const int x2 = lb->box_x + lb->box_w;
  1809.                                 const int y1 = (lb->citem - lb->first_item) * LINE_SPACING(*MEDIUM3_FONT, *GAME_FONT) + lb->box_y;
  1810.                                 const int y2 = y1 + h;
  1811.                                 if ( ((mx > x1) && (mx < x2)) && ((my > y1) && (my < y2)) )
  1812.                                 {
  1813.                                         // Tell callback, if it wants to close it will return window_event_result::close
  1814.                                         const d_select_event selected{lb->citem};
  1815.                                         if (lb->listbox_callback)
  1816.                                                 return (*lb->listbox_callback)(lb, selected, lb->userdata);
  1817.                                         return window_event_result::close;
  1818.                                 }
  1819.                         }
  1820.                         break;
  1821.                 }
  1822.                 case MBTN_RIGHT:
  1823.                 {
  1824.                         if (lb->allow_abort_flag && lb->mouse_state) {
  1825.                                 lb->citem = -1;
  1826.                                 return window_event_result::close;
  1827.                         }
  1828.                         break;
  1829.                 }
  1830.                 case MBTN_Z_UP:
  1831.                 {
  1832.                         if (lb->mouse_state)
  1833.                         {
  1834.                                 lb->citem--;
  1835.                                 update_scroll_position(lb);
  1836.                         }
  1837.                         break;
  1838.                 }
  1839.                 case MBTN_Z_DOWN:
  1840.                 {
  1841.                         if (lb->mouse_state)
  1842.                         {
  1843.                                 lb->citem++;
  1844.                                 update_scroll_position(lb);
  1845.                         }
  1846.                         break;
  1847.                 }
  1848.                 default:
  1849.                         break;
  1850.         }
  1851.  
  1852.         return window_event_result::ignored;
  1853. }
  1854.  
  1855. static window_event_result listbox_key_command(window *, const d_event &event, listbox *lb)
  1856. {
  1857.         int key = event_key_get(event);
  1858.         window_event_result rval = window_event_result::handled;
  1859.  
  1860.         switch(key)     {
  1861.                 case KEY_HOME:
  1862.                 case KEY_PAD7:
  1863.                         lb->citem = 0;
  1864.                         break;
  1865.                 case KEY_END:
  1866.                 case KEY_PAD1:
  1867.                         lb->citem = lb->nitems-1;
  1868.                         break;
  1869.                 case KEY_UP:
  1870.                 case KEY_PAD8:
  1871.                         lb->citem--;
  1872.                         break;
  1873.                 case KEY_DOWN:
  1874.                 case KEY_PAD2:
  1875.                         lb->citem++;
  1876.                         break;
  1877.                 case KEY_PAGEDOWN:
  1878.                 case KEY_PAD3:
  1879.                         lb->citem += lb->items_on_screen;
  1880.                         break;
  1881.                 case KEY_PAGEUP:
  1882.                 case KEY_PAD9:
  1883.                         lb->citem -= lb->items_on_screen;
  1884.                         break;
  1885.                 case KEY_ESC:
  1886.                         if (lb->allow_abort_flag) {
  1887.                                 lb->citem = -1;
  1888.                                 return window_event_result::close;
  1889.                         }
  1890.                         break;
  1891.                 case KEY_ENTER:
  1892.                 case KEY_PADENTER:
  1893.                         // Tell callback, if it wants to close it will return window_event_result::close
  1894.                         {
  1895.                                 const d_select_event selected{lb->citem};
  1896.                                 if (lb->listbox_callback)
  1897.                                         return (*lb->listbox_callback)(lb, selected, lb->userdata);
  1898.                         }
  1899.                         return window_event_result::close;
  1900.                 default:
  1901.                 {
  1902.                         const unsigned ascii = key_ascii();
  1903.                         if ( ascii < 255 )      {
  1904.                                 const unsigned upper_ascii = toupper(ascii);
  1905.                                 const auto nitems = lb->nitems;
  1906.                                 for (unsigned cc = lb->citem + 1, steps = 0; steps < nitems; ++steps, ++cc)
  1907.                                 {
  1908.                                         if (cc >= nitems)
  1909.                                                 cc = 0;
  1910.                                         if (toupper(static_cast<unsigned>(lb->item[cc][0])) == upper_ascii)
  1911.                                         {
  1912.                                                 lb->citem = cc;
  1913.                                                 break;
  1914.                                         }
  1915.                                 }
  1916.                         }
  1917.                         rval = window_event_result::ignored;
  1918.                 }
  1919.         }
  1920.         update_scroll_position(lb);
  1921.         return rval;
  1922. }
  1923.  
  1924. static void listbox_create_structure( listbox *lb)
  1925. {
  1926.         gr_set_default_canvas();
  1927.         auto &canvas = *grd_curcanv;
  1928.  
  1929.         auto &medium3_font = *MEDIUM3_FONT;
  1930.  
  1931.         lb->box_w = 0;
  1932.         const auto &&fspacx = FSPACX();
  1933.         const auto &&fspacx10 = fspacx(10);
  1934.         const unsigned max_box_width = SWIDTH - (BORDERX * 2);
  1935.         unsigned marquee_maxchars = UINT_MAX;
  1936.         range_for (const auto i, unchecked_partial_range(lb->item, lb->nitems))
  1937.         {
  1938.                 int w;
  1939.                 gr_get_string_size(medium3_font, i, &w, nullptr, nullptr);
  1940.                 w += fspacx10;
  1941.                 if (w > max_box_width)
  1942.                 {
  1943.                         unsigned mmc = 1;
  1944.                         for (;; ++mmc)
  1945.                         {
  1946.                                 int w2;
  1947.                                 gr_get_string_size(medium3_font, i, &w2, nullptr, nullptr, mmc);
  1948.                                 if (w2 > max_box_width - fspacx10 || mmc > 128)
  1949.                                         break;
  1950.                         }
  1951.                         /* mmc is now the shortest initial subsequence that is wider
  1952.                          * than max_box_width.
  1953.                          *
  1954.                          * Next, search for whether any internal subsequences of
  1955.                          * lesser length are also too wide.  This can happen if all
  1956.                          * the initial characters are narrow, then characters
  1957.                          * outside the initial subsequence are wide.
  1958.                          */
  1959.                         for (auto j = i;;)
  1960.                         {
  1961.                                 int w2;
  1962.                                 gr_get_string_size(medium3_font, j, &w2, nullptr, nullptr, mmc);
  1963.                                 if (w2 > max_box_width - fspacx10)
  1964.                                 {
  1965.                                         /* This subsequence is too long.  Reduce the length
  1966.                                          * and retry.
  1967.                                          */
  1968.                                         if (!--mmc)
  1969.                                                 break;
  1970.                                 }
  1971.                                 else
  1972.                                 {
  1973.                                         /* This subsequence fits.  Move to the next
  1974.                                          * character.
  1975.                                          */
  1976.                                         if (!*++j)
  1977.                                                 break;
  1978.                                 }
  1979.                         }
  1980.                         w = max_box_width;
  1981.                         if (marquee_maxchars > mmc)
  1982.                                 marquee_maxchars = mmc;
  1983.                 }
  1984.                 if (lb->box_w < w)
  1985.                         lb->box_w = w;
  1986.         }
  1987.  
  1988.         {
  1989.                 int w, h;
  1990.                 gr_get_string_size(medium3_font, lb->title, &w, &h, nullptr);
  1991.                 if ( w > lb->box_w )
  1992.                         lb->box_w = w;
  1993.                 lb->title_height = h+FSPACY(5);
  1994.         }
  1995.  
  1996.         // The box is bigger than we can fit on the screen since at least one string is too long. Check how many chars we can fit on the screen (at least only - MEDIUM*_FONT is variable font!) so we can make a marquee-like effect.
  1997.         if (marquee_maxchars != UINT_MAX)
  1998.         {
  1999.                 lb->box_w = max_box_width;
  2000.                 lb->marquee = listbox::marquee::allocate(marquee_maxchars);
  2001.                 lb->marquee->lasttime = timer_query();
  2002.         }
  2003.  
  2004.         const auto &&line_spacing = LINE_SPACING(medium3_font, *GAME_FONT);
  2005.         const unsigned bordery2 = BORDERY * 2;
  2006.         const auto items_on_screen = std::max<unsigned>(
  2007.                 std::min<unsigned>(((canvas.cv_bitmap.bm_h - bordery2 - lb->title_height) / line_spacing) - 2, lb->nitems),
  2008.                 LB_ITEMS_ON_SCREEN);
  2009.         lb->items_on_screen = items_on_screen;
  2010.         lb->height = line_spacing * items_on_screen;
  2011.         lb->box_x = (canvas.cv_bitmap.bm_w - lb->box_w) / 2;
  2012.         lb->box_y = (canvas.cv_bitmap.bm_h - (lb->height + lb->title_height)) / 2 + lb->title_height;
  2013.         if (lb->box_y < bordery2)
  2014.                 lb->box_y = bordery2;
  2015.  
  2016.         if ( lb->citem < 0 ) lb->citem = 0;
  2017.         if ( lb->citem >= lb->nitems ) lb->citem = 0;
  2018.  
  2019.         lb->first_item = 0;
  2020.         update_scroll_position(lb);
  2021.  
  2022.         lb->mouse_state = 0;    //dblclick_flag = 0;
  2023.         lb->swidth = SWIDTH;
  2024.         lb->sheight = SHEIGHT;
  2025.         lb->fntscalex = FNTScaleX;
  2026.         lb->fntscaley = FNTScaleY;
  2027. }
  2028.  
  2029. static window_event_result listbox_draw(window *, listbox *lb)
  2030. {
  2031.         if (lb->swidth != SWIDTH || lb->sheight != SHEIGHT || lb->fntscalex != FNTScaleX || lb->fntscaley != FNTScaleY)
  2032.                 listbox_create_structure ( lb );
  2033.  
  2034.         gr_set_default_canvas();
  2035.         auto &canvas = *grd_curcanv;
  2036.         nm_draw_background(canvas, lb->box_x - BORDERX, lb->box_y - lb->title_height - BORDERY,lb->box_x + lb->box_w + BORDERX, lb->box_y + lb->height + BORDERY);
  2037.         auto &medium3_font = *MEDIUM3_FONT;
  2038.         gr_string(canvas, medium3_font, 0x8000, lb->box_y - lb->title_height, lb->title);
  2039.  
  2040.         const auto &&line_spacing = LINE_SPACING(medium3_font, *GAME_FONT);
  2041.         for (int i = lb->first_item; i < lb->first_item + lb->items_on_screen; ++i)
  2042.         {
  2043.                 int y = (i - lb->first_item) * line_spacing + lb->box_y;
  2044.                 const auto &&fspacx = FSPACX();
  2045.                 const auto &&fspacy = FSPACY();
  2046.                 const uint8_t color5 = BM_XRGB(5, 5, 5);
  2047.                 const uint8_t color2 = BM_XRGB(2, 2, 2);
  2048.                 const uint8_t color0 = BM_XRGB(0, 0, 0);
  2049.                 if ( i >= lb->nitems )  {
  2050.                         gr_rect(canvas, lb->box_x + lb->box_w - fspacx(1), y - fspacy(1), lb->box_x + lb->box_w, y + line_spacing, color5);
  2051.                         gr_rect(canvas, lb->box_x - fspacx(1), y - fspacy(1), lb->box_x, y + line_spacing, color2);
  2052.                         gr_rect(canvas, lb->box_x, y - fspacy(1), lb->box_x + lb->box_w - fspacx(1), y + line_spacing, color0);
  2053.                 } else {
  2054.                         auto &mediumX_font = *(i == lb->citem ? MEDIUM2_FONT : MEDIUM1_FONT);
  2055.                         gr_rect(canvas, lb->box_x + lb->box_w - fspacx(1), y - fspacy(1), lb->box_x + lb->box_w, y + line_spacing, color5);
  2056.                         gr_rect(canvas, lb->box_x - fspacx(1), y - fspacy(1), lb->box_x, y + line_spacing, color2);
  2057.                         gr_rect(canvas, lb->box_x, y - fspacy(1), lb->box_x + lb->box_w - fspacx(1), y + line_spacing, color0);
  2058.  
  2059.                         const char *showstr;
  2060.                         std::size_t item_len;
  2061.                         if (lb->marquee && (item_len = strlen(lb->item[i])) > lb->marquee->maxchars)
  2062.                         {
  2063.                                 showstr = lb->marquee->text;
  2064.                                 static int prev_citem = -1;
  2065.                                
  2066.                                 if (prev_citem != lb->citem)
  2067.                                 {
  2068.                                         lb->marquee->pos = lb->marquee->scrollback = 0;
  2069.                                         lb->marquee->lasttime = timer_query();
  2070.                                         prev_citem = lb->citem;
  2071.                                 }
  2072.  
  2073.                                 unsigned srcoffset = 0;
  2074.                                 if (i == lb->citem)
  2075.                                 {
  2076.                                         const auto tq = timer_query();
  2077.                                         if (lb->marquee->lasttime + (F1_0 / 3) < tq)
  2078.                                         {
  2079.                                                 lb->marquee->pos += lb->marquee->scrollback ? -1 : 1;
  2080.                                                 lb->marquee->lasttime = tq;
  2081.                                         }
  2082.                                         if (lb->marquee->pos < 0) // reached beginning of string -> scroll forward
  2083.                                         {
  2084.                                                 lb->marquee->pos = 0;
  2085.                                                 lb->marquee->scrollback = 0;
  2086.                                         }
  2087.                                         if (lb->marquee->pos + lb->marquee->maxchars - 1 > item_len) // reached end of string -> scroll backward
  2088.                                         {
  2089.                                                 lb->marquee->pos = item_len - lb->marquee->maxchars + 1;
  2090.                                                 lb->marquee->scrollback = 1;
  2091.                                         }
  2092.                                         srcoffset = lb->marquee->pos;
  2093.                                 }
  2094.                                 snprintf(lb->marquee->text, lb->marquee->maxchars, "%s", lb->item[i] + srcoffset);
  2095.                         }
  2096.                         else
  2097.                         {
  2098.                                 showstr = lb->item[i];
  2099.                         }
  2100.                         gr_string(canvas, mediumX_font, lb->box_x + fspacx(5), y, showstr);
  2101.                 }
  2102.         }
  2103.  
  2104.                 if ( lb->listbox_callback )
  2105.                         return (*lb->listbox_callback)(lb, d_event{EVENT_NEWMENU_DRAW}, lb->userdata);
  2106.         return window_event_result::handled;
  2107. }
  2108.  
  2109. static window_event_result listbox_handler(window *wind,const d_event &event, listbox *lb)
  2110. {
  2111.         if (lb->listbox_callback)
  2112.         {
  2113.                 auto rval = (*lb->listbox_callback)(lb, event, lb->userdata);
  2114.                 if (rval != window_event_result::ignored)
  2115.                         return rval;            // event handled
  2116.         }
  2117.  
  2118. #if DXX_MAX_JOYSTICKS // Pierre-Marie Baty -- missing preprocessor condition
  2119.         if (joy_translate_menu_key(event))
  2120.                 return window_event_result::handled;
  2121. #endif
  2122.  
  2123.         switch (event.type)
  2124.         {
  2125.                 case EVENT_WINDOW_ACTIVATED:
  2126.                         game_flush_inputs();
  2127.                         event_toggle_focus(0);
  2128.                         key_toggle_repeat(1);
  2129.                         break;
  2130.  
  2131.                 case EVENT_WINDOW_DEACTIVATED:
  2132.                         //event_toggle_focus(1);        // No cursor recentering
  2133.                         key_toggle_repeat(0);
  2134.                         break;
  2135.  
  2136.                 case EVENT_MOUSE_BUTTON_DOWN:
  2137.                 case EVENT_MOUSE_BUTTON_UP:
  2138.                         lb->mouse_state = event.type == EVENT_MOUSE_BUTTON_DOWN;
  2139.                         return listbox_mouse(wind, event, lb, event_mouse_get_button(event));
  2140.                 case EVENT_KEY_COMMAND:
  2141.                         return listbox_key_command(wind, event, lb);
  2142.                 case EVENT_IDLE:
  2143.                         if (!(Game_mode & GM_MULTI && Game_wind))
  2144.                                 timer_delay2(CGameArg.SysMaxFPS);
  2145.                         return listbox_mouse(wind, event, lb, -1);
  2146.                 case EVENT_WINDOW_DRAW:
  2147.                         return listbox_draw(wind, lb);
  2148.                 case EVENT_WINDOW_CLOSE:
  2149.                         std::default_delete<listbox>()(lb);
  2150.                         break;
  2151.                 default:
  2152.                         break;
  2153.         }
  2154.         return window_event_result::ignored;
  2155. }
  2156.  
  2157. listbox *newmenu_listbox1(const char *const title, const uint_fast32_t nitems, const char *items[], const int allow_abort_flag, const int default_item, const listbox_subfunction_t<void> listbox_callback, void *const userdata)
  2158. {
  2159.         window *wind;
  2160.         newmenu_free_background();
  2161.  
  2162.         auto lb = std::make_unique<listbox>();
  2163.         *lb = {};
  2164.         lb->title = title;
  2165.         lb->nitems = nitems;
  2166.         lb->item = items;
  2167.         lb->citem = default_item;
  2168.         lb->allow_abort_flag = allow_abort_flag;
  2169.         lb->listbox_callback = listbox_callback;
  2170.         lb->userdata = userdata;
  2171.  
  2172.         set_screen_mode(SCREEN_MENU);   //hafta set the screen mode here or fonts might get changed/freed up if screen res changes
  2173.        
  2174.         listbox_create_structure(lb.get());
  2175.  
  2176.         wind = window_create(grd_curscreen->sc_canvas, lb->box_x-BORDERX, lb->box_y-lb->title_height-BORDERY, lb->box_w+2*BORDERX, lb->height+2*BORDERY, listbox_handler, lb.get());
  2177.         if (!wind)
  2178.         {
  2179.                 lb.reset();
  2180.         }
  2181.         return lb.release();
  2182. }
  2183.