File indexing completed on 2024-05-19 04:53:19

0001 /*
0002  * dvbscandialog.cpp
0003  *
0004  * Copyright (C) 2008-2011 Christoph Pfister <christophpfister@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as published by
0008  * the Free Software Foundation; either version 2 of the License, or
0009  * (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public License along
0017  * with this program; if not, write to the Free Software Foundation, Inc.,
0018  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
0019  */
0020 
0021 #include <KConfigGroup>
0022 #include <KLed>
0023 #include <KLocalizedString>
0024 #include <KMessageBox>
0025 #include <QAction>
0026 #include <QBoxLayout>
0027 #include <QCheckBox>
0028 #include <QComboBox>
0029 #include <QGroupBox>
0030 #include <QHeaderView>
0031 #include <QLocale>
0032 #include <QPainter>
0033 #include <QProgressBar>
0034 #include <QPushButton>
0035 #include <QSortFilterProxyModel>
0036 
0037 #include "dvbchanneldialog.h"
0038 #include "dvbdevice.h"
0039 #include "dvbliveview.h"
0040 #include "dvbmanager.h"
0041 #include "dvbscan.h"
0042 #include "dvbscandialog.h"
0043 
0044 DvbGradProgress::DvbGradProgress(QWidget *parent) : QLabel(parent), value(0), max(100.), min(0.)
0045 {
0046     setAlignment(Qt::AlignCenter);
0047     setFrameShape(Box);
0048     setText("");
0049 }
0050 
0051 DvbGradProgress::~DvbGradProgress()
0052 {
0053 }
0054 
0055 void DvbGradProgress::setValue(float value_, DvbBackendDevice::Scale scale)
0056 {
0057     QString text;
0058     value = value_;
0059 
0060     switch(scale) {
0061     case DvbBackendDevice::NotSupported: {
0062         text = '-';
0063         max = 100;
0064         min = 0;
0065         break;
0066         }
0067     case DvbBackendDevice::Percentage: {
0068         text = QString::number(value, 'f', 0) + '%';
0069         max = 100;
0070         min = 0;
0071         break;
0072         }
0073     case DvbBackendDevice::Decibel: {
0074         text = QString::number(value, 'f', 2) + " dB";
0075         max = 40;
0076         min = 0;
0077         break;
0078         }
0079     case DvbBackendDevice::dBuV: {
0080         text = QString::number(value, 'f', 2) + " dB" + QString((QChar) 0x00b5) + 'V';
0081         max = 80;
0082         min = 20;
0083         break;
0084         }
0085     }
0086 
0087     setText(i18n("%1", text));
0088     update();
0089 }
0090 
0091 void DvbGradProgress::paintEvent(QPaintEvent *event)
0092 {
0093     QPainter painter(this);
0094     int border = frameWidth();
0095     QRect rect(border, border, width() - 2 * border, height() - 2 * border);
0096     QLinearGradient gradient(rect.topLeft(), rect.topRight());
0097     gradient.setColorAt(0, Qt::red);
0098     gradient.setColorAt(1, Qt::green);
0099     if (value < min)
0100         value = min;
0101     if (value > max)
0102         value = max;
0103     rect.setWidth((rect.width() * (value - min)) / (max - min));
0104     painter.fillRect(rect, gradient);
0105 
0106     QLabel::paintEvent(event);
0107 }
0108 
0109 class DvbPreviewChannelTableModel : public QAbstractTableModel
0110 {
0111 public:
0112     explicit DvbPreviewChannelTableModel(QObject *parent) : QAbstractTableModel(parent) { }
0113     ~DvbPreviewChannelTableModel() { }
0114 
0115     enum ItemDataRole
0116     {
0117         DvbPreviewChannelRole = Qt::UserRole
0118     };
0119 
0120     QAbstractItemModel *createProxyModel(QObject *parent);
0121 
0122     int columnCount(const QModelIndex &parent) const override;
0123     int rowCount(const QModelIndex &parent) const override;
0124     QVariant data(const QModelIndex &index, int role) const override;
0125     QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
0126 
0127     void appendChannels(const QList<DvbPreviewChannel> &list)
0128     {
0129         beginInsertRows(QModelIndex(), channels.size(), channels.size() + list.size() - 1);
0130         channels += list;
0131         endInsertRows();
0132     }
0133 
0134     const DvbPreviewChannel &getChannel(int pos) const
0135     {
0136         return channels.at(pos);
0137     }
0138 
0139     QList<DvbPreviewChannel> getChannels() const
0140     {
0141         return channels;
0142     }
0143 
0144     void removeChannels()
0145     {
0146         beginResetModel();
0147         channels.clear();
0148         endResetModel();
0149     }
0150 
0151 private:
0152     QList<DvbPreviewChannel> channels;
0153 };
0154 
0155 Q_DECLARE_METATYPE(const DvbPreviewChannel *)
0156 
0157 QAbstractItemModel *DvbPreviewChannelTableModel::createProxyModel(QObject *parent)
0158 {
0159     QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(parent);
0160     proxyModel->setDynamicSortFilter(true);
0161     proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
0162     proxyModel->setSortLocaleAware(true);
0163     proxyModel->setSourceModel(this);
0164     return proxyModel;
0165 }
0166 
0167 int DvbPreviewChannelTableModel::columnCount(const QModelIndex &parent) const
0168 {
0169     if (parent.isValid()) {
0170         return 0;
0171     }
0172 
0173     return 3;
0174 }
0175 
0176 int DvbPreviewChannelTableModel::rowCount(const QModelIndex &parent) const
0177 {
0178     if (parent.isValid()) {
0179         return 0;
0180     }
0181 
0182     return channels.size();
0183 }
0184 
0185 QVariant DvbPreviewChannelTableModel::data(const QModelIndex &index, int role) const
0186 {
0187     const DvbPreviewChannel &channel = channels.at(index.row());
0188 
0189     switch (role) {
0190     case Qt::DecorationRole:
0191         if (index.column() == 0) {
0192             if (channel.hasVideo) {
0193                 if (!channel.isScrambled) {
0194                     return QIcon::fromTheme(QLatin1String("video-television"), QIcon(":video-television"));
0195                 } else {
0196                     return QIcon::fromTheme(QLatin1String("video-television-encrypted"), QIcon(":video-television-encrypted"));
0197                 }
0198             } else {
0199                 if (!channel.isScrambled) {
0200                     return QIcon::fromTheme(QLatin1String("text-speak"), QIcon(":text-speak"));
0201                 } else {
0202                     return QIcon::fromTheme(QLatin1String("audio-radio-encrypted"), QIcon(":audio-radio-encrypted"));
0203                 }
0204             }
0205         }
0206 
0207         break;
0208     case Qt::DisplayRole:
0209         switch (index.column()) {
0210         case 0:
0211             return channel.name;
0212         case 1:
0213             return channel.provider;
0214         case 2:
0215             return channel.snr;
0216         }
0217 
0218         break;
0219     case DvbPreviewChannelRole:
0220         return QVariant::fromValue(&channel);
0221     }
0222 
0223     return QVariant();
0224 }
0225 
0226 QVariant DvbPreviewChannelTableModel::headerData(int section, Qt::Orientation orientation,
0227     int role) const
0228 {
0229     if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
0230         return QVariant();
0231     }
0232 
0233     switch (section) {
0234     case 0:
0235         return i18nc("@title:column tv show", "Channel");
0236     case 1:
0237         return i18n("Provider");
0238     case 2:
0239         return i18n("SNR");
0240     }
0241 
0242     return QVariant();
0243 }
0244 
0245 DvbScanDialog::DvbScanDialog(DvbManager *manager_, QWidget *parent) : QDialog(parent),
0246     manager(manager_), internal(NULL)
0247 {
0248     setWindowTitle(i18n("Channels"));
0249 
0250     QWidget *mainWidget = new QWidget(this);
0251     QBoxLayout *mainLayout = new QHBoxLayout(mainWidget);
0252 
0253     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0254     connect(buttonBox, &QDialogButtonBox::accepted, this, &DvbScanDialog::dialogAccepted);
0255     connect(buttonBox, &QDialogButtonBox::rejected, this, &DvbScanDialog::reject);
0256 
0257     QGroupBox *groupBox = new QGroupBox(i18n("Channels"), mainWidget);
0258     QBoxLayout *groupLayout = new QVBoxLayout(groupBox);
0259     QBoxLayout *boxLayout = new QHBoxLayout();
0260 
0261     channelModel = new DvbChannelModel(this);
0262     channelModel->cloneFrom(manager->getChannelModel());
0263     DvbChannelTableModel *channelTableModel = new DvbChannelTableModel(this);
0264 
0265     DvbChannelView *channelView = new DvbChannelView(groupBox);
0266     channelView->setContextMenuPolicy(Qt::ActionsContextMenu);
0267     channelView->setDragDropMode(QAbstractItemView::InternalMove);
0268     channelView->setModel(channelTableModel);
0269     channelView->setRootIsDecorated(false);
0270     channelView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0271     QHeaderView *header = manager->getChannelView()->header();
0272     channelView->sortByColumn(header->sortIndicatorSection(), header->sortIndicatorOrder());
0273     channelView->setSortingEnabled(true);
0274     channelTableModel->setChannelModel(channelModel);
0275     connect(channelTableModel, &DvbChannelTableModel::checkChannelDragAndDrop, channelView, &DvbChannelView::checkChannelDragAndDrop);
0276 
0277     QAction *action = channelView->addEditAction();
0278     QPushButton *pushButton = new QPushButton(action->icon(), action->text(), groupBox);
0279     connect(pushButton, &QPushButton::clicked, channelView, &DvbChannelView::editChannel);
0280     boxLayout->addWidget(pushButton);
0281 
0282     action = channelView->addRemoveAction();
0283     pushButton = new QPushButton(action->icon(), action->text(), groupBox);
0284     connect(pushButton, &QPushButton::clicked, channelView, &DvbChannelView::removeChannel);
0285     boxLayout->addWidget(pushButton);
0286 
0287     pushButton = new QPushButton(QIcon::fromTheme(QLatin1String("edit-clear-list"), QIcon(":edit-clear-list")),
0288         i18nc("remove all items from a list", "Clear"), groupBox);
0289     connect(pushButton, &QPushButton::clicked, channelView, &DvbChannelView::removeAllChannels);
0290     boxLayout->addWidget(pushButton);
0291     groupLayout->addLayout(boxLayout);
0292 
0293     groupLayout->addWidget(channelView);
0294     mainLayout->addWidget(groupBox);
0295 
0296     boxLayout = new QVBoxLayout();
0297 
0298     groupBox = new QGroupBox(i18n("Channel Scan"), mainWidget);
0299     groupLayout = new QVBoxLayout(groupBox);
0300 
0301     groupLayout->addWidget(new QLabel(i18n("Source:")));
0302 
0303     sourceBox = new QComboBox(groupBox);
0304     groupLayout->addWidget(sourceBox);
0305 
0306     otherNitCheckBox = new QCheckBox(i18n("Search transponders for other Networks"), groupBox);
0307     otherNitCheckBox->setWhatsThis(i18n("On certain networks, it is possible that some transponders are encoded on separate Network Information Tables (other NITs). This is more common on DVB-C systems. Clicking on this icon will change the scan algorithm to take those other NIT data into account. Please notice that the scan will be a lot more slow if enabled."));
0308     groupLayout->addWidget(otherNitCheckBox);
0309 
0310     scanButton = new QPushButton(QIcon::fromTheme(QLatin1String("edit-find"), QIcon(":edit-find")), i18n("Start Scan"), groupBox);
0311     scanButton->setCheckable(true);
0312     connect(scanButton, &QPushButton::clicked, this, &DvbScanDialog::scanButtonClicked);
0313     groupLayout->addWidget(scanButton);
0314 
0315     QLabel *label = new QLabel(i18n("Scan data last updated on %1",
0316         QLocale().toString(manager->getScanDataDate(), QLocale::ShortFormat)));
0317     label->setWordWrap(true);
0318     groupLayout->addWidget(label);
0319 
0320     QGridLayout *gridLayout = new QGridLayout();
0321 
0322     gridLayout->addWidget(new QLabel(i18n("Signal:")), 0, 0);
0323 
0324     signalWidget = new DvbGradProgress(groupBox);
0325     gridLayout->addWidget(signalWidget, 0, 1);
0326 
0327     gridLayout->addWidget(new QLabel(i18n("SNR:")), 1, 0);
0328 
0329     snrWidget = new DvbGradProgress(groupBox);
0330     gridLayout->addWidget(snrWidget, 1, 1);
0331 
0332     gridLayout->addWidget(new QLabel(i18n("Tuned:")), 2, 0);
0333 
0334     tunedLed = new KLed(groupBox);
0335     gridLayout->addWidget(tunedLed, 2, 1);
0336     groupLayout->addLayout(gridLayout);
0337 
0338     progressBar = new QProgressBar(groupBox);
0339     progressBar->setValue(0);
0340     groupLayout->addWidget(progressBar);
0341     boxLayout->addWidget(groupBox);
0342 
0343     boxLayout->addStretch();
0344 
0345     groupBox = new QGroupBox(i18n("Filter"), mainWidget);
0346     groupLayout = new QVBoxLayout(groupBox);
0347 
0348     ftaCheckBox = new QCheckBox(i18n("Free to air"), groupBox);
0349     groupLayout->addWidget(ftaCheckBox);
0350 
0351     radioCheckBox = new QCheckBox(i18n("Radio"), groupBox);
0352     groupLayout->addWidget(radioCheckBox);
0353 
0354     tvCheckBox = new QCheckBox(i18n("TV"), groupBox);
0355     groupLayout->addWidget(tvCheckBox);
0356 
0357     providerCheckBox = new QCheckBox(i18n("Provider:"), groupBox);
0358     groupLayout->addWidget(providerCheckBox);
0359 
0360     providerBox = new QComboBox(groupBox);
0361     providerBox->setEnabled(false);
0362     connect(providerCheckBox, &QCheckBox::clicked, providerBox, &QComboBox::setEnabled);
0363     groupLayout->addWidget(providerBox);
0364 
0365     pushButton = new QPushButton(i18n("Add Filtered"), groupBox);
0366     connect(pushButton, &QPushButton::clicked, this, &DvbScanDialog::addFilteredChannels);
0367     groupLayout->addWidget(pushButton);
0368 
0369     pushButton = new QPushButton(i18n("Add Selected"), groupBox);
0370     connect(pushButton, &QPushButton::clicked, this, &DvbScanDialog::addSelectedChannels);
0371     groupLayout->addWidget(pushButton);
0372     boxLayout->addWidget(groupBox);
0373     mainLayout->addLayout(boxLayout);
0374 
0375     groupBox = new QGroupBox(i18n("Scan Results"), mainWidget);
0376     groupLayout = new QVBoxLayout(groupBox);
0377 
0378     previewModel = new DvbPreviewChannelTableModel(this);
0379     scanResultsView = new QTreeView(groupBox);
0380     scanResultsView->setModel(previewModel->createProxyModel(groupBox));
0381     scanResultsView->setRootIsDecorated(false);
0382     scanResultsView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0383     scanResultsView->sortByColumn(0, Qt::AscendingOrder);
0384     scanResultsView->setSortingEnabled(true);
0385     groupLayout->addWidget(scanResultsView);
0386     mainLayout->addWidget(groupBox);
0387 
0388     setDevice(manager->getLiveView()->getDevice());
0389 
0390     if (device != NULL) {
0391         sourceBox->addItem(i18n("Current Transponder"));
0392         sourceBox->setEnabled(false);
0393         isLive = true;
0394     } else {
0395         QStringList list = manager->getSources();
0396 
0397         if (!list.isEmpty()) {
0398             sourceBox->addItems(list);
0399         } else {
0400             sourceBox->setEnabled(false);
0401             scanButton->setEnabled(false);
0402         }
0403 
0404         isLive = false;
0405     }
0406 
0407     connect(&statusTimer, &QTimer::timeout, this, &DvbScanDialog::updateStatus);
0408 
0409     mainLayout = new QVBoxLayout;
0410     setLayout(mainLayout);
0411     mainLayout->addWidget(mainWidget);
0412     mainLayout->addWidget(buttonBox);
0413 }
0414 
0415 DvbScanDialog::~DvbScanDialog()
0416 {
0417     if (!isLive && device)
0418         manager->releaseDevice(device, DvbManager::Exclusive);
0419     delete internal;
0420 }
0421 
0422 void DvbScanDialog::scanButtonClicked(bool checked)
0423 {
0424     if (!checked) {
0425         // stop scan
0426         Q_ASSERT(internal != NULL);
0427         scanButton->setText(i18n("Start Scan"));
0428         progressBar->setValue(0);
0429 
0430         delete internal;
0431         internal = NULL;
0432 
0433         if (!isLive) {
0434             manager->releaseDevice(device, DvbManager::Exclusive);
0435             setDevice(NULL);
0436         }
0437 
0438         return;
0439     }
0440 
0441     // start scan
0442     Q_ASSERT(internal == NULL);
0443 
0444     if (!manager->getLiveView()->getChannel().isValid()) {
0445         isLive = false; // FIXME workaround
0446     }
0447 
0448     if (isLive) {
0449         const DvbSharedChannel &channel = manager->getLiveView()->getChannel();
0450         internal = new DvbScan(device, channel->source, channel->transponder, otherNitCheckBox->isChecked());
0451     } else {
0452         QString source = sourceBox->currentText();
0453         setDevice(manager->requestExclusiveDevice(source));
0454 
0455         if (device != NULL) {
0456             // FIXME ugly
0457             QString autoScanSource = manager->getAutoScanSource(source);
0458 
0459             if (autoScanSource.isEmpty()) {
0460                 internal = new DvbScan(device, source,
0461                     manager->getTransponders(device, source), otherNitCheckBox->isChecked());
0462             } else {
0463                 internal = new DvbScan(device, source, autoScanSource, otherNitCheckBox->isChecked());
0464             }
0465         } else {
0466             scanButton->setChecked(false);
0467             KMessageBox::information(this,
0468                 i18nc("message box", "No available device found."));
0469             return;
0470         }
0471     }
0472 
0473     scanButton->setText(i18n("Stop Scan"));
0474     providers.clear();
0475     providerBox->clear();
0476     previewModel->removeChannels();
0477 
0478     connect(internal, &DvbScan::foundChannels, this, &DvbScanDialog::foundChannels);
0479     connect(internal, &DvbScan::scanProgress, progressBar, &QProgressBar::setValue);
0480     // calling scanFinished() will delete internal, so we have to queue the signal!
0481     connect(internal, &DvbScan::scanFinished, this, &DvbScanDialog::scanFinished, Qt::QueuedConnection);
0482 
0483     internal->start();
0484 }
0485 
0486 void DvbScanDialog::dialogAccepted()
0487 {
0488     manager->getChannelModel()->cloneFrom(channelModel);
0489     manager->getChannelModel()->channelFlush();
0490 
0491     QDialog::accept();
0492 }
0493 
0494 static bool localeAwareLessThan2(const QString &x, const QString &y)
0495 {
0496     return x.localeAwareCompare(y) < 0;
0497 }
0498 
0499 void DvbScanDialog::foundChannels(const QList<DvbPreviewChannel> &channels)
0500 {
0501     previewModel->appendChannels(channels);
0502 
0503     foreach (const DvbPreviewChannel &channel, channels) {
0504         if (channel.provider.isEmpty()) {
0505             continue;
0506         }
0507 
0508         QStringList::const_iterator it = std::lower_bound(providers.constBegin(),
0509             providers.constEnd(), channel.provider, localeAwareLessThan2);
0510 
0511         if ((it != providers.constEnd()) && (*it == channel.provider)) {
0512             continue;
0513         }
0514 
0515         int pos = it - providers.constBegin();
0516         providers.insert(pos, channel.provider);
0517         providerBox->insertItem(pos, channel.provider);
0518     }
0519 }
0520 
0521 void DvbScanDialog::scanFinished()
0522 {
0523     // the state may have changed because the signal is queued
0524     if (scanButton->isChecked()) {
0525         scanButton->setChecked(false);
0526         scanButtonClicked(false);
0527     }
0528 }
0529 
0530 void DvbScanDialog::updateStatus()
0531 {
0532     if (device->getDeviceState() != DvbDevice::DeviceIdle) {
0533         DvbBackendDevice::Scale scaleSnr, scaleSignal;
0534         float signal = device->getSignal(scaleSignal);
0535         float snr = device->getSnr(scaleSnr);
0536 
0537         signalWidget->setValue(signal, scaleSignal);
0538         snrWidget->setValue(snr, scaleSnr);
0539         tunedLed->setState(device->isTuned() ? KLed::On : KLed::Off);
0540     }
0541 }
0542 
0543 void DvbScanDialog::addSelectedChannels()
0544 {
0545     QSet<int> selectedRows;
0546 
0547     foreach (const QModelIndex &modelIndex,
0548          scanResultsView->selectionModel()->selectedIndexes()) {
0549         if (!selectedRows.contains(modelIndex.row())) {
0550             selectedRows.insert(modelIndex.row());
0551             const DvbChannel *channel = scanResultsView->model()->data(modelIndex,
0552                 DvbPreviewChannelTableModel::DvbPreviewChannelRole).
0553                 value<const DvbPreviewChannel *>();
0554             DvbChannel newChannel(*channel);
0555             channelModel->addChannel(newChannel);
0556         }
0557     }
0558 }
0559 
0560 void DvbScanDialog::addFilteredChannels()
0561 {
0562     foreach (const DvbPreviewChannel &channel, previewModel->getChannels()) {
0563         if (ftaCheckBox->isChecked()) {
0564             // only fta channels
0565             if (channel.isScrambled) {
0566                 continue;
0567             }
0568         }
0569 
0570         if (radioCheckBox->isChecked()) {
0571             if (!tvCheckBox->isChecked()) {
0572                 // only radio channels
0573                 if (channel.hasVideo) {
0574                     continue;
0575                 }
0576             }
0577         } else {
0578             if (tvCheckBox->isChecked()) {
0579                 // only tv channels
0580                 if (!channel.hasVideo) {
0581                     continue;
0582                 }
0583             }
0584         }
0585 
0586         if (providerCheckBox->isChecked()) {
0587             // only channels from a certain provider
0588             if (channel.provider != providerBox->currentText()) {
0589                 continue;
0590             }
0591         }
0592 
0593         DvbChannel newChannel(channel);
0594         channelModel->addChannel(newChannel);
0595     }
0596 }
0597 
0598 void DvbScanDialog::setDevice(DvbDevice *newDevice)
0599 {
0600     device = newDevice;
0601 
0602     if (device == NULL) {
0603         statusTimer.stop();
0604         signalWidget->setValue(0, DvbBackendDevice::NotSupported);
0605         snrWidget->setValue(0, DvbBackendDevice::NotSupported);
0606         tunedLed->setState(KLed::Off);
0607     } else {
0608         statusTimer.start(1000);
0609         updateStatus();
0610     }
0611 }
0612 
0613 #include "moc_dvbscandialog.cpp"