File indexing completed on 2024-05-12 05:47:41
0001 /* 0002 * SPDX-FileCopyrightText: 2009 Peter Penz <peter.penz19@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "informationpanelcontent.h" 0008 0009 #include <KConfigGroup> 0010 #include <KIO/PreviewJob> 0011 #include <KIconEffect> 0012 #include <KIconLoader> 0013 #include <KJobWidgets> 0014 #include <KLocalizedString> 0015 #include <KSeparator> 0016 #include <KSharedConfig> 0017 #include <KStringHandler> 0018 #include <QPainterPath> 0019 0020 #include <QIcon> 0021 #include <QStyle> 0022 #include <QTextDocument> 0023 0024 #include <Baloo/FileMetaDataWidget> 0025 0026 #include <phonon/BackendCapabilities> 0027 #include <phonon/MediaObject> 0028 0029 #include <QDialogButtonBox> 0030 #include <QGesture> 0031 #include <QLabel> 0032 #include <QLinearGradient> 0033 #include <QPainter> 0034 #include <QPolygon> 0035 #include <QScrollArea> 0036 #include <QScroller> 0037 #include <QTextLayout> 0038 #include <QTimer> 0039 #include <QVBoxLayout> 0040 0041 #include "dolphin_informationpanelsettings.h" 0042 #include "phononwidget.h" 0043 #include "pixmapviewer.h" 0044 0045 const int PLAY_ARROW_SIZE = 24; 0046 const int PLAY_ARROW_BORDER_SIZE = 2; 0047 0048 InformationPanelContent::InformationPanelContent(QWidget *parent) 0049 : QWidget(parent) 0050 , m_item() 0051 , m_previewJob(nullptr) 0052 , m_outdatedPreviewTimer(nullptr) 0053 , m_preview(nullptr) 0054 , m_phononWidget(nullptr) 0055 , m_nameLabel(nullptr) 0056 , m_metaDataWidget(nullptr) 0057 , m_metaDataArea(nullptr) 0058 , m_isVideo(false) 0059 { 0060 parent->installEventFilter(this); 0061 0062 // Initialize timer for disabling an outdated preview with a small 0063 // delay. This prevents flickering if the new preview can be generated 0064 // within a very small timeframe. 0065 m_outdatedPreviewTimer = new QTimer(this); 0066 m_outdatedPreviewTimer->setInterval(100); 0067 m_outdatedPreviewTimer->setSingleShot(true); 0068 connect(m_outdatedPreviewTimer, &QTimer::timeout, this, &InformationPanelContent::markOutdatedPreview); 0069 0070 QVBoxLayout *layout = new QVBoxLayout(this); 0071 0072 // preview 0073 const int minPreviewWidth = KIconLoader::SizeEnormous + KIconLoader::SizeMedium; 0074 0075 m_preview = new PixmapViewer(parent); 0076 m_preview->setMinimumWidth(minPreviewWidth); 0077 m_preview->setMinimumHeight(KIconLoader::SizeEnormous); 0078 0079 m_phononWidget = new PhononWidget(parent); 0080 m_phononWidget->hide(); 0081 m_phononWidget->setMinimumWidth(minPreviewWidth); 0082 m_phononWidget->setAutoPlay(InformationPanelSettings::previewsAutoPlay()); 0083 connect(m_phononWidget, &PhononWidget::hasVideoChanged, this, &InformationPanelContent::slotHasVideoChanged); 0084 0085 // name 0086 m_nameLabel = new QLabel(parent); 0087 QFont font = m_nameLabel->font(); 0088 font.setBold(true); 0089 m_nameLabel->setFont(font); 0090 m_nameLabel->setTextFormat(Qt::PlainText); 0091 m_nameLabel->setAlignment(Qt::AlignHCenter); 0092 m_nameLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); 0093 m_nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); 0094 0095 const bool previewsShown = InformationPanelSettings::previewsShown(); 0096 m_preview->setVisible(previewsShown); 0097 0098 m_metaDataWidget = new Baloo::FileMetaDataWidget(parent); 0099 m_metaDataWidget->setDateFormat(static_cast<Baloo::DateFormats>(InformationPanelSettings::dateFormat())); 0100 connect(m_metaDataWidget, &Baloo::FileMetaDataWidget::urlActivated, this, &InformationPanelContent::urlActivated); 0101 m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0102 m_metaDataWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); 0103 0104 // Configuration 0105 m_configureLabel = new QLabel(i18nc("@label::textbox", "Select which data should be shown:"), this); 0106 m_configureLabel->setWordWrap(true); 0107 m_configureLabel->setVisible(false); 0108 0109 m_configureButtons = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel); 0110 m_configureButtons->setVisible(false); 0111 connect(m_configureButtons, &QDialogButtonBox::accepted, this, [this]() { 0112 m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::Accept); 0113 m_configureButtons->setVisible(false); 0114 m_configureLabel->setVisible(false); 0115 Q_EMIT configurationFinished(); 0116 }); 0117 connect(m_configureButtons, &QDialogButtonBox::rejected, this, [this]() { 0118 m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::Cancel); 0119 m_configureButtons->setVisible(false); 0120 m_configureLabel->setVisible(false); 0121 Q_EMIT configurationFinished(); 0122 }); 0123 0124 m_metaDataArea = new QScrollArea(parent); 0125 m_metaDataArea->setWidget(m_metaDataWidget); 0126 m_metaDataArea->setWidgetResizable(true); 0127 m_metaDataArea->setFrameShape(QFrame::NoFrame); 0128 0129 QWidget *viewport = m_metaDataArea->viewport(); 0130 QScroller::grabGesture(viewport, QScroller::TouchGesture); 0131 viewport->installEventFilter(this); 0132 0133 layout->addWidget(m_preview); 0134 layout->addWidget(m_phononWidget); 0135 layout->addWidget(m_nameLabel); 0136 layout->addWidget(new KSeparator()); 0137 layout->addWidget(m_configureLabel); 0138 layout->addWidget(m_metaDataArea); 0139 layout->addWidget(m_configureButtons); 0140 0141 grabGesture(Qt::TapAndHoldGesture); 0142 } 0143 0144 InformationPanelContent::~InformationPanelContent() 0145 { 0146 InformationPanelSettings::self()->save(); 0147 } 0148 0149 void InformationPanelContent::showItem(const KFileItem &item) 0150 { 0151 // compares item entries, comparing items only compares urls 0152 if (m_item.entry() != item.entry()) { 0153 m_item = item; 0154 m_preview->stopAnimatedImage(); 0155 refreshMetaData(); 0156 } 0157 0158 refreshPreview(); 0159 } 0160 0161 void InformationPanelContent::refreshPixmapView() 0162 { 0163 // If there is a preview job, kill it to prevent that we have jobs for 0164 // multiple items running, and thus a race condition (bug 250787). 0165 if (m_previewJob) { 0166 m_previewJob->kill(); 0167 } 0168 0169 // try to get a preview pixmap from the item... 0170 0171 // Mark the currently shown preview as outdated. This is done 0172 // with a small delay to prevent a flickering when the next preview 0173 // can be shown within a short timeframe. 0174 m_outdatedPreviewTimer->start(); 0175 0176 const KConfigGroup globalConfig(KSharedConfig::openConfig(), "PreviewSettings"); 0177 const QStringList plugins = globalConfig.readEntry("Plugins", KIO::PreviewJob::defaultPlugins()); 0178 m_previewJob = new KIO::PreviewJob(KFileItemList() << m_item, QSize(m_preview->width(), m_preview->height()), &plugins); 0179 m_previewJob->setScaleType(KIO::PreviewJob::Unscaled); 0180 m_previewJob->setIgnoreMaximumSize(m_item.isLocalFile() && !m_item.isSlow()); 0181 m_previewJob->setDevicePixelRatio(devicePixelRatioF()); 0182 if (m_previewJob->uiDelegate()) { 0183 KJobWidgets::setWindow(m_previewJob, this); 0184 } 0185 0186 connect(m_previewJob.data(), &KIO::PreviewJob::gotPreview, this, &InformationPanelContent::showPreview); 0187 connect(m_previewJob.data(), &KIO::PreviewJob::failed, this, &InformationPanelContent::showIcon); 0188 } 0189 0190 void InformationPanelContent::refreshPreview() 0191 { 0192 // If there is a preview job, kill it to prevent that we have jobs for 0193 // multiple items running, and thus a race condition (bug 250787). 0194 if (m_previewJob) { 0195 m_previewJob->kill(); 0196 } 0197 0198 m_preview->setCursor(Qt::ArrowCursor); 0199 setNameLabelText(m_item.text()); 0200 if (InformationPanelSettings::previewsShown()) { 0201 const QUrl itemUrl = m_item.url(); 0202 const bool isSearchUrl = itemUrl.scheme().contains(QLatin1String("search")) && m_item.localPath().isEmpty(); 0203 if (isSearchUrl) { 0204 m_preview->show(); 0205 m_phononWidget->hide(); 0206 0207 // in the case of a search-URL the URL is not readable for humans 0208 // (at least not useful to show in the Information Panel) 0209 m_preview->setPixmap(QIcon::fromTheme(QStringLiteral("baloo")).pixmap(m_preview->height(), m_preview->width())); 0210 } else { 0211 refreshPixmapView(); 0212 0213 const QString mimeType = m_item.mimetype(); 0214 const bool isAnimatedImage = m_preview->isAnimatedMimeType(mimeType); 0215 m_isVideo = !isAnimatedImage && mimeType.startsWith(QLatin1String("video/")); 0216 bool usePhonon = m_isVideo || mimeType.startsWith(QLatin1String("audio/")); 0217 0218 if (usePhonon) { 0219 // change the cursor of the preview 0220 m_preview->setCursor(Qt::PointingHandCursor); 0221 m_preview->installEventFilter(m_phononWidget); 0222 m_phononWidget->show(); 0223 0224 // if the video is playing, has been paused or stopped 0225 // we don't need to update the preview/phonon widget states 0226 // unless the previewed file has changed, 0227 // or the setting previewshown has changed 0228 if ((m_phononWidget->state() != Phonon::State::PlayingState && m_phononWidget->state() != Phonon::State::PausedState 0229 && m_phononWidget->state() != Phonon::State::StoppedState) 0230 || m_item.targetUrl() != m_phononWidget->url() || (!m_preview->isVisible() && !m_phononWidget->isVisible())) { 0231 if (InformationPanelSettings::previewsAutoPlay() && m_isVideo) { 0232 // hides the preview now to avoid flickering when the autoplay video starts 0233 m_preview->hide(); 0234 } else { 0235 // the video won't play before the preview is displayed 0236 m_preview->show(); 0237 } 0238 0239 m_phononWidget->setUrl(m_item.targetUrl(), m_isVideo ? PhononWidget::MediaKind::Video : PhononWidget::MediaKind::Audio); 0240 adjustWidgetSizes(parentWidget()->width()); 0241 } 0242 } else { 0243 if (isAnimatedImage) { 0244 m_preview->setAnimatedImageFileName(itemUrl.toLocalFile()); 0245 } 0246 // When we don't need it, hide the phonon widget first to avoid flickering 0247 m_phononWidget->hide(); 0248 m_preview->show(); 0249 m_preview->removeEventFilter(m_phononWidget); 0250 m_phononWidget->clearUrl(); 0251 } 0252 } 0253 } else { 0254 m_preview->stopAnimatedImage(); 0255 m_preview->hide(); 0256 m_phononWidget->hide(); 0257 } 0258 } 0259 0260 void InformationPanelContent::configureShownProperties() 0261 { 0262 m_configureLabel->setVisible(true); 0263 m_configureButtons->setVisible(true); 0264 m_metaDataWidget->setConfigurationMode(Baloo::ConfigurationMode::ReStart); 0265 } 0266 0267 void InformationPanelContent::refreshMetaData() 0268 { 0269 m_metaDataWidget->setDateFormat(static_cast<Baloo::DateFormats>(InformationPanelSettings::dateFormat())); 0270 m_metaDataWidget->show(); 0271 m_metaDataWidget->setItems(KFileItemList() << m_item); 0272 } 0273 0274 void InformationPanelContent::showItems(const KFileItemList &items) 0275 { 0276 // If there is a preview job, kill it to prevent that we have jobs for 0277 // multiple items running, and thus a race condition (bug 250787). 0278 if (m_previewJob) { 0279 m_previewJob->kill(); 0280 } 0281 0282 m_preview->stopAnimatedImage(); 0283 0284 m_preview->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(m_preview->height(), m_preview->width())); 0285 setNameLabelText(i18ncp("@label", "%1 item selected", "%1 items selected", items.count())); 0286 0287 m_metaDataWidget->setItems(items); 0288 0289 m_phononWidget->hide(); 0290 0291 m_item = KFileItem(); 0292 } 0293 0294 bool InformationPanelContent::eventFilter(QObject *obj, QEvent *event) 0295 { 0296 switch (event->type()) { 0297 case QEvent::Resize: { 0298 QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event); 0299 if (obj == m_metaDataArea->viewport()) { 0300 // The size of the meta text area has changed. Adjust the fixed 0301 // width in a way that no horizontal scrollbar needs to be shown. 0302 m_metaDataWidget->setFixedWidth(resizeEvent->size().width()); 0303 } else if (obj == parent()) { 0304 adjustWidgetSizes(resizeEvent->size().width()); 0305 } 0306 break; 0307 } 0308 0309 case QEvent::Polish: 0310 adjustWidgetSizes(parentWidget()->width()); 0311 break; 0312 0313 case QEvent::FontChange: 0314 m_metaDataWidget->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0315 break; 0316 0317 default: 0318 break; 0319 } 0320 0321 return QWidget::eventFilter(obj, event); 0322 } 0323 0324 bool InformationPanelContent::event(QEvent *event) 0325 { 0326 if (event->type() == QEvent::Gesture) { 0327 gestureEvent(static_cast<QGestureEvent *>(event)); 0328 return true; 0329 } 0330 return QWidget::event(event); 0331 } 0332 0333 bool InformationPanelContent::gestureEvent(QGestureEvent *event) 0334 { 0335 if (!underMouse()) { 0336 return false; 0337 } 0338 0339 QTapAndHoldGesture *tap = static_cast<QTapAndHoldGesture *>(event->gesture(Qt::TapAndHoldGesture)); 0340 0341 if (tap) { 0342 if (tap->state() == Qt::GestureFinished) { 0343 Q_EMIT contextMenuRequested(tap->position().toPoint()); 0344 } 0345 event->accept(); 0346 return true; 0347 } 0348 return false; 0349 } 0350 0351 void InformationPanelContent::showIcon(const KFileItem &item) 0352 { 0353 m_outdatedPreviewTimer->stop(); 0354 QPixmap pixmap = QIcon::fromTheme(item.iconName()).pixmap(m_preview->size(), devicePixelRatioF()); 0355 KIconLoader::global()->drawOverlays(item.overlays(), pixmap, KIconLoader::Desktop); 0356 m_preview->setPixmap(pixmap); 0357 } 0358 0359 void InformationPanelContent::showPreview(const KFileItem &item, const QPixmap &pixmap) 0360 { 0361 m_outdatedPreviewTimer->stop(); 0362 0363 QPixmap p = pixmap; 0364 KIconLoader::global()->drawOverlays(item.overlays(), p, KIconLoader::Desktop); 0365 0366 if (m_isVideo) { 0367 // adds a play arrow overlay 0368 0369 auto maxDim = qMax(p.width(), p.height()); 0370 auto arrowSize = qMax(PLAY_ARROW_SIZE, maxDim / 8); 0371 0372 // compute relative pixel positions 0373 const int zeroX = static_cast<int>((p.width() / 2 - arrowSize / 2) / pixmap.devicePixelRatio()); 0374 const int zeroY = static_cast<int>((p.height() / 2 - arrowSize / 2) / pixmap.devicePixelRatio()); 0375 0376 QPolygon arrow; 0377 arrow << QPoint(zeroX, zeroY); 0378 arrow << QPoint(zeroX, zeroY + arrowSize); 0379 arrow << QPoint(zeroX + arrowSize, zeroY + arrowSize / 2); 0380 0381 QPainterPath path; 0382 path.addPolygon(arrow); 0383 0384 QLinearGradient gradient(QPointF(zeroX, zeroY + arrowSize / 2), QPointF(zeroX + arrowSize, zeroY + arrowSize / 2)); 0385 0386 QColor whiteColor = Qt::white; 0387 QColor blackColor = Qt::black; 0388 gradient.setColorAt(0, whiteColor); 0389 gradient.setColorAt(1, blackColor); 0390 0391 QBrush brush(gradient); 0392 0393 QPainter painter(&p); 0394 0395 QPen pen(blackColor, PLAY_ARROW_BORDER_SIZE, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); 0396 painter.setPen(pen); 0397 0398 painter.setRenderHint(QPainter::Antialiasing); 0399 painter.drawPolygon(arrow); 0400 painter.fillPath(path, brush); 0401 } 0402 0403 m_preview->setPixmap(p); 0404 } 0405 0406 void InformationPanelContent::markOutdatedPreview() 0407 { 0408 if (m_item.isDir()) { 0409 // directory preview can be long 0410 // but since we always have icons to display 0411 // use it until the preview is done 0412 showIcon(m_item); 0413 } else { 0414 KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); 0415 QPixmap disabledPixmap = iconEffect->apply(m_preview->pixmap(), KIconLoader::Desktop, KIconLoader::DisabledState); 0416 m_preview->setPixmap(disabledPixmap); 0417 } 0418 } 0419 0420 KFileItemList InformationPanelContent::items() 0421 { 0422 return m_metaDataWidget->items(); 0423 } 0424 0425 void InformationPanelContent::slotHasVideoChanged(bool hasVideo) 0426 { 0427 m_preview->setVisible(InformationPanelSettings::previewsShown() && !hasVideo); 0428 if (m_preview->isVisible() && m_preview->size().width() != m_preview->pixmap().size().width()) { 0429 // in case the information panel has been resized when the preview was not displayed 0430 // we need to refresh its content 0431 refreshPixmapView(); 0432 } 0433 } 0434 0435 void InformationPanelContent::setPreviewAutoPlay(bool autoPlay) 0436 { 0437 m_phononWidget->setAutoPlay(autoPlay); 0438 } 0439 0440 void InformationPanelContent::setNameLabelText(const QString &text) 0441 { 0442 QTextOption textOption; 0443 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); 0444 0445 const QString processedText = Qt::mightBeRichText(text) ? text : KStringHandler::preProcessWrap(text); 0446 0447 QTextLayout textLayout(processedText); 0448 textLayout.setFont(m_nameLabel->font()); 0449 textLayout.setTextOption(textOption); 0450 0451 QString wrappedText; 0452 wrappedText.reserve(processedText.length()); 0453 0454 // wrap the text to fit into the width of m_nameLabel 0455 textLayout.beginLayout(); 0456 QTextLine line = textLayout.createLine(); 0457 while (line.isValid()) { 0458 line.setLineWidth(m_nameLabel->width()); 0459 wrappedText += QStringView(processedText).mid(line.textStart(), line.textLength()); 0460 0461 line = textLayout.createLine(); 0462 if (line.isValid()) { 0463 wrappedText += QChar::LineSeparator; 0464 } 0465 } 0466 textLayout.endLayout(); 0467 0468 m_nameLabel->setText(wrappedText); 0469 } 0470 0471 void InformationPanelContent::adjustWidgetSizes(int width) 0472 { 0473 // If the text inside the name label or the info label cannot 0474 // get wrapped, then the maximum width of the label is increased 0475 // so that the width of the information panel gets increased. 0476 // To prevent this, the maximum width is adjusted to 0477 // the current width of the panel. 0478 const int maxWidth = width - style()->layoutSpacing(QSizePolicy::DefaultType, QSizePolicy::DefaultType, Qt::Horizontal) * 4; 0479 m_nameLabel->setMaximumWidth(maxWidth); 0480 0481 // The metadata widget also contains a text widget which may return 0482 // a large preferred width. 0483 m_metaDataWidget->setMaximumWidth(maxWidth); 0484 0485 // try to increase the preview as large as possible 0486 m_preview->setSizeHint(QSize(maxWidth, maxWidth)); 0487 0488 if (m_phononWidget->isVisible()) { 0489 // assure that the size of the video player is the same as the preview size 0490 m_phononWidget->setVideoSize(QSize(maxWidth, maxWidth)); 0491 } 0492 } 0493 0494 #include "moc_informationpanelcontent.cpp"