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 zoomFactorComboBoxPointer = domainSettingsDialogUi.zoomFactorComboBox;
49 customZoomFactorSpinBoxPointer = domainSettingsDialogUi.customZoomFactorSpinBox;
50 QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
51 deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
52 QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
53 applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
54 resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
56 // Create a table model.
57 domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
59 // Set the table for the model.
60 domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
62 // Set the edit strategy to be manual.
63 domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
65 // Sort the output alphabetically.
66 domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
68 // Set the model for the list view.
69 domainsListViewPointer->setModel(domainsTableModelPointer);
71 // Set the visible column to be the domain name.
72 domainsListViewPointer->setModelColumn(1);
74 // Disable editing of the list view.
75 domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
77 // Read the data from the database and apply it to the table model.
78 domainsTableModelPointer->select();
80 // Select the first entry in the list view.
81 domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
83 // Populate the domain settings.
84 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
86 // Handle clicks on the domains.
87 connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
89 // Connect the domain settings.
90 connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
91 connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
92 connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
93 connect(zoomFactorComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(zoomFactorComboBoxChanged(int)));
94 connect(customZoomFactorSpinBoxPointer, SIGNAL(valueChanged(double)), this, SLOT(customZoomFactorChanged(double)));
96 // Connect the buttons.
97 connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
98 connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
99 connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
100 connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
101 connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
102 connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
108 void DomainSettingsDialog::apply() const
110 // Get the current index.
111 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
113 // Get the ID of the current index row.
114 QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
116 // Submit all pending changes.
117 domainsTableModelPointer->submitAll();
119 // Find the new index for the selected id. The `1` keeps searching after the first match.
120 QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
123 // Select the new index.
124 domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
129 // Emit the domain settings updated signal.
130 emit domainSettingsUpdated();
133 void DomainSettingsDialog::cancel()
135 // Revert all pending changes.
136 domainsTableModelPointer->revertAll();
142 void DomainSettingsDialog::customZoomFactorChanged(const double &newValue) const
144 // Update the domains table model.
145 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)),
153 void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
155 // Update the domains table model.
156 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
163 void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
165 // Populate the domain name line edit pointer.
166 domainNameLineEditPointer->setText(modelIndex.data().toString());
168 // Populate the JavaScript combo box.
169 javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
171 // Get the user agent string.
172 QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
174 // Get the user agent index.
175 int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
177 // Set the user agent combo box index.
178 userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
180 // Set the custom user agent if specified.
181 if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
183 // Get the zoom factor combo box index.
184 int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)).data().toInt();
186 // Populate the zoom factor combo box.
187 zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);
189 // Populate the custom zoom factor spin box.
190 customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
192 // Set the initial visibility of the custom zoom factor spin box.
193 customZoomFactorSpinBoxPointer->setVisible(zoomFactorComboBoxIndex);
195 // Populate the labels.
196 populateJavaScriptLabel();
197 populateUserAgentLabel(userAgentComboBoxPointer->currentText());
203 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
205 // Update the domains table model.
206 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
209 // Populate the JavaScript label.
210 populateJavaScriptLabel();
217 void DomainSettingsDialog::ok()
219 // Submit all pending changes.
220 domainsTableModelPointer->submitAll();
222 // Emit the domain settings updated signal.
223 domainSettingsUpdated();
229 void DomainSettingsDialog::populateJavaScriptLabel() const
231 // Populate the label according to the currently selected index.
232 switch (javaScriptComboBoxPointer->currentIndex())
234 case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
236 // Set the text according to the system default.
237 if (Settings::javaScript())
239 javaScriptLabelPointer->setText(i18nc("Domains settings labels", "JavaScript enabled"));
243 javaScriptLabelPointer->setText(i18nc("Domain settings labels", "JavaScript disabled"));
249 case (DomainsDatabaseHelper::DISABLED):
251 // Set the label text in bold.
252 javaScriptLabelPointer->setText(i18nc("Domain settings labels. The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
257 case (DomainsDatabaseHelper::ENABLED):
259 // Set the label text in bold.
260 javaScriptLabelPointer->setText(i18nc("Domains settings labels. The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
267 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
269 // Populate the label according to the type.
270 if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
272 // Display the system default user agent name.
273 userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
277 // Display the user agent name in bold.
278 userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
282 void DomainSettingsDialog::reset() const
284 // Cancel all pending changes.
285 domainsTableModelPointer->revertAll();
287 // Repopulate the domain settings.
288 domainSelected(domainsListViewPointer->currentIndex());
294 void DomainSettingsDialog::showAddMessageBox()
296 // Create an OK flag.
299 // Display a dialog to request the new domain name from the user.
300 QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
301 i18nc("Add domain message. The \n\n are newline codes that should be retained",
302 "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
303 "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
304 QLineEdit::Normal, QString(), &okClicked);
306 // Add the new domain if the user clicked OK.
309 // Create a new domain record.
310 QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
312 // Set the values for the new domain.
313 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
314 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
315 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
316 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
317 newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
319 // Insert the new domain. `-1` appends it to the end.
320 domainsTableModelPointer->insertRecord(-1, newDomainRecord);
322 // Submit all pending changes.
323 domainsTableModelPointer->submitAll();
325 // Find the index for the new domain. `-1` allows for multiple entries to be returned.
326 QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
327 Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
329 // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
330 domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
332 // Populate the domain settings.
333 domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
340 void DomainSettingsDialog::showDeleteMessageBox() const
342 // Instantiate a delete dialog message box.
343 QMessageBox deleteDialogMessageBox;
346 deleteDialogMessageBox.setIcon(QMessageBox::Warning);
348 // Set the window title.
349 deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
352 deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
354 // Set the informative text.
355 deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
357 // Set the standard buttons.
358 deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
360 // Set the default button.
361 deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
363 // Display the dialog and capture the return value.
364 int returnValue = deleteDialogMessageBox.exec();
366 if (returnValue == QMessageBox::Yes)
368 // Get the current index.
369 QModelIndex currentIndex = domainsListViewPointer->currentIndex();
371 // Delete the current row.
372 domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
374 // Submit all pending changes.
375 domainsTableModelPointer->submitAll();
377 // Select the row next to the deleted item if one exists.
378 if (domainsTableModelPointer->rowCount() > 0)
380 // Check the row of the deleted item.
381 if (currentIndex.row() == 0) // The first row was deleted.
383 // Reselect the current index.
384 domainsListViewPointer->setCurrentIndex(currentIndex);
386 else // A subsequent row was deleted.
388 // Select the crow above the deleted itemm.
389 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
392 // Populate the domain settings.
393 domainSelected(domainsListViewPointer->currentIndex());
401 void DomainSettingsDialog::updateUi() const
403 // Update the delete button status.
404 deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
406 // Update the apply button status.
407 applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
409 // Update the reset button status.
410 resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
412 // Display the domain settings if there is at least one domain.
413 domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
416 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
418 // Update the domains table model.
419 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
420 UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
422 // Populate the user agent label.
423 populateUserAgentLabel(updatedUserAgent);
429 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
431 // Update the domains table model.
432 domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)),
435 // Update the visibility of the custom zoom factor spin box.
436 customZoomFactorSpinBoxPointer->setVisible(newIndex);