Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include "explorer.h"
++ #include <QTimer>
++ #include <QDebug>
++ #include <qsensor.h>
++ #include <QMetaObject>
++ #include <QMetaProperty>
++
++ Explorer::Explorer(QWidget *parent)
++ : QMainWindow(parent)
++ , m_sensor(0)
++ , ignoreItemChanged(false)
++ {
++ ui.setupUi(this);
++ // Clear out example data from the .ui file
++ ui.sensors->clear();
++ clearSensorProperties();
++ clearReading();
++
++ // Force types to be registered
++ (void)QSensor::sensorTypes();
++ // Listen for changes to the registered types
++ QSensor *sensor = new QSensor(QByteArray(), this);
++ connect(sensor, SIGNAL(availableSensorsChanged()), this, SLOT(loadSensors()));
++ }
++
++ Explorer::~Explorer()
++ {
++ }
++
++ void Explorer::loadSensors()
++ {
++ qDebug() << "Explorer::loadSensors";
++
++ // Clear out anything that's in there now
++ ui.sensors->clear();
++
++ for (const QByteArray &type : QSensor::sensorTypes()) {
++ qDebug() << "Found type" << type;
++ for (const QByteArray &identifier : QSensor::sensorsForType(type)) {
++ qDebug() << "Found identifier" << identifier;
++ // Don't put in sensors we can't connect to
++ QSensor sensor(type);
++ sensor.setIdentifier(identifier);
++ if (!sensor.connectToBackend()) {
++ qDebug() << "Couldn't connect to" << identifier;
++ continue;
++ }
++
++ qDebug() << "Adding identifier" << identifier;
++ QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << QString::fromLatin1(identifier));
++ item->setData(0, Qt::UserRole, QString::fromLatin1(type));
++ ui.sensors->addTopLevelItem(item);
++ }
++ }
++
++ if (ui.sensors->topLevelItemCount() == 0) {
++ QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << tr("No Sensors Found"));
++ item->setData(0, Qt::UserRole, QString());
++ ui.sensors->addTopLevelItem(item);
++ }
++
++ ui.sensors->setCurrentItem(0);
++ ui.scrollArea->hide();
++
++ resizeSensors();
++ }
++
++ void Explorer::resizeSensors()
++ {
++ ui.sensors->resizeColumnToContents(0);
++ int length = ui.sensors->header()->length() + 4;
++ ui.sensors->setFixedWidth(length);
++ }
++
++ void Explorer::on_sensors_currentItemChanged()
++ {
++ qDebug() << "Explorer::sensorSelected";
++
++ // Clear out anything that's in there now
++ if (m_sensor) {
++ delete m_sensor;
++ m_sensor = 0;
++ }
++ clearSensorProperties();
++ clearReading();
++ ui.scrollArea->hide();
++
++ // Check that we've selected an item
++ QTreeWidgetItem *item = ui.sensors->currentItem();
++ if (!item) {
++ qWarning() << "Didn't select an item!";
++ return;
++ }
++
++ QByteArray type = item->data(0, Qt::UserRole).toString().toLatin1();
++ QByteArray identifier = item->data(0, Qt::DisplayRole).toString().toLatin1();
++
++ if (type.isEmpty()) {
++ // Uh oh, there aren't any sensors.
++ // The user has clicked the dummy list entry so just ignore it.
++ return;
++ }
++
++ // Connect to the sensor so we can probe it
++ m_sensor = new QSensor(type, this);
++ connect(m_sensor, SIGNAL(readingChanged()), this, SLOT(sensor_changed()));
++ m_sensor->setIdentifier(identifier);
++ if (!m_sensor->connectToBackend()) {
++ delete m_sensor;
++ m_sensor = 0;
++ qWarning() << "Can't connect to the sensor!";
++ return;
++ }
++
++ ui.scrollArea->show();
++ loadSensorProperties();
++ loadReading();
++
++ adjustTableColumns(ui.sensorprops);
++ adjustTableColumns(ui.reading);
++ QTimer::singleShot(100, this, SLOT(adjustSizes()));
++ }
++
++ void Explorer::clearReading()
++ {
++ ui.reading->setRowCount(0);
++ }
++
++ void Explorer::loadReading()
++ {
++ // Probe the reading using Qt's meta-object facilities
++ QSensorReading *reading = m_sensor->reading();
++ if (!reading)
++ return;
++ const QMetaObject *mo = reading->metaObject();
++ int firstProperty = QSensorReading::staticMetaObject.propertyOffset();
++
++ ui.reading->setRowCount(mo->propertyCount() - firstProperty);
++
++ for (int i = firstProperty; i < mo->propertyCount(); ++i) {
++ int row = i - firstProperty;
++ QTableWidgetItem *index;
++ if (row == 0)
++ // timestamp is not available via index
++ index = new QTableWidgetItem(QLatin1String("N/A"));
++ else
++ index = new QTableWidgetItem(QVariant(row - 1).toString());
++ QTableWidgetItem *prop = new QTableWidgetItem(mo->property(i).name());
++ QString typeName = QLatin1String(mo->property(i).typeName());
++ int delimiter = typeName.lastIndexOf("::");
++ if (delimiter != -1)
++ typeName = typeName.mid(delimiter + 2);
++ QTableWidgetItem *type = new QTableWidgetItem(typeName);
++ QTableWidgetItem *value = new QTableWidgetItem();
++
++ index->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ prop->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ type->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ value->setFlags(value->flags() ^ Qt::ItemIsEditable);
++
++ ui.reading->setItem(row, 0, index);
++ ui.reading->setItem(row, 1, prop);
++ ui.reading->setItem(row, 2, type);
++ ui.reading->setItem(row, 3, value);
++ }
++ }
++
++ void Explorer::clearSensorProperties()
++ {
++ ui.sensorprops->setRowCount(0);
++ }
++
++ void Explorer::loadSensorProperties()
++ {
++ ignoreItemChanged = true;
++
++ // Probe the sensor using Qt's meta-object facilities
++ const QMetaObject *mo = m_sensor->metaObject();
++ int firstProperty = QSensor::staticMetaObject.propertyOffset();
++
++ int rows = mo->propertyCount() - firstProperty;
++ ui.sensorprops->setRowCount(rows);
++
++ int offset = 0;
++ for (int i = firstProperty; i < mo->propertyCount(); ++i) {
++ int row = i - firstProperty - offset;
++ QLatin1String name(mo->property(i).name());
++ if (name == "identifier" ||
++ //name == "type" ||
++ name == "reading" ||
++ name == "connected" ||
++ name == "running" ||
++ name == "supportsPolling") {
++ ++offset;
++ continue;
++ }
++ QTableWidgetItem *prop = new QTableWidgetItem(name);
++ QString typeName = QLatin1String(mo->property(i).typeName());
++ int delimiter = typeName.lastIndexOf("::");
++ if (delimiter != -1)
++ typeName = typeName.mid(delimiter + 2);
++ QTableWidgetItem *type = new QTableWidgetItem(typeName);
++ QVariant v = mo->property(i).read(m_sensor);
++ QString val;
++ if (typeName == "qrangelist") {
++ qrangelist rl = v.value<qrangelist>();
++ QStringList out;
++ for (const qrange &r : rl) {
++ if (r.first == r.second)
++ out << QString("%1 Hz").arg(r.first);
++ else
++ out << QString("%1-%2 Hz").arg(r.first).arg(r.second);
++ }
++ val = out.join(", ");
++ } else if (typeName == "qoutputrangelist") {
++ qoutputrangelist rl = v.value<qoutputrangelist>();
++ QStringList out;
++ for (const qoutputrange &r : rl)
++ out << QString("(%1, %2) += %3").arg(r.minimum).arg(r.maximum).arg(r.accuracy);
++ val = out.join(", ");
++ } else {
++ val = v.toString();
++ }
++ QTableWidgetItem *value = new QTableWidgetItem(val);
++
++ prop->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ type->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ if (!mo->property(i).isWritable()) {
++ // clear the editable flag
++ value->setFlags(value->flags() ^ Qt::ItemIsEditable);
++ }
++
++ ui.sensorprops->setItem(row, 0, prop);
++ ui.sensorprops->setItem(row, 1, type);
++ ui.sensorprops->setItem(row, 2, value);
++ }
++
++ // We don't add all properties
++ ui.sensorprops->setRowCount(rows - offset);
++
++ ignoreItemChanged = false;
++ }
++
++ void Explorer::showEvent(QShowEvent *event)
++ {
++ // Once we're visible, load the sensors
++ // (don't delay showing the UI while we load plugins, connect to backends, etc.)
++ QTimer::singleShot(0, this, SLOT(loadSensors()));
++
++ QMainWindow::showEvent(event);
++ }
++
++ // Resize columns to fit the space.
++ // This shouldn't be so hard!
++ void Explorer::adjustTableColumns(QTableWidget *table)
++ {
++ if (table->rowCount() == 0) {
++ table->setFixedHeight(0);
++ return;
++ }
++
++ // At least this is easy to do
++ table->resizeColumnsToContents();
++ int length = table->verticalHeader()->length();
++ length += (length / static_cast<qreal>(table->verticalHeader()->count())); // Add 1 more (the header itself)
++ table->setFixedHeight(length);
++
++ int columns = table->columnCount();
++ QList<int> width;
++ int suggestedWidth = 0;
++ for (int i = 0; i < columns; ++i) {
++ int cwidth = table->columnWidth(i);
++ width << cwidth;
++ suggestedWidth += cwidth;
++ }
++
++ int actualWidth = table->size().width();
++ //qDebug() << "suggestedWidth" << suggestedWidth << "actualWidth" << actualWidth;
++
++ // We only scale the columns up, we don't scale down
++ if (actualWidth <= suggestedWidth)
++ return;
++
++ qreal multiplier = actualWidth / static_cast<qreal>(suggestedWidth);
++ int currentSpace = 4;
++ for (int i = 0; i < columns; ++i) {
++ width[i] = multiplier * width[i];
++ currentSpace += width[i];
++ }
++
++ // It ends up too big due to cell decorations or something.
++ // Make things smaller one pixel at a time in round robin fashion until we're good.
++ int i = 0;
++ while (currentSpace > actualWidth) {
++ --width[i];
++ --currentSpace;
++ i = (i + 1) % columns;
++ }
++
++ for (int i = 0; i < columns; ++i) {
++ table->setColumnWidth(i, width[i]);
++ }
++
++ table->setMinimumWidth(suggestedWidth);
++ }
++
++ void Explorer::adjustSizes()
++ {
++ adjustTableColumns(ui.reading);
++ adjustTableColumns(ui.sensorprops);
++ }
++
++ void Explorer::resizeEvent(QResizeEvent *event)
++ {
++ resizeSensors();
++ adjustSizes();
++
++ QMainWindow::resizeEvent(event);
++ }
++
++ void Explorer::on_start_clicked()
++ {
++ m_sensor->start();
++ QTimer::singleShot(0, this, SLOT(loadSensorProperties()));
++ }
++
++ void Explorer::on_stop_clicked()
++ {
++ m_sensor->stop();
++ QTimer::singleShot(0, this, SLOT(loadSensorProperties()));
++ }
++
++ void Explorer::sensor_changed()
++ {
++ QSensorReading *reading = m_sensor->reading();
++ filter(reading);
++ }
++
++ bool Explorer::filter(QSensorReading *reading)
++ {
++ const QMetaObject *mo = reading->metaObject();
++ int firstProperty = QSensorReading::staticMetaObject.propertyOffset();
++
++ for (int i = firstProperty; i < mo->propertyCount(); ++i) {
++ int row = i - firstProperty;
++ QString typeName = QLatin1String(mo->property(i).typeName());
++ int delimiter = typeName.lastIndexOf("::");
++ if (delimiter != -1)
++ typeName = typeName.mid(delimiter + 2);
++ QLatin1String name(mo->property(i).name());
++ QTableWidgetItem *value = ui.reading->item(row, 3);
++ QVariant val = mo->property(i).read(reading);
++ if (typeName == "LightLevel") {
++ QString text;
++ switch (val.toInt()) {
++ case 1:
++ text = "Dark";
++ break;
++ case 2:
++ text = "Twilight";
++ break;
++ case 3:
++ text = "Light";
++ break;
++ case 4:
++ text = "Bright";
++ break;
++ case 5:
++ text = "Sunny";
++ break;
++ default:
++ text = "Undefined";
++ break;
++ }
++ value->setText(text);
++ } else {
++ value->setText(val.toString());
++ }
++ }
++
++ adjustTableColumns(ui.reading);
++ //QTimer::singleShot(0, this, SLOT(adjustSizes()));
++
++ return false;
++ }
++
++ void Explorer::on_sensorprops_itemChanged(QTableWidgetItem *item)
++ {
++ if (ignoreItemChanged)
++ return;
++ if (!(item->flags() & Qt::ItemIsEditable))
++ return;
++
++ int row = item->row();
++ QString name = ui.sensorprops->item(row, 0)->text();
++ QVariant value = item->text();
++
++ qDebug() << "setProperty" << name << value;
++ m_sensor->setProperty(name.toLatin1().constData(), QVariant(value));
++
++ QTimer::singleShot(0, this, SLOT(loadSensorProperties()));
++ }
++