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"