File indexing completed on 2024-04-21 05:54:06
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 class DocumentModelPrivate 0038 { 0039 public: 0040 explicit DocumentModelPrivate(); 0041 0042 SkanpageUtils::DocumentPages m_pages; 0043 QList<PreviewPageProperties> m_details; 0044 QList<QUrl> m_fileUrls; 0045 bool m_changed = false; 0046 int m_activePageIndex = -1; 0047 int m_idCounter = 0; 0048 const QString m_defaultFileName; 0049 }; 0050 0051 DocumentModelPrivate::DocumentModelPrivate() 0052 : m_defaultFileName(i18n("New document")) 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 && newIndex >= 0 && newIndex < rowCount()) { 0111 d->m_activePageIndex = newIndex; 0112 Q_EMIT activePageChanged(); 0113 } 0114 } 0115 0116 void DocumentModel::save(const QUrl &fileUrl, const 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, const 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() && d->m_defaultFileName != d->m_fileUrls.first().fileName()) { 0375 d->m_fileUrls.first() = QUrl::fromLocalFile(d->m_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) const 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 } 0422 0423 #include "moc_DocumentModel.cpp"