File indexing completed on 2024-05-12 04:52:14

0001 /*
0002  * dvbepgdialog.cpp
0003  *
0004  * Copyright (C) 2009-2011 Christoph Pfister <christophpfister@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (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 along
0017  * with this program; if not, write to the Free Software Foundation, Inc.,
0018  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0019  */
0020 
0021 #include "../log.h"
0022 
0023 #include <KConfigGroup>
0024 #include <QAction>
0025 #include <QBoxLayout>
0026 #include <QCheckBox>
0027 #include <QComboBox>
0028 #include <QCoreApplication>
0029 #include <QDialogButtonBox>
0030 #include <QHeaderView>
0031 #include <QLabel>
0032 #include <QLineEdit>
0033 #include <QLocale>
0034 #include <QPushButton>
0035 #include <QScrollArea>
0036 #include <QVBoxLayout>
0037 
0038 #include "dvbepgdialog.h"
0039 #include "dvbepgdialog_p.h"
0040 #include "dvbmanager.h"
0041 #include "../iso-codes.h"
0042 
0043 DvbEpgDialog::DvbEpgDialog(DvbManager *manager_, QWidget *parent) : QDialog(parent),
0044     manager(manager_)
0045 {
0046     setWindowTitle(i18nc("@title:window", "Program Guide"));
0047 
0048     QWidget *mainWidget = new QWidget(this);
0049     QBoxLayout *mainLayout = new QHBoxLayout;
0050     setLayout(mainLayout);
0051 
0052     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0053     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
0054     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
0055     mainLayout->addWidget(mainWidget);
0056 
0057     QWidget *widget = new QWidget(this);
0058 
0059     epgChannelTableModel = new DvbEpgChannelTableModel(this);
0060     epgChannelTableModel->setManager(manager);
0061     channelView = new QTreeView(widget);
0062     channelView->setMaximumWidth(30 * fontMetrics().averageCharWidth());
0063     channelView->setModel(epgChannelTableModel);
0064     channelView->setRootIsDecorated(false);
0065     connect(channelView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0066         this, SLOT(channelActivated(QModelIndex)));
0067     mainLayout->addWidget(channelView);
0068 
0069     QBoxLayout *rightLayout = new QVBoxLayout();
0070     QBoxLayout *boxLayout = new QHBoxLayout();
0071     QBoxLayout *langLayout = new QHBoxLayout();
0072 
0073     QLabel *label = new QLabel(i18n("EPG language:"), mainWidget);
0074     langLayout->addWidget(label);
0075 
0076     languageBox = new QComboBox(mainWidget);
0077     languageBox->addItem("");
0078     QHashIterator<QString, bool> i(manager_->languageCodes);
0079     int j = 1;
0080     while (i.hasNext()) {
0081         i.next();
0082         QString lang = i.key();
0083         if (lang != FIRST_LANG) {
0084             languageBox->addItem(lang);
0085             if (manager_->currentEpgLanguage == lang) {
0086                 languageBox->setCurrentIndex(j);
0087                 currentLanguage = lang;
0088             }
0089             j++;
0090         }
0091     }
0092     langLayout->addWidget(languageBox);
0093     connect(languageBox, &QComboBox::currentTextChanged,
0094         this, &DvbEpgDialog::languageChanged);
0095     connect(manager_->getEpgModel(), &DvbEpgModel::languageAdded,
0096         this, &DvbEpgDialog::languageAdded);
0097 
0098     languageLabel = new QLabel(mainWidget);
0099     langLayout->addWidget(languageLabel);
0100     languageLabel->setBuddy(languageBox);
0101     QString languageString;
0102     if (IsoCodes::getLanguage(currentLanguage, &languageString))
0103         languageLabel->setText(languageString);
0104     else if (currentLanguage.isEmpty())
0105         languageLabel->setText(i18n("Any language"));
0106     else
0107         languageLabel->setText("");
0108 
0109     rightLayout->addLayout(langLayout);
0110 
0111     QHBoxLayout *showLayout = new QHBoxLayout();
0112     showLayout->addWidget(new QLabel(i18n("Show channels with empty EPG data")));
0113     showBox = new QCheckBox(widget);
0114     showLayout->addWidget(showBox, Qt::AlignLeft);
0115     rightLayout->addLayout(showLayout);
0116     connect(showBox, SIGNAL(clicked()), this, SLOT(changeShowMode()));
0117 
0118     QAction *scheduleAction = new QAction(QIcon::fromTheme(QLatin1String("media-record"), QIcon(":media-record")),
0119         i18nc("@action:inmenu tv show", "Record Show"), this);
0120     connect(scheduleAction, SIGNAL(triggered()), this, SLOT(scheduleProgram()));
0121 
0122     QPushButton *pushButton =
0123         new QPushButton(scheduleAction->icon(), scheduleAction->text(), widget);
0124     connect(pushButton, SIGNAL(clicked()), this, SLOT(scheduleProgram()));
0125     boxLayout->addWidget(pushButton);
0126 
0127     boxLayout->addWidget(new QLabel(i18nc("@label:textbox", "Search:"), widget));
0128 
0129     epgTableModel = new DvbEpgTableModel(this);
0130     epgTableModel->setEpgModel(manager->getEpgModel());
0131     epgTableModel->setLanguage(currentLanguage);
0132     connect(epgTableModel, SIGNAL(layoutChanged()), this, SLOT(checkEntry()));
0133     QLineEdit *lineEdit = new QLineEdit(widget);
0134     lineEdit->setClearButtonEnabled(true);
0135     connect(lineEdit, SIGNAL(textChanged(QString)),
0136         epgTableModel, SLOT(setContentFilter(QString)));
0137     boxLayout->addWidget(lineEdit);
0138     rightLayout->addLayout(boxLayout);
0139 
0140     epgView = new QTreeView(widget);
0141     epgView->addAction(scheduleAction);
0142     epgView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
0143     epgView->setContextMenuPolicy(Qt::ActionsContextMenu);
0144     epgView->setMinimumWidth(75 * fontMetrics().averageCharWidth());
0145     epgView->setModel(epgTableModel);
0146     epgView->setRootIsDecorated(false);
0147     epgView->setUniformRowHeights(true);
0148     connect(epgView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
0149         this, SLOT(entryActivated(QModelIndex)));
0150     rightLayout->addWidget(epgView);
0151 
0152     contentLabel = new QLabel(widget);
0153     contentLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
0154     contentLabel->setMargin(5);
0155     contentLabel->setWordWrap(true);
0156 
0157     QScrollArea *scrollArea = new QScrollArea(widget);
0158     scrollArea->setBackgroundRole(QPalette::Light);
0159     scrollArea->setMinimumHeight(12 * fontMetrics().height());
0160     scrollArea->setWidget(contentLabel);
0161     scrollArea->setWidgetResizable(true);
0162     rightLayout->addWidget(scrollArea);
0163     mainLayout->addLayout(rightLayout);
0164     mainLayout->addWidget(widget);
0165 
0166     rightLayout->addWidget(buttonBox);
0167 }
0168 
0169 DvbEpgDialog::~DvbEpgDialog()
0170 {
0171 }
0172 
0173 void DvbEpgDialog::setCurrentChannel(const DvbSharedChannel &channel)
0174 {
0175     channelView->setCurrentIndex(epgChannelTableModel->find(channel));
0176 }
0177 
0178 void DvbEpgDialog::channelActivated(const QModelIndex &index)
0179 {
0180     if (!index.isValid()) {
0181         epgTableModel->setChannelFilter(DvbSharedChannel());
0182         return;
0183     }
0184 
0185     epgTableModel->setChannelFilter(epgChannelTableModel->value(index));
0186     epgView->setCurrentIndex(epgTableModel->index(0, 0));
0187 }
0188 
0189 void DvbEpgDialog::languageChanged(const QString lang)
0190 {
0191     // Handle the any language case
0192     if (languageBox->currentIndex() <= 0)
0193         currentLanguage = "";
0194     else
0195         currentLanguage = lang;
0196     QString languageString;
0197     if (IsoCodes::getLanguage(currentLanguage, &languageString))
0198         languageLabel->setText(languageString);
0199     else if (currentLanguage.isEmpty())
0200         languageLabel->setText(i18n("Any language"));
0201     else
0202         languageLabel->setText(QString());
0203 
0204     epgTableModel->setLanguage(currentLanguage);
0205     epgView->setCurrentIndex(epgTableModel->index(0, 0));
0206     entryActivated(epgTableModel->index(0, 0));
0207     manager->currentEpgLanguage = currentLanguage;
0208 }
0209 
0210 void DvbEpgDialog::languageAdded(const QString lang)
0211 {
0212     if (lang != FIRST_LANG)
0213         languageBox->addItem(lang);
0214 }
0215 
0216 void DvbEpgDialog::entryActivated(const QModelIndex &index)
0217 {
0218     const DvbSharedEpgEntry &entry = epgTableModel->value(index.row());
0219 
0220     if (!entry.isValid()) {
0221         contentLabel->setText(QString());
0222         return;
0223     }
0224 
0225     QString text = "<font color=#008000 size=\"+1\">" + entry->title(currentLanguage) + "</font>";
0226 
0227     if (!entry->subheading().isEmpty()) {
0228         text += "<br/><font color=#808000>" + entry->subheading(currentLanguage) + "</font>";
0229     }
0230 
0231     QDateTime begin = entry->begin.toLocalTime();
0232     QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time();
0233     text += "<br/><br/><font color=#800080>" + QLocale().toString(begin, QLocale::LongFormat) + " - " + QLocale().toString(end) + "</font>";
0234 
0235     if (!entry->details(currentLanguage).isEmpty() && entry->details(currentLanguage) !=  entry->title(currentLanguage)) {
0236         text += "<br/><br/>" + entry->details(currentLanguage);
0237     }
0238 
0239     if (!entry->content.isEmpty()) {
0240         text += "<br/><br/><font color=#000080>" + entry->content + "</font>";
0241     }
0242     if (!entry->parental.isEmpty()) {
0243         text += "<br/><br/><font color=#800000>" + entry->parental + "</font>";
0244     }
0245 
0246     contentLabel->setText(text);
0247 }
0248 
0249 void DvbEpgDialog::checkEntry()
0250 {
0251     if (!epgView->currentIndex().isValid()) {
0252         // FIXME workaround --> file bug
0253         contentLabel->setText(QString());
0254     }
0255 }
0256 
0257 void DvbEpgDialog::scheduleProgram()
0258 {
0259     const DvbSharedEpgEntry &entry = epgTableModel->value(epgView->currentIndex());
0260 
0261     if (entry.isValid()) {
0262         manager->getEpgModel()->scheduleProgram(entry, manager->getBeginMargin(),
0263             manager->getEndMargin());
0264     }
0265 }
0266 
0267 void DvbEpgDialog::changeShowMode()
0268 {
0269     epgChannelTableModel->setViewMode(showBox->isChecked());
0270 }
0271 
0272 bool DvbEpgEntryLessThan::operator()(const DvbSharedEpgEntry &x, const DvbSharedEpgEntry &y) const
0273 {
0274     if (x->channel != y->channel) {
0275         return (x->channel->name.localeAwareCompare(y->channel->name) < 0);
0276     }
0277 
0278     if (x->begin != y->begin) {
0279         return (x->begin < y->begin);
0280     }
0281 
0282     if (x->duration != y->duration) {
0283         return (x->duration < y->duration);
0284     }
0285 
0286     if (x->title(FIRST_LANG) != y->title(FIRST_LANG)) {
0287         return (x->title(FIRST_LANG) < y->title(FIRST_LANG));
0288     }
0289 
0290     if (x->subheading(FIRST_LANG) != y->subheading(FIRST_LANG)) {
0291         return (x->subheading(FIRST_LANG) < y->subheading(FIRST_LANG));
0292     }
0293 
0294     if (x->details(FIRST_LANG) < y->details(FIRST_LANG)) {
0295         return (x->details(FIRST_LANG) < y->details(FIRST_LANG));
0296     }
0297 
0298     return (x < y);
0299 }
0300 
0301 DvbEpgChannelTableModel::DvbEpgChannelTableModel(QObject *parent) :
0302     TableModel<DvbEpgChannelTableModelHelper>(parent)
0303 {
0304 }
0305 
0306 DvbEpgChannelTableModel::~DvbEpgChannelTableModel()
0307 {
0308 }
0309 
0310 void DvbEpgChannelTableModel::setViewMode(bool enableEmptyEpgChannels)
0311 {
0312     if (enableEmptyEpgChannels) {
0313         DvbChannelModel *channelModel = manager->getChannelModel();
0314         reset(channelModel->getChannels());
0315     } else {
0316         DvbEpgModel *epgModel = manager->getEpgModel();
0317         resetFromKeys(epgModel->getEpgChannels());
0318     }
0319 }
0320 
0321 void DvbEpgChannelTableModel::setManager(DvbManager *_manager)
0322 {
0323     manager = _manager;
0324     DvbEpgModel *epgModel = manager->getEpgModel();
0325     connect(epgModel, SIGNAL(epgChannelAdded(DvbSharedChannel)),
0326         this, SLOT(epgChannelAdded(DvbSharedChannel)));
0327     connect(epgModel, SIGNAL(epgChannelRemoved(DvbSharedChannel)),
0328         this, SLOT(epgChannelRemoved(DvbSharedChannel)));
0329     // theoretically we should monitor the channel model for updated channels,
0330     // but it's very unlikely that this has practical relevance
0331 
0332     QHeaderView *headerView = manager->getChannelView()->header();
0333     DvbChannelLessThan::SortOrder sortOrder;
0334 
0335     if (headerView->sortIndicatorOrder() == Qt::AscendingOrder) {
0336         if (headerView->sortIndicatorSection() == 0) {
0337             sortOrder = DvbChannelLessThan::ChannelNameAscending;
0338         } else {
0339             sortOrder = DvbChannelLessThan::ChannelNumberAscending;
0340         }
0341     } else {
0342         if (headerView->sortIndicatorSection() == 0) {
0343             sortOrder = DvbChannelLessThan::ChannelNameDescending;
0344         } else {
0345             sortOrder = DvbChannelLessThan::ChannelNumberDescending;
0346         }
0347     }
0348 
0349     internalSort(sortOrder);
0350 
0351     setViewMode(false);
0352 }
0353 
0354 QVariant DvbEpgChannelTableModel::data(const QModelIndex &index, int role) const
0355 {
0356     const DvbSharedChannel &channel = value(index);
0357 
0358     if (channel.isValid() && (role == Qt::DisplayRole) && (index.column() == 0)) {
0359         return channel->name;
0360     }
0361 
0362     return QVariant();
0363 }
0364 
0365 QVariant DvbEpgChannelTableModel::headerData(int section, Qt::Orientation orientation,
0366     int role) const
0367 {
0368     if ((section == 0) && (orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) {
0369         return i18nc("@title:column tv show", "Channel");
0370     }
0371 
0372     return QVariant();
0373 }
0374 
0375 void DvbEpgChannelTableModel::epgChannelAdded(const DvbSharedChannel &channel)
0376 {
0377     insert(channel);
0378 }
0379 
0380 void DvbEpgChannelTableModel::epgChannelRemoved(const DvbSharedChannel &channel)
0381 {
0382     remove(channel);
0383 }
0384 
0385 bool DvbEpgTableModelHelper::filterAcceptsItem(const DvbSharedEpgEntry &entry) const
0386 {
0387     switch (filterType) {
0388     case ChannelFilter:
0389         return (entry->channel == channelFilter);
0390     case ContentFilter:
0391         return ((contentFilter.indexIn(entry->title(FIRST_LANG)) >= 0) ||
0392             (contentFilter.indexIn(entry->subheading(FIRST_LANG)) >= 0) ||
0393             (contentFilter.indexIn(entry->details(FIRST_LANG)) >= 0));
0394     }
0395 
0396     return false;
0397 }
0398 
0399 DvbEpgTableModel::DvbEpgTableModel(QObject *parent) : TableModel<DvbEpgTableModelHelper>(parent),
0400     epgModel(NULL), contentFilterEventPending(false)
0401 {
0402     helper.contentFilter.setCaseSensitivity(Qt::CaseInsensitive);
0403 }
0404 
0405 DvbEpgTableModel::~DvbEpgTableModel()
0406 {
0407 }
0408 
0409 void DvbEpgTableModel::setEpgModel(DvbEpgModel *epgModel_)
0410 {
0411     if (epgModel != NULL) {
0412         qCWarning(logEpg, "EPG model already set");
0413         return;
0414     }
0415 
0416     epgModel = epgModel_;
0417     connect(epgModel, SIGNAL(entryAdded(DvbSharedEpgEntry)),
0418         this, SLOT(entryAdded(DvbSharedEpgEntry)));
0419     connect(epgModel, SIGNAL(entryAboutToBeUpdated(DvbSharedEpgEntry)),
0420         this, SLOT(entryAboutToBeUpdated(DvbSharedEpgEntry)));
0421     connect(epgModel, SIGNAL(entryUpdated(DvbSharedEpgEntry)),
0422         this, SLOT(entryUpdated(DvbSharedEpgEntry)));
0423     connect(epgModel, SIGNAL(entryRemoved(DvbSharedEpgEntry)),
0424         this, SLOT(entryRemoved(DvbSharedEpgEntry)));
0425 }
0426 
0427 void DvbEpgTableModel::setChannelFilter(const DvbSharedChannel &channel)
0428 {
0429     helper.channelFilter = channel;
0430     helper.contentFilter.setPattern(QString());
0431     helper.filterType = DvbEpgTableModelHelper::ChannelFilter;
0432     reset(epgModel->getEntries());
0433 }
0434 
0435 void DvbEpgTableModel::setLanguage(QString lang)
0436 {
0437     currentLanguage = lang;
0438     reset(epgModel->getEntries());
0439 }
0440 
0441 QVariant DvbEpgTableModel::data(const QModelIndex &index, int role) const
0442 {
0443     const DvbSharedEpgEntry &entry = value(index);
0444 
0445     if (entry.isValid()) {
0446         switch (role) {
0447         case Qt::DecorationRole:
0448             if ((index.column() == 2) && entry->recording.isValid()) {
0449                 return QIcon::fromTheme(QLatin1String("media-record"), QIcon(":media-record"));
0450             }
0451 
0452             break;
0453         case Qt::DisplayRole:
0454             switch (index.column()) {
0455             case 0:
0456                 return QLocale().toString((entry->begin.toLocalTime()), QLocale::NarrowFormat);
0457             case 1:
0458                 return entry->duration.toString("HH:mm");
0459             case 2:
0460                 return entry->title(currentLanguage);
0461             case 3:
0462                 return entry->channel->name;
0463             }
0464 
0465             break;
0466         }
0467     }
0468 
0469     return QVariant();
0470 }
0471 
0472 QVariant DvbEpgTableModel::headerData(int section, Qt::Orientation orientation, int role) const
0473 {
0474     if ((orientation == Qt::Horizontal) && (role == Qt::DisplayRole)) {
0475         switch (section) {
0476         case 0:
0477             return i18nc("@title:column tv show", "Start");
0478         case 1:
0479             return i18nc("@title:column tv show", "Duration");
0480         case 2:
0481             return i18nc("@title:column tv show", "Title");
0482         case 3:
0483             return i18nc("@title:column tv show", "Channel");
0484         }
0485     }
0486 
0487     return QVariant();
0488 }
0489 
0490 void DvbEpgTableModel::setContentFilter(const QString &pattern)
0491 {
0492     helper.channelFilter = DvbSharedChannel();
0493     helper.contentFilter.setPattern(pattern);
0494 
0495     if (!pattern.isEmpty()) {
0496         helper.filterType = DvbEpgTableModelHelper::ContentFilter;
0497 
0498         if (!contentFilterEventPending) {
0499             contentFilterEventPending = true;
0500             QCoreApplication::postEvent(this, new QEvent(QEvent::User),
0501                 Qt::LowEventPriority);
0502         }
0503     } else {
0504         // use channel filter so that content won't be unnecessarily filtered
0505         helper.filterType = DvbEpgTableModelHelper::ChannelFilter;
0506         reset(QMap<DvbEpgEntryId, DvbSharedEpgEntry>());
0507     }
0508 }
0509 
0510 void DvbEpgTableModel::entryAdded(const DvbSharedEpgEntry &entry)
0511 {
0512     insert(entry);
0513 }
0514 
0515 void DvbEpgTableModel::entryAboutToBeUpdated(const DvbSharedEpgEntry &entry)
0516 {
0517     aboutToUpdate(entry);
0518 }
0519 
0520 void DvbEpgTableModel::entryUpdated(const DvbSharedEpgEntry &entry)
0521 {
0522     update(entry);
0523 }
0524 
0525 void DvbEpgTableModel::entryRemoved(const DvbSharedEpgEntry &entry)
0526 {
0527     remove(entry);
0528 }
0529 
0530 void DvbEpgTableModel::customEvent(QEvent *event)
0531 {
0532     Q_UNUSED(event)
0533     contentFilterEventPending = false;
0534 
0535     if (helper.filterType == DvbEpgTableModelHelper::ContentFilter) {
0536         reset(epgModel->getEntries());
0537     }
0538 }
0539 
0540 #include "moc_dvbepgdialog.cpp"
0541 #include "moc_dvbepgdialog_p.cpp"