File indexing completed on 2025-02-09 06:33:22
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 } 0326 0327 #include "moc_discover.cpp"