File indexing completed on 2024-04-21 04:18:48
0001 // vim: set tabstop=4 shiftwidth=4 expandtab: 0002 /* 0003 Gwenview: an image viewer 0004 Copyright 2009 Aurélien Gâteau <agateau@kde.org> 0005 0006 This program is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU General Public License 0008 as published by the Free Software Foundation; either version 2 0009 of the License, or (at your option) any later version. 0010 0011 This program is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 GNU General Public License for more details. 0015 0016 You should have received a copy of the GNU General Public License 0017 along with this program; if not, write to the Free Software 0018 Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. 0019 0020 */ 0021 // Self 0022 #include "thumbnailpage.h" 0023 0024 // Qt 0025 #include <QDesktopServices> 0026 #include <QDir> 0027 #include <QGraphicsOpacityEffect> 0028 #include <QIcon> 0029 #include <QProcess> 0030 #include <QPushButton> 0031 #include <QTreeView> 0032 0033 // KF 0034 #include <KAcceleratorManager> 0035 #include <KDirLister> 0036 #include <KDirModel> 0037 #include <KIO/DesktopExecParser> 0038 #include <KIO/StatJob> 0039 #include <KIconLoader> 0040 #include <KJobWidgets> 0041 #include <KMessageBox> 0042 #include <KModelIndexProxyMapper> 0043 #include <kio/global.h> 0044 0045 // Local 0046 #include <documentdirfinder.h> 0047 #include <importerconfigdialog.h> 0048 #include <lib/archiveutils.h> 0049 #include <lib/gwenviewconfig.h> 0050 #include <lib/kindproxymodel.h> 0051 #include <lib/recursivedirmodel.h> 0052 #include <lib/semanticinfo/sorteddirmodel.h> 0053 #include <lib/thumbnailprovider/thumbnailprovider.h> 0054 #include <lib/thumbnailview/abstractthumbnailviewhelper.h> 0055 #include <lib/thumbnailview/previewitemdelegate.h> 0056 #include <serializedurlmap.h> 0057 #include <ui_thumbnailpage.h> 0058 0059 namespace Gwenview 0060 { 0061 static const int DEFAULT_THUMBNAIL_SIZE = 128; 0062 static const qreal DEFAULT_THUMBNAIL_ASPECT_RATIO = 3. / 2.; 0063 0064 static const char *URL_FOR_BASE_URL_GROUP = "UrlForBaseUrl"; 0065 0066 class ImporterThumbnailViewHelper : public AbstractThumbnailViewHelper 0067 { 0068 public: 0069 ImporterThumbnailViewHelper(QObject *parent) 0070 : AbstractThumbnailViewHelper(parent) 0071 { 0072 } 0073 0074 void showContextMenu(QWidget *) override 0075 { 0076 } 0077 0078 void showMenuForUrlDroppedOnViewport(QWidget *, const QList<QUrl> &) override 0079 { 0080 } 0081 0082 void showMenuForUrlDroppedOnDir(QWidget *, const QList<QUrl> &, const QUrl &) override 0083 { 0084 } 0085 }; 0086 0087 inline KFileItem itemForIndex(const QModelIndex &index) 0088 { 0089 return index.data(KDirModel::FileItemRole).value<KFileItem>(); 0090 } 0091 0092 struct ThumbnailPagePrivate : public Ui_ThumbnailPage { 0093 ThumbnailPage *q = nullptr; 0094 SerializedUrlMap mUrlMap; 0095 0096 QIcon mSrcBaseIcon; 0097 QString mSrcBaseName; 0098 QUrl mSrcBaseUrl; 0099 QUrl mSrcUrl; 0100 KModelIndexProxyMapper *mSrcUrlModelProxyMapper = nullptr; 0101 0102 RecursiveDirModel *mRecursiveDirModel = nullptr; 0103 QAbstractItemModel *mFinalModel = nullptr; 0104 0105 ThumbnailProvider mThumbnailProvider; 0106 0107 // Placeholder view 0108 QLabel *mPlaceHolderIconLabel = nullptr; 0109 QLabel *mPlaceHolderLabel = nullptr; 0110 QLabel *mRequireRestartLabel = nullptr; 0111 QPushButton *mInstallProtocolSupportButton = nullptr; 0112 QVBoxLayout *mPlaceHolderLayout = nullptr; 0113 QWidget *mPlaceHolderWidget = nullptr; // To avoid clipping in gridLayout 0114 0115 QPushButton *mImportSelectedButton; 0116 QPushButton *mImportAllButton; 0117 QList<QUrl> mUrlList; 0118 0119 void setupDirModel() 0120 { 0121 mRecursiveDirModel = new RecursiveDirModel(q); 0122 0123 auto kindProxyModel = new KindProxyModel(q); 0124 kindProxyModel->setKindFilter(MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO); 0125 kindProxyModel->setSourceModel(mRecursiveDirModel); 0126 0127 auto sortModel = new QSortFilterProxyModel(q); 0128 sortModel->setDynamicSortFilter(true); 0129 sortModel->setSourceModel(kindProxyModel); 0130 sortModel->sort(0); 0131 0132 mFinalModel = sortModel; 0133 0134 QObject::connect(mFinalModel, &QAbstractItemModel::rowsInserted, q, &ThumbnailPage::updateImportButtons); 0135 QObject::connect(mFinalModel, &QAbstractItemModel::rowsRemoved, q, &ThumbnailPage::updateImportButtons); 0136 QObject::connect(mFinalModel, &QAbstractItemModel::modelReset, q, &ThumbnailPage::updateImportButtons); 0137 } 0138 0139 void setupIcons() 0140 { 0141 const int size = KIconLoader::SizeHuge; 0142 mSrcIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("camera-photo")).pixmap(size)); 0143 mDstIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("computer")).pixmap(size)); 0144 } 0145 0146 void setupSrcUrlWidgets() 0147 { 0148 mSrcUrlModelProxyMapper = nullptr; 0149 QObject::connect(mSrcUrlButton, &QAbstractButton::clicked, q, &ThumbnailPage::setupSrcUrlTreeView); 0150 QObject::connect(mSrcUrlButton, &QAbstractButton::clicked, q, &ThumbnailPage::toggleSrcUrlTreeView); 0151 mSrcUrlTreeView->hide(); 0152 KAcceleratorManager::setNoAccel(mSrcUrlButton); 0153 } 0154 0155 void setupDstUrlRequester() 0156 { 0157 mDstUrlRequester->setMode(KFile::Directory | KFile::LocalOnly); 0158 } 0159 0160 void setupThumbnailView() 0161 { 0162 mThumbnailView->setModel(mFinalModel); 0163 0164 mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0165 mThumbnailView->setThumbnailViewHelper(new ImporterThumbnailViewHelper(q)); 0166 0167 auto delegate = new PreviewItemDelegate(mThumbnailView); 0168 delegate->setThumbnailDetails(PreviewItemDelegate::FileNameDetail); 0169 PreviewItemDelegate::ContextBarActions actions; 0170 switch (GwenviewConfig::thumbnailActions()) { 0171 case ThumbnailActions::None: 0172 actions = PreviewItemDelegate::NoAction; 0173 break; 0174 case ThumbnailActions::ShowSelectionButtonOnly: 0175 case ThumbnailActions::AllButtons: 0176 actions = PreviewItemDelegate::SelectionAction; 0177 break; 0178 } 0179 delegate->setContextBarActions(actions); 0180 mThumbnailView->setItemDelegate(delegate); 0181 0182 QObject::connect(mSlider, &ZoomSlider::valueChanged, mThumbnailView, &ThumbnailView::setThumbnailWidth); 0183 QObject::connect(mThumbnailView, &ThumbnailView::thumbnailWidthChanged, mSlider, &ZoomSlider::setValue); 0184 int thumbnailSize = DEFAULT_THUMBNAIL_SIZE; 0185 mSlider->setValue(thumbnailSize); 0186 mSlider->updateToolTip(); 0187 mThumbnailView->setThumbnailAspectRatio(DEFAULT_THUMBNAIL_ASPECT_RATIO); 0188 mThumbnailView->setThumbnailWidth(thumbnailSize); 0189 mThumbnailView->setThumbnailProvider(&mThumbnailProvider); 0190 0191 QObject::connect(mThumbnailView->selectionModel(), &QItemSelectionModel::selectionChanged, q, &ThumbnailPage::updateImportButtons); 0192 } 0193 0194 void setupPlaceHolderView(const QString &errorText) 0195 { 0196 mPlaceHolderWidget = new QWidget(q); 0197 // Use QSizePolicy::MinimumExpanding to avoid clipping 0198 mPlaceHolderWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); 0199 0200 // Icon 0201 mPlaceHolderIconLabel = new QLabel(mPlaceHolderWidget); 0202 const QSize iconSize(KIconLoader::SizeHuge, KIconLoader::SizeHuge); 0203 mPlaceHolderIconLabel->setMinimumSize(iconSize); 0204 mPlaceHolderIconLabel->setPixmap(QIcon::fromTheme(QStringLiteral("edit-none")).pixmap(iconSize)); 0205 auto iconEffect = new QGraphicsOpacityEffect(mPlaceHolderIconLabel); 0206 iconEffect->setOpacity(0.5); 0207 mPlaceHolderIconLabel->setGraphicsEffect(iconEffect); 0208 0209 // Label: see dolphin/src/views/dolphinview.cpp 0210 const QSizePolicy labelSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred, QSizePolicy::Label); 0211 mPlaceHolderLabel = new QLabel(mPlaceHolderWidget); 0212 mPlaceHolderLabel->setSizePolicy(labelSizePolicy); 0213 QFont placeholderLabelFont; 0214 // To match the size of a level 2 Heading/KTitleWidget 0215 placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3)); 0216 mPlaceHolderLabel->setFont(placeholderLabelFont); 0217 mPlaceHolderLabel->setTextInteractionFlags(Qt::NoTextInteraction); 0218 mPlaceHolderLabel->setWordWrap(true); 0219 mPlaceHolderLabel->setAlignment(Qt::AlignCenter); 0220 // Match opacity of QML placeholder label component 0221 auto effect = new QGraphicsOpacityEffect(mPlaceHolderLabel); 0222 effect->setOpacity(0.5); 0223 mPlaceHolderLabel->setGraphicsEffect(effect); 0224 // Show more friendly text when the protocol is "camera" (which is the usual case) 0225 const QString scheme(mSrcBaseUrl.scheme()); 0226 // Truncate long protocol name 0227 const QString truncatedScheme( 0228 scheme.length() <= 10 ? scheme : QStringView(scheme).left(5).toString() + QStringLiteral("…") + QStringView(scheme).right(5).toString()); 0229 // clang-format off 0230 if (scheme == QLatin1String("camera")) { 0231 mPlaceHolderLabel->setText(i18nc("@info above install button when Kamera is not installed", "Support for your camera is not installed.")); 0232 } else if (!errorText.isEmpty()) { 0233 mPlaceHolderLabel->setText(i18nc("@info above install button, %1 protocol name %2 error text from KIO", "The protocol support library for \"%1\" is not installed. Error: %2", truncatedScheme, errorText)); 0234 } else { 0235 mPlaceHolderLabel->setText(i18nc("@info above install button, %1 protocol name", "The protocol support library for \"%1\" is not installed.", truncatedScheme)); 0236 } 0237 0238 // Label to guide the user to restart the wizard after installing the protocol support library 0239 mRequireRestartLabel = new QLabel(mPlaceHolderWidget); 0240 mRequireRestartLabel->setSizePolicy(labelSizePolicy); 0241 mRequireRestartLabel->setTextInteractionFlags(Qt::NoTextInteraction); 0242 mRequireRestartLabel->setWordWrap(true); 0243 mRequireRestartLabel->setAlignment(Qt::AlignCenter); 0244 auto effect2 = new QGraphicsOpacityEffect(mRequireRestartLabel); 0245 effect2->setOpacity(0.5); 0246 mRequireRestartLabel->setGraphicsEffect(effect2); 0247 mRequireRestartLabel->setText(i18nc("@info:usagetip above install button", "After finishing the installation process, restart this Importer to continue.")); 0248 0249 // Button 0250 // Check if Discover is installed 0251 q->mDiscoverAvailable = !QStandardPaths::findExecutable("plasma-discover").isEmpty(); 0252 QIcon buttonIcon(q->mDiscoverAvailable ? QIcon::fromTheme("plasmadiscover") : QIcon::fromTheme("install")); 0253 QString buttonText, whatsThisText; 0254 QString tooltipText(i18nc("@info:tooltip for a button, %1 protocol name", "Launch Discover to install the protocol support library for \"%1\"", scheme)); 0255 if (scheme == QLatin1String("camera")) { 0256 buttonText = i18nc("@action:button", "Install Support for this Camera…"); 0257 whatsThisText = i18nc("@info:whatsthis for a button when Kamera is not installed", "You need Kamera installed on your system to read from the camera. Click here to launch Discover to install Kamera to enable protocol support for \"camera:/\" on your system."); 0258 } else { 0259 if (q->mDiscoverAvailable) { 0260 buttonText = i18nc("@action:button %1 protocol name", "Install Protocol Support for \"%1\"…", truncatedScheme); 0261 whatsThisText = i18nc("@info:whatsthis for a button, %1 protocol name", "Click here to launch Discover to install the missing protocol support library to enable the protocol support for \"%1:/\" on your system.", scheme); 0262 } else { 0263 // If Discover is not found on the system, guide the user to search the web. 0264 buttonIcon = QIcon::fromTheme("internet-web-browser"); 0265 buttonText = i18nc("@action:button %1 protocol name", "Search the Web for How to Install Protocol Support for \"%1\"…", truncatedScheme); 0266 tooltipText.clear(); 0267 } 0268 } 0269 mInstallProtocolSupportButton = new QPushButton(buttonIcon, buttonText, mPlaceHolderWidget); 0270 mInstallProtocolSupportButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 0271 mInstallProtocolSupportButton->setToolTip(tooltipText); 0272 mInstallProtocolSupportButton->setWhatsThis(whatsThisText); 0273 // Highlight the button so the user can notice it more easily. 0274 mInstallProtocolSupportButton->setDefault(true); 0275 mInstallProtocolSupportButton->setFocus(); 0276 // Button action 0277 if (q->mDiscoverAvailable || scheme == QLatin1String("camera")) { 0278 QObject::connect(mInstallProtocolSupportButton, &QAbstractButton::clicked, q, &ThumbnailPage::installProtocolSupport); 0279 } else { 0280 QObject::connect(mInstallProtocolSupportButton, &QAbstractButton::clicked, q, [scheme]() { 0281 const QString searchKeyword(QUrl::toPercentEncoding(i18nc("@info this text will be used as a search term in an online search engine, %1 protocol name", "How to install protocol support for \"%1\" on Linux", scheme)).constData()); 0282 const QString searchEngineURL(i18nc("search engine URL, %1 search keyword, and translators can replace duckduckgo with other search engines", "https://duckduckgo.com/?q=%1", searchKeyword)); 0283 QDesktopServices::openUrl(QUrl(searchEngineURL)); 0284 }); 0285 } 0286 // clang-format on 0287 0288 // VBoxLayout 0289 mPlaceHolderLayout = new QVBoxLayout(mPlaceHolderWidget); 0290 mPlaceHolderLayout->addStretch(); 0291 mPlaceHolderLayout->addWidget(mPlaceHolderIconLabel, 0, Qt::AlignCenter); 0292 mPlaceHolderLayout->addWidget(mPlaceHolderLabel); 0293 mPlaceHolderLayout->addWidget(mRequireRestartLabel); 0294 mPlaceHolderLayout->addWidget(mInstallProtocolSupportButton, 0, Qt::AlignCenter); // Do not stretch the button 0295 mPlaceHolderLayout->addStretch(); 0296 0297 // Hide other controls 0298 gridLayout->removeItem(verticalLayout); 0299 gridLayout->removeItem(verticalLayout_2); 0300 gridLayout->removeWidget(label); 0301 gridLayout->removeWidget(label_2); 0302 gridLayout->removeWidget(widget); 0303 gridLayout->removeWidget(widget); 0304 gridLayout->removeWidget(mDstUrlRequester); 0305 mDstIconLabel->hide(); 0306 mSrcIconLabel->hide(); 0307 label->hide(); 0308 label_2->hide(); 0309 mDstUrlRequester->hide(); 0310 widget->hide(); 0311 widget_2->hide(); 0312 mConfigureButton->hide(); 0313 mImportSelectedButton->hide(); 0314 mImportAllButton->hide(); 0315 0316 gridLayout->addWidget(mPlaceHolderWidget, 0, 0, 1, 0); 0317 } 0318 0319 void setupButtonBox() 0320 { 0321 QObject::connect(mConfigureButton, &QAbstractButton::clicked, q, &ThumbnailPage::showConfigDialog); 0322 0323 mImportSelectedButton = mButtonBox->addButton(i18n("Import Selected"), QDialogButtonBox::AcceptRole); 0324 QObject::connect(mImportSelectedButton, &QAbstractButton::clicked, q, &ThumbnailPage::slotImportSelected); 0325 0326 mImportAllButton = mButtonBox->addButton(i18n("Import All"), QDialogButtonBox::AcceptRole); 0327 QObject::connect(mImportAllButton, &QAbstractButton::clicked, q, &ThumbnailPage::slotImportAll); 0328 0329 QObject::connect(mButtonBox, &QDialogButtonBox::rejected, q, &ThumbnailPage::rejected); 0330 } 0331 0332 QUrl urlForBaseUrl() const 0333 { 0334 QUrl url = mUrlMap.value(mSrcBaseUrl); 0335 if (!url.isValid()) { 0336 return {}; 0337 } 0338 0339 KIO::StatJob *job = KIO::stat(url); 0340 KJobWidgets::setWindow(job, q); 0341 if (!job->exec()) { 0342 return {}; 0343 } 0344 KFileItem item(job->statResult(), url, true /* delayedMimeTypes */); 0345 return item.isDir() ? url : QUrl(); 0346 } 0347 0348 void rememberUrl(const QUrl &url) 0349 { 0350 mUrlMap.insert(mSrcBaseUrl, url); 0351 } 0352 }; 0353 0354 ThumbnailPage::ThumbnailPage() 0355 : d(new ThumbnailPagePrivate) 0356 { 0357 d->q = this; 0358 d->mUrlMap.setConfigGroup(KConfigGroup(KSharedConfig::openConfig(), URL_FOR_BASE_URL_GROUP)); 0359 d->setupUi(this); 0360 d->setupIcons(); 0361 d->setupDirModel(); 0362 d->setupSrcUrlWidgets(); 0363 d->setupDstUrlRequester(); 0364 d->setupThumbnailView(); 0365 d->setupButtonBox(); 0366 updateImportButtons(); 0367 } 0368 0369 ThumbnailPage::~ThumbnailPage() 0370 { 0371 delete d; 0372 } 0373 0374 void ThumbnailPage::setSourceUrl(const QUrl &srcBaseUrl, const QString &iconName, const QString &name) 0375 { 0376 d->mSrcBaseIcon = QIcon::fromTheme(iconName); 0377 d->mSrcBaseName = name; 0378 0379 const int size = KIconLoader::SizeHuge; 0380 d->mSrcIconLabel->setPixmap(d->mSrcBaseIcon.pixmap(size)); 0381 0382 d->mSrcBaseUrl = srcBaseUrl; 0383 if (!d->mSrcBaseUrl.path().endsWith('/')) { 0384 d->mSrcBaseUrl.setPath(d->mSrcBaseUrl.path() + '/'); 0385 } 0386 QUrl url = d->urlForBaseUrl(); 0387 0388 if (url.isValid()) { 0389 openUrl(url); 0390 } else { 0391 auto finder = new DocumentDirFinder(srcBaseUrl); 0392 connect(finder, &DocumentDirFinder::done, this, &ThumbnailPage::slotDocumentDirFinderDone); 0393 connect(finder, &DocumentDirFinder::protocollNotSupportedError, this, [this](const QString &errorText) { 0394 d->setupPlaceHolderView(errorText); 0395 }); 0396 finder->start(); 0397 } 0398 } 0399 0400 void ThumbnailPage::slotDocumentDirFinderDone(const QUrl &url, DocumentDirFinder::Status /*status*/) 0401 { 0402 d->rememberUrl(url); 0403 openUrl(url); 0404 } 0405 0406 void ThumbnailPage::openUrl(const QUrl &url) 0407 { 0408 d->mSrcUrl = url; 0409 QString path = QDir(d->mSrcBaseUrl.path()).relativeFilePath(d->mSrcUrl.path()); 0410 QString text; 0411 if (path.isEmpty() || path == QLatin1String(".")) { 0412 text = d->mSrcBaseName; 0413 } else { 0414 path = QUrl::fromPercentEncoding(path.toUtf8()); 0415 path.replace('/', QString::fromUtf8(" › ")); 0416 text = QString::fromUtf8("%1 › %2").arg(d->mSrcBaseName, path); 0417 } 0418 d->mSrcUrlButton->setText(text); 0419 d->mRecursiveDirModel->setUrl(url); 0420 } 0421 0422 QList<QUrl> ThumbnailPage::urlList() const 0423 { 0424 return d->mUrlList; 0425 } 0426 0427 void ThumbnailPage::setDestinationUrl(const QUrl &url) 0428 { 0429 d->mDstUrlRequester->setUrl(url); 0430 } 0431 0432 QUrl ThumbnailPage::destinationUrl() const 0433 { 0434 return d->mDstUrlRequester->url(); 0435 } 0436 0437 void ThumbnailPage::slotImportSelected() 0438 { 0439 importList(d->mThumbnailView->selectionModel()->selectedIndexes()); 0440 } 0441 0442 void ThumbnailPage::slotImportAll() 0443 { 0444 QModelIndexList list; 0445 QAbstractItemModel *model = d->mThumbnailView->model(); 0446 for (int row = model->rowCount() - 1; row >= 0; --row) { 0447 list << model->index(row, 0); 0448 } 0449 importList(list); 0450 } 0451 0452 void ThumbnailPage::importList(const QModelIndexList &list) 0453 { 0454 d->mUrlList.clear(); 0455 for (const QModelIndex &index : list) { 0456 KFileItem item = itemForIndex(index); 0457 if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { 0458 d->mUrlList << item.url(); 0459 } 0460 // FIXME: Handle dirs (do we want to import recursively?) 0461 } 0462 Q_EMIT importRequested(); 0463 } 0464 0465 void ThumbnailPage::updateImportButtons() 0466 { 0467 d->mImportSelectedButton->setEnabled(d->mThumbnailView->selectionModel()->hasSelection()); 0468 d->mImportAllButton->setEnabled(d->mThumbnailView->model()->rowCount(QModelIndex()) > 0); 0469 } 0470 0471 void ThumbnailPage::showConfigDialog() 0472 { 0473 auto dialog = new ImporterConfigDialog(this); 0474 dialog->setAttribute(Qt::WA_DeleteOnClose); 0475 dialog->setModal(true); 0476 dialog->show(); 0477 } 0478 0479 void ThumbnailPage::installProtocolSupport() const 0480 { 0481 const QString scheme(d->mSrcBaseUrl.scheme()); 0482 // clang-format off 0483 if (scheme == QLatin1String("camera")) { 0484 const QUrl kameraInstallUrl("appstream://org.kde.kamera"); 0485 if (KIO::DesktopExecParser::hasSchemeHandler(kameraInstallUrl)) { 0486 QDesktopServices::openUrl(kameraInstallUrl); 0487 } else { 0488 KMessageBox::error(d->widget, xi18nc("@info when failing to open the appstream URL", "Opening Discover failed.<nl/>Please check if Discover is installed on your system, or use your system's package manager to install \"Kamera\" package.")); 0489 } 0490 } else if (!QProcess::startDetached(QStringLiteral("plasma-discover"), QStringList({"--search", scheme}))) { 0491 // For other protocols, search for the protocol name in Discover. 0492 KMessageBox::error(d->widget, xi18nc("@info when failing to launch plasma-discover, %1 protocol name", "Opening Discover failed.<nl/>Please check if Discover is installed on your system, or use your system's package manager to install the protocol support library for \"%1\".", scheme)); 0493 } 0494 // clang-format on 0495 } 0496 0497 /** 0498 * This model allows only the url passed in the constructor to appear at the root 0499 * level. This makes it possible to select the url, but not its siblings. 0500 * It also provides custom role values for the root item. 0501 */ 0502 class OnlyBaseUrlProxyModel : public QSortFilterProxyModel 0503 { 0504 public: 0505 OnlyBaseUrlProxyModel(const QUrl &url, const QIcon &icon, const QString &name, QObject *parent) 0506 : QSortFilterProxyModel(parent) 0507 , mUrl(url) 0508 , mIcon(icon) 0509 , mName(name) 0510 { 0511 } 0512 0513 bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override 0514 { 0515 if (sourceParent.isValid()) { 0516 return true; 0517 } 0518 QModelIndex index = sourceModel()->index(sourceRow, 0); 0519 KFileItem item = itemForIndex(index); 0520 return item.url().matches(mUrl, QUrl::StripTrailingSlash); 0521 } 0522 0523 QVariant data(const QModelIndex &index, int role) const override 0524 { 0525 if (index.parent().isValid()) { 0526 return QSortFilterProxyModel::data(index, role); 0527 } 0528 switch (role) { 0529 case Qt::DisplayRole: 0530 return mName; 0531 case Qt::DecorationRole: 0532 return mIcon; 0533 case Qt::ToolTipRole: 0534 return mUrl.toDisplayString(QUrl::PreferLocalFile); 0535 default: 0536 return QSortFilterProxyModel::data(index, role); 0537 } 0538 } 0539 0540 private: 0541 QUrl mUrl; 0542 QIcon mIcon; 0543 QString mName; 0544 }; 0545 0546 void ThumbnailPage::setupSrcUrlTreeView() 0547 { 0548 if (d->mSrcUrlTreeView->model()) { 0549 // Already initialized 0550 return; 0551 } 0552 auto dirModel = new KDirModel(this); 0553 dirModel->dirLister()->setDirOnlyMode(true); 0554 dirModel->dirLister()->openUrl(KIO::upUrl(d->mSrcBaseUrl)); 0555 0556 auto onlyBaseUrlModel = new OnlyBaseUrlProxyModel(d->mSrcBaseUrl, d->mSrcBaseIcon, d->mSrcBaseName, this); 0557 onlyBaseUrlModel->setSourceModel(dirModel); 0558 0559 auto sortModel = new QSortFilterProxyModel(this); 0560 sortModel->setDynamicSortFilter(true); 0561 sortModel->setSourceModel(onlyBaseUrlModel); 0562 sortModel->sort(0); 0563 0564 d->mSrcUrlModelProxyMapper = new KModelIndexProxyMapper(dirModel, sortModel, this); 0565 0566 d->mSrcUrlTreeView->setModel(sortModel); 0567 for (int i = 1; i < dirModel->columnCount(); ++i) { 0568 d->mSrcUrlTreeView->hideColumn(i); 0569 } 0570 connect(d->mSrcUrlTreeView, &QAbstractItemView::activated, this, &ThumbnailPage::openUrlFromIndex); 0571 connect(d->mSrcUrlTreeView, &QAbstractItemView::clicked, this, &ThumbnailPage::openUrlFromIndex); 0572 0573 dirModel->expandToUrl(d->mSrcUrl); 0574 connect(dirModel, &KDirModel::expand, this, &ThumbnailPage::slotSrcUrlModelExpand); 0575 } 0576 0577 void ThumbnailPage::slotSrcUrlModelExpand(const QModelIndex &index) 0578 { 0579 QModelIndex viewIndex = d->mSrcUrlModelProxyMapper->mapLeftToRight(index); 0580 d->mSrcUrlTreeView->expand(viewIndex); 0581 KFileItem item = itemForIndex(index); 0582 if (item.url() == d->mSrcUrl) { 0583 d->mSrcUrlTreeView->selectionModel()->select(viewIndex, QItemSelectionModel::ClearAndSelect); 0584 } 0585 } 0586 0587 void ThumbnailPage::toggleSrcUrlTreeView() 0588 { 0589 d->mSrcUrlTreeView->setVisible(!d->mSrcUrlTreeView->isVisible()); 0590 } 0591 0592 void ThumbnailPage::openUrlFromIndex(const QModelIndex &index) 0593 { 0594 KFileItem item = itemForIndex(index); 0595 if (item.isNull()) { 0596 return; 0597 } 0598 QUrl url = item.url(); 0599 d->rememberUrl(url); 0600 openUrl(url); 0601 } 0602 0603 } // namespace 0604 0605 #include "moc_thumbnailpage.cpp"