1 /************************************************************************
2 * *
3 * This file is part of Kooka, a scanning/OCR application using *
4 * Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>. *
5 * *
6 * Copyright (C) 2002-2016 Klaas Freitag <freitag@suse.de> *
7 * Jonathan Marten <jjm@keelhaul.me.uk> *
8 * *
9 * Kooka is free software; you can redistribute it and/or modify it *
10 * under the terms of the GNU Library General Public License as *
11 * published by the Free Software Foundation and appearing in the *
12 * file COPYING included in the packaging of this file; either *
13 * version 2 of the License, or (at your option) any later version. *
14 * *
15 * As a special exception, permission is given to link this program *
16 * with any version of the KADMOS OCR/ICR engine (a product of *
17 * reRecognition GmbH, Kreuzlingen), and distribute the resulting *
18 * executable without including the source code for KADMOS in the *
19 * source distribution. *
20 * *
21 * This program is distributed in the hope that it will be useful, *
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
24 * GNU General Public License for more details. *
25 * *
26 * You should have received a copy of the GNU General Public *
27 * License along with this program; see the file COPYING. If *
28 * not, see <http://www.gnu.org/licenses/>. *
29 * *
30 ************************************************************************/
31
32 #include "thumbview.h"
33
34 #include <qabstractitemview.h>
35 #include <qlistview.h>
36 #include <qaction.h>
37 #include <qdebug.h>
38 #include <qmenu.h>
39 #include <qstandardpaths.h>
40 #include <QSignalBlocker>
41
42 #include <kfileitem.h>
43 #include <kactionmenu.h>
44 #include <kdiroperator.h>
45 #include <klocalizedstring.h>
46 #include <kcolorscheme.h>
47
48 #include "kookapref.h"
49 #include "kookasettings.h"
50
51
createActionForSize(KIconLoader::StdSizes size)52 void ThumbView::createActionForSize(KIconLoader::StdSizes size)
53 {
54 KToggleAction *act = new KToggleAction(sizeName(size), this);
55 connect(act, &QAction::triggered, this, [=]() { slotSetSize(size); });
56 m_sizeMap[size] = act;
57 m_sizeMenu->addAction(act);
58 }
59
60
ThumbView(QWidget * parent)61 ThumbView::ThumbView(QWidget *parent)
62 : KDirOperator(QUrl(), parent)
63 {
64 setObjectName("ThumbView");
65
66 m_thumbSize = KIconLoader::SizeHuge;
67 m_firstMenu = true;
68
69 // There seems to be no way to set the maximum preview file size or to
70 // ignore it directly, the preview job with that setting is private to
71 // KFilePreviewGenerator. But we can set the size limit in our application's
72 // configuration - this also has a useful side effect that the user can
73 // change the setting there if they so wish.
74 // See PreviewJob::startPreview() in kio/src/widgets/previewjob.cpp
75 qDebug() << "Maximum preview file size is" << KookaSettings::previewMaximumSize();
76
77 setUrl(QUrl::fromUserInput(KookaPref::galleryRoot()), true);
78 // initial location
79 setPreviewWidget(nullptr); // no preview at side
80 setMode(KFile::File); // implies single selection mode
81 setInlinePreviewShown(true); // show file previews
82 setView(KFile::Simple); // simple icon view
83 dirLister()->setMimeExcludeFilter(QStringList("inode/directory"));
84 // only files, not directories
85
86 connect(this, SIGNAL(fileSelected(KFileItem)),
87 SLOT(slotFileSelected(KFileItem)));
88 connect(this, SIGNAL(fileHighlighted(KFileItem)),
89 SLOT(slotFileHighlighted(KFileItem)));
90 connect(this, SIGNAL(finishedLoading()),
91 SLOT(slotFinishedLoading()));
92
93 // We want to provide our own context menu, not the one that
94 // KDirOperator has built in.
95 disconnect(view(), SIGNAL(customContextMenuRequested(QPoint)), nullptr, nullptr);
96 connect(view(), SIGNAL(customContextMenuRequested(QPoint)),
97 SLOT(slotContextMenu(QPoint)));
98
99 mContextMenu = new QMenu(this);
100 mContextMenu->addSection(i18n("Thumbnails"));
101
102 // Scrolling to the selected item only works after the KDirOperator's
103 // KDirModel has received this signal.
104 connect(dirLister(), SIGNAL(refreshItems(QList<QPair<KFileItem,KFileItem> >)),
105 SLOT(slotEnsureVisible()));
106
107 m_lastSelected = url();
108 m_toSelect = QUrl();
109 m_toChangeTo = QUrl();
110
111 readSettings();
112
113 m_sizeMenu = new KActionMenu(i18n("Preview Size"), this);
114 createActionForSize(KIconLoader::SizeEnormous);
115 createActionForSize(KIconLoader::SizeHuge);
116 createActionForSize(KIconLoader::SizeLarge);
117 createActionForSize(KIconLoader::SizeMedium);
118 createActionForSize(KIconLoader::SizeSmallMedium);
119
120 setMinimumSize(64, 64); // sensible minimum size
121 }
122
~ThumbView()123 ThumbView::~ThumbView()
124 {
125 saveConfig();
126 }
127
slotHighlightItem(const QUrl & url,bool isDir)128 void ThumbView::slotHighlightItem(const QUrl &url, bool isDir)
129 {
130 QUrl cur = this->url(); // directory currently showing
131 QUrl urlToShow = url; // new URL to show
132 QUrl dirToShow = urlToShow; // directory part of that
133 if (!isDir) dirToShow = dirToShow.adjusted(QUrl::RemoveFilename);
134
135 if (cur.adjusted(QUrl::StripTrailingSlash).path() != dirToShow.adjusted(QUrl::StripTrailingSlash).path())
136 { // see if changing path
137 if (!isDir) m_toSelect = urlToShow; // select that when loading finished
138
139 // Bug 216928: Need to check whether the KDirOperator's KDirLister is
140 // currently busy. If it is, then trying to set the KDirOperator to a
141 // new directory at this point is accepted but fails soon afterwards
142 // with an assertion such as:
143 //
144 // kooka(7283)/kio (KDirModel): Items emitted in directory
145 // KUrl("file:///home/jjm4/Documents/KookaGallery/a")
146 // but that directory isn't in KDirModel!
147 // Root directory: KUrl("file:///home/jjm4/Documents/KookaGallery/a/a")
148 // ASSERT: "result" in file /ws/trunk/kdelibs/kio/kio/kdirmodel.cpp, line 372
149 //
150 // To fix this, if the KDirLister is busy we delay changing to the new
151 // directory until the it has finished, the finishedLoading() signal
152 // will then call our slotFinishedLoading() and do the setUrl() there.
153 //
154 // There are two possible (but extremely unlikely) race conditions here.
155
156 #ifdef WORKAROUND_216928
157 if (dirLister()->isFinished()) { // idle, can do this now
158 //qDebug() << "lister idle, changing dir to" << dirToShow;
159 setUrl(dirToShow, true); // change path and reload
160 } else {
161 //qDebug() << "lister busy, deferring change to" << dirToShow;
162 m_toChangeTo = dirToShow; // note to do later
163 }
164 #else
165 //qDebug() << "changing dir to" << dirToShow;
166 setUrl(dirToShow, true); // change path and reload
167 #endif
168 return;
169 }
170
171 KFileItemList selItems = selectedItems();
172 if (!selItems.isEmpty()) { // the current selection
173 KFileItem curItem = selItems.first();
174 if (curItem.url() == urlToShow) {
175 return; // already selected
176 }
177 }
178
179 QSignalBlocker block(this);
180 setCurrentItem(isDir ? QUrl() : urlToShow);
181 }
182
slotContextMenu(const QPoint & pos)183 void ThumbView::slotContextMenu(const QPoint &pos)
184 {
185 if (m_firstMenu) { // first time menu activated
186 mContextMenu->addSeparator();
187 mContextMenu->addAction(m_sizeMenu); // append size menu at end
188 m_firstMenu = false; // note this has been done
189 }
190
191 for (QMap<KIconLoader::StdSizes, KToggleAction *>::const_iterator it = m_sizeMap.constBegin();
192 it != m_sizeMap.constEnd(); ++it) { // tick applicable size entry
193 (*it)->setChecked(m_thumbSize == it.key());
194 }
195
196 mContextMenu->exec(QCursor::pos());
197 }
198
slotSetSize(KIconLoader::StdSizes size)199 void ThumbView::slotSetSize(KIconLoader::StdSizes size)
200 {
201 m_thumbSize = size;
202
203 // see KDirOperator::setIconsZoom() in kio/src/kfilewidgets/kdiroperator.cpp
204 const int val = ((size-KIconLoader::SizeSmall)*100) / (KIconLoader::SizeEnormous-KIconLoader::SizeSmall);
205 //qDebug() << "size" << size << "-> val" << val;
206 setIconsZoom(val);
207 }
208
slotFinishedLoading()209 void ThumbView::slotFinishedLoading()
210 {
211 if (m_toChangeTo.isValid()) { // see if change deferred
212 //qDebug() << "setting dirop url to" << m_toChangeTo;
213 setUrl(m_toChangeTo, true); // change path and reload
214 m_toChangeTo = QUrl(); // have dealt with this now
215 return;
216 }
217
218 if (m_toSelect.isValid()) { // see if something to select
219 //qDebug() << "selecting" << m_toSelect;
220 QSignalBlocker block(this);
221 setCurrentItem(m_toSelect);
222 m_toSelect = QUrl(); // have dealt with this now
223 }
224 }
225
slotEnsureVisible()226 void ThumbView::slotEnsureVisible()
227 {
228 QListView *v = qobject_cast<QListView *>(view());
229 if (v == nullptr) {
230 return;
231 }
232
233 // Ensure that the currently selected item is visible,
234 // from KDirOperator::Private::_k_assureVisibleSelection()
235 // in kdelibs/kfile/kdiroperator.cpp
236 QItemSelectionModel *selModel = v->selectionModel();
237 if (selModel->hasSelection()) {
238 const QModelIndex index = selModel->currentIndex();
239 //qDebug() << "ensuring visible" << index;
240 v->scrollTo(index, QAbstractItemView::EnsureVisible);
241 }
242 }
243
slotFileSelected(const KFileItem & kfi)244 void ThumbView::slotFileSelected(const KFileItem &kfi)
245 {
246 QUrl u = (!kfi.isNull() ? kfi.url() : url());
247 //qDebug() << u;
248
249 if (u != m_lastSelected) {
250 m_lastSelected = u;
251 emit itemActivated(u);
252 }
253 }
254
slotFileHighlighted(const KFileItem & kfi)255 void ThumbView::slotFileHighlighted(const KFileItem &kfi)
256 {
257 QUrl u = (!kfi.isNull() ? kfi.url() : url());
258 //qDebug() << u;
259 emit itemHighlighted(u);
260 }
261
readSettings()262 void ThumbView::readSettings()
263 {
264 slotSetSize(static_cast<KIconLoader::StdSizes>(KookaSettings::thumbnailPreviewSize()));
265 setBackground();
266 }
267
saveConfig()268 void ThumbView::saveConfig()
269 {
270 // Do nothing, preview size set by menu is for this session only.
271 // Set the default size in Kooka Preferences.
272 }
273
setBackground()274 void ThumbView::setBackground()
275 {
276 QPixmap bgPix;
277 QWidget *iv = view()->viewport(); // go down to the icon view
278 QPalette pal = iv->palette();
279
280 QString newBgImg = KookaSettings::thumbnailBackgroundPath();
281 bool newCustomBg = KookaSettings::thumbnailCustomBackground();
282 //qDebug() << "custom" << newCustomBg << "img" << newBgImg;
283
284 if (newCustomBg && !newBgImg.isEmpty()) { // can set custom background
285 if (bgPix.load(newBgImg)) {
286 pal.setBrush(iv->backgroundRole(), QBrush(bgPix));
287 } else {
288 qWarning() << "Failed to load background image" << newBgImg;
289 }
290 } else { // reset to default
291 KColorScheme sch(QPalette::Normal);
292 pal.setBrush(iv->backgroundRole(), sch.background());
293 }
294
295 iv->setPalette(pal);
296 }
297
slotImageChanged(const KFileItem * kfi)298 void ThumbView::slotImageChanged(const KFileItem *kfi)
299 {
300 //qDebug() << kfi->url();
301 // TODO: is there an equivalent?
302 //m_fileview->updateView(kfi); // update that view item
303 }
304
slotImageRenamed(const KFileItem * item,const QString & newName)305 void ThumbView::slotImageRenamed(const KFileItem *item, const QString &newName)
306 {
307 //qDebug() << item->url() << "->" << newName;
308
309 // Nothing to do here.
310 // KDirLister::refreshItems signal -> slotEnsureVisible()
311 // will scroll to the selected item.
312 }
313
slotImageDeleted(const KFileItem * kfi)314 void ThumbView::slotImageDeleted(const KFileItem *kfi)
315 {
316 // No need to do anything here.
317 }
318
319 // TODO: code directly in settings
standardBackground()320 QString ThumbView::standardBackground()
321 {
322 return (QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/thumbviewtile.png"));
323 }
324
sizeName(KIconLoader::StdSizes size)325 QString ThumbView::sizeName(KIconLoader::StdSizes size)
326 {
327 switch (size) {
328 case KIconLoader::SizeEnormous: return (i18n("Very Large"));
329 case KIconLoader::SizeHuge: return (i18n("Large"));
330 case KIconLoader::SizeLarge: return (i18n("Medium"));
331 case KIconLoader::SizeMedium: return (i18n("Small"));
332 case KIconLoader::SizeSmallMedium: return (i18n("Very Small"));
333 case KIconLoader::SizeSmall: return (i18n("Tiny"));
334 default: return ("?");
335 }
336 }
337