Skip to content
API Documentation
BastionGuard™
PasswordVaultView.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 "PasswordVaultView.hpp"
22#include <glibmm/i18n.h>
23#include <algorithm>
24#include <fstream>
25
26
28: parent_(parent),
29 vault_(vault),
30 toolBar_(Gtk::Orientation::HORIZONTAL, 8),
31 exportBtn_(_("💾 Esporta CSV"))
32{
33 dialog_ = new Gtk::Dialog(_("Vault — credenziali salvate"), parent, true);
34 dialog_->set_default_size(820, 580);
35 buildUi();
36}
37
41
44 populate();
45 dialog_->present();
46}
47
48
50 auto* content = dialog_->get_content_area();
51 content->set_margin(0);
52 content->set_spacing(0);
53
54 auto* mainBox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 8);
55 mainBox->set_margin(16);
56
57 toolBar_.set_margin_bottom(4);
58
59 searchEntry_.set_placeholder_text(_("Filtra per nome, username, URL…"));
60 searchEntry_.set_hexpand(true);
61 searchEntry_.signal_search_changed().connect(
62 sigc::mem_fun(*this, &PasswordVaultView::onSearchChanged));
63
64 countLabel_.set_xalign(0.0f);
65 countLabel_.get_style_context()->add_class("dim-label");
66
68
69 toolBar_.append(searchEntry_);
70 toolBar_.append(countLabel_);
71 toolBar_.append(exportBtn_);
72 mainBox->append(toolBar_);
73
74 mainBox->append(*Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL));
75
76 {
77 auto* hdr = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
78 hdr->set_margin_start(8);
79 hdr->set_margin_end(8);
80 hdr->set_margin_top(4);
81 hdr->set_margin_bottom(4);
82
83 auto makeHdr = [](const std::string& t, int chars) {
84 auto* l = Gtk::make_managed<Gtk::Label>();
85 l->set_markup("<b>" + Glib::Markup::escape_text(t) + "</b>");
86 l->set_xalign(0.0f);
87 l->set_width_chars(chars);
88 l->set_max_width_chars(chars);
89 l->set_ellipsize(Pango::EllipsizeMode::END);
90 return l;
91 };
92 hdr->append(*makeHdr(_("Nome / Sito"), 24));
93 hdr->append(*makeHdr(_("Username"), 20));
94 hdr->append(*makeHdr(_("URL"), 26));
95 hdr->append(*makeHdr(_("Aggiornato"), 16));
96 mainBox->append(*hdr);
97 }
98
99 mainBox->append(*Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL));
100
101 // ── Lista ──
102 list_.set_selection_mode(Gtk::SelectionMode::SINGLE);
103 scrolled_.set_child(list_);
104 scrolled_.set_vexpand(true);
105 mainBox->append(scrolled_);
106
107 content->append(*mainBox);
108 dialog_->add_button(_("Chiudi"), Gtk::ResponseType::CANCEL);
109
110 dialog_->signal_response().connect([this](int) { dialog_->hide(); });
111
112 exportBtn_.signal_clicked().connect(
113 sigc::mem_fun(*this, &PasswordVaultView::onExportClicked));
114}
115
117 exportBtn_.get_style_context()->add_class("suggested-action");
118 auto provider = Gtk::CssProvider::create();
119 provider->load_from_data(
120 "button.suggested-action {"
121 " background: linear-gradient(to bottom, #3584e4, #1c71d8);"
122 " color: white;"
123 " font-weight: bold;"
124 " padding: 6px 18px;"
125 " border-radius: 6px;"
126 " border: 1px solid #1558b0;"
127 "}");
128 exportBtn_.get_style_context()->add_provider(
129 provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
130}
131
132
133void PasswordVaultView::populate(const std::string& filter) {
134 while (auto* child = list_.get_first_child())
135 list_.remove(*child);
136
137 std::string needle = filter;
138 std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower);
139
140 int shown = 0;
141 for (const auto& entry : entries_) {
142 std::string hay = entry.name + " " + entry.username + " " + entry.url;
143 std::transform(hay.begin(), hay.end(), hay.begin(), ::tolower);
144 if (!needle.empty() && hay.find(needle) == std::string::npos) continue;
145
146 auto* row = Gtk::make_managed<Gtk::ListBoxRow>();
147 auto* box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 0);
148 box->set_margin_top(6);
149 box->set_margin_bottom(6);
150 box->set_margin_start(8);
151 box->set_margin_end(8);
152
153 auto makeCell = [](const std::string& t, int chars) {
154 auto* l = Gtk::make_managed<Gtk::Label>(t);
155 l->set_xalign(0.0f);
156 l->set_width_chars(chars);
157 l->set_max_width_chars(chars);
158 l->set_ellipsize(Pango::EllipsizeMode::END);
159 return l;
160 };
161
162 box->append(*makeCell(entry.name, 24));
163 box->append(*makeCell(entry.username, 20));
164 box->append(*makeCell(entry.url, 26));
165 box->append(*makeCell(entry.updated_at, 16));
166 auto* pwdBtn = Gtk::make_managed<Gtk::Button>(_("👁"));
167 pwdBtn->set_tooltip_text(_("Mostra password"));
168 pwdBtn->get_style_context()->add_class("flat");
169 pwdBtn->signal_clicked().connect([this, entry]() {
170 showEntryDetail(entry);
171 });
172 box->append(*pwdBtn);
173
174 row->set_child(*box);
175 list_.append(*row);
176 ++shown;
177 }
178
179 countLabel_.set_text(std::to_string(shown) + " / " +
180 std::to_string(entries_.size()) +
181 std::string(_(" credenziali")));
182}
183
184
188
189
191 vault_.getSecretAsync(entry.id, [this, entry](std::string secret) {
192 auto* dlg = new Gtk::Dialog(_("Dettaglio credenziale"), *dialog_, true);
193 dlg->set_default_size(420, 320);
194
195 auto* box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 10);
196 box->set_margin(16);
197
198 auto addRow = [&](const std::string& label, const std::string& value, bool selectable = true) {
199 auto* row = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 8);
200 auto* lbl = Gtk::make_managed<Gtk::Label>();
201 lbl->set_markup("<b>" + Glib::Markup::escape_text(label) + "</b>");
202 lbl->set_xalign(0.0f);
203 lbl->set_size_request(100, -1);
204
205 auto* val = Gtk::make_managed<Gtk::Label>(value);
206 val->set_xalign(0.0f);
207 val->set_hexpand(true);
208 val->set_selectable(selectable);
209 val->set_ellipsize(Pango::EllipsizeMode::END);
210
211 row->append(*lbl);
212 row->append(*val);
213 box->append(*row);
214 };
215
216 addRow(_("Nome:"), entry.name);
217 addRow(_("Username:"), entry.username);
218 addRow(_("URL:"), entry.url);
219 addRow(_("Password:"), secret, true);
220 if (!entry.notes.empty())
221 addRow(_("Note:"), entry.notes);
222 addRow(_("Creato:"), entry.created_at, false);
223 addRow(_("Modificato:"), entry.updated_at, false);
224
225 dlg->get_content_area()->set_margin(0);
226 dlg->get_content_area()->append(*box);
227 dlg->add_button(_("Chiudi"), Gtk::ResponseType::CANCEL);
228
229 dlg->signal_response().connect([dlg](int) {
230 dlg->hide();
231 delete dlg;
232 });
233 dlg->present();
234 });
235}
236
237
239 auto* fc = new Gtk::FileChooserDialog(
240 _("Salva CSV"), Gtk::FileChooser::Action::SAVE);
241 fc->set_transient_for(*dialog_);
242 fc->set_modal(true);
243 fc->set_current_name("passwords_export.csv");
244
245 auto filter = Gtk::FileFilter::create();
246 filter->set_name(_("File CSV (*.csv)"));
247 filter->add_pattern("*.csv");
248 fc->add_filter(filter);
249
250 fc->add_button(_("Annulla"), Gtk::ResponseType::CANCEL);
251 fc->add_button(_("Salva"), Gtk::ResponseType::OK);
252
253 fc->signal_response().connect([this, fc](int r) {
254 if (r == Gtk::ResponseType::OK) {
255 const auto file = fc->get_file();
256 if (file) {
257 const std::string path = file->get_path();
258 exportBtn_.set_sensitive(false);
259 exportBtn_.set_label(_("⏳ Esportazione…"));
260
261 vault_.exportToCSV([this, path](std::string csv, std::string err) {
262 exportBtn_.set_sensitive(true);
263 exportBtn_.set_label(_("💾 Esporta CSV"));
264
265 if (!err.empty()) {
266 auto* msg = new Gtk::MessageDialog(
267 *dialog_, _("Errore export"),
268 false, Gtk::MessageType::ERROR,
269 Gtk::ButtonsType::CLOSE, true);
270 msg->set_secondary_text(err);
271 msg->signal_response().connect([msg](int) { msg->hide(); delete msg; });
272 msg->present();
273 return;
274 }
275
276 std::ofstream out(path, std::ios::trunc);
277 if (!out.good()) return;
278 out << csv;
279
280 auto* msg = new Gtk::MessageDialog(
281 *dialog_, _("Export completato"),
282 false, Gtk::MessageType::INFO,
283 Gtk::ButtonsType::CLOSE, true);
284 msg->set_secondary_text(
285 std::to_string(entries_.size()) +
286 std::string(_(" credenziali salvate in:\n")) + path);
287 msg->signal_response().connect([msg](int) { msg->hide(); delete msg; });
288 msg->present();
289 });
290 }
291 }
292 fc->hide();
293 delete fc;
294 });
295 fc->present();
296}
#define _(s)
void populate(const std::string &filter="")
PasswordVault & vault_
std::vector< PasswordEntry > entries_
Gtk::ScrolledWindow scrolled_
Gtk::SearchEntry searchEntry_
PasswordVaultView(Gtk::Window &parent, PasswordVault &vault)
void showEntryDetail(const PasswordEntry &entry)
void getSecretAsync(const std::string &id, std::function< void(std::string)> callback) const
std::vector< PasswordEntry > loadIndex() const
void exportToCSV(std::function< void(std::string csv, std::string err)> onDone) const
std::string created_at
std::string url
std::string id
std::string username
std::string updated_at
std::string notes
std::string name