+/*
+ * Copyright © 2022 Soren Stoutner <soren@stoutner.com>.
+ *
+ * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
+ *
+ * Privacy Browser PC is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Privacy Browser PC is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Privacy Browser PC. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+// Application headers.
+#include "DomainSettingsDialog.h"
+#include "ui_DomainSettingsDialog.h"
+#include "helpers/DomainsDatabaseHelper.h"
+
+// Qt toolkit headers.
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QPushButton>
+
+DomainSettingsDialog::DomainSettingsDialog(QWidget *parent) : QDialog(parent)
+{
+ // Instantiate the domain settings view UI.
+ Ui::DomainSettingsDialog domainSettingsDialogUi;
+
+ // Setup the UI.
+ domainSettingsDialogUi.setupUi(this);
+
+ // Get handles for the views.
+ domainsListViewPointer = domainSettingsDialogUi.domainsListView;
+ domainSettingsWidgetPointer = domainSettingsDialogUi.domainSettingsWidget;
+ domainNameLineEditPointer = domainSettingsDialogUi.domainNameLineEdit;
+ javaScriptComboBoxPointer = domainSettingsDialogUi.javaScriptComboBox;
+ QPushButton *addDomainButtonPointer = domainSettingsDialogUi.addDomainButton;
+ deleteDomainButtonPointer = domainSettingsDialogUi.deleteDomainButton;
+ QDialogButtonBox *dialogButtonBoxPointer = domainSettingsDialogUi.dialogButtonBox;
+ applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Apply);
+ resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::StandardButton::Reset);
+
+ // Create a table model.
+ domainsTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME));
+
+ // Set the table for the model.
+ domainsTableModelPointer->setTable(DomainsDatabaseHelper::DOMAINS_TABLE);
+
+ // Set the edit strategy to be manual.
+ domainsTableModelPointer->setEditStrategy(QSqlTableModel::EditStrategy::OnManualSubmit);
+
+ // Sort the output alphabetically.
+ domainsTableModelPointer->setSort(1, Qt::SortOrder::AscendingOrder);
+
+ // Set the model for the list view.
+ domainsListViewPointer->setModel(domainsTableModelPointer);
+
+ // Set the visible column to be the domain name.
+ domainsListViewPointer->setModelColumn(1);
+
+ // Disable editing of the list view.
+ domainsListViewPointer->setEditTriggers(QAbstractItemView::NoEditTriggers);
+
+ // Read the data from the database and apply it to the table model.
+ domainsTableModelPointer->select();
+
+ // Select the first entry in the list view.
+ domainsListViewPointer->setCurrentIndex(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
+
+ // Populate the domain settings.
+ domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
+
+ // Handle clicks on the domains.
+ connect(domainsListViewPointer, SIGNAL(activated(QModelIndex)), this, SLOT(domainSelected(QModelIndex)));
+
+ // Connect the domain settings.
+ connect(domainNameLineEditPointer, SIGNAL(textEdited(QString)), this, SLOT(domainNameChanged(QString)));
+ connect(javaScriptComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(javaScriptChanged(int)));
+
+ // Connect the buttons.
+ connect(addDomainButtonPointer, SIGNAL(released()), this, SLOT(showAddMessageBox()));
+ connect(deleteDomainButtonPointer, SIGNAL(released()), this, SLOT(showDeleteMessageBox()));
+ connect(resetButtonPointer, SIGNAL(released()), this, SLOT(reset()));
+ connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
+ connect(applyButtonPointer, SIGNAL(released()), this, SLOT(apply()));
+ connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(cancel()));
+
+ // Update the UI.
+ updateUi();
+}
+
+void DomainSettingsDialog::apply() const
+{
+ // Get the current index.
+ QModelIndex currentIndex = domainsListViewPointer->currentIndex();
+
+ // Get the ID of the current index row.
+ QVariant currentId = currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)).data();
+
+ // Submit all pending changes.
+ domainsTableModelPointer->submitAll();
+
+ // Find the new index for the selected id. The `1` keeps searching after the first match.
+ QModelIndexList newIndexList = domainsTableModelPointer->match(currentIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::_ID)), Qt::DisplayRole, currentId,
+ 1, Qt::MatchWrap);
+
+ // Select the new index.
+ domainsListViewPointer->setCurrentIndex(newIndexList[0].siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)));
+
+ // Update the UI.
+ updateUi();
+}
+
+void DomainSettingsDialog::cancel()
+{
+ // Revert all pending changes.
+ domainsTableModelPointer->revertAll();
+
+ // Close the dialog.
+ reject();
+}
+
+void DomainSettingsDialog::domainNameChanged(QString updatedDomainName) const
+{
+ // Update the domains table model.
+ domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex(), updatedDomainName);
+
+ // Update the UI.
+ updateUi();
+}
+
+
+void DomainSettingsDialog::domainSelected(QModelIndex modelIndex) const
+{
+ // Populate the domain name line edit pointer.
+ domainNameLineEditPointer->setText(modelIndex.data().toString());
+
+ // Populate the JavaScript combo box.
+ javaScriptComboBoxPointer->setCurrentIndex(modelIndex.siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)).data().toInt());
+
+ // Update the UI.
+ updateUi();
+}
+
+void DomainSettingsDialog::javaScriptChanged(int newIndex) const
+{
+ // Update the domains table model.
+ domainsTableModelPointer->setData(domainsListViewPointer->selectionModel()->currentIndex().siblingAtColumn(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT)),
+ newIndex);
+
+ // Update the UI.
+ updateUi();
+}
+
+
+void DomainSettingsDialog::ok()
+{
+ // Submit all pending changes.
+ domainsTableModelPointer->submitAll();
+
+ // Close the dialog.
+ accept();
+}
+
+void DomainSettingsDialog::reset() const
+{
+ // Cancel all pending changes.
+ domainsTableModelPointer->revertAll();
+
+ // Repopulate the domain name line edit.
+ domainNameLineEditPointer->setText(domainsListViewPointer->currentIndex().data().toString());
+
+ // Update the UI.
+ updateUi();
+}
+
+void DomainSettingsDialog::showAddMessageBox()
+{
+ // Create an OK flag.
+ bool okClicked;
+
+ // Display a dialog to request the new domain name from the user.
+ QString newDomainName = QInputDialog::getText(this, i18nc("Add domain dialog title", "Add Domain"),
+ i18nc("Add domain message. The \n\n are newline codes that should be retained",
+ "Add a new domain. Doing so will also save any pending changes that have been made to other domains.\n\n"
+ "*. may be prepended to a domain to include all subdomains (eg. *.stoutner.com)."),
+ QLineEdit::Normal, QString(), &okClicked);
+
+ // Add the new domain if the user clicked OK.
+ if (okClicked)
+ {
+ // Create a new domain record.
+ QSqlRecord newDomainRecord = QSqlDatabase::database(DomainsDatabaseHelper::CONNECTION_NAME).record(DomainsDatabaseHelper::DOMAINS_TABLE);
+
+ // Add the new domain name.
+ newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME), newDomainName);
+
+ // Set the default value of `0` for the other columns.
+ newDomainRecord.setValue(domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::JAVASCRIPT), 0);
+
+ // Insert the new domain. `-1` appends it to the end.
+ domainsTableModelPointer->insertRecord(-1, newDomainRecord);
+
+ // Submit all pending changes.
+ domainsTableModelPointer->submitAll();
+
+ // Find the index for the new domain. `-1` allows for multiple entries to be returned.
+ QModelIndexList newDomainIndex = domainsTableModelPointer->match(domainsTableModelPointer->index(0, domainsTableModelPointer->fieldIndex(DomainsDatabaseHelper::DOMAIN_NAME)),
+ Qt::DisplayRole, newDomainName, -1, Qt::MatchWrap);
+
+ // Move to the new domain. If there are multiple domains with the same name, the new one should be the last in the list.
+ domainsListViewPointer->setCurrentIndex(newDomainIndex[newDomainIndex.size() - 1]);
+
+ // Populate the domain settings.
+ domainSelected(domainsListViewPointer->selectionModel()->currentIndex());
+
+ // Update the UI.
+ updateUi();
+ }
+}
+
+void DomainSettingsDialog::showDeleteMessageBox() const
+{
+ // Instantiate a delete dialog message box.
+ QMessageBox deleteDialogMessageBox;
+
+ // Set the icon.
+ deleteDialogMessageBox.setIcon(QMessageBox::Warning);
+
+ // Set the window title.
+ deleteDialogMessageBox.setWindowTitle(i18nc("Delete domain dialog title", "Delete Domain"));
+
+ // Set the text.
+ deleteDialogMessageBox.setText(i18nc("Delete domain main message", "Delete the current domain?"));
+
+ // Set the informative text.
+ deleteDialogMessageBox.setInformativeText(i18nc("Delete domain secondary message", "Doing so will also save any pending changes that have been made to other domains."));
+
+ // Set the standard buttons.
+ deleteDialogMessageBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+
+ // Set the default button.
+ deleteDialogMessageBox.setDefaultButton(QMessageBox::No);
+
+ // Display the dialog and capture the return value.
+ int returnValue = deleteDialogMessageBox.exec();
+
+ if (returnValue == QMessageBox::Yes)
+ {
+ // Get the current index.
+ QModelIndex currentIndex = domainsListViewPointer->currentIndex();
+
+ // Delete the current row.
+ domainsTableModelPointer->removeRow(domainsListViewPointer->selectionModel()->currentIndex().row());
+
+ // Submit all pending changes.
+ domainsTableModelPointer->submitAll();
+
+ // Select the row next to the deleted item if one exists.
+ if (domainsTableModelPointer->rowCount() > 0)
+ {
+ // Check the row of the deleted item.
+ if (currentIndex.row() == 0) // The first row was deleted.
+ {
+ // Reselect the current index.
+ domainsListViewPointer->setCurrentIndex(currentIndex);
+ }
+ else // A subsequent row was deleted.
+ {
+ // Select the crow above the deleted itemm.
+ domainsListViewPointer->setCurrentIndex(currentIndex.siblingAtRow(currentIndex.row() - 1));
+ }
+
+ // Populate the domain settings.
+ domainSelected(domainsListViewPointer->currentIndex());
+ }
+
+ // Update the Ui.
+ updateUi();
+ }
+}
+
+void DomainSettingsDialog::updateUi() const
+{
+ // Update the delete button status.
+ deleteDomainButtonPointer->setEnabled(domainsListViewPointer->selectionModel()->hasSelection());
+
+ // Update the apply button status.
+ applyButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
+
+ // Update the reset button status.
+ resetButtonPointer->setEnabled(domainsTableModelPointer->isDirty());
+
+ // Display the domain settings if there is at least one domain.
+ domainSettingsWidgetPointer->setVisible(domainsTableModelPointer->rowCount() > 0);
+}
+