1 /* This file is part of the KDE project
2    Copyright (C) 2005-2006 Peter Simonsson <psn@linux.se>
3    Copyright 2012 Friedrich W. H. Kossebau <kossebau@kde.org>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 */
20 
21 #include "KoRecentDocumentsPane.h"
22 
23 #include <QFile>
24 #include <QStandardItemModel>
25 #include <QUrl>
26 
27 #include <KSharedConfig>
28 #include <KConfigGroup>
29 #include <kfileitem.h>
30 #include <kio/previewjob.h>
31 
32 #include <KoIcon.h>
33 
34 
35 static const int MAX_RECENTFILES_ENTRIES = 10;
36 
37 
38 enum KoRecentDocumentRoles {
39     PreviewRole = Qt::UserRole
40 };
41 
42 class KoFileListItem : public QStandardItem
43 {
44 public:
KoFileListItem(const QIcon & icon,const QString & text,const KFileItem & item)45     KoFileListItem(const QIcon &icon, const QString &text, const KFileItem& item)
46             : QStandardItem(icon, text)
47             , m_fileItem(item){
48     }
49 
~KoFileListItem()50     ~KoFileListItem() override {
51     }
52 
fileItem() const53     const KFileItem &fileItem() const {
54         return m_fileItem;
55     }
56 
57 private:
58     KFileItem m_fileItem;
59 };
60 
61 
62 class KoRecentDocumentsPanePrivate
63 {
64 public:
KoRecentDocumentsPanePrivate()65     KoRecentDocumentsPanePrivate()
66     {
67     }
68 
~KoRecentDocumentsPanePrivate()69     ~KoRecentDocumentsPanePrivate()
70     {
71         foreach(KJob* job, m_previewJobs)
72             job->kill();
73     }
74 
75     QList<KJob*> m_previewJobs;
76 };
77 
78 
KoRecentDocumentsPane(QWidget * parent,const QString & header)79 KoRecentDocumentsPane::KoRecentDocumentsPane(QWidget* parent, const QString& header)
80         : KoDetailsPane(parent, header)
81         , d(new KoRecentDocumentsPanePrivate)
82 {
83     setFocusProxy(m_documentList);
84     m_openButton->setText(i18n("Open This Document"));
85     m_openButton->setIcon(koIcon("document-open"));
86 
87     m_alwaysUseCheckBox->hide();
88 
89     model()->setSortRole(0); // Disable sorting
90 
91     // load list of recent files from config
92     KConfigGroup config(KSharedConfig::openConfig(), "RecentFiles");
93 
94     QString fileKey;
95     QString fileValue;
96     QUrl url;
97     QString nameValue;
98     KFileItemList fileList;
99     QStandardItem* rootItem = model()->invisibleRootItem();
100 
101     for (int i = 1; i <= MAX_RECENTFILES_ENTRIES; ++i) {
102         fileValue = config.readPathEntry(QString("File%1").arg(i), QString());
103 
104         // ignore empty entries
105         if (fileValue.isEmpty()) {
106             continue;
107         }
108 
109         url = QUrl::fromUserInput(fileValue);
110 
111         // ignore entries for files known to no longer exist
112         if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
113             continue;
114         }
115         // ignore duplicated entries
116         if (!fileList.findByUrl(url).isNull()) {
117             continue;
118         }
119 
120         nameValue = config.readPathEntry(QString("Name%1").arg(i), QString());
121         // handle name entries with empty strings
122         if (nameValue.isEmpty()) {
123             nameValue = url.fileName();
124         }
125 
126         KFileItem fileItem(url);
127         fileList.prepend(fileItem);
128         const QIcon icon = QIcon::fromTheme(fileItem.iconName());
129         KoFileListItem* item = new KoFileListItem(icon, nameValue, fileItem);
130         item->setEditable(false);
131         rootItem->insertRow(0, item);
132     }
133 
134 
135     //Select the first file
136     QModelIndex firstIndex = model()->indexFromItem(model()->item(0));
137     m_documentList->selectionModel()->select(firstIndex, QItemSelectionModel::Select);
138     m_documentList->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::Select);
139 
140     QStringList availablePlugins = KIO::PreviewJob::availablePlugins();
141     KIO::PreviewJob *previewJob = KIO::filePreview(fileList, QSize(IconExtent, IconExtent), &availablePlugins);
142 
143     d->m_previewJobs.append(previewJob);
144     connect(previewJob, SIGNAL(result(KJob*)), SLOT(previewResult(KJob*)));
145     connect(previewJob, SIGNAL(gotPreview(KFileItem,QPixmap)),
146             SLOT(updateIcon(KFileItem,QPixmap)));
147 }
148 
~KoRecentDocumentsPane()149 KoRecentDocumentsPane::~KoRecentDocumentsPane()
150 {
151     delete d;
152 }
153 
selectionChanged(const QModelIndex & index)154 void KoRecentDocumentsPane::selectionChanged(const QModelIndex& index)
155 {
156     if (index.isValid()) {
157         KoFileListItem* item = static_cast<KoFileListItem*>(model()->itemFromIndex(index));
158         const KFileItem fileItem = item->fileItem();
159 
160         m_openButton->setEnabled(true);
161         m_titleLabel->setText(item->data(Qt::DisplayRole).toString());
162 
163         QPixmap preview = item->data(PreviewRole).value<QPixmap>();
164         if (preview.isNull()) {
165             // need to fetch preview
166             const KFileItemList fileList = KFileItemList() << fileItem;
167             QStringList availablePlugins = KIO::PreviewJob::availablePlugins();
168             KIO::PreviewJob *previewJob = KIO::filePreview(fileList, QSize(PreviewExtent, PreviewExtent), &availablePlugins);
169 
170             d->m_previewJobs.append(previewJob);
171             connect(previewJob, SIGNAL(result(KJob*)), SLOT(previewResult(KJob*)));
172             connect(previewJob, SIGNAL(gotPreview(KFileItem,QPixmap)),
173                     SLOT(updatePreview(KFileItem,QPixmap)));
174 
175             // for now set preview to icon
176             preview = item->icon().pixmap(PreviewExtent);
177             if (preview.width() < PreviewExtent && preview.height() < PreviewExtent) {
178                 preview = preview.scaled(PreviewExtent, PreviewExtent, Qt::KeepAspectRatio, Qt::SmoothTransformation);
179             }
180         }
181         m_previewLabel->setPixmap(preview);
182 
183         if (!fileItem.isNull()) {
184             // TODO: think about not displaying Modified/Accessed if not available
185             QString details = QString("<center>%1<br>").arg(fileItem.url().toDisplayString(QUrl::PreferLocalFile)) +
186                 "<table border=\"0\">" +
187                 i18nc("File modification date and time. %1 is date time",
188                       "<tr><td><b>Modified:</b></td><td>%1</td></tr>",
189                       QString(fileItem.timeString(KFileItem::ModificationTime))) +
190                 i18nc("File access date and time. %1 is date time",
191                       "<tr><td><b>Accessed:</b></td><td>%1</td></tr>",
192                       QString(fileItem.timeString(KFileItem::AccessTime))) +
193                 "</table></center>";
194             m_detailsLabel->setHtml(details);
195         } else {
196             m_detailsLabel->clear();
197         }
198     } else {
199         m_openButton->setEnabled(false);
200         m_titleLabel->clear();
201         m_previewLabel->setPixmap(QPixmap());
202         m_detailsLabel->clear();
203     }
204 }
205 
openFile()206 void KoRecentDocumentsPane::openFile()
207 {
208     KoDetailsPane::openFile();
209 }
210 
openFile(const QModelIndex & index)211 void KoRecentDocumentsPane::openFile(const QModelIndex& index)
212 {
213     if (!index.isValid()) return;
214 
215     KConfigGroup cfgGrp(KSharedConfig::openConfig(), "TemplateChooserDialog");
216     cfgGrp.writeEntry("LastReturnType", "File");
217 
218     KoFileListItem* item = static_cast<KoFileListItem*>(model()->itemFromIndex(index));
219     KFileItem fileItem = item->fileItem();
220 
221     if (!fileItem.isNull()) {
222         emit openUrl(fileItem.url());
223     }
224 }
225 
previewResult(KJob * job)226 void KoRecentDocumentsPane::previewResult(KJob* job)
227 {
228     d->m_previewJobs.removeOne(job);
229 }
230 
updatePreview(const KFileItem & fileItem,const QPixmap & preview)231 void KoRecentDocumentsPane::updatePreview(const KFileItem& fileItem, const QPixmap& preview)
232 {
233     if (preview.isNull()) {
234         return;
235     }
236 
237     QStandardItem* rootItem = model()->invisibleRootItem();
238 
239     for (int i = 0; i < rootItem->rowCount(); ++i) {
240         KoFileListItem* item = static_cast<KoFileListItem*>(rootItem->child(i));
241         if (item->fileItem().url() == fileItem.url()) {
242             item->setData(preview, PreviewRole);
243 
244             if (m_documentList->selectionModel()->currentIndex() == item->index()) {
245                 m_previewLabel->setPixmap(preview);
246             }
247 
248             break;
249         }
250     }
251 }
252 
updateIcon(const KFileItem & fileItem,const QPixmap & pixmap)253 void KoRecentDocumentsPane::updateIcon(const KFileItem& fileItem, const QPixmap& pixmap)
254 {
255     if (pixmap.isNull()) {
256         return;
257     }
258 
259     QStandardItem *rootItem = model()->invisibleRootItem();
260 
261     for (int i = 0; i < rootItem->rowCount(); ++i) {
262         KoFileListItem *item = static_cast<KoFileListItem*>(rootItem->child(i));
263         if (item->fileItem().url() == fileItem.url()) {
264             // ensure squareness
265             QImage icon = pixmap.toImage();
266             icon = icon.convertToFormat(QImage::Format_ARGB32);
267             icon = icon.copy((icon.width() - IconExtent) / 2, (icon.height() - IconExtent) / 2, IconExtent, IconExtent);
268 
269             item->setIcon(QIcon(QPixmap::fromImage(icon)));
270 
271             break;
272         }
273     }
274 }
275