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