Add User Agent to Domain Settings.
[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     userAgentComboBoxPointer = domainSettingsDialogUi.userAgentComboBox;
47     userAgentLabelPointer = domainSettingsDialogUi.userAgentLabel;
48     QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
49     deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
50     QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
51     applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
52     resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
53
54     // Create a table model.
55     domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
56
57     // Set the table for the model.
58     domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
59
60     // Set the edit strategy to be manual.
61     domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
62
63     // Sort the output alphabetically.
64     domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
65
66     // Set the model for the list view.
67     domainsListViewPointer->setModel(domainsTableModelPointer);
68
69     // Set the visible column to be the domain name.
70     domainsListViewPointer->setModelColumn(1);
71
72     // Disable editing of the list view.
73     domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
74
75     // Read the data from the database and apply it to the table model.
76     domainsTableModelPointer->select();
77
78     // Select the first entry in the list view.
79     domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
80
81     // Populate the domain settings.
82     domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
83
84     // Handle clicks on the domains.
85     connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
86
87     // Connect the domain settings.
88     connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
89     connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
90     connect(userAgentComboBoxPointer, SIGNAL(currentTextChanged(QString)), this, SLOT(userAgentChanged(QString)));
91
92     // Connect the buttons.
93     connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
94     connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
95     connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
96     connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
97     connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
98     connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
99
100     // Update the UI.
101     updateUi();
102 }
103
104 void DomainSettingsDialog::apply() const
105 {
106     // Get the current index.
107     QModelIndex currentIndex = domainsListViewPointer->currentIndex();
108
109     // Get the ID of the current index row.
110     QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
111
112     // Submit all pending changes.
113     domainsTableModelPointer->submitAll();
114
115     // Find the new index for the selected id.  The `1` keeps searching after the first match.
116     QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
117                                                                    1, Qt::MatchWrap);
118
119     // Select the new index.
120     domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
121
122     // Update the UI.
123     updateUi();
124
125     // Emit the domain settings updated signal.
126     emit domainSettingsUpdated();
127 }
128
129 void DomainSettingsDialog::cancel()
130 {
131     // Revert all pending changes.
132     domainsTableModelPointer->revertAll();
133
134     // Close the dialog.
135     reject();
136 }
137
138 void DomainSettingsDialog::domainNameChanged(QString updatedDomainName) const
139 {
140     // Update the domains table model.
141     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
142
143     // Update the UI.
144     updateUi();
145 }
146
147
148 void DomainSettingsDialog::domainSelected(QModelIndex modelIndex) const
149 {
150     // Populate the domain name line edit pointer.
151     domainNameLineEditPointer->setText(modelIndex.data().toString());
152
153     // Populate the JavaScript combo box.
154     javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
155
156     // Get the user agent string.
157     QString userAgent = modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)).data().toString();
158
159     // Get the user agent index.
160     int userAgentIndex = UserAgentHelper::getDomainSettingsUserAgentIndex(userAgent);
161
162     // Set the user agent combo box index.
163     userAgentComboBoxPointer->setCurrentIndex(userAgentIndex);
164
165     // Set the custom user agent if specified.
166     if (userAgentIndex == -1) userAgentComboBoxPointer->setCurrentText(userAgent);
167
168     // Populate the labels.
169     populateJavaScriptLabel();
170     populateUserAgentLabel(userAgentComboBoxPointer->currentText());
171
172     // Update the UI.
173     updateUi();
174 }
175
176 void DomainSettingsDialog::javaScriptChanged(int newIndex) const
177 {
178     // Update the domains table model.
179     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
180                                       newIndex);
181
182     // Populate the JavaScript label.
183     populateJavaScriptLabel();
184
185     // Update the UI.
186     updateUi();
187 }
188
189
190 void DomainSettingsDialog::ok()
191 {
192     // Submit all pending changes.
193     domainsTableModelPointer->submitAll();
194
195     // Emit the domain settings updated signal.
196     domainSettingsUpdated();
197
198     // Close the dialog.
199     accept();
200 }
201
202 void DomainSettingsDialog::populateJavaScriptLabel() const
203 {
204     // Populate the label according to the currently selected index.
205     switch (javaScriptComboBoxPointer->currentIndex())
206     {
207         case (DomainsDatabaseHelper::SYSTEM_DEFAULT):
208         {
209             // Set the text according to the system default.
210             if (Settings::javaScript())
211             {
212                 javaScriptLabelPointer->setText(i18nc("Domains settings labels", "JavaScript enabled"));
213             }
214             else
215             {
216                 javaScriptLabelPointer->setText(i18nc("Domain settings labels", "JavaScript disabled"));
217             }
218
219             break;
220         }
221
222         case (DomainsDatabaseHelper::DISABLED):
223         {
224             // Set the label text in bold.
225             javaScriptLabelPointer->setText(i18nc("Domain settings labels.  The <strong> tags should be retained.", "<strong>JavaScript disabled</strong>"));
226
227             break;
228         }
229
230         case (DomainsDatabaseHelper::ENABLED):
231         {
232             // Set the label text in bold.
233             javaScriptLabelPointer->setText(i18nc("Domains settings labels.  The <strong> tags should be retained.", "<strong>JavaScript enabled</strong>"));
234
235             break;
236         }
237     }
238 }
239
240 void DomainSettingsDialog::populateUserAgentLabel(const QString &userAgentName) const
241 {
242     // Populate the label according to the type.
243     if (userAgentName == UserAgentHelper::SYSTEM_DEFAULT_TRANSLATED)
244     {
245         // Display the system default user agent name.
246         userAgentLabelPointer->setText(UserAgentHelper::getTranslatedUserAgentName(Settings::userAgent()));
247     }
248     else
249     {
250         // Display the user agent name in bold.
251         userAgentLabelPointer->setText("<strong>" + userAgentName + "</strong>");
252     }
253 }
254
255 void DomainSettingsDialog::reset() const
256 {
257     // Cancel all pending changes.
258     domainsTableModelPointer->revertAll();
259
260     // Repopulate the domain settings.
261     domainSelected(domainsListViewPointer->currentIndex());
262
263     // Update the UI.
264     updateUi();
265 }
266
267 void DomainSettingsDialog::showAddMessageBox()
268 {
269     // Create an OK flag.
270     bool okClicked;
271
272     // Display a dialog to request the new domain name from the user.
273     QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
274                                                   i18nc("Add domain message.  The \n\n are newline codes that should be retained",
275                                                         "Add a new domain.  Doing so will also save any pending changes that have been made to other domains.\n\n"
276                                                         "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
277                                                   QLineEdit::Normal, QString(), &okClicked);
278
279     // Add the new domain if the user clicked OK.
280     if (okClicked)
281     {
282         // Create a new domain record.
283         QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
284
285         // Add the new domain name.
286         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
287
288         // Set the default value of `0` for JavaScript.
289         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), 0);
290
291         // Set the default value for the user agent.
292         newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT), UserAgentHelper::SYSTEM_DEFAULT_DATABASE);
293
294         // Insert the new domain.  `-1` appends it to the end.
295         domainsTableModelPointer->insertRecord(-1, newDomainRecord);
296
297         // Submit all pending changes.
298         domainsTableModelPointer->submitAll();
299
300         // Find the index for the new domain.  `-1` allows for multiple entries to be returned.
301         QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
302                                                                          Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
303
304         // Move to the new domain.  If there are multiple domains with the same name, the new one should be the last in the list.
305         domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
306
307         // Populate the domain settings.
308         domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
309
310         // Update the UI.
311         updateUi();
312     }
313 }
314
315 void DomainSettingsDialog::showDeleteMessageBox() const
316 {
317     // Instantiate a delete dialog message box.
318     QMessageBox deleteDialogMessageBox;
319
320     // Set the icon.
321     deleteDialogMessageBox.setIcon(QMessageBox::Warning);
322
323     // Set the window title.
324     deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
325
326     // Set the text.
327     deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
328
329     // Set the informative text.
330     deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
331
332     // Set the standard buttons.
333     deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
334
335     // Set the default button.
336     deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
337
338     // Display the dialog and capture the return value.
339     int returnValue = deleteDialogMessageBox.exec();
340
341     if (returnValue == QMessageBox::Yes)
342     {
343         // Get the current index.
344         QModelIndex currentIndex = domainsListViewPointer->currentIndex();
345
346         // Delete the current row.
347         domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
348
349         // Submit all pending changes.
350         domainsTableModelPointer->submitAll();
351
352         // Select the row next to the deleted item if one exists.
353         if (domainsTableModelPointer->rowCount() > 0)
354         {
355             // Check the row of the deleted item.
356             if (currentIndex.row() == 0)  // The first row was deleted.
357             {
358                 // Reselect the current index.
359                 domainsListViewPointer->setCurrentIndex(currentIndex);
360             }
361             else  // A subsequent row was deleted.
362             {
363                 // Select the crow above the deleted itemm.
364                 domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
365             }
366
367             // Populate the domain settings.
368             domainSelected(domainsListViewPointer->currentIndex());
369         }
370
371         // Update the Ui.
372         updateUi();
373     }
374 }
375
376 void DomainSettingsDialog::updateUi() const
377 {
378     // Update the delete button status.
379     deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
380
381     // Update the apply button status.
382     applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
383
384     // Update the reset button status.
385     resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
386
387     // Display the domain settings if there is at least one domain.
388     domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
389 }
390
391 void DomainSettingsDialog::userAgentChanged(const QString updatedUserAgent) const
392 {
393     // Update the domains table model.
394     domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::USER_AGENT)),
395                                       UserAgentHelper::getDatabaseUserAgentName(updatedUserAgent));
396
397     // Populate the user agent label.
398     populateUserAgentLabel(updatedUserAgent);
399
400     // Update the UI.
401     updateUi();
402 }
403
404