File indexing completed on 2024-04-21 15:12:07

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 2002-2016 Klaas Freitag <freitag@suse.de>     *
0007  *                          Jonathan Marten <jjm@keelhaul.me.uk>    *
0008  *                                  *
0009  *  Kooka is free software; you can redistribute it and/or modify it    *
0010  *  under the terms of the GNU Library General Public License as    *
0011  *  published by the Free Software Foundation and appearing in the  *
0012  *  file COPYING included in the packaging of this file;  either    *
0013  *  version 2 of the License, or (at your option) any later version.    *
0014  *                                  *
0015  *  As a special exception, permission is given to link this program    *
0016  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0017  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0018  *  executable without including the source code for KADMOS in the  *
0019  *  source distribution.                        *
0020  *                                  *
0021  *  This program is distributed in the hope that it will be useful, *
0022  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0023  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0024  *  GNU General Public License for more details.            *
0025  *                                  *
0026  *  You should have received a copy of the GNU General Public       *
0027  *  License along with this program;  see the file COPYING.  If     *
0028  *  not, see <http://www.gnu.org/licenses/>.                *
0029  *                                  *
0030  ************************************************************************/
0031 
0032 #include "thumbview.h"
0033 
0034 #include <qabstractitemview.h>
0035 #include <qlistview.h>
0036 #include <qaction.h>
0037 #include <qmenu.h>
0038 #include <qstandardpaths.h>
0039 #include <qtimer.h>
0040 #include <QSignalBlocker>
0041 
0042 #include <kfileitem.h>
0043 #include <kactionmenu.h>
0044 #include <kdiroperator.h>
0045 #include <klocalizedstring.h>
0046 #include <kcolorscheme.h>
0047 #include <kio_version.h>
0048 
0049 #include "galleryroot.h"
0050 #include "kookasettings.h"
0051 #include "kooka_logging.h"
0052 
0053 
0054 void ThumbView::createActionForSize(KIconLoader::StdSizes size)
0055 {
0056     KToggleAction *act = new KToggleAction(sizeName(size), this);
0057     connect(act, &QAction::triggered, this, [=]() { slotSetSize(size); });
0058     m_sizeMap[size] = act;
0059     m_sizeMenu->addAction(act);
0060 }
0061 
0062 
0063 ThumbView::ThumbView(QWidget *parent)
0064     : KDirOperator(QUrl(), parent)
0065 {
0066     setObjectName("ThumbView");
0067 
0068     m_thumbSize = KIconLoader::SizeHuge;
0069     m_firstMenu = true;
0070 
0071     // There seems to be no way to set the maximum preview file size or to
0072     // ignore it directly, the preview job with that setting is private to
0073     // KFilePreviewGenerator.  But we can set the size limit in our application's
0074     // configuration - this also has a useful side effect that the user can
0075     // change the setting there if they so wish.
0076     // See PreviewJob::startPreview() in kio/src/widgets/previewjob.cpp
0077     qCDebug(KOOKA_LOG) << "Maximum preview file size is" << KookaSettings::previewMaximumSize();
0078 
0079     setUrl(GalleryRoot::root(), true);          // initial location
0080     setPreviewWidget(nullptr);              // no preview at side
0081     setMode(KFile::File);               // implies single selection mode
0082     setInlinePreviewShown(true);            // show file previews
0083     setViewMode(KFile::Simple);             // simple icon view
0084     dirLister()->setMimeExcludeFilter(QStringList("inode/directory"));
0085                             // only files, not directories
0086     connect(this, &KDirOperator::fileSelected, this, &ThumbView::slotFileSelected);
0087     connect(this, &KDirOperator::fileHighlighted, this, &ThumbView::slotFileHighlighted);
0088     connect(this, &KDirOperator::finishedLoading, this, &ThumbView::slotFinishedLoading);
0089 
0090     // We want to provide our own context menu, not the one that
0091     // KDirOperator has built in.
0092     disconnect(view(), &QWidget::customContextMenuRequested, nullptr, nullptr);
0093     connect(view(), &QWidget::customContextMenuRequested, this, &ThumbView::slotContextMenu);
0094 
0095     mContextMenu = new QMenu(this);
0096     mContextMenu->addSection(i18n("Thumbnails"));
0097 
0098     // Scrolling to the selected item only works after the KDirOperator's
0099     // KDirModel has received this signal.
0100     connect(dirLister(), &KCoreDirLister::refreshItems, this, &ThumbView::slotEnsureVisible);
0101 
0102     m_lastSelected = url();
0103     m_toSelect = QUrl();
0104 
0105     readSettings();
0106 
0107     m_sizeMenu = new KActionMenu(i18n("Preview Size"), this);
0108     createActionForSize(KIconLoader::SizeEnormous);
0109     createActionForSize(KIconLoader::SizeHuge);
0110     createActionForSize(KIconLoader::SizeLarge);
0111     createActionForSize(KIconLoader::SizeMedium);
0112     createActionForSize(KIconLoader::SizeSmallMedium);
0113 
0114     setMinimumSize(64, 64);             // sensible minimum size
0115 }
0116 
0117 ThumbView::~ThumbView()
0118 {
0119     saveConfig();
0120 }
0121 
0122 void ThumbView::slotHighlightItem(const QUrl &url, bool isDir)
0123 {
0124     QUrl cur = this->url();             // directory currently showing
0125     QUrl urlToShow = url;               // new URL to show
0126     QUrl dirToShow = urlToShow;             // directory part of that
0127     if (!isDir) dirToShow = dirToShow.adjusted(QUrl::RemoveFilename);
0128 
0129     if (cur.adjusted(QUrl::StripTrailingSlash).path() != dirToShow.adjusted(QUrl::StripTrailingSlash).path())
0130     {                           // see if changing path
0131         if (!isDir) m_toSelect = urlToShow;     // select that when loading finished
0132 
0133         // A workaround was formerly needed here:
0134         //
0135         // Bug 216928: Need to check whether the KDirOperator's KDirLister is
0136         // currently busy.  If it is, then trying to set the KDirOperator to a
0137         // new directory at this point is accepted but fails soon afterwards
0138         // with an assertion such as:
0139         //
0140         //    kooka(7283)/kio (KDirModel): Items emitted in directory
0141         //    KUrl("file:///home/jjm4/Documents/KookaGallery/a")
0142         //    but that directory isn't in KDirModel!
0143         //    Root directory: KUrl("file:///home/jjm4/Documents/KookaGallery/a/a")
0144         //    ASSERT: "result" in file /ws/trunk/kdelibs/kio/kio/kdirmodel.cpp, line 372
0145         //
0146         // To fix this, if the KDirLister is busy we delay changing to the new
0147         // directory until the it has finished, the finishedLoading() signal
0148         // will then call our slotFinishedLoading() and do the setUrl() there.
0149         //
0150         // There are two possible (but extremely unlikely) race conditions here.
0151         //
0152         // It is not clear whether this is the same fundamental problem as tried to
0153         // work around above, but it seems to be possible to reliably trigger a
0154         // segfault in KDirModel when a new directory is created for the second time
0155         // at the same level (e.g. the top) of the gallery tree:
0156         //
0157         //    Application: Kooka (kooka), signal: Segmentation fault
0158         //    KFileItem::isDir (this=this@entry=0x8) at kio/src/core/kfileitem.cpp:1284
0159         //    KDirModelPrivate::isDir (node=0x0) at kio/src/widgets/kdirmodel.cpp:222
0160         //    KDirModelPrivate::isDir (node=0x0) at kio/src/widgets/kdirmodel.cpp:220
0161         //    KDirModelPrivate::_k_slotCompleted() at kio/src/widgets/kdirmodel.cpp:572
0162         //    [signal despatch]
0163         //    KCoreDirLister::listingDirCompleted() at moc_kcoredirlister.cpp:497
0164         //    KCoreDirListerCache::slotUpdateResult() at kio/src/core/kcoredirlister.cpp:1842
0165         //    KJob::result() at moc_kjob.cpp:635
0166         //
0167         // The twofold solution seems to be first to stop the lister if it appears
0168         // to be currently busy, and then to defer setting the new URL until after
0169         // a hopefully imperceptible delay.
0170 
0171         if (!dirLister()->isFinished())         // not idle, ensure stopped first
0172         {
0173             qCDebug(KOOKA_LOG) << "lister busy, stopping";
0174             dirLister()->stop();
0175         }
0176 
0177         qCDebug(KOOKA_LOG) << "deferring change to" << dirToShow;
0178         QTimer::singleShot(100, this, [this,dirToShow]()
0179         {
0180             setUrl(dirToShow, true);            // change path and reload
0181         });
0182         return;
0183     }
0184 
0185     KFileItemList selItems = selectedItems();
0186     if (!selItems.isEmpty())                // the current selection
0187     {
0188         KFileItem curItem = selItems.first();
0189         if (curItem.url() == urlToShow) return;     // already selected
0190     }
0191 
0192     QSignalBlocker block(this);
0193     setCurrentItem(isDir ? QUrl() : urlToShow);
0194 }
0195 
0196 void ThumbView::slotContextMenu(const QPoint &pos)
0197 {
0198     if (m_firstMenu) {                  // first time menu activated
0199         mContextMenu->addSeparator();
0200         mContextMenu->addAction(m_sizeMenu);        // append size menu at end
0201         m_firstMenu = false;                // note this has been done
0202     }
0203 
0204     for (QMap<KIconLoader::StdSizes, KToggleAction *>::const_iterator it = m_sizeMap.constBegin();
0205             it != m_sizeMap.constEnd(); ++it) {     // tick applicable size entry
0206         (*it)->setChecked(m_thumbSize == it.key());
0207     }
0208 
0209     mContextMenu->exec(QCursor::pos());
0210 }
0211 
0212 void ThumbView::slotSetSize(KIconLoader::StdSizes size)
0213 {
0214     m_thumbSize = size;
0215 
0216 #if KIO_VERSION>=QT_VERSION_CHECK(5, 76, 0)
0217     setIconSize(size);
0218 #else
0219     // see KDirOperator::setIconsZoom() in kio/src/kfilewidgets/kdiroperator.cpp
0220     const int val = ((size-KIconLoader::SizeSmall)*100) / (KIconLoader::SizeEnormous-KIconLoader::SizeSmall);
0221     qCDebug(KOOKA_LOG) << "size" << size << "-> val" << val;
0222     setIconsZoom(val);
0223 #endif
0224 }
0225 
0226 void ThumbView::slotFinishedLoading()
0227 {
0228     if (m_toSelect.isValid()) {             // see if something to select
0229         qCDebug(KOOKA_LOG) << "selecting" << m_toSelect;
0230         QSignalBlocker block(this);
0231         setCurrentItem(m_toSelect);
0232         m_toSelect = QUrl();                // have dealt with this now
0233     }
0234 }
0235 
0236 void ThumbView::slotEnsureVisible()
0237 {
0238     QListView *v = qobject_cast<QListView *>(view());
0239     if (v == nullptr) {
0240         return;
0241     }
0242 
0243     // Ensure that the currently selected item is visible,
0244     // from KDirOperator::Private::_k_assureVisibleSelection()
0245     // in kdelibs/kfile/kdiroperator.cpp
0246     QItemSelectionModel *selModel = v->selectionModel();
0247     if (selModel->hasSelection()) {
0248         const QModelIndex index = selModel->currentIndex();
0249         qCDebug(KOOKA_LOG) << "ensuring visible" << index;
0250         v->scrollTo(index, QAbstractItemView::EnsureVisible);
0251     }
0252 }
0253 
0254 void ThumbView::slotFileSelected(const KFileItem &kfi)
0255 {
0256     QUrl u = (!kfi.isNull() ? kfi.url() : url());
0257     //qCDebug(KOOKA_LOG) << u;
0258 
0259     if (u != m_lastSelected) {
0260         m_lastSelected = u;
0261         emit itemActivated(u);
0262     }
0263 }
0264 
0265 void ThumbView::slotFileHighlighted(const KFileItem &kfi)
0266 {
0267     QUrl u = (!kfi.isNull() ? kfi.url() : url());
0268     //qCDebug(KOOKA_LOG) << u;
0269     emit itemHighlighted(u);
0270 }
0271 
0272 void ThumbView::readSettings()
0273 {
0274     slotSetSize(static_cast<KIconLoader::StdSizes>(KookaSettings::thumbnailPreviewSize()));
0275     setBackground();
0276 }
0277 
0278 void ThumbView::saveConfig()
0279 {
0280     // Do nothing, preview size set by menu is for this session only.
0281     // Set the default size in Kooka Preferences.
0282 }
0283 
0284 void ThumbView::setBackground()
0285 {
0286     QPixmap bgPix;
0287     QWidget *iv = view()->viewport();           // go down to the icon view
0288     QPalette pal = iv->palette();
0289 
0290     QString newBgImg = KookaSettings::thumbnailBackgroundPath();
0291     bool newCustomBg = KookaSettings::thumbnailCustomBackground();
0292     qCDebug(KOOKA_LOG) << "custom" << newCustomBg << "img" << newBgImg;
0293 
0294     if (newCustomBg && !newBgImg.isEmpty()) {       // can set custom background
0295         if (bgPix.load(newBgImg)) {
0296             pal.setBrush(iv->backgroundRole(), QBrush(bgPix));
0297         } else {
0298             qCWarning(KOOKA_LOG) << "Failed to load background image" << newBgImg;
0299         }
0300     } else {                        // reset to default
0301         KColorScheme sch(QPalette::Normal);
0302         pal.setBrush(iv->backgroundRole(), sch.background());
0303     }
0304 
0305     iv->setPalette(pal);
0306 }
0307 
0308 void ThumbView::slotImageChanged(const KFileItem *kfi)
0309 {
0310     //qCDebug(KOOKA_LOG) << kfi->url();
0311     // TODO: is there an equivalent?
0312     //m_fileview->updateView(kfi);          // update that view item
0313 }
0314 
0315 void ThumbView::slotImageRenamed(const KFileItem *item, const QString &newName)
0316 {
0317     //qCDebug(KOOKA_LOG) << item->url() << "->" << newName;
0318 
0319     // Nothing to do here.
0320     // KDirLister::refreshItems signal -> slotEnsureVisible()
0321     // will scroll to the selected item.
0322 }
0323 
0324 void ThumbView::slotImageDeleted(const KFileItem *kfi)
0325 {
0326     // No need to do anything here.
0327 }
0328 
0329 // TODO: code directly in settings
0330 QString ThumbView::standardBackground()
0331 {
0332     return (QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/thumbviewtile.png"));
0333 }
0334 
0335 QString ThumbView::sizeName(KIconLoader::StdSizes size)
0336 {
0337     switch (size) {
0338     case KIconLoader::SizeEnormous: return (i18n("Very Large"));
0339     case KIconLoader::SizeHuge:     return (i18n("Large"));
0340     case KIconLoader::SizeLarge:    return (i18n("Medium"));
0341     case KIconLoader::SizeMedium:   return (i18n("Small"));
0342     case KIconLoader::SizeSmallMedium:  return (i18n("Very Small"));
0343     case KIconLoader::SizeSmall:    return (i18n("Tiny"));
0344     default:                return ("?");
0345     }
0346 }