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 }