1 /** 2 * @file 3 * @brief Menus and associated malarkey. 4 **/ 5 6 #pragma once 7 8 #include <algorithm> 9 #include <cstdio> 10 #include <ctime> 11 #include <chrono> 12 #include <functional> 13 #include <string> 14 #include <vector> 15 16 #include "tiles-build-specific.h" 17 #include "cio.h" 18 #include "format.h" 19 #ifdef USE_TILE 20 #include "tiledoll.h" 21 #endif 22 #ifdef USE_TILE_LOCAL 23 #include "tilebuf.h" 24 #endif 25 #include "ui.h" 26 27 class formatted_string; 28 29 enum MenuEntryLevel 30 { 31 MEL_NONE = -1, 32 MEL_TITLE, 33 MEL_SUBTITLE, 34 MEL_ITEM, 35 }; 36 37 struct menu_letter 38 { 39 char letter; 40 menu_lettermenu_letter41 menu_letter() : letter('a') { } menu_lettermenu_letter42 menu_letter(char c) : letter(c) { } 43 44 operator char () const { return letter; } 45 46 const menu_letter &operator ++ () 47 { 48 letter = letter == 'z'? 'A' : 49 letter == 'Z'? 'a' : 50 letter + 1; 51 return *this; 52 } 53 54 // dummy postfix argument unnamed to stop gcc from complaining 55 menu_letter operator ++ (int) 56 { 57 menu_letter copy = *this; 58 operator++(); 59 return copy; 60 } 61 }; 62 63 // XXX Use inheritance instead of duplicate code 64 struct menu_letter2 65 { 66 char letter; 67 menu_letter2menu_letter268 menu_letter2() : letter('a') { } menu_letter2menu_letter269 menu_letter2(char c) : letter(c) { } 70 71 operator char () const { return letter; } 72 const menu_letter2 &operator ++ () 73 { 74 letter = letter == 'z'? '0' : 75 letter == '9'? 'a' : 76 letter + 1; 77 return *this; 78 } 79 80 menu_letter2 operator ++ (int) 81 { 82 menu_letter2 copy = *this; 83 operator++(); 84 return copy; 85 } 86 }; 87 88 struct item_def; 89 class Menu; 90 91 int menu_colour(const string &itemtext, 92 const string &prefix = "", 93 const string &tag = ""); 94 95 const int MENU_ITEM_STOCK_COLOUR = LIGHTGREY; 96 class MenuEntry 97 { 98 public: 99 string tag; 100 string text; 101 int quantity, selected_qty; 102 colour_t colour; 103 vector<int> hotkeys; 104 MenuEntryLevel level; 105 bool preselected; 106 bool indent_no_hotkeys; 107 void *data; 108 109 #ifdef USE_TILE 110 vector<tile_def> tiles; 111 #endif 112 113 public: 114 MenuEntry(const string &txt = string(), 115 MenuEntryLevel lev = MEL_ITEM, 116 int qty = 0, 117 int hotk = 0, 118 bool preselect = false) : text(txt)119 text(txt), quantity(qty), selected_qty(0), colour(-1), 120 hotkeys(), level(lev), preselected(preselect), 121 indent_no_hotkeys(false), 122 data(nullptr) 123 { 124 colour = (lev == MEL_ITEM ? MENU_ITEM_STOCK_COLOUR : 125 lev == MEL_SUBTITLE ? BLUE : 126 WHITE); 127 if (hotk) 128 hotkeys.push_back(hotk); 129 } ~MenuEntry()130 virtual ~MenuEntry() { } 131 132 bool operator<(const MenuEntry& rhs) const 133 { 134 return text < rhs.text; 135 } 136 add_hotkey(int key)137 void add_hotkey(int key) 138 { 139 if (key && !is_hotkey(key)) 140 hotkeys.push_back(key); 141 } 142 is_hotkey(int key)143 bool is_hotkey(int key) const 144 { 145 return find(hotkeys.begin(), hotkeys.end(), key) != hotkeys.end(); 146 } 147 is_primary_hotkey(int key)148 bool is_primary_hotkey(int key) const 149 { 150 return hotkeys.size() && hotkeys[0] == key; 151 } 152 153 virtual string get_text(const bool unused = false) const; 154 highlight_colour()155 virtual int highlight_colour() const 156 { 157 return menu_colour(get_text(), "", tag); 158 } 159 selected()160 virtual bool selected() const 161 { 162 return selected_qty > 0 && quantity; 163 } 164 165 // -1: Invert 166 // -2: Select all 167 virtual void select(int qty = -1) 168 { 169 if (qty == -2) 170 selected_qty = quantity; 171 else if (selected()) 172 selected_qty = 0; 173 else if (quantity) 174 selected_qty = (qty == -1 ? quantity : qty); 175 } 176 get_filter_text()177 virtual string get_filter_text() const 178 { 179 return get_text(); 180 } 181 182 virtual bool get_tiles(vector<tile_def>& tileset) const; 183 184 virtual void add_tile(tile_def tile); 185 }; 186 187 class ToggleableMenuEntry : public MenuEntry 188 { 189 public: 190 string alt_text; 191 192 ToggleableMenuEntry(const string &txt = string(), 193 const string &alt_txt = string(), 194 MenuEntryLevel lev = MEL_ITEM, 195 int qty = 0, int hotk = 0, 196 bool preselect = false) : MenuEntry(txt,lev,qty,hotk,preselect)197 MenuEntry(txt, lev, qty, hotk, preselect), alt_text(alt_txt) {} 198 toggle()199 void toggle() { text.swap(alt_text); } 200 }; 201 202 class MonsterMenuEntry : public MenuEntry 203 { 204 public: 205 MonsterMenuEntry(const string &str, const monster_info* mon, int hotkey); 206 207 #ifdef USE_TILE 208 virtual bool get_tiles(vector<tile_def>& tileset) const override; 209 #endif 210 }; 211 212 #ifdef USE_TILE 213 class PlayerMenuEntry : public MenuEntry 214 { 215 public: 216 PlayerMenuEntry(const string &str); 217 218 virtual bool get_tiles(vector<tile_def>& tileset) const override; 219 }; 220 #endif 221 222 class FeatureMenuEntry : public MenuEntry 223 { 224 public: 225 coord_def pos; 226 dungeon_feature_type feat; 227 228 FeatureMenuEntry(const string &str, const coord_def p, int hotkey); 229 FeatureMenuEntry(const string &str, const dungeon_feature_type f, 230 int hotkey); 231 232 #ifdef USE_TILE 233 virtual bool get_tiles(vector<tile_def>& tileset) const override; 234 #endif 235 }; 236 237 class MenuHighlighter 238 { 239 public: 240 virtual int entry_colour(const MenuEntry *entry) const; ~MenuHighlighter()241 virtual ~MenuHighlighter() { } 242 }; 243 244 // if you update this, update mf in enums.js 245 enum MenuFlag 246 { 247 MF_NOSELECT = 0x00001, ///< No selection is permitted 248 MF_SINGLESELECT = 0x00002, ///< Select just one item 249 MF_MULTISELECT = 0x00004, ///< Select multiple items 250 MF_NO_SELECT_QTY = 0x00008, ///< Disallow partial selections 251 MF_ANYPRINTABLE = 0x00010, ///< Any printable character is valid, and 252 ///< closes the menu. 253 MF_SELECT_BY_PAGE = 0x00020, ///< Allow selections to occur only on 254 ///< currently-visible page. 255 MF_ALWAYS_SHOW_MORE = 0x00040, ///< Always show the -more- footer 256 MF_WRAP = 0x00080, ///< Paging past the end will wrap back. 257 MF_ALLOW_FILTER = 0x00100, ///< Control-F will ask for regex and 258 ///< select the appropriate items. 259 MF_ALLOW_FORMATTING = 0x00200, ///< Parse index for formatted-string 260 MF_TOGGLE_ACTION = 0x00400, ///< ToggleableMenu toggles action as well 261 MF_NO_WRAP_ROWS = 0x00800, ///< For menus used as tables (eg. ability) 262 MF_START_AT_END = 0x01000, ///< Scroll to end of list 263 MF_PRESELECTED = 0x02000, ///< Has a preselected entry. 264 MF_QUIET_SELECT = 0x04000, ///< No selection box and no count. 265 266 MF_USE_TWO_COLUMNS = 0x08000, ///< Only valid for tiles menus 267 MF_UNCANCEL = 0x10000, ///< Menu is uncancellable 268 MF_SPECIAL_MINUS = 0x20000, ///< '-' isn't PGUP or clear multiselect 269 MF_ARROWS_SELECT = 0x40000, ///< arrow keys select, rather than scroll 270 }; 271 272 class UIMenu; 273 class UIMenuPopup; 274 class UIShowHide; 275 class UIMenuMore; 276 277 /////////////////////////////////////////////////////////////////////// 278 // NOTE 279 // As a general contract, any pointers you pass to Menu methods are OWNED BY 280 // THE MENU, and will be deleted by the Menu on destruction. So any pointers 281 // you pass in MUST be allocated with new, or Crawl will crash. 282 283 #define NUMBUFSIZ 10 284 285 class Menu 286 { 287 friend class UIMenu; 288 friend class UIMenuPopup; 289 public: 290 Menu(int flags = MF_MULTISELECT, const string& tagname = "", KeymapContext kmc = KMC_MENU); 291 292 virtual ~Menu(); 293 294 // Remove all items from the Menu, leave title intact. 295 void clear(); 296 297 virtual void set_flags(int new_flags); get_flags()298 int get_flags() const { return flags; } 299 virtual bool is_set(int flag) const; set_tag(const string & t)300 void set_tag(const string& t) { tag = t; } 301 302 bool minus_is_pageup() const; 303 // Sets a replacement for the default -more- string. 304 void set_more(const formatted_string &more); 305 void set_more(const string s); 306 // Shows a stock message about scrolling the menu instead of -more- 307 void set_more(); get_more()308 const formatted_string &get_more() const { return more; } 309 void set_min_col_width(int w); 310 311 void set_highlighter(MenuHighlighter *h); 312 void set_title(MenuEntry *e, bool first = true, bool indent = false); 313 void add_entry(MenuEntry *entry); add_entry(unique_ptr<MenuEntry> entry)314 void add_entry(unique_ptr<MenuEntry> entry) 315 { 316 add_entry(entry.release()); 317 } 318 void get_selected(vector<MenuEntry*> *sel) const; 319 virtual int get_cursor() const; 320 set_select_filter(vector<text_pattern> filter)321 void set_select_filter(vector<text_pattern> filter) 322 { 323 select_filter = filter; 324 } 325 326 void update_menu(bool update_entries = false); 327 void set_hovered(int index); 328 getkey()329 virtual int getkey() const { return lastch; } 330 331 void reset(); 332 virtual vector<MenuEntry *> show(bool reuse_selections = false); 333 vector<MenuEntry *> selected_entries() const; 334 item_count()335 size_t item_count() const { return items.size(); } 336 337 // Get entry index, skipping quantity 0 entries. Returns -1 if not found. 338 int get_entry_index(const MenuEntry *e) const; 339 340 virtual int item_colour(const MenuEntry *me) const; 341 342 typedef string (*selitem_tfn)(const vector<MenuEntry*> *sel); 343 typedef int (*keyfilter_tfn)(int keyin); 344 345 selitem_tfn f_selitem; 346 keyfilter_tfn f_keyfilter; 347 function<bool(const MenuEntry&)> on_single_selection; 348 function<bool()> on_show; 349 350 enum cycle { CYCLE_NONE, CYCLE_TOGGLE, CYCLE_CYCLE } action_cycle; 351 enum action { ACT_EXECUTE, ACT_EXAMINE, ACT_MISC, ACT_NUM } menu_action; 352 void cycle_hover(bool reverse=false); 353 354 bool title_prompt(char linebuf[], int bufsz, const char* prompt); 355 356 virtual bool process_key(int keyin); 357 358 #ifdef USE_TILE_WEB 359 void webtiles_write_menu(bool replace = false) const; 360 void webtiles_scroll(int first, int hover); 361 void webtiles_handle_item_request(int start, int end); 362 #endif 363 protected: 364 MenuEntry *title; 365 MenuEntry *title2; 366 bool m_indent_title; 367 368 int flags; 369 string tag; 370 371 int cur_page; 372 int num_pages; 373 374 formatted_string more; 375 bool m_keyhelp_more; 376 377 vector<MenuEntry*> items; 378 vector<MenuEntry*> sel; 379 vector<text_pattern> select_filter; 380 381 // Class that is queried to colour menu entries. 382 MenuHighlighter *highlighter; 383 384 int num; 385 386 int lastch; 387 388 bool alive; 389 390 int last_selected; 391 int last_hovered; 392 KeymapContext m_kmc; 393 394 resumable_line_reader *m_filter; 395 396 struct { 397 shared_ptr<ui::Popup> popup; 398 shared_ptr<UIMenu> menu; 399 shared_ptr<ui::Scroller> scroller; 400 shared_ptr<ui::Text> title; 401 shared_ptr<UIMenuMore> more; 402 shared_ptr<UIShowHide> more_bin; 403 shared_ptr<ui::Box> vbox; 404 } m_ui; 405 406 void check_add_formatted_line(int firstcol, int nextcol, 407 string &line, bool check_eol); 408 void do_menu(); 409 virtual string get_select_count_string(int count) const; 410 411 #ifdef USE_TILE_WEB 412 void webtiles_set_title(const formatted_string title); 413 414 void webtiles_write_tiles(const MenuEntry& me) const; 415 void webtiles_update_items(int start, int end) const; 416 void webtiles_update_item(int index) const; 417 void webtiles_update_title() const; 418 void webtiles_update_scroll_pos() const; 419 420 virtual void webtiles_write_title() const; 421 virtual void webtiles_write_item(const MenuEntry *me) const; 422 423 bool _webtiles_title_changed; 424 formatted_string _webtiles_title; 425 #endif 426 427 virtual formatted_string calc_title(); 428 void update_more(); 429 virtual bool page_down(); 430 virtual bool line_down(); 431 virtual bool page_up(); 432 virtual bool line_up(); 433 434 virtual int pre_process(int key); 435 virtual int post_process(int key); 436 437 bool in_page(int index, bool strict=false) const; 438 bool snap_in_page(int index); 439 int get_first_visible() const; 440 441 void deselect_all(bool update_view = true); 442 virtual void select_items(int key, int qty = -1); 443 virtual void select_item_index(int idx, int qty, bool draw_cursor = true); 444 void select_index(int index, int qty = -1); 445 446 bool is_hotkey(int index, int key); 447 virtual bool is_selectable(int index) const; 448 help_key()449 virtual string help_key() const { return ""; } 450 451 virtual void update_title(); 452 bool filter_with_regex(const char *re); 453 }; 454 455 /// Allows toggling by specific keys. 456 class ToggleableMenu : public Menu 457 { 458 public: 459 ToggleableMenu(int _flags = MF_MULTISELECT) Menu(_flags)460 : Menu(_flags) {} add_toggle_key(int newkey)461 void add_toggle_key(int newkey) { toggle_keys.push_back(newkey); } 462 protected: 463 virtual int pre_process(int key) override; 464 465 vector<int> toggle_keys; 466 }; 467 468 // This is only tangentially related to menus, but what the heck. 469 // Note, column margins start on 1, not 0. 470 class column_composer 471 { 472 public: 473 // Number of columns and left margins for 2nd, 3rd, ... nth column. 474 column_composer(int ncols, ...); 475 476 void clear(); 477 void add_formatted(int ncol, 478 const string &tagged_text, 479 bool add_separator = true, 480 int margin = -1); 481 482 vector<formatted_string> formatted_lines() const; 483 484 private: 485 struct column; 486 void compose_formatted_column( 487 const vector<formatted_string> &lines, 488 int start_col, 489 int margin); 490 void strip_blank_lines(vector<formatted_string> &) const; 491 492 private: 493 struct column 494 { 495 int margin; 496 int lines; 497 margincolumn498 column(int marg = 1) : margin(marg), lines(0) { } 499 }; 500 501 vector<column> columns; 502 vector<formatted_string> flines; 503 }; 504 505 int linebreak_string(string& s, int maxcol, bool indent = false); 506 string get_linebreak_string(const string& s, int maxcol); 507