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 according to the zoom factor combo box.
194 if (zoomFactorComboBoxIndex == 0) // System default zoom factor is selected.
196 // Display the default zoom factor.
197 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
199 else // Custom zoom factor is selected.
201 // Display the custom zoom factor from the domain settings.
202 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
205 // Set the initial status of the custom zoom factor spin box.
206 customZoomFactorSpinBoxPointer->setEnabled(zoomFactorComboBoxIndex);
208 // Populate the labels.
209 populateJavaScriptLabel();
210 populateLocalStorageLabel();
211 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
217 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
219 // Update the domains table model.
220 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
223 // Populate the JavaScript label.
224 populateJavaScriptLabel();
230 void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
232 // Update the domains table model.
233 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)),
236 // Populate the local storage label.
237 populateLocalStorageLabel();
243 void DomainSettingsDialog::ok()
245 // Submit all pending changes.
246 domainsTableModelPointer->submitAll();
248 // Emit the domain settings updated signal.
249 domainSettingsUpdated();
255 void DomainSettingsDialog::populateJavaScriptLabel() const
257 // Populate the label according to the currently selected index.
258 switch (javaScriptComboBoxPointer->currentIndex())
260 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
262 // Set the text according to the system default.
263 if (Settings::javaScript()) javaScriptLabelPointer->setText(i18nc("Domains settings label", "JavaScript enabled"));
264 else javaScriptLabelPointer->setText(i18nc("Domain settings label", "JavaScript disabled"));
269 case (DomainsDatabaseHelper::DISABLED):
271 // Set the label text in bold.
272 javaScriptLabelPointer->setText(i18nc("Domain settings label. The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
277 case (DomainsDatabaseHelper::ENABLED):
279 // Set the label text in bold.
280 javaScriptLabelPointer->setText(i18nc("Domains settings label. The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
287 void DomainSettingsDialog::populateLocalStorageLabel() const
289 // Populate the label according to the currently selected index.
290 switch (localStorageComboBoxPointer->currentIndex())
292 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
294 // Set the text according to the system default.
295 if (Settings::localStorage()) localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage enabled"));
296 else localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage disabled"));
301 case (DomainsDatabaseHelper::DISABLED):
303 // Set the label text in bold.
304 localStorageLabelPointer->setText(i18nc("Local storage label. The <string> tags should be retained.", "<strong>Local storage disabled</strong>"));
309 case (DomainsDatabaseHelper::ENABLED):
311 // Set the label text in bold.
312 localStorageLabelPointer->setText(i18nc("Local storage label. The <strong> tags should be retained.", "<strong>Local storage enabled</strong>"));
319 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
321 // Populate the label according to the type.
322 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
324 // Display the system default user agent name.
325 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
329 // Display the user agent name in bold.
330 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
334 void DomainSettingsDialog::reset() const
336 // Cancel all pending changes.
337 domainsTableModelPointer->revertAll();
339 // Repopulate the domain settings.
340 domainSelected(domainsListViewPointer->currentIndex());
346 void DomainSettingsDialog::showAddMessageBox()
348 // Create an OK flag.
351 // Display a dialog to request the new domain name from the user.
352 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
353 i18nc("Add domain message. The \n\n are newline codes that should be retained",
354 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
355 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
356 QLineEdit::Normal, QString(), &okClicked);
358 // Add the new domain if the user clicked OK.
361 // Create a new domain record.
362 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
364 // Set the values for the new domain.
365 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
366 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
367 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE), DomainsDatabaseHelper::SYSTEM_DEFAULT);
368 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
369 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
370 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
372 // Insert the new domain. `-1` appends it to the end.
373 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
375 // Submit all pending changes.
376 domainsTableModelPointer->submitAll();
378 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
379 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
380 Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
382 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
383 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
385 // Populate the domain settings.
386 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
393 void DomainSettingsDialog::showDeleteMessageBox() const
395 // Instantiate a delete dialog message box.
396 QMessageBox deleteDialogMessageBox;
399 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
401 // Set the window title.
402 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
405 deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
407 // Set the informative text.
408 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
410 // Set the standard buttons.
411 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
413 // Set the default button.
414 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
416 // Display the dialog and capture the return value.
417 int returnValue = deleteDialogMessageBox.exec();
419 if (returnValue == QMessageBox::Yes)
421 // Get the current index.
422 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
424 // Delete the current row.
425 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
427 // Submit all pending changes.
428 domainsTableModelPointer->submitAll();
430 // Select the row next to the deleted item if one exists.
431 if (domainsTableModelPointer->rowCount() > 0)
433 // Check the row of the deleted item.
434 if (currentIndex.row() == 0) // The first row was deleted.
436 // Reselect the current index.
437 domainsListViewPointer->setCurrentIndex(currentIndex);
439 else // A subsequent row was deleted.
441 // Select the crow above the deleted itemm.
442 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
445 // Populate the domain settings.
446 domainSelected(domainsListViewPointer->currentIndex());
454 void DomainSettingsDialog::updateUi() const
456 // Update the delete button status.
457 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
459 // Update the apply button status.
460 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
462 // Update the reset button status.
463 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
465 // Display the domain settings if there is at least one domain.
466 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
469 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
471 // Update the domains table model.
472 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
473 UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
475 // Populate the user agent label.
476 populateUserAgentLabel(updatedUserAgent);
482 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
484 // Get the current model index.
485 QModelIndex modelIndex = domainsListViewPointer->selectionModel()->currentIndex();
487 // Update the domains table model.
488 domainsTableModelPointer->setData(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)), newIndex);
490 // Populate the custom zoom factor spin box according to the zoom factor combo box.
491 if (newIndex == 0) // System default zoom factor is selected.
493 // Display the default zoom factor.
494 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
496 else // Custom zoom factor is selected.
498 // Display the custom zoom factor from the domain settings.
499 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
502 // Update the status of the custom zoom factor spin box.
503 customZoomFactorSpinBoxPointer->setEnabled(newIndex);