462b22d4f2c5fc6a41a74240484f28e865753981
[PrivacyBrowserPC.git] / src / dialogs / DomainSettingsDialog.cpp
1 /*
2  * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
5  *
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.
10  *
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.
15  *
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/>.
18  */
19
20 // Application headers.
21 #include "DomainSettingsDialog.h"
22 #include "Settings.h"
23 #include "ui_DomainSettingsDialog.h"
24 #include "helpers/DomainsDatabaseHelper.h"
25 #include "helpers/UserAgentHelper.h"
26
27 // Qt toolkit headers.
28 #include <QInputDialog>
29 #include <QMessageBox>
30 #include <QPushButton>
31
32 DomainSettingsDialog::DomainSettingsDialog(QWidget *parent) : QDialog(parent)
33 {
34     // Instantiate the domain settings view UI.
35     Ui::DomainSettingsDialog domainSettingsDialogUi;
36
37     // Setup the UI.
38     domainSettingsDialogUi.setupUi(this);
39
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);
57
58     // Create a table model.
59     domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
60
61     // Set the table for the model.
62     domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
63
64     // Set the edit strategy to be manual.
65     domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
66
67     // Sort the output alphabetically.
68     domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
69
70     // Set the model for the list view.
71     domainsListViewPointer->setModel(domainsTableModelPointer);
72
73     // Set the visible column to be the domain name.
74     domainsListViewPointer->setModelColumn(1);
75
76     // Disable editing of the list view.
77     domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
78
79     // Read the data from the database and apply it to the table model.
80     domainsTableModelPointer->select();
81
82     // Select the first entry in the list view.
83     domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
84
85     // Populate the domain settings.
86     domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
87
88     // Handle clicks on the domains.
89     connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
90
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)));
98
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()));
106
107     // Update the UI.
108     updateUi();
109 }
110
111 void DomainSettingsDialog::apply() const
112 {
113     // Get the current index.
114     QModelIndex currentIndex = domainsListViewPointer->currentIndex();
115
116     // Get the ID of the current index row.
117     QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
118
119     // Submit all pending changes.
120     domainsTableModelPointer->submitAll();
121
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,
124                                                                    1, Qt::MatchWrap);
125
126     // Select the new index.
127     domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
128
129     // Update the UI.
130     updateUi();
131
132     // Emit the domain settings updated signal.
133     emit domainSettingsUpdated();
134 }
135
136 void DomainSettingsDialog::cancel()
137 {
138     // Revert all pending changes.
139     domainsTableModelPointer->revertAll();
140
141     // Close the dialog.
142     reject();
143 }
144
145 void DomainSettingsDialog::customZoomFactorChanged(const double &newValue) const
146 {
147     // Update the domains table model.
148     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)),
149                                       newValue);
150
151     // Update the UI.
152     updateUi();
153 }
154
155 void DomainSettingsDialog::domainNameChanged(const QString &updatedDomainName) const
156 {
157     // Update the domains table model.
158     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
159
160     // Update the UI.
161     updateUi();
162 }
163
164 void DomainSettingsDialog::domainSelected(const QModelIndex &modelIndex) const
165 {
166     // Populate the domain name line edit pointer.
167     domainNameLineEditPointer->setText(modelIndex.data().toString());
168
169     // Populate the JavaScript combo box.
170     javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
171
172     // Populate the local storage combo box.
173     localStorageComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)).data().toInt());
174
175     // Get the user agent string.
176     QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
177
178     // Get the user agent index.
179     int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
180
181     // Set the user agent combo box index.
182     userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
183
184     // Set the custom user agent if specified.
185     if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
186
187     // Get the zoom factor combo box index.
188     int zoomFactorComboBoxIndex = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)).data().toInt();
189
190     // Populate the zoom factor combo box.
191     zoomFactorComboBoxPointer->setCurrentIndex(zoomFactorComboBoxIndex);
192
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.
195     {
196         // Display the default zoom factor.
197         customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
198     }
199     else  // Custom zoom factor is selected.
200     {
201         // Display the custom zoom factor from the domain settings.
202         customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
203     }
204
205     // Set the initial status of the custom zoom factor spin box.
206     customZoomFactorSpinBoxPointer->setEnabled(zoomFactorComboBoxIndex);
207
208     // Populate the labels.
209     populateJavaScriptLabel();
210     populateLocalStorageLabel();
211     populateUserAgentLabel(userAgentComboBoxPointer->currentText());
212
213     // Update the UI.
214     updateUi();
215 }
216
217 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
218 {
219     // Update the domains table model.
220     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
221                                       newIndex);
222
223     // Populate the JavaScript label.
224     populateJavaScriptLabel();
225
226     // Update the UI.
227     updateUi();
228 }
229
230 void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
231 {
232     // Update the domains table model.
233     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)),
234                                       newIndex);
235
236     // Populate the local storage label.
237     populateLocalStorageLabel();
238
239     // Update the UI.
240     updateUi();
241 }
242
243 void DomainSettingsDialog::ok()
244 {
245     // Submit all pending changes.
246     domainsTableModelPointer->submitAll();
247
248     // Emit the domain settings updated signal.
249     domainSettingsUpdated();
250
251     // Close the dialog.
252     accept();
253 }
254
255 void DomainSettingsDialog::populateJavaScriptLabel() const
256 {
257     // Populate the label according to the currently selected index.
258     switch (javaScriptComboBoxPointer->currentIndex())
259     {
260         case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
261         {
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"));
265
266             break;
267         }
268
269         case (DomainsDatabaseHelper::DISABLED):
270         {
271             // Set the label text in bold.
272             javaScriptLabelPointer->setText(i18nc("Domain settings label.  The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
273
274             break;
275         }
276
277         case (DomainsDatabaseHelper::ENABLED):
278         {
279             // Set the label text in bold.
280             javaScriptLabelPointer->setText(i18nc("Domains settings label.  The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
281
282             break;
283         }
284     }
285 }
286
287 void DomainSettingsDialog::populateLocalStorageLabel() const
288 {
289     // Populate the label according to the currently selected index.
290     switch (localStorageComboBoxPointer->currentIndex())
291     {
292         case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
293         {
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"));
297
298             break;
299         }
300
301         case (DomainsDatabaseHelper::DISABLED):
302         {
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>"));
305
306             break;
307         }
308
309         case (DomainsDatabaseHelper::ENABLED):
310         {
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>"));
313
314             break;
315         }
316     }
317 }
318
319 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
320 {
321     // Populate the label according to the type.
322     if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
323     {
324         // Display the system default user agent name.
325         userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
326     }
327     else
328     {
329         // Display the user agent name in bold.
330         userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
331     }
332 }
333
334 void DomainSettingsDialog::reset() const
335 {
336     // Cancel all pending changes.
337     domainsTableModelPointer->revertAll();
338
339     // Repopulate the domain settings.
340     domainSelected(domainsListViewPointer->currentIndex());
341
342     // Update the UI.
343     updateUi();
344 }
345
346 void DomainSettingsDialog::showAddMessageBox()
347 {
348     // Create an OK flag.
349     bool okClicked;
350
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);
357
358     // Add the new domain if the user clicked OK.
359     if (okClicked)
360     {
361         // Create a new domain record.
362         QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
363
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);
371
372         // Insert the new domain.  `-1` appends it to the end.
373         domainsTableModelPointer->insertRecord(-1, newDomainRecord);
374
375         // Submit all pending changes.
376         domainsTableModelPointer->submitAll();
377
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);
381
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]);
384
385         // Populate the domain settings.
386         domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
387
388         // Update the UI.
389         updateUi();
390     }
391 }
392
393 void DomainSettingsDialog::showDeleteMessageBox() const
394 {
395     // Instantiate a delete dialog message box.
396     QMessageBox deleteDialogMessageBox;
397
398     // Set the icon.
399     deleteDialogMessageBox.setIcon(QMessageBox::Warning);
400
401     // Set the window title.
402     deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
403
404     // Set the text.
405     deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
406
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."));
409
410     // Set the standard buttons.
411     deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
412
413     // Set the default button.
414     deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
415
416     // Display the dialog and capture the return value.
417     int returnValue = deleteDialogMessageBox.exec();
418
419     if (returnValue == QMessageBox::Yes)
420     {
421         // Get the current index.
422         QModelIndex currentIndex = domainsListViewPointer->currentIndex();
423
424         // Delete the current row.
425         domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
426
427         // Submit all pending changes.
428         domainsTableModelPointer->submitAll();
429
430         // Select the row next to the deleted item if one exists.
431         if (domainsTableModelPointer->rowCount() > 0)
432         {
433             // Check the row of the deleted item.
434             if (currentIndex.row() == 0)  // The first row was deleted.
435             {
436                 // Reselect the current index.
437                 domainsListViewPointer->setCurrentIndex(currentIndex);
438             }
439             else  // A subsequent row was deleted.
440             {
441                 // Select the crow above the deleted itemm.
442                 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
443             }
444
445             // Populate the domain settings.
446             domainSelected(domainsListViewPointer->currentIndex());
447         }
448
449         // Update the Ui.
450         updateUi();
451     }
452 }
453
454 void DomainSettingsDialog::updateUi() const
455 {
456     // Update the delete button status.
457     deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
458
459     // Update the apply button status.
460     applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
461
462     // Update the reset button status.
463     resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
464
465     // Display the domain settings if there is at least one domain.
466     domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
467 }
468
469 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
470 {
471     // Update the domains table model.
472     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
473                                       UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
474
475     // Populate the user agent label.
476     populateUserAgentLabel(updatedUserAgent);
477
478     // Update the UI.
479     updateUi();
480 }
481
482 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
483 {
484     // Get the current model index.
485     QModelIndex modelIndex = domainsListViewPointer->selectionModel()->currentIndex();
486
487     // Update the domains table model.
488     domainsTableModelPointer->setData(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)), newIndex);
489
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.
492     {
493         // Display the default zoom factor.
494         customZoomFactorSpinBoxPointer->setValue(Settings::zoomFactor());
495     }
496     else  // Custom zoom factor is selected.
497     {
498         // Display the custom zoom factor from the domain settings.
499         customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
500     }
501
502     // Update the status of the custom zoom factor spin box.
503     customZoomFactorSpinBoxPointer->setEnabled(newIndex);
504
505     // Update the UI.
506     updateUi();
507 }