/*************************************************************************** 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 #include #include #include #include #include #include #include #include #include #include #include BCValueDelegate::BCValueDelegate(const BCValueList& valueList, BCDeviceView* view) : QStyledItemDelegate{view}, _valueList{valueList}, _view{view} { } QWidget* BCValueDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const { Q_UNUSED(option) Q_UNUSED(index) const BCValue& bcValue = *(_valueList[ index.row() ].get()); BCValue::ValueRange params; bool hasData = bcValue.hasValuesForSlider( params ); qDebug() << " --- Create Editor: " << bcValue.label() << " value: " << params.value << " min: " << params.min << " max: " << params.max << " ratio:" << bcValue.calcMinMaxRatio()*100.0 << '%'; if( bcValue.isBoolean() ) { auto* toggleSwitch = new BCToggleSwitch{parent}; toggleSwitch->setChecked(bcValue.isChecked() ); // Signal für sofortige Updates connect(toggleSwitch, &BCToggleSwitch::toggled, this, [this, toggleSwitch](bool checked) { qDebug() << "--- toggled: " << checked; // Commit data sofort bei Änderung emit const_cast(this)->commitData(toggleSwitch); }); return toggleSwitch; } if( !hasData ) return nullptr; auto* valueSlider = new BCValueSlider{parent}; valueSlider->setValueAndRange( params ); // Signal für sofortige Updates connect(valueSlider, &BCValueSlider::valueChanged, this, [this, valueSlider]() { // Commit data sofort bei Änderung emit const_cast(this)->commitData(valueSlider); }); return valueSlider; } void BCValueDelegate::setEditorData(QWidget *editor, const QModelIndex& index) const { Q_UNUSED(editor) Q_UNUSED(index) // tue nix. } void BCValueDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index) const BCValue& bcValue = *(_valueList[ index.row()].get()); QRect editorRect = bcValue.isBoolean() ? adjustEditorRect(option.rect, 0, 6, -130, -6) : adjustEditorRect(option.rect, 0, 0, 8, 0); editor->setGeometry(editorRect); } void BCValueDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex& index) const { if (index.column() != 1) return; QVariant reValue; const BCValue& bcValue = *(_valueList[index.row()].get()); if (bcValue.isBoolean()) { if (BCToggleSwitch* toggleSswitch = qobject_cast(editor)) reValue = toggleSswitch->isChecked() ? 1 : 0; } else { if (BCValueSlider* slider = qobject_cast(editor)) reValue = slider->value(); } model->setData(index, reValue, Qt::EditRole); } void BCValueDelegate::paint(QPainter *painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { int row = index.row(); if( index.column() != 1 || row<0 || row >= _valueList.size() ) return; QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const BCValue& bcValue = *(_valueList[ index.row()].get()); if( bcValue.isBoolean() ) { paintBooleanValue( painter, opt, bcValue ); } else { // Standard-Zeichnen (Text, Hintergrund, Selection) durchführen QStyledItemDelegate::paint(painter, opt, index); } if( !bcValue.isReadOnly() ) { // Wir zeichnen boolean Values an toggle switches if (bcValue.isBoolean()) { //paintPlainToggleSwitch(painter, opt, bcValue); } else { paintPlainSliderIndicator(painter, opt.rect, bcValue.calcMinMaxRatio()); } } if(_rowOpacities.contains(row)) paintHighlightRow(painter, opt,index.row()); } void BCValueDelegate::paintBooleanValue( QPainter *painter, const QStyleOptionViewItem& option, const BCValue& bcValue ) const { QRect textRect = option.rect.adjusted( 2,0,0,0); // 3. Den tatsächlichen Wert aus dem Model auslesen QString text = bcValue.rawValue() == 1 ? BCTags::Yes : BCTags::No; // 3. Die korrekte Textfarbe ermitteln (extrem wichtig für die UX!) // Wenn die Zeile markiert ist (Selected), muss der Text meist weiß sein, // ansonsten schwarz (oder je nach System-Theme). QPalette::ColorRole textRole = (option.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text; // 4. Den Text nativ durch den Style zeichnen lassen QApplication::style()->drawItemText( painter, textRect, Qt::AlignLeft | Qt::AlignVCenter, // Ausrichtung innerhalb von textRect option.palette, // Farbpalette der View übernehmen option.state & QStyle::State_Enabled, // Prüfen, ob die Zelle klickbar/aktiv ist text, // Der zu zeichnende String textRole // Die ermittelte Farb-Rolle ); } QRect BCValueDelegate::adjustEditorRect( const QRect& rect, int x1, int y1, int x2, int y2 ) const { return rect.adjusted ( rect.width() - cTextBlockOffset + x1, // Von rechts: cTextBlockOffset (==130) px (Breite der Progress Bar) y1, // Oben: kein Offset x2, // Rechts: 8px Padding y2 // Unten: kein Offset ); } // option.rect.adjusted(option.rect.width() - cTextBlockOffset,4,0,0); /** * @brief Zeichnet der 'Sliderindicator', also den Anteil des Gesamtwerts als Fortschrittsbalken */ void BCValueDelegate::paintPlainSliderIndicator(QPainter* painter, const QRect& rect, double ratio )const { QRect sliderRect = adjustEditorRect( rect, 0, 0, -35, 0 ); // Kleinen Slider-Indikator zeichnen painter->save(); painter->setRenderHint(QPainter::Antialiasing); QRect barRect = sliderRect; int yOffset = sliderRect.height()/2; barRect.setY( rect.y() + yOffset - 3 ); barRect.setHeight( 6); // Mini Progress Bar: der Gesamtbereich painter->setPen(Qt::NoPen); painter->setBrush(QColor(0xE0E0E0)); painter->drawRoundedRect(barRect, 4, 4); // Mini Progress Bar: der Wertebereich barRect.setWidth( ratio * barRect.width() ); painter->setBrush(QColor(0x0078D4)); painter->drawRoundedRect(barRect, 4, 4); painter->restore(); } void BCValueDelegate::paintHighlightRow(QPainter* painter, const QStyleOptionViewItem& option, int row) const { painter->save(); painter->setRenderHint(QPainter::Antialiasing); qreal opacity =_rowOpacities.value(row); painter->setOpacity(opacity); // Margin von 2px const int m = 3; QRect itemRect = option.rect.adjusted(m,m,-m,-m); // Border (2px solid #2196F3) // oranger rahmen QPen borderPen( QColor(0xFF8C00), 1); painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); // highlight background //QColor highlightColor = option.palette.highlight().color(); //highlightColor.setAlphaF(0.3); // 0.0 bis 1.0 (float ist oft lesbarer) //painter->fillRect(option.rect, highlightColor); painter->drawRoundedRect(itemRect, 2, 2); painter->restore(); } void BCValueDelegate::paintPlainToggleSwitch(QPainter* painter, const QStyleOptionViewItem& option, const BCValue& bcValue) const { BCToggleSwitch::paintToggleIndicator(painter, option.rect, bcValue.isChecked(), 20, option.widget->palette()); } /** * @brief Startet die Animation für die übergebene Zeile * @param row */ void BCValueDelegate::onHighlightRow(int row) { // Alte Animation für diese Zeile stoppen falls vorhanden if (_rowAnimations.contains(row)) { _rowAnimations[row]->stop(); _rowAnimations[row]->deleteLater(); } // QVariantAnimation ist flexibler als QPropertyAnimation auto* anim = new QVariantAnimation(this); anim->setDuration(800); anim->setStartValue(0.0); anim->setEndValue(1.0); // Custom Easing für Fade-in/out Effekt anim->setEasingCurve(QEasingCurve::OutQuad); connect(anim, &QVariantAnimation::valueChanged, this, [this, row](const QVariant& value) { qreal progress = value.toReal(); qreal opacity; // Schnelles Fade-in (20%), langsames Fade-out (80%) if (progress < 0.2) { opacity = progress * 5.0; // 0->1 in 20% } else { opacity = 1.0 - ((progress - 0.2) / 0.8); // 1->0 in 80% } _rowOpacities[row] = opacity; updateRow(row); }); connect(anim, &QVariantAnimation::finished, this, [this, row, anim]() { _rowOpacities.remove(row); _rowAnimations.remove(row); updateRow(row); anim->deleteLater(); }); _rowAnimations[row] = anim; anim->start(QAbstractAnimation::DeleteWhenStopped); } /** * @brief Stopt alle gerade laufenden Animationen */ void BCValueDelegate::clearAllHighlights() { for(auto* anim : std::as_const(_rowAnimations)) { anim->stop(); anim->deleteLater(); } _rowAnimations.clear(); _rowOpacities.clear(); if (_view) _view->viewport()->update(); } /** * @brief Zeichnet die übegebene Zeile neu. * @param row */ void BCValueDelegate::updateRow(int row) { if (_view && _view->model() && row >= 0) { QModelIndex idx = _view->model()->index(row,1); QRect rect = _view->visualRect(idx); if (!rect.isEmpty()) _view->viewport()->update(rect); } }