Warning, file /utilities/skanpage/src/DocumentModel.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /**
0002  * SPDX-FileCopyrightText: 2015 by Kåre Särs <kare.sars@iki .fi>
0003  * SPDX-FileCopyrightText: 2021 by Alexander Stippich <a.stippich@gmx.net>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006  */
0007 
0008 #include "DocumentModel.h"
0009 
0010 #include <QUrl>
0011 #include <QImage>
0012 #include <QStandardPaths>
0013 #include <QThread>
0014 
0015 #include <KLocalizedString>
0016 
0017 #include "skanpage_debug.h"
0018 
0019 struct PreviewPageProperties {
0020     double aspectRatio;
0021     int previewWidth;
0022     int previewHeight;
0023     int pageID;
0024     bool isSaved;
0025 };
0026 
0027 QDebug operator<<(QDebug d, const PreviewPageProperties& pageProperties)
0028 {
0029     d << "ID: " << pageProperties.pageID << "\n";
0030     d << "Aspect ratio: " << pageProperties.aspectRatio << "\n";
0031     d << "Preview width: " << pageProperties.previewWidth << "\n";
0032     d << "Preview height: " << pageProperties.previewHeight << "\n";
0033     d << "Is saved: " << pageProperties.isSaved << "\n";
0034     return d;
0035 }
0036 
0037 const static QString defaultFileName = i18n("New document");
0038 
0039 class DocumentModelPrivate
0040 {
0041 public:
0042     explicit DocumentModelPrivate();
0043 
0044     SkanpageUtils::DocumentPages m_pages;
0045     QList<PreviewPageProperties> m_details;
0046     QList<QUrl> m_fileUrls;
0047     bool m_changed = false;
0048     int m_activePageIndex = -1;
0049     int m_idCounter = 0;
0050 };
0051 
0052 DocumentModelPrivate::DocumentModelPrivate()
0053 {
0054 }
0055 
0056 DocumentModel::DocumentModel(QObject *parent)
0057     : QAbstractListModel(parent)
0058     , d(std::make_unique<DocumentModelPrivate>())
0059 {
0060 }
0061 
0062 DocumentModel::~DocumentModel()
0063 {
0064 }
0065 
0066 QString DocumentModel::name() const
0067 {
0068     if (d->m_fileUrls.isEmpty()) {
0069         return i18n("New document");
0070     }
0071     if (d->m_fileUrls.count() > 1) {
0072         return i18nc("for file names, indicates a range: from file0000.png to file0014.png","%1 ... %2", d->m_fileUrls.first().fileName(), d->m_fileUrls.last().fileName());
0073     }
0074     return d->m_fileUrls.first().fileName();
0075 }
0076 
0077 QString DocumentModel::fileName() const
0078 {
0079     if (d->m_fileUrls.isEmpty() || d->m_fileUrls.count() > 1) {
0080         return QString(i18n("New document") + QStringLiteral(".pdf"));
0081     }
0082     return d->m_fileUrls.first().fileName();
0083 }
0084 
0085 bool DocumentModel::changed() const
0086 {
0087     return d->m_changed;
0088 }
0089 
0090 int DocumentModel::activePageIndex() const
0091 {
0092     return d->m_activePageIndex;
0093 }
0094 
0095 int DocumentModel::activePageRotation() const
0096 {
0097     if (d->m_activePageIndex >= 0 && d->m_activePageIndex < rowCount()) {
0098         return d->m_pages.at(d->m_activePageIndex).rotationAngle;
0099     }
0100     return 0;
0101 }
0102 
0103 QUrl DocumentModel::activePageSource() const
0104 {
0105     return data(index(d->m_activePageIndex, 0), ImageUrlRole).toUrl();
0106 }
0107 
0108 void DocumentModel::setActivePageIndex(int newIndex)
0109 {
0110     if (newIndex != d->m_activePageIndex) {
0111         d->m_activePageIndex = newIndex;
0112         Q_EMIT activePageChanged();
0113     }
0114 }
0115 
0116 void DocumentModel::save(const QUrl &fileUrl, QList<int> pageNumbers)
0117 {
0118     if (pageNumbers.isEmpty()) {
0119         Q_EMIT saveDocument(fileUrl, d->m_pages);
0120     } else {
0121         Q_EMIT saveDocument(fileUrl, selectPages(pageNumbers), SkanpageUtils::PageSelection);
0122     }
0123 }
0124 
0125 void DocumentModel::createSharingFile(const QString &suffix, QList<int> pageNumbers)
0126 {
0127     if (d->m_pages.isEmpty()) {
0128         return;
0129     }
0130 
0131     const QUrl temporaryLocation = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::TempLocation)
0132          + QStringLiteral("/document.") + suffix);
0133     Q_EMIT saveDocument(temporaryLocation, selectPages(pageNumbers), SkanpageUtils::SharingDocument);
0134 }
0135     
0136 void DocumentModel::exportPDF(const QUrl &fileUrl, const QString &title, const bool useOCR)
0137 {
0138     if (useOCR) {
0139         Q_EMIT saveDocument(fileUrl, d->m_pages, SkanpageUtils::OCRDocument, title);
0140     } else {
0141         Q_EMIT saveDocument(fileUrl, d->m_pages, SkanpageUtils::EntireDocument, title);
0142     }
0143 }
0144 
0145 void DocumentModel::addImage(const QImage &image)
0146 {
0147     const double aspectRatio = static_cast<double>(image.height())/image.width();
0148     beginInsertRows(QModelIndex(), d->m_pages.count(), d->m_pages.count());
0149     const PreviewPageProperties newPage = {aspectRatio, 500, static_cast<int>(500 * aspectRatio), d->m_idCounter++, false};
0150     qCDebug(SKANPAGE_LOG) << "Inserting new page into model:" << newPage;
0151     d->m_details.append(newPage);
0152     d->m_pages.append({nullptr, QPageSize(), 0});
0153     endInsertRows();
0154     Q_EMIT countChanged();
0155     Q_EMIT saveNewPageTemporary(newPage.pageID, image);
0156 }
0157 
0158 void DocumentModel::updatePageInModel(const int pageID, const SkanpageUtils::PageProperties &page)
0159 {
0160     if (d->m_details.count() <= 0) {
0161         return;
0162     }
0163     /* Most likely, the updated page is the last one in the model
0164      * unless the user has deleted a page between the finished scanning and the
0165      * processing. Thus try this first and look for the page ID if this is not the case. */
0166     int pageIndex = d->m_details.count() - 1;
0167     if (d->m_details.at(pageIndex).pageID != pageID) {
0168         for (int i = d->m_details.count() - 1; i >= 0; i--) {
0169             if (d->m_details.at(i).pageID == pageID) {
0170                 pageIndex = i;
0171                 break;
0172             }
0173         }
0174     }
0175     d->m_pages[pageIndex].dpi = page.dpi;
0176     d->m_pages[pageIndex].temporaryFile = page.temporaryFile;
0177     d->m_pages[pageIndex].pageSize = page.pageSize;
0178     d->m_details[pageIndex].isSaved = true;
0179     Q_EMIT dataChanged(index(pageIndex, 0), index(pageIndex, 0), {ImageUrlRole, IsSavedRole});
0180 
0181     if (!d->m_changed) {
0182         d->m_changed = true;
0183         Q_EMIT changedChanged();
0184     }
0185 
0186     d->m_activePageIndex = pageIndex;
0187     Q_EMIT activePageChanged();
0188     Q_EMIT newPageAdded();
0189 }
0190 
0191 void DocumentModel::moveImage(int from, int to)
0192 {
0193     int add = 0;
0194     if (from == to) {
0195         return;
0196     }
0197     if (to > from) {
0198         add = 1;
0199     }
0200     if (from < 0 || from >= d->m_pages.count()) {
0201         return;
0202     }
0203     if (to < 0 || to >= d->m_pages.count()) {
0204         return;
0205     }
0206 
0207     bool ok = beginMoveRows(QModelIndex(), from, from, QModelIndex(), to + add);
0208     if (!ok) {
0209         qCDebug(SKANPAGE_LOG) << "Failed to move" << from << to << add << d->m_pages.count();
0210         return;
0211     }
0212     d->m_pages.move(from, to);
0213     d->m_details.move(from, to);
0214     endMoveRows();
0215 
0216     if (d->m_activePageIndex == from) {
0217         d->m_activePageIndex = to;
0218     } else if (d->m_activePageIndex == to) {
0219         d->m_activePageIndex = from;
0220     }
0221     Q_EMIT activePageChanged();
0222 
0223     if (!d->m_changed) {
0224         d->m_changed = true;
0225         Q_EMIT changedChanged();
0226     }
0227 }
0228 
0229 void DocumentModel::rotateImage(int row, RotateOption rotate)
0230 {
0231     if (row < 0 || row >= rowCount()) {
0232         return;
0233     }
0234     int rotationAngle = d->m_pages.at(row).rotationAngle;
0235     if (rotate == RotateOption::Rotate90positive) {
0236         rotationAngle += 90;
0237     } else if (rotate == RotateOption::Flip180) {
0238         rotationAngle += 180;
0239     } else {
0240         rotationAngle -= 90;
0241     }
0242     if (rotationAngle < 0) {
0243         rotationAngle = rotationAngle + 360;
0244     } else if (rotationAngle >= 360) {
0245         rotationAngle = rotationAngle - 360;
0246     }
0247     d->m_pages[row].rotationAngle = rotationAngle;
0248     if (row == d->m_activePageIndex) {
0249         Q_EMIT activePageChanged();
0250     }
0251     Q_EMIT dataChanged(index(row, 0), index(row, 0), {RotationAngleRole});
0252 }
0253 
0254 void DocumentModel::reorderPages(ReorderOption reorder)
0255 {
0256     Q_EMIT layoutAboutToBeChanged();
0257     if (reorder == Reverse) {
0258         std::reverse(d->m_pages.begin(), d->m_pages.end());
0259         std::reverse(d->m_details.begin(), d->m_details.end());
0260     } else {
0261         SkanpageUtils::DocumentPages newPages;
0262         QList<PreviewPageProperties> newDetails;
0263         newPages.reserve(d->m_pages.count());
0264         newDetails.reserve(d->m_pages.count());
0265         const int halfStart = d->m_pages.count() / 2;
0266         const int oddOffset = d->m_pages.count() % 2;
0267         if (reorder == ReorderDuplex) {
0268             for (int i = 0; i < halfStart; i++) {
0269                 newPages.append(d->m_pages.at(i));
0270                 newPages.append(d->m_pages.at(i + halfStart + oddOffset));
0271                 newDetails.append(d->m_details.at(i));
0272                 newDetails.append(d->m_details.at(i + halfStart + oddOffset));
0273             }
0274         } else {
0275             for (int i = 0; i < halfStart; i++) {
0276                 newPages.append(d->m_pages.at(i));
0277                 newPages.append(d->m_pages.at(d->m_pages.count() - 1 - i));
0278                 newDetails.append(d->m_details.at(i));
0279                 newDetails.append(d->m_details.at(d->m_details.count() - 1 - i));
0280             }
0281         }
0282         if (oddOffset == 1) {
0283             newPages.append(d->m_pages.at(halfStart));
0284             newDetails.append(d->m_details.at(halfStart));
0285         };
0286         d->m_pages = newPages;
0287         d->m_details = newDetails;
0288     }
0289     Q_EMIT layoutChanged();
0290 }
0291 
0292 void DocumentModel::removeImage(int row)
0293 {
0294     if (row < 0 || row >= d->m_pages.count()) {
0295         return;
0296     }
0297 
0298     beginRemoveRows(QModelIndex(), row, row);
0299     d->m_pages.removeAt(row);
0300     d->m_details.removeAt(row);
0301     endRemoveRows();
0302 
0303     if (row < d->m_activePageIndex) {
0304         d->m_activePageIndex -= 1;
0305     } else if (d->m_activePageIndex >= d->m_pages.count()) {
0306         d->m_activePageIndex = d->m_pages.count() - 1;
0307     }
0308     Q_EMIT activePageChanged();
0309     Q_EMIT countChanged();
0310 
0311     if (!d->m_changed) {
0312         d->m_changed = true;
0313         Q_EMIT changedChanged();
0314     }
0315 }
0316 
0317 QHash<int, QByteArray> DocumentModel::roleNames() const
0318 {
0319     QHash<int, QByteArray> roles;
0320     roles[ImageUrlRole] = "imageUrl";
0321     roles[RotationAngleRole] = "rotationAngle";
0322     roles[IsSavedRole] = "isSaved";
0323     roles[PreviewWidthRole] = "previewWidth";
0324     roles[PreviewHeightRole] = "previewHeight";
0325     roles[AspectRatioRole] = "aspectRatio";
0326     return roles;
0327 }
0328 
0329 int DocumentModel::rowCount(const QModelIndex &) const
0330 {
0331     return d->m_pages.count();
0332 }
0333 
0334 QVariant DocumentModel::data(const QModelIndex &index, int role) const
0335 {
0336     if (!index.isValid()) {
0337         return QVariant();
0338     }
0339 
0340     if (index.row() >= d->m_pages.size() || index.row() < 0) {
0341         return QVariant();
0342     }
0343 
0344     switch (role) {
0345     case ImageUrlRole:
0346         if (d->m_details.at(index.row()).isSaved || d->m_pages.at(index.row()).temporaryFile.get() != nullptr) {
0347             return QUrl::fromLocalFile(d->m_pages.at(index.row()).temporaryFile->fileName());
0348         } else {
0349             return QUrl();
0350         }
0351     case RotationAngleRole:
0352         return d->m_pages.at(index.row()).rotationAngle;
0353     case IsSavedRole:
0354         return d->m_details.at(index.row()).isSaved;
0355     case AspectRatioRole:
0356         return d->m_details.at(index.row()).aspectRatio;
0357     case PreviewWidthRole:
0358         return d->m_details.at(index.row()).previewWidth;
0359     case PreviewHeightRole:
0360         return d->m_details.at(index.row()).previewHeight;
0361     }
0362     return QVariant();
0363 }
0364 
0365 void DocumentModel::clearData()
0366 {
0367     beginResetModel();
0368     d->m_pages.clear();
0369     d->m_details.clear();
0370     d->m_activePageIndex = -1;
0371     endResetModel();
0372     Q_EMIT countChanged();
0373 
0374     if (!d->m_fileUrls.isEmpty() && defaultFileName != d->m_fileUrls.first().fileName()) {
0375         d->m_fileUrls.first() = QUrl::fromLocalFile(defaultFileName);
0376         Q_EMIT nameChanged();
0377     }
0378     if (d->m_changed) {
0379         d->m_changed = false;
0380         Q_EMIT changedChanged();
0381     }
0382 }
0383 
0384 void DocumentModel::updateFileInformation(const QList<QUrl> &fileUrls, const SkanpageUtils::DocumentPages &document)
0385 {
0386     if (document == d->m_pages && d->m_changed) {
0387         d->m_changed = false;
0388         Q_EMIT changedChanged();
0389     }
0390 
0391     if (d->m_fileUrls != fileUrls) {
0392         d->m_fileUrls = fileUrls;
0393         Q_EMIT nameChanged();
0394     }
0395 }
0396 
0397 void DocumentModel::updateSharingFileInformation(const QList<QUrl> &fileUrls)
0398 {
0399     QVariantList temp;
0400     for (const auto &url : fileUrls) {
0401         temp << url.toString();
0402     }
0403     Q_EMIT sharingDocumentsCreated(temp);
0404 }
0405 
0406 SkanpageUtils::DocumentPages DocumentModel::selectPages(QList<int> pageNumbers)
0407 {
0408     if (pageNumbers.isEmpty()) {
0409         return d->m_pages;
0410     }
0411 
0412     SkanpageUtils::DocumentPages document;
0413     std::sort(pageNumbers.begin(), pageNumbers.end());
0414     for (int i = 0; i < pageNumbers.count(); i++) {
0415         const int page = pageNumbers.at(i);
0416         if (page >= 0 && page <  d->m_pages.count()) {
0417             document.append(d->m_pages.at(pageNumbers.at(i)));
0418         }
0419     }
0420     return document;
0421 }