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 // Define the public static int constants.
33 const int DomainSettingsDialog::SHOW_ALL_DOMAINS = 0;
34 const int DomainSettingsDialog::ADD_DOMAIN = 1;
35 const int DomainSettingsDialog::EDIT_DOMAIN = 2;
37 DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &domainName) : QDialog(nullptr)
39 // Instantiate the domain settings view UI.
40 Ui::DomainSettingsDialog domainSettingsDialogUi;
43 domainSettingsDialogUi.setupUi(this);
45 // Get handles for the views.
46 domainsListViewPointer = domainSettingsDialogUi.domainsListView;
47 domainSettingsWidgetPointer = domainSettingsDialogUi.domainSettingsWidget;
48 domainNameLineEditPointer = domainSettingsDialogUi.domainNameLineEdit;
49 javaScriptComboBoxPointer = domainSettingsDialogUi.javaScriptComboBox;
50 javaScriptLabelPointer = domainSettingsDialogUi.javaScriptLabel;
51 localStorageComboBoxPointer = domainSettingsDialogUi.localStorageComboBox;
52 localStorageLabelPointer = domainSettingsDialogUi.localStorageLabel;
53 userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
54 userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
55 zoomFactorComboBoxPointer = domainSettingsDialogUi.zoomFactorComboBox;
56 customZoomFactorSpinBoxPointer = domainSettingsDialogUi.customZoomFactorSpinBox;
57 QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
58 deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
59 QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
60 applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
61 resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
63 // Create a table model.
64 domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
66 // Set the table for the model.
67 domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
69 // Set the edit strategy to be manual.
70 domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
72 // Sort the output alphabetically.
73 domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
75 // Set the model for the list view.
76 domainsListViewPointer->setModel(domainsTableModelPointer);
78 // Set the visible column to be the domain name.
79 domainsListViewPointer->setModelColumn(1);
81 // Disable editing of the list view.
82 domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
84 // Read the data from the database and apply it to the table model.
85 domainsTableModelPointer->select();
87 // Setup the dialog according to the start type.
90 case SHOW_ALL_DOMAINS:
92 // Select the first entry in the list view.
93 domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
95 // Populate the domain settings.
96 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
103 // Add the new domain.
104 addDomain(domainName);
111 // Find the index for the new domain. `1` returns the first match.
112 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
113 Qt::DisplayRole, domainName, 1, Qt::MatchWrap);
115 // Move to the new domain.
116 domainsListViewPointer->setCurrentIndex(newDomainIndex[0]);
118 // Populate the domain settings.
119 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
123 // Handle clicks on the domains.
124 connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
126 // Connect the domain settings.
127 connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
128 connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
129 connect(localStorageComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(localStorageChanged(int)));
130 connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
131 connect(zoomFactorComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomFactorComboBoxChanged(int)));
132 connect(customZoomFactorSpinBoxPointer, SIGNAL(valueChanged(double)), this, SLOT(customZoomFactorChanged(double)));
134 // Connect the buttons.
135 connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
136 connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
137 connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
138 connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
139 connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
140 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
146 void DomainSettingsDialog::addDomain(const QString &domainName) const
148 // Create a new domain record.
149 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
151 // Set the values for the new domain.
152 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), domainName);
153 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
154 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE), DomainsDatabaseHelper::SYSTEM_DEFAULT);
155 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
156 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
157 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
159 // Insert the new domain. `-1` appends it to the end.
160 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
162 // Submit all pending changes.
163 domainsTableModelPointer->submitAll();
165 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
166 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
167 Qt::DisplayRole, domainName, -1, Qt::MatchWrap);
169 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
170 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
172 // Populate the domain settings.
173 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
180 void DomainSettingsDialog::apply() const
182 // Get the current index.
183 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
185 // Get the ID of the current index row.
186 QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
188 // Submit all pending changes.
189 domainsTableModelPointer->submitAll();
191 // Find the new index for the selected id. The `1` keeps searching after the first match.
192 QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
195 // Select the new index.
196 domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
201 // Emit the domain settings updated signal.
202 emit domainSettingsUpdated();
205 void DomainSettingsDialog::cancel()
207 // Revert all pending changes.
208 domainsTableModelPointer->revertAll();
214 void DomainSettingsDialog::customZoomFactorChanged(const double &newValue) const
216 // Update the domains table model.
217 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)),
224 void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
226 // Update the domains table model.
227 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
233 void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
235 // Populate the domain name line edit pointer.
236 domainNameLineEditPointer->setText(modelIndex.data().toString());
238 // Populate the JavaScript combo box.
239 javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
241 // Populate the local storage combo box.
242 localStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)).data().toInt());
244 // Get the user agent string.
245 QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
247 // Get the user agent index.
248 int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
250 // Set the user agent combo box index.
251 userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
253 // Set the custom user agent if specified.
254 if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
256 // Get the zoom factor combo box index.
257 int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)).data().toInt();
259 // Populate the zoom factor combo box.
260 zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);
262 // Populate the custom zoom factor spin box according to the zoom factor combo box.
263 if (zoomFactorComboBoxIndex == 0) // System default zoom factor is selected.
265 // Display the default zoom factor.
266 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
268 else // Custom zoom factor is selected.
270 // Display the custom zoom factor from the domain settings.
271 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
274 // Set the initial status of the custom zoom factor spin box.
275 customZoomFactorSpinBoxPointer->setEnabled(zoomFactorComboBoxIndex);
277 // Populate the labels.
278 populateJavaScriptLabel();
279 populateLocalStorageLabel();
280 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
286 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
288 // Update the domains table model.
289 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
292 // Populate the JavaScript label.
293 populateJavaScriptLabel();
299 void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
301 // Update the domains table model.
302 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)),
305 // Populate the local storage label.
306 populateLocalStorageLabel();
312 void DomainSettingsDialog::ok()
314 // Submit all pending changes.
315 domainsTableModelPointer->submitAll();
317 // Emit the domain settings updated signal.
318 domainSettingsUpdated();
324 void DomainSettingsDialog::populateJavaScriptLabel() const
326 // Populate the label according to the currently selected index.
327 switch (javaScriptComboBoxPointer->currentIndex())
329 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
331 // Set the text according to the system default.
332 if (Settings::javaScript()) javaScriptLabelPointer->setText(i18nc("Domains settings label", "JavaScript enabled"));
333 else javaScriptLabelPointer->setText(i18nc("Domain settings label", "JavaScript disabled"));
338 case (DomainsDatabaseHelper::DISABLED):
340 // Set the label text in bold.
341 javaScriptLabelPointer->setText(i18nc("Domain settings label. The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
346 case (DomainsDatabaseHelper::ENABLED):
348 // Set the label text in bold.
349 javaScriptLabelPointer->setText(i18nc("Domains settings label. The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
356 void DomainSettingsDialog::populateLocalStorageLabel() const
358 // Populate the label according to the currently selected index.
359 switch (localStorageComboBoxPointer->currentIndex())
361 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
363 // Set the text according to the system default.
364 if (Settings::localStorage()) localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage enabled"));
365 else localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage disabled"));
370 case (DomainsDatabaseHelper::DISABLED):
372 // Set the label text in bold.
373 localStorageLabelPointer->setText(i18nc("Local storage label. The <string> tags should be retained.", "<strong>Local storage disabled</strong>"));
378 case (DomainsDatabaseHelper::ENABLED):
380 // Set the label text in bold.
381 localStorageLabelPointer->setText(i18nc("Local storage label. The <strong> tags should be retained.", "<strong>Local storage enabled</strong>"));
388 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
390 // Populate the label according to the type.
391 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
393 // Display the system default user agent name.
394 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
398 // Display the user agent name in bold.
399 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
403 void DomainSettingsDialog::reset() const
405 // Cancel all pending changes.
406 domainsTableModelPointer->revertAll();
408 // Repopulate the domain settings.
409 domainSelected(domainsListViewPointer->currentIndex());
415 void DomainSettingsDialog::showAddMessageBox()
417 // Create an OK flag.
420 // Display a dialog to request the new domain name from the user.
421 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
422 i18nc("Add domain message. The \n\n are newline codes that should be retained",
423 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
424 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
425 QLineEdit::Normal, QString(), &okClicked);
427 // Add the new domain if the user clicked OK.
428 if (okClicked) addDomain(newDomainName);
431 void DomainSettingsDialog::showDeleteMessageBox() const
433 // Instantiate a delete dialog message box.
434 QMessageBox deleteDialogMessageBox;
437 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
439 // Set the window title.
440 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
443 deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
445 // Set the informative text.
446 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
448 // Set the standard buttons.
449 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
451 // Set the default button.
452 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
454 // Display the dialog and capture the return value.
455 int returnValue = deleteDialogMessageBox.exec();
457 if (returnValue == QMessageBox::Yes)
459 // Get the current index.
460 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
462 // Delete the current row.
463 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
465 // Submit all pending changes.
466 domainsTableModelPointer->submitAll();
468 // Select the row next to the deleted item if one exists.
469 if (domainsTableModelPointer->rowCount() > 0)
471 // Check the row of the deleted item.
472 if (currentIndex.row() == 0) // The first row was deleted.
474 // Reselect the current index.
475 domainsListViewPointer->setCurrentIndex(currentIndex);
477 else // A subsequent row was deleted.
479 // Select the crow above the deleted itemm.
480 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
483 // Populate the domain settings.
484 domainSelected(domainsListViewPointer->currentIndex());
492 void DomainSettingsDialog::updateUi() const
494 // Update the delete button status.
495 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
497 // Update the apply button status.
498 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
500 // Update the reset button status.
501 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
503 // Display the domain settings if there is at least one domain.
504 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
507 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
509 // Update the domains table model.
510 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
511 UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
513 // Populate the user agent label.
514 populateUserAgentLabel(updatedUserAgent);
520 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
522 // Get the current model index.
523 QModelIndex modelIndex = domainsListViewPointer->selectionModel()->currentIndex();
525 // Update the domains table model.
526 domainsTableModelPointer->setData(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)), newIndex);
528 // Populate the custom zoom factor spin box according to the zoom factor combo box.
529 if (newIndex == 0) // System default zoom factor is selected.
531 // Display the default zoom factor.
532 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
534 else // Custom zoom factor is selected.
536 // Display the custom zoom factor from the domain settings.
537 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
540 // Update the status of the custom zoom factor spin box.
541 customZoomFactorSpinBoxPointer->setEnabled(newIndex);