2 * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
6 * Privacy Browser PC 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 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser PC is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser PC. If not, see <http://www.gnu.org/licenses/>.
20 // Application headers.
21 #include "DomainSettingsDialog.h"
23 #include "ui_DomainSettingsDialog.h"
24 #include "helpers/DomainsDatabaseHelper.h"
25 #include "helpers/UserAgentHelper.h"
27 // Qt toolkit headers.
28 #include <QInputDialog>
29 #include <QMessageBox>
30 #include <QPushButton>
32 DomainSettingsDialog::DomainSettingsDialog(QWidget *parent) : QDialog(parent)
34 // Instantiate the domain settings view UI.
35 Ui::DomainSettingsDialog domainSettingsDialogUi;
38 domainSettingsDialogUi.setupUi(this);
40 // Get handles for the views.
41 domainsListViewPointer = domainSettingsDialogUi.domainsListView;
42 domainSettingsWidgetPointer = domainSettingsDialogUi.domainSettingsWidget;
43 domainNameLineEditPointer = domainSettingsDialogUi.domainNameLineEdit;
44 javaScriptComboBoxPointer = domainSettingsDialogUi.javaScriptComboBox;
45 javaScriptLabelPointer = domainSettingsDialogUi.javaScriptLabel;
46 userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
47 userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
48 QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
49 deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
50 QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
51 applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
52 resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
54 // Create a table model.
55 domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
57 // Set the table for the model.
58 domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
60 // Set the edit strategy to be manual.
61 domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
63 // Sort the output alphabetically.
64 domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
66 // Set the model for the list view.
67 domainsListViewPointer->setModel(domainsTableModelPointer);
69 // Set the visible column to be the domain name.
70 domainsListViewPointer->setModelColumn(1);
72 // Disable editing of the list view.
73 domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
75 // Read the data from the database and apply it to the table model.
76 domainsTableModelPointer->select();
78 // Select the first entry in the list view.
79 domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
81 // Populate the domain settings.
82 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
84 // Handle clicks on the domains.
85 connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
87 // Connect the domain settings.
88 connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
89 connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
90 connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
92 // Connect the buttons.
93 connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
94 connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
95 connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
96 connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
97 connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
98 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
104 void DomainSettingsDialog::apply() const
106 // Get the current index.
107 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
109 // Get the ID of the current index row.
110 QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
112 // Submit all pending changes.
113 domainsTableModelPointer->submitAll();
115 // Find the new index for the selected id. The `1` keeps searching after the first match.
116 QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
119 // Select the new index.
120 domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
125 // Emit the domain settings updated signal.
126 emit domainSettingsUpdated();
129 void DomainSettingsDialog::cancel()
131 // Revert all pending changes.
132 domainsTableModelPointer->revertAll();
138 void DomainSettingsDialog::domainNameChanged(QString updatedDomainName) const
140 // Update the domains table model.
141 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
148 void DomainSettingsDialog::domainSelected(QModelIndex modelIndex) const
150 // Populate the domain name line edit pointer.
151 domainNameLineEditPointer->setText(modelIndex.data().toString());
153 // Populate the JavaScript combo box.
154 javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
156 // Get the user agent string.
157 QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
159 // Get the user agent index.
160 int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
162 // Set the user agent combo box index.
163 userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
165 // Set the custom user agent if specified.
166 if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
168 // Populate the labels.
169 populateJavaScriptLabel();
170 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
176 void DomainSettingsDialog::javaScriptChanged(int newIndex) const
178 // Update the domains table model.
179 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
182 // Populate the JavaScript label.
183 populateJavaScriptLabel();
190 void DomainSettingsDialog::ok()
192 // Submit all pending changes.
193 domainsTableModelPointer->submitAll();
195 // Emit the domain settings updated signal.
196 domainSettingsUpdated();
202 void DomainSettingsDialog::populateJavaScriptLabel() const
204 // Populate the label according to the currently selected index.
205 switch (javaScriptComboBoxPointer->currentIndex())
207 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
209 // Set the text according to the system default.
210 if (Settings::javaScript())
212 javaScriptLabelPointer->setText(i18nc("Domains settings labels", "JavaScript enabled"));
216 javaScriptLabelPointer->setText(i18nc("Domain settings labels", "JavaScript disabled"));
222 case (DomainsDatabaseHelper::DISABLED):
224 // Set the label text in bold.
225 javaScriptLabelPointer->setText(i18nc("Domain settings labels. The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
230 case (DomainsDatabaseHelper::ENABLED):
232 // Set the label text in bold.
233 javaScriptLabelPointer->setText(i18nc("Domains settings labels. The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
240 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
242 // Populate the label according to the type.
243 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
245 // Display the system default user agent name.
246 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
250 // Display the user agent name in bold.
251 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
255 void DomainSettingsDialog::reset() const
257 // Cancel all pending changes.
258 domainsTableModelPointer->revertAll();
260 // Repopulate the domain settings.
261 domainSelected(domainsListViewPointer->currentIndex());
267 void DomainSettingsDialog::showAddMessageBox()
269 // Create an OK flag.
272 // Display a dialog to request the new domain name from the user.
273 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
274 i18nc("Add domain message. The \n\n are newline codes that should be retained",
275 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
276 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
277 QLineEdit::Normal, QString(), &okClicked);
279 // Add the new domain if the user clicked OK.
282 // Create a new domain record.
283 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
285 // Add the new domain name.
286 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
288 // Set the default value of `0` for JavaScript.
289 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), 0);
291 // Set the default value for the user agent.
292 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
294 // Insert the new domain. `-1` appends it to the end.
295 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
297 // Submit all pending changes.
298 domainsTableModelPointer->submitAll();
300 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
301 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
302 Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
304 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
305 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
307 // Populate the domain settings.
308 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
315 void DomainSettingsDialog::showDeleteMessageBox() const
317 // Instantiate a delete dialog message box.
318 QMessageBox deleteDialogMessageBox;
321 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
323 // Set the window title.
324 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
327 deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
329 // Set the informative text.
330 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
332 // Set the standard buttons.
333 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
335 // Set the default button.
336 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
338 // Display the dialog and capture the return value.
339 int returnValue = deleteDialogMessageBox.exec();
341 if (returnValue == QMessageBox::Yes)
343 // Get the current index.
344 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
346 // Delete the current row.
347 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
349 // Submit all pending changes.
350 domainsTableModelPointer->submitAll();
352 // Select the row next to the deleted item if one exists.
353 if (domainsTableModelPointer->rowCount() > 0)
355 // Check the row of the deleted item.
356 if (currentIndex.row() == 0) // The first row was deleted.
358 // Reselect the current index.
359 domainsListViewPointer->setCurrentIndex(currentIndex);
361 else // A subsequent row was deleted.
363 // Select the crow above the deleted itemm.
364 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
367 // Populate the domain settings.
368 domainSelected(domainsListViewPointer->currentIndex());
376 void DomainSettingsDialog::updateUi() const
378 // Update the delete button status.
379 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
381 // Update the apply button status.
382 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
384 // Update the reset button status.
385 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
387 // Display the domain settings if there is at least one domain.
388 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
391 void DomainSettingsDialog::userAgentChanged(const QString updatedUserAgent) const
393 // Update the domains table model.
394 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
395 UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
397 // Populate the user agent label.
398 populateUserAgentLabel(updatedUserAgent);