#include "core.h"
// This file is part of JustAudio.
// JustAudio 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.
// JustAudio is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with BashWire under the GPL.txt file. If not, see
// .
Core::Core(QObject *parent) : QObject(parent)
{
// the Core class acts as a non-gui centralized class that house the majority
// of all functions the application needs to operate.
nextCheck = new QCheckBox(tr("Play files in directory"));
muteCheck = new QCheckBox(tr("Mute"));
volumeSlider = new QSlider(Qt::Horizontal);
currentFile = new QLabel(tr("Ready"));
audFile = new AudFile(this);
sysTray = 0;
// QMediaPlayer volume range 0-100.
volumeSlider->setMinimum(0);
volumeSlider->setMaximum(100);
volumeSlider->setTracking(true);
currentFile->setWordWrap(true);
QFile file(CONFIG_FILE, this);
bool ok = false;
if (file.open(QFile::ReadOnly))
{
// the CONFIG_FILE is formated in csv so all data is seperated by a ','
// the data will simply not load if the strings are not in exactly the
// same order as it was saved.
// data formats:
// VOLUME = numeric string
// PLAY_DIR_CHECK = string ("Y" or "N")
// MUTE = string ("Y" or "N")
QByteArray confData = file.readAll();
QList split = confData.split(',');
if (split.size() == 3)
{
int volume = split[VOLUME].toInt(&ok);
if (ok)
{
volumeSlider->setValue(volume);
emit setVolume(volume);
if (split[PLAY_DIR_CHECK] == "Y") nextCheck->setChecked(true);
else nextCheck->setChecked(false);
if (split[MUTE] == "Y") muteCheck->setChecked(true);
else muteCheck->setChecked(false);
}
}
}
if (!ok)
{
// set default values on failure to read the CONFIG_FILE.
emit setVolume(50);
volumeSlider->setValue(50);
nextCheck->setChecked(false);
muteCheck->setChecked(false);
}
file.close();
// it's very important that the player stops playback before closing the application
// or it will cause a crash.
connect(audFile, SIGNAL(endOfPlayback()), this, SLOT(playBackFinished()));
connect(QApplication::instance(), SIGNAL(aboutToQuit()), this, SIGNAL(stop()));
}
Core::~Core()
{
audFile->close();
QFile file(CONFIG_FILE, this);
if (file.open(QFile::WriteOnly | QFile::Truncate))
{
// CONFIG_FILE format:
// [volume],[play_dir_check],[mute]
// data formats:
// VOLUME = numeric string
// PLAY_DIR_CHECK = string ("Y" or "N")
// MUTE = string ("Y" or "N")
file.write(QByteArray::number(volumeSlider->value()));
file.write(",");
if (nextCheck->isChecked()) file.write("Y");
else file.write("N");
file.write(",");
if (muteCheck->isChecked()) file.write("Y");
else file.write("N");
}
file.close();
}
void Core::setFileWatcher(QFileSystemWatcher *watcher)
{
connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(processfileChanged(QString)));
}
void Core::setPlayer(QMediaPlayer *player)
{
// the Core class does not run funtions directly on the QMediaPlayer
// class that lives in the main function so everything it needs to
// do are connected via slots and signals.
connect(this, SIGNAL(start()), player, SLOT(play()));
connect(this, SIGNAL(pause()), player, SLOT(pause()));
connect(this, SIGNAL(stop()), player, SLOT(stop()));
connect(this, SIGNAL(setMedia(QMediaContent,QIODevice*)), player, SLOT(setMedia(QMediaContent,QIODevice*)));
connect(this, SIGNAL(setVolume(int)), player, SLOT(setVolume(int)));
connect(this, SIGNAL(muteState(bool)), player, SLOT(setMuted(bool)));
connect(player, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(stateChanged(QMediaPlayer::State)));
}
void Core::setTrayIcon(QSystemTrayIcon *tray)
{
// must user interaction will be on the system tray icon for this app.
// this funtion sets up the context menu so the user can have access
// to controls like play, pause, next, previous, etc...
QMenu *contextMenu = new QMenu();
QWidget *volumeWidget = new QWidget();
QHBoxLayout *volumeLayout = new QHBoxLayout(volumeWidget);
QWidgetAction *nextCheckAction = new QWidgetAction(this);
QWidgetAction *muteAction = new QWidgetAction(this);
QWidgetAction *volumeAction = new QWidgetAction(this);
QWidgetAction *fileNameAction = new QWidgetAction(this);
QToolButton *volUpIcon = new QToolButton();
QToolButton *volDownIcon = new QToolButton();
fileNameAction->setDefaultWidget(currentFile);
contextMenu->addAction(fileNameAction);
contextMenu->addSeparator();
playAction = contextMenu->addAction(QIcon(":/png/play"), tr("Play"), this, SIGNAL(start()));
pauseAction = contextMenu->addAction(QIcon(":/png/pause"), tr("Pause"), this, SIGNAL(pause()));
stopAction = contextMenu->addAction(QIcon(":/png/stop"), tr("Stop"), this, SIGNAL(stop()));
sysTray = tray;
volUpIcon->setIcon(QIcon(":/png/volume_up"));
volDownIcon->setIcon(QIcon(":/png/volume_down"));
volumeLayout->addWidget(volDownIcon);
volumeLayout->addWidget(volumeSlider);
volumeLayout->addWidget(volUpIcon);
playAction->setEnabled(false); // play, pause, stop, previous and next are disabled initially.
pauseAction->setEnabled(false); // this will change when an audio file is opened
stopAction->setEnabled(false); // (see stateChanged())
volumeAction->setDefaultWidget(volumeWidget);
nextCheckAction->setDefaultWidget(nextCheck);
muteAction->setDefaultWidget(muteCheck);
contextMenu->addAction(QIcon(":/png/open"), tr("Open"), this, SLOT(openDialog()));
nextAction = contextMenu->addAction(QIcon(":/png/next"), tr("Next"), this, SLOT(nextFile()));
prevAction = contextMenu->addAction(QIcon(":/png/prev"), tr("Previous"), this, SLOT(prevFile()));
nextAction->setEnabled(false);
prevAction->setEnabled(false);
contextMenu->addSeparator();
contextMenu->addAction(nextCheckAction);
contextMenu->addAction(muteAction);
contextMenu->addSeparator();
contextMenu->addAction(volumeAction);
contextMenu->addSeparator();
contextMenu->addAction(tr("License"), this, SLOT(showLicense()));
contextMenu->addAction(tr("Exit"), QApplication::instance(), SLOT(quit()));
tray->setContextMenu(contextMenu);
connect(tray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(trayClicked(QSystemTrayIcon::ActivationReason)));
connect(muteCheck, SIGNAL(clicked(bool)), this, SIGNAL(muteState(bool)));
connect(volumeSlider, SIGNAL(valueChanged(int)), this, SIGNAL(setVolume(int)));
connect(volUpIcon, SIGNAL(clicked()), this, SLOT(volumeUp()));
connect(volDownIcon, SIGNAL(clicked()), this, SLOT(volumeDown()));
connect(pauseAction, SIGNAL(triggered()), audFile, SLOT(userInterupt()));
connect(stopAction, SIGNAL(triggered()), audFile, SLOT(userInterupt()));
connect(playAction, SIGNAL(triggered()), audFile, SLOT(userResume()));
}
void Core::processfileChanged(const QString &path)
{
Q_UNUSED(path);
QFile file(PROCESS_FILE, this);
if (file.open(QFile::ReadOnly))
{
// expected data from the PROCESS_FILE should just be a directory path
// string to the audio file to be opened. play() will call out an error
// if it fails to open this file.
play(file.readAll().trimmed());
}
file.close();
}
void Core::play(const QString &path)
{
emit stop();
if (audFile->openFile(path))
{
QFileInfo info(path);
currentFile->setText(info.fileName());
if (sysTray)
{
sysTray->setToolTip(QApplication::applicationName() + " " + QApplication::applicationVersion() + " - " + info.fileName());
}
emit setMedia(0, audFile);
emit start();
}
else
{
QMessageBox box;
box.setText(tr("Failed to open file: ") + path);
box.setDetailedText(audFile->errorString());
box.exec();
}
}
void Core::nextFile()
{
fileDir('+');
}
void Core::prevFile()
{
fileDir('-');
}
void Core::fileDir(char direction)
{
// using the current file as a reference, this function will first find out the
// directory the file lives in and then use that directory to list all files within
// it. then using the current file as a reference again, it trys to find it in the
// list of files and then use that file position to find out the next or previous
// file in the directory to play.
// if the reference file no longer exist then it will attempt to play 1st file in the
// directory. if the directory no longer exist then nothing happens.
QFileInfo info(audFile->fileName());
QDir dir(info.path());
QStringList list = dir.entryList(audExtensions(), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase);
int pos = list.indexOf(info.fileName());
if (!list.isEmpty())
{
if (direction == '+') pos++;
else if (direction == '-') pos--;
if (pos < 0) pos = 0;
if (pos < list.size())
{
play(info.path() + QDir::separator() + list[pos]);
}
}
}
void Core::openDialog()
{
QFileDialog fileDialog;
QString filterString = tr("Audio Files (");
QStringList list = audExtensions();
for (int i = 0; i < list.size(); ++i)
{
filterString.append(list[i] + " ");
}
filterString = filterString.trimmed();
filterString.append(")");
fileDialog.setFileMode(QFileDialog::ExistingFile);
fileDialog.setNameFilter(filterString);
fileDialog.setDirectory(QDir::homePath());
if (fileDialog.exec())
{
play(fileDialog.selectedFiles()[0]);
}
}
void Core::playBackFinished()
{
if (nextCheck->isChecked()) nextFile();
}
void Core::stateChanged(QMediaPlayer::State state)
{
mediaState = state;
switch(state)
{
case QMediaPlayer::PlayingState:
{
playAction->setEnabled(false);
pauseAction->setEnabled(true);
stopAction->setEnabled(true);
prevAction->setEnabled(true);
nextAction->setEnabled(true);
break;
}
case QMediaPlayer::StoppedState:
{
playAction->setEnabled(true);
pauseAction->setEnabled(false);
stopAction->setEnabled(false);
break;
}
case QMediaPlayer::PausedState:
{
playAction->setEnabled(true);
pauseAction->setEnabled(false);
stopAction->setEnabled(true);
break;
}
}
}
void Core::trayClicked(QSystemTrayIcon::ActivationReason reason)
{
if (reason == QSystemTrayIcon::DoubleClick)
{
nextAction->trigger();
}
else if (reason == QSystemTrayIcon::Trigger)
{
if (mediaState == QMediaPlayer::PlayingState)
{
pauseAction->trigger();
}
else if ((mediaState == QMediaPlayer::PausedState) || (mediaState == QMediaPlayer::StoppedState))
{
playAction->trigger();
}
}
}
void Core::volumeDown()
{
volumeSlider->setValue(volumeSlider->value() - 4);
}
void Core::volumeUp()
{
volumeSlider->setValue(volumeSlider->value() + 4);
}
void Core::showLicense()
{
QDialog dialog;
QFile file(":/gpl", this);
file.open(QFile::ReadOnly);
QPlainTextEdit *text = new QPlainTextEdit(file.readAll());
QVBoxLayout *layout = new QVBoxLayout(&dialog);
QRect rect = QApplication::desktop()->screenGeometry();
layout->addWidget(text);
text->setReadOnly(true);
text->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
file.close();
dialog.setMinimumWidth(rect.width() / 4);
dialog.setMinimumHeight(rect.height() - (rect.height() / 6));
dialog.exec();
}
QStringList Core::audExtensions()
{
QStringList ret;
// the media plugins are not checked for file format support. this is just an arbitrary list
// of audio file extentions that are used when filtering for files to play. if a file fails
// to play, the application will remain in silence or move on to the next file if nextCheck
// is enabled.
ret.append("*.mp3");
ret.append("*.ogg");
ret.append("*.flac");
ret.append("*.wav");
ret.append("*.au");
ret.append("*.aif");
ret.append("*.mid");
ret.append("*.rmi");
ret.append("*.m4a");
ret.append("*.aiff");
ret.append("*.ape");
ret.append("*.wv");
ret.append("*.3gp");
ret.append("*.aa");
ret.append("*.aac");
ret.append("*.aax");
ret.append("*.act");
ret.append("*.amr");
ret.append("*.au");
ret.append("*.awb");
ret.append("*.dct");
ret.append("*.gsm");
ret.append("*.m4b");
ret.append("*.oga");
ret.append("*.mogg");
ret.append("*.ra");
ret.append("*.rm");
ret.append("*.raw");
return ret;
}