The Battle for Wesnoth  1.19.13+dev
help_browser.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2017 - 2025
3  by Charles Dang <exodia339@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "gui/widgets/button.hpp"
21 #include "gui/widgets/label.hpp"
24 #include "gui/widgets/text_box.hpp"
28 #include "gui/widgets/window.hpp"
29 #include "help/help.hpp"
31 #include "utils/ci_searcher.hpp"
32 
33 static lg::log_domain log_help("help");
34 #define ERR_HP LOG_STREAM(err, log_help)
35 #define WRN_HP LOG_STREAM(warn, log_help)
36 #define DBG_HP LOG_STREAM(debug, log_help)
37 
38 namespace gui2::dialogs
39 {
40 
41 namespace
42 {
43  int win_w = 0;
44  int tree_w = 0;
45 }
46 
47 REGISTER_DIALOG(help_browser)
48 
49 help_browser::help_browser(const help::section& toplevel, const std::string& initial)
50  : modal_dialog(window_id())
51  , initial_topic_(initial.empty() ? help::default_show_topic : initial)
52  , current_topic_()
53  , toplevel_(toplevel)
54  , history_()
55  , history_pos_(0)
56 {
57  if(initial_topic_.compare(0, 2, "..") == 0) {
58  initial_topic_.replace(0, 2, "+");
59  } else {
60  initial_topic_.insert(0, "-");
61  }
63 }
64 
66 {
67  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
68 
69  button& back_button = find_widget<button>("back");
70  button& next_button = find_widget<button>("next");
71 
72  rich_label& topic_text = find_widget<rich_label>("topic_text");
73 
74  next_button.set_active(false);
75  back_button.set_active(false);
76  connect_signal_mouse_left_click(back_button, std::bind(&help_browser::on_history_navigate, this, true));
77  connect_signal_mouse_left_click(next_button, std::bind(&help_browser::on_history_navigate, this, false));
78 
79  connect_signal<event::BACK_BUTTON_CLICK>([this](auto&&...) {
80  on_history_navigate(true);
82  connect_signal<event::FORWARD_BUTTON_CLICK>([this](auto&&...) {
83  on_history_navigate(false);
85 
86  toggle_button& contents = find_widget<toggle_button>("contents");
87 
88  contents.set_value(true);
89  topic_tree.set_visible(true);
90  connect_signal_mouse_left_click(contents, [&](auto&&...) {
91  auto parent = topic_text.get_window();
92  // Cache the initial values, get_best_size() keeps changing
93  if ((parent != nullptr) && (win_w == 0)) {
94  win_w = parent->get_best_size().x;
95  }
96  if (tree_w == 0) {
97  tree_w = topic_tree.get_best_size().x;
98  }
99 
100  // Set RL's width and reshow
101  bool is_contents_visible = (topic_tree.get_visible() == widget::visibility::visible);
102  if (topic_text.get_window()) {
103  topic_text.set_width(win_w - (is_contents_visible ? 0 : tree_w) - 20 /* Padding */);
104  show_topic(history_.at(history_pos_), false);
105  }
106  topic_tree.set_visible(!is_contents_visible);
108  });
109 
110  text_box& filter = find_widget<text_box>("filter_box");
112  filter.on_modified([this](const auto& box) { update_list(box.text()); });
113 
114  topic_text.register_link_callback(std::bind(&help_browser::show_topic, this, std::placeholders::_1, true));
115 
116  connect_signal_notify_modified(topic_tree, std::bind(&help_browser::on_topic_select, this));
117 
118  keyboard_capture(&topic_tree);
119 
121 
122  tree_view_node& initial_node = topic_tree.find_widget<tree_view_node>(initial_topic_);
123  initial_node.select_node(true);
124 
125  on_topic_select();
126 }
127 
128 void help_browser::update_list(const std::string& filter_text) {
129  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
130  topic_tree.clear();
131  if(!add_topics_for_section(toplevel_, topic_tree.get_root_node(), filter_text)) {
132  // Add everything if nothing matches
134  }
135 }
136 
137 bool help_browser::add_topics_for_section(const help::section& parent_section, tree_view_node& parent_node, const std::string& filter_text)
138 {
139  bool topics_added = false;
140  const auto match = translation::make_ci_matcher(filter_text);
141 
142  for(const help::section& section : parent_section.sections) {
143  tree_view_node& section_node = add_topic(section.id, section.title, true, parent_node);
144  bool subtopics_added = add_topics_for_section(section, section_node, filter_text);
145 
146  if (subtopics_added || (match(section.id) || match(section.title))) {
147  if (!filter_text.empty()) {
148  section_node.unfold();
149  }
150  topics_added = true;
151  } else {
152  find_widget<tree_view>("topic_tree").remove_node(&section_node);
153  }
154  }
155 
156  for(const help::topic& topic : parent_section.topics) {
157  if ((match(topic.id) || match(topic.title)) && (topic.id.compare(0, 2, "..") != 0)) {
158  add_topic(topic.id, topic.title, false, parent_node);
159  topics_added = true;
160  }
161  }
162 
163  return topics_added;
164 }
165 
166 tree_view_node& help_browser::add_topic(const std::string& topic_id, const std::string& topic_title,
167  bool expands, tree_view_node& parent)
168 {
170  widget_item item;
171 
172  item["label"] = topic_title;
173  data.emplace("topic_name", item);
174 
175  tree_view_node& new_node = parent.add_child(expands ? "section" : "topic", data);
176  new_node.set_id(std::string(expands ? "+" : "-") + topic_id);
177 
178  return new_node;
179 }
180 
181 void help_browser::show_topic(std::string topic_id, bool add_to_history)
182 {
183  if(topic_id.empty() || topic_id == current_topic_) {
184  return;
185  } else {
186  current_topic_ = topic_id;
187  }
188 
189  if(topic_id[0] == '+') {
190  topic_id.replace(topic_id.begin(), topic_id.begin() + 1, 2, '.');
191  }
192 
193  if(topic_id[0] == '-') {
194  topic_id.erase(topic_id.begin(), topic_id.begin() + 1);
195  }
196 
197  auto iter = parsed_pages_.find(topic_id);
198  if(iter == parsed_pages_.end()) {
199  const help::topic* topic = help::find_topic(toplevel_, topic_id);
200  if(!topic) {
201  ERR_HP << "Help browser tried to show topic with id '" << topic_id
202  << "' but that topic could not be found." << std::endl;
203  return;
204  }
205 
206  DBG_HP << "Showing topic: " << topic->id << ": " << topic->title;
207 
208  std::string topic_id_temp = topic->id;
209  if(topic_id_temp.compare(0, 2, "..") == 0) {
210  topic_id_temp.replace(0, 2, "+");
211  } else {
212  topic_id_temp.insert(0, "-");
213  }
214  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
215  tree_view_node& selected_node = topic_tree.find_widget<tree_view_node>(topic_id_temp);
216  selected_node.select_node(true, false);
217 
218  find_widget<label>("topic_title").set_label(topic->title);
219  find_widget<rich_label>("topic_text").set_dom(topic->text.parsed_text());
220 
222  }
223 
224  if(add_to_history) {
225  // history pos is 0 initially, so it's already at first entry
226  // no need to increment first time
227  if (!history_.empty()) {
228  // don't add duplicate entries back-to-back
229  if (history_.back() == topic_id) {
230  return;
231  }
232  history_pos_++;
233  }
234  history_.push_back(topic_id);
235 
236  find_widget<button>("back").set_active(history_pos_ != 0);
237  }
238 }
239 
241 {
242  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
243 
244  if(topic_tree.empty()) {
245  return;
246  }
247 
248  tree_view_node* selected = topic_tree.selected_item();
249  assert(selected);
250 
251  show_topic(selected->id());
252 }
253 
255 {
256  if(backwards) {
257  if (history_pos_ > 0) {
258  history_pos_--;
259  } else {
260  return;
261  }
262  } else {
263  if (history_pos_ < history_.size() - 1) {
264  history_pos_++;
265  } else {
266  return;
267  }
268  }
269  find_widget<button>("back").set_active(!history_.empty() && history_pos_ != 0);
270  find_widget<button>("next").set_active(!history_.empty() && history_pos_ != (history_.size()-1));
271 
272  show_topic(history_.at(history_pos_), false);
273 }
274 
275 } // namespace dialogs
Simple push button.
Definition: button.hpp:36
virtual void set_active(const bool active) override
See styled_widget::set_active.
Definition: button.cpp:64
Help browser dialog.
const help::section & toplevel_
void update_list(const std::string &)
bool add_topics_for_section(const help::section &parent_section, tree_view_node &parent_node, const std::string &filter_text="")
void show_topic(std::string topic_id, bool add_to_history=true)
std::map< std::string, int > parsed_pages_
virtual void pre_show() override
Actions to be taken before showing the window.
void on_history_navigate(bool backwards)
tree_view_node & add_topic(const std::string &topic_id, const std::string &topic_title, bool expands, tree_view_node &parent)
std::vector< std::string > history_
Abstract base class for all modal dialogs.
A rich_label takes marked up text and shows it correctly formatted and wrapped but no scrollbars are ...
Definition: rich_label.hpp:38
void set_width(const int width)
Definition: rich_label.hpp:136
void register_link_callback(std::function< void(std::string)> link_handler)
Definition: rich_label.cpp:835
A widget that allows the user to input text in single line.
Definition: text_box.hpp:125
void select_node(bool expand_parents=false, bool fire_event=true)
void unfold(const bool recursive=false)
const tree_view_node & get_root_node() const
Definition: tree_view.hpp:53
bool empty() const
Definition: tree_view.cpp:99
tree_view_node * selected_item()
Definition: tree_view.hpp:98
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:203
void set_visible(const visibility visible)
Definition: widget.cpp:479
void set_id(const std::string &id)
Definition: widget.cpp:98
visibility get_visible() const
Definition: widget.cpp:506
window * get_window()
Get the parent window.
Definition: widget.cpp:117
@ visible
The user sets the widget visible, that means:
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
widget * parent()
Definition: widget.cpp:170
void keyboard_capture(widget *widget)
Definition: window.cpp:1201
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:760
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1210
const config & parsed_text() const
Definition: help_impl.cpp:377
This file contains the window object, this object is a top level container which has the event manage...
#define DBG_HP
#define ERR_HP
static lg::log_domain log_help("help")
REGISTER_DIALOG(editor_edit_unit)
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:189
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:163
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
void init_help()
Definition: help.cpp:145
const topic * find_topic(const section &sec, const std::string &id)
Search for the topic with the specified identifier in the section and its subsections.
Definition: help_impl.cpp:1279
const std::string default_show_topic
Definition: help_impl.cpp:82
auto make_ci_matcher(std::string_view filter_text)
Returns a function which performs locale-aware case-insensitive search.
Definition: ci_searcher.hpp:24
constexpr auto filter
Definition: ranges.hpp:38
std::string_view data
Definition: picture.cpp:188
A section contains topics and sections along with title and ID.
Definition: help_impl.hpp:148
section_list sections
Definition: help_impl.hpp:168
std::string id
Definition: help_impl.hpp:166
std::string title
Definition: help_impl.hpp:166
topic_list topics
Definition: help_impl.hpp:167
A topic contains a title, an id and some text.
Definition: help_impl.hpp:115
std::string id
Definition: help_impl.hpp:139
topic_text text
Definition: help_impl.hpp:140
std::string title
Definition: help_impl.hpp:139