File indexing completed on 2024-03-24 17:00:27

0001 /*
0002     SPDX-FileCopyrightText: 2010 Alex Fiestas <alex@eyeos.org>
0003     SPDX-FileCopyrightText: 2010 UFO Coders <info@ufocoders.com>
0004 
0005     SPDX-License-Identifier: GPL-3.0-or-later
0006 */
0007 
0008 #include "discover.h"
0009 #include "../bluewizard.h"
0010 #include "../wizardagent.h"
0011 #include "bluedevil_wizard.h"
0012 
0013 #include <QAction>
0014 #include <QRegularExpressionValidator>
0015 #include <QScroller>
0016 #include <QSortFilterProxyModel>
0017 
0018 #include <KLocalizedString>
0019 #include <KMessageWidget>
0020 
0021 #include <BluezQt/Adapter>
0022 #include <BluezQt/Device>
0023 #include <BluezQt/DevicesModel>
0024 #include <BluezQt/Manager>
0025 
0026 class DevicesProxyModel : public QSortFilterProxyModel
0027 {
0028 public:
0029     explicit DevicesProxyModel(QObject *parent);
0030 
0031     void setDevicesModel(BluezQt::DevicesModel *model);
0032 
0033     QVariant data(const QModelIndex &index, int role) const override;
0034     bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
0035     bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
0036 
0037     BluezQt::DevicePtr device(const QModelIndex &index) const;
0038 
0039     QString searchString() const;
0040     void setSearchString(const QString &searchString);
0041 
0042 private:
0043     BluezQt::DevicesModel *m_devicesModel = nullptr;
0044     QString m_searchString;
0045 };
0046 
0047 DevicesProxyModel::DevicesProxyModel(QObject *parent)
0048     : QSortFilterProxyModel(parent)
0049 {
0050     setDynamicSortFilter(true);
0051     sort(0, Qt::DescendingOrder);
0052 }
0053 
0054 void DevicesProxyModel::setDevicesModel(BluezQt::DevicesModel *model)
0055 {
0056     m_devicesModel = model;
0057     setSourceModel(model);
0058 }
0059 
0060 QVariant DevicesProxyModel::data(const QModelIndex &index, int role) const
0061 {
0062     switch (role) {
0063     case Qt::DecorationRole:
0064         return QIcon::fromTheme(index.data(BluezQt::DevicesModel::IconRole).toString(), QIcon::fromTheme(QStringLiteral("preferences-system-bluetooth")));
0065 
0066     default:
0067         return QSortFilterProxyModel::data(index, role);
0068     }
0069 }
0070 
0071 bool DevicesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
0072 {
0073     qint16 leftRssi = left.data(BluezQt::DevicesModel::RssiRole).toInt();
0074     qint16 rightRssi = right.data(BluezQt::DevicesModel::RssiRole).toInt();
0075 
0076     return leftRssi < rightRssi;
0077 }
0078 
0079 bool DevicesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
0080 {
0081     QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
0082 
0083     bool devicePaired = index.data(BluezQt::DevicesModel::PairedRole).toBool();
0084     if (devicePaired) {
0085         return false;
0086     }
0087 
0088     bool adapterPowered = index.data(BluezQt::DevicesModel::AdapterPoweredRole).toBool();
0089     bool adapterPairable = index.data(BluezQt::DevicesModel::AdapterPairableRole).toBool();
0090     if (!adapterPowered || !adapterPairable) {
0091         return false;
0092     }
0093 
0094     if (!m_searchString.isEmpty()) {
0095         const QString displayString = index.data(Qt::DisplayRole).toString();
0096         if (!displayString.contains(m_searchString, Qt::CaseInsensitive)) {
0097             return false;
0098         }
0099     }
0100 
0101     return true;
0102 }
0103 
0104 BluezQt::DevicePtr DevicesProxyModel::device(const QModelIndex &index) const
0105 {
0106     Q_ASSERT(m_devicesModel);
0107     return m_devicesModel->device(mapToSource(index));
0108 }
0109 
0110 QString DevicesProxyModel::searchString() const
0111 {
0112     return m_searchString;
0113 }
0114 
0115 void DevicesProxyModel::setSearchString(const QString &searchString)
0116 {
0117     if (m_searchString == searchString) {
0118         return;
0119     }
0120 
0121     m_searchString = searchString;
0122     invalidateFilter();
0123 }
0124 
0125 DiscoverPage::DiscoverPage(BlueWizard *parent)
0126     : QWizardPage(parent)
0127     , m_wizard(parent)
0128     , m_model(new DevicesProxyModel(this))
0129 {
0130     setupUi(this);
0131     setTitle(i18n("Select a device"));
0132 
0133     deviceView->setModel(m_model);
0134 
0135     connect(deviceView->selectionModel(), &QItemSelectionModel::currentChanged, this, &DiscoverPage::indexSelected);
0136     connect(deviceView, &QListView::doubleClicked, this, &DiscoverPage::itemDoubleClicked);
0137 
0138     QAction *findAction = new QAction(this);
0139     connect(findAction, &QAction::triggered, searchField, qOverload<>(&QWidget::setFocus));
0140     findAction->setShortcut(QKeySequence::Find);
0141     connect(searchField, &QLineEdit::textChanged, m_model, &DevicesProxyModel::setSearchString);
0142     addAction(findAction);
0143 }
0144 
0145 void DiscoverPage::initializePage()
0146 {
0147     qCDebug(BLUEDEVIL_WIZARD_LOG) << "Initialize Discover Page";
0148 
0149     QList<QWizard::WizardButton> list;
0150     list << QWizard::Stretch;
0151     list << QWizard::NextButton;
0152     list << QWizard::CancelButton;
0153     m_wizard->setButtonLayout(list);
0154 
0155     QRegularExpression rx(QStringLiteral("[0-9]{1,16}"));
0156     pinText->setValidator(new QRegularExpressionValidator(rx, this));
0157 
0158     connect(manualPin, &QCheckBox::toggled, pinText, &QLineEdit::setEnabled);
0159     connect(manualPin, &QCheckBox::toggled, this, &DiscoverPage::completeChanged);
0160     connect(pinText, &QLineEdit::textChanged, this, &DiscoverPage::completeChanged);
0161 
0162     m_manager = m_wizard->manager();
0163 
0164     m_adapter = m_manager->usableAdapter();
0165     if (m_adapter && !m_adapter->isDiscovering()) {
0166         qCDebug(BLUEDEVIL_WIZARD_LOG) << "Starting scanning";
0167         m_adapter->startDiscovery();
0168     }
0169 
0170     if (!m_model->sourceModel()) {
0171         m_model->setDevicesModel(new BluezQt::DevicesModel(m_manager, this));
0172     }
0173 
0174     deviceView->setCurrentIndex(QModelIndex());
0175     manualPin->setChecked(false);
0176     pinText->clear();
0177 
0178     checkAdapters();
0179     connect(m_manager, &BluezQt::Manager::adapterAdded, this, &DiscoverPage::checkAdapters);
0180     connect(m_manager, &BluezQt::Manager::adapterChanged, this, &DiscoverPage::checkAdapters);
0181     connect(m_manager, &BluezQt::Manager::bluetoothBlockedChanged, this, &DiscoverPage::checkAdapters);
0182     connect(m_manager, &BluezQt::Manager::usableAdapterChanged, this, &DiscoverPage::usableAdapterChanged);
0183 
0184     QScroller::grabGesture(deviceView);
0185 }
0186 
0187 bool DiscoverPage::isComplete() const
0188 {
0189     if (!m_wizard->device() || m_wizard->device()->isPaired()) {
0190         return false;
0191     }
0192 
0193     if (manualPin->isChecked() && pinText->text().isEmpty()) {
0194         return false;
0195     }
0196 
0197     return true;
0198 }
0199 
0200 int DiscoverPage::nextId() const
0201 {
0202     if (!isComplete()) {
0203         return BlueWizard::Discover;
0204     }
0205 
0206     if (!m_wizard) {
0207         return BlueWizard::Discover;
0208     }
0209 
0210     if (!m_wizard->device()) {
0211         return BlueWizard::Discover;
0212     }
0213 
0214     BluezQt::DevicePtr device = m_wizard->device();
0215 
0216     if (device->isPaired()) {
0217         return BlueWizard::Discover;
0218     }
0219 
0220     if (m_adapter && m_adapter->isDiscovering()) {
0221         qCDebug(BLUEDEVIL_WIZARD_LOG) << "Stopping scanning";
0222         m_adapter->stopDiscovery();
0223     }
0224 
0225     QString pin = m_wizard->agent()->getPin(device);
0226 
0227     if (manualPin->isChecked()) {
0228         pin = pinText->text();
0229         m_wizard->agent()->setPin(pin);
0230     }
0231 
0232     qCDebug(BLUEDEVIL_WIZARD_LOG) << "Device type: " << BluezQt::Device::typeToString(device->type());
0233     qCDebug(BLUEDEVIL_WIZARD_LOG) << "Legacy: " << device->hasLegacyPairing();
0234     qCDebug(BLUEDEVIL_WIZARD_LOG) << "From DB: " << m_wizard->agent()->isFromDatabase();
0235     qCDebug(BLUEDEVIL_WIZARD_LOG) << "PIN: " << pin;
0236 
0237     // NULL pin means that we should only connect to device
0238     if (pin == QLatin1String("NULL")) {
0239         return BlueWizard::Connect;
0240     }
0241 
0242     return BlueWizard::Pairing;
0243 }
0244 
0245 void DiscoverPage::showEvent(QShowEvent *event)
0246 {
0247     Q_UNUSED(event);
0248     // Focus the device view by default, not the search field.
0249     deviceView->setFocus();
0250 }
0251 
0252 void DiscoverPage::indexSelected(const QModelIndex &index)
0253 {
0254     if (m_wizard->currentId() != BlueWizard::Discover) {
0255         return;
0256     }
0257 
0258     BluezQt::DevicePtr device = m_model->device(index);
0259     m_wizard->setDevice(device);
0260 
0261     Q_EMIT completeChanged();
0262 }
0263 
0264 void DiscoverPage::itemDoubleClicked(const QModelIndex &index)
0265 {
0266     indexSelected(index);
0267     m_wizard->next();
0268 }
0269 
0270 void DiscoverPage::usableAdapterChanged(BluezQt::AdapterPtr adapter)
0271 {
0272     m_adapter = adapter;
0273 
0274     if (m_adapter && !m_adapter->isDiscovering()) {
0275         m_adapter->startDiscovery();
0276     }
0277 
0278     checkAdapters();
0279 }
0280 
0281 void DiscoverPage::checkAdapters()
0282 {
0283     bool error = false;
0284 
0285     Q_FOREACH (BluezQt::AdapterPtr adapter, m_manager->adapters()) {
0286         if (!adapter->isPowered() || !adapter->isPairable()) {
0287             error = true;
0288             break;
0289         }
0290     }
0291 
0292     delete m_warningWidget;
0293     m_warningWidget = nullptr;
0294 
0295     if (!error && !m_manager->isBluetoothBlocked()) {
0296         return;
0297     }
0298 
0299     QAction *fixAdapters = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok-apply")), i18nc("Action to fix a problem", "Fix it"), m_warningWidget);
0300     connect(fixAdapters, &QAction::triggered, this, &DiscoverPage::fixAdaptersError);
0301 
0302     m_warningWidget = new KMessageWidget(this);
0303     m_warningWidget->setMessageType(KMessageWidget::Warning);
0304     m_warningWidget->setCloseButtonVisible(false);
0305     if (m_manager->isBluetoothBlocked()) {
0306         m_warningWidget->setText(i18n("Bluetooth is disabled."));
0307         fixAdapters->setText(i18nc("Action to enable Bluetooth adapter", "Enable"));
0308     } else {
0309         m_warningWidget->setText(i18n("Your Bluetooth adapter is not pairable."));
0310         fixAdapters->setText(i18nc("Action to make Bluetooth adapter pairable", "Make Pairable"));
0311     }
0312 
0313     m_warningWidget->addAction(fixAdapters);
0314     verticalLayout->insertWidget(0, m_warningWidget);
0315 }
0316 
0317 void DiscoverPage::fixAdaptersError()
0318 {
0319     m_manager->setBluetoothBlocked(false);
0320 
0321     Q_FOREACH (BluezQt::AdapterPtr adapter, m_manager->adapters()) {
0322         adapter->setPowered(true);
0323         adapter->setPairable(true);
0324     }
0325 }