1 /* 2 Copyright (C) 2007 - 2018 by Mark de Wever <koraq@xs4all.nl> 3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/ 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2 of the License, or 8 (at your option) any later version. 9 This program is distributed in the hope that it will be useful, 10 but WITHOUT ANY WARRANTY. 11 12 See the COPYING file for more details. 13 */ 14 15 /** 16 * @file 17 * This file contains the window object, this object is a top level container 18 * which has the event management as well. 19 */ 20 21 #pragma once 22 23 #include "formula/callable.hpp" 24 #include "formula/function.hpp" 25 #include "gui/auxiliary/typed_formula.hpp" 26 #include "gui/core/event/handler.hpp" 27 #include "gui/core/window_builder.hpp" 28 #include "gui/widgets/panel.hpp" 29 #include "gui/widgets/retval.hpp" 30 31 #include <functional> 32 #include <map> 33 #include <memory> 34 #include <string> 35 #include <vector> 36 37 class CVideo; 38 class surface; 39 struct point; 40 41 namespace gui2 42 { 43 44 class widget; 45 namespace event { struct message; } 46 47 // ------------ WIDGET -----------{ 48 49 namespace dialogs { class modal_dialog; } 50 class debug_layout_graph; 51 class pane; 52 53 namespace event 54 { 55 class distributor; 56 } // namespace event 57 58 /** 59 * base class of top level items, the only item 60 * which needs to store the final canvases to draw on 61 */ 62 class window : public panel 63 { 64 friend class debug_layout_graph; 65 friend window* build(const builder_window::window_resolution*); 66 friend struct window_implementation; 67 friend class invalidate_layout_blocker; 68 friend class pane; 69 70 public: 71 explicit window(const builder_window::window_resolution* definition); 72 73 ~window(); 74 75 /** 76 * Returns the instance of a window. 77 * 78 * @param handle The instance id of the window. 79 * 80 * @returns The window or nullptr. 81 */ 82 static window* window_instance(const unsigned handle); 83 84 /** Gets the retval for the default buttons. */ 85 static retval get_retval_by_id(const std::string& id); 86 87 /** 88 * @todo Clean up the show functions. 89 * 90 * the show functions are a bit messy and can use a proper cleanup. 91 */ 92 93 /** 94 * Shows the window. 95 * 96 * @param restore Restore the screenarea the window was on 97 * after closing it? 98 * @param auto_close_timeout The time in ms after which the window will 99 * automatically close, if 0 it doesn't close. 100 * @note the timeout is a minimum time and 101 * there's no guarantee about how fast it closes 102 * after the minimum. 103 * 104 * @returns The close code of the window, predefined 105 * values are listed in retval. 106 */ 107 int show(const bool restore = true, const unsigned auto_close_timeout = 0); 108 109 /** 110 * Shows the window as a tooltip. 111 * 112 * A tooltip can't be interacted with and is just shown. 113 * 114 * @todo implement @p auto_close_timeout. 115 * 116 * @p auto_close_timeout The time in ms after which the window will 117 * automatically close, if 0 it doesn't close. 118 * @note the timeout is a minimum time and 119 * there's no guarantee about how fast it closes 120 * after the minimum. 121 */ 122 void show_tooltip(/*const unsigned auto_close_timeout = 0*/); 123 124 /** 125 * Shows the window non modal. 126 * 127 * A tooltip can be interacted with unlike the tooltip. 128 * 129 * @todo implement @p auto_close_timeout. 130 * 131 * @p auto_close_timeout The time in ms after which the window will 132 * automatically close, if 0 it doesn't close. 133 * @note the timeout is a minimum time and 134 * there's no guarantee about how fast it closes 135 * after the minimum. 136 */ 137 void show_non_modal(/*const unsigned auto_close_timeout = 0*/); 138 139 /** 140 * Draws the window. 141 * 142 * This routine draws the window if needed, it's called from the event 143 * handler. This is done by a drawing event. When a window is shown it 144 * manages an SDL timer which fires a drawing event every X milliseconds, 145 * that event calls this routine. Don't call it manually. 146 */ 147 void draw(); 148 149 /** 150 * Undraws the window. 151 */ 152 void undraw(); 153 154 /** 155 * Adds an item to the dirty_list_. 156 * 157 * @param call_stack The list of widgets traversed to get to the 158 * dirty widget. 159 */ add_to_dirty_list(const std::vector<widget * > & call_stack)160 void add_to_dirty_list(const std::vector<widget*>& call_stack) 161 { 162 dirty_list_.push_back(call_stack); 163 } 164 165 /** The status of the window. */ 166 enum status { 167 NEW, /**< The window is new and not yet shown. */ 168 SHOWING, /**< The window is being shown. */ 169 REQUEST_CLOSE, /**< The window has been requested to be 170 * closed but still needs to evaluate the 171 * request. 172 */ 173 CLOSED /**< The window has been closed. */ 174 }; 175 176 /** 177 * Requests to close the window. 178 * 179 * At the moment the request is always honored but that might change in the 180 * future. 181 */ close()182 void close() 183 { 184 status_ = REQUEST_CLOSE; 185 } 186 187 /** 188 * Helper class to block invalidate_layout. 189 * 190 * Some widgets can handling certain layout aspects without help. For 191 * example a listbox can handle hiding and showing rows without help but 192 * setting the visibility calls invalidate_layout(). When this blocker is 193 * Instantiated the call to invalidate_layout() becomes a nop. 194 * 195 * @note The class can't be used recursively. 196 */ 197 class invalidate_layout_blocker 198 { 199 public: 200 invalidate_layout_blocker(window& window); 201 ~invalidate_layout_blocker(); 202 203 private: 204 window& window_; 205 }; 206 207 /** Is invalidate_layout blocked, see invalidate_layout_blocker. */ invalidate_layout_blocked() const208 bool invalidate_layout_blocked() const 209 { 210 return invalidate_layout_blocked_; 211 } 212 213 /** 214 * Updates the size of the window. 215 * 216 * If the window has automatic placement set this function recalculates the 217 * window. To be used after creation and after modification or items which 218 * can have different sizes eg listboxes. 219 */ 220 void invalidate_layout(); 221 222 /** See @ref widget::find_at. */ 223 virtual widget* find_at(const point& coordinate, 224 const bool must_be_active) override; 225 226 /** See @ref widget::find_at. */ 227 virtual const widget* find_at(const point& coordinate, 228 const bool must_be_active) const override; 229 230 /** Inherited from widget. */ dialog()231 dialogs::modal_dialog* dialog() 232 { 233 return owner_; 234 } 235 236 /** See @ref widget::find. */ 237 widget* find(const std::string& id, const bool must_be_active) override; 238 239 /** See @ref widget::find. */ 240 const widget* find(const std::string& id, 241 const bool must_be_active) const override; 242 243 #if 0 244 /** @todo Implement these functions. */ 245 /** 246 * Register a widget that prevents easy closing. 247 * 248 * Duplicate registration are ignored. See click_dismiss_ for more info. 249 * 250 * @param id The id of the widget to register. 251 */ 252 void add_click_dismiss_blocker(const std::string& id); 253 254 /** 255 * Unregister a widget the prevents easy closing. 256 * 257 * Removing a non registered id is allowed but will do nothing. See 258 * click_dismiss_ for more info. 259 * 260 * @param id The id of the widget to register. 261 */ 262 void remove_click_dismiss_blocker(const std::string& id); 263 #endif 264 265 /** 266 * Does the window close easily? 267 * 268 * The behavior can change at run-time, but that might cause oddities 269 * with the easy close button (when one is needed). 270 * 271 * @returns Whether or not the window closes easily. 272 */ does_click_dismiss() const273 bool does_click_dismiss() const 274 { 275 return click_dismiss_ && !disable_click_dismiss(); 276 } 277 278 /** 279 * Disable the enter key. 280 * 281 * This is added to block dialogs from being closed automatically. 282 * 283 * @todo this function should be merged with the hotkey support once 284 * that has been added. 285 */ set_enter_disabled(const bool enter_disabled)286 void set_enter_disabled(const bool enter_disabled) 287 { 288 enter_disabled_ = enter_disabled; 289 } 290 291 /** 292 * Disable the escape key. 293 * 294 * This is added to block dialogs from being closed automatically. 295 * 296 * @todo this function should be merged with the hotkey support once 297 * that has been added. 298 */ set_escape_disabled(const bool escape_disabled)299 void set_escape_disabled(const bool escape_disabled) 300 { 301 escape_disabled_ = escape_disabled; 302 } 303 304 /** 305 * Initializes a linked size group. 306 * 307 * Note at least one of fixed_width or fixed_height must be true. 308 * 309 * @param id The id of the group. 310 * @param fixed_width Does the group have a fixed width? 311 * @param fixed_height Does the group have a fixed height? 312 */ 313 void init_linked_size_group(const std::string& id, 314 const bool fixed_width, 315 const bool fixed_height); 316 317 /** 318 * Is the linked size group defined for this window? 319 * 320 * @param id The id of the group. 321 * 322 * @returns True if defined, false otherwise. 323 */ 324 bool has_linked_size_group(const std::string& id); 325 326 /** 327 * Adds a widget to a linked size group. 328 * 329 * The group needs to exist, which is done by calling 330 * init_linked_size_group. A widget may only be member of one group. 331 * @todo Untested if a new widget is added after showing the widgets. 332 * 333 * @param id The id of the group. 334 * @param widget The widget to add to the group. 335 */ 336 void add_linked_widget(const std::string& id, widget* widget); 337 338 /** 339 * Removes a widget from a linked size group. 340 * 341 * The group needs to exist, which is done by calling 342 * init_linked_size_group. If the widget is no member of the group the 343 * function does nothing. 344 * 345 * @param id The id of the group. 346 * @param widget The widget to remove from the group. 347 */ 348 void remove_linked_widget(const std::string& id, const widget* widget); 349 350 /***** ***** ***** setters / getters for members ***** ****** *****/ 351 video()352 CVideo& video() 353 { 354 return video_; 355 } 356 357 /** 358 * Sets there return value of the window. 359 * 360 * @param retval The return value for the window. 361 * @param close_window Close the window after setting the value. 362 */ set_retval(const int retval,const bool close_window=true)363 void set_retval(const int retval, const bool close_window = true) 364 { 365 retval_ = retval; 366 if(close_window) 367 close(); 368 } 369 get_retval()370 int get_retval() 371 { 372 return retval_; 373 } 374 set_owner(dialogs::modal_dialog * owner)375 void set_owner(dialogs::modal_dialog* owner) 376 { 377 owner_ = owner; 378 } 379 set_click_dismiss(const bool click_dismiss)380 void set_click_dismiss(const bool click_dismiss) 381 { 382 click_dismiss_ = click_dismiss; 383 } 384 get_need_layout() const385 bool get_need_layout() const 386 { 387 return need_layout_; 388 } 389 set_variable(const std::string & key,const wfl::variant & value)390 void set_variable(const std::string& key, const wfl::variant& value) 391 { 392 variables_.add(key, value); 393 set_is_dirty(true); 394 } get_linked_size(const std::string & linked_group_id) const395 point get_linked_size(const std::string& linked_group_id) const 396 { 397 std::map<std::string, linked_size>::const_iterator it = linked_size_.find(linked_group_id); 398 if(it != linked_size_.end()) { 399 return point(it->second.width, it->second.height); 400 } 401 402 return point(-1, -1); 403 } 404 405 /** 406 * Sets the window's exit hook. 407 * 408 * A window will only close if this function returns true. 409 * 410 * @param func A function taking a window reference and returning a boolean result. 411 */ set_exit_hook(std::function<bool (window &)> func)412 void set_exit_hook(std::function<bool(window&)> func) 413 { 414 exit_hook_ = func; 415 } 416 set_exit_hook_ok_only(std::function<bool (window &)> func)417 void set_exit_hook_ok_only(std::function<bool(window&)> func) 418 { 419 exit_hook_ = [func](window& w)->bool { return w.get_retval() != OK || func(w); }; 420 } 421 422 /** 423 * Sets a callback that will be called after the window is drawn next time. 424 * The callback is automatically removed after calling it once. 425 * Useful if you need to do something after the window is drawn for the first time 426 * and it's timing-sensitive (i.e. pre_show is too early). 427 */ set_callback_next_draw(std::function<void ()> func)428 void set_callback_next_draw(std::function<void()> func) 429 { 430 callback_next_draw_ = func; 431 } 432 433 enum show_mode { 434 none, 435 modal, 436 modeless, 437 tooltip 438 }; 439 440 private: 441 /** Needed so we can change what's drawn on the screen. */ 442 CVideo& video_; 443 444 /** The status of the window. */ 445 status status_; 446 447 /** 448 * The mode in which the window is shown. 449 * 450 * This is used to determine whether or not to remove the tip. 451 */ 452 show_mode show_mode_; 453 454 // return value of the window, 0 default. 455 int retval_; 456 457 /** The dialog that owns the window. */ 458 dialogs::modal_dialog* owner_; 459 460 /** 461 * When set the form needs a full layout redraw cycle. 462 * 463 * This happens when either a widget changes it's size or visibility or 464 * the window is resized. 465 */ 466 bool need_layout_; 467 468 /** The variables of the canvas. */ 469 wfl::map_formula_callable variables_; 470 471 /** Is invalidate_layout blocked, see invalidate_layout_blocker. */ 472 bool invalidate_layout_blocked_; 473 474 /** Avoid drawing the window. */ 475 bool suspend_drawing_; 476 477 /** Whether the window should undraw the window using restorer_ */ 478 bool restore_; 479 480 /** Whether the window has other windows behind it */ 481 bool is_toplevel_; 482 483 /** When the window closes this surface is used to undraw the window. */ 484 surface restorer_; 485 486 /** Do we wish to place the widget automatically? */ 487 const bool automatic_placement_; 488 489 /** 490 * Sets the horizontal placement. 491 * 492 * Only used if automatic_placement_ is true. 493 * The value should be a grid placement flag. 494 */ 495 const unsigned horizontal_placement_; 496 497 /** 498 * Sets the vertical placement. 499 * 500 * Only used if automatic_placement_ is true. 501 * The value should be a grid placement flag. 502 */ 503 const unsigned vertical_placement_; 504 505 /** The maximum width if automatic_placement_ is true. */ 506 unsigned maximum_width_; 507 508 /** The maximum height if automatic_placement_ is true. */ 509 unsigned maximum_height_; 510 511 /** The formula to calculate the x value of the dialog. */ 512 typed_formula<unsigned> x_; 513 514 /** The formula to calculate the y value of the dialog. */ 515 typed_formula<unsigned> y_; 516 517 /** The formula to calculate the width of the dialog. */ 518 typed_formula<unsigned> w_; 519 520 /** The formula to calculate the height of the dialog. */ 521 typed_formula<unsigned> h_; 522 523 /** The formula to determine whether the size is good. */ 524 typed_formula<bool> reevaluate_best_size_; 525 526 /** The formula definitions available for the calculation formulas. */ 527 wfl::function_symbol_table functions_; 528 529 /** The settings for the tooltip. */ 530 builder_window::window_resolution::tooltip_info tooltip_; 531 532 /** The settings for the helptip. */ 533 builder_window::window_resolution::tooltip_info helptip_; 534 535 /** 536 * Do we want to have easy close behavior? 537 * 538 * Easy closing means that whenever a mouse click is done the dialog will 539 * be closed. The widgets in the window may override this behavior by 540 * registering themselves as blockers. This is tested by the function 541 * disable_click_dismiss(). 542 * 543 * The handling of easy close is done in the window, in order to do so a 544 * window either needs a click_dismiss or an ok button. Both will be hidden 545 * when not needed and when needed first the ok is tried and then the 546 * click_dismiss button. this allows adding a click_dismiss button to the 547 * window definition and use the ok from the window instance. 548 * 549 * @todo After testing the click dismiss feature it should be documented in 550 * the wiki. 551 */ 552 bool click_dismiss_; 553 554 /** Disable the enter key see our setter for more info. */ 555 bool enter_disabled_; 556 557 /** Disable the escape key see our setter for more info. */ 558 bool escape_disabled_; 559 560 /** 561 * Helper struct to force widgets the have the same size. 562 * 563 * Widget which are linked will get the same width and/or height. This 564 * can especially be useful for listboxes, but can also be used for other 565 * applications. 566 */ 567 struct linked_size 568 { linked_sizegui2::window::linked_size569 linked_size(const bool width = false, const bool height = false) 570 : widgets(), width(width ? 0 : -1), height(height ? 0 : -1) 571 { 572 } 573 574 /** The widgets linked. */ 575 std::vector<widget*> widgets; 576 577 /** The current width of all widgets in the group, -1 if the width is not linked. */ 578 int width; 579 580 /** The current height of all widgets in the group, -1 if the height is not linked. */ 581 int height; 582 }; 583 584 /** List of the widgets, whose size are linked together. */ 585 std::map<std::string, linked_size> linked_size_; 586 587 /** List of widgets in the tabbing order. */ 588 std::vector<widget*> tab_order; 589 590 /** 591 * Layouts the window. 592 * 593 * This part does the pre and post processing for the actual layout 594 * algorithm. 595 * 596 * See @ref layout_algorithm for more information. 597 */ 598 void layout(); 599 600 /** 601 * Layouts the linked widgets. 602 * 603 * See @ref layout_algorithm for more information. 604 */ 605 void layout_linked_widgets(); 606 607 /** 608 * Handles a mouse click event for dismissing the dialog. 609 * 610 * @param mouse_button_mask The SDL_BUTTON mask for the button used to 611 * dismiss the click. If the caller is from the 612 * keyboard code the value should be 0. 613 * 614 * @return Whether the event should be considered as 615 * handled. 616 */ 617 bool click_dismiss(const int mouse_button_mask); 618 619 /** 620 * The state of the mouse button. 621 * 622 * When click dismissing a dialog in the past the DOWN event was used. 623 * This lead to a bug [1]. The obvious change was to switch to the UP 624 * event, this lead to another bug; the dialog was directly dismissed. 625 * Since the game map code uses the UP and DOWN event to select a unit 626 * there is no simple solution. 627 * 628 * Upon entry this value stores the mouse button state at entry. When a 629 * button is DOWN and goes UP that button does \em not trigger a dismissal 630 * of the dialog, instead that button's down state is removed from this 631 * variable. Therefore the next UP event does dismiss the dialog. 632 * 633 * [1] https://gna.org/bugs/index.php?18970 634 */ 635 int mouse_button_state_; 636 637 public: 638 /** Static type getter that does not rely on the widget being constructed. */ 639 static const std::string& type(); 640 641 private: 642 /** Inherited from styled_widget, implemented by REGISTER_WIDGET. */ 643 virtual const std::string& get_control_type() const override; 644 645 /** 646 * The list with dirty items in the window. 647 * 648 * When drawing only the widgets that are dirty are updated. The draw() 649 * function has more information about the dirty_list_. 650 */ 651 std::vector<std::vector<widget*>> dirty_list_; 652 653 /** 654 * In how many consecutive frames the window has changed. This is used to 655 * detect the situation where the title screen changes in every frame, 656 * forcing all other windows to redraw everything all the time. 657 */ 658 unsigned int consecutive_changed_frames_ = 0u; 659 660 /** Schedules windows on top of us (if any) to redraw. */ 661 void redraw_windows_on_top() const; 662 663 /** 664 * Finishes the initialization of the grid. 665 * 666 * @param content_grid The new contents for the content grid. 667 */ 668 void finalize(const std::shared_ptr<builder_grid>& content_grid); 669 670 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS 671 debug_layout_graph* debug_layout_; 672 673 public: 674 /** wrapper for debug_layout_graph::generate_dot_file. */ 675 void generate_dot_file(const std::string& generator, const unsigned domain); 676 677 private: 678 #else 679 void generate_dot_file(const std::string&, const unsigned) 680 { 681 } 682 #endif 683 684 std::unique_ptr<event::distributor> event_distributor_; 685 686 public: 687 /** Gets a reference to the window's distributor to allow some state peeking. */ get_distributor() const688 const event::distributor& get_distributor() const 689 { 690 return *event_distributor_; 691 } 692 693 /** Returns the dialog mode for this window. */ mode() const694 show_mode mode() const 695 { 696 return show_mode_; 697 } 698 699 // mouse and keyboard_capture should be renamed and stored in the 700 // dispatcher. Chaining probably should remain exclusive to windows. 701 void mouse_capture(const bool capture = true); 702 void keyboard_capture(widget* widget); 703 704 /** 705 * Adds the widget to the keyboard chain. 706 * 707 * @todo rename to keyboard_add_to_chain. 708 * @param widget The widget to add to the chain. The widget 709 * should be valid widget, which hasn't been 710 * added to the chain yet. 711 */ 712 void add_to_keyboard_chain(widget* widget); 713 714 /** 715 * Remove the widget from the keyboard chain. 716 * 717 * @todo rename to keyboard_remove_from_chain. 718 * 719 * @param widget The widget to be removed from the chain. 720 */ 721 void remove_from_keyboard_chain(widget* widget); 722 723 /** 724 * Add the widget to the tabbing order 725 * @param widget The widget to be added to the tabbing order 726 * @param at A hint for where to place the widget in the tabbing order 727 */ 728 void add_to_tab_order(widget* widget, int at = -1); 729 730 private: 731 /***** ***** ***** signal handlers ***** ****** *****/ 732 733 void signal_handler_sdl_video_resize(const event::ui_event event, 734 bool& handled, 735 const point& new_size); 736 737 /** 738 * The handler for the click dismiss mouse 'event'. 739 * 740 * @param event See @ref event::dispatcher::fire. 741 * @param handled See @ref event::dispatcher::fire. 742 * @param halt See @ref event::dispatcher::fire. 743 * @param mouse_button_mask Forwared to @ref click_dismiss. 744 */ 745 void signal_handler_click_dismiss(const event::ui_event event, 746 bool& handled, 747 bool& halt, 748 const int mouse_button_mask); 749 750 void signal_handler_sdl_key_down(const event::ui_event event, 751 bool& handled, 752 const SDL_Keycode key, 753 const SDL_Keymod mod, 754 bool handle_tab); 755 756 void signal_handler_message_show_tooltip(const event::ui_event event, 757 bool& handled, 758 event::message& message); 759 760 void signal_handler_message_show_helptip(const event::ui_event event, 761 bool& handled, 762 event::message& message); 763 764 void signal_handler_request_placement(const event::ui_event event, 765 bool& handled); 766 767 void signal_handler_close_window(); 768 769 std::function<bool(window&)> exit_hook_; 770 std::function<void()> callback_next_draw_; 771 }; 772 773 // }---------- DEFINITION ---------{ 774 775 struct window_definition : public styled_widget_definition 776 { 777 explicit window_definition(const config& cfg); 778 779 struct resolution : public panel_definition::resolution 780 { 781 explicit resolution(const config& cfg); 782 783 builder_grid_ptr grid; 784 }; 785 }; 786 787 // }------------ END -------------- 788 789 } // namespace gui2 790