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(¤tImage); 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 == ¤tImage) { 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"