Skip to content
API Documentation
BastionGuard™
SettingsWindow.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
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, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#include "SettingsWindow.hpp"
22#include "Resource.hpp"
23#include "StyleProvider.hpp"
24#include <gdkmm/pixbuf.h>
25#include <glibmm/i18n.h>
26#include <iostream>
27#include <set>
28
30{
31 set_title("");
32 set_default_size(600, 400);
33 set_modal(true);
34 set_resizable(true);
35
37
38 stack_.get_style_context()->add_class("content");
39 stack_.set_transition_type(Gtk::StackTransitionType::SLIDE_LEFT_RIGHT);
40 stack_.set_transition_duration(220);
41 stack_.set_interpolate_size(true);
42
43 settingsPage_ = Gtk::make_managed<SettingsPage>(config);
45 settingsPage_->set_hexpand(true);
46 settingsPage_->set_vexpand(true);
47
48 stack_.add(*settingsPage_, "settings_root");
49
50 mainBox_.get_style_context()->add_class("settings-main");
51 build_sidebar(config);
52 mainBox_.append(stack_);
53 stack_.set_hexpand(true);
54 stack_.set_vexpand(true);
55 mainBox_.set_margin(0);
56 mainBox_.set_margin_top(0);
57 mainBox_.set_margin_bottom(0);
58 mainBox_.set_margin_start(0);
59 mainBox_.set_margin_end(0);
60
61 mainBox_.set_hexpand(true);
62 mainBox_.set_vexpand(true);
63 set_child(mainBox_);
64 get_style_context()->add_class("settings-window");
65
66 load_style();
67
68 signal_map().connect([this]() {
69 if (global_css)
70 get_style_context()->add_provider(global_css, GTK_STYLE_PROVIDER_PRIORITY_USER);
71 });
72
73 signal_hide().connect([this]() {
74 active_button_ = nullptr;
75 active_page_ = 0;
76 });
77
78 signal_show().connect([this]() {
80 if (!all_leaf_buttons_.empty())
82 });
83}
84
85Gtk::Button* SettingsWindow::make_group_button(const std::string& icon_path,
86 const std::string& label_text,
87 Gtk::Box** out_submenu)
88{
89 auto outer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 6);
90
91 auto icon_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
92 icon_box->set_size_request(16, 16);
93 icon_box->set_halign(Gtk::Align::START);
94 icon_box->set_valign(Gtk::Align::CENTER);
95
96 auto pic = Gtk::make_managed<Gtk::Picture>();
97 if (!icon_path.empty()) {
98 try {
99 auto pb = Gdk::Pixbuf::create_from_file(icon_path);
100 auto scaled = pb->scale_simple(16, 16, Gdk::InterpType::BILINEAR);
101 pic->set_paintable(Gdk::Texture::create_for_pixbuf(scaled));
102 pic->set_content_fit(Gtk::ContentFit::FILL);
103 pic->set_keep_aspect_ratio(true);
104 } catch (...) {}
105 }
106
107 icon_box->append(*pic);
108 outer->append(*icon_box);
109
110 auto lbl = Gtk::make_managed<Gtk::Label>(label_text);
111 lbl->set_halign(Gtk::Align::START);
112 lbl->set_hexpand(true);
113 outer->append(*lbl);
114
115 auto chevron = Gtk::make_managed<Gtk::Label>("▸");
116 chevron->set_halign(Gtk::Align::END);
117 outer->append(*chevron);
118
119 auto btn = Gtk::make_managed<Gtk::Button>();
120 btn->set_child(*outer);
121 btn->get_style_context()->add_class("sidebar-button");
122
123 if (out_submenu) {
124 auto sub = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 2);
125 sub->set_visible(false);
126 sub->get_style_context()->add_class("sidebar-submenu");
127 *out_submenu = sub;
128
129 btn->signal_clicked().connect([sub, chevron]() {
130 bool vis = sub->get_visible();
131 sub->set_visible(!vis);
132 chevron->set_text(vis ? "▸" : "▾");
133 });
134 }
135
136 return btn;
137}
138
139Gtk::Button* SettingsWindow::make_leaf_button(const std::string& label_text,
140 bool indent,
141 Gtk::Box* parent_submenu)
142{
143 auto lbl = Gtk::make_managed<Gtk::Label>(label_text);
144 lbl->set_halign(Gtk::Align::START);
145
146 auto btn = Gtk::make_managed<Gtk::Button>();
147 btn->set_child(*lbl);
148 btn->get_style_context()->add_class("sidebar-button");
149 btn->get_style_context()->add_class("sidebar-leaf");
150
151 if (indent)
152 btn->get_style_context()->add_class("sidebar-leaf-indent");
153
154 all_leaf_buttons_.push_back(btn);
155 button_submenu_[btn] = parent_submenu;
156 return btn;
157}
158
160{
161 active_button_ = btn;
162
163 for (auto* b : all_leaf_buttons_)
164 b->get_style_context()->remove_class("active-sidebar-button");
165
166 if (btn)
167 btn->get_style_context()->add_class("active-sidebar-button");
168
169 Gtk::Box* active_sub = button_submenu_.count(btn) ? button_submenu_.at(btn) : nullptr;
170
171 std::set<Gtk::Box*> all_submenus;
172 for (auto& [b, sub] : button_submenu_)
173 if (sub)
174 all_submenus.insert(sub);
175
176 for (auto* sub : all_submenus) {
177 const bool should_open = (sub == active_sub);
178 sub->set_visible(should_open);
179
180 if (auto* parent_box = dynamic_cast<Gtk::Box*>(sub->get_parent())) {
181 Gtk::Widget* prev = nullptr;
182
183 for (auto* child = parent_box->get_first_child();
184 child != nullptr;
185 child = child->get_next_sibling())
186 {
187 if (child == sub) {
188 if (auto* grp_btn = dynamic_cast<Gtk::Button*>(prev)) {
189 if (auto* outer = dynamic_cast<Gtk::Box*>(grp_btn->get_child())) {
190 Gtk::Widget* last = nullptr;
191 for (auto* w = outer->get_first_child(); w; w = w->get_next_sibling())
192 last = w;
193
194 if (auto* chev = dynamic_cast<Gtk::Label*>(last))
195 chev->set_text(should_open ? "▾" : "▸");
196 }
197 }
198 break;
199 }
200 prev = child;
201 }
202 }
203 }
204}
205
207{
208 sidebar_box_ = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 6);
209 sidebar_box_->set_size_request(220, -1);
210 sidebar_box_->get_style_context()->add_class("sidebar");
211 sidebar_box_->set_vexpand(true);
212
213 {
214 auto header = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
215 header->get_style_context()->add_class("sidebar-header");
216
217 try {
218 auto pix = Gdk::Pixbuf::create_from_file(resource("logo-sidebar.png"), 48, 48, true);
219 header->append(*Gtk::make_managed<Gtk::Image>(pix));
220 } catch (...) {}
221
222 auto title_lbl = Gtk::make_managed<Gtk::Label>(_("Impostazioni"));
223 title_lbl->set_halign(Gtk::Align::START);
224 title_lbl->get_style_context()->add_class("sidebar-title");
225 header->append(*title_lbl);
226 sidebar_box_->append(*header);
227 }
228
229 {
230 auto sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
231 sep->get_style_context()->add_class("sidebar-separator");
232 sidebar_box_->append(*sep);
233 }
234
235 auto menu_box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 2);
236 menu_box->set_margin_top(4);
237 menu_box->set_margin_bottom(4);
238 menu_box->set_vexpand(true);
239
240 auto go = [this](int idx) {
241 active_page_ = idx;
243 };
244
245 {
246 auto* btn = make_leaf_button(_("Base"), false, nullptr);
247 btn->signal_clicked().connect([this, btn, go]() {
248 activate_button(btn);
249 go(0);
250 });
251 menu_box->append(*btn);
252 }
253
254 {
255 auto* btn = make_leaf_button(_("Avanzate"), false, nullptr);
256 btn->signal_clicked().connect([this, btn, go]() {
257 activate_button(btn);
258 go(1);
259 });
260 menu_box->append(*btn);
261 }
262
263 {
264 Gtk::Box* submenu = nullptr;
265 auto* grp = make_group_button(resource("icons/ransomware.svg"),
266 _("Anti-Ransomware"),
267 &submenu);
268 menu_box->append(*grp);
269 menu_box->append(*submenu);
270
271 auto* sub_base = make_leaf_button(_("Base"), true, submenu);
272 sub_base->signal_clicked().connect([this, sub_base, go]() {
273 activate_button(sub_base);
274 go(2);
275 });
276 submenu->append(*sub_base);
277
278 auto* sub_adv = make_leaf_button(_("Avanzate"), true, submenu);
279 sub_adv->signal_clicked().connect([this, sub_adv, go]() {
280 activate_button(sub_adv);
281 go(3);
282 });
283 submenu->append(*sub_adv);
284 }
285
286 {
287 Gtk::Box* submenu = nullptr;
288 auto* grp = make_group_button(resource("icons/scan.svg"),
289 _("Anti-Phishing"),
290 &submenu);
291 menu_box->append(*grp);
292 menu_box->append(*submenu);
293
294 auto* sub_prot = make_leaf_button(_("Protezione"), true, submenu);
295 sub_prot->signal_clicked().connect([this, sub_prot, go]() {
296 activate_button(sub_prot);
297 go(4);
298 });
299 submenu->append(*sub_prot);
300
301 auto* sub_wl = make_leaf_button(_("Whitelist / Blacklist"), true, submenu);
302 sub_wl->signal_clicked().connect([this, sub_wl, go]() {
303 activate_button(sub_wl);
304 go(5);
305 });
306 submenu->append(*sub_wl);
307 }
308
309 {
310 Gtk::Box* submenu = nullptr;
311 auto* grp = make_group_button(resource("icons/payments.svg"),
312 _("Pagamenti Sicuri"),
313 &submenu);
314 menu_box->append(*grp);
315 menu_box->append(*submenu);
316
317 auto* sub_pay = make_leaf_button(_("Domini Pagamenti"), true, submenu);
318 sub_pay->signal_clicked().connect([this, sub_pay, go]() {
319 activate_button(sub_pay);
320 go(6);
321 });
322 submenu->append(*sub_pay);
323
324 auto* sub_bypass = make_leaf_button(_("Bypass Proxy"), true, submenu);
325 sub_bypass->signal_clicked().connect([this, sub_bypass, go]() {
326 activate_button(sub_bypass);
327 go(7);
328 });
329 submenu->append(*sub_bypass);
330 }
331
332 {
333 Gtk::Box* submenu = nullptr;
334 auto* grp = make_group_button(resource("icons/settings.svg"),
335 _("Servizi"),
336 &submenu);
337 menu_box->append(*grp);
338 menu_box->append(*submenu);
339
340 auto* sub_user = make_leaf_button(_("Servizi Utente"), true, submenu);
341 sub_user->signal_clicked().connect([this, sub_user, go]() {
342 activate_button(sub_user);
343 go(8);
344 });
345 submenu->append(*sub_user);
346
347 auto* sub_sys = make_leaf_button(_("Servizi Sistema"), true, submenu);
348 sub_sys->signal_clicked().connect([this, sub_sys, go]() {
349 activate_button(sub_sys);
350 go(10);
351 });
352 submenu->append(*sub_sys);
353 }
354
355 {
356 auto* btn = make_leaf_button(_("Email"), false, nullptr);
357 btn->signal_clicked().connect([this, btn, go]() {
358 activate_button(btn);
359 go(9);
360 });
361 menu_box->append(*btn);
362 }
363
364 {
365 auto* btn = make_leaf_button(_("DB ClamAV"), false, nullptr);
366 btn->signal_clicked().connect([this, btn, go]() {
367 activate_button(btn);
368 go(11);
369 });
370 menu_box->append(*btn);
371 }
372
373 {
374 auto* btn = make_leaf_button(_("Opzioni"), false, nullptr);
375 btn->signal_clicked().connect([this, btn, go]() {
376 activate_button(btn);
377 go(12);
378 });
379 menu_box->append(*btn);
380 }
381
382 sidebar_box_->append(*menu_box);
383
384 {
385 auto* spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
386 spacer->set_vexpand(true);
387 sidebar_box_->append(*spacer);
388
389 auto* sep = Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL);
390 sep->get_style_context()->add_class("sidebar-separator");
391 sidebar_box_->append(*sep);
392 }
393
394 mainBox_.append(*sidebar_box_);
395
396 if (!all_leaf_buttons_.empty())
398
400}
401
403{
404 auto header = Gtk::make_managed<Gtk::HeaderBar>();
405 header->set_show_title_buttons(false);
406 header->set_decoration_layout("");
407 header->get_style_context()->add_class("custom-headerbar");
408
409 auto header_left = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
410 header_left->set_size_request(220, -1);
411 header_left->get_style_context()->add_class("header-left");
412
413 auto accent = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
414 accent->set_size_request(6, -1);
415 accent->get_style_context()->add_class("header-accent");
416 header_left->append(*accent);
417
418 header->pack_start(*header_left);
419
420 auto header_spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
421 header_spacer->set_hexpand(true);
422 header->pack_start(*header_spacer);
423
424 auto header_right = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
425 header_right->get_style_context()->add_class("header-right");
426
427 try {
428 auto pix = Gdk::Pixbuf::create_from_file(resource("icon-close.png"), 32, 32, true);
429 auto btn_close = Gtk::make_managed<Gtk::Button>();
430 btn_close->set_child(*Gtk::make_managed<Gtk::Image>(pix));
431 btn_close->get_style_context()->add_class("header-button");
432 btn_close->signal_clicked().connect([this]() { this->close(); });
433 header_right->append(*btn_close);
434 } catch (...) {
435 std::cerr << _("⚠ Errore caricamento icona close") << "\n";
436 }
437
438 header->pack_end(*header_right);
439 set_titlebar(*header);
440}
#define _(s)
void goto_page(int n)
void set_notebook_no_tabs()
std::vector< Gtk::Button * > all_leaf_buttons_
Gtk::Button * make_leaf_button(const std::string &label_text, bool indent=false, Gtk::Box *parent_submenu=nullptr)
Gtk::Box * sidebar_box_
SettingsWindow(ClamdConfig &config)
SettingsPage * settingsPage_
Gtk::Button * active_button_
Gtk::Button * make_group_button(const std::string &icon_path, const std::string &label_text, Gtk::Box **out_submenu=nullptr)
void build_sidebar(ClamdConfig &config)
void activate_button(Gtk::Button *btn)
std::map< Gtk::Button *, Gtk::Box * > button_submenu_
Gtk.Button btn_close
std::string resource(const std::string &rel)
Definition Resource.hpp:31
void load_style()
Glib::RefPtr< Gtk::CssProvider > global_css