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_, false, false);
123  if(initial_node) {
124  initial_node->select_node(true);
125  } else {
126  ERR_HP << "Help browser tried to show topic with id '" << initial_topic_
127  << "' but that topic could not be found." << std::endl;
128  }
129 
130  on_topic_select();
131 }
132 
133 void help_browser::update_list(const std::string& filter_text) {
134  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
135  topic_tree.clear();
136  if(!add_topics_for_section(toplevel_, topic_tree.get_root_node(), filter_text)) {
137  // Add everything if nothing matches
139  }
140 }
141 
142 bool help_browser::add_topics_for_section(const help::section& parent_section, tree_view_node& parent_node, const std::string& filter_text)
143 {
144  bool topics_added = false;
145  const auto match = translation::make_ci_matcher(filter_text);
146 
147  for(const help::section& section : parent_section.sections) {
148  tree_view_node& section_node = add_topic(section.id, section.title, true, parent_node);
149  bool subtopics_added = add_topics_for_section(section, section_node, filter_text);
150 
151  if (subtopics_added || (match(section.id) || match(section.title))) {
152  if (!filter_text.empty()) {
153  section_node.unfold();
154  }
155  topics_added = true;
156  } else {
157  find_widget<tree_view>("topic_tree").remove_node(&section_node);
158  }
159  }
160 
161  for(const help::topic& topic : parent_section.topics) {
162  if ((match(topic.id) || match(topic.title)) && (topic.id.compare(0, 2, "..") != 0)) {
163  add_topic(topic.id, topic.title, false, parent_node);
164  topics_added = true;
165  }
166  }
167 
168  return topics_added;
169 }
170 
171 tree_view_node& help_browser::add_topic(const std::string& topic_id, const std::string& topic_title,
172  bool expands, tree_view_node& parent)
173 {
175  widget_item item;
176 
177  item["label"] = topic_title;
178  data.emplace("topic_name", item);
179 
180  tree_view_node& new_node = parent.add_child(expands ? "section" : "topic", data);
181  new_node.set_id(std::string(expands ? "+" : "-") + topic_id);
182 
183  return new_node;
184 }
185 
186 void help_browser::show_topic(std::string topic_id, bool add_to_history)
187 {
188  if(topic_id.empty() || topic_id == current_topic_) {
189  return;
190  } else {
191  current_topic_ = topic_id;
192  }
193 
194  if(topic_id[0] == '+') {
195  topic_id.replace(topic_id.begin(), topic_id.begin() + 1, 2, '.');
196  }
197 
198  if(topic_id[0] == '-') {
199  topic_id.erase(topic_id.begin(), topic_id.begin() + 1);
200  }
201 
202  auto iter = parsed_pages_.find(topic_id);
203  if(iter == parsed_pages_.end()) {
204  const help::topic* topic = help::find_topic(toplevel_, topic_id);
205  if(!topic) {
206  ERR_HP << "Help browser tried to show topic with id '" << topic_id
207  << "' but that topic could not be found." << std::endl;
208  return;
209  }
210 
211  DBG_HP << "Showing topic: " << topic->id << ": " << topic->title;
212 
213  std::string topic_id_temp = topic->id;
214  if(topic_id_temp.compare(0, 2, "..") == 0) {
215  topic_id_temp.replace(0, 2, "+");
216  } else {
217  topic_id_temp.insert(0, "-");
218  }
219  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
220  tree_view_node& selected_node = topic_tree.find_widget<tree_view_node>(topic_id_temp);
221  selected_node.select_node(true, false);
222 
223  find_widget<label>("topic_title").set_label(topic->title);
224  find_widget<rich_label>("topic_text").set_dom(topic->text.parsed_text());
225 
227  }
228 
229  if(add_to_history) {
230  // history pos is 0 initially, so it's already at first entry
231  // no need to increment first time
232  if (!history_.empty()) {
233  // don't add duplicate entries back-to-back
234  if (history_.back() == topic_id) {
235  return;
236  }
237  history_pos_++;
238  }
239  history_.push_back(topic_id);
240 
241  find_widget<button>("back").set_active(history_pos_ != 0);
242  }
243 }
244 
246 {
247  tree_view& topic_tree = find_widget<tree_view>("topic_tree");
248 
249  if(topic_tree.empty()) {
250  return;
251  }
252 
253  tree_view_node* selected = topic_tree.selected_item();
254  assert(selected);
255 
256  show_topic(selected->id());
257 }
258 
260 {
261  if(backwards) {
262  if (history_pos_ > 0) {
263  history_pos_--;
264  } else {
265  return;
266  }
267  } else {
268  if (history_pos_ < history_.size() - 1) {
269  history_pos_++;
270  } else {
271  return;
272  }
273  }
274  find_widget<button>("back").set_active(!history_.empty() && history_pos_ != 0);
275  find_widget<button>("next").set_active(!history_.empty() && history_pos_ != (history_.size()-1));
276 
277  show_topic(history_.at(history_pos_), false);
278 }
279 
280 } // 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