File indexing completed on 2024-05-19 05:01:24

0001 /*
0002     This file is part of the KDE project.
0003 
0004     SPDX-FileCopyrightText: 2020 Stefano Crocco <posta@stefanocrocco.it>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "webfieldsdataview.h"
0010 
0011 #include <KLocalizedString>
0012 
0013 #include <QEvent>
0014 #include <QHeaderView>
0015 
0016 using WebForm = WebEngineWallet::WebForm;
0017 using WebFormList = WebEngineWallet::WebFormList;
0018 using WebField = WebEngineWallet::WebForm::WebField;
0019 using WebFieldType = WebEngineWallet::WebForm::WebFieldType;
0020 
0021 WebFieldsDataViewPasswordDelegate::WebFieldsDataViewPasswordDelegate(QObject* parent): QStyledItemDelegate(parent)
0022 {
0023 }
0024 
0025 bool WebFieldsDataViewPasswordDelegate::isPassword(const QModelIndex& idx)
0026 {
0027     return idx.data(WebFieldsDataModel::PasswordRole).toBool();
0028 }
0029 
0030 QString WebFieldsDataViewPasswordDelegate::passwordReplacement(const QStyleOptionViewItem& option, const QModelIndex& index)
0031 {
0032         const QWidget *w = option.widget;
0033         QStyle *s = w->style();
0034         QChar passwdChar(s->styleHint(QStyle::StyleHint::SH_LineEdit_PasswordCharacter, &option, w));
0035         return QString(index.data().toString().length(), passwdChar);
0036 }
0037 
0038 void WebFieldsDataViewPasswordDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0039 {
0040     if (!isPassword(index)) {
0041         QStyledItemDelegate::paint(painter, option, index);
0042     } else {
0043         QString str = passwordReplacement(option, index);
0044         option.widget->style()->drawItemText(painter, option.rect, index.data(Qt::TextAlignmentRole).toInt(), option.palette, true, str);
0045     }
0046 }
0047 
0048 QSize WebFieldsDataViewPasswordDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0049 {
0050     if (!isPassword(index)) {
0051         return QStyledItemDelegate::sizeHint(option, index);
0052     } else {
0053         QString str = passwordReplacement(option, index);
0054         return option.widget->style()->itemTextRect(option.fontMetrics, option.rect, option.displayAlignment, true, str).size();
0055     }
0056 }
0057 
0058 WebFieldsDataModel::WebFieldsDataModel(bool checkableItems, QObject* parent): QStandardItemModel(parent), m_checkableItems(checkableItems)
0059 {
0060     setHorizontalHeaderLabels({"",
0061         i18nc("Label of a web field", "Field name"),
0062         i18nc("Value of a web field", "Field value"),
0063         i18nc("Name attribute of a web field", "Internal field name"),
0064         i18nc("Type of a web field", "Field type"),
0065         i18nc("The id of a web field", "Field id"),
0066         i18nc("Other details about a web field", "Details")});
0067 }
0068 
0069 WebFieldsDataModel::~WebFieldsDataModel()
0070 {
0071 }
0072 
0073 void WebFieldsDataModel::setForms(const WebEngineWallet::WebFormList &forms)
0074 {
0075     m_forms = forms;
0076     removeRows(0, rowCount());
0077     for (int i = 0; i < m_forms.size(); ++i) {
0078         const WebForm &form = m_forms.at(i);
0079         for (int j = 0; j < form.fields.size(); ++j) {
0080             const WebField &field = form.fields.at(j);
0081             appendRow(createRowForField(field, i, j));
0082         }
0083     }
0084 }
0085 
0086 void WebFieldsDataModel::clearForms()
0087 {
0088     m_forms.clear();
0089     removeRows(0, rowCount());
0090 }
0091 
0092 WebEngineWallet::WebFormList WebFieldsDataModel::checkedFields() const
0093 {
0094     if (!m_checkableItems) {
0095         return {};
0096     }
0097     QMap<int, QVector<int>> fields;
0098     for (int i = 0; i < rowCount(); ++i) {
0099         QStandardItem *it  = item(i, ChosenCol);
0100         if (it->checkState() == Qt::Checked) {
0101             fields[it->data(FormRole).toInt()].append(it->data(FieldRole).toInt());
0102         }
0103     }
0104     WebFormList lst;
0105     for (QMap<int, QVector<int>>::const_iterator it = fields.constBegin(); it != fields.constEnd(); ++it) {
0106         if (it.value().isEmpty()) {
0107             continue;
0108         }
0109         const WebForm &oldForm = m_forms.at(it.key());
0110         WebForm form(oldForm);
0111         form.fields.clear();
0112         for (int i : it.value()) {
0113             form.fields.append(oldForm.fields.at(i));
0114         }
0115         lst.append(form);
0116     }
0117     return lst;
0118 }
0119 
0120 QList<QStandardItem *> WebFieldsDataModel::createRowForField(const WebEngineWallet::WebForm::WebField& field, int formIndex, int fieldIndex)
0121 {
0122     QString type = WebForm::fieldNameFromType(field.type, true);
0123     QStringList notes;
0124     if (field.readOnly) {
0125         notes << i18nc("web field has the readonly attribute", "read only");
0126     }
0127     if (!field.autocompleteAllowed) {
0128         notes << i18nc("web field has the autocomplete attribute set to off", "auto-completion off");
0129     }
0130     if (field.disabled) {
0131         notes << i18nc("web field is disabled", "disabled");
0132     }
0133     QString label = !field.label.isEmpty() ? field.label : field.name;
0134     QStringList contents{QString(), label, field.value, field.name, type, field.id, notes.join(", ")};
0135     QList<QStandardItem*> row;
0136     row.reserve(contents.size());
0137     auto itemFromString = [](const QString &s){
0138         QStandardItem *it = new QStandardItem(s);
0139         it->setTextAlignment(Qt::AlignCenter);
0140         return it;
0141     };
0142     std::transform(contents.constBegin(), contents.constEnd(), std::back_inserter(row), itemFromString);
0143     row[ValueCol]->setData(field.type == WebFieldType::Password, PasswordRole);
0144     if (m_checkableItems) {
0145         row[ChosenCol]->setCheckable(true);
0146     }
0147     QString toolTip = toolTipForField(field);
0148     row[LabelCol]->setToolTip(toolTip);
0149     row[ValueCol]->setToolTip(toolTip);
0150     QStandardItem *chosen = row.at(ChosenCol);
0151     chosen->setData(formIndex, FormRole);
0152     chosen->setData(fieldIndex, FieldRole);
0153     return row;
0154 }
0155 
0156 QString WebFieldsDataModel::toolTipForField(const WebEngineWallet::WebForm::WebField& field)
0157 {
0158     QString type = WebForm::fieldNameFromType(field.type, true);
0159     const QString yes = i18nc("A statement about a field is true", "yes");
0160     const QString no = i18nc("A statement about a field is false", "no");
0161     auto boolToYesNo = [yes, no](bool val){return val ? yes : no;};
0162     QString toolTip = i18n(
0163         "<ul><li><b>Field internal name: </b>%1</li>"
0164         "<li><b>Field type: </b>%2</li>"
0165         "<li><b>Field id: </b>%3</li>"
0166         "<li><b>Field is read only: </b>%4</li>"
0167         "<li><b>Field is enabled: </b>%5</li>"
0168         "<li><b>Autocompletion is enabled: </b>%6</li>"
0169         "</ul>",
0170         field.name, type, field.id, boolToYesNo(field.readOnly), boolToYesNo(!field.disabled), boolToYesNo(field.autocompleteAllowed));
0171     return toolTip;
0172 }
0173 
0174 WebFieldsDataView::WebFieldsDataView(QWidget* parent): QTableView(parent),
0175     m_passwordDelegate(new WebFieldsDataViewPasswordDelegate(this)), m_showPasswords(false), 
0176     m_showDetails(false), m_showToolTips(true)
0177 {
0178     setItemDelegateForColumn(WebFieldsDataModel::ValueCol, m_passwordDelegate);
0179     setEditTriggers(QAbstractItemView::NoEditTriggers);
0180     verticalHeader()->hide();
0181 }
0182 
0183 WebFieldsDataView::~WebFieldsDataView()
0184 {
0185 }
0186 
0187 void WebFieldsDataView::setModel(QAbstractItemModel* model)
0188 {
0189     QTableView::setModel(model);
0190     setDetailsVisible(m_showDetails);
0191     horizontalHeader()->setStretchLastSection(true);
0192     horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
0193     WebFieldsDataModel *m = qobject_cast<WebFieldsDataModel*>(model);
0194     if (m) {
0195         setColumnHidden(WebFieldsDataModel::ChosenCol, !m->areItemsCheckable());
0196     }
0197 }
0198 
0199 void WebFieldsDataView::togglePasswords(bool show)
0200 {
0201     if (m_showPasswords == show) {
0202         return;
0203     }
0204     m_showPasswords = show;
0205     setItemDelegateForColumn(WebFieldsDataModel::ValueCol, show ? itemDelegate() : m_passwordDelegate);
0206 }
0207 
0208 void WebFieldsDataView::toggleDetails(bool show)
0209 {
0210     if (m_showDetails == show) {
0211         return;
0212     }
0213     setDetailsVisible(show);
0214 }
0215 
0216 void WebFieldsDataView::setDetailsVisible(bool visible)
0217 {
0218     m_showDetails = visible;
0219     for (int i = WebFieldsDataModel::InternalNameCol; i <= WebFieldsDataModel::DetailsCol; ++i) {
0220         setColumnHidden(i, !visible);
0221     }
0222 }
0223 
0224 void WebFieldsDataView::toggleToolTips(bool show)
0225 {
0226     m_showToolTips = show;
0227 }
0228 
0229 bool WebFieldsDataView::viewportEvent(QEvent* e)
0230 {
0231     if (!m_showToolTips && (e->type() == QEvent::ToolTip || e->type() == QEvent::ToolTipChange)) {
0232         e->accept();
0233         return true;
0234     } else {
0235         return QTableView::viewportEvent(e);
0236     }
0237 }
0238 
0239 QSize WebFieldsDataView::sizeHint() const
0240 {
0241     QSize hint = QTableView::sizeHint();
0242     int h = 2*frameWidth();
0243     if (horizontalHeader()->isVisible()) {
0244         h += horizontalHeader()->height();
0245     }
0246     if (model() && model()->rowCount() != 0) {
0247         h += rowHeight(0) * model()->rowCount();
0248     }
0249     hint.setHeight(h);
0250     return hint;
0251 }