/*************************************************************************** BionxControl © 2025 -2026 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 @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 #include #include "qassert.h" #include #include #include #include /** * @brief Das Mainwindow erzeugen * @param parent Das Elternwidget */ BCMainWindow::BCMainWindow(QWidget *parent) : QMainWindow(parent) { // __fix! in der Form nötig? qRegisterMetaType("BCValue"); qRegisterMetaType>("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 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; // Die actions an die Buttons binden configureAction(_motorButton, _motorAction, BCDevice::ID::Motor ); configureAction(_consoleButton, _consoleAction, BCDevice::ID::Console ); configureAction(_batteryButton, _batteryAction, BCDevice::ID::Battery ); /* 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); */ initStatusBar(); // besser: model::emit dataChanged // also: emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, ValueRole}); connect( _connectButton, &QToolButton::clicked, &_transmitter, &BCTransmitter::onToggleDriverConnection ); connect( _syncButton, &QToolButton::clicked, this, &BCMainWindow::onSyncDeviceView ); connect( &_transmitter, &BCTransmitter::valueUpdated, this, &BCMainWindow::onValueUpdated ); connect(this, &BCMainWindow::requestValueUpdate, &_transmitter, &BCTransmitter::onUpdateValue); connect(&_worker, &QThread::finished, &_transmitter, &QObject::deleteLater); connect( &_transmitter, &BCTransmitter::driverStateChanged, this, &BCMainWindow::onDriverStateChanged ); // transmitter starten _transmitter.moveToThread(&_worker); _worker.start(); // die Daten des eBikes laden _dataManager.loadXmlBikeData(":/bikeinfo.xml"_L1); //_consoleAction->trigger(); _batteryAction->trigger(); } /* // 2. Bild für den Zustand UNCHECKED (Off) hinzufügen // Das ist das Symbol, wenn NICHT verbunden ist (z.B. ein Stecker) connectIcon.addFile(":/icons/plug_disconnected.svg", QSize(), QIcon::Normal, QIcon::Off); // 3. Bild für den Zustand CHECKED (On) hinzufügen // Das ist das Symbol, wenn verbunden IST (z.B. Stecker in Dose) connectIcon.addFile(":/icons/plug_connected.svg", QSize(), QIcon::Normal, QIcon::On); */ void BCMainWindow::initStatusBar() { QStatusBar *statBar = statusBar(); BCDriverStateWidget* conState = new BCDriverStateWidget(this); connect( &_transmitter, &BCTransmitter::driverStateChanged, conState, &BCDriverStateWidget::onDriverStateChanged ); connect( conState, &BCDriverStateWidget::clicked, _connectAction, &QAction::trigger ); statBar->addPermanentWidget(conState); conState->installEventFilter(this); BCThemeSwitchButton* themeBtn = new BCThemeSwitchButton(this); statBar->addPermanentWidget(themeBtn); connect(themeBtn, &BCThemeSwitchButton::themeChanged, this, [this](bool isDark) { QString message = isDark ? "Dark Mode Activated" : "Light Mode Activated"; statusBar()->showMessage( message, 3000); setApplicationStyleSheet( isDark ? ":/claude_dark_mode.qss"_L1 : ":/claude_light_mode.qss"_L1 ); }); // Wir starten im light mode //themeBtn->setDarkMode( false ); statBar->showMessage("Ready"); } /* bool BCMainWindow::eventFilter(QObject *obj, QEvent *event) { if (obj == myWidget && event->type() == QEvent::MouseButtonRelease) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { myAction->trigger(); return true; // Event wurde verarbeitet } } return QObject::eventFilter(obj, event); } */ /** * @brief Setzt das Stylesheet, hier: Dark- oder Lightmode * @param path Der Pfad zuum Stylesheet * @return */ bool BCMainWindow::setApplicationStyleSheet( QAnyStringView path ) { QFile styleFile( path.toString() ); if (styleFile.open(QIODevice::ReadOnly | QIODevice::Text)) { QString style = styleFile.readAll(); qApp->setStyleSheet(style); styleFile.close(); return false; } qWarning() << "Konnte Stylesheet nicht laden:" << styleFile.errorString(); return true; } /** * @brief Setzt den Headerlabel ( == die Device-Bezeichnung ) * @param headerLabel Der headerLabel */ void BCMainWindow::setHeaderLabel( const QString& headerText) { _headerLabel->setText( " BionxControl: " + headerText ); } void BCMainWindow::onShowMessage( const QString& message, int timeOut ) { _statusbar->showMessage( message, timeOut ); } void BCMainWindow::autoConnect() { // __fix! // if( !connect) // fallBack } void BCMainWindow::onDriverStateChanged( BCDriver::DriverState state, const QString& message ) { qDebug() << " --- on DriverStatusChanged: " << state << ":" << message; _statusbar->showMessage( message, 8000 ); } 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::onSyncDeviceView() { Q_ASSERT_X(_currentPanel, "onSyncDeviceView()", "_currentpanel ist null!"); qDebug() << " ---Syncing"; const BCValueList& currentList =_currentPanel->getValueListX(); // alle einzeln? echt jetzt? for( const BCValuePtr& value : currentList ) { qDebug() << " --- begin sync of value: " << QThread::currentThreadId() << " : " << value->label; // wir setzen auf 'lesen' value->state.setFlag( BCValue::State::ReadMe ); // statt '_transmitter.onUpdateValue( value )' müssen wir hier // über emit requestValueUpdate() zur Thread sysnchronisation // entkoppeln, emit requestValueUpdate( value); } }