264 lines
7.6 KiB
C++
264 lines
7.6 KiB
C++
/***************************************************************************
|
|
|
|
BionxControl
|
|
Copyright © 2025 christoph holzheuer
|
|
christoph.holzheuer@gmail.com
|
|
|
|
Using:
|
|
|
|
mhs_can_drv.c
|
|
© 2011 - 2023 by MHS-Elektronik GmbH & Co. KG, Germany
|
|
Klaus Demlehner, klaus@mhs-elektronik.de
|
|
@see www.mhs-elektronik.de
|
|
|
|
Based on Bionx data type descriptions from:
|
|
|
|
BigXionFlasher USB V 0.2.4 rev. 97
|
|
© 2011-2013 by Thomas Koenig <info@bigxionflasher.org>
|
|
@see www.bigxionflasher.org
|
|
|
|
Bionx Bike Info
|
|
© 2018 Thorsten Schmidt (tschmidt@ts-soft.de)
|
|
@see www.ts-soft.de
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
@see https://github.com/bikemike/bionx-bikeinfo
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
#include <QTimer>
|
|
|
|
#include "qassert.h"
|
|
#include <bcmainwindow.h>
|
|
#include <bcanimateddelegate.h>
|
|
#include <ui_bcmainwindow.h>
|
|
|
|
|
|
/**
|
|
* @brief Das Mainwindow erzeugen
|
|
* @param parent Das Elternwidget
|
|
*/
|
|
|
|
BCMainWindow::BCMainWindow(QWidget *parent)
|
|
: QMainWindow(parent)
|
|
{
|
|
// __fix! in der Form nötig?
|
|
qRegisterMetaType<BCValue>("BCValue");
|
|
qRegisterMetaType<QList<BCValue>>("BCValueList");
|
|
|
|
setupUi(this);
|
|
|
|
// Wir schreiben den 'initMainWindow()' Aufruf mit Hilfe des
|
|
// timers in die Event-Queue, damit er erst ausgeführt wird,
|
|
// wenn das Fenster sichtbar ist.
|
|
|
|
QTimer::singleShot(0, this, [this]()
|
|
{
|
|
initMainWindow();
|
|
});
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Destruktor. Räumt den Workthread auf.
|
|
*/
|
|
|
|
BCMainWindow::~BCMainWindow()
|
|
{
|
|
_worker.quit(); // Event Loop stoppen
|
|
_worker.wait(); // Warten bis Thread wirklich fertig ist
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Setzt den Headerlabel ( == die Device-Bezeichnung )
|
|
* @param headerLabel Der headerLabel
|
|
*/
|
|
|
|
void BCMainWindow::setHeaderLabel( const QString& headerText)
|
|
{
|
|
_headerLabel->setText( " BionxControl: " + headerText );
|
|
}
|
|
|
|
/**
|
|
* @brief Initialisiert alle Komponenten des MainWindows.
|
|
*/
|
|
|
|
void BCMainWindow::initMainWindow()
|
|
{
|
|
|
|
// Lambda um die buttons mit ihren Actions zu verbinden
|
|
auto configureAction = [&]( QToolButton* button, QAction* action, BCDevice::ID deviceID )
|
|
{
|
|
|
|
// Action an den Button binden
|
|
button->setDefaultAction( action);
|
|
|
|
// new way: die DeviceID muss aber explizit vom Lambda eingefanden werden.
|
|
connect( action, &QAction::triggered, this, [this,deviceID]()
|
|
{
|
|
onShowDevicePanel( deviceID );
|
|
});
|
|
|
|
if( _devicePanels.contains(deviceID) )
|
|
{
|
|
BCDeviceView* currentPanel = _devicePanels[deviceID];
|
|
// ... und ihre device ID
|
|
currentPanel->setDeviceID( deviceID );
|
|
// Wenn ein Device (entspricht einem Datenmodel) fertig eingelesen wurde,
|
|
// wird es weitergereicht.
|
|
// Problem: alle Panels bekommen alle Datenmodelle angeboten.
|
|
connect( &_dataManager, &BCXmlLoader::valueListReady, currentPanel, &BCDeviceView::onValueListReady );
|
|
}
|
|
};
|
|
|
|
// Wir wollen die Devices den Views zuordnen können
|
|
_devicePanels[BCDevice::ID::Console] = _consolePanel;
|
|
_devicePanels[BCDevice::ID::Battery] = _batteryPanel;
|
|
_devicePanels[BCDevice::ID::Motor] = _motorPanel;
|
|
_devicePanels[BCDevice::ID::Pimp] = _pimpPanel;
|
|
|
|
// Die actions an die Buttons binden
|
|
configureAction(_motorButton, _motorAction, BCDevice::ID::Motor );
|
|
configureAction(_consoleButton, _consoleAction, BCDevice::ID::Console );
|
|
configureAction(_batteryButton, _batteryAction, BCDevice::ID::Battery );
|
|
configureAction(_pimpButton, _pimpAction, BCDevice::ID::Pimp );
|
|
|
|
bool m_isDarkMode = false;
|
|
QString icon = m_isDarkMode ? "☀️" : "🌙";
|
|
fitzeButton->setText(icon);
|
|
|
|
QString style = QString(
|
|
"QPushButton {"
|
|
" background-color: %1;"
|
|
" border: 1px solid %2;"
|
|
" border-radius: 6px;"
|
|
" font-size: 12pt;"
|
|
" padding: 0px;"
|
|
"}"
|
|
"QPushButton:hover {"
|
|
" background-color: %3;"
|
|
"}"
|
|
).arg(m_isDarkMode ? "#2B2B2B" : "#FFFFFF")
|
|
.arg(m_isDarkMode ? "#3F3F3F" : "#E1DFDD")
|
|
.arg(m_isDarkMode ? "#3A3A3A" : "#F9F9F9");
|
|
|
|
fitzeButton->setStyleSheet(style);
|
|
|
|
// besser: model::emit dataChanged
|
|
// also: emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, ValueRole});
|
|
connect( _connectButton, &QToolButton::clicked, &_transmitter, &BCTransmitter::onToggleConnectionState );
|
|
connect( _syncButton, &QToolButton::clicked, this, &BCMainWindow::onSyncFromDevice );
|
|
connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated );
|
|
|
|
|
|
_transmitter.moveToThread(&_worker);
|
|
|
|
connect(this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::enqueueValueOp);
|
|
connect(&_worker, &QThread::finished, &_transmitter, &QObject::deleteLater);
|
|
|
|
_worker.start();
|
|
|
|
|
|
|
|
// die Daten des eBikes laden
|
|
_dataManager.loadXmlBikeData(":/bikeinfo.xml"_L1);
|
|
_consoleAction->trigger();
|
|
|
|
|
|
// --- STATUSBAR SETUP ---
|
|
QStatusBar *statBar = statusBar();
|
|
|
|
// Optional: Normale Nachricht links
|
|
statBar->showMessage("Ready");
|
|
|
|
// 1. Unseren Switcher erstellen
|
|
ThemeSwitchButton *themeBtn = new ThemeSwitchButton(this);
|
|
|
|
// 2. WICHTIG: Rechts hinzufügen
|
|
statBar->addPermanentWidget(themeBtn);
|
|
|
|
// 3. Signal verbinden: Button klick -> Theme ändern
|
|
connect(themeBtn, &ThemeSwitchButton::themeChanged, this, [this](bool isDark){
|
|
if (isDark)
|
|
{
|
|
//applyFluentDarkTheme(*qApp); // Funktion von vorhin
|
|
statusBar()->showMessage("Dark Mode Activated", 3000);
|
|
}
|
|
else
|
|
{
|
|
//applyFluentLightTheme(*qApp); // Funktion von vorhin
|
|
statusBar()->showMessage("Light Mode Activated", 3000);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
void BCMainWindow::onShowDevicePanel( BCDevice::ID deviceID )
|
|
{
|
|
qDebug() << " --- onShowDevicePanel:" << deviceID;
|
|
if( _devicePanels.contains( deviceID ) )
|
|
{
|
|
BCDeviceView* nxtPanel = _devicePanels[deviceID];
|
|
if( nxtPanel != _currentPanel )
|
|
{
|
|
_currentPanel = nxtPanel;
|
|
qDebug() << " --- Firz: " << _currentPanel->property( BCKeyHeaderLabel );
|
|
setHeaderLabel( _currentPanel->property( BCKeyHeaderLabel ).toString() );
|
|
_stackedWidget->setCurrentWidget( nxtPanel );
|
|
|
|
// knopf auch abschalten?
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void BCMainWindow::onConnectButtonToggled(bool checked )
|
|
{
|
|
//_dataManager.setDriverConnectionState( checked );
|
|
}
|
|
|
|
void BCMainWindow::onValueUpdated(BCDevice::ID deviceID, int index, BCValue::State state, const QString& newValue )
|
|
{
|
|
qDebug() << "Reply: from: " << deviceID << " at: " << index << "finished. Success:" << (uint8_t)state << " on:" << newValue;
|
|
if( _devicePanels.contains( deviceID ) )
|
|
{
|
|
BCDeviceView& panel = *_devicePanels[deviceID];
|
|
panel.onValueUpdated( index, state, newValue );
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief SLOT, der aufgerufen wird, um das akutelle Device (Battery, Motor, ... )
|
|
* zu synchronisieren, d.h. die aktuellen Werte über den CAN-Bus abzufragen.
|
|
*/
|
|
|
|
void BCMainWindow::onSyncFromDevice()
|
|
{
|
|
|
|
Q_ASSERT_X(_currentPanel, "onSyncFromDevice()", "_currentpanel ist null!");
|
|
|
|
qDebug() << " ---Syncing";
|
|
|
|
const BCValueList& currentList =_currentPanel->getValueListX();
|
|
|
|
// alle einzeln? echt jetzt?
|
|
|
|
for( const BCValue& value : currentList )
|
|
{
|
|
qDebug() << " --- value: " << value.label;
|
|
|
|
// statt '_transmitter.enqueueValueCommand( value )' entkoppeln
|
|
// wir das eleganter über emit requestValueUpdate()
|
|
emit requestValueUpdate( BCValue::OpID::ReadValue, &value);
|
|
|
|
}
|
|
|
|
}
|