diff --git a/CMakeLists.txt b/CMakeLists.txt index dce84405c..eac98539f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,8 +161,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android") set(ANDROID_PKG AndroidExtras) set(ANDROID_LIB Qt5::AndroidExtras) endif() -find_package(Qt5 REQUIRED COMPONENTS Core Concurrent Widgets Network ${WEBKIT_PKG} ${PRINTING_PKG} Svg Test LinguistTools ${QT_QUICK_PKG} ${ANDROID_PKG}) -set(QT_LIBRARIES Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${WEBKIT_LIB} ${PRINTING_LIB} Qt5::Svg ${QT_QUICK_LIB} ${ANDROID_LIB}) +find_package(Qt5 REQUIRED COMPONENTS Core Concurrent Widgets Network ${WEBKIT_PKG} ${PRINTING_PKG} Svg Test LinguistTools ${QT_QUICK_PKG} ${ANDROID_PKG} Bluetooth) +set(QT_LIBRARIES Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${WEBKIT_LIB} ${PRINTING_LIB} Qt5::Svg ${QT_QUICK_LIB} ${ANDROID_LIB} Qt5::Bluetooth) set(QT_TEST_LIBRARIES ${QT_LIBRARIES} Qt5::Test) # Generate the ssrf-config.h every 'make' @@ -352,6 +352,7 @@ source_group("Subsurface Models" FILES ${SUBSURFACE_MODELS}) set(SUBSURFACE_INTERFACE qt-ui/updatemanager.cpp qt-ui/about.cpp + qt-ui/btdeviceselectiondialog.cpp qt-ui/divecomputermanagementdialog.cpp qt-ui/divelistview.cpp qt-ui/diveplanner.cpp diff --git a/libdivecomputer.h b/libdivecomputer.h index dfb62675f..f5c0cadbe 100644 --- a/libdivecomputer.h +++ b/libdivecomputer.h @@ -37,6 +37,7 @@ typedef struct device_data_t bool create_new_trip; bool libdc_log; bool libdc_dump; + bool bluetooth_mode; FILE *libdc_logfile; struct dive_table *download_table; } device_data_t; diff --git a/qt-ui/btdeviceselectiondialog.cpp b/qt-ui/btdeviceselectiondialog.cpp new file mode 100644 index 000000000..436dca510 --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.cpp @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include "ui_btdeviceselectiondialog.h" +#include "btdeviceselectiondialog.h" + +BtDeviceSelectionDialog::BtDeviceSelectionDialog(QWidget *parent) : + QDialog(parent), + localDevice(new QBluetoothLocalDevice), + ui(new Ui::BtDeviceSelectionDialog) +{ + // Check if Bluetooth is available on this device + if (!localDevice->isValid()) { + QMessageBox::warning(this, tr("Warning"), + "This should never happen, please contact the Subsurface developers " + "and tell them that the Bluetooth download mode doesn't work."); + return; + } + + ui->setupUi(this); + + // Quit button callbacks + QShortcut *quit = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this); + connect(quit, SIGNAL(activated()), this, SLOT(reject())); + connect(ui->quit, SIGNAL(clicked()), this, SLOT(reject())); + + // Disable the save button because there is no device selected + ui->save->setEnabled(false); + + connect(ui->discoveredDevicesList, SIGNAL(itemActivated(QListWidgetItem*)), + this, SLOT(itemActivated(QListWidgetItem*))); + + // Set UI information about the local device + ui->deviceAddress->setText(localDevice->address().toString()); + ui->deviceName->setText(localDevice->name()); + + connect(localDevice, SIGNAL(hostModeStateChanged(QBluetoothLocalDevice::HostMode)), + this, SLOT(hostModeStateChanged(QBluetoothLocalDevice::HostMode))); + + // Initialize the state of the local device and activate/deactive the scan button + hostModeStateChanged(localDevice->hostMode()); + + // Intialize the discovery agent + remoteDeviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(); + + connect(remoteDeviceDiscoveryAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), + this, SLOT(addRemoteDevice(QBluetoothDeviceInfo))); + connect(remoteDeviceDiscoveryAgent, SIGNAL(finished()), + this, SLOT(remoteDeviceScanFinished())); + + // Add context menu for devices to be able to pair them + ui->discoveredDevicesList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->discoveredDevicesList, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(displayPairingMenu(QPoint))); + connect(localDevice, SIGNAL(pairingFinished(QBluetoothAddress, QBluetoothLocalDevice::Pairing)), + this, SLOT(pairingFinished(QBluetoothAddress, QBluetoothLocalDevice::Pairing))); + + connect(localDevice, SIGNAL(error(QBluetoothLocalDevice::Error)), + this, SLOT(error(QBluetoothLocalDevice::Error))); +} + +BtDeviceSelectionDialog::~BtDeviceSelectionDialog() +{ + delete ui; +} + +void BtDeviceSelectionDialog::on_changeDeviceState_clicked() +{ + if (localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff) { + ui->dialogStatus->setText("Trying to turn on the local Bluetooth device..."); + localDevice->powerOn(); + } else { + ui->dialogStatus->setText("Trying to turn off the local Bluetooth device..."); + localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff); + } +} + +void BtDeviceSelectionDialog::on_save_clicked() +{ + // Get the selected device. There will be always a selected device if the save button is enabled. + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + QBluetoothDeviceInfo remoteDeviceInfo = currentItem->data(Qt::UserRole).value(); + + // Save the selected device + selectedRemoteDeviceInfo = QSharedPointer(new QBluetoothDeviceInfo(remoteDeviceInfo)); + + // Close the device selection dialog and set the result code to Accepted + accept(); +} + +void BtDeviceSelectionDialog::on_clear_clicked() +{ + ui->dialogStatus->setText("Remote devices list was cleaned."); + ui->discoveredDevicesList->clear(); + ui->save->setEnabled(false); +} + +void BtDeviceSelectionDialog::on_scan_clicked() +{ + ui->dialogStatus->setText("Scanning for remote devices..."); + remoteDeviceDiscoveryAgent->start(); + ui->scan->setEnabled(false); +} + +void BtDeviceSelectionDialog::remoteDeviceScanFinished() +{ + ui->dialogStatus->setText("Scanning finished."); + ui->scan->setEnabled(true); +} + +void BtDeviceSelectionDialog::hostModeStateChanged(QBluetoothLocalDevice::HostMode mode) +{ + bool on = !(mode == QBluetoothLocalDevice::HostPoweredOff); + + ui->dialogStatus->setText(QString("The local Bluetooth device was turned %1.") + .arg(on? "ON" : "OFF")); + ui->deviceState->setChecked(on); + ui->scan->setEnabled(on); +} + +void BtDeviceSelectionDialog::addRemoteDevice(const QBluetoothDeviceInfo &remoteDeviceInfo) +{ + QString deviceLable = QString("%1 (%2)").arg(remoteDeviceInfo.name()).arg(remoteDeviceInfo.address().toString()); + QList itemsWithSameSignature = ui->discoveredDevicesList->findItems(deviceLable, Qt::MatchStartsWith); + + // Check if the remote device is already in the list + if (itemsWithSameSignature.empty()) { + QListWidgetItem *item = new QListWidgetItem(deviceLable); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(remoteDeviceInfo.address()); + item->setData(Qt::UserRole, QVariant::fromValue(remoteDeviceInfo)); + + if (pairingStatus == QBluetoothLocalDevice::Paired) { + item->setText(QString("%1 [State: PAIRED]").arg(item->text())); + item->setBackgroundColor(QColor(Qt::gray)); + } else if (pairingStatus == QBluetoothLocalDevice::AuthorizedPaired) { + item->setText(QString("%1 [State: AUTHORIZED_PAIRED]").arg(item->text())); + item->setBackgroundColor(QColor(Qt::blue)); + } else { + item->setText(QString("%1 [State: UNPAIRED]").arg(item->text())); + item->setTextColor(QColor(Qt::black)); + } + + ui->discoveredDevicesList->addItem(item); + } +} + +void BtDeviceSelectionDialog::itemActivated(QListWidgetItem *item) +{ + QBluetoothDeviceInfo remoteDeviceInfo = item->data(Qt::UserRole).value(); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(remoteDeviceInfo.address()); + + if (pairingStatus == QBluetoothLocalDevice::Unpaired) { + ui->dialogStatus->setText(QString("The device %1 must be paired in order to be used. Please use the context menu for pairing options.") + .arg(remoteDeviceInfo.address().toString())); + ui->save->setEnabled(false); + } else { + ui->dialogStatus->setText(QString("The device %1 can be used for connection. You can press the Save button.") + .arg(remoteDeviceInfo.address().toString())); + ui->save->setEnabled(true); + } +} + +void BtDeviceSelectionDialog::displayPairingMenu(const QPoint &pos) +{ + QMenu menu(this); + QAction *pairAction = menu.addAction("Pair"); + QAction *removePairAction = menu.addAction("Remove Pairing"); + QAction *chosenAction = menu.exec(ui->discoveredDevicesList->viewport()->mapToGlobal(pos)); + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + QBluetoothDeviceInfo currentRemoteDeviceInfo = currentItem->data(Qt::UserRole).value(); + QBluetoothLocalDevice::Pairing pairingStatus = localDevice->pairingStatus(currentRemoteDeviceInfo.address()); + + //TODO: disable the actions + if (pairingStatus == QBluetoothLocalDevice::Unpaired) { + pairAction->setEnabled(true); + removePairAction->setEnabled(false); + } else { + pairAction->setEnabled(false); + removePairAction->setEnabled(true); + } + + if (chosenAction == pairAction) { + ui->dialogStatus->setText(QString("Trying to pair device %1") + .arg(currentRemoteDeviceInfo.address().toString())); + localDevice->requestPairing(currentRemoteDeviceInfo.address(), QBluetoothLocalDevice::Paired); + } else if (chosenAction == removePairAction) { + ui->dialogStatus->setText(QString("Trying to unpair device %1") + .arg(currentRemoteDeviceInfo.address().toString())); + localDevice->requestPairing(currentRemoteDeviceInfo.address(), QBluetoothLocalDevice::Unpaired); + } +} + +void BtDeviceSelectionDialog::pairingFinished(const QBluetoothAddress &address, QBluetoothLocalDevice::Pairing pairing) +{ + QString remoteDeviceStringAddress = address.toString(); + QList items = ui->discoveredDevicesList->findItems(remoteDeviceStringAddress, Qt::MatchContains); + + if (pairing == QBluetoothLocalDevice::Paired || pairing == QBluetoothLocalDevice::Paired ) { + ui->dialogStatus->setText(QString("Device %1 was paired.") + .arg(remoteDeviceStringAddress)); + + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + + item->setText(QString("%1 [State: PAIRED]").arg(remoteDeviceStringAddress)); + item->setBackgroundColor(QColor(Qt::gray)); + } + + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + + if (currentItem != NULL && currentItem->text().contains(remoteDeviceStringAddress, Qt::CaseInsensitive)) { + ui->dialogStatus->setText(QString("The device %1 can now be used for connection. You can press the Save button.") + .arg(remoteDeviceStringAddress)); + ui->save->setEnabled(true); + } + } else { + ui->dialogStatus->setText(QString("Device %1 was unpaired.") + .arg(remoteDeviceStringAddress)); + + for (int i = 0; i < items.count(); ++i) { + QListWidgetItem *item = items.at(i); + + item->setText(QString("%1 [State: UNPAIRED]").arg(remoteDeviceStringAddress)); + item->setBackgroundColor(QColor(Qt::white)); + } + + QListWidgetItem *currentItem = ui->discoveredDevicesList->currentItem(); + + if (currentItem != NULL && currentItem->text().contains(remoteDeviceStringAddress, Qt::CaseInsensitive)) { + ui->dialogStatus->setText(QString("The device %1 must be paired in order to be used. Please use the context menu for pairing options.") + .arg(remoteDeviceStringAddress)); + ui->save->setEnabled(false); + } + } +} + +void BtDeviceSelectionDialog::error(QBluetoothLocalDevice::Error error) +{ + ui->dialogStatus->setText(QString("Local device error: %1.") + .arg((error == QBluetoothLocalDevice::PairingError)? "Pairing error" : "Unknown error")); +} + +QString BtDeviceSelectionDialog::getSelectedDeviceAddress() +{ + if (selectedRemoteDeviceInfo) { + return selectedRemoteDeviceInfo.data()->address().toString(); + } + + return QString(); +} + +QString BtDeviceSelectionDialog::getSelectedDeviceName() +{ + if (selectedRemoteDeviceInfo) { + return selectedRemoteDeviceInfo.data()->name(); + } + + return QString(); +} diff --git a/qt-ui/btdeviceselectiondialog.h b/qt-ui/btdeviceselectiondialog.h new file mode 100644 index 000000000..6bcc43f34 --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.h @@ -0,0 +1,46 @@ +#ifndef BTDEVICESELECTIONDIALOG_H +#define BTDEVICESELECTIONDIALOG_H + +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QBluetoothDeviceInfo) + +namespace Ui { + class BtDeviceSelectionDialog; +} + +class BtDeviceSelectionDialog : public QDialog { + Q_OBJECT + +public: + explicit BtDeviceSelectionDialog(QWidget *parent = 0); + ~BtDeviceSelectionDialog(); + QString getSelectedDeviceAddress(); + QString getSelectedDeviceName(); + +private slots: + void on_changeDeviceState_clicked(); + void on_save_clicked(); + void on_clear_clicked(); + void on_scan_clicked(); + void remoteDeviceScanFinished(); + void hostModeStateChanged(QBluetoothLocalDevice::HostMode mode); + void addRemoteDevice(const QBluetoothDeviceInfo &remoteDeviceInfo); + void itemActivated(QListWidgetItem *item); + void displayPairingMenu(const QPoint &pos); + void pairingFinished(const QBluetoothAddress &address,QBluetoothLocalDevice::Pairing pairing); + void error(QBluetoothLocalDevice::Error error); + +private: + Ui::BtDeviceSelectionDialog *ui; + QBluetoothLocalDevice *localDevice; + QBluetoothDeviceDiscoveryAgent *remoteDeviceDiscoveryAgent; + QSharedPointer selectedRemoteDeviceInfo; +}; + +#endif // BTDEVICESELECTIONDIALOG_H diff --git a/qt-ui/btdeviceselectiondialog.ui b/qt-ui/btdeviceselectiondialog.ui new file mode 100644 index 000000000..c28bdcbe8 --- /dev/null +++ b/qt-ui/btdeviceselectiondialog.ui @@ -0,0 +1,214 @@ + + + BtDeviceSelectionDialog + + + + 0 + 0 + 735 + 460 + + + + Remote Bluetooth device selection + + + + + + + 0 + 0 + + + + + 75 + true + + + + Discovered devices + + + + + + + + + Save + + + + + + + + 0 + 0 + + + + Quit + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + 0 + 0 + + + + Scan + + + + + + + + 0 + 0 + + + + Clear + + + + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Local Bluetooth device details + + + false + + + + + + Name: + + + + + + + true + + + + + + + Address: + + + + + + + true + + + + + + + false + + + + 0 + 0 + + + + + 75 + true + + + + Bluetooth powered on + + + true + + + + + + + + 0 + 0 + + + + + 50 + false + + + + Turn On/Off + + + + + + + + + + + + + true + + + + + + + + diff --git a/qt-ui/downloadfromdivecomputer.cpp b/qt-ui/downloadfromdivecomputer.cpp index f1bb05823..276e4047c 100644 --- a/qt-ui/downloadfromdivecomputer.cpp +++ b/qt-ui/downloadfromdivecomputer.cpp @@ -99,6 +99,8 @@ DownloadFromDCWidget::DownloadFromDCWidget(QWidget *parent, Qt::WindowFlags f) : ui.ok->setEnabled(false); ui.downloadCancelRetryButton->setEnabled(true); ui.downloadCancelRetryButton->setText(tr("Download")); + + btDeviceSelectionDialog = 0; ui.chooseBluetoothDevice->setEnabled(ui.bluetoothMode->isChecked()); connect(ui.bluetoothMode, SIGNAL(stateChanged(int)), this, SLOT(enableBluetoothMode(int))); connect(ui.chooseBluetoothDevice, SIGNAL(clicked()), this, SLOT(selectRemoteBluetoothDevice())); @@ -311,7 +313,11 @@ void DownloadFromDCWidget::on_downloadCancelRetryButton_clicked() data.vendor = strdup(ui.vendor->currentText().toUtf8().data()); data.product = strdup(ui.product->currentText().toUtf8().data()); - if (same_string(data.vendor, "Uemis")) { + data.bluetooth_mode = ui.bluetoothMode->isChecked(); + if (data.bluetooth_mode) { + // Get the selected device address + data.devname = strdup(btDeviceSelectionDialog->getSelectedDeviceAddress().toUtf8().data()); + } else if (same_string(data.vendor, "Uemis")) { char *colon; char *devname = strdup(ui.device->currentText().toUtf8().data()); @@ -523,7 +529,24 @@ void DownloadFromDCWidget::markChildrenAsEnabled() void DownloadFromDCWidget::selectRemoteBluetoothDevice() { - //TODO add implementation + if (!btDeviceSelectionDialog) { + btDeviceSelectionDialog = new BtDeviceSelectionDialog(this); + connect(btDeviceSelectionDialog, SIGNAL(finished(int)), + this, SLOT(bluetoothSelectionDialogIsFinished(int))); + } + + btDeviceSelectionDialog->show(); +} + +void DownloadFromDCWidget::bluetoothSelectionDialogIsFinished(int result) +{ + if (result == QDialog::Accepted) { + /* Make the selected Bluetooth device default */ + ui.device->setCurrentText(btDeviceSelectionDialog->getSelectedDeviceName()); + } else if (result == QDialog::Rejected){ + /* Disable Bluetooth download mode */ + ui.bluetoothMode->setChecked(false); + } } void DownloadFromDCWidget::enableBluetoothMode(int state) diff --git a/qt-ui/downloadfromdivecomputer.h b/qt-ui/downloadfromdivecomputer.h index 0b63d28ed..734e5f7d1 100644 --- a/qt-ui/downloadfromdivecomputer.h +++ b/qt-ui/downloadfromdivecomputer.h @@ -10,6 +10,7 @@ #include "libdivecomputer.h" #include "configuredivecomputerdialog.h" #include "ui_downloadfromdivecomputer.h" +#include "btdeviceselectiondialog.h" class QStringListModel; @@ -81,6 +82,7 @@ slots: void pickDumpFile(); void pickLogFile(); void selectRemoteBluetoothDevice(); + void bluetoothSelectionDialogIsFinished(int result); private: void markChildrenAsDisabled(); @@ -106,6 +108,7 @@ private: bool dumpWarningShown; OstcFirmwareCheck *ostcFirmwareCheck; DiveImportedModel *diveImportedModel; + BtDeviceSelectionDialog *btDeviceSelectionDialog; public: bool preferDownloaded();