File indexing completed on 2024-05-12 16:01:45

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "KisAutoSaveRecoveryDialog.h"
0008 
0009 #include <KoStore.h>
0010 
0011 #include <kwidgetitemdelegate.h>
0012 #include <klocalizedstring.h>
0013 
0014 #include <QVBoxLayout>
0015 #include <QHBoxLayout>
0016 #include <QListView>
0017 #include <QAbstractTableModel>
0018 #include <QLabel>
0019 #include <QDir>
0020 #include <QFileInfo>
0021 #include <QDateTime>
0022 #include <QImage>
0023 #include <QPixmap>
0024 #include <QHeaderView>
0025 #include <QStyledItemDelegate>
0026 #include <QStandardPaths>
0027 #include <QPainter>
0028 #include <QCheckBox>
0029 #include <kis_debug.h>
0030 
0031 
0032 struct FileItem {
0033 
0034     FileItem() : checked(true) {}
0035 
0036     QImage thumbnail;
0037     QString name;
0038     QString date;
0039     bool checked;
0040 };
0041 
0042 class FileItemDelegate : public KWidgetItemDelegate
0043 {
0044 public:
0045 
0046     FileItemDelegate(QAbstractItemView *itemView, KisAutoSaveRecoveryDialog *dlg)
0047         : KWidgetItemDelegate(itemView, dlg)
0048         , m_parent(dlg)
0049     {
0050     }
0051 
0052     QList<QWidget*> createItemWidgets(const QModelIndex& index) const override
0053     {
0054         // a lump of coal and a piece of elastic will get you through the world
0055         QWidget *page = new QWidget;
0056         QHBoxLayout *layout = new QHBoxLayout(page);
0057 
0058         QCheckBox *checkBox = new QCheckBox;
0059         checkBox->setProperty("fileitem", index.data());
0060 
0061         connect(checkBox, SIGNAL(toggled(bool)), m_parent, SLOT(toggleFileItem(bool)));
0062         QLabel *thumbnail = new QLabel;
0063         QLabel *filename = new QLabel;
0064         QLabel *dateModified = new QLabel;
0065 
0066         layout->addWidget(checkBox);
0067         layout->addWidget(thumbnail);
0068         layout->addWidget(filename);
0069         layout->addWidget(dateModified);
0070 
0071         page->setFixedSize(600, 200);
0072 
0073         return QList<QWidget*>() << page;
0074     }
0075 
0076     void updateItemWidgets(const QList<QWidget*> widgets,
0077                            const QStyleOptionViewItem &option,
0078                            const QPersistentModelIndex &index) const override
0079     {
0080         FileItem *fileItem = (FileItem*)index.data().value<void*>();
0081 
0082         QWidget* page= widgets[0];
0083         QHBoxLayout* layout = qobject_cast<QHBoxLayout*>(page->layout());
0084         QCheckBox *checkBox = qobject_cast<QCheckBox*>(layout->itemAt(0)->widget());
0085         QLabel *thumbnail = qobject_cast<QLabel*>(layout->itemAt(1)->widget());
0086         QLabel *filename = qobject_cast<QLabel*>(layout->itemAt(2)->widget());
0087         QLabel *modified = qobject_cast<QLabel*>(layout->itemAt(3)->widget());
0088 
0089         checkBox->setChecked(fileItem->checked);
0090         thumbnail->setPixmap(QPixmap::fromImage(fileItem->thumbnail));
0091         filename->setText(fileItem->name);
0092         modified->setText(fileItem->date);
0093 
0094         // move the page _up_ otherwise it will draw relative to the actual position
0095         page->setGeometry(option.rect.translated(0, -option.rect.y()));
0096     }
0097 
0098     void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const override
0099     {
0100         //paint background for selected or hovered item
0101         QStyleOptionViewItem opt = option;
0102         itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, 0);
0103     }
0104 
0105     QSize sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const override
0106     {
0107         return QSize(600, 200);
0108     }
0109 
0110 
0111     KisAutoSaveRecoveryDialog *m_parent;
0112 };
0113 
0114 class KisAutoSaveRecoveryDialog::FileItemModel : public QAbstractListModel
0115 {
0116 public:
0117     FileItemModel(QList<FileItem*> fileItems, QObject *parent)
0118         : QAbstractListModel(parent)
0119         , m_fileItems(fileItems){}
0120 
0121     ~FileItemModel() override
0122     {
0123         qDeleteAll(m_fileItems);
0124         m_fileItems.clear();
0125     }
0126 
0127     int rowCount(const QModelIndex &/*parent*/) const override { return m_fileItems.size(); }
0128 
0129     Qt::ItemFlags flags(const QModelIndex& /*index*/) const override
0130     {
0131         Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable;
0132         return flags;
0133     }
0134 
0135     QVariant data(const QModelIndex& index, int role) const override
0136     {
0137         if (index.isValid() && index.row() < m_fileItems.size()) {
0138 
0139             FileItem *item = m_fileItems.at(index.row());
0140 
0141             switch (role) {
0142             case Qt::DisplayRole:
0143             {
0144                 return QVariant::fromValue<void*>((void*)item);
0145             }
0146             case Qt::SizeHintRole:
0147                 return QSize(600, 200);
0148             }
0149         }
0150         return QVariant();
0151     }
0152 
0153     bool setData(const QModelIndex& index, const QVariant& /*value*/, int role) override
0154     {
0155         if (index.isValid() && index.row() < m_fileItems.size()) {
0156             if (role == Qt::CheckStateRole) {
0157                 m_fileItems.at(index.row())->checked = !m_fileItems.at(index.row())->checked;
0158                 return true;
0159             }
0160         }
0161         return false;
0162     }
0163     QList<FileItem *> m_fileItems;
0164 };
0165 
0166 KisAutoSaveRecoveryDialog::KisAutoSaveRecoveryDialog(const QStringList &filenames, QWidget *parent) :
0167     KoDialog(parent)
0168 {
0169     setCaption(i18nc("@title:window", "Recover Files"));
0170     setButtons( KoDialog::Ok | KoDialog::Cancel | KoDialog::User1 );
0171     setButtonText(KoDialog::User1, i18n("Discard All"));
0172     setMinimumSize(650, 500);
0173     QWidget *page = new QWidget(this);
0174     QVBoxLayout *layout = new QVBoxLayout(page);
0175     if (filenames.size() == 1) {
0176         layout->addWidget(new QLabel(i18n("The following autosave file can be recovered:")));
0177     }
0178     else {
0179         layout->addWidget(new QLabel(i18n("The following autosave files can be recovered:")));
0180     }
0181 
0182     m_listView = new QListView();
0183     m_listView->setAcceptDrops(false);
0184     KWidgetItemDelegate *delegate = new FileItemDelegate(m_listView, this);
0185     m_listView->setItemDelegate(delegate);
0186 
0187     QList<FileItem*> fileItems;
0188     Q_FOREACH (const QString &filename, filenames) {
0189 
0190         FileItem *file = new FileItem();
0191         file->name = filename;
0192 
0193         QString path = autoSaveLocation() + "/" + filename;
0194         // get thumbnail -- almost all Krita-supported formats save a thumbnail
0195         KoStore* store = KoStore::createStore(path, KoStore::Read);
0196 
0197         if (store) {
0198             QString thumbnailPath = QLatin1String("Thumbnails/thumbnail.png");
0199             QString previewPath = QLatin1String("preview.png");
0200             bool thumbnailExists = store->hasFile(thumbnailPath);
0201             bool previewExists = store->hasFile(previewPath);
0202             QString pathToUse = thumbnailExists ? thumbnailPath : (previewExists ? previewPath : "");
0203 
0204             if (!pathToUse.isEmpty() && store->open(pathToUse)) {
0205                 // Hooray! No long delay for the user...
0206                 QByteArray bytes = store->read(store->size());
0207                 store->close();
0208                 QImage img;
0209                 img.loadFromData(bytes);
0210                 file->thumbnail = img.scaled(QSize(200,200), Qt::KeepAspectRatio, Qt::SmoothTransformation);
0211             }
0212 
0213             delete store;
0214         }
0215 
0216         // get the date
0217         QDateTime date = QFileInfo(path).lastModified();
0218         file->date = "(" + date.toString(Qt::LocalDate) + ")";
0219 
0220         fileItems.append(file);
0221     }
0222 
0223     m_model = new FileItemModel(fileItems, m_listView);
0224     m_listView->setModel(m_model);
0225     layout->addWidget(m_listView);
0226     layout->addWidget(new QLabel(i18n("If you select Cancel, all recoverable files will be kept.\nIf you press OK, selected files will be recovered, the unselected files discarded.")));
0227     setMainWidget(page);
0228     
0229     setAttribute(Qt::WA_DeleteOnClose, false);
0230     connect( this, SIGNAL(user1Clicked()), this, SLOT(slotDeleteAll()) );
0231 }
0232 
0233 KisAutoSaveRecoveryDialog::~KisAutoSaveRecoveryDialog()
0234 {
0235     delete m_listView->itemDelegate();
0236     delete m_model;
0237     delete m_listView;
0238 }
0239 
0240 void KisAutoSaveRecoveryDialog::slotDeleteAll()
0241 {
0242     foreach(FileItem* fileItem, m_model->m_fileItems) {
0243         fileItem->checked = false;
0244     }
0245     accept();
0246 }
0247 
0248 QStringList KisAutoSaveRecoveryDialog::recoverableFiles()
0249 {
0250     QStringList files;
0251     Q_FOREACH (FileItem* fileItem, m_model->m_fileItems) {
0252         if (fileItem->checked) {
0253             files << fileItem->name;
0254         }
0255     }
0256     return files;
0257 }
0258 
0259 QString KisAutoSaveRecoveryDialog::autoSaveLocation()
0260 {
0261 #if defined(Q_OS_WIN)
0262     // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921)
0263     return QDir::tempPath();
0264 #elif defined(Q_OS_ANDROID)
0265     QString path = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/krita-backup");
0266     if (!QDir(path).exists()) {
0267         QDir().mkpath(path);
0268     }
0269     return path;
0270 #else
0271     // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's
0272     // autosave file
0273     return QDir::homePath();
0274 #endif
0275 }
0276 
0277 void KisAutoSaveRecoveryDialog::toggleFileItem(bool toggle)
0278 {
0279     // I've made better man from a piece of putty and matchstick!
0280     QVariant v = sender()->property("fileitem") ;
0281     if (v.isValid()) {
0282         FileItem *fileItem = (FileItem*)v.value<void*>();
0283         fileItem->checked = toggle;
0284     }
0285 }