Display podcast episode information. (#6203)

* Display podcast episode information.
Add an EpisodeInfoWidget with title, author, duration, date, and description fields. Include this in the PodcastInfoDialog. If exactly one episode is selected, then show both the podcast and episode widgets and display the episode's URL. Otherwise, hide the episode widget and follow the existing behavior. Note that the desription field for the EpisodeInfoWidget uses the QLabel and does not currently download embedded images.
Add an always_show_hours option to the PrettyTime methods to include hours in formatting even if the field is zero. This is less ambiguious in some cases where duration is displayed.

* Apply patch from automated formatter test.
This commit is contained in:
Jim Broadus 2018-11-17 05:29:16 -08:00 committed by John Maguire
parent d8bab5a49f
commit 5a1a5a9d95
10 changed files with 309 additions and 18 deletions

View File

@ -263,6 +263,7 @@ set(SOURCES
internet/podcasts/addpodcastbyurl.cpp
internet/podcasts/addpodcastdialog.cpp
internet/podcasts/addpodcastpage.cpp
internet/podcasts/episodeinfowidget.cpp
internet/podcasts/fixedopmlpage.cpp
internet/podcasts/gpoddersearchpage.cpp
internet/podcasts/gpoddersync.cpp
@ -560,6 +561,7 @@ set(HEADERS
internet/podcasts/addpodcastbyurl.h
internet/podcasts/addpodcastdialog.h
internet/podcasts/addpodcastpage.h
internet/podcasts/episodeinfowidget.h
internet/podcasts/fixedopmlpage.h
internet/podcasts/gpoddersearchpage.h
internet/podcasts/gpoddersync.h
@ -715,6 +717,7 @@ set(UI
internet/podcasts/addpodcastbyurl.ui
internet/podcasts/addpodcastdialog.ui
internet/podcasts/episodeinfowidget.ui
internet/podcasts/gpoddersearchpage.ui
internet/podcasts/itunessearchpage.ui
internet/podcasts/podcastinfodialog.ui

View File

@ -89,7 +89,7 @@ QString PrettyTimeDelta(int seconds) {
return (seconds >= 0 ? "+" : "-") + PrettyTime(seconds);
}
QString PrettyTime(int seconds) {
QString PrettyTime(int seconds, bool always_show_hours) {
// last.fm sometimes gets the track length wrong, so you end up with
// negative times.
seconds = qAbs(seconds);
@ -99,7 +99,7 @@ QString PrettyTime(int seconds) {
seconds %= 60;
QString ret;
if (hours)
if (hours || always_show_hours)
ret.sprintf("%d:%02d:%02d", hours, minutes,
seconds); // NOLINT(runtime/printf)
else
@ -108,8 +108,8 @@ QString PrettyTime(int seconds) {
return ret;
}
QString PrettyTimeNanosec(qint64 nanoseconds) {
return PrettyTime(nanoseconds / kNsecPerSec);
QString PrettyTimeNanosec(qint64 nanoseconds, bool always_show_hours) {
return PrettyTime(nanoseconds / kNsecPerSec, always_show_hours);
}
QString WordyTime(quint64 seconds) {

View File

@ -39,9 +39,9 @@ class QXmlStreamReader;
struct QMetaObject;
namespace Utilities {
QString PrettyTime(int seconds);
QString PrettyTime(int seconds, bool always_show_hours = false);
QString PrettyTimeDelta(int seconds);
QString PrettyTimeNanosec(qint64 nanoseconds);
QString PrettyTimeNanosec(qint64 nanoseconds, bool always_show_hours = false);
QString PrettySize(quint64 bytes);
QString PrettySize(const QSize& size);
QString WordyTime(quint64 seconds);

View File

@ -0,0 +1,40 @@
/* This file is part of Clementine.
Copyright 2018, Jim Broadus <jbroadus@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/utilities.h"
#include "episodeinfowidget.h"
#include "ui_episodeinfowidget.h"
#include <QTime>
EpisodeInfoWidget::EpisodeInfoWidget(QWidget* parent)
: QWidget(parent), ui_(new Ui_EpisodeInfoWidget), app_(nullptr) {
ui_->setupUi(this);
}
EpisodeInfoWidget::~EpisodeInfoWidget() { delete ui_; }
void EpisodeInfoWidget::SetApplication(Application* app) { app_ = app; }
void EpisodeInfoWidget::SetEpisode(const PodcastEpisode& episode) {
episode_ = episode;
ui_->title->setText(episode.title());
ui_->description->setText(episode.description());
ui_->author->setText(episode.author());
ui_->date->setText(episode.publication_date().toString("d MMMM yyyy"));
ui_->duration->setText(Utilities::PrettyTime(episode.duration_secs(), true));
}

View File

@ -0,0 +1,46 @@
/* This file is part of Clementine.
Copyright 2018, Jim Broadus <jbroadus@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INTERNET_PODCASTS_EPISODEINFOWIDGET_H_
#define INTERNET_PODCASTS_EPISODEINFOWIDGET_H_
#include "podcastepisode.h"
#include <QFrame>
class Application;
class Ui_EpisodeInfoWidget;
class EpisodeInfoWidget : public QWidget {
Q_OBJECT
public:
explicit EpisodeInfoWidget(QWidget* parent = nullptr);
~EpisodeInfoWidget();
void SetApplication(Application* app);
void SetEpisode(const PodcastEpisode& episode);
private:
Ui_EpisodeInfoWidget* ui_;
Application* app_;
PodcastEpisode episode_;
};
#endif // INTERNET_PODCASTS_EPISODEINFOWIDGET_H_

View File

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EpisodeInfoWidget</class>
<widget class="QWidget" name="EpisodeInfoWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>551</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="styleSheet">
<string notr="true">#title {
font-weight: bold;
}
#description {
font-size: smaller;
}
QLineEdit {
background: transparent;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinAndMaxSize</enum>
</property>
<item>
<widget class="QLabel" name="title">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="description">
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinAndMaxSize</enum>
</property>
<item row="0" column="0">
<widget class="QLabel" name="author_label">
<property name="text">
<string>Author</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="date">
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="author">
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="date_label">
<property name="text">
<string>Date</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="duration_label">
<property name="text">
<string>Duration</string>
</property>
<property name="field_label" stdset="0">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="duration">
<property name="frame">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -24,7 +24,8 @@ PodcastInfoDialog::PodcastInfoDialog(Application* app, QWidget* parent)
app_(app),
ui_(new Ui_PodcastInfoDialog) {
ui_->setupUi(this);
ui_->details->SetApplication(app);
ui_->podcast_details->SetApplication(app);
ui_->episode_details->SetApplication(app);
}
PodcastInfoDialog::~PodcastInfoDialog() {
@ -32,8 +33,19 @@ PodcastInfoDialog::~PodcastInfoDialog() {
}
void PodcastInfoDialog::ShowPodcast(const Podcast& podcast) {
show();
ui_->episode_info_scroll_area->hide();
ui_->podcast_url->setText(podcast.url().toString());
ui_->podcast_url->setReadOnly(true);
ui_->details->SetPodcast(podcast);
ui_->podcast_details->SetPodcast(podcast);
show();
}
void PodcastInfoDialog::ShowEpisode(const PodcastEpisode& episode,
const Podcast& podcast) {
ui_->episode_info_scroll_area->show();
ui_->podcast_url->setText(episode.url().toString());
ui_->podcast_url->setReadOnly(true);
ui_->podcast_details->SetPodcast(podcast);
ui_->episode_details->SetEpisode(episode);
show();
}

View File

@ -22,6 +22,7 @@
class Application;
class Podcast;
class PodcastEpisode;
class Ui_PodcastInfoDialog;
class PodcastInfoDialog : public QDialog {
@ -32,6 +33,7 @@ class PodcastInfoDialog : public QDialog {
~PodcastInfoDialog();
void ShowPodcast(const Podcast& podcast);
void ShowEpisode(const PodcastEpisode& episode, const Podcast& podcast);
private:
Application* app_;

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>493</width>
<height>395</height>
<height>415</height>
</rect>
</property>
<property name="windowTitle">
@ -18,7 +18,36 @@
<widget class="QLineEdit" name="podcast_url"/>
</item>
<item>
<widget class="QScrollArea" name="details_scroll_area">
<widget class="QScrollArea" name="episode_info_scroll_area">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>250</width>
<height>100</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="EpisodeInfoWidget" name="episode_details">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>163</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QScrollArea" name="podcast_info_scroll_area">
<property name="minimumSize">
<size>
<width>250</width>
@ -37,13 +66,13 @@
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="PodcastInfoWidget" name="details">
<widget class="PodcastInfoWidget" name="podcast_details">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>473</width>
<height>313</height>
<height>162</height>
</rect>
</property>
</widget>
@ -68,6 +97,12 @@
<header>internet/podcasts/podcastinfowidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>EpisodeInfoWidget</class>
<extends>QWidget</extends>
<header location="global">internet/podcasts/episodeinfowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>

View File

@ -528,8 +528,15 @@ void PodcastService::ShowContextMenu(const QPoint& global_pos) {
}
if (selected_podcasts_.count() == 1) {
info_selected_action_->setEnabled(true);
if (selected_episodes_.count() == 1) {
info_selected_action_->setText(tr("Episode information"));
info_selected_action_->setEnabled(true);
} else {
info_selected_action_->setText(tr("Podcast information"));
info_selected_action_->setEnabled(true);
}
} else {
info_selected_action_->setText(tr("Podcast information"));
info_selected_action_->setEnabled(false);
}
@ -699,10 +706,19 @@ void PodcastService::DownloadSelectedEpisode() {
}
void PodcastService::PodcastInfo() {
if (selected_podcasts_.count() > 0) {
const Podcast podcast =
selected_podcasts_[0].data(Role_Podcast).value<Podcast>();
podcast_info_dialog_.reset(new PodcastInfoDialog(app_));
if (selected_podcasts_.isEmpty()) {
// Should never happen.
return;
}
const Podcast podcast =
selected_podcasts_[0].data(Role_Podcast).value<Podcast>();
podcast_info_dialog_.reset(new PodcastInfoDialog(app_));
if (selected_episodes_.count() == 1) {
const PodcastEpisode episode =
selected_episodes_[0].data(Role_Episode).value<PodcastEpisode>();
podcast_info_dialog_->ShowEpisode(episode, podcast);
} else {
podcast_info_dialog_->ShowPodcast(podcast);
}
}