diff --git a/images/web-radio.png b/images/web-radio.png new file mode 100644 index 0000000..c68d7c1 Binary files /dev/null and b/images/web-radio.png differ diff --git a/translations/webradioplugin_de.ts b/translations/webradioplugin_de.ts new file mode 100644 index 0000000..5a9cdcb --- /dev/null +++ b/translations/webradioplugin_de.ts @@ -0,0 +1,101 @@ + + + + + WebRadioDialog + + + Radio + Radio + + + + Play + Abspielen + + + + Pause + Pause + + + + Stop + Stop + + + + Stopped + Gestoppt + + + + Playing + Wird abgespielt + + + + Paused + Pausiert + + + + + Unknown + Unbekannt + + + + Unknown media + Unbekanntes Medium + + + + No media + Kein Medium + + + + Loading media... + Lade Medium... + + + + Loaded media + Medium geladen + + + + Stalled media + Medium angehalten + + + + Buffering media + Puffere Medium + + + + Buffered media + Medium gepuffert + + + + End of media + Ende des Mediums + + + + Invalid media + Ungültiges Medium + + + + WebRadioPlugin + + + Play webradio + Webradio spielen + + + diff --git a/translations/webradioplugin_en.ts b/translations/webradioplugin_en.ts new file mode 100644 index 0000000..7ba7a5b --- /dev/null +++ b/translations/webradioplugin_en.ts @@ -0,0 +1,101 @@ + + + + + WebRadioDialog + + + Radio + + + + + Play + + + + + Pause + + + + + Stop + + + + + Stopped + + + + + Playing + + + + + Paused + + + + + + Unknown + + + + + Unknown media + + + + + No media + + + + + Loading media... + + + + + Loaded media + + + + + Stalled media + + + + + Buffering media + + + + + Buffered media + + + + + End of media + + + + + Invalid media + + + + + WebRadioPlugin + + + Play webradio + + + + diff --git a/webradiodialog.cpp b/webradiodialog.cpp new file mode 100644 index 0000000..236ee90 --- /dev/null +++ b/webradiodialog.cpp @@ -0,0 +1,128 @@ +#include "webradiodialog.h" +#include "ui_webradiodialog.h" + +#include "mainwindow.h" +#include "zeiterfassungsettings.h" + +WebRadioDialog::WebRadioDialog(MainWindow &mainWindow) : + ZeiterfassungDialog(&mainWindow), + ui(new Ui::WebRadioDialog), + m_mainWindow(mainWindow), + m_settings(m_mainWindow.settings()) +{ + ui->setupUi(this); + + m_player = new QMediaPlayer(this); + connect(m_player, &QMediaPlayer::stateChanged, this, &WebRadioDialog::stateChanged); + connect(m_player, &QMediaPlayer::stateChanged, this, &WebRadioDialog::updateWidgets); + connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &WebRadioDialog::mediaStatusChanged); + connect(m_player, &QMediaPlayer::mediaStatusChanged, this, &WebRadioDialog::updateWidgets); + connect(m_player, static_cast(&QMediaPlayer::error), this, &WebRadioDialog::error); + + for(const auto &url : m_settings.urls()) + ui->comboBox->addItem(url, url); + + ui->comboBox->setCurrentIndex(ui->comboBox->findData(m_settings.lastUrl())); + + connect(ui->comboBox, static_cast(&QComboBox::currentIndexChanged), + this, &WebRadioDialog::currentIndexChanged); + + connect(ui->comboBox, static_cast(&QComboBox::currentIndexChanged), + this, &WebRadioDialog::updateWidgets); + + connect(ui->pushButtonPlay, &QAbstractButton::pressed, this, &WebRadioDialog::play); + connect(ui->pushButtonPause, &QAbstractButton::pressed, m_player, &QMediaPlayer::pause); + connect(ui->pushButtonStop, &QAbstractButton::pressed, m_player, &QMediaPlayer::stop); + + m_player->setVolume(m_settings.volume()); + ui->horizontalSlider->setValue(m_player->volume()); + connect(ui->horizontalSlider, &QAbstractSlider::valueChanged, this, &WebRadioDialog::volumeChanged); + + stateChanged(m_player->state()); + mediaStatusChanged(m_player->mediaStatus()); + currentIndexChanged(ui->comboBox->currentIndex()); + updateWidgets(); +} + +WebRadioDialog::~WebRadioDialog() +{ + // To avoid crash on app close + disconnect(m_player, &QMediaPlayer::stateChanged, this, &WebRadioDialog::stateChanged); + disconnect(m_player, &QMediaPlayer::stateChanged, this, &WebRadioDialog::updateWidgets); + disconnect(m_player, &QMediaPlayer::mediaStatusChanged, this, &WebRadioDialog::mediaStatusChanged); + disconnect(m_player, &QMediaPlayer::mediaStatusChanged, this, &WebRadioDialog::updateWidgets); + disconnect(m_player, static_cast(&QMediaPlayer::error), this, &WebRadioDialog::error); + + delete ui; +} + +void WebRadioDialog::stateChanged(QMediaPlayer::State newState) +{ + switch(newState) + { + case QMediaPlayer::StoppedState: ui->labelState->setText(tr("Stopped")); break; + case QMediaPlayer::PlayingState: ui->labelState->setText(tr("Playing")); break; + case QMediaPlayer::PausedState: ui->labelState->setText(tr("Paused")); break; + default: + qWarning() << "unknown state" << newState; + ui->labelState->setText(tr("Unknown")); + } +} + +void WebRadioDialog::mediaStatusChanged(QMediaPlayer::MediaStatus status) +{ + switch(status) + { + case QMediaPlayer::UnknownMediaStatus: ui->labelMediaStatus->setText(tr("Unknown media")); break; + case QMediaPlayer::NoMedia: ui->labelMediaStatus->setText(tr("No media")); break; + case QMediaPlayer::LoadingMedia: ui->labelMediaStatus->setText(tr("Loading media...")); break; + case QMediaPlayer::LoadedMedia: ui->labelMediaStatus->setText(tr("Loaded media")); break; + case QMediaPlayer::StalledMedia: ui->labelMediaStatus->setText(tr("Stalled media")); break; + case QMediaPlayer::BufferingMedia: ui->labelMediaStatus->setText(tr("Buffering media")); break; + case QMediaPlayer::BufferedMedia: ui->labelMediaStatus->setText(tr("Buffered media")); break; + case QMediaPlayer::EndOfMedia: ui->labelMediaStatus->setText(tr("End of media")); break; + case QMediaPlayer::InvalidMedia: ui->labelMediaStatus->setText(tr("Invalid media")); break; + default: + qWarning() << "unknown mediaStatus" << status; + ui->labelMediaStatus->setText(tr("Unknown")); + } +} + +void WebRadioDialog::error(QMediaPlayer::Error error) +{ + qWarning() << error; +} + +void WebRadioDialog::currentIndexChanged(int index) +{ + if(index == -1) + m_player->setMedia(QMediaContent()); + else + m_player->setMedia(QMediaContent(QUrl(ui->comboBox->currentData().toString()))); +} + +void WebRadioDialog::play() +{ + if(ui->comboBox->currentIndex() == -1) + return; + + m_settings.setLastUrl(ui->comboBox->currentData().toString()); + + m_player->play(); +} + +void WebRadioDialog::updateWidgets() +{ + ui->comboBox->setEnabled(m_player->state() != QMediaPlayer::PlayingState); + ui->pushButtonPlay->setEnabled(ui->comboBox->currentIndex() > -1 && + (m_player->state() == QMediaPlayer::StoppedState || m_player->state() == QMediaPlayer::PausedState) && + (m_player->mediaStatus() == QMediaPlayer::LoadedMedia || m_player->mediaStatus() == QMediaPlayer::BufferedMedia)); + ui->pushButtonPause->setEnabled(m_player->state() == QMediaPlayer::PlayingState || m_player->state() == QMediaPlayer::PausedState); + ui->pushButtonStop->setEnabled(m_player->state() == QMediaPlayer::PlayingState || m_player->state() == QMediaPlayer::PausedState); +} + +void WebRadioDialog::volumeChanged(int volume) +{ + m_settings.setVolume(volume); + m_player->setVolume(volume); +} diff --git a/webradiodialog.h b/webradiodialog.h new file mode 100644 index 0000000..b1c6c73 --- /dev/null +++ b/webradiodialog.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "zeiterfassungdialog.h" + +#include "webradiosettings.h" + +class MainWindow; + +namespace Ui { class WebRadioDialog; } + +class WebRadioDialog : public ZeiterfassungDialog +{ + Q_OBJECT + +public: + explicit WebRadioDialog(MainWindow &mainWindow); + ~WebRadioDialog(); + +private Q_SLOTS: + void stateChanged(QMediaPlayer::State newState); + void mediaStatusChanged(QMediaPlayer::MediaStatus status); + void error(QMediaPlayer::Error error); + void currentIndexChanged(int index); + void volumeChanged(int volume); + void play(); + +private: + void updateWidgets(); + + Ui::WebRadioDialog *ui; + + MainWindow &m_mainWindow; + WebRadioSettings m_settings; + + QMediaPlayer *m_player; +}; diff --git a/webradiodialog.ui b/webradiodialog.ui new file mode 100644 index 0000000..5758e10 --- /dev/null +++ b/webradiodialog.ui @@ -0,0 +1,135 @@ + + + WebRadioDialog + + + + 0 + 0 + 494 + 155 + + + + Radio + + + + + + + + + + + + 0 + 50 + + + + Play + + + + + + + + 0 + 50 + + + + Pause + + + + + + + + 0 + 50 + + + + Stop + + + + + + + Qt::Horizontal + + + + + + + + + + + state + + + + + + + mediaStatus + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + WebRadioDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + WebRadioDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/webradioplugin.cpp b/webradioplugin.cpp new file mode 100644 index 0000000..d75bfcb --- /dev/null +++ b/webradioplugin.cpp @@ -0,0 +1,45 @@ +#include "webradioplugin.h" + +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" + +#include "webradiodialog.h" +#include "webradiosettingswidget.h" + +WebRadioPlugin::WebRadioPlugin(QObject *parent) : + ZeiterfassungPlugin(parent) +{ + qDebug() << "called"; + + static auto dir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("translations")); + + if(m_translator.load(QLocale(), QStringLiteral("webradioplugin"), QStringLiteral("_"), dir)) + { + if(!QCoreApplication::installTranslator(&m_translator)) + { + qWarning() << "could not install translation webradioplugin"; + } + } + else + { + qWarning() << "could not load translation webradioplugin"; + } +} + +void WebRadioPlugin::attachTo(MainWindow &mainWindow) +{ + auto dialog = new WebRadioDialog(mainWindow); + mainWindow.menuTools()->addAction(QIcon(QStringLiteral(":/zeiterfassung/plugins/webradioplugin/images/web-radio.png")), + tr("Play webradio"), dialog, &QWidget::show); +} + +SettingsWidget *WebRadioPlugin::settingsWidget(ZeiterfassungSettings &settings, QWidget *parent) const +{ + return new WebRadioSettingsWidget(settings, parent); +} diff --git a/webradioplugin.h b/webradioplugin.h new file mode 100644 index 0000000..6644b3f --- /dev/null +++ b/webradioplugin.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "zeiterfassungplugin.h" + +class Q_DECL_EXPORT WebRadioPlugin : public ZeiterfassungPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "dbsoftware.zeiterfassung.plugin/1.0" FILE "webradioplugin.json") + Q_INTERFACES(ZeiterfassungPlugin) + +public: + explicit WebRadioPlugin(QObject *parent = Q_NULLPTR); + + // ZeiterfassungPlugin interface + void attachTo(MainWindow &mainWindow) Q_DECL_OVERRIDE; + + SettingsWidget *settingsWidget(ZeiterfassungSettings &settings, QWidget *parent = Q_NULLPTR) const Q_DECL_OVERRIDE; + +private: + QTranslator m_translator; +}; diff --git a/webradioplugin.json b/webradioplugin.json new file mode 100644 index 0000000..e69de29 diff --git a/webradioplugin.pro b/webradioplugin.pro new file mode 100644 index 0000000..2999d4b --- /dev/null +++ b/webradioplugin.pro @@ -0,0 +1,26 @@ +QT += core network gui widgets multimedia + +DBLIBS += dbgui zeiterfassungcore zeiterfassunggui + +TARGET = webradioplugin + +HEADERS += webradiodialog.h \ + webradioplugin.h \ + webradiosettings.h \ + webradiosettingswidget.h + +SOURCES += webradiodialog.cpp \ + webradioplugin.cpp \ + webradiosettings.cpp \ + webradiosettingswidget.cpp + +FORMS += webradiodialog.ui + +RESOURCES += webradioplugin_resources.qrc + +TRANSLATIONS += translations/webradioplugin_en.ts \ + translations/webradioplugin_de.ts + +OTHER_FILES += webradioplugin.json + +include(../plugin.pri) diff --git a/webradioplugin_resources.qrc b/webradioplugin_resources.qrc new file mode 100644 index 0000000..f7b12b5 --- /dev/null +++ b/webradioplugin_resources.qrc @@ -0,0 +1,5 @@ + + + images/web-radio.png + + diff --git a/webradiosettings.cpp b/webradiosettings.cpp new file mode 100644 index 0000000..615c44a --- /dev/null +++ b/webradiosettings.cpp @@ -0,0 +1,114 @@ +#include "webradiosettings.h" + +#include "zeiterfassungsettings.h" + +const QString WebRadioSettings::m_urls("WebRadioPlugin/urls"); +const QString WebRadioSettings::m_lastUrl("WebRadioPlugin/lastUrl"); +const QString WebRadioSettings::m_volume("WebRadioPlugin/volume"); +const QStringList WebRadioSettings::m_defaultUrls { + QStringLiteral("http://stream.drumandbass.fm:9002"), + QStringLiteral("http://stream.trap.fm:6002"), + QStringLiteral("http://stream.dubbase.fm:7002"), + QStringLiteral("http://lw1.mp3.tb-group.fm/hb.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/tb.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/tt.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/ht.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/trb.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/ct.mp3"), + QStringLiteral("http://lw1.mp3.tb-group.fm/clt.mp3"), + QStringLiteral("https://live.helsinki.at:8088/live160.ogg") +}; +const int WebRadioSettings::m_defaultVolume(100); + +WebRadioSettings::WebRadioSettings(ZeiterfassungSettings &settings, QObject *parent) : + QObject(parent), + m_settings(settings) +{ +} + +QStringList WebRadioSettings::urls() const +{ + return m_settings.value(m_urls, m_defaultUrls).toStringList(); +} + +bool WebRadioSettings::setUrls(const QStringList &urls) +{ + if(this->urls() == urls) + return true; + + if(urls == m_defaultUrls) + m_settings.remove(m_urls); + else + m_settings.setValue(m_urls, urls); + + m_settings.sync(); + + const auto success = m_settings.status() == QSettings::NoError; + if(success) + Q_EMIT urlsChanged(urls); + else + { + Q_EMIT m_settings.saveErrorOccured(); + Q_EMIT saveErrorOccured(); + } + + return success; +} + +QString WebRadioSettings::lastUrl() const +{ + return m_settings.value(m_lastUrl).toString(); +} + +bool WebRadioSettings::setLastUrl(const QString &lastUrl) +{ + if(this->lastUrl() == lastUrl) + return true; + + if(lastUrl.isNull()) + m_settings.remove(m_lastUrl); + else + m_settings.setValue(m_lastUrl, lastUrl); + + m_settings.sync(); + + const auto success = m_settings.status() == QSettings::NoError; + if(success) + Q_EMIT lastUrlChanged(lastUrl); + else + { + Q_EMIT m_settings.saveErrorOccured(); + Q_EMIT saveErrorOccured(); + } + + return success; +} + +int WebRadioSettings::volume() const +{ + return m_settings.value(m_volume, m_defaultVolume).toInt(); +} + +bool WebRadioSettings::setVolume(int volume) +{ + if(this->volume() == volume) + return true; + + if(volume == m_defaultVolume) + m_settings.remove(m_volume); + else + m_settings.setValue(m_volume, volume); + + m_settings.sync(); + + const auto success = m_settings.status() == QSettings::NoError; + if(success) + Q_EMIT volumeChanged(volume); + else + { + Q_EMIT m_settings.saveErrorOccured(); + Q_EMIT saveErrorOccured(); + } + + return success; +} diff --git a/webradiosettings.h b/webradiosettings.h new file mode 100644 index 0000000..0e8ceb6 --- /dev/null +++ b/webradiosettings.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +class ZeiterfassungSettings; + +class WebRadioSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(QStringList urls READ urls WRITE setUrls NOTIFY urlsChanged) + Q_PROPERTY(QString lastUrl READ lastUrl WRITE setLastUrl NOTIFY lastUrlChanged) + Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) + +public: + WebRadioSettings(ZeiterfassungSettings &settings, QObject *parent = Q_NULLPTR); + + QStringList urls() const; + bool setUrls(const QStringList &urls); + + QString lastUrl() const; + bool setLastUrl(const QString &lastUrl); + + int volume() const; + bool setVolume(int volume); + +Q_SIGNALS: + void saveErrorOccured(); + + void urlsChanged(const QStringList &urls); + void lastUrlChanged(const QString &lastUrl); + void volumeChanged(int volume); + +private: + ZeiterfassungSettings &m_settings; + + static const QString m_urls; + static const QString m_lastUrl; + static const QString m_volume; + static const QStringList m_defaultUrls; + static const int m_defaultVolume; +}; diff --git a/webradiosettingswidget.cpp b/webradiosettingswidget.cpp new file mode 100644 index 0000000..04f8b56 --- /dev/null +++ b/webradiosettingswidget.cpp @@ -0,0 +1,37 @@ +#include "webradiosettingswidget.h" + +#include + +#include "stringlistwidget.h" + +WebRadioSettingsWidget::WebRadioSettingsWidget(ZeiterfassungSettings &settings, QWidget *parent) : + SettingsWidget(parent), + m_settings(settings) +{ + auto layout = new QFormLayout(this); + layout->setMargin(0); + + m_urlsWidget = new StringListWidget(m_settings.urls(), this); + layout->addRow(tr("URLs:"), m_urlsWidget); + + setLayout(layout); +} + +bool WebRadioSettingsWidget::isValid(QString &message) const +{ + for(const auto &url : m_urlsWidget->value()) + { + if(!QUrl::fromUserInput(url).isValid()) + { + message = tr("A web radio url is invalid!"); + return false; + } + } + + return true; +} + +bool WebRadioSettingsWidget::apply() +{ + return m_settings.setUrls(m_urlsWidget->value()); +} diff --git a/webradiosettingswidget.h b/webradiosettingswidget.h new file mode 100644 index 0000000..04f0181 --- /dev/null +++ b/webradiosettingswidget.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "settingswidget.h" + +#include "webradiosettings.h" + +class StringListWidget; + +class ZeiterfassungSettings; + +class WebRadioSettingsWidget : public SettingsWidget +{ + Q_OBJECT + +public: + explicit WebRadioSettingsWidget(ZeiterfassungSettings &settings, QWidget *parent = Q_NULLPTR); + + bool isValid(QString &message) const Q_DECL_OVERRIDE; + +public Q_SLOTS: + virtual bool apply() Q_DECL_OVERRIDE; + +private: + WebRadioSettings m_settings; + + StringListWidget *m_urlsWidget; +};