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 localStorageComboBoxPointer = domainSettingsDialogUi.localStorageComboBox;
47 localStorageLabelPointer = domainSettingsDialogUi.localStorageLabel;
48 userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
49 userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
50 zoomFactorComboBoxPointer = domainSettingsDialogUi.zoomFactorComboBox;
51 customZoomFactorSpinBoxPointer = domainSettingsDialogUi.customZoomFactorSpinBox;
52 QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
53 deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
54 QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
55 applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
56 resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
58 // Create a table model.
59 domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
61 // Set the table for the model.
62 domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
64 // Set the edit strategy to be manual.
65 domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
67 // Sort the output alphabetically.
68 domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
70 // Set the model for the list view.
71 domainsListViewPointer->setModel(domainsTableModelPointer);
73 // Set the visible column to be the domain name.
74 domainsListViewPointer->setModelColumn(1);
76 // Disable editing of the list view.
77 domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
79 // Read the data from the database and apply it to the table model.
80 domainsTableModelPointer->select();
82 // Select the first entry in the list view.
83 domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
85 // Populate the domain settings.
86 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
88 // Handle clicks on the domains.
89 connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
91 // Connect the domain settings.
92 connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
93 connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
94 connect(localStorageComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(localStorageChanged(int)));
95 connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
96 connect(zoomFactorComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomFactorComboBoxChanged(int)));
97 connect(customZoomFactorSpinBoxPointer, SIGNAL(valueChanged(double)), this, SLOT(customZoomFactorChanged(double)));
99 // Connect the buttons.
100 connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
101 connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
102 connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
103 connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
104 connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
105 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
111 void DomainSettingsDialog::apply() const
113 // Get the current index.
114 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
116 // Get the ID of the current index row.
117 QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
119 // Submit all pending changes.
120 domainsTableModelPointer->submitAll();
122 // Find the new index for the selected id. The `1` keeps searching after the first match.
123 QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
126 // Select the new index.
127 domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
132 // Emit the domain settings updated signal.
133 emit domainSettingsUpdated();
136 void DomainSettingsDialog::cancel()
138 // Revert all pending changes.
139 domainsTableModelPointer->revertAll();
145 void DomainSettingsDialog::customZoomFactorChanged(const double &newValue) const
147 // Update the domains table model.
148 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)),
155 void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
157 // Update the domains table model.
158 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
164 void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
166 // Populate the domain name line edit pointer.
167 domainNameLineEditPointer->setText(modelIndex.data().toString());
169 // Populate the JavaScript combo box.
170 javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
172 // Populate the local storage combo box.
173 localStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)).data().toInt());
175 // Get the user agent string.
176 QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
178 // Get the user agent index.
179 int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
181 // Set the user agent combo box index.
182 userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
184 // Set the custom user agent if specified.
185 if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
187 // Get the zoom factor combo box index.
188 int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)).data().toInt();
190 // Populate the zoom factor combo box.
191 zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);
193 // Populate the custom zoom factor spin box.
194 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
196 // Set the initial visibility of the custom zoom factor spin box.
197 customZoomFactorSpinBoxPointer->setVisible(zoomFactorComboBoxIndex);
199 // Populate the labels.
200 populateJavaScriptLabel();
201 populateLocalStorageLabel();
202 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
208 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
210 // Update the domains table model.
211 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
214 // Populate the JavaScript label.
215 populateJavaScriptLabel();
221 void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
223 // Update the domains table model.
224 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)),
227 // Populate the local storage label.
228 populateLocalStorageLabel();
234 void DomainSettingsDialog::ok()
236 // Submit all pending changes.
237 domainsTableModelPointer->submitAll();
239 // Emit the domain settings updated signal.
240 domainSettingsUpdated();
246 void DomainSettingsDialog::populateJavaScriptLabel() const
248 // Populate the label according to the currently selected index.
249 switch (javaScriptComboBoxPointer->currentIndex())
251 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
253 // Set the text according to the system default.
254 if (Settings::javaScript()) javaScriptLabelPointer->setText(i18nc("Domains settings label", "JavaScript enabled"));
255 else javaScriptLabelPointer->setText(i18nc("Domain settings label", "JavaScript disabled"));
260 case (DomainsDatabaseHelper::DISABLED):
262 // Set the label text in bold.
263 javaScriptLabelPointer->setText(i18nc("Domain settings label. The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
268 case (DomainsDatabaseHelper::ENABLED):
270 // Set the label text in bold.
271 javaScriptLabelPointer->setText(i18nc("Domains settings label. The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
278 void DomainSettingsDialog::populateLocalStorageLabel() const
280 // Populate the label according to the currently selected index.
281 switch (localStorageComboBoxPointer->currentIndex())
283 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
285 // Set the text according to the system default.
286 if (Settings::localStorage()) localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage enabled"));
287 else localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage disabled"));
292 case (DomainsDatabaseHelper::DISABLED):
294 // Set the label text in bold.
295 localStorageLabelPointer->setText(i18nc("Local storage label. The <string> tags should be retained.", "<strong>Local storage disabled</strong>"));
300 case (DomainsDatabaseHelper::ENABLED):
302 // Set the label text in bold.
303 localStorageLabelPointer->setText(i18nc("Local storage label. The <strong> tags should be retained.", "<strong>Local storage enabled</strong>"));
310 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
312 // Populate the label according to the type.
313 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
315 // Display the system default user agent name.
316 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
320 // Display the user agent name in bold.
321 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
325 void DomainSettingsDialog::reset() const
327 // Cancel all pending changes.
328 domainsTableModelPointer->revertAll();
330 // Repopulate the domain settings.
331 domainSelected(domainsListViewPointer->currentIndex());
337 void DomainSettingsDialog::showAddMessageBox()
339 // Create an OK flag.
342 // Display a dialog to request the new domain name from the user.
343 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
344 i18nc("Add domain message. The \n\n are newline codes that should be retained",
345 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
346 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
347 QLineEdit::Normal, QString(), &okClicked);
349 // Add the new domain if the user clicked OK.
352 // Create a new domain record.
353 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
355 // Set the values for the new domain.
356 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
357 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
358 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE), DomainsDatabaseHelper::SYSTEM_DEFAULT);
359 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
360 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
361 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
363 // Insert the new domain. `-1` appends it to the end.
364 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
366 // Submit all pending changes.
367 domainsTableModelPointer->submitAll();
369 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
370 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
371 Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
373 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
374 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
376 // Populate the domain settings.
377 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
384 void DomainSettingsDialog::showDeleteMessageBox() const
386 // Instantiate a delete dialog message box.
387 QMessageBox deleteDialogMessageBox;
390 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
392 // Set the window title.
393 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
396 deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
398 // Set the informative text.
399 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
401 // Set the standard buttons.
402 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
404 // Set the default button.
405 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
407 // Display the dialog and capture the return value.
408 int returnValue = deleteDialogMessageBox.exec();
410 if (returnValue == QMessageBox::Yes)
412 // Get the current index.
413 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
415 // Delete the current row.
416 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
418 // Submit all pending changes.
419 domainsTableModelPointer->submitAll();
421 // Select the row next to the deleted item if one exists.
422 if (domainsTableModelPointer->rowCount() > 0)
424 // Check the row of the deleted item.
425 if (currentIndex.row() == 0) // The first row was deleted.
427 // Reselect the current index.
428 domainsListViewPointer->setCurrentIndex(currentIndex);
430 else // A subsequent row was deleted.
432 // Select the crow above the deleted itemm.
433 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
436 // Populate the domain settings.
437 domainSelected(domainsListViewPointer->currentIndex());
445 void DomainSettingsDialog::updateUi() const
447 // Update the delete button status.
448 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
450 // Update the apply button status.
451 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
453 // Update the reset button status.
454 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
456 // Display the domain settings if there is at least one domain.
457 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
460 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
462 // Update the domains table model.
463 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
464 UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
466 // Populate the user agent label.
467 populateUserAgentLabel(updatedUserAgent);
473 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
475 // Update the domains table model.
476 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)),
479 // Update the visibility of the custom zoom factor spin box.
480 customZoomFactorSpinBoxPointer->setVisible(newIndex);