File indexing completed on 2024-04-28 15:51:59

0001 /*
0002     SPDX-FileCopyrightText: 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@gmail.com>
0003     SPDX-FileCopyrightText: 2023 g10 Code GmbH
0004     SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "signaturepartutils.h"
0010 
0011 #include "core/document.h"
0012 #include "core/form.h"
0013 #include "core/page.h"
0014 #include "pageview.h"
0015 
0016 #include <QAction>
0017 #include <QApplication>
0018 #include <QFileDialog>
0019 #include <QFileInfo>
0020 #include <QImageReader>
0021 #include <QInputDialog>
0022 #include <QLabel>
0023 #include <QListView>
0024 #include <QMenu>
0025 #include <QMimeDatabase>
0026 #include <QPainter>
0027 #include <QPushButton>
0028 #include <QStandardItemModel>
0029 #include <QVBoxLayout>
0030 
0031 #include "ui_selectcertificatedialog.h"
0032 #include <KConfigGroup>
0033 #include <KLocalizedString>
0034 #include <KMessageBox>
0035 #include <KSharedConfig>
0036 namespace
0037 {
0038 static inline QString ConfigGroup()
0039 {
0040     return QStringLiteral("Signature");
0041 }
0042 static inline QString ConfigBackgroundKey()
0043 {
0044     return QStringLiteral("RecentBackgrounds");
0045 }
0046 static inline QString ConfigLastReason()
0047 {
0048     return QStringLiteral("Reason");
0049 }
0050 static inline QString ConfigLastLocation()
0051 {
0052     return QStringLiteral("Location");
0053 }
0054 static inline QString ConfigLastKeyNick()
0055 {
0056     return QStringLiteral("KeyNick");
0057 }
0058 
0059 }
0060 
0061 namespace SignaturePartUtils
0062 {
0063 static QImage scaleAndFitCanvas(const QImage &input, const QSize expectedSize)
0064 {
0065     if (input.size() == expectedSize) {
0066         return input;
0067     }
0068     const auto scaled = input.scaled(expectedSize, Qt::KeepAspectRatio);
0069     if (scaled.size() == expectedSize) {
0070         return scaled;
0071     }
0072     QImage canvas(expectedSize, QImage::Format_ARGB32);
0073     canvas.fill(Qt::transparent);
0074     const auto scaledSize = scaled.size();
0075     QPoint topLeft((expectedSize.width() - scaledSize.width()) / 2, (expectedSize.height() - scaledSize.height()) / 2);
0076     QPainter painter(&canvas);
0077     painter.drawImage(topLeft, scaled);
0078     return canvas;
0079 }
0080 
0081 class ImageItemDelegate : public QStyledItemDelegate
0082 {
0083     Q_OBJECT
0084 public:
0085     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
0086     {
0087         auto style = option.widget ? option.widget->style() : QApplication::style();
0088         // This paints the background without initializing the
0089         // styleoption from the actual index. Given we want default background
0090         // and paint the foreground a bit later
0091         // This accomplishes it quite nicely.
0092         style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
0093         const auto path = index.data(Qt::DisplayRole).value<QString>();
0094 
0095         QImageReader reader(path);
0096         const QSize imageSize = reader.size();
0097         if (!reader.size().isNull()) {
0098             reader.setScaledSize(imageSize.scaled(option.rect.size(), Qt::KeepAspectRatio));
0099         }
0100         const auto input = reader.read();
0101         if (!input.isNull()) {
0102             const auto scaled = scaleAndFitCanvas(input, option.rect.size());
0103             painter->drawImage(option.rect.topLeft(), scaled);
0104         }
0105     }
0106     QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
0107     {
0108         Q_UNUSED(index);
0109         QSize defaultSize(10, 10); // let's start with a square
0110         if (auto view = qobject_cast<QListView *>(option.styleObject)) {
0111             auto frameRect = view->frameRect().size();
0112             frameRect.setWidth((frameRect.width() - view->style()->pixelMetric(QStyle::PM_ScrollBarExtent)) / 2 - 2 * view->frameWidth() - view->spacing());
0113             return defaultSize.scaled(frameRect, Qt::KeepAspectRatio);
0114         }
0115         return defaultSize;
0116     }
0117 };
0118 
0119 class RecentImagesModel : public QAbstractListModel
0120 {
0121     Q_OBJECT
0122 public:
0123     RecentImagesModel()
0124     {
0125         const auto recentList = KSharedConfig::openConfig()->group(ConfigGroup()).readEntry<QStringList>(ConfigBackgroundKey(), QStringList());
0126         for (const auto &element : recentList) {
0127             if (QFile::exists(element)) { // maybe the image has been removed from disk since last invocation
0128                 m_storedElements.push_back(element);
0129             }
0130         }
0131     }
0132     QVariant roleFromString(const QString &data, int role) const
0133     {
0134         switch (role) {
0135         case Qt::DisplayRole:
0136         case Qt::ToolTipRole:
0137             return data;
0138         default:
0139             return QVariant();
0140         }
0141     }
0142     QVariant data(const QModelIndex &index, int role) const override
0143     {
0144         Q_ASSERT(checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));
0145         int row = index.row();
0146         if (m_selectedFromFileSystem.has_value()) {
0147             if (row == 0) {
0148                 return roleFromString(*m_selectedFromFileSystem, role);
0149             } else {
0150                 row--;
0151             }
0152         }
0153         if (row < m_storedElements.size()) {
0154             return roleFromString(m_storedElements.at(row), role);
0155         }
0156         return QVariant();
0157     }
0158     int rowCount(const QModelIndex &parent = {}) const override
0159     {
0160         if (parent.isValid()) {
0161             return 0;
0162         }
0163         return m_storedElements.size() + (m_selectedFromFileSystem.has_value() ? 1 : 0);
0164     }
0165     void setFileSystemSelection(const QString &selection)
0166     {
0167         if (m_storedElements.contains(selection)) {
0168             return;
0169         }
0170         if (selection.isEmpty()) {
0171             if (!m_selectedFromFileSystem) {
0172                 return;
0173             }
0174             beginRemoveRows(QModelIndex(), 0, 0);
0175             m_selectedFromFileSystem.reset();
0176             endRemoveRows();
0177             return;
0178         }
0179         if (!QFile::exists(selection)) {
0180             return;
0181         }
0182         if (m_selectedFromFileSystem) {
0183             m_selectedFromFileSystem = selection;
0184             Q_EMIT dataChanged(index(0, 0), index(0, 0));
0185         } else {
0186             beginInsertRows(QModelIndex(), 0, 0);
0187             m_selectedFromFileSystem = selection;
0188             endInsertRows();
0189         }
0190     }
0191     void clear()
0192     {
0193         beginResetModel();
0194         m_selectedFromFileSystem = {};
0195         m_storedElements.clear();
0196         endResetModel();
0197     }
0198     void removeItem(const QString &text)
0199     {
0200         if (text == m_selectedFromFileSystem) {
0201             beginRemoveRows(QModelIndex(), 0, 0);
0202             m_selectedFromFileSystem.reset();
0203             endRemoveRows();
0204             return;
0205         }
0206         auto elementIndex = m_storedElements.indexOf(text);
0207         auto beginRemove = elementIndex;
0208         if (m_selectedFromFileSystem) {
0209             beginRemove++;
0210         }
0211         beginRemoveRows(QModelIndex(), beginRemove, beginRemove);
0212         m_storedElements.removeAt(elementIndex);
0213         endRemoveRows();
0214     }
0215     void saveBack()
0216     {
0217         QStringList elementsToStore = m_storedElements;
0218         if (m_selectedFromFileSystem) {
0219             elementsToStore.push_front(*m_selectedFromFileSystem);
0220         }
0221         while (elementsToStore.size() > 3) {
0222             elementsToStore.pop_back();
0223         }
0224         KSharedConfig::openConfig()->group(ConfigGroup()).writeEntry(ConfigBackgroundKey(), elementsToStore);
0225     }
0226 
0227 private:
0228     std::optional<QString> m_selectedFromFileSystem;
0229     QStringList m_storedElements;
0230 };
0231 
0232 std::optional<SigningInformation> getCertificateAndPasswordForSigning(PageView *pageView, Okular::Document *doc, SigningInformationOptions opts)
0233 {
0234     const Okular::CertificateStore *certStore = doc->certificateStore();
0235     bool userCancelled, nonDateValidCerts;
0236     QList<Okular::CertificateInfo> certs = certStore->signingCertificatesForNow(&userCancelled, &nonDateValidCerts);
0237     if (userCancelled) {
0238         return std::nullopt;
0239     }
0240 
0241     if (certs.isEmpty()) {
0242         pageView->showNoSigningCertificatesDialog(nonDateValidCerts);
0243         return std::nullopt;
0244     }
0245     QString password;
0246     QString documentPassword;
0247 
0248     QStandardItemModel items;
0249     QHash<QString, Okular::CertificateInfo> nickToCert;
0250     qsizetype minWidth = -1;
0251     bool showIcons = false;
0252     int selectIndex = 0;
0253     auto config = KSharedConfig::openConfig();
0254     const QString lastNick = config->group(ConfigGroup()).readEntry<QString>(ConfigLastKeyNick(), QString());
0255     for (const auto &cert : std::as_const(certs)) {
0256         auto item = std::make_unique<QStandardItem>();
0257         QString commonName = cert.subjectInfo(Okular::CertificateInfo::CommonName, Okular::CertificateInfo::EmptyString::Empty);
0258         item->setData(commonName, Qt::UserRole);
0259         QString emailAddress = cert.subjectInfo(Okular::CertificateInfo::EmailAddress, Okular::CertificateInfo::EmptyString::Empty);
0260         item->setData(emailAddress, Qt::UserRole + 1);
0261 
0262         minWidth = std::max(minWidth, std::max(cert.nickName().size(), emailAddress.size() + commonName.size()));
0263 
0264         switch (cert.keyLocation()) {
0265         case Okular::CertificateInfo::KeyLocation::Computer:
0266             item->setData(QIcon::fromTheme(QStringLiteral("view-certificate")), Qt::DecorationRole);
0267             showIcons = true;
0268             break;
0269         case Okular::CertificateInfo::KeyLocation::HardwareToken:
0270             /* Better icon requested in https://bugs.kde.org/show_bug.cgi?id=428278*/
0271             item->setData(QIcon::fromTheme(QStringLiteral("auth-sim")), Qt::DecorationRole);
0272             showIcons = true;
0273             break;
0274         case Okular::CertificateInfo::KeyLocation::Unknown:; //
0275             break;
0276         case Okular::CertificateInfo::KeyLocation::Other:
0277             break;
0278         }
0279 
0280         item->setData(cert.nickName(), Qt::DisplayRole);
0281         item->setData(cert.subjectInfo(Okular::CertificateInfo::DistinguishedName, Okular::CertificateInfo::EmptyString::Empty), Qt::ToolTipRole);
0282         item->setEditable(false);
0283         if (cert.nickName() == lastNick) {
0284             selectIndex = items.rowCount();
0285         }
0286         items.appendRow(item.release());
0287         nickToCert[cert.nickName()] = cert;
0288     }
0289 
0290     SelectCertificateDialog dialog(pageView);
0291     auto keyDelegate = new KeyDelegate(dialog.ui->list);
0292     keyDelegate->showIcon = showIcons;
0293     dialog.ui->list->setItemDelegate(keyDelegate);
0294     QFontMetrics fm = dialog.fontMetrics();
0295     dialog.ui->list->setMinimumWidth(fm.averageCharWidth() * (minWidth + 5));
0296     dialog.ui->list->setModel(&items);
0297     dialog.ui->list->selectionModel()->select(items.index(selectIndex, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
0298     if (items.rowCount() < 3) {
0299         auto rowHeight = dialog.ui->list->sizeHintForRow(0);
0300         dialog.ui->list->setFixedHeight(rowHeight * items.rowCount() + (items.rowCount() - 1) * dialog.ui->list->spacing() + dialog.ui->list->contentsMargins().top() + dialog.ui->list->contentsMargins().bottom());
0301     }
0302     QObject::connect(dialog.ui->list->selectionModel(), &QItemSelectionModel::selectionChanged, &dialog, [dialog = &dialog](auto &&, auto &&) {
0303         // One can ctrl-click on the selected item to deselect it, that would
0304         // leave the selection empty, so better prevent the OK button
0305         // from being usable
0306         dialog->ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(dialog->ui->list->selectionModel()->hasSelection());
0307     });
0308 
0309     RecentImagesModel imagesModel;
0310     if (!(opts & SigningInformationOption::BackgroundImage)) {
0311         dialog.ui->backgroundInput->hide();
0312         dialog.ui->backgroundLabel->hide();
0313         dialog.ui->recentBackgrounds->hide();
0314         dialog.ui->recentLabel->hide();
0315         dialog.ui->backgroundButton->hide();
0316     } else {
0317         dialog.ui->recentBackgrounds->setModel(&imagesModel);
0318         dialog.ui->recentBackgrounds->setSelectionMode(QAbstractItemView::SingleSelection);
0319         dialog.ui->recentBackgrounds->setItemDelegate(new ImageItemDelegate);
0320         dialog.ui->recentBackgrounds->setViewMode(QListView::IconMode);
0321         dialog.ui->recentBackgrounds->setDragEnabled(false);
0322         dialog.ui->recentBackgrounds->setSpacing(3);
0323         dialog.ui->recentBackgrounds->setContextMenuPolicy(Qt::CustomContextMenu);
0324         QObject::connect(dialog.ui->recentBackgrounds, &QListView::activated, &dialog, [&lineEdit = dialog.ui->backgroundInput](const QModelIndex &idx) { lineEdit->setText(idx.data(Qt::DisplayRole).toString()); });
0325         const bool haveRecent = imagesModel.rowCount(QModelIndex()) != 0;
0326         if (!haveRecent) {
0327             dialog.ui->recentBackgrounds->hide();
0328             dialog.ui->recentLabel->hide();
0329             QObject::connect(&imagesModel, &QAbstractItemModel::rowsInserted, &dialog, [&dialog]() {
0330                 dialog.ui->recentBackgrounds->show();
0331                 dialog.ui->recentLabel->show();
0332             });
0333         }
0334 
0335         QObject::connect(dialog.ui->backgroundInput, &QLineEdit::textChanged, &dialog, [recentModel = &imagesModel, selectionModel = dialog.ui->recentBackgrounds->selectionModel()](const QString &newText) {
0336             recentModel->setFileSystemSelection(newText);
0337             /*Update selection*/
0338             for (int row = 0; row < recentModel->rowCount(); row++) {
0339                 const auto index = recentModel->index(row, 0);
0340                 if (index.data().toString() == newText) {
0341                     selectionModel->select(index, QItemSelectionModel::ClearAndSelect);
0342                     break;
0343                 }
0344             }
0345         });
0346         // needs to happen after textChanged connection on backgroundInput
0347         if (haveRecent) {
0348             dialog.ui->backgroundInput->setText(imagesModel.index(0, 0).data().toString());
0349         }
0350 
0351         QObject::connect(dialog.ui->backgroundButton, &QPushButton::clicked, &dialog, [lineEdit = dialog.ui->backgroundInput]() {
0352             const auto supportedFormats = QImageReader::supportedImageFormats();
0353             QString formats;
0354             for (const auto &format : supportedFormats) {
0355                 if (!formats.isEmpty()) {
0356                     formats += QLatin1Char(' ');
0357                 }
0358                 formats += QStringLiteral("*.") + QString::fromUtf8(format);
0359             }
0360             const QString imageFormats = i18nc("file types in a file open dialog", "Images (%1)", formats);
0361             const QString filename = QFileDialog::getOpenFileName(lineEdit, i18n("Select background image"), QDir::homePath(), imageFormats);
0362             lineEdit->setText(filename);
0363         });
0364         QObject::connect(dialog.ui->recentBackgrounds, &QWidget::customContextMenuRequested, &dialog, [recentModel = &imagesModel, view = dialog.ui->recentBackgrounds](QPoint pos) {
0365             auto current = view->indexAt(pos);
0366             QAction currentImage(i18n("Forget image"));
0367             QAction allImages(i18n("Forget all images"));
0368             QList<QAction *> actions;
0369             if (current.isValid()) {
0370                 actions.append(&currentImage);
0371             }
0372             if (recentModel->rowCount() > 1 || actions.empty()) {
0373                 actions.append(&allImages);
0374             }
0375             const QAction *selected = QMenu::exec(actions, view->viewport()->mapToGlobal(pos), nullptr, view);
0376             if (selected == &currentImage) {
0377                 recentModel->removeItem(current.data(Qt::DisplayRole).toString());
0378                 recentModel->saveBack();
0379             } else if (selected == &allImages) {
0380                 recentModel->clear();
0381                 recentModel->saveBack();
0382             }
0383         });
0384     }
0385     dialog.ui->reasonInput->setText(config->group(ConfigGroup()).readEntry(ConfigLastReason(), QString()));
0386     dialog.ui->locationInput->setText(config->group(ConfigGroup()).readEntry(ConfigLastLocation(), QString()));
0387     auto result = dialog.exec();
0388 
0389     if (result == QDialog::Rejected) {
0390         return std::nullopt;
0391     }
0392     const auto certNicknameToUse = dialog.ui->list->selectionModel()->currentIndex().data(Qt::DisplayRole).toString();
0393     auto backGroundImage = dialog.ui->backgroundInput->text();
0394     if (!backGroundImage.isEmpty()) {
0395         if (QFile::exists(backGroundImage)) {
0396             imagesModel.setFileSystemSelection(backGroundImage);
0397             imagesModel.saveBack();
0398         } else {
0399             // no need to send a nonworking image anywhere
0400             backGroundImage.clear();
0401         }
0402     }
0403 
0404     // I could not find any case in which i need to enter a password to use the certificate, seems that once you unlcok the firefox/NSS database
0405     // you don't need a password anymore, but still there's code to do that in NSS so we have code to ask for it if needed. What we do is
0406     // ask if the empty password is fine, if it is we don't ask the user anything, if it's not, we ask for a password
0407     Okular::CertificateInfo cert = nickToCert.value(certNicknameToUse);
0408     bool passok = cert.checkPassword(password);
0409     while (!passok) {
0410         const QString title = i18n("Enter password (if any) to unlock certificate: %1", certNicknameToUse);
0411         bool ok;
0412         password = QInputDialog::getText(pageView, i18n("Enter certificate password"), title, QLineEdit::Password, QString(), &ok);
0413         if (ok) {
0414             passok = cert.checkPassword(password);
0415         } else {
0416             passok = false;
0417             break;
0418         }
0419     }
0420     if (!passok) {
0421         return std::nullopt;
0422     }
0423 
0424     if (doc->metaData(QStringLiteral("DocumentHasPassword")).toString() == QLatin1String("yes")) {
0425         documentPassword = QInputDialog::getText(pageView, i18n("Enter document password"), i18n("Enter document password"), QLineEdit::Password, QString(), &passok);
0426     }
0427 
0428     if (passok) {
0429         config->group(ConfigGroup()).writeEntry(ConfigLastKeyNick(), cert.nickName());
0430         config->group(ConfigGroup()).writeEntry(ConfigLastReason(), dialog.ui->reasonInput->text());
0431         config->group(ConfigGroup()).writeEntry(ConfigLastLocation(), dialog.ui->locationInput->text());
0432         return SigningInformation {std::make_unique<Okular::CertificateInfo>(std::move(cert)), password, documentPassword, dialog.ui->reasonInput->text(), dialog.ui->locationInput->text(), backGroundImage};
0433     }
0434     return std::nullopt;
0435 }
0436 
0437 QString getFileNameForNewSignedFile(PageView *pageView, Okular::Document *doc)
0438 {
0439     QMimeDatabase db;
0440     const QString typeName = doc->documentInfo().get(Okular::DocumentInfo::MimeType);
0441     const QMimeType mimeType = db.mimeTypeForName(typeName);
0442     const QString mimeTypeFilter = i18nc("File type name and pattern", "%1 (%2)", mimeType.comment(), mimeType.globPatterns().join(QLatin1Char(' ')));
0443 
0444     const QUrl currentFileUrl = doc->currentDocument();
0445     const QString localFilePathIfAny = currentFileUrl.isLocalFile() ? QFileInfo(currentFileUrl.path()).canonicalPath() + QLatin1Char('/') : QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
0446     const QString newFileName = localFilePathIfAny + getSuggestedFileNameForSignedFile(currentFileUrl.fileName(), mimeType.preferredSuffix());
0447 
0448     return QFileDialog::getSaveFileName(pageView, i18n("Save Signed File As"), newFileName, mimeTypeFilter);
0449 }
0450 
0451 void signUnsignedSignature(const Okular::FormFieldSignature *form, PageView *pageView, Okular::Document *doc)
0452 {
0453     Q_ASSERT(form && form->signatureType() == Okular::FormFieldSignature::UnsignedSignature);
0454     const std::optional<SigningInformation> signingInfo = getCertificateAndPasswordForSigning(pageView, doc, SigningInformationOption::None);
0455     if (!signingInfo) {
0456         return;
0457     }
0458 
0459     Okular::NewSignatureData data;
0460     data.setCertNickname(signingInfo->certificate->nickName());
0461     data.setCertSubjectCommonName(signingInfo->certificate->subjectInfo(Okular::CertificateInfo::CommonName, Okular::CertificateInfo::EmptyString::TranslatedNotAvailable));
0462     data.setPassword(signingInfo->certificatePassword);
0463     data.setDocumentPassword(signingInfo->documentPassword);
0464     data.setReason(signingInfo->reason);
0465     data.setLocation(signingInfo->location);
0466 
0467     const QString newFilePath = getFileNameForNewSignedFile(pageView, doc);
0468 
0469     if (!newFilePath.isEmpty()) {
0470         const bool success = form->sign(data, newFilePath);
0471         if (success) {
0472             Q_EMIT pageView->requestOpenFile(newFilePath, form->page()->number() + 1);
0473         } else {
0474             KMessageBox::error(pageView, i18nc("%1 is a file path", "Could not sign. Invalid certificate password or could not write to '%1'", newFilePath));
0475         }
0476     }
0477 }
0478 
0479 SelectCertificateDialog::SelectCertificateDialog(QWidget *parent)
0480     : QDialog(parent)
0481     , ui {std::make_unique<Ui_SelectCertificateDialog>()}
0482 {
0483     ui->setupUi(this);
0484 }
0485 SelectCertificateDialog::~SelectCertificateDialog() = default;
0486 
0487 QSize KeyDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0488 {
0489     auto baseSize = QStyledItemDelegate::sizeHint(option, index);
0490     baseSize.setHeight(baseSize.height() * 2);
0491     return baseSize;
0492 }
0493 
0494 void KeyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0495 {
0496     auto style = option.widget ? option.widget->style() : QApplication::style();
0497 
0498     // This paints the background without initializing the
0499     // styleoption from the actual index. Given we want default background
0500     // and paint the foreground a bit later
0501     // This accomplishes it quite nicely.
0502     style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
0503 
0504     QPalette::ColorGroup cg;
0505     if (option.state & QStyle::State_Active) {
0506         cg = QPalette::Normal;
0507     } else {
0508         cg = QPalette::Inactive;
0509     }
0510 
0511     if (option.state & QStyle::State_Selected) {
0512         painter->setPen(QPen {option.palette.brush(cg, QPalette::HighlightedText), 0});
0513     } else {
0514         painter->setPen(QPen {option.palette.brush(cg, QPalette::Text), 0});
0515     }
0516 
0517     auto textRect = option.rect;
0518     int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, option.widget) + 1;
0519     if (showIcon) {
0520         textRect.adjust(textRect.height() + textMargin, 0, 0, 0); // make space for icon
0521     }
0522     textRect.adjust(textMargin, 0, -textMargin, 0);
0523 
0524     QRect topHalf {textRect.x(), textRect.y(), textRect.width(), textRect.height() / 2};
0525     QRect bottomHalf {textRect.x(), textRect.y() + textRect.height() / 2, textRect.width(), textRect.height() / 2};
0526 
0527     style->drawItemText(painter, topHalf, (option.displayAlignment & Qt::AlignVertical_Mask) | Qt::AlignLeft, option.palette, true, index.data(Qt::DisplayRole).toString());
0528     style->drawItemText(painter, bottomHalf, (option.displayAlignment & Qt::AlignVertical_Mask) | Qt::AlignRight, option.palette, true, index.data(Qt::UserRole + 1).toString());
0529     style->drawItemText(painter, bottomHalf, (option.displayAlignment & Qt::AlignVertical_Mask) | Qt::AlignLeft, option.palette, true, index.data(Qt::UserRole).toString());
0530     if (showIcon) {
0531         if (auto icon = index.data(Qt::DecorationRole).value<QIcon>(); !icon.isNull()) {
0532             icon.paint(painter, QRect(option.rect.topLeft(), QSize(textRect.height(), textRect.height())));
0533         }
0534     }
0535 }
0536 }
0537 
0538 #include "signaturepartutils.moc"