From 56c9e973dffc2e5164b64b740cfd693e0f62e49a Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 21 Nov 2023 14:33:48 +0100 Subject: [PATCH] initial slim implementation of media player for qt6, will fix for qt5 and polish and integrate better later --- CMakeLists.txt | 2 +- localization/rssguard_en.ts | 111 ++++++++--- resources/icons.qrc | 16 ++ src/librssguard/CMakeLists.txt | 4 + src/librssguard/gui/dialogs/formmain.cpp | 2 +- src/librssguard/gui/feedmessageviewer.cpp | 2 +- .../notifications/basetoastnotification.cpp | 4 +- .../toastnotificationsmanager.cpp | 2 +- src/librssguard/gui/reusable/mediaplayer.cpp | 188 ++++++++++++++++++ src/librssguard/gui/reusable/mediaplayer.h | 72 +++++++ src/librssguard/gui/reusable/mediaplayer.ui | 95 +++++++++ .../gui/reusable/plaintoolbutton.cpp | 4 +- src/librssguard/gui/tabwidget.cpp | 24 ++- src/librssguard/gui/tabwidget.h | 2 + .../webviewers/webengine/webengineviewer.cpp | 4 + .../standard/gui/formdiscoverfeeds.cpp | 15 +- .../services/standard/gui/formdiscoverfeeds.h | 2 +- .../standard/gui/formdiscoverfeeds.ui | 4 +- .../services/standard/parsers/atomparser.cpp | 4 +- .../services/standard/parsers/atomparser.h | 2 +- .../services/standard/parsers/feedparser.cpp | 6 +- .../services/standard/parsers/feedparser.h | 2 +- .../services/standard/parsers/jsonparser.cpp | 4 +- .../services/standard/parsers/jsonparser.h | 2 +- .../services/standard/parsers/rdfparser.cpp | 4 +- .../services/standard/parsers/rdfparser.h | 2 +- .../services/standard/parsers/rssparser.cpp | 4 +- .../services/standard/parsers/rssparser.h | 2 +- .../standard/parsers/sitemapparser.cpp | 20 +- .../services/standard/parsers/sitemapparser.h | 2 +- .../standardfeedsimportexportmodel.cpp | 6 +- 31 files changed, 545 insertions(+), 68 deletions(-) create mode 100644 src/librssguard/gui/reusable/mediaplayer.cpp create mode 100644 src/librssguard/gui/reusable/mediaplayer.h create mode 100644 src/librssguard/gui/reusable/mediaplayer.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c38a8c16..12bead7d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,7 @@ set(QT_COMPONENTS ) if(NOT OS2) - list(APPEND QT_COMPONENTS Multimedia) + list(APPEND QT_COMPONENTS Multimedia MultimediaWidgets) endif() if(WIN32 AND NOT BUILD_WITH_QT6) diff --git a/localization/rssguard_en.ts b/localization/rssguard_en.ts index 833d5e4f7..c20512f8f 100644 --- a/localization/rssguard_en.ts +++ b/localization/rssguard_en.ts @@ -381,7 +381,7 @@ version by clicking this popup notification. BaseToastNotification - + Close this notification @@ -468,12 +468,12 @@ version by clicking this popup notification. DiscoveredFeedsModel - + Title Title - + Type @@ -1838,7 +1838,7 @@ QtWebEngine cache folder -> "%7" - Discover Sitemaps too (can take some time for bigger websites) + Recursive discovery (can take some time for bigger websites) @@ -1897,12 +1897,12 @@ QtWebEngine cache folder -> "%7" Error: %1 - + URL is valid. - + URL is NOT valid. @@ -4496,6 +4496,59 @@ Login tokens expiration: %2 + + MediaPlayer + + + Form + + + + + No media + + + + + Loading... + + + + + Media loaded + + + + + Media stalled + + + + + Buffering... + + + + + Loaded + + + + + Ended + + + + + Media is invalid + + + + + Unknown + + + MessageBrowser @@ -5719,50 +5772,50 @@ List of supported readers: - - - - + + + + XML is not well-formed, %1 - + not an ATOM feed - + JSON error '%1' - + not a JSON feed - + not an RDF feed - + not a RSS feed - + support for gzipped sitemaps is not enabled - + sitemap indices are not supported - + not a Sitemap @@ -7895,22 +7948,27 @@ Unread news: %2 Feeds - + Displays main menu. Displays main menu. - + Main menu Main menu - + Downloads Downloads - + + Media player + + + + Web browser Web browser @@ -8439,12 +8497,17 @@ Last login on: %4 - + + Open link as audio/video + + + + Open with external tool - + No external tools activated diff --git a/resources/icons.qrc b/resources/icons.qrc index f8a10e675..ec6dfadc8 100644 --- a/resources/icons.qrc +++ b/resources/icons.qrc @@ -12,6 +12,7 @@ ./graphics/Breeze/actions/32/arrow-right.svg ./graphics/Breeze/actions/32/arrow-up.svg ./graphics/Breeze/actions/32/arrow-up-double.svg + ./graphics/Breeze/status/22/audio-volume-muted.svg ./graphics/Breeze/actions/32/call-start.svg ./graphics/Breeze/actions/32/dialog-cancel.svg ./graphics/Breeze/actions/22/dialog-close.svg @@ -77,7 +78,11 @@ ./graphics/Breeze/actions/32/mail-reply-sender.svg ./graphics/Breeze/actions/32/mail-send.svg ./graphics/Breeze/actions/32/mail-sent.svg + ./graphics/Breeze/actions/32/media-playback-pause.svg ./graphics/Breeze/actions/32/media-playback-start.svg + ./graphics/Breeze/actions/32/media-playback-stop.svg + ./graphics/Breeze/actions/22/player-volume.svg + ./graphics/Breeze/actions/22/player-volume-muted.svg ./graphics/Breeze/actions/22/process-stop.svg ./graphics/Breeze/actions/22/system-search.svg ./graphics/Breeze/actions/22/system-upgrade.svg @@ -106,6 +111,7 @@ ./graphics/Breeze Dark/actions/32/arrow-right.svg ./graphics/Breeze Dark/actions/32/arrow-up.svg ./graphics/Breeze Dark/actions/32/arrow-up-double.svg + ./graphics/Breeze Dark/status/22/audio-volume-muted.svg ./graphics/Breeze Dark/actions/32/call-start.svg ./graphics/Breeze Dark/actions/32/dialog-cancel.svg ./graphics/Breeze Dark/actions/22/dialog-close.svg @@ -171,7 +177,11 @@ ./graphics/Breeze Dark/actions/32/mail-reply-sender.svg ./graphics/Breeze Dark/actions/32/mail-send.svg ./graphics/Breeze Dark/actions/32/mail-sent.svg + ./graphics/Breeze Dark/actions/32/media-playback-pause.svg ./graphics/Breeze Dark/actions/32/media-playback-start.svg + ./graphics/Breeze Dark/actions/32/media-playback-stop.svg + ./graphics/Breeze Dark/actions/22/player-volume.svg + ./graphics/Breeze Dark/actions/22/player-volume-muted.svg ./graphics/Breeze Dark/actions/22/process-stop.svg ./graphics/Breeze Dark/actions/22/system-search.svg ./graphics/Breeze Dark/actions/22/system-upgrade.svg @@ -194,6 +204,7 @@ ./graphics/Faenza/categories/64/applications-office.png ./graphics/Faenza/categories/64/applications-science.png ./graphics/Faenza/categories/64/applications-system.png + ./graphics/Faenza/status/64/audio-volume-muted.png ./graphics/Faenza/actions/64/browser-download.png ./graphics/Faenza/actions/64/call-start.png ./graphics/Faenza/status/64/dialog-error.png @@ -256,7 +267,9 @@ ./graphics/Faenza/actions/64/mail-reply-sender.png ./graphics/Faenza/actions/64/mail-send.png ./graphics/Faenza/actions/64/mail-sent.png + ./graphics/Faenza/actions/64/media-playback-pause.png ./graphics/Faenza/actions/64/media-playback-start.png + ./graphics/Faenza/actions/64/media-playback-stop.png ./graphics/Faenza/actions/64/process-stop.png ./graphics/Faenza/actions/64/reload.png ./graphics/Faenza/actions/64/system-search.png @@ -284,6 +297,7 @@ ./graphics/Numix/22/actions/arrow-right.svg ./graphics/Numix/22/actions/arrow-up.svg ./graphics/Numix/22/actions/arrow-up-double.svg + ./graphics/Numix/22/status/audio-volume-muted.svg ./graphics/Numix/22/actions/browser-download.svg ./graphics/Numix/22/actions/call-start.svg ./graphics/Numix/22/actions/dialog-cancel.svg @@ -349,7 +363,9 @@ ./graphics/Numix/22/actions/mail-reply-sender.svg ./graphics/Numix/22/actions/mail-send.svg ./graphics/Numix/22/places/mail-sent.svg + ./graphics/Numix/22/actions/media-playback-pause.svg ./graphics/Numix/22/actions/media-playback-start.svg + ./graphics/Numix/22/actions/media-playback-stop.svg ./graphics/Numix/22/actions/process-stop.svg ./graphics/Numix/22/actions/reload.svg ./graphics/Numix/22/actions/system-search.svg diff --git a/src/librssguard/CMakeLists.txt b/src/librssguard/CMakeLists.txt index 90be4f626..bde335375 100644 --- a/src/librssguard/CMakeLists.txt +++ b/src/librssguard/CMakeLists.txt @@ -133,6 +133,8 @@ set(SOURCES gui/reusable/locationlineedit.h gui/reusable/messagecountspinbox.cpp gui/reusable/messagecountspinbox.h + gui/reusable/mediaplayer.cpp + gui/reusable/mediaplayer.h gui/reusable/networkproxydetails.cpp gui/reusable/networkproxydetails.h gui/reusable/nonclosablemenu.cpp @@ -464,6 +466,7 @@ set(UI_FILES gui/notifications/notificationseditor.ui gui/notifications/singlenotificationeditor.ui gui/notifications/toastnotification.ui + gui/reusable/mediaplayer.ui gui/reusable/networkproxydetails.ui gui/reusable/searchtextwidget.ui gui/richtexteditor/mrichtextedit.ui @@ -769,6 +772,7 @@ endif() if(NOT OS2) target_link_libraries(rssguard PUBLIC Qt${QT_VERSION_MAJOR}::Multimedia + Qt${QT_VERSION_MAJOR}::MultimediaWidgets ) endif() diff --git a/src/librssguard/gui/dialogs/formmain.cpp b/src/librssguard/gui/dialogs/formmain.cpp index 16964ba66..a1e0a9223 100644 --- a/src/librssguard/gui/dialogs/formmain.cpp +++ b/src/librssguard/gui/dialogs/formmain.cpp @@ -673,7 +673,7 @@ void FormMain::loadSize() { setWindowState(windowState() | Qt::WindowState::WindowMaximized); // We process events so that window is really maximized fast. - qApp->processEvents(); + QCoreApplication::processEvents(); } m_ui->m_actionMessagePreviewEnabled diff --git a/src/librssguard/gui/feedmessageviewer.cpp b/src/librssguard/gui/feedmessageviewer.cpp index dfdb49fe1..ce15a8204 100644 --- a/src/librssguard/gui/feedmessageviewer.cpp +++ b/src/librssguard/gui/feedmessageviewer.cpp @@ -244,7 +244,7 @@ void FeedMessageViewer::loadMessageToFeedAndArticleList(Feed* feed, const Messag // TODO: expand properly m_feedsView->setExpanded(idx_map, true); m_feedsView->setCurrentIndex(idx_map); - qApp->processEvents(); + QCoreApplication::processEvents(); auto idx_map_msg = m_messagesView->model()->indexFromMessage(message); auto msg_is_visible = !m_messagesView->isRowHidden(idx_map_msg.row(), idx_map_msg); diff --git a/src/librssguard/gui/notifications/basetoastnotification.cpp b/src/librssguard/gui/notifications/basetoastnotification.cpp index 0466743b2..c15f60b35 100644 --- a/src/librssguard/gui/notifications/basetoastnotification.cpp +++ b/src/librssguard/gui/notifications/basetoastnotification.cpp @@ -15,7 +15,6 @@ using namespace std::chrono_literals; BaseToastNotification::BaseToastNotification(QWidget* parent) : QDialog(parent), m_timerId(-1) { setAttribute(Qt::WidgetAttribute::WA_ShowWithoutActivating); - // setFixedWidth(qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToastNotificationsWidth)).toInt()); setFocusPolicy(Qt::FocusPolicy::NoFocus); setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose, false); @@ -82,7 +81,8 @@ bool BaseToastNotification::eventFilter(QObject* watched, QEvent* event) { if (event->type() == QEvent::Type::MouseButtonPress || event->type() == QEvent::Type::MouseButtonRelease) { if (dynamic_cast(event)->button() == Qt::MouseButton::RightButton) { event->accept(); - QTimer::singleShot(100, this, &BaseToastNotification::close); + QCoreApplication::processEvents(); + QTimer::singleShot(200, this, &BaseToastNotification::close); return true; } } diff --git a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp index 72ff3f999..3feda646e 100644 --- a/src/librssguard/gui/notifications/toastnotificationsmanager.cpp +++ b/src/librssguard/gui/notifications/toastnotificationsmanager.cpp @@ -100,7 +100,7 @@ void ToastNotificationsManager::processNotification(BaseToastNotification* notif // Make sure notification is finally resized. notif->adjustSize(); - qApp->processEvents(); + QCoreApplication::processEvents(); // Move notification, at this point we already need to know its precise size. moveNotificationToCorner(notif, notif_new_pos); diff --git a/src/librssguard/gui/reusable/mediaplayer.cpp b/src/librssguard/gui/reusable/mediaplayer.cpp new file mode 100644 index 000000000..5552908c3 --- /dev/null +++ b/src/librssguard/gui/reusable/mediaplayer.cpp @@ -0,0 +1,188 @@ +// For license of this file, see /LICENSE.md. + +#include "mediaplayer.h" + +#include "miscellaneous/iconfactory.h" + +#if QT_VERSION_MAJOR == 6 +#include +#endif + +MediaPlayer::MediaPlayer(QWidget* parent) + : TabContent(parent), +#if QT_VERSION_MAJOR == 6 + m_audio(new QAudioOutput(this)), +#endif + m_player(new QMediaPlayer(this)), m_muted(false) { + m_ui.setupUi(this); + + m_player->setVideoOutput(m_ui.m_video); + m_player->setAudioOutput(m_audio); + + setupIcons(); + createConnections(); + + onPlaybackStateChanged(QMediaPlayer::PlaybackState::StoppedState); + onMediaStatusChanged(QMediaPlayer::MediaStatus::NoMedia); +} + +MediaPlayer::~MediaPlayer() {} + +WebBrowser* MediaPlayer::webBrowser() const { + return nullptr; +} + +void MediaPlayer::playUrl(const QString& url) { + setVolume(m_ui.m_slidVolume->value()); + + m_player->setSource(url); + m_player->play(); +} + +void MediaPlayer::playPause() { + if (m_player->playbackState() != QMediaPlayer::PlaybackState::PlayingState) { + m_player->play(); + } + else { + m_player->pause(); + } +} + +void MediaPlayer::stop() { + m_player->stop(); +} + +void MediaPlayer::download() { + emit urlDownloadRequested(m_player->source()); +} + +void MediaPlayer::muteUnmute() { + m_ui.m_slidVolume->setEnabled(m_muted); + setVolume(m_muted ? m_ui.m_slidVolume->value() : 0); + + m_muted = !m_muted; +} + +void MediaPlayer::setVolume(int volume) { + m_player->audioOutput()->setVolume(volume / 100.0f); + m_ui.m_btnVolume->setIcon(volume <= 0 ? m_iconMute : m_iconUnmute); +} + +void MediaPlayer::seek(int position) { + m_player->setPosition(position * 1000); +} + +void MediaPlayer::onDurationChanged(qint64 duration) { + m_ui.m_slidProgress->blockSignals(true); + m_ui.m_slidProgress->setMaximum(duration / 1000); + m_ui.m_slidProgress->blockSignals(false); +} + +void MediaPlayer::onErrorOccurred(QMediaPlayer::Error error, const QString& error_string) { + m_ui.m_lblStatus->setText(error_string); +} + +void MediaPlayer::onAudioAvailable(bool available) { + m_ui.m_slidVolume->setEnabled(available); + m_ui.m_btnVolume->setEnabled(available); +} + +void MediaPlayer::onVideoAvailable(bool available) { + Q_UNUSED(available) +} + +void MediaPlayer::onMediaStatusChanged(QMediaPlayer::MediaStatus status) { + m_ui.m_lblStatus->setText(mediaStatusToString(status)); +} + +void MediaPlayer::onPlaybackStateChanged(QMediaPlayer::PlaybackState state) { + switch (state) { + case QMediaPlayer::StoppedState: + m_ui.m_btnPlayPause->setIcon(m_iconPlay); + m_ui.m_btnStop->setEnabled(false); + break; + + case QMediaPlayer::PlayingState: + m_ui.m_btnPlayPause->setIcon(m_iconPause); + m_ui.m_btnStop->setEnabled(true); + break; + + case QMediaPlayer::PausedState: + m_ui.m_btnPlayPause->setIcon(m_iconPlay); + m_ui.m_btnStop->setEnabled(true); + break; + } +} + +void MediaPlayer::onPositionChanged(qint64 position) { + m_ui.m_slidProgress->blockSignals(true); + m_ui.m_slidProgress->setValue(position / 1000); + m_ui.m_slidProgress->blockSignals(false); +} + +void MediaPlayer::onSeekableChanged(bool seekable) { + m_ui.m_slidProgress->setEnabled(seekable); + + if (!seekable) { + onPositionChanged(0); + } +} + +QString MediaPlayer::mediaStatusToString(QMediaPlayer::MediaStatus status) const { + switch (status) { + case QMediaPlayer::NoMedia: + return tr("No media"); + + case QMediaPlayer::LoadingMedia: + return tr("Loading..."); + + case QMediaPlayer::LoadedMedia: + return tr("Media loaded"); + + case QMediaPlayer::StalledMedia: + return tr("Media stalled"); + + case QMediaPlayer::BufferingMedia: + return tr("Buffering..."); + + case QMediaPlayer::BufferedMedia: + return tr("Loaded"); + + case QMediaPlayer::EndOfMedia: + return tr("Ended"); + + case QMediaPlayer::InvalidMedia: + return tr("Media is invalid"); + + default: + return tr("Unknown"); + } +} + +void MediaPlayer::setupIcons() { + m_iconPlay = qApp->icons()->fromTheme(QSL("media-playback-start"), QSL("player_play")); + m_iconPause = qApp->icons()->fromTheme(QSL("media-playback-pause"), QSL("player_pause")); + m_iconMute = qApp->icons()->fromTheme(QSL("player-volume-muted"), QSL("audio-volume-muted")); + m_iconUnmute = qApp->icons()->fromTheme(QSL("player-volume"), QSL("stock_volume")); + + m_ui.m_btnDownload->setIcon(qApp->icons()->fromTheme(QSL("download"), QSL("browser-download"))); + m_ui.m_btnStop->setIcon(qApp->icons()->fromTheme(QSL("media-playback-stop"), QSL("player_stop"))); +} + +void MediaPlayer::createConnections() { + connect(m_player, &QMediaPlayer::durationChanged, this, &MediaPlayer::onDurationChanged); + connect(m_player, &QMediaPlayer::errorOccurred, this, &MediaPlayer::onErrorOccurred); + connect(m_player, &QMediaPlayer::hasAudioChanged, this, &MediaPlayer::onAudioAvailable); + connect(m_player, &QMediaPlayer::hasVideoChanged, this, &MediaPlayer::onVideoAvailable); + connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &MediaPlayer::onMediaStatusChanged); + connect(m_player, &QMediaPlayer::playbackStateChanged, this, &MediaPlayer::onPlaybackStateChanged); + connect(m_player, &QMediaPlayer::positionChanged, this, &MediaPlayer::onPositionChanged); + connect(m_player, &QMediaPlayer::seekableChanged, this, &MediaPlayer::onSeekableChanged); + + connect(m_ui.m_btnPlayPause, &PlainToolButton::clicked, this, &MediaPlayer::playPause); + connect(m_ui.m_btnStop, &PlainToolButton::clicked, this, &MediaPlayer::stop); + connect(m_ui.m_btnDownload, &PlainToolButton::clicked, this, &MediaPlayer::download); + connect(m_ui.m_btnVolume, &PlainToolButton::clicked, this, &MediaPlayer::muteUnmute); + connect(m_ui.m_slidVolume, &QSlider::valueChanged, this, &MediaPlayer::setVolume); + connect(m_ui.m_slidProgress, &QSlider::valueChanged, this, &MediaPlayer::seek); +} diff --git a/src/librssguard/gui/reusable/mediaplayer.h b/src/librssguard/gui/reusable/mediaplayer.h new file mode 100644 index 000000000..c19d531f9 --- /dev/null +++ b/src/librssguard/gui/reusable/mediaplayer.h @@ -0,0 +1,72 @@ +// For license of this file, see /LICENSE.md. + +#ifndef MEDIAPLAYER_H +#define MEDIAPLAYER_H + +#include "gui/tabcontent.h" + +#include "ui_mediaplayer.h" + +#include + +class QAudioOutput; + +class MediaPlayer : public TabContent { + Q_OBJECT + + public: + explicit MediaPlayer(QWidget* parent = nullptr); + virtual ~MediaPlayer(); + + virtual WebBrowser* webBrowser() const; + + public slots: + void playUrl(const QString& url); + + private slots: + void playPause(); + void stop(); + void download(); + void muteUnmute(); + + // NOTE: Volume is from 0 to 100. + void setVolume(int volume); + + // NOTE: Media is seekable in miliseconds, but that is too muc + // for "int" data type, therefore we seek by second. + void seek(int position); + + void onDurationChanged(qint64 duration); + void onErrorOccurred(QMediaPlayer::Error error, const QString& error_string); + void onAudioAvailable(bool available); + void onVideoAvailable(bool available); + void onMediaStatusChanged(QMediaPlayer::MediaStatus status); + void onPlaybackStateChanged(QMediaPlayer::PlaybackState state); + void onPositionChanged(qint64 position); + void onSeekableChanged(bool seekable); + + signals: + void urlDownloadRequested(const QUrl& url); + + private: + QString mediaStatusToString(QMediaPlayer::MediaStatus status) const; + + void setupIcons(); + void createConnections(); + + private: + Ui::MediaPlayer m_ui; + +#if QT_VERSION_MAJOR == 6 + QAudioOutput* m_audio; +#endif + + QMediaPlayer* m_player; + QIcon m_iconPlay; + QIcon m_iconPause; + QIcon m_iconMute; + QIcon m_iconUnmute; + bool m_muted; +}; + +#endif // MEDIAPLAYER_H diff --git a/src/librssguard/gui/reusable/mediaplayer.ui b/src/librssguard/gui/reusable/mediaplayer.ui new file mode 100644 index 000000000..72b2f139d --- /dev/null +++ b/src/librssguard/gui/reusable/mediaplayer.ui @@ -0,0 +1,95 @@ + + + MediaPlayer + + + + 0 + 0 + 588 + 300 + + + + Form + + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + + + + 0 + 0 + + + + 100 + + + 50 + + + Qt::Horizontal + + + 5 + + + + + + + + + + + + + QVideoWidget + QWidget +
qvideowidget.h
+ 1 +
+ + PlainToolButton + QToolButton +
plaintoolbutton.h
+
+
+ + +
diff --git a/src/librssguard/gui/reusable/plaintoolbutton.cpp b/src/librssguard/gui/reusable/plaintoolbutton.cpp index 5b784b834..6dd87ec61 100644 --- a/src/librssguard/gui/reusable/plaintoolbutton.cpp +++ b/src/librssguard/gui/reusable/plaintoolbutton.cpp @@ -22,11 +22,11 @@ void PlainToolButton::paintEvent(QPaintEvent* e) { if (isEnabled()) { if (underMouse() || isChecked()) { - p.setOpacity(0.7); + p.setOpacity(0.8); } } else { - p.setOpacity(0.3); + p.setOpacity(0.2); } icon().paint(&p, rect); diff --git a/src/librssguard/gui/tabwidget.cpp b/src/librssguard/gui/tabwidget.cpp index 5dc5c0c6e..bce5ec166 100644 --- a/src/librssguard/gui/tabwidget.cpp +++ b/src/librssguard/gui/tabwidget.cpp @@ -8,6 +8,7 @@ #include "gui/feedsview.h" #include "gui/messagepreviewer.h" #include "gui/messagesview.h" +#include "gui/reusable/mediaplayer.h" #include "gui/reusable/plaintoolbutton.h" #include "gui/tabbar.h" #include "gui/webbrowser.h" @@ -86,14 +87,13 @@ void TabWidget::checkTabBarVisibility() { if (should_be_visible) { setCornerWidget(m_btnMainMenu, Qt::Corner::TopLeftCorner); - m_btnMainMenu->setVisible(true); } else { setCornerWidget(nullptr, Qt::Corner::TopLeftCorner); setCornerWidget(nullptr, Qt::Corner::TopRightCorner); - m_btnMainMenu->setVisible(false); } + m_btnMainMenu->setVisible(should_be_visible); tabBar()->setVisible(should_be_visible); } @@ -224,6 +224,26 @@ int TabWidget::addEmptyBrowser() { return addBrowser(false, true); } +int TabWidget::addMediaPlayer(const QString& url, bool make_active) { + auto* player = new MediaPlayer(this); + + int index = addTab(player, + qApp->icons()->fromTheme(QSL("player_play"), QSL("media-playback-start")), + tr("Media player"), + TabBar::TabType::Closable); + + if (make_active) { + setCurrentIndex(index); + player->setFocus(Qt::FocusReason::OtherFocusReason); + } + + QTimer::singleShot(500, player, [player, url]() { + player->playUrl(url); + }); + + return index; +} + int TabWidget::addLinkedBrowser(const QUrl& initial_url) { return addBrowser(false, false, initial_url); } diff --git a/src/librssguard/gui/tabwidget.h b/src/librssguard/gui/tabwidget.h index 2ba3775fb..9196dc5f6 100644 --- a/src/librssguard/gui/tabwidget.h +++ b/src/librssguard/gui/tabwidget.h @@ -78,6 +78,8 @@ class TabWidget : public QTabWidget { // Adds new WebBrowser tab to global TabWidget. int addEmptyBrowser(); + int addMediaPlayer(const QString& url, bool make_active); + // Adds new WebBrowser with link. This is used when user // selects to "Open link in new tab.". int addLinkedBrowser(const QUrl& initial_url = QUrl()); diff --git a/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp b/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp index 61657f1d4..866898ef0 100644 --- a/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp +++ b/src/librssguard/gui/webviewers/webengine/webengineviewer.cpp @@ -100,6 +100,10 @@ void WebEngineViewer::contextMenuEvent(QContextMenuEvent* event) { }); } }); + + menu->addAction(qApp->icons()->fromTheme(QSL("document-open")), tr("Open link as audio/video"), [link_url]() { + qApp->mainForm()->tabWidget()->addMediaPlayer(link_url, true); + }); } if (menu_data.mediaUrl().isValid() || menu_data.linkUrl().isValid()) { diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp index 79ece916b..0945975f1 100644 --- a/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.cpp @@ -141,8 +141,10 @@ FormDiscoverFeeds::~FormDiscoverFeeds() { m_discoveredModel->setRootItem(nullptr); } -QList FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser* parser, const QString& url) { - auto feeds = parser->discoverFeeds(m_serviceRoot, url); +QList FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser* parser, + const QString& url, + bool greedy) { + auto feeds = parser->discoverFeeds(m_serviceRoot, url, greedy); QPixmap icon; int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); @@ -158,15 +160,10 @@ QList FormDiscoverFeeds::discoverFeedsWithParser(const FeedParser void FormDiscoverFeeds::discoverFeeds() { QString url = m_ui.m_txtUrl->lineEdit()->text(); - bool sitemap_discover = m_ui.m_cbDiscoverSitemaps->isChecked(); + bool greedy_discover = m_ui.m_cbDiscoverRecursive->isChecked(); std::function(const FeedParser*)> func = [=](const FeedParser* parser) -> QList { - if (!sitemap_discover && dynamic_cast(parser) != nullptr) { - return {}; - } - else { - return discoverFeedsWithParser(parser, url); - } + return discoverFeedsWithParser(parser, url, greedy_discover); }; std::function(QList&, const QList&)> reducer = diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.h b/src/librssguard/services/standard/gui/formdiscoverfeeds.h index 249cf7e34..58020bf58 100644 --- a/src/librssguard/services/standard/gui/formdiscoverfeeds.h +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.h @@ -57,7 +57,7 @@ class FormDiscoverFeeds : public QDialog { StandardFeed* selectedFeed() const; RootItem* targetParent() const; - QList discoverFeedsWithParser(const FeedParser* parser, const QString& url); + QList discoverFeedsWithParser(const FeedParser* parser, const QString& url, bool greedy); void userWantsAdvanced(); void loadDiscoveredFeeds(const QList& feeds); diff --git a/src/librssguard/services/standard/gui/formdiscoverfeeds.ui b/src/librssguard/services/standard/gui/formdiscoverfeeds.ui index a64735027..571726ab1 100644 --- a/src/librssguard/services/standard/gui/formdiscoverfeeds.ui +++ b/src/librssguard/services/standard/gui/formdiscoverfeeds.ui @@ -46,9 +46,9 @@ - + - Discover Sitemaps too (can take some time for bigger websites) + Recursive discovery (can take some time for bigger websites) diff --git a/src/librssguard/services/standard/parsers/atomparser.cpp b/src/librssguard/services/standard/parsers/atomparser.cpp index f2e5a459a..4fd984c55 100644 --- a/src/librssguard/services/standard/parsers/atomparser.cpp +++ b/src/librssguard/services/standard/parsers/atomparser.cpp @@ -25,7 +25,9 @@ AtomParser::AtomParser(const QString& data) : FeedParser(data) { AtomParser::~AtomParser() {} -QList AtomParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList AtomParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + Q_UNUSED(greedy) + QString my_url = url.toString(); QList feeds; diff --git a/src/librssguard/services/standard/parsers/atomparser.h b/src/librssguard/services/standard/parsers/atomparser.h index 2082d35a6..d5365cdf4 100644 --- a/src/librssguard/services/standard/parsers/atomparser.h +++ b/src/librssguard/services/standard/parsers/atomparser.h @@ -15,7 +15,7 @@ class AtomParser : public FeedParser { explicit AtomParser(const QString& data); virtual ~AtomParser(); - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/feedparser.cpp b/src/librssguard/services/standard/parsers/feedparser.cpp index 3b108dbd5..829d3178e 100644 --- a/src/librssguard/services/standard/parsers/feedparser.cpp +++ b/src/librssguard/services/standard/parsers/feedparser.cpp @@ -41,7 +41,11 @@ FeedParser::FeedParser(QString data, bool is_xml) FeedParser::~FeedParser() {} -QList FeedParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList FeedParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + Q_UNUSED(root) + Q_UNUSED(url) + Q_UNUSED(greedy) + return {}; } diff --git a/src/librssguard/services/standard/parsers/feedparser.h b/src/librssguard/services/standard/parsers/feedparser.h index fe693a154..56b5475dd 100644 --- a/src/librssguard/services/standard/parsers/feedparser.h +++ b/src/librssguard/services/standard/parsers/feedparser.h @@ -20,7 +20,7 @@ class FeedParser { virtual ~FeedParser(); // Returns list of absolute URLs of discovered feeds from provided base URL. - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; // Guesses feed. virtual QPair> guessFeed(const QByteArray& content, diff --git a/src/librssguard/services/standard/parsers/jsonparser.cpp b/src/librssguard/services/standard/parsers/jsonparser.cpp index 8845e76ed..955c68b19 100644 --- a/src/librssguard/services/standard/parsers/jsonparser.cpp +++ b/src/librssguard/services/standard/parsers/jsonparser.cpp @@ -19,7 +19,9 @@ JsonParser::JsonParser(const QString& data) : FeedParser(data, false) {} JsonParser::~JsonParser() {} -QList JsonParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList JsonParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + Q_UNUSED(greedy) + QString my_url = url.toString(); QList feeds; diff --git a/src/librssguard/services/standard/parsers/jsonparser.h b/src/librssguard/services/standard/parsers/jsonparser.h index 5f1c1f95a..31c46b4da 100644 --- a/src/librssguard/services/standard/parsers/jsonparser.h +++ b/src/librssguard/services/standard/parsers/jsonparser.h @@ -12,7 +12,7 @@ class JsonParser : public FeedParser { explicit JsonParser(const QString& data); virtual ~JsonParser(); - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/rdfparser.cpp b/src/librssguard/services/standard/parsers/rdfparser.cpp index 87358e02c..71c9b2e29 100644 --- a/src/librssguard/services/standard/parsers/rdfparser.cpp +++ b/src/librssguard/services/standard/parsers/rdfparser.cpp @@ -18,7 +18,9 @@ RdfParser::RdfParser(const QString& data) RdfParser::~RdfParser() {} -QList RdfParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList RdfParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + Q_UNUSED(greedy) + QString my_url = url.toString(); QList feeds; diff --git a/src/librssguard/services/standard/parsers/rdfparser.h b/src/librssguard/services/standard/parsers/rdfparser.h index d399624b4..4323bdf1e 100644 --- a/src/librssguard/services/standard/parsers/rdfparser.h +++ b/src/librssguard/services/standard/parsers/rdfparser.h @@ -14,7 +14,7 @@ class RdfParser : public FeedParser { explicit RdfParser(const QString& data); virtual ~RdfParser(); - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/rssparser.cpp b/src/librssguard/services/standard/parsers/rssparser.cpp index 1e8f703c7..f31dd8c61 100644 --- a/src/librssguard/services/standard/parsers/rssparser.cpp +++ b/src/librssguard/services/standard/parsers/rssparser.cpp @@ -18,7 +18,9 @@ RssParser::RssParser(const QString& data) : FeedParser(data) {} RssParser::~RssParser() {} -QList RssParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList RssParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { + Q_UNUSED(greedy) + QString my_url = url.toString(); QList feeds; diff --git a/src/librssguard/services/standard/parsers/rssparser.h b/src/librssguard/services/standard/parsers/rssparser.h index b1e9e4846..f7f69e32c 100644 --- a/src/librssguard/services/standard/parsers/rssparser.h +++ b/src/librssguard/services/standard/parsers/rssparser.h @@ -14,7 +14,7 @@ class RssParser : public FeedParser { explicit RssParser(const QString& data); virtual ~RssParser(); - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/parsers/sitemapparser.cpp b/src/librssguard/services/standard/parsers/sitemapparser.cpp index 6b136752e..25db01f00 100644 --- a/src/librssguard/services/standard/parsers/sitemapparser.cpp +++ b/src/librssguard/services/standard/parsers/sitemapparser.cpp @@ -21,20 +21,24 @@ SitemapParser::SitemapParser(const QString& data) : FeedParser(data) {} SitemapParser::~SitemapParser() {} -QList SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& url) const { +QList SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const { QHash feeds; QStringList to_process_sitemaps; int sitemap_index_limit = 2; int timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); - // 1. Process "URL/robots.txt" file. - // 2. Process "URLHOST/robots.txt" file. - // 3. Direct URL test. If sitemap index, process its children. + // 1. Direct URL test. If sitemap index, process its children. If found, stop if non-recursive + // discovery is chosen. + // 2. Process "URL/robots.txt" file. + // 3. Process "URLHOST/robots.txt" file. // 4. Test "URL/sitemap.xml" endpoint. // 5. Test "URL/sitemap.xml.gz" endpoint. // 1. + to_process_sitemaps.append(url.toString()); + // 2. + // 3. QStringList to_process_robots = { url.toString(QUrl::UrlFormattingOption::StripTrailingSlash).replace(QRegularExpression(QSL("\\/$")), QString()) + QSL("/robots.txt"), @@ -70,9 +74,6 @@ QList SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& } } - // 3. - to_process_sitemaps.append(url.toString()); - // 4. to_process_sitemaps.append(url.toString(QUrl::UrlFormattingOption::StripTrailingSlash) .replace(QRegularExpression(QSL("\\/$")), QString()) + @@ -107,13 +108,16 @@ QList SitemapParser::discoverFeeds(ServiceRoot* root, const QUrl& if (res.m_networkError == QNetworkReply::NetworkError::NoError) { try { - // 1. auto guessed_feed = guessFeed(data, res.m_contentType); guessed_feed.first->setSource(my_url); guessed_feed.first->setTitle(my_url); feeds.insert(my_url, guessed_feed.first); + + if (!greedy) { + break; + } } catch (const FeedRecognizedButFailedException& ex) { // This is index. diff --git a/src/librssguard/services/standard/parsers/sitemapparser.h b/src/librssguard/services/standard/parsers/sitemapparser.h index d16438706..74dd40b38 100644 --- a/src/librssguard/services/standard/parsers/sitemapparser.h +++ b/src/librssguard/services/standard/parsers/sitemapparser.h @@ -12,7 +12,7 @@ class SitemapParser : public FeedParser { explicit SitemapParser(const QString& data); virtual ~SitemapParser(); - virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url) const; + virtual QList discoverFeeds(ServiceRoot* root, const QUrl& url, bool greedy) const; virtual QPair> guessFeed(const QByteArray& content, const QString& content_type) const; diff --git a/src/librssguard/services/standard/standardfeedsimportexportmodel.cpp b/src/librssguard/services/standard/standardfeedsimportexportmodel.cpp index ee359b695..aa34e4b5d 100644 --- a/src/librssguard/services/standard/standardfeedsimportexportmodel.cpp +++ b/src/librssguard/services/standard/standardfeedsimportexportmodel.cpp @@ -49,7 +49,7 @@ FeedsImportExportModel::~FeedsImportExportModel() { if (m_watcherLookup.isRunning()) { m_watcherLookup.cancel(); m_watcherLookup.waitForFinished(); - qApp->processEvents(); + QCoreApplication::processEvents(); } if (sourceModel() != nullptr && sourceModel()->rootItem() != nullptr && m_mode == Mode::Import) { @@ -433,7 +433,7 @@ void FeedsImportExportModel::importAsOPML20(const QByteArray& data, if (!fetch_metadata_online) { m_watcherLookup.waitForFinished(); - qApp->processEvents(); + QCoreApplication::processEvents(); } } @@ -502,7 +502,7 @@ void FeedsImportExportModel::importAsTxtURLPerLine(const QByteArray& data, if (!fetch_metadata_online) { m_watcherLookup.waitForFinished(); - qApp->processEvents(); + QCoreApplication::processEvents(); } }