#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; }