File indexing completed on 2024-05-19 04:29:10

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