]> gitweb.stoutner.com Git - PrivacyBrowserPC.git/blob - src/dialogs/DomainSettingsDialog.cpp
Add controls for local storage. https://redmine.stoutner.com/issues/830
[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.
194     customZoomFactorSpinBoxPointer->setValue(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR)).data().toDouble());
195
196     // Set the initial visibility of the custom zoom factor spin box.
197     customZoomFactorSpinBoxPointer->setVisible(zoomFactorComboBoxIndex);
198
199     // Populate the labels.
200     populateJavaScriptLabel();
201     populateLocalStorageLabel();
202     populateUserAgentLabel(userAgentComboBoxPointer->currentText());
203
204     // Update the UI.
205     updateUi();
206 }
207
208 void DomainSettingsDialog::javaScriptChanged(const int &newIndex) const
209 {
210     // Update the domains table model.
211     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
212                                       newIndex);
213
214     // Populate the JavaScript label.
215     populateJavaScriptLabel();
216
217     // Update the UI.
218     updateUi();
219 }
220
221 void DomainSettingsDialog::localStorageChanged(const int &newIndex) const
222 {
223     // Update the domains table model.
224     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE)),
225                                       newIndex);
226
227     // Populate the local storage label.
228     populateLocalStorageLabel();
229
230     // Update the UI.
231     updateUi();
232 }
233
234 void DomainSettingsDialog::ok()
235 {
236     // Submit all pending changes.
237     domainsTableModelPointer->submitAll();
238
239     // Emit the domain settings updated signal.
240     domainSettingsUpdated();
241
242     // Close the dialog.
243     accept();
244 }
245
246 void DomainSettingsDialog::populateJavaScriptLabel() const
247 {
248     // Populate the label according to the currently selected index.
249     switch (javaScriptComboBoxPointer->currentIndex())
250     {
251         case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
252         {
253             // Set the text according to the system default.
254             if (Settings::javaScript()) javaScriptLabelPointer->setText(i18nc("Domains settings label", "JavaScript enabled"));
255             else javaScriptLabelPointer->setText(i18nc("Domain settings label", "JavaScript disabled"));
256
257             break;
258         }
259
260         case (DomainsDatabaseHelper::DISABLED):
261         {
262             // Set the label text in bold.
263             javaScriptLabelPointer->setText(i18nc("Domain settings label.  The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
264
265             break;
266         }
267
268         case (DomainsDatabaseHelper::ENABLED):
269         {
270             // Set the label text in bold.
271             javaScriptLabelPointer->setText(i18nc("Domains settings label.  The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
272
273             break;
274         }
275     }
276 }
277
278 void DomainSettingsDialog::populateLocalStorageLabel() const
279 {
280     // Populate the label according to the currently selected index.
281     switch (localStorageComboBoxPointer->currentIndex())
282     {
283         case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
284         {
285             // Set the text according to the system default.
286             if (Settings::localStorage()) localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage enabled"));
287             else localStorageLabelPointer->setText(i18nc("Local storage label", "Local storage disabled"));
288
289             break;
290         }
291
292         case (DomainsDatabaseHelper::DISABLED):
293         {
294             // Set the label text in bold.
295             localStorageLabelPointer->setText(i18nc("Local storage label.  The <string> tags should be retained.", "<strong>Local storage disabled</strong>"));
296
297             break;
298         }
299
300         case (DomainsDatabaseHelper::ENABLED):
301         {
302             // Set the label text in bold.
303             localStorageLabelPointer->setText(i18nc("Local storage label.  The <strong> tags should be retained.", "<strong>Local storage enabled</strong>"));
304
305             break;
306         }
307     }
308 }
309
310 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
311 {
312     // Populate the label according to the type.
313     if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
314     {
315         // Display the system default user agent name.
316         userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
317     }
318     else
319     {
320         // Display the user agent name in bold.
321         userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
322     }
323 }
324
325 void DomainSettingsDialog::reset() const
326 {
327     // Cancel all pending changes.
328     domainsTableModelPointer->revertAll();
329
330     // Repopulate the domain settings.
331     domainSelected(domainsListViewPointer->currentIndex());
332
333     // Update the UI.
334     updateUi();
335 }
336
337 void DomainSettingsDialog::showAddMessageBox()
338 {
339     // Create an OK flag.
340     bool okClicked;
341
342     // Display a dialog to request the new domain name from the user.
343     QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
344                                                   i18nc("Add domain message.  The \n\n are newline codes that should be retained",
345                                                         "Add a new domain.  Doing so will also save any pending changes that have been made to other domains.\n\n"
346                                                         "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
347                                                   QLineEdit::Normal, QString(), &okClicked);
348
349     // Add the new domain if the user clicked OK.
350     if (okClicked)
351     {
352         // Create a new domain record.
353         QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
354
355         // Set the values for the new domain.
356         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
357         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), DomainsDatabaseHelper::SYSTEM_DEFAULT);
358         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::LOCAL_STORAGE), DomainsDatabaseHelper::SYSTEM_DEFAULT);
359         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
360         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR), DomainsDatabaseHelper::SYSTEM_DEFAULT);
361         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::CUSTOM_ZOOM_FACTOR), 1.0);
362
363         // Insert the new domain.  `-1` appends it to the end.
364         domainsTableModelPointer->insertRecord(-1, newDomainRecord);
365
366         // Submit all pending changes.
367         domainsTableModelPointer->submitAll();
368
369         // Find the index for the new domain.  `-1` allows for multiple entries to be returned.
370         QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
371                                                                          Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
372
373         // Move to the new domain.  If there are multiple domains with the same name, the new one should be the last in the list.
374         domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
375
376         // Populate the domain settings.
377         domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
378
379         // Update the UI.
380         updateUi();
381     }
382 }
383
384 void DomainSettingsDialog::showDeleteMessageBox() const
385 {
386     // Instantiate a delete dialog message box.
387     QMessageBox deleteDialogMessageBox;
388
389     // Set the icon.
390     deleteDialogMessageBox.setIcon(QMessageBox::Warning);
391
392     // Set the window title.
393     deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
394
395     // Set the text.
396     deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
397
398     // Set the informative text.
399     deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
400
401     // Set the standard buttons.
402     deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
403
404     // Set the default button.
405     deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
406
407     // Display the dialog and capture the return value.
408     int returnValue = deleteDialogMessageBox.exec();
409
410     if (returnValue == QMessageBox::Yes)
411     {
412         // Get the current index.
413         QModelIndex currentIndex = domainsListViewPointer->currentIndex();
414
415         // Delete the current row.
416         domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
417
418         // Submit all pending changes.
419         domainsTableModelPointer->submitAll();
420
421         // Select the row next to the deleted item if one exists.
422         if (domainsTableModelPointer->rowCount() > 0)
423         {
424             // Check the row of the deleted item.
425             if (currentIndex.row() == 0)  // The first row was deleted.
426             {
427                 // Reselect the current index.
428                 domainsListViewPointer->setCurrentIndex(currentIndex);
429             }
430             else  // A subsequent row was deleted.
431             {
432                 // Select the crow above the deleted itemm.
433                 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
434             }
435
436             // Populate the domain settings.
437             domainSelected(domainsListViewPointer->currentIndex());
438         }
439
440         // Update the Ui.
441         updateUi();
442     }
443 }
444
445 void DomainSettingsDialog::updateUi() const
446 {
447     // Update the delete button status.
448     deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
449
450     // Update the apply button status.
451     applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
452
453     // Update the reset button status.
454     resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
455
456     // Display the domain settings if there is at least one domain.
457     domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
458 }
459
460 void DomainSettingsDialog::userAgentChanged(const QString &updatedUserAgent) const
461 {
462     // Update the domains table model.
463     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
464                                       UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
465
466     // Populate the user agent label.
467     populateUserAgentLabel(updatedUserAgent);
468
469     // Update the UI.
470     updateUi();
471 }
472
473 void DomainSettingsDialog::zoomFactorComboBoxChanged(const int &newIndex) const
474 {
475     // Update the domains table model.
476     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::ZOOM_FACTOR)),
477                                       newIndex);
478
479     // Update the visibility of the custom zoom factor spin box.
480     customZoomFactorSpinBoxPointer->setVisible(newIndex);
481
482     // Update the UI.
483     updateUi();
484 }