/* * Cantata * * Copyright (c) 2011-2022 Craig Drummond * * ---- * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "podcastwidget.h" #include "podcastsearchdialog.h" #include "podcastsettingsdialog.h" #include "widgets/itemview.h" #include "widgets/toolbutton.h" #include "widgets/icons.h" #include "widgets/menubutton.h" #include "support/action.h" #include "support/messagebox.h" #include "support/configuration.h" #include "support/monoicon.h" #include "support/utils.h" #include #include PodcastWidget::PodcastWidget(PodcastService *s, QWidget *p) : SinglePageWidget(p) , srv(s) , proxy(this) { QIcon newIcon = MonoIcon::icon(FontAwesome::asterisk, Utils::monoIconColor()); subscribeAction = new Action(Icons::self()->addNewItemIcon, tr("Add Subscription"), this); unSubscribeAction = new Action(Icons::self()->removeIcon, tr("Remove Subscription"), this); downloadAction = new Action(Icons::self()->downloadIcon, tr("Download Episodes"), this); deleteAction = new Action(MonoIcon::icon(FontAwesome::trash, MonoIcon::constRed, MonoIcon::constRed), tr("Delete Downloaded Episodes"), this); cancelDownloadAction = new Action(Icons::self()->cancelIcon, tr("Cancel Download"), this); markAsNewAction = new Action(newIcon, tr("Mark Episodes As New"), this); markAsListenedAction = new Action(tr("Mark Episodes As Listened"), this); //unplayedOnlyAction = new Action(newIcon, tr("Show Unplayed Only"), this); //unplayedOnlyAction->setCheckable(true); exportAction = new Action(tr("Export Current Subscriptions"), this); proxy.setSourceModel(srv); view->setModel(&proxy); view->alwaysShowHeader(); connect(view, SIGNAL(headerClicked(int)), SLOT(headerClicked(int))); connect(subscribeAction, SIGNAL(triggered()), this, SLOT(subscribe())); connect(unSubscribeAction, SIGNAL(triggered()), this, SLOT(unSubscribe())); connect(downloadAction, SIGNAL(triggered()), this, SLOT(download())); connect(deleteAction, SIGNAL(triggered()), this, SLOT(deleteDownload())); connect(cancelDownloadAction, SIGNAL(triggered()), this, SLOT(cancelDownload())); connect(markAsNewAction, SIGNAL(triggered()), this, SLOT(markAsNew())); connect(markAsListenedAction, SIGNAL(triggered()), this, SLOT(markAsListened())); //connect(unplayedOnlyAction, SIGNAL(toggled(bool)), this, SLOT(showUnplayedOnly(bool))); connect(exportAction, SIGNAL(triggered()), SLOT(exportSubscriptions())); connect(srv->refreshAct(), SIGNAL(triggered()), this, SLOT(refreshPodcast())); view->setMode(ItemView::Mode_DetailedTree); Configuration config(metaObject()->className()); view->load(config); MenuButton *menu=new MenuButton(this); ToolButton *addSub=new ToolButton(this); //ToolButton *unplayedOnlyBtn=new ToolButton(this); addSub->setDefaultAction(subscribeAction); //unplayedOnlyBtn->setDefaultAction(unplayedOnlyAction); menu->addAction(createViewMenu(QList() << ItemView::Mode_BasicTree << ItemView::Mode_SimpleTree << ItemView::Mode_DetailedTree << ItemView::Mode_List)); Action *configureAction=new Action(Icons::self()->configureIcon, tr("Configure"), this); connect(configureAction, SIGNAL(triggered()), SLOT(configure())); menu->addAction(configureAction); menu->addAction(exportAction); init(ReplacePlayQueue|AppendToPlayQueue|Refresh, QList() << menu/* << unplayedOnlyBtn*/, QList() << addSub); view->addAction(subscribeAction); view->addAction(unSubscribeAction); view->addSeparator(); view->addAction(downloadAction); view->addAction(deleteAction); view->addAction(cancelDownloadAction); view->addSeparator(); view->addAction(markAsNewAction); view->addAction(markAsListenedAction); view->setInfoText(tr("Use the + icon (below) to add podcast subscriptions.")); } PodcastWidget::~PodcastWidget() { Configuration config(metaObject()->className()); view->save(config); } QStringList PodcastWidget::selectedFiles(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QStringList(); } return srv->filenames(proxy.mapToSource(selected), allowPlaylists); } QList PodcastWidget::selectedSongs(bool allowPlaylists) const { QModelIndexList selected = view->selectedIndexes(); if (selected.isEmpty()) { return QList(); } return srv->songs(proxy.mapToSource(selected), allowPlaylists); } void PodcastWidget::headerClicked(int level) { if (0==level) { emit close(); } } void PodcastWidget::subscribe() { if (0==PodcastSearchDialog::instanceCount()) { PodcastSearchDialog *dlg=new PodcastSearchDialog(srv, this); dlg->show(); } } void PodcastWidget::unSubscribe() { const QModelIndexList selected = view->selectedIndexes(false); // Dont need sorted selection here... if (1!=selected.size()) { return; } PodcastService::Item *item=static_cast(proxy.mapToSource(selected.first()).internalPointer()); if (!item->isPodcast()) { return; } if (MessageBox::No==MessageBox::warningYesNo(this, tr("Unsubscribe from '%1'?").arg(item->name))) { return; } srv->unSubscribe(static_cast(item)); } enum GetEp { GetEp_Downloaded = 0x01, GetEp_NotDownloaded = 0x02, GetEp_Listened = 0x04, GetEp_NotListened = 0x08 }; static bool useEpisode(const PodcastService::Episode *episode, int get) { return ! ((get&GetEp_Downloaded && episode->localFile.isEmpty()) || (get&GetEp_NotDownloaded && !episode->localFile.isEmpty()) || (get&GetEp_Listened && !episode->played) || (get&GetEp_NotListened && episode->played) ); } static QMap > getEpisodes(PodcastService::Proxy &proxy, const QModelIndexList &selected, int get) { QMap > urls; for (const QModelIndex &idx: selected) { PodcastService::Item *item=static_cast(proxy.mapToSource(idx).internalPointer()); if (item->isPodcast()) { for (PodcastService::Episode *episode: static_cast(item)->episodes) { if (useEpisode(episode, get)) { urls[static_cast(item)].insert(episode); } } } else { PodcastService::Episode *episode=static_cast(item); if (useEpisode(episode, get)) { urls[episode->parent].insert(episode); } } } return urls; } void PodcastWidget::download() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(true), GetEp_NotDownloaded/*|(unplayedOnlyAction->isChecked() ? GetEp_NotListened : 0)*/); if (!urls.isEmpty()) { QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->downloadPodcasts(it.key(), it.value().values()); } } } void PodcastWidget::cancelDownload() { if (srv->isDownloading()) { srv->cancelAll(); } } void PodcastWidget::deleteDownload() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_Downloaded); if (!urls.isEmpty()) { if (MessageBox::No==MessageBox::questionYesNo(this, tr("Do you wish to the delete downloaded files of the selected podcast episodes?"))) { return; } QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->deleteDownloadedPodcasts(it.key(), it.value().values()); } } } void PodcastWidget::markAsNew() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_Listened); if (!urls.isEmpty()) { QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->setPodcastsAsListened(it.key(), it.value().values(), false); } } } void PodcastWidget::markAsListened() { QMap > urls=getEpisodes(proxy, view->selectedIndexes(false), GetEp_NotListened); if (!urls.isEmpty()) { QMap >::ConstIterator it(urls.constBegin()); QMap >::ConstIterator end(urls.constEnd()); for (; it!=end; ++it) { srv->setPodcastsAsListened(it.key(), it.value().values(), true); } } } /* void PodcastWidget::showUnplayedOnly(bool on) { view->goToTop(); proxy.showUnplayedOnly(on); } */ void PodcastWidget::doSearch() { QString text=view->searchText().trimmed(); proxy.update(text); if (proxy.enabled() && !text.isEmpty()) { view->expandAll(); } } void PodcastWidget::refreshPodcast() { doRefresh(true); } void PodcastWidget::refresh() { doRefresh(false); } void PodcastWidget::doRefresh(bool singleOnly) { QModelIndexList sel=view->selectedIndexes(false); QModelIndexList selected; for (const QModelIndex &i: sel) { if (!i.parent().isValid()) { selected+=proxy.mapToSource(i); } } if (singleOnly) { if (1==selected.count()) { srv->refresh(selected); } return; } if (selected.isEmpty() || selected.count()==srv->podcastCount()) { srv->refreshAll(); return; } switch (MessageBox::questionYesNoCancel(this, tr("Refresh all subscriptions, or only those selected?"), tr("Refresh"), GuiItem(tr("Refresh All")), GuiItem(tr("Refresh Selected")))) { case MessageBox::Yes: srv->refreshAll(); break; case MessageBox::No: srv->refresh(selected); break; default: break; } } void PodcastWidget::configure() { srv->configure(this); } void PodcastWidget::exportSubscriptions() { if (0==srv->podcastCount()) { return; } QString filename = QFileDialog::getSaveFileName(this, tr("Export Podcast Subscriptions"), QDir::homePath(), QString(".opml")); if (filename.isEmpty()) { return; } if (!srv->exportSubscriptions(filename)) { MessageBox::error(this, tr("Export failed!")); } } void PodcastWidget::controlActions() { SinglePageWidget::controlActions(); QModelIndexList selected=view->selectedIndexes(false); // Dont need sorted selection here... downloadAction->setEnabled(!selected.isEmpty()); unSubscribeAction->setEnabled(1==selected.count() && static_cast(proxy.mapToSource(selected.first()).internalPointer())->isPodcast()); downloadAction->setEnabled(!selected.isEmpty()); deleteAction->setEnabled(!selected.isEmpty()); cancelDownloadAction->setEnabled(!selected.isEmpty()); markAsNewAction->setEnabled(!selected.isEmpty()); markAsListenedAction->setEnabled(!selected.isEmpty()); } #include "moc_podcastwidget.cpp"