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 }