516 lines
13 KiB
C++
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";
|
|
}
|
|
|