diff --git a/src/dvb/dvbconfigdialog.cpp b/src/dvb/dvbconfigdialog.cpp index 8d45db8..7b45777 100644 --- a/src/dvb/dvbconfigdialog.cpp +++ b/src/dvb/dvbconfigdialog.cpp @@ -1,1687 +1,1695 @@ /* * dvbconfigdialog.cpp * * Copyright (C) 2007-2011 Christoph Pfister * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dvbconfig.h" #include "dvbconfigdialog.h" #include "dvbdevice.h" #include "dvbmanager.h" #include "dvbrecording.h" DvbConfigDialog::DvbConfigDialog(DvbManager *manager_, QWidget *parent) : QDialog(parent), manager(manager_) { setWindowTitle(i18nc("@title:window", "Configure Television")); tabWidget = new QTabWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(tabWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QWidget *widget = new QWidget(tabWidget); mainLayout->addWidget(widget); QBoxLayout *boxLayout = new QVBoxLayout(widget); int line = 0; QGridLayout *gridLayout = new QGridLayout(); gridLayout->addWidget(new QLabel(i18n("Recording folder:")), line, 0); recordingFolderEdit = new QLineEdit(widget); recordingFolderEdit->setText(manager->getRecordingFolder()); gridLayout->addWidget(recordingFolderEdit, line, 1); QToolButton *toolButton = new QToolButton(widget); toolButton->setIcon(QIcon::fromTheme(QLatin1String("document-open-folder"), QIcon(":document-open-folder"))); toolButton->setToolTip(i18n("Select Folder")); connect(toolButton, SIGNAL(clicked()), this, SLOT(changeRecordingFolder())); gridLayout->addWidget(toolButton, line++, 2); gridLayout->addWidget(new QLabel(i18n("Time shift folder:")), line, 0); timeShiftFolderEdit = new QLineEdit(widget); timeShiftFolderEdit->setText(manager->getTimeShiftFolder()); gridLayout->addWidget(timeShiftFolderEdit, line, 1); toolButton = new QToolButton(widget); toolButton->setIcon(QIcon::fromTheme(QLatin1String("document-open-folder"), QIcon(":document-open-folder"))); toolButton->setToolTip(i18n("Select Folder")); connect(toolButton, SIGNAL(clicked()), this, SLOT(changeTimeShiftFolder())); gridLayout->addWidget(toolButton, line++, 2); gridLayout->addWidget(new QLabel(i18n("xmltv file name (optional):")), line, 0); xmltvFileNameEdit = new QLineEdit(widget); xmltvFileNameEdit->setText(manager->getXmltvFileName()); gridLayout->addWidget(xmltvFileNameEdit, line, 1); toolButton = new QToolButton(widget); toolButton->setIcon(QIcon::fromTheme(QLatin1String("document-open-folder"), QIcon(":document-open-folder"))); toolButton->setToolTip(i18n("Add optional file to allow reading EPG data from xmltv files")); connect(toolButton, SIGNAL(clicked()), this, SLOT(changeXmltvFileName())); gridLayout->addWidget(toolButton, line++, 2); + gridLayout->addWidget(new QLabel(i18n("Disable parsing EPG data from MPEG-TS tables:")), + line, 0); + toolButton->setToolTip(i18n("Use this if your TV provider doesn't broadcast reliable EPG data")); + disableEpgBox = new QCheckBox(widget); + disableEpgBox->setChecked(manager->disableEpg()); + gridLayout->addWidget(disableEpgBox, line++, 1); + boxLayout->addLayout(gridLayout); gridLayout = new QGridLayout(); gridLayout->addWidget(new QLabel(i18n("Begin margin (minutes):")), line, 0); beginMarginBox = new QSpinBox(widget); beginMarginBox->setRange(0, 99); beginMarginBox->setValue(manager->getBeginMargin() / 60); gridLayout->addWidget(beginMarginBox, line++, 1); gridLayout->addWidget(new QLabel(i18n("End margin (minutes):")), line, 0); endMarginBox = new QSpinBox(widget); endMarginBox->setRange(0, 99); endMarginBox->setValue(manager->getEndMargin() / 60); gridLayout->addWidget(endMarginBox, line++, 1); gridLayout->addWidget(new QLabel(i18n("Naming style for recordings:")), line, 0); namingFormat = new QLineEdit(widget); namingFormat->setText(manager->getNamingFormat()); namingFormat->setToolTip(i18n("The following substitutions work: \"%year\" for year (YYYY) and the following: %month, %day, %hour, %min, %sec, %channel and %title")); connect(namingFormat, SIGNAL(textChanged(QString)), this, SLOT(namingFormatChanged(QString))); gridLayout->addWidget(namingFormat, line, 1); validPixmap = QIcon::fromTheme(QLatin1String("dialog-ok-apply"), QIcon(":dialog-ok-apply")).pixmap(22); invalidPixmap = QIcon::fromTheme(QLatin1String("dialog-cancel"), QIcon(":dialog-cancel")).pixmap(22); namingFormatValidLabel = new QLabel(widget); namingFormatValidLabel->setPixmap(validPixmap); gridLayout->addWidget(namingFormatValidLabel, line++,2); gridLayout->addWidget(new QLabel(i18n("Action after recording finishes:")), line, 0); actionAfterRecordingLineEdit = new QLineEdit(widget); actionAfterRecordingLineEdit->setText(manager->getActionAfterRecording()); actionAfterRecordingLineEdit->setToolTip(i18n("Leave empty for no command.")); gridLayout->addWidget(actionAfterRecordingLineEdit, line++, 1); boxLayout->addLayout(gridLayout); gridLayout = new QGridLayout(); gridLayout->addWidget(new QLabel(i18n("Use ISO 8859-1 charset instead of ISO 6937:")), 1, 0); override6937CharsetBox = new QCheckBox(widget); override6937CharsetBox->setChecked(manager->override6937Charset()); gridLayout->addWidget(override6937CharsetBox, 1, 1); gridLayout->addWidget(new QLabel(i18n("Create info files to accompany EPG recordings:")), 2, 0); createInfoFileBox = new QCheckBox(widget); createInfoFileBox->setChecked(manager->createInfoFile()); gridLayout->addWidget(createInfoFileBox, 2, 1); #if 0 // FIXME: this functionality is not working. Comment it out gridLayout->addWidget(new QLabel(i18n("Scan channels when idle to fetch fresh EPG data:")), 3, 0); scanWhenIdleBox = new QCheckBox(widget); scanWhenIdleBox->setChecked(manager->isScanWhenIdle()); gridLayout->addWidget(scanWhenIdleBox, 3, 1); #endif QFrame *frame = new QFrame(widget); frame->setFrameShape(QFrame::HLine); boxLayout->addWidget(frame); boxLayout->addWidget(new QLabel(i18n("Scan data last updated on %1", QLocale().toString(manager->getScanDataDate(), QLocale::ShortFormat)))); QPushButton *pushButton = new QPushButton(i18n("Update Scan Data over Internet"), widget); connect(pushButton, SIGNAL(clicked()), this, SLOT(updateScanFile())); boxLayout->addWidget(pushButton); QPushButton *openScanFileButton = new QPushButton(i18n("Edit Scanfile"), widget); connect(openScanFileButton, SIGNAL(clicked()), this, SLOT(openScanFile())); boxLayout->addWidget(openScanFileButton); openScanFileButton->setToolTip(i18n("You can add channels manually to this file before scanning for them.")); frame = new QFrame(widget); frame->setFrameShape(QFrame::HLine); boxLayout->addWidget(frame); boxLayout->addLayout(gridLayout); QStyleOptionTab option; option.initFrom(tabWidget); int metric = style()->pixelMetric(QStyle::PM_SmallIconSize, &option, tabWidget); validPixmap = QIcon::fromTheme(QLatin1String("dialog-ok-apply"), QIcon(":dialog-ok-apply")).pixmap(metric); invalidPixmap = QIcon::fromTheme(QLatin1String("dialog-cancel"), QIcon(":dialog-cancel")).pixmap(metric); boxLayout->addStretch(); tabWidget->addTab(widget, QIcon::fromTheme(QLatin1String("configure"), QIcon(":configure")), i18n("General Options")); QWidget *widgetAutomaticRecording = new QWidget(tabWidget); QBoxLayout *boxLayoutAutomaticRecording = new QVBoxLayout(widgetAutomaticRecording); QGridLayout *buttonGrid = new QGridLayout(); initRegexButtons(buttonGrid); regexGrid = new QGridLayout(); int j = 0; foreach (const QString regex, manager->getRecordingRegexList()) { RegexInputLine *inputLine = new RegexInputLine(); inputLine->lineEdit = new QLineEdit(widget); inputLine->lineEdit->setText(regex); regexGrid->addWidget(inputLine->lineEdit, j, 0); inputLine->checkBox = new QCheckBox(widget); inputLine->checkBox->setChecked(false); regexGrid->addWidget(inputLine->checkBox, j, 2); inputLine->spinBox = new QSpinBox(); inputLine->spinBox->setValue(manager->getRecordingRegexPriorityList().value(j)); regexGrid->addWidget(inputLine->spinBox, j, 1); inputLine->index = j; regexInputList.append(inputLine); j = j + 1; } boxLayoutAutomaticRecording->addLayout(buttonGrid); boxLayoutAutomaticRecording->addLayout(regexGrid); tabWidget->addTab(widgetAutomaticRecording, QIcon::fromTheme(QLatin1String("configure"), QIcon(":configure")), i18n("Automatic Recording")); // int i = 1; foreach (const DvbDeviceConfig &deviceConfig, manager->getDeviceConfigs()) { DvbConfigPage *configPage = new DvbConfigPage(tabWidget, manager, &deviceConfig); connect(configPage, SIGNAL(moveLeft(DvbConfigPage*)), this, SLOT(moveLeft(DvbConfigPage*))); connect(configPage, SIGNAL(moveRight(DvbConfigPage*)), this, SLOT(moveRight(DvbConfigPage*))); connect(configPage, SIGNAL(remove(DvbConfigPage*)), this, SLOT(remove(DvbConfigPage*))); tabWidget->addTab(configPage, QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), i18n("Device %1", i)); configPages.append(configPage); ++i; } // Use a size that would allow show multiple devices resize(100 * fontMetrics().averageCharWidth(), 28 * fontMetrics().height()); if (!configPages.isEmpty()) { configPages.at(0)->setMoveLeftEnabled(false); configPages.at(configPages.size() - 1)->setMoveRightEnabled(false); } mainLayout->addWidget(buttonBox); } DvbConfigDialog::~DvbConfigDialog() { } void DvbConfigDialog::changeRecordingFolder() { QString path = QFileDialog::getExistingDirectory(this, QString(), recordingFolderEdit->text()); if (path.isEmpty()) { return; } recordingFolderEdit->setText(path); } void DvbConfigDialog::changeTimeShiftFolder() { QString path = QFileDialog::getExistingDirectory(this, QString(), timeShiftFolderEdit->text()); if (path.isEmpty()) { return; } timeShiftFolderEdit->setText(path); } void DvbConfigDialog::changeXmltvFileName() { QString path = QFileDialog::getOpenFileName(this, QString(), xmltvFileNameEdit->text()); if (path.isEmpty()) { return; } xmltvFileNameEdit->setText(path); } void DvbConfigDialog::updateScanFile() { QDialog *dialog = new DvbScanFileDownloadDialog(manager, this); dialog->setAttribute(Qt::WA_DeleteOnClose, true); dialog->setModal(true); dialog->show(); } void DvbConfigDialog::newRegex() { RegexInputLine *inputLine = new RegexInputLine(); inputLine->lineEdit = new QLineEdit(tabWidget); inputLine->lineEdit->setText(""); regexGrid->addWidget(inputLine->lineEdit, regexInputList.size(), 0); inputLine->checkBox = new QCheckBox(tabWidget); inputLine->checkBox->setChecked(false); regexGrid->addWidget(inputLine->checkBox, regexInputList.size(), 2); inputLine->spinBox = new QSpinBox(tabWidget); inputLine->spinBox->setRange(0, 99); inputLine->spinBox->setValue(5); regexGrid->addWidget(inputLine->spinBox, regexInputList.size(), 1); regexInputList.append(inputLine); } /** * Helper function. Deletes all child widgets of the given layout @a item. */ void deleteChildWidgets(QLayoutItem *item) { if (item->layout()) { // Process all child items recursively. for (int i = 0; i < item->layout()->count(); i++) { deleteChildWidgets(item->layout()->itemAt(i)); } } delete item->widget(); } /** * Helper function. Removes all layout items within the given @a layout * which either span the given @a row or @a column. If @a deleteWidgets * is true, all concerned child widgets become not only removed from the * layout, but also deleted. */ void DvbConfigDialog::removeWidgets(QGridLayout *layout, int row, int column, bool deleteWidgets) { // We avoid usage of QGridLayout::itemAtPosition() here to improve performance. for (int i = layout->count() - 1; i >= 0; i--) { int r, c, rs, cs; layout->getItemPosition(i, &r, &c, &rs, &cs); if ((r <= row && r + rs - 1 >= row) || (c <= column && c + cs - 1 >= column)) { // This layout item is subject to deletion. QLayoutItem *item = layout->takeAt(i); if (deleteWidgets) { deleteChildWidgets(item); } delete item; } } } void DvbConfigDialog::initRegexButtons(QGridLayout *buttonGrid) { QWidgetAction *action = new QWidgetAction(tabWidget); action->setIcon(QIcon::fromTheme(QLatin1String("list-add"), QIcon(":list-add"))); action->setText(i18nc("@action", "Add new Regex")); connect(action, SIGNAL(triggered()), this, SLOT(newRegex())); tabWidget->addAction(action); QPushButton *pushButtonAdd = new QPushButton(action->icon(), action->text(), tabWidget); connect(pushButtonAdd, SIGNAL(clicked()), this, SLOT(newRegex())); buttonGrid->addWidget(pushButtonAdd, 0, 0); pushButtonAdd->setToolTip(i18n("Add another regular expression.")); action = new QWidgetAction(tabWidget); action->setIcon(QIcon::fromTheme(QLatin1String("edit-delete"), QIcon(":edit-delete"))); action->setText(i18nc("@action", "Remove Regex")); connect(action, SIGNAL(triggered()), this, SLOT(removeRegex())); tabWidget->addAction(action); QPushButton *pushButtonRemove = new QPushButton(action->icon(), action->text(), tabWidget); connect(pushButtonRemove, SIGNAL(clicked()), this, SLOT(removeRegex())); buttonGrid->addWidget(pushButtonRemove, 0, 1); pushButtonRemove->setToolTip(i18n("Remove checked regular expressions.")); } void DvbConfigDialog::removeRegex() { //regexGrid = new QGridLayout(tabWidget); //regexBoxMap = QMap(); QList copyList = QList(); foreach(RegexInputLine *inputLine, regexInputList) { copyList.append(inputLine); } foreach(RegexInputLine *inputLine, copyList) { qCDebug(logDvb, "list:"); if (inputLine->checkBox->isChecked()){ qCDebug(logDvb, "checked:"); if (regexInputList.removeOne(inputLine)) { qCDebug(logDvb, "removed:"); } } } QWidget *widgetAutomaticRecording = new QWidget(tabWidget); QBoxLayout *boxLayoutAutomaticRecording = new QVBoxLayout(widgetAutomaticRecording); QGridLayout *buttonGrid = new QGridLayout(); regexGrid = new QGridLayout(); initRegexButtons(buttonGrid); int j = 0; foreach (RegexInputLine *oldLine, regexInputList) { RegexInputLine *inputLine = new RegexInputLine(); inputLine->lineEdit = new QLineEdit(); inputLine->lineEdit->setText(oldLine->lineEdit->text()); regexGrid->addWidget(inputLine->lineEdit, j, 0); inputLine->checkBox = new QCheckBox(); inputLine->checkBox->setChecked(false); regexGrid->addWidget(inputLine->checkBox, j, 2); inputLine->spinBox = new QSpinBox(); inputLine->spinBox->setValue(oldLine->spinBox->value()); regexGrid->addWidget(inputLine->spinBox, j, 1); inputLine->index = j; j = j + 1; } boxLayoutAutomaticRecording->addLayout(buttonGrid); boxLayoutAutomaticRecording->addLayout(regexGrid); tabWidget->removeTab(1); tabWidget->addTab(widgetAutomaticRecording, QIcon::fromTheme(QLatin1String("configure"), QIcon(":configure")), i18n("Automatic Recording")); tabWidget->move(tabWidget->count()-1, 1); tabWidget->setCurrentIndex(1); } void DvbConfigDialog::openScanFile() { QString file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/scanfile.dvb")); QDesktopServices::openUrl(QUrl(file)); } void DvbConfigDialog::namingFormatChanged(QString text) { QString temp = text.replace("%year", " "); temp = temp.replace("%month", " "); temp = temp.replace("%day", " "); temp = temp.replace("%hour", " "); temp = temp.replace("%min", " "); temp = temp.replace("%sec"," "); temp = temp.replace("%channel", " "); temp = temp.replace("%title", " "); if (!temp.contains("%")) { namingFormatValidLabel->setPixmap(validPixmap); } else { namingFormatValidLabel->setPixmap(invalidPixmap); } } void DvbConfigDialog::moveLeft(DvbConfigPage *configPage) { int index = configPages.indexOf(configPage); if (index <= 0) { return; } configPages.swap(index, index - 1); if (index == 1) { configPages.at(0)->setMoveLeftEnabled(false); configPages.at(1)->setMoveLeftEnabled(true); } if (index == (configPages.size() - 1)) { configPages.at(index)->setMoveRightEnabled(false); configPages.at(index - 1)->setMoveRightEnabled(true); } // configPages and tabWidget indexes differ by two tabWidget->insertTab(index + 1, configPages.at(index - 1), QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), i18n("Device %1", index)); tabWidget->setTabText(index + 2, i18n("Device %1", index + 1)); tabWidget->setCurrentIndex(index + 1); } void DvbConfigDialog::moveRight(DvbConfigPage *configPage) { int index = configPages.indexOf(configPage) + 1; if ((index <= 0) || (index == configPages.size())) { return; } configPages.swap(index, index - 1); if (index == 1) { configPages.at(0)->setMoveLeftEnabled(false); configPages.at(1)->setMoveLeftEnabled(true); } if (index == (configPages.size() - 1)) { configPages.at(index)->setMoveRightEnabled(false); configPages.at(index - 1)->setMoveRightEnabled(true); } // configPages and tabWidget indexes differ by two tabWidget->insertTab(index + 1, configPages.at(index - 1), QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television")), i18n("Device %1", index)); tabWidget->setTabText(index + 2, i18n("Device %1", index + 1)); tabWidget->setCurrentIndex(index + 2); } void DvbConfigDialog::remove(DvbConfigPage *configPage) { int index = configPages.indexOf(configPage); if (index < 0) { return; } delete configPages.takeAt(index); if ((index == 0) && !configPages.isEmpty()) { configPages.at(0)->setMoveLeftEnabled(false); } if ((index > 0) && (index == configPages.size())) { configPages.at(index - 1)->setMoveRightEnabled(false); } for (; index < configPages.size(); ++index) { // configPages and tabWidget indexes differ by two tabWidget->setTabText(index + 2, i18n("Device %1", index + 1)); } } void DvbConfigDialog::accept() { manager->setRecordingFolder(recordingFolderEdit->text()); manager->setTimeShiftFolder(timeShiftFolderEdit->text()); manager->setXmltvFileName(xmltvFileNameEdit->text()); manager->setNamingFormat(namingFormat->text()); manager->setActionAfterRecording(actionAfterRecordingLineEdit->text()); manager->setBeginMargin(beginMarginBox->value() * 60); manager->setEndMargin(endMarginBox->value() * 60); manager->setOverride6937Charset(override6937CharsetBox->isChecked()); manager->setCreateInfoFile(createInfoFileBox->isChecked()); + manager->setDisableEpg(disableEpgBox->isChecked()); #if 0 manager->setScanWhenIdle(scanWhenIdleBox->isChecked()); #endif manager->setRecordingRegexList(QStringList()); manager->setRecordingRegexPriorityList(QList()); foreach (RegexInputLine *regexInputLine, regexInputList) { manager->addRecordingRegex(regexInputLine->lineEdit->text()); qCDebug(logDvb, "saved regex: %s", qPrintable(regexInputLine->lineEdit->text())); manager->addRecordingRegexPriority(regexInputLine->spinBox->value()); qCDebug(logDvb, "saved priority: %i", regexInputLine->spinBox->value()); } QList configUpdates; foreach (DvbConfigPage *configPage, configPages) { DvbDeviceConfigUpdate configUpdate(configPage->getDeviceConfig()); configUpdate.configs = configPage->getConfigs(); configUpdates.append(configUpdate); } manager->updateDeviceConfigs(configUpdates); manager->getRecordingModel()->findNewRecordings(); manager->getRecordingModel()->removeDuplicates(); manager->getRecordingModel()->disableConflicts(); //manager->getRecordingModel()->scanChannels(); manager->writeDeviceConfigs(); QDialog::accept(); } DvbScanFileDownloadDialog::DvbScanFileDownloadDialog(DvbManager *manager_, QWidget *parent) : QDialog(parent), manager(manager_) { setWindowTitle(i18n("Update Scan Data")); QWidget *mainWidget = new QWidget(this); mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); QWidget *widget = new QWidget(this); mainLayout->addWidget(widget); mainLayout->addWidget(widget); label = new QLabel(i18n("Downloading scan data"), widget); mainLayout->addWidget(label); progressBar = new QProgressBar(widget); mainLayout->addWidget(progressBar); progressBar->setRange(0, 100); mainLayout->addWidget(buttonBox); job = KIO::get(QUrl("https://autoconfig.kde.org/kaffeine/scantable.dvb.qz"), KIO::NoReload, KIO::HideProgressInfo); // FIXME NoReload or Reload? job->setAutoDelete(false); connect(job, SIGNAL(percent(KJob*,ulong)), this, SLOT(progressChanged(KJob*,ulong))); connect(job, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(dataArrived(KIO::Job*,QByteArray))); connect(job, SIGNAL(result(KJob*)), this, SLOT(jobFinished())); } DvbScanFileDownloadDialog::~DvbScanFileDownloadDialog() { delete job; } void DvbScanFileDownloadDialog::progressChanged(KJob *, unsigned long percent) { progressBar->setValue(int(percent)); } void DvbScanFileDownloadDialog::dataArrived(KIO::Job *, const QByteArray &data) { if ((scanData.size() + data.size()) <= (64 * 1024)) { scanData.append(data); } else { job->kill(KJob::EmitResult); } } void DvbScanFileDownloadDialog::jobFinished() { progressBar->setValue(100); if (job->error() != 0) { if (job->error() == KJob::KilledJobError) { label->setText(i18n("Scan data update failed.")); } else { label->setText(job->errorString()); } return; } if (manager->updateScanData(scanData)) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); label->setText(i18n("Scan data successfully updated. Changes take\n" "effect after you have closed the configuration dialog.")); } else { label->setText(i18n("Scan data update failed.")); } } DvbConfigPage::DvbConfigPage(QWidget *parent, DvbManager *manager, const DvbDeviceConfig *deviceConfig_) : QWidget(parent), deviceConfig(deviceConfig_), dvbSObject(NULL) { boxLayout = new QVBoxLayout(this); boxLayout->addWidget(new QLabel(i18n("Name: %1", deviceConfig->frontendName))); QBoxLayout *horizontalLayout = new QHBoxLayout(); moveLeftButton = new QPushButton(QIcon::fromTheme(QLatin1String("arrow-left"), QIcon(":arrow-left")), i18n("Move Left"), this); connect(moveLeftButton, SIGNAL(clicked()), this, SLOT(moveLeft())); horizontalLayout->addWidget(moveLeftButton); if (deviceConfig->device != NULL) { QPushButton *pushButton = new QPushButton(QIcon::fromTheme(QLatin1String("edit-undo"), QIcon(":edit-undo")), i18n("Reset"), this); connect(pushButton, SIGNAL(clicked()), this, SIGNAL(resetConfig())); horizontalLayout->addWidget(pushButton); } else { QPushButton *pushButton = new QPushButton(QIcon::fromTheme(QLatin1String("edit-delete"), QIcon(":edit-delete")), i18nc("@action", "Remove"), this); connect(pushButton, SIGNAL(clicked()), this, SLOT(removeConfig())); horizontalLayout->addWidget(pushButton); } moveRightButton = new QPushButton(QIcon::fromTheme(QLatin1String("arrow-right"), QIcon(":arrow-right")), i18n("Move Right"), this); connect(moveRightButton, SIGNAL(clicked()), this, SLOT(moveRight())); horizontalLayout->addWidget(moveRightButton); boxLayout->addLayout(horizontalLayout); if (deviceConfig->device == NULL) { addHSeparator(i18n("Device not connected.")); boxLayout->addStretch(); configs = deviceConfig->configs; return; } DvbDevice::TransmissionTypes transmissionTypes = deviceConfig->device->getTransmissionTypes(); DvbConfig dvbCConfig; QList dvbSConfigs; DvbConfig dvbTConfig; DvbConfig atscConfig; DvbConfig isdbTConfig; foreach (const DvbConfig &config, deviceConfig->configs) { switch (config->getTransmissionType()) { case DvbConfigBase::DvbC: dvbCConfig = config; break; case DvbConfigBase::DvbS: dvbSConfigs.append(config); break; case DvbConfigBase::DvbT: dvbTConfig = config; break; case DvbConfigBase::Atsc: atscConfig = config; break; case DvbConfigBase::IsdbT: isdbTConfig = config; break; } } if ((transmissionTypes & DvbDevice::DvbC) != 0) { addHSeparator(i18n("DVB-C")); if (dvbCConfig.constData() == NULL) { DvbConfigBase *config = new DvbConfigBase(DvbConfigBase::DvbC); config->timeout = 1500; dvbCConfig = DvbConfig(config); } new DvbConfigObject(this, boxLayout, manager, dvbCConfig.data(), false); configs.append(dvbCConfig); } if ((transmissionTypes & (DvbDevice::DvbS | DvbDevice::DvbS2)) != 0) { bool dvbS2 = ((transmissionTypes & DvbDevice::DvbS2) != 0); if (dvbS2) { addHSeparator(i18n("DVB-S2")); } else { addHSeparator(i18n("DVB-S")); } dvbSObject = new DvbSConfigObject(this, boxLayout, manager, dvbSConfigs, deviceConfig->device, dvbS2); } if ((transmissionTypes & DvbDevice::DvbT) != 0) { bool dvbT2 = ((transmissionTypes & DvbDevice::DvbT2) != 0); if (dvbT2) { addHSeparator(i18n("DVB-T2")); } else { addHSeparator(i18n("DVB-T")); } if (dvbTConfig.constData() == NULL) { DvbConfigBase *config = new DvbConfigBase(DvbConfigBase::DvbT); config->timeout = 1500; dvbTConfig = DvbConfig(config); } new DvbConfigObject(this, boxLayout, manager, dvbTConfig.data(), dvbT2); configs.append(dvbTConfig); } if ((transmissionTypes & DvbDevice::Atsc) != 0) { addHSeparator(i18n("ATSC")); if (atscConfig.constData() == NULL) { DvbConfigBase *config = new DvbConfigBase(DvbConfigBase::Atsc); config->timeout = 1500; atscConfig = DvbConfig(config); } new DvbConfigObject(this, boxLayout, manager, atscConfig.data(), false); configs.append(atscConfig); } if ((transmissionTypes & DvbDevice::IsdbT) != 0) { addHSeparator(i18n("ISDB-T")); if (isdbTConfig.constData() == NULL) { DvbConfigBase *config = new DvbConfigBase(DvbConfigBase::IsdbT); config->timeout = 1500; isdbTConfig = DvbConfig(config); } new DvbConfigObject(this, boxLayout, manager, isdbTConfig.data(), false); configs.append(isdbTConfig); } boxLayout->addStretch(); } DvbConfigPage::~DvbConfigPage() { } void DvbConfigPage::setMoveLeftEnabled(bool enabled) { moveLeftButton->setEnabled(enabled); } void DvbConfigPage::setMoveRightEnabled(bool enabled) { moveRightButton->setEnabled(enabled); } const DvbDeviceConfig *DvbConfigPage::getDeviceConfig() const { return deviceConfig; } QList DvbConfigPage::getConfigs() { if (dvbSObject != NULL) { dvbSObject->appendConfigs(configs); } for (int i = 0; i < configs.count(); ++i) { const DvbConfig &config = configs.at(i); if (config->name.isEmpty() || config->scanSource.isEmpty()) { configs.removeAt(i); --i; } } return configs; } void DvbConfigPage::moveLeft() { emit moveLeft(this); } void DvbConfigPage::moveRight() { emit moveRight(this); } void DvbConfigPage::removeConfig() { emit remove(this); } void DvbConfigPage::addHSeparator(const QString &title) { QFrame *frame = new QFrame(this); frame->setFrameShape(QFrame::HLine); boxLayout->addWidget(frame); boxLayout->addWidget(new QLabel(title, this)); } DvbConfigObject::DvbConfigObject(QWidget *parent, QBoxLayout *layout, DvbManager *manager, DvbConfigBase *config_, bool isGen2_) : QObject(parent), config(config_) { QStringList sources; int sourceIndex = -1; isGen2 = isGen2_; switch (config->getTransmissionType()) { case DvbConfigBase::DvbC: defaultName = i18n("Cable"); sources = manager->getScanSources(DvbManager::DvbC); sourceIndex = sources.indexOf(config->scanSource); break; case DvbConfigBase::DvbS: // handled separately break; case DvbConfigBase::DvbT: if (isGen2) { defaultName = i18n("Terrestrial (T2)"); sources.append(QLatin1String("AUTO-T2-Normal")); sources.append(QLatin1String("AUTO-T2-Offsets")); sources.append(QLatin1String("AUTO-T2-Australia")); sources.append(QLatin1String("AUTO-T2-Italy")); sources.append(QLatin1String("AUTO-T2-Taiwan")); sources += manager->getScanSources(DvbManager::DvbT2); } else { defaultName = i18n("Terrestrial"); sources.append(QLatin1String("AUTO-T-Normal")); sources.append(QLatin1String("AUTO-T-Offsets")); sources.append(QLatin1String("AUTO-T-Australia")); sources.append(QLatin1String("AUTO-T-Italy")); sources.append(QLatin1String("AUTO-T-Taiwan")); sources += manager->getScanSources(DvbManager::DvbT); } sourceIndex = sources.indexOf(config->scanSource); sources.replace(0, i18n("Autoscan")); sources.replace(1, i18n("Autoscan with 167 kHz Offsets")); sources.replace(2, i18n("Autoscan Australia")); sources.replace(3, i18n("Autoscan Italy")); sources.replace(4, i18n("Autoscan Taiwan")); break; case DvbConfigBase::Atsc: defaultName = i18n("Atsc"); sources = manager->getScanSources(DvbManager::Atsc); sourceIndex = sources.indexOf(config->scanSource); break; case DvbConfigBase::IsdbT: defaultName = i18n("ISDB-T"); sources.append(QLatin1String("AUTO-UHF-6MHz")); sources.replace(0, i18n("Autoscan")); sources += manager->getScanSources(DvbManager::IsdbT); sourceIndex = sources.indexOf(config->scanSource); break; } QGridLayout *gridLayout = new QGridLayout(); layout->addLayout(gridLayout); gridLayout->addWidget(new QLabel(i18n("Tuner timeout (ms):")), 0, 0); timeoutBox = new QSpinBox(parent); timeoutBox->setRange(100, 5000); timeoutBox->setSingleStep(100); timeoutBox->setValue(config->timeout); connect(timeoutBox, SIGNAL(valueChanged(int)), this, SLOT(timeoutChanged(int))); gridLayout->addWidget(timeoutBox, 0, 1); gridLayout->addWidget(new QLabel(i18n("Source:")), 1, 0); sourceBox = new QComboBox(parent); sourceBox->addItem(i18n("No Source")); sourceBox->addItems(sources); sourceBox->setCurrentIndex(sourceIndex + 1); connect(sourceBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceChanged(int))); gridLayout->addWidget(sourceBox, 1, 1); gridLayout->addWidget(new QLabel(i18n("Name:")), 2, 0); nameEdit = new QLineEdit(parent); nameEdit->setText(config->name); connect(nameEdit, SIGNAL(editingFinished()), this, SLOT(nameChanged())); gridLayout->addWidget(nameEdit, 2, 1); timeoutChanged(timeoutBox->value()); sourceChanged(sourceBox->currentIndex()); nameChanged(); connect(parent, SIGNAL(resetConfig()), this, SLOT(resetConfig())); } DvbConfigObject::~DvbConfigObject() { } void DvbConfigObject::timeoutChanged(int timeout) { config->timeout = timeout; } void DvbConfigObject::sourceChanged(int index) { if (index <= 0) { // no source selected nameEdit->setEnabled(false); config->scanSource.clear(); } else if ((index <= 5) && (config->getTransmissionType() == DvbConfigBase::DvbT)) { nameEdit->setEnabled(true); if (isGen2) { switch (index - 1) { case 0: config->scanSource = QLatin1String("AUTO-T2-Normal"); break; case 1: config->scanSource = QLatin1String("AUTO-T2-Offsets"); break; case 2: config->scanSource = QLatin1String("AUTO-T2-Australia"); break; case 3: config->scanSource = QLatin1String("AUTO-T2-Italy"); break; case 4: config->scanSource = QLatin1String("AUTO-T2-Taiwan"); break; } } else { switch (index - 1) { case 0: config->scanSource = QLatin1String("AUTO-T-Normal"); break; case 1: config->scanSource = QLatin1String("AUTO-T-Offsets"); break; case 2: config->scanSource = QLatin1String("AUTO-T-Australia"); break; case 3: config->scanSource = QLatin1String("AUTO-T-Italy"); break; case 4: config->scanSource = QLatin1String("AUTO-T-Taiwan"); break; } } } else { nameEdit->setEnabled(true); config->scanSource = sourceBox->currentText(); } } void DvbConfigObject::nameChanged() { QString name = nameEdit->text(); if (name.isEmpty()) { nameEdit->setText(defaultName); config->name = defaultName; } else { config->name = name; } } void DvbConfigObject::resetConfig() { timeoutBox->setValue(1500); // FIXME hardcoded sourceBox->setCurrentIndex(0); nameEdit->setText(defaultName); } DvbSConfigObject::DvbSConfigObject(QWidget *parent_, QBoxLayout *boxLayout, DvbManager *manager, const QList &configs, DvbDevice *device_, bool dvbS2) : QObject(parent_), parent(parent_), device(device_) { if (!configs.isEmpty()) { lnbConfig = new DvbConfigBase(*configs.at(0)); } else { lnbConfig = createConfig(0); } if (dvbS2) { sources = manager->getScanSources(DvbManager::DvbS2); } else { sources = manager->getScanSources(DvbManager::DvbS); } layout = new QGridLayout(); boxLayout->addLayout(layout); layout->addWidget(new QLabel(i18n("Tuner timeout (ms):")), 0, 0); timeoutBox = new QSpinBox(parent); timeoutBox->setRange(100, 5000); timeoutBox->setSingleStep(100); timeoutBox->setValue(lnbConfig->timeout); layout->addWidget(timeoutBox, 0, 1); layout->addWidget(new QLabel(i18n("Use Higher LNBf voltage:")), 1, 0); higherVoltageBox = new QCheckBox(parent); higherVoltageBox->setTristate(true); higherVoltageBox->setCheckState((Qt::CheckState)lnbConfig->higherVoltage); higherVoltageBox->setToolTip(i18n("On some DVB devices, there's an extra control that allows to increase the \n" "voltage sent to LNBf, in order to compensate for cabling loss. This tri-state\n" "button defaults to not use it. It may be set to:\n" "- normal mode (unchecked);\n" "- higher voltage mode (checked);\n" "- tristate (don't send the control).")); layout->addWidget(higherVoltageBox, 1, 1); layout->addWidget(new QLabel(i18n("Configuration:")), 2, 0); configBox = new QComboBox(parent); configBox->addItem(i18n("DiSEqC Switch")); configBox->addItem(i18n("USALS Rotor")); configBox->addItem(i18n("Positions Rotor")); configBox->addItem(i18n("Disable DiSEqC")); configBox->setCurrentIndex(lnbConfig->configuration); connect(configBox, SIGNAL(currentIndexChanged(int)), this, SLOT(configChanged(int))); layout->addWidget(configBox, 2, 1); // Diseqc switch for (int lnbNumber = 0; lnbNumber < 4; ++lnbNumber) { DvbConfigBase *config; if (((lnbConfig->configuration == DvbConfigBase::DiseqcSwitch) || (lnbConfig->configuration == DvbConfigBase::NoDiseqc)) && (lnbNumber < configs.size())) { config = new DvbConfigBase(*configs.at(lnbNumber)); } else { config = createConfig(lnbNumber); } QPushButton *pushButton = new QPushButton(i18n("LNB %1 Settings", lnbNumber + 1), parent); if (lnbNumber > 0) connect(this, &DvbSConfigObject::setDiseqcVisible, pushButton, &QPushButton::setVisible); else connect(this, &DvbSConfigObject::setFirstLnbVisible, pushButton, &QPushButton::setVisible); layout->addWidget(pushButton, lnbNumber + 3, 0); QComboBox *comboBox = new QComboBox(parent); comboBox->addItem(i18n("No Source")); comboBox->addItems(sources); comboBox->setCurrentIndex(sources.indexOf(config->scanSource) + 1); if (lnbNumber > 0) connect(this, &DvbSConfigObject::setDiseqcVisible, comboBox, &QComboBox::setVisible); else connect(this, &DvbSConfigObject::setFirstLnbVisible, comboBox, &QComboBox::setVisible); layout->addWidget(comboBox, lnbNumber + 3, 1); diseqcConfigs.append(DvbConfig(config)); lnbConfigs.append(new DvbSLnbConfigObject(timeoutBox, higherVoltageBox, comboBox, pushButton, config, device)); } // USALS rotor / Positions rotor QPushButton *pushButton = new QPushButton(i18n("LNB Settings"), parent); connect(this, &DvbSConfigObject::setRotorVisible, pushButton, &QPushButton::setVisible); layout->addWidget(pushButton, 7, 0); lnbConfigs.append(new DvbSLnbConfigObject(timeoutBox, higherVoltageBox, NULL, pushButton, lnbConfig, device)); sourceBox = new QComboBox(parent); sourceBox->addItems(sources); connect(this, &DvbSConfigObject::setRotorVisible, sourceBox, &QComboBox::setVisible); layout->addWidget(sourceBox, 7, 1); satelliteView = new QTreeWidget(parent); // Usals rotor pushButton = new QPushButton(i18n("Add Satellite"), parent); connect(this, &DvbSConfigObject::setUsalsVisible, pushButton, &QPushButton::setVisible); connect(pushButton, SIGNAL(clicked()), this, SLOT(addSatellite())); layout->addWidget(pushButton, 8, 0, 1, 2); // Positions rotor rotorSpinBox = new QSpinBox(parent); rotorSpinBox->setRange(0, 255); connect(this, &DvbSConfigObject::setPositionsVisible, rotorSpinBox, &QSpinBox::setVisible); layout->addWidget(rotorSpinBox, 9, 0); pushButton = new QPushButton(i18n("Add Satellite"), parent); connect(this, &DvbSConfigObject::setPositionsVisible, pushButton, &QPushButton::setVisible); connect(pushButton, SIGNAL(clicked()), this, SLOT(addSatellite())); layout->addWidget(pushButton, 9, 1); // USALS rotor / Positions rotor satelliteView->setColumnCount(2); satelliteView->setHeaderLabels(QStringList() << i18n("Satellite") << i18n("Position")); satelliteView->setMinimumHeight(100); satelliteView->setRootIsDecorated(false); satelliteView->sortByColumn(0, Qt::AscendingOrder); satelliteView->setSortingEnabled(true); connect(this, SIGNAL(setRotorVisible(bool)), satelliteView, SLOT(setVisible(bool))); layout->addWidget(satelliteView, 10, 0, 1, 2); if ((lnbConfig->configuration == DvbConfigBase::UsalsRotor) || (lnbConfig->configuration == DvbConfigBase::PositionsRotor)) { foreach (const DvbConfig &config, configs) { QStringList stringList; stringList << config->scanSource << QString::number(config->lnbNumber); satelliteView->addTopLevelItem(new QTreeWidgetItem(stringList)); } } pushButton = new QPushButton(i18n("Remove Satellite"), parent); connect(this, &DvbSConfigObject::setRotorVisible, pushButton, &QPushButton::setVisible); connect(pushButton, SIGNAL(clicked()), this, SLOT(removeSatellite())); layout->addWidget(pushButton, 11, 0, 1, 2); // Latitude/Longitude for USALS rotor layout = new QGridLayout(); validPixmap = QIcon::fromTheme(QLatin1String("dialog-ok-apply")).pixmap(22); invalidPixmap = QIcon::fromTheme(QLatin1String("dialog-cancel")).pixmap(22); QLabel *label = new QLabel(i18n("Your position:")); layout->addWidget(label); connect(this, &DvbSConfigObject::setUsalsVisible, label, &QLabel::setVisible); label = new QLabel(i18n("Latitude:")); layout->addWidget(label, 1, 0); connect(this, &DvbSConfigObject::setUsalsVisible, label, &QLabel::setVisible); label = new QLabel(i18n("[S -90 ... 90 N]")); layout->addWidget(label, 1, 1); connect(this, &DvbSConfigObject::setUsalsVisible, label, &QLabel::setVisible); latitudeEdit = new QLineEdit(parent); latitudeEdit->setText(QString::number(lnbConfig->latitude, 'g', 10)); connect(latitudeEdit, SIGNAL(textChanged(QString)), this, SLOT(latitudeChanged(QString))); layout->addWidget(latitudeEdit, 1, 2); connect(this, &DvbSConfigObject::setUsalsVisible, latitudeEdit, &QLineEdit::setVisible); latitudeValidLabel = new QLabel(parent); latitudeValidLabel->setPixmap(validPixmap); layout->addWidget(latitudeValidLabel, 1, 3); connect(this, &DvbSConfigObject::setUsalsVisible, latitudeValidLabel, &QLineEdit::setVisible); label = new QLabel(i18n("Longitude:")); layout->addWidget(label, 2, 0); connect(this, &DvbSConfigObject::setUsalsVisible, label, &QLabel::setVisible); label = new QLabel(i18n("[W -180 ... 180 E]")); layout->addWidget(label, 2, 1); connect(this, &DvbSConfigObject::setUsalsVisible, label, &QLabel::setVisible); longitudeEdit = new QLineEdit(parent); longitudeEdit->setText(QString::number(lnbConfig->longitude, 'g', 10)); connect(this, SIGNAL(setUsalsVisible(bool)), longitudeEdit, SLOT(setVisible(bool))); connect(this, &DvbSConfigObject::setUsalsVisible, longitudeEdit, &QLineEdit::setVisible); layout->addWidget(longitudeEdit, 2, 2); longitudeValidLabel = new QLabel(parent); longitudeValidLabel->setPixmap(validPixmap); layout->addWidget(longitudeValidLabel, 2, 3); boxLayout->addLayout(layout); connect(this, &DvbSConfigObject::setUsalsVisible, longitudeValidLabel, &QLineEdit::setVisible); configChanged(configBox->currentIndex()); connect(parent, SIGNAL(resetConfig()), this, SLOT(resetConfig())); } DvbSConfigObject::~DvbSConfigObject() { delete lnbConfig; } void DvbSConfigObject::latitudeChanged(const QString &text) { bool ok; toLatitude(text, &ok); if (ok) { latitudeValidLabel->setPixmap(validPixmap); } else { latitudeValidLabel->setPixmap(invalidPixmap); } } void DvbSConfigObject::longitudeChanged(const QString &text) { bool ok; toLongitude(text, &ok); if (ok) { longitudeValidLabel->setPixmap(validPixmap); } else { longitudeValidLabel->setPixmap(invalidPixmap); } } double DvbSConfigObject::toLatitude(const QString &text, bool *ok) { if (text.isEmpty()) { *ok = true; return 0; } double value = text.toDouble(ok); if (qAbs(value) > 90) { *ok = false; } return value; } double DvbSConfigObject::toLongitude(const QString &text, bool *ok) { if (text.isEmpty()) { *ok = true; return 0; } double value = text.toDouble(ok); if (qAbs(value) > 180) { *ok = false; } return value; } void DvbSConfigObject::appendConfigs(QList &list) { int index = configBox->currentIndex(); if (index == 0 || index == 3) { // Diseqc switch or No Diseqc if (index == 3) diseqcConfigs[0]->configuration = DvbConfigBase::NoDiseqc; else diseqcConfigs[0]->configuration = DvbConfigBase::DiseqcSwitch; list += diseqcConfigs; } else if ((index == 1) || (index == 2)) { // Usals rotor / Positions rotor for (int i = 0;; ++i) { QTreeWidgetItem *item = satelliteView->topLevelItem(i); if (item == NULL) { break; } QString satellite = item->text(0); DvbConfigBase *config = new DvbConfigBase(*lnbConfig); config->name = satellite; config->scanSource = satellite; if (index == 1) { // USALS rotor config->configuration = DvbConfigBase::UsalsRotor; bool latitudeOk; bool longitudeOk; double latitude = toLatitude(latitudeEdit->text(), &latitudeOk); double longitude = toLongitude(longitudeEdit->text(), &longitudeOk); if (latitudeOk && longitudeOk) { config->latitude = latitude; config->longitude = longitude; } } else { // Positions rotor config->configuration = DvbConfigBase::PositionsRotor; config->lnbNumber = item->text(1).toInt(); } list.append(DvbConfig(config)); } } } void DvbSConfigObject::configChanged(int index) { if (index == 0) { // Diseqc switch emit setDiseqcVisible(true); emit setFirstLnbVisible(true); emit setRotorVisible(false); emit setUsalsVisible(false); emit setPositionsVisible(false); } else if (index == 1) { // Usals rotor satelliteView->hideColumn(1); emit setDiseqcVisible(false); emit setFirstLnbVisible(false); emit setRotorVisible(true); emit setUsalsVisible(true); emit setPositionsVisible(false); } else if (index == 2) { // Positions rotor if (satelliteView->isColumnHidden(1)) { int width = satelliteView->columnWidth(0); satelliteView->showColumn(1); satelliteView->setColumnWidth(0, width / 2); } emit setDiseqcVisible(false); emit setFirstLnbVisible(false); emit setRotorVisible(true); emit setUsalsVisible(false); emit setPositionsVisible(true); } else if (index == 3) { // No Diseqc switch emit setDiseqcVisible(false); emit setFirstLnbVisible(true); emit setRotorVisible(false); emit setUsalsVisible(false); emit setPositionsVisible(false); } } void DvbSConfigObject::addSatellite() { QString satellite = sourceBox->currentText(); QString index = rotorSpinBox->text(); QStringList stringList = QStringList() << satellite << index; if (configBox->currentIndex() == 1) { // USALS rotor if (satelliteView->findItems(satellite, Qt::MatchExactly).isEmpty()) { satelliteView->addTopLevelItem(new QTreeWidgetItem(stringList)); } } else { // Positions rotor QList items = satelliteView->findItems(index, Qt::MatchExactly, 1); if (!items.isEmpty()) { items.at(0)->setText(0, sourceBox->currentText()); } else { satelliteView->addTopLevelItem(new QTreeWidgetItem(stringList)); } } } void DvbSConfigObject::removeSatellite() { qDeleteAll(satelliteView->selectedItems()); } void DvbSConfigObject::resetConfig() { timeoutBox->setValue(1500); higherVoltageBox->setChecked(Qt::PartiallyChecked); configBox->setCurrentIndex(0); for (int i = 0; i < lnbConfigs.size(); ++i) { lnbConfigs[i]->resetConfig(); } satelliteView->clear(); } DvbConfigBase *DvbSConfigObject::createConfig(int lnbNumber) { DvbConfigBase *config = new DvbConfigBase(DvbConfigBase::DvbS); config->timeout = 1500; config->higherVoltage = Qt::PartiallyChecked; config->configuration = DvbConfigBase::DiseqcSwitch; config->lnbNumber = lnbNumber; config->currentLnb = device->getLnbSatModels().at(0); config->bpf = 0; return config; } DvbSLnbConfigObject::DvbSLnbConfigObject(QSpinBox *timeoutSpinBox, QCheckBox *higherVoltageBox, QComboBox *sourceBox_, QPushButton *configureButton_, DvbConfigBase *config_, DvbDevice *device_) : QObject(timeoutSpinBox), sourceBox(sourceBox_), configureButton(configureButton_), config(config_), device(device_) { connect(timeoutSpinBox, SIGNAL(valueChanged(int)), this, SLOT(timeoutChanged(int))); connect(higherVoltageBox, SIGNAL(stateChanged(int)), this, SLOT(higherVoltageChanged(int))); connect(configureButton, SIGNAL(clicked()), this, SLOT(configure())); if (sourceBox != NULL) { connect(sourceBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceChanged(int))); sourceChanged(sourceBox->currentIndex()); } timeoutChanged(timeoutSpinBox->value()); } DvbSLnbConfigObject::~DvbSLnbConfigObject() { } void DvbSLnbConfigObject::resetConfig() { config->currentLnb = device->getLnbSatModels().at(0); config->bpf = 0; if (sourceBox != NULL) { sourceBox->setCurrentIndex(0); } } void DvbSLnbConfigObject::timeoutChanged(int value) { config->timeout = value; } void DvbSLnbConfigObject::higherVoltageChanged(int value) { config->higherVoltage = value; } void DvbSLnbConfigObject::sourceChanged(int index) { if (index <= 0) { // no source selected configureButton->setEnabled(false); config->name.clear(); config->scanSource.clear(); } else { configureButton->setEnabled(true); config->name = sourceBox->currentText(); config->scanSource = config->name; } } void DvbSLnbConfigObject::configure() { QVBoxLayout *mainLayout = new QVBoxLayout(); dialog = new QDialog(configureButton); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("LNB Settings")); dialog->setLayout(mainLayout); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::accepted, this, &DvbSLnbConfigObject::dialogAccepted); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); QWidget *mainWidget = new QWidget(dialog); QGridLayout *gridLayout = new QGridLayout(mainWidget); mainLayout->addWidget(mainWidget); lnbSelectionGroup = new QButtonGroup(mainWidget); lnbSelectionGroup->setExclusive(true); connect(lnbSelectionGroup, SIGNAL(buttonClicked(int)), this, SLOT(selectType(int))); int i, size = device->getLnbSatModels().size(); currentType = -1; for (i = 0; i < size; i++) { struct lnbSat lnb = device->getLnbSatModels().at(i); QRadioButton *radioButton = new QRadioButton(i18nd("libdvbv5", lnb.name.toLocal8Bit().constData()), mainWidget); mainLayout->addWidget(radioButton); lnbSelectionGroup->addButton(radioButton, i + 1); gridLayout->addWidget(radioButton, i % ((size + 1) / 2), i / ((size + 1) / 2)); if (config->currentLnb.alias.isEmpty() || config->currentLnb.alias == lnb.alias) { radioButton->setChecked(true); config->currentLnb = lnb; currentType = i + 1; } } // shouldn't happen, except if the config file has an invalid LNBf if (currentType < 0) { config->currentLnb = device->getLnbSatModels().at(0); currentType = 1; } QFrame *frame = new QFrame(mainWidget); frame->setFrameShape(QFrame::VLine); gridLayout->addWidget(frame, 0, 3, 6, 1); // FIXME: Those are actually the IF frequencies lowBandLabel = new QLabel(i18n("Low frequency (KHz)"), mainWidget); gridLayout->addWidget(lowBandLabel, 0, 4); lowBandSpinBox = new QSpinBox(mainWidget); gridLayout->addWidget(lowBandSpinBox, 0, 5); lowBandSpinBox->setRange(0, 15000); lowBandSpinBox->setValue(config->currentLnb.lowFreq); lowBandSpinBox->setEnabled(false); highBandLabel = new QLabel(i18n("High frequency (MHz)"), mainWidget); gridLayout->addWidget(highBandLabel, 1, 4); highBandSpinBox = new QSpinBox(mainWidget); gridLayout->addWidget(highBandSpinBox, 1, 5); highBandSpinBox->setRange(0, 15000); highBandSpinBox->setValue(config->currentLnb.highFreq); highBandSpinBox->setEnabled(false); switchLabel = new QLabel(i18n("Switch frequency (MHz)"), mainWidget); gridLayout->addWidget(switchLabel, 2, 4); switchSpinBox = new QSpinBox(mainWidget); gridLayout->addWidget(switchSpinBox, 2, 5); switchSpinBox->setRange(0, 15000); switchSpinBox->setValue(config->currentLnb.rangeSwitch); switchSpinBox->setEnabled(false); lowRangeLabel = new QLabel(i18n("Low range: %1 MHz to %2 MHz", config->currentLnb.freqRange[0].low, config->currentLnb.freqRange[0].high), mainWidget); gridLayout->addWidget(lowRangeLabel, 3, 4, 1, 2); highRangeLabel = new QLabel(mainWidget); gridLayout->addWidget(highRangeLabel, 4, 4, 1, 2); selectType(currentType); mainLayout->addWidget(buttonBox); dialog->setModal(true); dialog->show(); } void DvbSLnbConfigObject::selectType(int type) { struct lnbSat lnb = device->getLnbSatModels().at(type - 1); lowBandSpinBox->setValue(lnb.lowFreq); if (!lnb.lowFreq) { lowBandLabel->hide(); lowBandSpinBox->hide(); } else { lowBandLabel->show(); lowBandSpinBox->show(); } highBandSpinBox->setValue(lnb.highFreq); if (!lnb.highFreq) { highBandLabel->hide(); highBandSpinBox->hide(); } else { highBandLabel->show(); highBandSpinBox->show(); } switchSpinBox->setValue(lnb.rangeSwitch); if (!lnb.rangeSwitch) { switchLabel->hide(); switchSpinBox->hide(); } else { switchLabel->show(); switchSpinBox->show(); } lowRangeLabel->setText(i18n("Low range: %1 MHz to %2 MHz", lnb.freqRange[0].low, lnb.freqRange[0].high)); if (!lnb.freqRange[1].high) { if (!lnb.highFreq) { highRangeLabel->hide(); } else { highRangeLabel->setText(i18n("Bandstacked")); highRangeLabel->show(); } } else { highRangeLabel->setText(i18n("High range: %1 MHz to %2 MHz", lnb.freqRange[1].low, lnb.freqRange[1].high)); highRangeLabel->show(); } currentType = type; } void DvbSLnbConfigObject::dialogAccepted() { config->currentLnb = device->getLnbSatModels().at(currentType - 1); qCDebug(logDvb) << "Selected LNBf:" << config->currentLnb.alias; dialog->accept(); } diff --git a/src/dvb/dvbconfigdialog.h b/src/dvb/dvbconfigdialog.h index a18c26b..f46de8d 100644 --- a/src/dvb/dvbconfigdialog.h +++ b/src/dvb/dvbconfigdialog.h @@ -1,290 +1,291 @@ /* * dvbconfigdialog.h * * Copyright (C) 2007-2011 Christoph Pfister * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef DVBCONFIGDIALOG_H #define DVBCONFIGDIALOG_H #include #include #include #include class QBoxLayout; class QButtonGroup; class QCheckBox; class QGridLayout; class QLabel; class QProgressBar; class QSpinBox; class QTreeWidget; class QComboBox; class QComboBox; class KJob; class QLineEdit; class QTabWidget; class QDialogButtonBox; namespace KIO { class Job; class TransferJob; } class DvbConfig; class DvbConfigBase; class DvbConfigPage; class DvbDeviceConfig; class DvbManager; class DvbDevice; class DvbSConfigObject; class DvbSLnbConfigObject; class RegexInputLine : public QObject { Q_OBJECT public: int index; QLineEdit *lineEdit; QSpinBox *spinBox; QCheckBox *checkBox; }; class DvbConfigDialog : public QDialog { Q_OBJECT public: DvbConfigDialog(DvbManager *manager_, QWidget *parent); ~DvbConfigDialog(); signals: void removeRegex(DvbConfigPage *page); private slots: void changeRecordingFolder(); void changeTimeShiftFolder(); void changeXmltvFileName(); void updateScanFile(); void openScanFile(); void newRegex(); void removeRegex(); void namingFormatChanged(QString text); void moveLeft(DvbConfigPage *configPage); void moveRight(DvbConfigPage *configPage); void remove(DvbConfigPage *configPage); private: void removeWidgets(QGridLayout *layout, int row, int column, bool deleteWidgets); void initRegexButtons(QGridLayout *buttonGrid); //void deleteChildWidgets(QLayoutItem *item); void accept(); DvbManager *manager; QTabWidget *tabWidget; QLineEdit *recordingFolderEdit; QLineEdit *timeShiftFolderEdit; QLineEdit *xmltvFileNameEdit; QSpinBox *beginMarginBox; QSpinBox *endMarginBox; QLineEdit *namingFormat; QCheckBox *override6937CharsetBox; QCheckBox *createInfoFileBox; + QCheckBox *disableEpgBox; QCheckBox *scanWhenIdleBox; QPixmap validPixmap; QPixmap invalidPixmap; QLabel *namingFormatValidLabel; QList configPages; QLineEdit *actionAfterRecordingLineEdit; QGridLayout *regexGrid; QList regexInputList; }; class DvbScanFileDownloadDialog : public QDialog { Q_OBJECT public: DvbScanFileDownloadDialog(DvbManager *manager_, QWidget *parent); ~DvbScanFileDownloadDialog(); private slots: void progressChanged(KJob *, unsigned long percent); void dataArrived(KIO::Job *, const QByteArray &data); void jobFinished(); private: DvbManager *manager; QProgressBar *progressBar; QLabel *label; KIO::TransferJob *job; QByteArray scanData; QVBoxLayout *mainLayout; QDialogButtonBox *buttonBox; }; class DvbConfigPage : public QWidget { Q_OBJECT public: DvbConfigPage(QWidget *parent, DvbManager *manager, const DvbDeviceConfig *deviceConfig_); ~DvbConfigPage(); void setMoveLeftEnabled(bool enabled); void setMoveRightEnabled(bool enabled); const DvbDeviceConfig *getDeviceConfig() const; QList getConfigs(); signals: void moveLeft(DvbConfigPage *page); void moveRight(DvbConfigPage *page); void remove(DvbConfigPage *page); void resetConfig(); private slots: void moveLeft(); void moveRight(); void removeConfig(); private: void addHSeparator(const QString &title); const DvbDeviceConfig *deviceConfig; QBoxLayout *boxLayout; QPushButton *moveLeftButton; QPushButton *moveRightButton; QList configs; DvbSConfigObject *dvbSObject; }; class DvbConfigObject : public QObject { Q_OBJECT public: DvbConfigObject(QWidget *parent, QBoxLayout *layout, DvbManager *manager, DvbConfigBase *config_, bool isGen2); ~DvbConfigObject(); private slots: void timeoutChanged(int timeout); void sourceChanged(int index); void nameChanged(); void resetConfig(); private: DvbConfigBase *config; QString defaultName; QSpinBox *timeoutBox; QComboBox *sourceBox; QLineEdit *nameEdit; bool isGen2; }; class DvbSConfigObject : public QObject { Q_OBJECT public: DvbSConfigObject(QWidget *parent_, QBoxLayout *boxLayout, DvbManager *manager, const QList &configs, DvbDevice *device, bool isGen2); ~DvbSConfigObject(); void appendConfigs(QList &list); signals: void setDiseqcVisible(bool visible); void setFirstLnbVisible(bool visible); void setRotorVisible(bool visible); // common parts of usals / positions ui void setUsalsVisible(bool visible); // usals-specific parts of ui void setPositionsVisible(bool visible); // positions-specific parts of ui private slots: void latitudeChanged(const QString &text); void longitudeChanged(const QString &text); void configChanged(int index); void addSatellite(); void removeSatellite(); void resetConfig(); private: DvbConfigBase *createConfig(int lnbNumber); static double toLatitude(const QString &text, bool *ok); static double toLongitude(const QString &text, bool *ok); QWidget *parent; DvbDevice *device; DvbConfigBase *lnbConfig; QList diseqcConfigs; QList lnbConfigs; QStringList sources; QGridLayout *layout; QSpinBox *timeoutBox; QCheckBox *higherVoltageBox; QComboBox *configBox; QComboBox *sourceBox; QSpinBox *rotorSpinBox; QTreeWidget *satelliteView; QPixmap validPixmap; QPixmap invalidPixmap; QLabel *latitudeValidLabel; QLabel *longitudeValidLabel; QLineEdit *latitudeEdit; QLineEdit *longitudeEdit; }; class DvbSLnbConfigObject : public QObject { Q_OBJECT public: DvbSLnbConfigObject(QSpinBox *timeoutSpinBox, QCheckBox *higherVoltageBox, QComboBox *sourceBox_, QPushButton *configureButton_, DvbConfigBase *config_, DvbDevice *device_); ~DvbSLnbConfigObject(); void resetConfig(); private slots: void timeoutChanged(int value); void higherVoltageChanged(int value); void sourceChanged(int index); void configure(); void selectType(int type); void dialogAccepted(); private: QComboBox *sourceBox; QPushButton *configureButton; DvbConfigBase *config; DvbDevice *device; QDialog *dialog; QButtonGroup *lnbSelectionGroup; QLabel *lowBandLabel; QLabel *switchLabel; QLabel *highBandLabel; QLabel *lowRangeLabel; QLabel *highRangeLabel; QSpinBox *lowBandSpinBox; QSpinBox *switchSpinBox; QSpinBox *highBandSpinBox; int currentType; }; #endif /* DVBCONFIGDIALOG_H */ diff --git a/src/dvb/dvbepg.cpp b/src/dvb/dvbepg.cpp index 60b14f5..e466ac9 100644 --- a/src/dvb/dvbepg.cpp +++ b/src/dvb/dvbepg.cpp @@ -1,1325 +1,1328 @@ /* * dvbepg.cpp * * Copyright (C) 2009-2011 Christoph Pfister * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include "../ensurenopendingoperation.h" #include "../iso-codes.h" #include "dvbdevice.h" #include "dvbepg.h" #include "dvbepg_p.h" #include "dvbmanager.h" #include "dvbsi.h" bool DvbEpgEntry::validate() const { if (channel.isValid() && begin.isValid() && (begin.timeSpec() == Qt::UTC) && duration.isValid()) { return true; } return false; } bool DvbEpgEntryId::operator<(const DvbEpgEntryId &other) const { if (entry->channel != other.entry->channel) { return (entry->channel < other.entry->channel); } if (entry->begin != other.entry->begin) { return (entry->begin < other.entry->begin); } return false; } DvbEpgModel::DvbEpgModel(DvbManager *manager_, QObject *parent) : QObject(parent), manager(manager_), hasPendingOperation(false) { currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); startTimer(54000); DvbChannelModel *channelModel = manager->getChannelModel(); connect(channelModel, SIGNAL(channelAboutToBeUpdated(DvbSharedChannel)), this, SLOT(channelAboutToBeUpdated(DvbSharedChannel))); connect(channelModel, SIGNAL(channelUpdated(DvbSharedChannel)), this, SLOT(channelUpdated(DvbSharedChannel))); connect(channelModel, SIGNAL(channelRemoved(DvbSharedChannel)), this, SLOT(channelRemoved(DvbSharedChannel))); connect(manager->getRecordingModel(), SIGNAL(recordingRemoved(DvbSharedRecording)), this, SLOT(recordingRemoved(DvbSharedRecording))); // TODO use SQL to store epg data QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); if (!file.open(QIODevice::ReadOnly)) { qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); return; } QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); DvbRecordingModel *recordingModel = manager->getRecordingModel(); bool hasRecordingKey = true, hasParental = true, hasMultilang = true; int version; stream >> version; if (version == 0x1ce0eca7) { hasRecordingKey = false; } else if (version == 0x79cffd36) { hasParental = false; } else if (version == 0x140c37b5) { hasMultilang = false; } else if (version != 0x20171112) { qCWarning(logEpg, "Wrong DB version for: %s", qPrintable(file.fileName())); return; } while (!stream.atEnd()) { DvbEpgEntry entry; QString channelName; stream >> channelName; entry.channel = channelModel->findChannelByName(channelName); stream >> entry.begin; entry.begin = entry.begin.toUTC(); stream >> entry.duration; if (hasMultilang) { int i, count; stream >> count; for (i = 0; i < count; i++) { QString code; DvbEpgLangEntry langEntry; stream >> code; stream >> langEntry.title; stream >> langEntry.subheading; stream >> langEntry.details; entry.langEntry[code] = langEntry; if (!langEntry.title.isEmpty() && !manager->languageCodes.contains(code)) manager->languageCodes[code] = true; } } else { DvbEpgLangEntry langEntry; stream >> langEntry.title; stream >> langEntry.subheading; stream >> langEntry.details; entry.langEntry[FIRST_LANG] = langEntry; } if (hasRecordingKey) { SqlKey recordingKey; stream >> recordingKey.sqlKey; if (recordingKey.isSqlKeyValid()) { entry.recording = recordingModel->findRecordingByKey(recordingKey); } } if (hasParental) { unsigned type; stream >> type; stream >> entry.content; stream >> entry.parental; if (type <= DvbEpgEntry::EitLast) entry.type = DvbEpgEntry::EitType(type); else entry.type = DvbEpgEntry::EitActualTsSchedule; } if (stream.status() != QDataStream::Ok) { qCWarning(logEpg, "Corrupt data %s", qPrintable(file.fileName())); break; } addEntry(entry); } } DvbEpgModel::~DvbEpgModel() { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); } if (!dvbEpgFilters.isEmpty() || !atscEpgFilters.isEmpty()) { qCWarning(logEpg, "filter list not empty"); } QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/epgdata.dvb")); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(logEpg, "Cannot open %s", qPrintable(file.fileName())); return; } QDataStream stream(&file); stream.setVersion(QDataStream::Qt_4_4); int version = 0x20171112; stream << version; foreach (const DvbSharedEpgEntry &entry, entries) { SqlKey recordingKey; if (entry->recording.isValid()) { recordingKey = *entry->recording; } stream << entry->channel->name; stream << entry->begin; stream << entry->duration; stream << entry->langEntry.size(); QHashIterator i(entry->langEntry); while (i.hasNext()) { i.next(); stream << i.key(); DvbEpgLangEntry langEntry = i.value(); stream << langEntry.title; stream << langEntry.subheading; stream << langEntry.details; } stream << recordingKey.sqlKey; stream << int(entry->type); stream << entry->content; stream << entry->parental; } } QMap DvbEpgModel::getRecordings() const { return recordings; } void DvbEpgModel::setRecordings(const QMap map) { recordings = map; } QMap DvbEpgModel::getEntries() const { return entries; } QHash DvbEpgModel::getEpgChannels() const { return epgChannels; } QList DvbEpgModel::getCurrentNext(const DvbSharedChannel &channel) const { QList result; DvbEpgEntry fakeEntry(channel); for (ConstIterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); it != entries.constEnd(); ++it) { const DvbSharedEpgEntry &entry = *it; if (entry->channel != channel) { break; } result.append(entry); if (result.size() == 2) { break; } } return result; } void DvbEpgModel::Debug(QString text, const DvbSharedEpgEntry &entry) { if (!QLoggingCategory::defaultCategory()->isEnabled(QtDebugMsg)) return; QDateTime begin = entry->begin.toLocalTime(); QTime end = entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)).toLocalTime().time(); qCDebug(logEpg, "event %s: type %d, from %s to %s: %s: %s: %s : %s", qPrintable(text), entry->type, qPrintable(QLocale().toString(begin, QLocale::ShortFormat)), qPrintable(QLocale().toString(end)), qPrintable(entry->title()), qPrintable(entry->subheading()), qPrintable(entry->details()), qPrintable(entry->content)); } DvbSharedEpgEntry DvbEpgModel::addEntry(const DvbEpgEntry &entry) { if (!entry.validate()) { qCWarning(logEpg, "Invalid entry: channel is %s, begin is %s, duration is %s", entry.channel.isValid() ? "valid" : "invalid", entry.begin.isValid() ? "valid" : "invalid", entry.duration.isValid() ? "valid" : "invalid"); return DvbSharedEpgEntry(); } if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return DvbSharedEpgEntry(); } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); // Check if the event was already recorded const QDateTime end = entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)); // Optimize duplicated register logic by using find, with is O(log n) Iterator it = entries.find(DvbEpgEntryId(&entry)); while (it != entries.end()) { const DvbSharedEpgEntry &existingEntry = *it; // Don't do anything if the event already exists if (*existingEntry == entry) return DvbSharedEpgEntry(); const QDateTime enEnd = existingEntry->begin.addSecs(QTime(0, 0, 0).secsTo(existingEntry->duration)); // The logic here was simplified due to performance. // It won't check anymore if an event has its start time // switched, as that would require a O(n) loop, with is // too slow, specially on DVB-S/S2. So, we're letting the QMap // to use a key with just channel/begin time, identifying // obsolete entries only if the end time doesn't match. // A new event conflicts with an existing one if (end != enEnd) { Debug("removed", existingEntry); it = removeEntry(it); break; } // New event data for the same event if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) { emit entryAboutToBeUpdated(existingEntry); QHashIterator i(entry.langEntry); while (i.hasNext()) { i.next(); DvbEpgLangEntry langEntry = i.value(); const_cast(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; } emit entryUpdated(existingEntry); Debug("updated", existingEntry); } return existingEntry; } if (entry.begin.addSecs(QTime(0, 0, 0).secsTo(entry.duration)) > currentDateTimeUtc) { DvbSharedEpgEntry existingEntry = entries.value(DvbEpgEntryId(&entry)); if (existingEntry.isValid()) { if (existingEntry->details(FIRST_LANG).isEmpty() && !entry.details(FIRST_LANG).isEmpty()) { // needed for atsc emit entryAboutToBeUpdated(existingEntry); QHashIterator i(entry.langEntry); while (i.hasNext()) { i.next(); DvbEpgLangEntry langEntry = i.value(); const_cast(existingEntry.constData())->langEntry[i.key()].details = langEntry.details; } emit entryUpdated(existingEntry); Debug("updated2", existingEntry); } return existingEntry; } DvbSharedEpgEntry newEntry(new DvbEpgEntry(entry)); entries.insert(DvbEpgEntryId(newEntry), newEntry); if (newEntry->recording.isValid()) { recordings.insert(newEntry->recording, newEntry); } if (++epgChannels[newEntry->channel] == 1) { emit epgChannelAdded(newEntry->channel); } emit entryAdded(newEntry); Debug("new", newEntry); return newEntry; } return DvbSharedEpgEntry(); } void DvbEpgModel::scheduleProgram(const DvbSharedEpgEntry &entry, int extraSecondsBefore, int extraSecondsAfter, bool checkForRecursion, int priority) { if (!entry.isValid() || (entries.value(DvbEpgEntryId(entry)) != entry)) { qCWarning(logEpg, "Can't schedule program: invalid entry"); return; } if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); emit entryAboutToBeUpdated(entry); DvbSharedRecording oldRecording; if (!entry->recording.isValid()) { DvbRecording recording; recording.priority = priority; recording.name = entry->title(manager->currentEpgLanguage); recording.channel = entry->channel; recording.begin = entry->begin.addSecs(-extraSecondsBefore); recording.beginEPG = entry->begin; recording.duration = entry->duration.addSecs(extraSecondsBefore + extraSecondsAfter); recording.durationEPG = entry->duration; recording.subheading = entry->subheading(manager->currentEpgLanguage); recording.details = entry->details(manager->currentEpgLanguage); recording.disabled = false; const_cast(entry.constData())->recording = manager->getRecordingModel()->addRecording(recording, checkForRecursion); recordings.insert(entry->recording, entry); } else { oldRecording = entry->recording; recordings.remove(entry->recording); const_cast(entry.constData())->recording = DvbSharedRecording(); } emit entryUpdated(entry); if (oldRecording.isValid()) { // recordingRemoved() will be called hasPendingOperation = false; manager->getRecordingModel()->removeRecording(oldRecording); } } void DvbEpgModel::startEventFilter(DvbDevice *device, const DvbSharedChannel &channel) { + if (manager->disableEpg()) + return; + switch (channel->transponder.getTransmissionType()) { case DvbTransponderBase::Invalid: break; case DvbTransponderBase::DvbC: case DvbTransponderBase::DvbS: case DvbTransponderBase::DvbS2: case DvbTransponderBase::DvbT: case DvbTransponderBase::DvbT2: case DvbTransponderBase::IsdbT: dvbEpgFilters.append(QExplicitlySharedDataPointer( new DvbEpgFilter(manager, device, channel))); break; case DvbTransponderBase::Atsc: atscEpgFilters.append(QExplicitlySharedDataPointer( new AtscEpgFilter(manager, device, channel))); break; } } void DvbEpgModel::stopEventFilter(DvbDevice *device, const DvbSharedChannel &channel) { switch (channel->transponder.getTransmissionType()) { case DvbTransponderBase::Invalid: break; case DvbTransponderBase::DvbC: case DvbTransponderBase::DvbS: case DvbTransponderBase::DvbS2: case DvbTransponderBase::DvbT: case DvbTransponderBase::DvbT2: case DvbTransponderBase::IsdbT: for (int i = 0; i < dvbEpgFilters.size(); ++i) { const DvbEpgFilter *epgFilter = dvbEpgFilters.at(i).constData(); if ((epgFilter->device == device) && (epgFilter->source == channel->source) && (epgFilter->transponder.corresponds(channel->transponder))) { dvbEpgFilters.removeAt(i); break; } } break; case DvbTransponderBase::Atsc: for (int i = 0; i < atscEpgFilters.size(); ++i) { const AtscEpgFilter *epgFilter = atscEpgFilters.at(i).constData(); if ((epgFilter->device == device) && (epgFilter->source == channel->source) && (epgFilter->transponder.corresponds(channel->transponder))) { atscEpgFilters.removeAt(i); break; } } break; } } void DvbEpgModel::channelAboutToBeUpdated(const DvbSharedChannel &channel) { updatingChannel = *channel; } void DvbEpgModel::channelUpdated(const DvbSharedChannel &channel) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); if (DvbChannelId(channel) != DvbChannelId(&updatingChannel)) { DvbEpgEntry fakeEntry(channel); Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { it = removeEntry(it); } } } void DvbEpgModel::channelRemoved(const DvbSharedChannel &channel) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); DvbEpgEntry fakeEntry(channel); Iterator it = entries.lowerBound(DvbEpgEntryId(&fakeEntry)); while ((ConstIterator(it) != entries.constEnd()) && ((*it)->channel == channel)) { it = removeEntry(it); } } void DvbEpgModel::recordingRemoved(const DvbSharedRecording &recording) { if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); DvbSharedEpgEntry entry = recordings.take(recording); if (entry.isValid()) { emit entryAboutToBeUpdated(entry); const_cast(entry.constData())->recording = DvbSharedRecording(); emit entryUpdated(entry); } } void DvbEpgModel::timerEvent(QTimerEvent *event) { Q_UNUSED(event) if (hasPendingOperation) { qCWarning(logEpg, "Illegal recursive call"); return; } EnsureNoPendingOperation ensureNoPendingOperation(hasPendingOperation); currentDateTimeUtc = QDateTime::currentDateTime().toUTC(); Iterator it = entries.begin(); while (ConstIterator(it) != entries.constEnd()) { const DvbSharedEpgEntry &entry = *it; if (entry->begin.addSecs(QTime(0, 0, 0).secsTo(entry->duration)) > currentDateTimeUtc) { ++it; } else { it = removeEntry(it); } } } DvbEpgModel::Iterator DvbEpgModel::removeEntry(Iterator it) { const DvbSharedEpgEntry &entry = *it; if (entry->recording.isValid()) { recordings.remove(entry->recording); } if (--epgChannels[entry->channel] == 0) { epgChannels.remove(entry->channel); emit epgChannelRemoved(entry->channel); } emit entryRemoved(entry); return entries.erase(it); } DvbEpgFilter::DvbEpgFilter(DvbManager *manager_, DvbDevice *device_, const DvbSharedChannel &channel) : device(device_) { manager = manager_; source = channel->source; transponder = channel->transponder; device->addSectionFilter(0x12, this); channelModel = manager->getChannelModel(); epgModel = manager->getEpgModel(); } DvbEpgFilter::~DvbEpgFilter() { device->removeSectionFilter(0x12, this); } QTime DvbEpgFilter::bcdToTime(int bcd) { return QTime(((bcd >> 20) & 0x0f) * 10 + ((bcd >> 16) & 0x0f), ((bcd >> 12) & 0x0f) * 10 + ((bcd >> 8) & 0x0f), ((bcd >> 4) & 0x0f) * 10 + (bcd & 0x0f)); } static const QByteArray contentStr[16][16] = { [0] = {}, [1] = { /* Movie/Drama */ {}, {I18N_NOOP("Detective")}, {I18N_NOOP("Adventure")}, {I18N_NOOP("Science Fiction")}, {I18N_NOOP("Comedy")}, {I18N_NOOP("Soap")}, {I18N_NOOP("Romance")}, {I18N_NOOP("Classical")}, {I18N_NOOP("Adult")}, {I18N_NOOP("User defined")}, }, [2] = { /* News/Current affairs */ {}, {I18N_NOOP("Weather")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Documentary")}, {I18N_NOOP("Discussion")}, {I18N_NOOP("User Defined")}, }, [3] = { /* Show/Game show */ {}, {I18N_NOOP("Quiz")}, {I18N_NOOP("Variety")}, {I18N_NOOP("Talk")}, {I18N_NOOP("User Defined")}, }, [4] = { /* Sports */ {}, {I18N_NOOP("Events")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Football")}, {I18N_NOOP("Tennis")}, {I18N_NOOP("Team")}, {I18N_NOOP("Athletics")}, {I18N_NOOP("Motor")}, {I18N_NOOP("Water")}, {I18N_NOOP("Winter")}, {I18N_NOOP("Equestrian")}, {I18N_NOOP("Martial")}, {I18N_NOOP("User Defined")}, }, [5] = { /* Children's/Youth */ {}, {I18N_NOOP("Preschool")}, {I18N_NOOP("06 to 14")}, {I18N_NOOP("10 to 16")}, {I18N_NOOP("Educational")}, {I18N_NOOP("Cartoons")}, {I18N_NOOP("User Defined")}, }, [6] = { /* Music/Ballet/Dance */ {}, {I18N_NOOP("Poprock")}, {I18N_NOOP("Classical")}, {I18N_NOOP("Folk")}, {I18N_NOOP("Jazz")}, {I18N_NOOP("Opera")}, {I18N_NOOP("Ballet")}, {I18N_NOOP("User Defined")}, }, [7] = { /* Arts/Culture */ {}, {I18N_NOOP("Performance")}, {I18N_NOOP("Fine Arts")}, {I18N_NOOP("Religion")}, {I18N_NOOP("Traditional")}, {I18N_NOOP("Literature")}, {I18N_NOOP("Cinema")}, {I18N_NOOP("Experimental")}, {I18N_NOOP("Press")}, {I18N_NOOP("New Media")}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Fashion")}, {I18N_NOOP("User Defined")}, }, [8] = { /* Social/Political/Economics */ {}, {I18N_NOOP("Magazine")}, {I18N_NOOP("Advisory")}, {I18N_NOOP("People")}, {I18N_NOOP("User Defined")}, }, [9] = { /* Education/Science/Factual */ {}, {I18N_NOOP("Nature")}, {I18N_NOOP("Technology")}, {I18N_NOOP("Medicine")}, {I18N_NOOP("Foreign")}, {I18N_NOOP("Social")}, {I18N_NOOP("Further")}, {I18N_NOOP("Language")}, {I18N_NOOP("User Defined")}, }, [10] = { /* Leisure/Hobbies */ {}, {I18N_NOOP("Travel")}, {I18N_NOOP("Handicraft")}, {I18N_NOOP("Motoring")}, {I18N_NOOP("Fitness")}, {I18N_NOOP("Cooking")}, {I18N_NOOP("Shopping")}, {I18N_NOOP("Gardening")}, {I18N_NOOP("User Defined")}, }, [11] = { /* Special characteristics */ {I18N_NOOP("Original Language")}, {I18N_NOOP("Black and White ")}, {I18N_NOOP("Unpublished")}, {I18N_NOOP("Live")}, {I18N_NOOP("Planostereoscopic")}, {I18N_NOOP("User Defined")}, {I18N_NOOP("User Defined 1")}, {I18N_NOOP("User Defined 2")}, {I18N_NOOP("User Defined 3")}, {I18N_NOOP("User Defined 4")} } }; static const QByteArray nibble1Str[16] = { [0] = {I18N_NOOP("Undefined")}, [1] = {I18N_NOOP("Movie")}, [2] = {I18N_NOOP("News")}, [3] = {I18N_NOOP("Show")}, [4] = {I18N_NOOP("Sports")}, [5] = {I18N_NOOP("Children")}, [6] = {I18N_NOOP("Music")}, [7] = {I18N_NOOP("Culture")}, [8] = {I18N_NOOP("Social")}, [9] = {I18N_NOOP("Education")}, [10] = {I18N_NOOP("Leisure")}, [11] = {I18N_NOOP("Special")}, [12] = {I18N_NOOP("Reserved")}, [13] = {I18N_NOOP("Reserved")}, [14] = {I18N_NOOP("Reserved")}, [15] = {I18N_NOOP("User defined")}, }; static const QByteArray braNibble1Str[16] = { [0] = {I18N_NOOP("News")}, [1] = {I18N_NOOP("Sports")}, [2] = {I18N_NOOP("Education")}, [3] = {I18N_NOOP("Soap opera")}, [4] = {I18N_NOOP("Mini-series")}, [5] = {I18N_NOOP("Series")}, [6] = {I18N_NOOP("Variety")}, [7] = {I18N_NOOP("Reality show")}, [8] = {I18N_NOOP("Information")}, [9] = {I18N_NOOP("Comical")}, [10] = {I18N_NOOP("Children")}, [11] = {I18N_NOOP("Erotic")}, [12] = {I18N_NOOP("Movie")}, [13] = {I18N_NOOP("Raffle, television sales, prizing")}, [14] = {I18N_NOOP("Debate/interview")}, [15] = {I18N_NOOP("Other")}, }; // Using the terms from the English version of NBR 15603-2:2007 // The table omits nibble2="Other", as it is better to show nibble 1 // definition instead. // when nibble2[x][0] == nibble1[x] and it has no other definition, // except for "Other", the field will be kept in blank, as the logic // will fall back to the definition at nibble 1. static QByteArray braNibble2Str[16][16] = { [0] = { {I18N_NOOP("News")}, {I18N_NOOP("Report")}, {I18N_NOOP("Documentary")}, {I18N_NOOP("Biography")}, }, [1] = {}, [2] = { {I18N_NOOP("Educative")}, }, [3] = {}, [4] = {}, [5] = {}, [6] = { {I18N_NOOP("Auditorium")}, {I18N_NOOP("Show")}, {I18N_NOOP("Musical")}, {I18N_NOOP("Making of")}, {I18N_NOOP("Feminine")}, {I18N_NOOP("Game show")}, }, [7] = {}, [8] = { {I18N_NOOP("Cooking")}, {I18N_NOOP("Fashion")}, {I18N_NOOP("Country")}, {I18N_NOOP("Health")}, {I18N_NOOP("Travel")}, }, [9] = {}, [10] = {}, [11] = {}, [12] = {}, [13] = { {I18N_NOOP("Raffle")}, {I18N_NOOP("Television sales")}, {I18N_NOOP("Prizing")}, }, [14] = { {I18N_NOOP("Discussion")}, {I18N_NOOP("Interview")}, }, [15] = { {I18N_NOOP("Adult cartoon")}, {I18N_NOOP("Interactive")}, {I18N_NOOP("Policy")}, {I18N_NOOP("Religion")}, }, }; QString DvbEpgFilter::getContent(DvbContentDescriptor &descriptor) { QString content; for (DvbEitContentEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) { const int nibble1 = entry.contentNibbleLevel1(); const int nibble2 = entry.contentNibbleLevel2(); QByteArray s; // FIXME: should do it only for ISDB-Tb (Brazilian variation), // as the Japanese variation uses the same codes as DVB if (transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { s = braNibble2Str[nibble1][nibble2]; if (s == "") s = braNibble1Str[nibble1]; if (s != "") content += i18n(s) + '\n'; } else { s = contentStr[nibble1][nibble2]; if (s == "") s = nibble1Str[nibble1]; if (s != "") content += i18n(s) + '\n'; } } if (content != "") { // xgettext:no-c-format return (i18n("Genre: %1", content)); } return content; } /* As defined at ABNT NBR 15603-2 */ static const QByteArray braRating[] = { [0] = {I18N_NOOP("reserved")}, [1] = {I18N_NOOP("all audiences")}, [2] = {I18N_NOOP("10 years")}, [3] = {I18N_NOOP("12 years")}, [4] = {I18N_NOOP("14 years")}, [5] = {I18N_NOOP("16 years")}, [6] = {I18N_NOOP("18 years")}, }; #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) QString DvbEpgFilter::getParental(DvbParentalRatingDescriptor &descriptor) { QString parental; for (DvbParentalRatingEntry entry = descriptor.contents(); entry.isValid(); entry.advance()) { QString code; code.append(QChar(entry.languageCode1())); code.append(QChar(entry.languageCode2())); code.append(QChar(entry.languageCode3())); QString country; IsoCodes::getCountry(code, &country); if (country.isEmpty()) country = code; // Rating from 0x10 to 0xff are broadcaster's specific if (entry.rating() == 0) { // xgettext:no-c-format parental += i18n("Country %1: not rated\n", country); } else if (entry.rating() < 0x10) { if (code == "BRA" && transponder.getTransmissionType() == DvbTransponderBase::IsdbT) { unsigned int rating = entry.rating(); if (rating >= ARRAY_SIZE(braRating)) rating = 0; // Reserved QString GenStr; int genre = entry.rating() >> 4; if (genre & 0x2) GenStr = i18n("violence / "); if (genre & 0x4) GenStr = i18n("sex / "); if (genre & 0x1) GenStr = i18n("drugs / "); if (genre) { GenStr.truncate(GenStr.size() - 2); GenStr = " (" + GenStr + ')'; } QString ratingStr = i18n(braRating[entry.rating()]); // xgettext:no-c-format parental += i18n("Country %1: rating: %2%3\n", country, ratingStr, GenStr); } else { // xgettext:no-c-format parental += i18n("Country %1: rating: %2 years.\n", country, entry.rating() + 3); } } } return parental; } DvbEpgLangEntry *DvbEpgFilter::getLangEntry(DvbEpgEntry &epgEntry, int code1, int code2, int code3, bool add_code, QString *code_) { DvbEpgLangEntry *langEntry; QString code; if (!code1 || code1 == 0x20) code = FIRST_LANG; else { code.append(QChar(code1)); code.append(QChar(code2)); code.append(QChar(code3)); code = code.toUpper(); } if (code_) code_ = new QString(code); if (!epgEntry.langEntry.contains(code)) { DvbEpgLangEntry e; epgEntry.langEntry.insert(code, e); if (add_code) { if (!manager->languageCodes.contains(code)) { manager->languageCodes[code] = true; emit epgModel->languageAdded(code); } } } langEntry = &epgEntry.langEntry[code]; return langEntry; } void DvbEpgFilter::processSection(const char *data, int size) { unsigned char tableId = data[0]; if ((tableId < 0x4e) || (tableId > 0x6f)) { return; } DvbEitSection eitSection(data, size); if (!eitSection.isValid()) { qCDebug(logEpg, "section is invalid"); return; } DvbChannel fakeChannel; fakeChannel.source = source; fakeChannel.transponder = transponder; fakeChannel.networkId = eitSection.originalNetworkId(); fakeChannel.transportStreamId = eitSection.transportStreamId(); fakeChannel.serviceId = eitSection.serviceId(); DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); if (!channel.isValid()) { fakeChannel.networkId = -1; channel = channelModel->findChannelById(fakeChannel); } if (!channel.isValid()) { qCDebug(logEpg, "channel invalid"); return; } if (eitSection.entries().getLength()) qCDebug(logEpg, "table 0x%02x, extension 0x%04x, session %d/%d, size %d", eitSection.tableId(), eitSection.tableIdExtension(), eitSection.sectionNumber(), eitSection.lastSectionNumber(), eitSection.entries().getLength()); for (DvbEitSectionEntry entry = eitSection.entries(); entry.isValid(); entry.advance()) { DvbEpgEntry epgEntry; DvbEpgLangEntry *langEntry; if (tableId == 0x4e) epgEntry.type = DvbEpgEntry::EitActualTsPresentFollowing; else if (tableId == 0x4f) epgEntry.type = DvbEpgEntry::EitOtherTsPresentFollowing; else if (tableId < 0x60) epgEntry.type = DvbEpgEntry::EitActualTsSchedule; else epgEntry.type = DvbEpgEntry::EitOtherTsSchedule; epgEntry.channel = channel; /* * ISDB-T Brazil uses time in UTC-3, * as defined by ABNT NBR 15603-2:2007. */ if (channel->transponder.getTransmissionType() == DvbTransponderBase::IsdbT) epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), bcdToTime(entry.startTime()), Qt::OffsetFromUTC, -10800).toUTC(); else epgEntry.begin = QDateTime(QDate::fromJulianDay(entry.startDate() + 2400001), bcdToTime(entry.startTime()), Qt::UTC); epgEntry.duration = bcdToTime(entry.duration()); for (DvbDescriptor descriptor = entry.descriptors(); descriptor.isValid(); descriptor.advance()) { switch (descriptor.descriptorTag()) { case 0x4d: { DvbShortEventDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } langEntry = getLangEntry(epgEntry, eventDescriptor.languageCode1(), eventDescriptor.languageCode2(), eventDescriptor.languageCode3()); langEntry->title += eventDescriptor.eventName(); langEntry->subheading += eventDescriptor.text(); break; } case 0x4e: { DvbExtendedEventDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } langEntry = getLangEntry(epgEntry, eventDescriptor.languageCode1(), eventDescriptor.languageCode2(), eventDescriptor.languageCode3()); langEntry->details += eventDescriptor.text(); break; } case 0x54: { DvbContentDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } epgEntry.content += getContent(eventDescriptor); break; } case 0x55: { DvbParentalRatingDescriptor eventDescriptor(descriptor); if (!eventDescriptor.isValid()) { break; } epgEntry.parental += getParental(eventDescriptor); break; } } } epgModel->addEntry(epgEntry); } } void AtscEpgMgtFilter::processSection(const char *data, int size) { epgFilter->processMgtSection(data, size); } void AtscEpgEitFilter::processSection(const char *data, int size) { epgFilter->processEitSection(data, size); } void AtscEpgEttFilter::processSection(const char *data, int size) { epgFilter->processEttSection(data, size); } AtscEpgFilter::AtscEpgFilter(DvbManager *manager, DvbDevice *device_, const DvbSharedChannel &channel) : device(device_), mgtFilter(this), eitFilter(this), ettFilter(this) { source = channel->source; transponder = channel->transponder; device->addSectionFilter(0x1ffb, &mgtFilter); channelModel = manager->getChannelModel(); epgModel = manager->getEpgModel(); } AtscEpgFilter::~AtscEpgFilter() { foreach (int pid, eitPids) { device->removeSectionFilter(pid, &eitFilter); } foreach (int pid, ettPids) { device->removeSectionFilter(pid, &ettFilter); } device->removeSectionFilter(0x1ffb, &mgtFilter); } void AtscEpgFilter::processMgtSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xc7) { return; } AtscMgtSection mgtSection(data, size); if (!mgtSection.isValid()) { return; } int entryCount = mgtSection.entryCount(); QList newEitPids; QList newEttPids; AtscMgtSectionEntry entry = mgtSection.entries(); for (int i = 0; i < entryCount; i++) { if (!entry.isValid()) break; int tableType = entry.tableType(); if ((tableType >= 0x0100) && (tableType <= 0x017f)) { int pid = entry.pid(); int index = (qLowerBound(newEitPids, pid) - newEitPids.constBegin()); if ((index >= newEitPids.size()) || (newEitPids.at(index) != pid)) { newEitPids.insert(index, pid); } } if ((tableType >= 0x0200) && (tableType <= 0x027f)) { int pid = entry.pid(); int index = (qLowerBound(newEttPids, pid) - newEttPids.constBegin()); if ((index >= newEttPids.size()) || (newEttPids.at(index) != pid)) { newEttPids.insert(index, pid); } } if (i < entryCount - 1) entry.advance(); } for (int i = 0; i < eitPids.size(); ++i) { int pid = eitPids.at(i); int index = (qBinaryFind(newEitPids, pid) - newEitPids.constBegin()); if (index < newEitPids.size()) { newEitPids.removeAt(index); } else { device->removeSectionFilter(pid, &eitFilter); eitPids.removeAt(i); --i; } } for (int i = 0; i < ettPids.size(); ++i) { int pid = ettPids.at(i); int index = (qBinaryFind(newEttPids, pid) - newEttPids.constBegin()); if (index < newEttPids.size()) { newEttPids.removeAt(index); } else { device->removeSectionFilter(pid, &ettFilter); ettPids.removeAt(i); --i; } } for (int i = 0; i < newEitPids.size(); ++i) { int pid = newEitPids.at(i); eitPids.append(pid); device->addSectionFilter(pid, &eitFilter); } for (int i = 0; i < newEttPids.size(); ++i) { int pid = newEttPids.at(i); ettPids.append(pid); device->addSectionFilter(pid, &ettFilter); } } void AtscEpgFilter::processEitSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xcb) { return; } AtscEitSection eitSection(data, size); if (!eitSection.isValid()) { qCDebug(logEpg, "section is invalid"); return; } DvbChannel fakeChannel; fakeChannel.source = source; fakeChannel.transponder = transponder; fakeChannel.networkId = eitSection.sourceId(); DvbSharedChannel channel = channelModel->findChannelById(fakeChannel); if (!channel.isValid()) { qCDebug(logEpg, "channel is invalid"); return; } qCDebug(logEpg, "Processing EIT section with size %d", size); int entryCount = eitSection.entryCount(); // 1980-01-06T000000 minus 15 secs (= UTC - GPS in 2011) QDateTime baseDateTime = QDateTime(QDate(1980, 1, 5), QTime(23, 59, 45), Qt::UTC); AtscEitSectionEntry eitEntry = eitSection.entries(); for (int i = 0; i < entryCount; i++) { if (!eitEntry.isValid()) break; DvbEpgEntry epgEntry; epgEntry.channel = channel; epgEntry.begin = baseDateTime.addSecs(eitEntry.startTime()); epgEntry.duration = QTime(0, 0, 0).addSecs(eitEntry.duration()); DvbEpgLangEntry *langEntry; /* Should be similar to DvbEpgFilter::getLangEntry */ if (!epgEntry.langEntry.contains(FIRST_LANG)) { DvbEpgLangEntry e; epgEntry.langEntry.insert(FIRST_LANG, e); } langEntry = &epgEntry.langEntry[FIRST_LANG]; langEntry->title = eitEntry.title(); quint32 id = ((quint32(fakeChannel.networkId) << 16) | quint32(eitEntry.eventId())); DvbSharedEpgEntry entry = epgEntries.value(id); entry = epgModel->addEntry(epgEntry); epgEntries.insert(id, entry); if ( i < entryCount -1) eitEntry.advance(); } } void AtscEpgFilter::processEttSection(const char *data, int size) { unsigned char tableId = data[0]; if (tableId != 0xcc) { return; } AtscEttSection ettSection(data, size); if (!ettSection.isValid() || (ettSection.messageType() != 0x02)) { return; } quint32 id = ((quint32(ettSection.sourceId()) << 16) | quint32(ettSection.eventId())); DvbSharedEpgEntry entry = epgEntries.value(id); if (entry.isValid()) { QString details = ettSection.text(); if (entry->details() != details) { DvbEpgEntry modifiedEntry = *entry; DvbEpgLangEntry *langEntry; if (modifiedEntry.langEntry.contains(FIRST_LANG)) langEntry = &modifiedEntry.langEntry[FIRST_LANG]; else langEntry = new(DvbEpgLangEntry); langEntry->details = details; entry = epgModel->addEntry(modifiedEntry); epgEntries.insert(id, entry); } } } diff --git a/src/dvb/dvbmanager.cpp b/src/dvb/dvbmanager.cpp index 9965f76..047553f 100644 --- a/src/dvb/dvbmanager.cpp +++ b/src/dvb/dvbmanager.cpp @@ -1,1046 +1,1056 @@ /* * dvbmanager.cpp * * Copyright (C) 2008-2011 Christoph Pfister * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "../log.h" #include #include #include #include #include #include #include #include "dvbconfig.h" #include "dvbdevice.h" #include "dvbdevice_linux.h" #include "dvbepg.h" #include "dvbliveview.h" #include "dvbmanager.h" #include "dvbmanager_p.h" #include "dvbsi.h" #include "xmltv.h" DvbManager::DvbManager(MediaWidget *mediaWidget_, QWidget *parent_) : QObject(parent_), parent(parent_), mediaWidget(mediaWidget_), channelView(NULL), dvbDumpEnabled(false) { channelModel = DvbChannelModel::createSqlModel(this); recordingModel = new DvbRecordingModel(this, this); epgModel = new DvbEpgModel(this, this); liveView = new DvbLiveView(this, this); xmlTv = new XmlTv(this); readDeviceConfigs(); updateSourceMapping(); loadDeviceManager(); DvbSiText::setOverride6937(override6937Charset()); QString xmlFile = getXmltvFileName(); if (xmlFile != "") xmlTv->addFile(xmlFile); } DvbManager::~DvbManager() { writeDeviceConfigs(); // we need an explicit deletion order (device users ; devices ; device manager) delete xmlTv; delete epgModel; epgModel = NULL; delete recordingModel; foreach (const DvbDeviceConfig &deviceConfig, deviceConfigs) { delete deviceConfig.device; } } DvbDevice *DvbManager::requestDevice(const QString &source, const DvbTransponder &transponder, DvbManager::RequestType requestType) { Q_ASSERT(requestType != Exclusive); // FIXME call DvbEpgModel::startEventFilter / DvbEpgModel::stopEventFilter here? reacquireDevice = false; for (int i = 0; i < deviceConfigs.size(); ++i) { const DvbDeviceConfig &it = deviceConfigs.at(i); if ((it.device == NULL) || (it.useCount < 1)) { continue; } if ((it.source == source) && it.transponder.corresponds(transponder)) { ++deviceConfigs[i].useCount; if (requestType == Prioritized) { ++deviceConfigs[i].prioritizedUseCount; } return it.device; } } for (int i = 0; i < deviceConfigs.size(); ++i) { const DvbDeviceConfig &it = deviceConfigs.at(i); if ((it.device == NULL) || (it.useCount != 0)) { continue; } foreach (const DvbConfig &config, it.configs) { if (config->name == source) { DvbDevice *device = it.device; if (!device->acquire(config.constData())) { continue; } deviceConfigs[i].useCount = 1; if (requestType == Prioritized) { deviceConfigs[i].prioritizedUseCount = 1; } deviceConfigs[i].source = source; deviceConfigs[i].transponder = transponder; device->tune(transponder); return device; } } } if (requestType != Prioritized) { return NULL; } for (int i = 0; i < deviceConfigs.size(); ++i) { const DvbDeviceConfig &it = deviceConfigs.at(i); if ((it.device == NULL) || (it.useCount == 0) || (it.prioritizedUseCount != 0)) { continue; } foreach (const DvbConfig &config, it.configs) { if (config->name == source) { deviceConfigs[i].useCount = 1; deviceConfigs[i].prioritizedUseCount = 1; deviceConfigs[i].source = source; deviceConfigs[i].transponder = transponder; DvbDevice *device = it.device; device->reacquire(config.constData()); device->tune(transponder); reacquireDevice = true; return device; } } } return NULL; } DvbDevice *DvbManager::requestExclusiveDevice(const QString &source) { for (int i = 0; i < deviceConfigs.size(); ++i) { const DvbDeviceConfig &it = deviceConfigs.at(i); if ((it.device == NULL) || (it.useCount != 0)) { continue; } foreach (const DvbConfig &config, it.configs) { if (config->name == source) { DvbDevice *device = it.device; if (!device->acquire(config.constData())) { continue; } deviceConfigs[i].useCount = -1; deviceConfigs[i].source.clear(); return device; } } } return NULL; } void DvbManager::releaseDevice(DvbDevice *device, RequestType requestType) { for (int i = 0; i < deviceConfigs.size(); ++i) { const DvbDeviceConfig &it = deviceConfigs.at(i); if (it.device == device) { switch (requestType) { case Prioritized: --deviceConfigs[i].prioritizedUseCount; Q_ASSERT(it.prioritizedUseCount >= 0); // fall through case Shared: --deviceConfigs[i].useCount; Q_ASSERT(it.useCount >= 0); Q_ASSERT(it.useCount >= it.prioritizedUseCount); if (it.useCount == 0) { it.device->release(); } break; case Exclusive: Q_ASSERT(it.useCount == -1); Q_ASSERT(it.prioritizedUseCount == 0); deviceConfigs[i].useCount = 0; it.device->release(); break; } break; } } } QList DvbManager::getDeviceConfigs() const { return deviceConfigs; } void DvbManager::updateDeviceConfigs(const QList &configUpdates) { for (int i = 0; i < configUpdates.size(); ++i) { const DvbDeviceConfigUpdate &configUpdate = configUpdates.at(i); for (int j = i;; ++j) { Q_ASSERT(j < deviceConfigs.size()); if (&deviceConfigs.at(j) == configUpdate.deviceConfig) { if (i != j) { deviceConfigs.move(j, i); } deviceConfigs[i].configs = configUpdate.configs; break; } } } for (int i = configUpdates.size(); i < deviceConfigs.size(); ++i) { if (deviceConfigs.at(i).device != NULL) { deviceConfigs[i].configs.clear(); } else { deviceConfigs.removeAt(i); --i; } } updateSourceMapping(); } QDate DvbManager::getScanDataDate() { if (!scanDataDate.isValid()) { readScanData(); } return scanDataDate; } QStringList DvbManager::getScanSources(TransmissionType type) { if (scanData.isEmpty()) { readScanData(); } return scanSources.value(type); } QString DvbManager::getAutoScanSource(const QString &source) const { QPair scanSource = sourceMapping.value(source); if (scanSource.second.isEmpty()) { qCWarning(logDvb, "Invalid source for autoscan"); return QString(); } if (((scanSource.first == DvbT) || (scanSource.first == IsdbT)) && (scanSource.second.startsWith(QLatin1String("AUTO")))) { return scanSource.second; } return QString(); } QList DvbManager::getTransponders(DvbDevice *device, const QString &source) { if (scanData.isEmpty()) { readScanData(); } QPair scanSource = sourceMapping.value(source); if (scanSource.second.isEmpty()) { qCWarning(logDvb, "Invalid source. Can't get transponder"); return QList(); } if ((scanSource.first == DvbS) && ((device->getTransmissionTypes() & DvbDevice::DvbS2) != 0)) { scanSource.first = DvbS2; } if ((scanSource.first == DvbT) && ((device->getTransmissionTypes() & DvbDevice::DvbT2) != 0)) { scanSource.first = DvbT2; } return scanData.value(scanSource); } bool DvbManager::updateScanData(const QByteArray &data) { QByteArray uncompressed = qUncompress(data); if (uncompressed.isEmpty()) { qCWarning(logDvb, "Failed to uncompress the scan data file"); return false; } if (!DvbScanData(uncompressed).readDate().isValid()) { qCWarning(logDvb, "Invalid format at the scan data file"); return false; } QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/scanfile.dvb")); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(logDvb, "Cannot open %s", qPrintable(file.fileName())); return false; } file.write(uncompressed); file.close(); readScanData(); return true; } QString DvbManager::getRecordingFolder() const { return KSharedConfig::openConfig()->group("DVB").readEntry("RecordingFolder", QDir::homePath()); } QString DvbManager::getTimeShiftFolder() const { return KSharedConfig::openConfig()->group("DVB").readEntry("TimeShiftFolder", QDir::homePath()); } QString DvbManager::getXmltvFileName() const { return KSharedConfig::openConfig()->group("DVB").readEntry("XmltvFileName", ""); } int DvbManager::getBeginMargin() const { return KSharedConfig::openConfig()->group("DVB").readEntry("BeginMargin", 300); } int DvbManager::getEndMargin() const { return KSharedConfig::openConfig()->group("DVB").readEntry("EndMargin", 600); } QString DvbManager::getNamingFormat() const { return KSharedConfig::openConfig()->group("DVB").readEntry("NamingFormat", "%title"); } QString DvbManager::getRecordingRegex() const { return KSharedConfig::openConfig()->group("DVB").readEntry("RecordingRegex", ""); } QStringList DvbManager::getRecordingRegexList() const { return KSharedConfig::openConfig()->group("DVB").readEntry("RecordingRegexList", QStringList()); } QList DvbManager::getRecordingRegexPriorityList() const { return KSharedConfig::openConfig()->group("DVB").readEntry("RecordingRegexPriorityList", QList()); } QString DvbManager::getActionAfterRecording() const { return KSharedConfig::openConfig()->group("DVB").readEntry("ActionAfterRecording", ""); } bool DvbManager::override6937Charset() const { return KSharedConfig::openConfig()->group("DVB").readEntry("Override6937", false); } bool DvbManager::isScanWhenIdle() const { return KSharedConfig::openConfig()->group("DVB").readEntry("ScanWhenIdle", false); } bool DvbManager::createInfoFile() const { return KSharedConfig::openConfig()->group("DVB").readEntry("CreateInfoFile", false); } +bool DvbManager::disableEpg() const +{ + return KSharedConfig::openConfig()->group("DVB").readEntry("DisableEpg", false); +} + void DvbManager::setRecordingFolder(const QString &path) { KSharedConfig::openConfig()->group("DVB").writeEntry("RecordingFolder", path); } void DvbManager::setTimeShiftFolder(const QString &path) { KSharedConfig::openConfig()->group("DVB").writeEntry("TimeShiftFolder", path); } void DvbManager::setXmltvFileName(const QString &path) { KSharedConfig::openConfig()->group("DVB").writeEntry("XmltvFileName", path); xmlTv->clear(); xmlTv->addFile(path); } void DvbManager::setBeginMargin(int beginMargin) { KSharedConfig::openConfig()->group("DVB").writeEntry("BeginMargin", beginMargin); } void DvbManager::setEndMargin(int endMargin) { KSharedConfig::openConfig()->group("DVB").writeEntry("EndMargin", endMargin); } void DvbManager::setNamingFormat(QString namingFormat) { KSharedConfig::openConfig()->group("DVB").writeEntry("NamingFormat", namingFormat); } void DvbManager::setRecordingRegex(QString regex) { KSharedConfig::openConfig()->group("DVB").writeEntry("RecordingRegex", regex); } void DvbManager::setRecordingRegexList(const QStringList regexList) { KSharedConfig::openConfig()->group("DVB").writeEntry("RecordingRegexList", regexList); } void DvbManager::setRecordingRegexPriorityList(const QList regexList) { KSharedConfig::openConfig()->group("DVB").writeEntry("RecordingRegexPriorityList", regexList); } bool DvbManager::addRecordingRegex(QString regex) { QStringList regexList = getRecordingRegexList(); regexList.append(regex); setRecordingRegexList(regexList); return true; } bool DvbManager::addRecordingRegexPriority(int regexPriority) { QList regexPriorityList = getRecordingRegexPriorityList(); regexPriorityList.append(regexPriority); setRecordingRegexPriorityList(regexPriorityList); return true; } bool DvbManager::removeRecordingRegex(QString regex) { QStringList regexList = getRecordingRegexList(); if (regexList.contains(regex)) { regexList.removeOne(regex); setRecordingRegexList(regexList); return true; } setRecordingRegexList(regexList); return false; } bool DvbManager::removeRecordingRegexPriority(int priority) { QList regexPriorityList = getRecordingRegexPriorityList(); if (regexPriorityList.contains(priority)) { regexPriorityList.removeOne(priority); setRecordingRegexPriorityList(regexPriorityList); return true; } setRecordingRegexPriorityList(regexPriorityList); return false; } void DvbManager::setActionAfterRecording(QString actionAfterRecording) { KSharedConfig::openConfig()->group("DVB").writeEntry("ActionAfterRecording", actionAfterRecording); } void DvbManager::setOverride6937Charset(bool override) { KSharedConfig::openConfig()->group("DVB").writeEntry("Override6937", override); DvbSiText::setOverride6937(override); } void DvbManager::setScanWhenIdle(bool scanWhenIdle) { KSharedConfig::openConfig()->group("DVB").writeEntry("ScanWhenIdle", scanWhenIdle); } void DvbManager::setCreateInfoFile(bool createInfoFile) { KSharedConfig::openConfig()->group("DVB").writeEntry("CreateInfoFile", createInfoFile); } +void DvbManager::setDisableEpg(bool disableEpg) +{ + KSharedConfig::openConfig()->group("DVB").writeEntry("DisableEpg", disableEpg); +} + void DvbManager::enableDvbDump() { if (dvbDumpEnabled) { return; } dvbDumpEnabled = true; foreach (const DvbDeviceConfig &deviceConfig, deviceConfigs) { if (deviceConfig.device != NULL) { deviceConfig.device->enableDvbDump(); } } } void DvbManager::requestBuiltinDeviceManager(QObject *&builtinDeviceManager) { builtinDeviceManager = new DvbLinuxDeviceManager(this); } void DvbManager::deviceAdded(DvbBackendDevice *backendDevice) { DvbDevice *device = new DvbDevice(backendDevice, this); QString deviceId = device->getDeviceId(); QString frontendName = device->getFrontendName(); if (dvbDumpEnabled) { device->enableDvbDump(); } for (int i = 0;; ++i) { if (i == deviceConfigs.size()) { deviceConfigs.append(DvbDeviceConfig(deviceId, frontendName, device)); break; } const DvbDeviceConfig &it = deviceConfigs.at(i); if ((it.deviceId.isEmpty() || deviceId.isEmpty() || (it.deviceId == deviceId)) && (it.frontendName == frontendName) && (it.device == NULL)) { deviceConfigs[i].device = device; break; } } } void DvbManager::deviceRemoved(DvbBackendDevice *backendDevice) { for (int i = 0; i < deviceConfigs.size(); ++i) { DvbDeviceConfig &it = deviceConfigs[i]; if (it.device && it.device->getBackendDevice() == backendDevice) { if (it.useCount != 0) { it.useCount = 0; it.prioritizedUseCount = 0; it.device->release(); } delete it.device; it.device = NULL; break; } } } void DvbManager::loadDeviceManager() { QDir dir(QString::fromUtf8(KAFFEINE_LIB_INSTALL_DIR "/")); QStringList entries = dir.entryList(QStringList(QLatin1String("*kaffeinedvb*")), QDir::NoFilter, QDir::Name | QDir::Reversed); foreach (const QString &entry, entries) { QString path = dir.filePath(entry); if (!QLibrary::isLibrary(path)) { continue; } QObject *deviceManager = QPluginLoader(path).instance(); if (deviceManager == NULL) { qCWarning(logDvb, "Cannot load dvb device manager %s", qPrintable(path)); break; } qCInfo(logDvb, "Using dvb device manager %s", qPrintable(path)); deviceManager->setParent(this); connect(deviceManager, SIGNAL(requestBuiltinDeviceManager(QObject*&)), this, SLOT(requestBuiltinDeviceManager(QObject*&))); connect(deviceManager, SIGNAL(deviceAdded(DvbBackendDevice*)), this, SLOT(deviceAdded(DvbBackendDevice*))); connect(deviceManager, SIGNAL(deviceRemoved(DvbBackendDevice*)), this, SLOT(deviceRemoved(DvbBackendDevice*))); QMetaObject::invokeMethod(deviceManager, "doColdPlug"); return; } qCInfo(logDvb, "Using built-in dvb device manager"); DvbLinuxDeviceManager *deviceManager = new DvbLinuxDeviceManager(this); connect(deviceManager, SIGNAL(deviceAdded(DvbBackendDevice*)), this, SLOT(deviceAdded(DvbBackendDevice*))); connect(deviceManager, SIGNAL(deviceRemoved(DvbBackendDevice*)), this, SLOT(deviceRemoved(DvbBackendDevice*))); deviceManager->doColdPlug(); } void DvbManager::readDeviceConfigs() { QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/config.dvb")); const char *errMsg; if (!file.open(QIODevice::ReadOnly)) { qCWarning(logDvb, "Cannot open %s", qPrintable(file.fileName())); return; } DvbDeviceConfigReader reader(&file); while (!reader.atEnd()) { if (reader.readLine() != QLatin1String("[device]")) { continue; } QString deviceId = reader.readString(QLatin1String("deviceId")); QString frontendName = reader.readString(QLatin1String("frontendName")); int configCount = reader.readInt(QLatin1String("configCount")); if (!reader.isValid()) { errMsg = "device section invalid"; break; } DvbDeviceConfig deviceConfig(deviceId, frontendName, NULL); for (int i = 0; i < configCount; ++i) { while (!reader.atEnd()) { if (reader.readLine() == QLatin1String("[config]")) { break; } } DvbConfigBase::TransmissionType type = reader.readEnum(QLatin1String("type"), DvbConfigBase::TransmissionTypeMax); if (!reader.isValid()) { errMsg = "transmission type invalid"; break; } DvbConfigBase *config = new DvbConfigBase(type); config->numberOfTuners = 1; config->name = reader.readString(QLatin1String("name")); config->scanSource = reader.readString(QLatin1String("scanSource")); config->timeout = reader.readInt(QLatin1String("timeout")); if (type == DvbConfigBase::DvbS) { config->latitude = 0; config->longitude = 0; config->higherVoltage = Qt::PartiallyChecked; config->configuration = reader.readEnum(QLatin1String("configuration"), DvbConfigBase::ConfigurationMax); config->lnbNumber = reader.readInt(QLatin1String("lnbNumber")); config->currentLnb.alias = reader.readString(QLatin1String("lnb")); config->higherVoltage = reader.readInt(QLatin1String("higherVoltage")); if (config->configuration == DvbConfigBase::UsalsRotor) { config->latitude = reader.readDouble(QLatin1String("latitude")); config->longitude = reader.readInt(QLatin1String("longitude")); } } if (!reader.isValid()) { errMsg = "DVB device data invalid"; delete config; break; } deviceConfig.configs.append(DvbConfig(config)); } deviceConfigs.append(deviceConfig); } if (!reader.isValid()) qCWarning(logDvb, "Found some problems at %s: %s", qPrintable(file.fileName()), errMsg); } void DvbManager::writeDeviceConfigs() { QFile file(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/config.dvb")); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qCWarning(logDvb, "Cannot open %s", qPrintable(file.fileName())); return; } DvbDeviceConfigWriter writer(&file); foreach (const DvbDeviceConfig &deviceConfig, deviceConfigs) { writer.write(QLatin1String("[device]")); writer.write(QLatin1String("deviceId"), deviceConfig.deviceId); writer.write(QLatin1String("frontendName"), deviceConfig.frontendName); writer.write(QLatin1String("configCount"), deviceConfig.configs.size()); for (int i = 0; i < deviceConfig.configs.size(); ++i) { const DvbConfig &config = deviceConfig.configs.at(i); writer.write(QLatin1String("[config]")); writer.write(QLatin1String("type"), config->getTransmissionType()); writer.write(QLatin1String("name"), config->name); writer.write(QLatin1String("scanSource"), config->scanSource); writer.write(QLatin1String("timeout"), config->timeout); if (config->getTransmissionType() == DvbConfigBase::DvbS) { writer.write(QLatin1String("configuration"), config->configuration); writer.write(QLatin1String("lnbNumber"), config->lnbNumber); writer.write(QLatin1String("lnb"), config->currentLnb.alias); writer.write(QLatin1String("higherVoltage"), config->higherVoltage); if (config->configuration == DvbConfigBase::UsalsRotor) { writer.write(QLatin1String("latitude"), config->latitude); writer.write(QLatin1String("longitude"), config->longitude); } } } } } void DvbManager::updateSourceMapping() { sourceMapping.clear(); foreach (const DvbDeviceConfig &deviceConfig, deviceConfigs) { for (int i = 0; i < deviceConfig.configs.size(); ++i) { const DvbConfig &config = deviceConfig.configs.at(i); TransmissionType type; switch (config->getTransmissionType()) { case DvbConfigBase::DvbC: type = DvbC; break; case DvbConfigBase::DvbS: type = DvbS; break; case DvbConfigBase::DvbT: type = DvbT; break; case DvbConfigBase::Atsc: type = Atsc; break; case DvbConfigBase::IsdbT: type = IsdbT; break; default: Q_ASSERT(false); continue; } sourceMapping.insert(config->name, qMakePair(type, config->scanSource)); } } sources = sourceMapping.keys(); } void DvbManager::readScanData() { scanSources.clear(); scanData.clear(); QFile globalFile(QString::fromUtf8(KAFFEINE_DATA_INSTALL_DIR "/kaffeine/scanfile.dvb")); QDate globalDate; if (globalFile.open(QIODevice::ReadOnly)) { globalDate = DvbScanData(globalFile.read(1024)).readDate(); if (globalDate.isNull()) { qCWarning(logDvb, "Cannot parse %s", qPrintable(globalFile.fileName())); } globalFile.close(); } else { qCWarning(logDvb, "Cannot open global scanfile %s", qPrintable(globalFile.fileName())); } QFile localFile(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/scanfile.dvb")); QByteArray localData; QDate localDate; if (localFile.open(QIODevice::ReadOnly)) { localData = localFile.readAll(); localDate = DvbScanData(localData).readDate(); if (localDate.isNull()) { qCWarning(logDvb, "Cannot parse %s", qPrintable(localFile.fileName())); } localFile.close(); } if (localDate < globalDate) { localData.clear(); if (localFile.exists() && !localFile.remove()) { qCWarning(logDvb, "Cannot remove %s", qPrintable(localFile.fileName())); } if (!globalFile.copy(localFile.fileName())) { qCWarning(logDvb, "Cannot copy %s to %s", qPrintable(globalFile.fileName()), qPrintable(localFile.fileName())); } if (localFile.open(QIODevice::ReadOnly)) { localData = localFile.readAll(); localFile.close(); } else { qCWarning(logDvb, "Cannot open %s", qPrintable(localFile.fileName())); scanDataDate = QDate(1900, 1, 1); return; } } DvbScanData data(localData); scanDataDate = data.readDate(); if (!scanDataDate.isValid()) { qCWarning(logDvb, "Cannot parse %s", qPrintable(localFile.fileName())); scanDataDate = QDate(1900, 1, 1); return; } // Parse scan file QRegularExpression rejex = QRegularExpression("\\[(\\S+)/(\\S+)\\]"); QRegularExpressionMatch match; TransmissionType type; while (!data.checkEnd()) { const char *line = data.readLine(); // Discard empty lines if (*line == 0) continue; QString qLine(line); if (!qLine.contains(rejex, &match)) { qCWarning(logDvb, "Unrecognized line: '%s'", line); continue; } QString typeStr = match.captured(1); QString name = match.captured(2); if (!typeStr.compare("dvb-c", Qt::CaseInsensitive)) type = DvbC; else if (!typeStr.compare("dvb-s", Qt::CaseInsensitive)) type = DvbS; else if (!typeStr.compare("dvb-t", Qt::CaseInsensitive)) type = DvbT; else if (!typeStr.compare("atsc", Qt::CaseInsensitive)) type = Atsc; else if (!typeStr.compare("isdb-t", Qt::CaseInsensitive)) type = IsdbT; else { qCWarning(logDvb, "Transmission type '%s' unknown", qPrintable(typeStr)); continue; } QList transponders; bool containsDvbS1 = false; bool containsDvbT1 = false; while (!data.checkEnd()) { line = data.getLine(); if ((*line == '[') || (*line == 0)) { break; } line = data.readLine(); // Ignore lines with empty strings if (*line == 0) continue; DvbTransponder transponder = DvbTransponder::fromString(QString::fromLatin1(line)); if (!transponder.isValid()) { qCWarning(logDvb, "Error parsing line : '%s'", qPrintable(line)); } else { transponders.append(transponder); if (transponder.getTransmissionType() == DvbTransponderBase::DvbS) { containsDvbS1 = true; } if (transponder.getTransmissionType() == DvbTransponderBase::DvbT) { containsDvbT1 = true; } } } if (type == DvbS || type == DvbS2) { scanSources[DvbS2].append(name); scanData.insert(qMakePair(DvbS2, name), transponders); if (containsDvbS1) { for (int i = 0; i < transponders.size(); ++i) { if (transponders.at(i).getTransmissionType() == DvbTransponderBase::DvbS2) { transponders.removeAt(i); --i; } } scanSources[DvbS].append(name); scanData.insert(qMakePair(DvbS, name), transponders); } } else if (type == DvbT || type == DvbT2) { scanSources[DvbT2].append(name); scanData.insert(qMakePair(DvbT2, name), transponders); if (containsDvbT1) { for (int i = 0; i < transponders.size(); ++i) { if (transponders.at(i).getTransmissionType() == DvbTransponderBase::DvbT2) { transponders.removeAt(i); --i; } } scanSources[DvbT].append(name); scanData.insert(qMakePair(DvbT, name), transponders); } } else { scanSources[type].append(name); scanData.insert(qMakePair(type, name), transponders); } } if (!data.checkEnd()) qCWarning(logDvb, "Some data at the scan file were not parsed"); } DvbDeviceConfig::DvbDeviceConfig(const QString &deviceId_, const QString &frontendName_, DvbDevice *device_) : deviceId(deviceId_), frontendName(frontendName_), device(device_), useCount(0), prioritizedUseCount(0) { } DvbDeviceConfig::~DvbDeviceConfig() { } DvbDeviceConfigUpdate::DvbDeviceConfigUpdate(const DvbDeviceConfig *deviceConfig_) : deviceConfig(deviceConfig_) { } DvbDeviceConfigUpdate::~DvbDeviceConfigUpdate() { } DvbScanData::DvbScanData(const QByteArray &data_) : data(data_) { begin = data.begin(); pos = data.begin(); end = data.constEnd(); } DvbScanData::~DvbScanData() { } bool DvbScanData::checkEnd() const { return (pos == end); } const char *DvbScanData::getLine() const { return pos; } const char *DvbScanData::readLine() { // ignore comments while (*pos == '#') { do { ++pos; if (pos == end) { return end; } } while (*pos != '\n'); ++pos; } char *line = pos; while (pos < end) { if (*pos == ' ') ++pos; if (*pos == '\n') { *pos = 0; ++pos; break; } ++pos; } return line; } QDate DvbScanData::readDate() { if (strcmp(readLine(), "[date]") != 0) { return QDate(); } return QDate::fromString(QString::fromLatin1(readLine()), Qt::ISODate); } diff --git a/src/dvb/dvbmanager.h b/src/dvb/dvbmanager.h index 030a76a..0dd5d7f 100644 --- a/src/dvb/dvbmanager.h +++ b/src/dvb/dvbmanager.h @@ -1,226 +1,228 @@ /* * dvbmanager.h * * Copyright (C) 2008-2011 Christoph Pfister * * 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; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef DVBMANAGER_H #define DVBMANAGER_H #include #include #include #include #include #include "dvbtransponder.h" class QTreeView; class DvbBackendDevice; class DvbChannelModel; class DvbConfig; class DvbDevice; class DvbDeviceConfig; class DvbDeviceConfigUpdate; class DvbEpgModel; class DvbLiveView; class DvbRecordingModel; class DvbScanData; class MediaWidget; class XmlTv; class DvbManager : public QObject { Q_OBJECT public: enum RequestType { Shared, Exclusive, // you can freely tune() and stop(), because the device isn't shared Prioritized // takes precedence over 'Shared' and 'Exclusive' }; enum TransmissionType { DvbC, DvbS, DvbS2, // includes DvbS DvbT, DvbT2, // includes DvbT Atsc, IsdbT }; DvbManager(MediaWidget *mediaWidget_, QWidget *parent_); ~DvbManager(); QWidget *getParentWidget() const { return parent; } MediaWidget *getMediaWidget() const { return mediaWidget; } QStringList getSources() const { return sources; } DvbChannelModel *getChannelModel() const { return channelModel; } QTreeView *getChannelView() const { return channelView; } DvbEpgModel *getEpgModel() const { return epgModel; } DvbLiveView *getLiveView() const { return liveView; } DvbRecordingModel *getRecordingModel() const { return recordingModel; } void setChannelView(QTreeView *channelView_) { channelView = channelView_; } DvbDevice *requestDevice(const QString &source, const DvbTransponder &transponder, RequestType requestType); DvbDevice *requestExclusiveDevice(const QString &source); void releaseDevice(DvbDevice *device, RequestType requestType); QList getDeviceConfigs() const; void updateDeviceConfigs(const QList &configUpdates); QDate getScanDataDate(); QStringList getScanSources(TransmissionType type); QString getAutoScanSource(const QString &source) const; QList getTransponders(DvbDevice *device, const QString &source); QHash languageCodes; QString currentEpgLanguage; bool updateScanData(const QByteArray &data); QString getRecordingFolder() const; QString getTimeShiftFolder() const; QString getXmltvFileName() const; QString getNamingFormat() const; QString getRecordingRegex() const; QStringList getRecordingRegexList() const; QList getRecordingRegexPriorityList() const; QString getActionAfterRecording() const; int getBeginMargin() const; // seconds int getEndMargin() const; // seconds bool override6937Charset() const; bool createInfoFile() const; + bool disableEpg() const; bool isScanWhenIdle() const; void setRecordingFolder(const QString &path); void setTimeShiftFolder(const QString &path); void setXmltvFileName(const QString &path); void setNamingFormat(const QString namingFormat); void setRecordingRegex(const QString regex); void setRecordingRegexList(const QStringList regexList); void setRecordingRegexPriorityList(const QList regexList); bool removeRecordingRegex(QString regex); bool addRecordingRegex(QString regex); bool removeRecordingRegexPriority(int priority); bool addRecordingRegexPriority(int index); void setActionAfterRecording(const QString actionAfterRecording); void setBeginMargin(int beginMargin); // seconds void setEndMargin(int endMargin); // seconds void setOverride6937Charset(bool override); void setCreateInfoFile(bool createInfoFile); + void setDisableEpg(bool disableEpg); void setScanWhenIdle(bool scanWhenIdle); void writeDeviceConfigs(); void enableDvbDump(); bool hasReacquired() { return reacquireDevice; }; private slots: void requestBuiltinDeviceManager(QObject *&builtinDeviceManager); void deviceAdded(DvbBackendDevice *backendDevice); void deviceRemoved(DvbBackendDevice *backendDevice); private: void loadDeviceManager(); void readDeviceConfigs(); void updateSourceMapping(); void readScanData(); bool readScanSources(DvbScanData &data, const char *tag, TransmissionType type); QWidget *parent; MediaWidget *mediaWidget; DvbChannelModel *channelModel; QTreeView *channelView; DvbEpgModel *epgModel; XmlTv *xmlTv; DvbLiveView *liveView; DvbRecordingModel *recordingModel; bool reacquireDevice; QList deviceConfigs; bool dvbDumpEnabled; QMap > sourceMapping; QStringList sources; QDate scanDataDate; QMap scanSources; QMap, QList > scanData; }; class DvbDeviceConfig { public: DvbDeviceConfig(const QString &deviceId_, const QString &frontendName_, DvbDevice *device_); ~DvbDeviceConfig(); QString deviceId; QString frontendName; DvbDevice *device; QList configs; int useCount; // -1 means exclusive use int prioritizedUseCount; int numberOfTuners; QString source; DvbTransponder transponder; }; class DvbDeviceConfigUpdate { public: explicit DvbDeviceConfigUpdate(const DvbDeviceConfig *deviceConfig_); ~DvbDeviceConfigUpdate(); const DvbDeviceConfig *deviceConfig; QList configs; }; #endif /* DVBMANAGER_H */