The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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 "font/pango/escape.hpp"
23#include "gui/widgets/label.hpp"
31#include "help/help.hpp"
34#include "utils/ci_searcher.hpp"
35
36static lg::log_domain log_help("help");
37#define ERR_HP LOG_STREAM(err, log_help)
38#define WRN_HP LOG_STREAM(warn, log_help)
39#define DBG_HP LOG_STREAM(debug, log_help)
40
41namespace gui2::dialogs
42{
43
44namespace
45{
46 int win_w = 0;
47 int rl_init_w = 0;
48}
49
50REGISTER_DIALOG(help_browser)
51
52help_browser::help_browser(const help::section& toplevel, const std::string& initial)
53 : modal_dialog(window_id())
54 , initial_topic_(initial.empty() ? help::default_show_topic : initial)
55 , current_topic_()
56 , toplevel_(toplevel)
57 , history_()
58 , history_pos_(0)
59{
60 if(initial_topic_.compare(0, 2, "..") == 0) {
61 initial_topic_.replace(0, 2, "+");
62 } else {
63 initial_topic_.insert(0, "-");
64 }
65}
66
68{
69 tree_view& topic_tree = find_widget<tree_view>("topic_tree");
70
71 button& back_button = find_widget<button>("back");
72 button& next_button = find_widget<button>("next");
73
74 rich_label& topic_text = find_widget<rich_label>("topic_text");
75 panel& topic_panel = find_widget<panel>("topic_panel");
76
77 next_button.set_active(false);
78 back_button.set_active(false);
79 connect_signal_mouse_left_click(back_button, std::bind(&help_browser::on_history_navigate, this, true));
80 connect_signal_mouse_left_click(next_button, std::bind(&help_browser::on_history_navigate, this, false));
81
82 connect_signal<event::BACK_BUTTON_CLICK>([this](auto&&...) {
85 connect_signal<event::FORWARD_BUTTON_CLICK>([this](auto&&...) {
88
89 toggle_button& contents = find_widget<toggle_button>("contents");
90
91 contents.set_value(true);
92 topic_panel.set_visible(true);
93 connect_signal_mouse_left_click(contents, [&](auto&&...) {
94 auto parent = topic_panel.get_window();
95 // Cache the initial values, get_best_size() keeps changing
96 // initial width of the window
97 if ((parent != nullptr) && (win_w == 0)) {
98 win_w = parent->get_best_size().x;
99 }
100
101 // initial width of the rich label
102 if(rl_init_w == 0) {
103 rl_init_w = topic_text.get_width();
104 }
105
106 // Set RL's width and reshow
107 bool is_contents_visible = (topic_panel.get_visible() == widget::visibility::visible);
108 if (parent) {
109 topic_text.set_width(is_contents_visible ? win_w : rl_init_w);
110 if(!history_.empty()) {
111 show_topic(history_.at(history_pos_), false, true);
112 }
113 }
114 topic_panel.set_visible(!is_contents_visible);
116 });
117
118 text_box& filter = find_widget<text_box>("filter_box");
119 add_to_keyboard_chain(&filter);
120 filter.on_modified([this](const auto& box) { update_list(box.text()); });
121
122 topic_text.register_link_callback(std::bind(&help_browser::show_topic, this, std::placeholders::_1, true, false));
123
124 connect_signal_notify_modified(topic_tree, std::bind(&help_browser::on_topic_select, this));
125
126 keyboard_capture(&topic_tree);
127
129
130 tree_view_node* initial_node = topic_tree.find_widget<tree_view_node>(initial_topic_, false, false);
131 if(initial_node) {
132 initial_node->select_node(true);
133 }
134
136}
137
138void help_browser::update_list(const std::string& filter_text) {
139 tree_view& topic_tree = find_widget<tree_view>("topic_tree");
140 topic_tree.clear();
141 if(!add_topics_for_section(toplevel_, topic_tree.get_root_node(), filter_text)) {
142 // Add everything if nothing matches
144 }
145}
146
147bool help_browser::add_topics_for_section(const help::section& parent_section, tree_view_node& parent_node, const std::string& filter_text)
148{
149 bool topics_added = false;
150 const auto match = translation::make_ci_matcher(filter_text);
151
152 for(const help::section& section : parent_section.sections) {
153 tree_view_node& section_node = add_topic(section.id, section.title, true, parent_node);
154 bool subtopics_added = add_topics_for_section(section, section_node, filter_text);
155
156 if (subtopics_added || (match(section.id) || match(section.title))) {
157 if (!filter_text.empty()) {
158 section_node.unfold();
159 }
160 topics_added = true;
161 } else {
162 find_widget<tree_view>("topic_tree").remove_node(&section_node);
163 }
164 }
165
166 for(const help::topic& topic : parent_section.topics) {
167 if (topic.id[0] == '.') {
168 continue;
169 }
170
171 if ((match(topic.id) || match(topic.title)) && (topic.id.compare(0, 2, "..") != 0)) {
172 add_topic(topic.id, topic.title, false, parent_node);
173 topics_added = true;
174 }
175 }
176
177 return topics_added;
178}
179
180tree_view_node& help_browser::add_topic(const std::string& topic_id, const std::string& topic_title,
181 bool expands, tree_view_node& parent)
182{
184 widget_item item;
185
186 item["label"] = topic_title;
187 data.emplace("topic_name", item);
188
189 tree_view_node& new_node = parent.add_child(expands ? "section" : "topic", data);
190 new_node.set_id(std::string(expands ? "+" : "-") + topic_id);
191
192 return new_node;
193}
194
195void help_browser::show_topic(std::string topic_id, bool add_to_history, bool reshow)
196{
197 if(reshow) {
198 const help::topic* topic = help::find_topic(toplevel_, topic_id);
199 if(topic) {
200 find_widget<rich_label>("topic_text").set_dom(topic->text.parsed_text());
202 }
203 return;
204 }
205
206 if(topic_id.empty() || topic_id == current_topic_) {
207 return;
208 } else {
209 current_topic_ = topic_id;
210 }
211
212 if(topic_id[0] == '+') {
213 topic_id.replace(topic_id.begin(), topic_id.begin() + 1, 2, '.');
214 }
215
216 if(topic_id[0] == '-') {
217 topic_id.erase(topic_id.begin(), topic_id.begin() + 1);
218 }
219
220 auto iter = parsed_pages_.find(topic_id);
221 if(iter == parsed_pages_.end()) {
222 const help::topic* topic = help::find_topic(toplevel_, topic_id);
223 if(!topic) {
224 ERR_HP << "Help browser tried to show topic with id '" << topic_id
225 << "' but that topic could not be found." << std::endl;
226 return;
227 }
228
229 DBG_HP << "Showing topic: " << topic->id << ": " << topic->title;
230
231 std::string topic_id_temp = topic->id;
232 if(topic_id_temp.compare(0, 2, "..") == 0) {
233 topic_id_temp.replace(0, 2, "+");
234 } else {
235 topic_id_temp.insert(0, "-");
236 }
237 tree_view& topic_tree = find_widget<tree_view>("topic_tree");
238 tree_view_node* selected_node = topic_tree.find_widget<tree_view_node>(topic_id_temp, false, false);
239 if(selected_node) {
240 selected_node->select_node(true, false);
241 }
242
243 find_widget<label>("topic_title").set_label(topic->title);
244 try {
245 find_widget<rich_label>("topic_text").set_dom(topic->text.parsed_text());
246 } catch(const markup::parse_error& e) {
247 find_widget<rich_label>("topic_text").set_label(
249 "Error parsing markup in help page with ID: " + topic->id + "\n"
250 + font::escape_text(e.message)));
251 }
252
254 }
255
256 if(add_to_history) {
257 // history pos is 0 initially, so it's already at first entry
258 // no need to increment first time
259 if (!history_.empty()) {
260 // don't add duplicate entries back-to-back
261 if (history_.back() == topic_id) {
262 return;
263 }
264 history_pos_++;
265 }
266 history_.push_back(topic_id);
267
268 find_widget<button>("back").set_active(history_pos_ != 0);
269 }
270}
271
273{
274 tree_view& topic_tree = find_widget<tree_view>("topic_tree");
275
276 if(topic_tree.empty()) {
277 return;
278 }
279
280 tree_view_node* selected = topic_tree.selected_item();
281 assert(selected);
282
283 show_topic(selected->id());
284}
285
287{
288 if(history_.empty()) {
289 return;
290 }
291
292 if(backwards) {
293 if (history_pos_ > 0) {
294 history_pos_--;
295 } else {
296 return;
297 }
298 } else {
299 if (history_pos_ < history_.size() - 1) {
300 history_pos_++;
301 } else {
302 return;
303 }
304 }
305 find_widget<button>("back").set_active(!history_.empty() && history_pos_ != 0);
306 find_widget<button>("next").set_active(!history_.empty() && history_pos_ != (history_.size()-1));
307
308 show_topic(history_.at(history_pos_), false);
309}
310
311} // 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="")
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)
void show_topic(std::string topic_id, bool add_to_history=true, bool reshow=false)
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 ...
void set_width(const int width)
void register_link_callback(std::function< void(std::string)> link_handler)
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)
tree_view_node * selected_item()
bool empty() const
Definition tree_view.cpp:99
const tree_view_node & get_root_node() const
Definition tree_view.hpp:53
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
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:
widget * parent()
Definition widget.cpp:170
void keyboard_capture(widget *widget)
Definition window.cpp:1197
void invalidate_layout()
Updates the size of the window.
Definition window.cpp:759
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition window.cpp:1211
const config & parsed_text() const
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")
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
const color_t BAD_COLOR
std::string escape_text(std::string_view text)
Escapes the pango markup characters in a text.
Definition escape.hpp:33
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
std::map< std::string, t_string > widget_item
Definition widget.hpp:33
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.
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition markup.hpp:110
STL namespace.
auto make_ci_matcher(std::string_view filter_text)
Returns a function which performs locale-aware case-insensitive search.
std::string_view data
Definition picture.cpp:188
A section contains topics and sections along with title and ID.
section_list sections
topic_list topics
A topic contains a title, an id and some text.
Definition help_impl.hpp:91
std::string id
topic_text text
std::string title
Thrown when the help system fails to parse something.
Definition markup.hpp:213
#define e