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 // Construct the class.
38 DomainSettingsDialog::DomainSettingsDialog(const int &startType, const QString &domainName) : QDialog(nullptr)
40 // Set the window title.
41 setWindowTitle(i18nc("The domain settings dialog window title", "Domain Settings"));
43 // Set the window modality.
44 setWindowModality(Qt::WindowModality::ApplicationModal);;
46 // Instantiate the domain settings dialog UI.
47 Ui::DomainSettingsDialog domainSettingsDialogUi;
50 domainSettingsDialogUi.setupUi(this);
52 // Get handles for the widgets.
53 domainsListViewPointer = domainSettingsDialogUi.domainsListView;
54 domainSettingsWidgetPointer = domainSettingsDialogUi.domainSettingsWidget;
55 domainNameLineEditPointer = domainSettingsDialogUi.domainNameLineEdit;
56 javaScriptComboBoxPointer = domainSettingsDialogUi.javaScriptComboBox;
57 javaScriptLabelPointer = domainSettingsDialogUi.javaScriptLabel;
58 domStorageComboBoxPointer = domainSettingsDialogUi.domStorageComboBox;
59 domStorageLabelPointer = domainSettingsDialogUi.domStorageLabel;
60 userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
61 userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
62 zoomFactorComboBoxPointer = domainSettingsDialogUi.zoomFactorComboBox;
63 customZoomFactorSpinBoxPointer = domainSettingsDialogUi.customZoomFactorSpinBox;
64 QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
65 deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
66 QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
67 applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
68 resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
70 // Create a table model.
71 domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
73 // Set the table for the model.
74 domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
76 // Set the edit strategy to be manual.
77 domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
79 // Sort the output alphabetically.
80 domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
82 // Set the model for the list view.
83 domainsListViewPointer->setModel(domainsTableModelPointer);
85 // Set the visible column to be the domain name.
86 domainsListViewPointer->setModelColumn(1);
88 // Disable editing of the list view.
89 domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
91 // Read the data from the database and apply it to the table model.
92 domainsTableModelPointer->select();
94 // Setup the dialog according to the start type.
97 case SHOW_ALL_DOMAINS:
99 // Select the first entry in the list view.
100 domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
102 // Populate the domain settings.
103 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
110 // Add the new domain.
111 addDomain(domainName);
118 // Find the index for the new domain. `1` returns the first match.
119 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
120 Qt::DisplayRole, domainName, 1, Qt::MatchWrap);
122 // Move to the new domain.
123 domainsListViewPointer->setCurrentIndex(newDomainIndex[0]);
125 // Populate the domain settings.
126 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
130 // Handle clicks on the domains.
131 connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
133 // Process changes to the domain settings.
134 connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
135 connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
136 connect(domStorageComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(domStorageChanged(int)));
137 connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
138 connect(zoomFactorComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomFactorComboBoxChanged(int)));
139 connect(customZoomFactorSpinBoxPointer, SIGNAL(valueChanged(double)), this, SLOT(customZoomFactorChanged(double)));
141 // Connect the buttons.
142 connect(addDomainButtonPointer, SIGNAL(clicked()), this, SLOT(showAddMessageBox()));
143 connect(deleteDomainButtonPointer, SIGNAL(clicked()), this, SLOT(showDeleteMessageBox()));
144 connect(resetButtonPointer, SIGNAL(clicked()), this, SLOT(reset()));
145 connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
146 connect(applyButtonPointer, SIGNAL(clicked()), this, SLOT(apply()));
147 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
153 void DomainSettingsDialog::addDomain(const QString &domainName) const
155 // Create a new domain record.
156 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
158 // Set the values for the new domain.
159 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), domainName);
160 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
161 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOM_STORAGE), DomainsDatabaseHelper::SYSTEM_DEFAULT);
162 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
163 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
164 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
166 // Insert the new domain. `-1` appends it to the end.
167 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
169 // Submit all pending changes.
170 domainsTableModelPointer->submitAll();
172 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
173 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
174 Qt::DisplayRole, domainName, -1, Qt::MatchWrap);
176 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
177 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
179 // Populate the domain settings.
180 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
187 void DomainSettingsDialog::apply() const
189 // Get the current index.
190 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
192 // Get the ID of the current index row.
193 QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
195 // Submit all pending changes.
196 domainsTableModelPointer->submitAll();
198 // Find the new index for the selected id. The `1` keeps searching after the first match.
199 QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
202 // Select the new index.
203 domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
208 // Emit the domain settings updated signal.
209 emit domainSettingsUpdated();
212 void DomainSettingsDialog::cancel()
214 // Revert all pending changes.
215 domainsTableModelPointer->revertAll();
221 void DomainSettingsDialog::customZoomFactorChanged(const double &newValue) const
223 // Update the domains table model.
224 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)),
231 void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
233 // Update the domains table model.
234 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
240 void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
242 // Populate the domain name line edit pointer.
243 domainNameLineEditPointer->setText(modelIndex.data().toString());
245 // Populate the JavaScript combo box.
246 javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
248 // Populate the DOM storage combo box.
249 domStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOM_STORAGE)).data().toInt());
251 // Get the user agent string.
252 QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
254 // Get the user agent index.
255 int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
257 // Set the user agent combo box index.
258 userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
260 // Set the custom user agent if specified.
261 if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
263 // Get the zoom factor combo box index.
264 int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)).data().toInt();
266 // Populate the zoom factor combo box.
267 zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);
269 // Populate the custom zoom factor spin box according to the zoom factor combo box.
270 if (zoomFactorComboBoxIndex == 0) // System default zoom factor is selected.
272 // Display the default zoom factor.
273 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
275 else // Custom zoom factor is selected.
277 // Display the custom zoom factor from the domain settings.
278 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
281 // Set the initial status of the custom zoom factor spin box.
282 customZoomFactorSpinBoxPointer->setEnabled(zoomFactorComboBoxIndex);
284 // Populate the labels.
285 populateJavaScriptLabel();
286 populateDomStorageLabel();
287 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
293 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
295 // Update the domains table model.
296 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
299 // Populate the JavaScript label.
300 populateJavaScriptLabel();
306 void DomainSettingsDialog::domStorageChanged(const int &newIndex) const
308 // Update the domains table model.
309 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOM_STORAGE)),
312 // Populate the DOM storage label.
313 populateDomStorageLabel();
319 void DomainSettingsDialog::ok()
321 // Submit all pending changes.
322 domainsTableModelPointer->submitAll();
324 // Emit the domain settings updated signal.
325 domainSettingsUpdated();
331 void DomainSettingsDialog::populateJavaScriptLabel() const
333 // Populate the label according to the currently selected index.
334 switch (javaScriptComboBoxPointer->currentIndex())
336 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
338 // Set the text according to the system default.
339 if (Settings::javaScriptEnabled())
340 javaScriptLabelPointer->setText(i18nc("Domains settings label", "JavaScript enabled"));
342 javaScriptLabelPointer->setText(i18nc("Domain settings label", "JavaScript disabled"));
347 case (DomainsDatabaseHelper::DISABLED):
349 // Set the label text in bold.
350 javaScriptLabelPointer->setText(i18nc("Domain settings label. The <b> tags should be retained.", "<b>JavaScript disabled</b>"));
355 case (DomainsDatabaseHelper::ENABLED):
357 // Set the label text in bold.
358 javaScriptLabelPointer->setText(i18nc("Domains settings label. The <b> tags should be retained.", "<b>JavaScript enabled</b>"));
365 void DomainSettingsDialog::populateDomStorageLabel() const
367 // Populate the label according to the currently selected index.
368 switch (domStorageComboBoxPointer->currentIndex())
370 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
372 // Set the text according to the system default.
373 if (Settings::domStorageEnabled())
374 domStorageLabelPointer->setText(i18nc("DOM storage label", "DOM storage enabled"));
376 domStorageLabelPointer->setText(i18nc("DOM storage label", "DOM storage disabled"));
381 case (DomainsDatabaseHelper::DISABLED):
383 // Set the label text in bold.
384 domStorageLabelPointer->setText(i18nc("DOM storage label. The <b> tags should be retained.", "<b>DOM storage disabled</b>"));
389 case (DomainsDatabaseHelper::ENABLED):
391 // Set the label text in bold.
392 domStorageLabelPointer->setText(i18nc("DOM storage label. The <b> tags should be retained.", "<b>DOM storage enabled</b>"));
399 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
401 // Populate the label according to the type.
402 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
404 // Display the system default user agent name.
405 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentNameFromDatabaseName(Settings::userAgent()));
409 // Display the user agent name in bold.
410 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
414 void DomainSettingsDialog::reset() const
416 // Cancel all pending changes.
417 domainsTableModelPointer->revertAll();
419 // Repopulate the domain settings.
420 domainSelected(domainsListViewPointer->currentIndex());
426 void DomainSettingsDialog::showAddMessageBox()
428 // Create an OK flag.
431 // Display a dialog to request the new domain name from the user.
432 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
433 i18nc("Add domain message. The \n\n are newline codes that should be retained",
434 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
435 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
436 QLineEdit::Normal, QString(), &okClicked);
438 // Add the new domain if the user clicked OK.
439 if (okClicked) addDomain(newDomainName);
442 void DomainSettingsDialog::showDeleteMessageBox() const
444 // Instantiate a delete dialog message box.
445 QMessageBox deleteDialogMessageBox;
448 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
450 // Set the window title.
451 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
454 deleteDialogMessageBox.setText(i18nc("Delete domain dialog main message", "Delete the current domain?"));
456 // Set the informative text.
457 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain dialog secondary message", "Doing so will also save any pending changes that have been made to other domains."));
459 // Set the standard buttons.
460 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
462 // Set the default button.
463 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
465 // Display the dialog and capture the return value.
466 int returnValue = deleteDialogMessageBox.exec();
468 // Delete the domain if instructed.
469 if (returnValue == QMessageBox::Yes)
471 // Get the current index.
472 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
474 // Delete the current row.
475 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
477 // Submit all pending changes.
478 domainsTableModelPointer->submitAll();
480 // Select the row next to the deleted item if one exists.
481 if (domainsTableModelPointer->rowCount() > 0)
483 // Check the row of the deleted item.
484 if (currentIndex.row() == 0) // The first row was deleted.
486 // Reselect the current index.
487 domainsListViewPointer->setCurrentIndex(currentIndex);
489 else // A subsequent row was deleted.
491 // Select the crow above the deleted itemm.
492 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
495 // Populate the domain settings.
496 domainSelected(domainsListViewPointer->currentIndex());
504 void DomainSettingsDialog::updateUi() const
506 // Update the delete button status.
507 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
509 // Update the apply button status.
510 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
512 // Update the reset button status.
513 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
515 // Display the domain settings if there is at least one domain.
516 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
519 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
521 // Update the domains table model.
522 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
523 UserAgentHelper::getDatabaseUserAgentNameFromTranslatedName(updatedUserAgent));
525 // Populate the user agent label.
526 populateUserAgentLabel(updatedUserAgent);
532 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
534 // Get the current model index.
535 QModelIndex modelIndex = domainsListViewPointer->selectionModel()->currentIndex();
537 // Update the domains table model.
538 domainsTableModelPointer->setData(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)), newIndex);
540 // Populate the custom zoom factor spin box according to the zoom factor combo box.
541 if (newIndex == 0) // System default zoom factor is selected.
543 // Display the default zoom factor.
544 customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
546 else // Custom zoom factor is selected.
548 // Display the custom zoom factor from the domain settings.
549 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
552 // Update the status of the custom zoom factor spin box.
553 customZoomFactorSpinBoxPointer->setEnabled(newIndex);