Files
raDIYo/raDIYo.cpp
2025-08-05 22:36:00 +02:00

516 lines
13 KiB
C++

/***************************************************************************
source::worx raDIYo
Copyright © 2020-2022 c.holzheuer
chris@sourceworx.org
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 2 of the License, or
(at your option) any later version.
***************************************************************************/
#include <QDebug>
#include <QStandardPaths>
#include <QFile>
#include <QTextStream>
#include <QAction>
#include <QCoreApplication>
#include <QMouseEvent>
#include <QPainter>
#include <QTime>
#include <QUrl>
#include <QFileInfo>
#include <QStyle>
#include <QFileDialog>
#include <QMessageBox>
#include <QFontDatabase>
#include <QByteArray>
#include <QFileInfo>
//#include <stdio.h>
#include <raDIYo.h>
#include <swcontrol.h>
#include <swdialbutton.h>
#include <swsendercontrol.h>
#include <swsongscontrol.h>
#include <swshutdowncontrol.h>
#include <swclockcontrol.h>
#include <swalarmcontrol.h>
#include <swsetupcontrol.h>
#include <swusbcontrol.h>
#include <swbluetoothcontrol.h>
#include <swplayercontrol.h>
#include <SWPiGPIO.h>
RaDIYo::RaDIYo()
: QWidget( nullptr )
{
setupUi( this );
// startup
// Das ist die Bildschirmgröße des Raspi 7 inch displays
resize( SWScreenLargeX, SWScreenLargeY );
setupFonts();
setupControls();
setupConnections();
setupDefaults();
// bei untätigkeit kommt wieder die Uhr
//_idleTimer.setInterval( raDIYo::IdleTimeOut );
//_idleTimer.setSingleShot( true );
// hier sind wir auch für die voreinstellungen zuständig: falls
// nicht vorhanden, default setzen
// '_alarmControl->onShow();' stellt den Alarmtimer ein
// Das gehört hierher ins setup, weil
// wir den Alarm auch gesetzt haben wollen
// _ohne_ das AlarmConmtrol aufrufen zu müssen.
_alarmControl->onShow();
// wir starten mit der Uhr
onChangeState( RaDIYo::Clock );
// Der Startwert für die Lautstärke
// ist 20% vom Maximum und muss
// hier als Absolutwert gesetzt werden
// weil zur Laufzeit nur mit deltas
// gearbeitet wird.
int vol = 20;
_nowPlaying.volume = vol;
_volumeWidget->setValue( vol );
_playerControl->setValue( vol );
// not least
_version->setText( raDIYo::Version );
qDebug( raDIYo::Version );
}
RaDIYo::~RaDIYo()
{
_playerControl->stopPlaying();
#ifdef Q_OS_LINUX
delete _dialLeft;
delete _dialRight;
#endif
for( SWControl* ctrl : _controls )
delete ctrl;
}
/**
* Behandelt die 'direkten' (vom touchscreen) Buttonsclicks aus der Buttongroup
* und den 'indirekten' Aufruf über die RotaryDials: Wenn die Buttonleiste aktiv ist,
* wird beim Klick auf den SenderButton auch 'idActivated(newId)' gesendet.
*
* @param newID id des Buttons ( entspricht der id des neuen Controls )
*/
void RaDIYo::onChangeState( int newState )
{
//qDebug() << " ------- RaDIYo::onChangeState: " << newState << ": " << stateName( newState ) << " old: " << stateName( _curCtrlIdx );
bool pauseClicked = false;
switch( newState )
{
// wir sind am abspielen, pause geklickt
case RaDIYo::Pause :
// hide button
pauseClicked = true;
newState = RaDIYo::Play;
// fallthrough ...
// play button geklickt
case RaDIYo::Play :
showTitleText( pauseClicked );
_playerControl->togglePlaying( !pauseClicked );
_buttonPlay->setVisible( pauseClicked );
_buttonPause->setVisible( !pauseClicked );
break;
case RaDIYo::Stop :
showTitleText( false );
_playerControl->stopPlaying();
// Pause gilt nicht mehr nach 'stop'
_buttonPlay->setVisible( true );
_buttonPause->setVisible( false );
// fallthrough..
case RaDIYo::Back :
//_curCtrlIdx = _lstState;
newState = Clock; //?? oder select
} // switch
if( _curCtrlIdx == newState )
return;
// Hier wird unterschieden zwischen 'echten' Controls ('play', 'sender' ...)
// und dummies ohne eigene Seite ('stop', 'pause', 'back' )
_upMode = true;
SWControl* newControl = _controls[newState];
// wenn vorhanden ...
if( nullptr != newControl )
{
// ... dann das alte aus-
_controls[_curCtrlIdx]->fadeOut();
//_curCtrlIdx = newState;
// und das neue Control einblenden ...
newControl->fadeIn();
// Kontrolle übergeben ...
_activeReceiver = newControl;
// Wird das 'neue' Control vom RotaryDial gesteuert?
if( !newControl->acceptDial() )
{
_activeReceiver = static_cast<SWDialHandler*>( &_raDIYoButtons );
_upMode = false;
}
}
_buttonBack->setVisible( _upMode );
_buttonShutdown->setVisible( !_upMode );
_curCtrlIdx = newState;
_raDIYoButtons.setCurrentActiveId( _curCtrlIdx );
bool isPlaying = _playerState == QMediaPlayer::PlayingState;
_buttonStop->setEnabled( isPlaying );
}
void RaDIYo::showTitleText( bool pauseClicked )
{
QString color = pauseClicked ? "rgb(181,181,181)" : "white" ;
_currentTitle->setStyleSheet( _titleCss.arg( color ) );
_currentTitle->setText( _nowPlaying.title );
}
// Wird nicht verwendet
void RaDIYo::onIdleTimeOut()
{
// swap back
onChangeState( RaDIYo::Clock );
}
/**
* @brief Slot, der bei Zustandsänderungen des Players
* aufgerufen wird
* @param state der neue PlayerState
*/
void RaDIYo::onPlayingChanged( QMediaPlayer::State state )
{
bool callStop = false;
// State-Änderung kommt von 'innen', also vom Player weil der Song zu Ende ist.
if( _curCtrlIdx == RaDIYo::Play && state == QMediaPlayer::StoppedState )
callStop = true;
_playerState = state;
if( callStop )
onChangeState( RaDIYo::Stop );
}
/**
* @brief Es wurde ein Sender oder Song aus der jeweiligen ListView ausgewählt.
* @param item der Sender/Song
* @brief Event von aussen: Ein PlayListEntry wurde aktiviert
* und wir jetzt abgespielt.
* @param item
*/
void RaDIYo::onPlayUrl( SWUrl item )
{
_nowPlaying = item;
onPlayCurrentUrl();
}
/**
* @brief von aussen und von innen, spielt
* die derzeitige defaultquelle ab, wird
* vom Wecker benutzt.
*/
void RaDIYo::onPlayCurrentUrl()
{
_playerControl->setUrl( _nowPlaying.urlText );
onChangeState( RaDIYo::Play );
}
/**
*/
void RaDIYo::onRightButtonClicked()
{
onChangeState( _upMode ? RaDIYo::Back : RaDIYo::Shutdown );
}
/**
* @brief Behandelt Impulse der Linken DialControls.
* @param delta: +1 oder -1 je nach Drehrichtung
*/
void RaDIYo::onRightDeltaChanged( int delta )
{
// 'setValue( value )' reicht hier nicht,
// denn der neue Wert muss weiter gereicht
// werden, also 'onDialDeltaChanged'
_volumeWidget->onDialDeltaChanged( delta );
_playerControl->onDialDeltaChanged( delta );
}
/**
* @brief Der linke Button wird an das aktive Control
* weitergeleitet.
*/
void RaDIYo::onLeftButtonClicked()
{
// ex.left
Q_ASSERT( _activeReceiver != nullptr );
_activeReceiver->onDialPushed();
}
/**
* @brief KISS: der rechte Regler steuert immer die Lautstärke.
*/
void RaDIYo::onLeftDeltaChanged( int delta )
{
Q_ASSERT( _activeReceiver != nullptr );
_activeReceiver->onDialDeltaChanged( delta );
}
///
/// --- Setup
///
/**
* @brief Fonts explicit laden, _vor_ setupUi
*/
void RaDIYo::setupFonts()
{
QStringList fontList = QDir( raDIYo::FontDir ).entryList();
for( const QString& fontName : fontList )
QFontDatabase::addApplicationFont( raDIYo::FontDir + fontName );
}
/**
* @brief
* @param id
* @param control
*/
void RaDIYo::addControl( int id, SWControl* control )
{
_controls[ id ] = control;
}
/**
* @brief setupControls
*/
void RaDIYo::setupControls()
{
// Setup Controls: 'RaDIYo' Buttons
_raDIYoButtons.addKeyButton( _buttonPlay, Play, "play" );
_raDIYoButtons.addKeyButton( _buttonPause, Pause, "pause" );
_raDIYoButtons.addKeyButton( _buttonStop, Stop, "stop" );
_raDIYoButtons.addKeyButton( _buttonAlarm, Alarm, "alarm" );
_raDIYoButtons.addKeyButton( _buttonClock, Clock, "clock" );
_raDIYoButtons.addKeyButton( _buttonSender, Sender, "sender" );
_raDIYoButtons.addKeyButton( _buttonSongs, Songs, "songs" );
_raDIYoButtons.addKeyButton( _buttonUSB, USB, "usb" );
_raDIYoButtons.addKeyButton( _buttonBack, Back, "down" );
_raDIYoButtons.addKeyButton( _buttonShutdown, Shutdown, "shutdown" );
// Startzustand darstellen
_buttonBack->hide();
_buttonPause->hide();
// Alle Controls
_senderControl = new SWSenderControl( this, &_mainSettings );
_songsControl = new SWSongsControl( this, &_mainSettings );
_playerControl = new SWPlayerControl( this, &_mainSettings );
_alarmControl = new SWAlarmControl( this, &_mainSettings );
_usbControl = new SWUSBControl( this, &_mainSettings );
// Leere Plätze mit 'nullptr' als solche kennzeichnen
addControl( Play, _playerControl );
addControl( Pause, nullptr );
addControl( Stop, nullptr );
addControl( Clock, new SWClockControl( this, &_mainSettings ) );
addControl( Alarm, _alarmControl ) ;
addControl( Sender, _senderControl );
addControl( Songs, _songsControl );
addControl( USB, _usbControl );
addControl( Back, nullptr );
addControl( Shutdown, new SWShutdownControl( this, &_mainSettings ) );
}
void RaDIYo::setupConnections()
{
// Linux, kompiliert auch unter Win
_dialLeft = new PiGRotaryDial( 22, 27, 17, this );
_dialRight = new PiGRotaryDial( 06, 13, 05, this );
#ifdef Q_OS_LINUX
connect( _dialLeft, SIGNAL(clicked()), this, SLOT(onLeftButtonClicked() ) );
connect( _dialLeft, SIGNAL(deltaChanged(int)), this, SLOT(onLeftDeltaChanged(int) ) );
connect( _dialRight, SIGNAL(clicked()), this, SLOT(onRightButtonClicked() ) );
connect( _dialRight, SIGNAL(deltaChanged(int)), this, SLOT(onRightDeltaChanged(int) ) );
#endif
#ifdef Q_OS_WIN
// Setup Controls: Fake Dials
connect( &_dialDialog.leftDial().pushButton(), &QPushButton::clicked, this, &RaDIYo::onLeftButtonClicked );
connect( &_dialDialog, &SWDummyDialDialog::leftDeltaChanged, this, &RaDIYo::onLeftDeltaChanged );
connect( &_dialDialog.rightDial().pushButton(), &QPushButton::clicked, this, &RaDIYo::onRightButtonClicked );
connect( &_dialDialog, &SWDummyDialDialog::rightDeltaChanged, this, &RaDIYo::onRightDeltaChanged );
_dialDialog.show();
#endif
connect( _senderControl, SIGNAL( entryActivated(SWUrl) ), this, SLOT( onPlayUrl(SWUrl) ) );
connect( _songsControl, SIGNAL( entryActivated(SWUrl) ), this, SLOT( onPlayUrl(SWUrl) ) );
connect( _usbControl, SIGNAL( entryActivated(SWUrl) ), this, SLOT( onPlayUrl(SWUrl) ) );
connect( _usbControl, SIGNAL( driveMounted(int) ), this, SLOT( onChangeState(int) ) );
qRegisterMetaType< QMediaPlayer::State>(" QMediaPlayer::State");
connect( _playerControl, SIGNAL( stateChanged(QMediaPlayer::State) ), this, SLOT( onPlayingChanged(QMediaPlayer::State) ) );
// Der Wecker sendet ggf auch ...
connect( _alarmControl, SIGNAL( playCurrentUrl() ), this, SLOT( onPlayCurrentUrl() ) );
connect( &_raDIYoButtons,SIGNAL( idClicked(int) ), this, SLOT( onChangeState(int) ) );
// weiterleiten
connect( _volumeWidget, SIGNAL( deltaChanged(int) ), this, SLOT( onRightDeltaChanged(int) ) );
}
void RaDIYo::setupDefaults()
{
// default Sender belegen
if( !_mainSettings.contains( raDIYo::KeyDefaultSender ) )
_mainSettings.setValue( raDIYo::KeyDefaultSender, raDIYo::DefaultSender );
// default playlist item soll nicht leer sein
QString defSender =_mainSettings.value( raDIYo::KeyDefaultSender ).toString();
_nowPlaying = SWUrl( defSender, raDIYo::DefaultVolume );
_playerControl->setUrl( _nowPlaying.urlText );
// wir nehmen den default-sender
_currentTitle->setText( _nowPlaying.title );
// not least: Pfad setzen
// windows: c:\users\chris\<my.Radiyo>
// linux: /home/chris/<my.Radiyo>
QString songsPath = _mainSettings.value( raDIYo::KeySongsPath ).toString();
if( songsPath.isEmpty() )
{
songsPath = QStandardPaths::writableLocation( QStandardPaths::DocumentsLocation );
songsPath += _mainSettings.value( raDIYo::KeySongsDir, raDIYo::SongsDir ).toString();
_mainSettings.setValue( raDIYo::KeySongsPath, songsPath );
}
// css kram erzeugen
_titleCss = readResource( raDIYo::ResTitleStyle );
}
/**
* @brief Debugfunktion, die den Namen eines State ausgibt.
* @param state
*/
QString RaDIYo::stateName( int state )
{
switch( state )
{
case Sender: return "Sender";
case Songs: return "Songs";
case USB: return "USB";
case Clock: return "Clock";
case Alarm: return "Alarm";
case Play: return "Player";
case Pause: return "Pause";
case Stop: return "Stop";
case Back: return "Back";
case Shutdown: return "Shutdown";
}
return "42";
}