Soundux/mainwindow.cpp

649 lines
18 KiB
C++
Raw Permalink Normal View History

2019-09-15 01:44:12 +02:00
#include "mainwindow.h"
2019-11-10 02:37:54 +01:00
#include "ui_mainwindow.h"
2019-09-15 01:44:12 +02:00
static vector<QHotkey *> hotkeys;
static string configFolder;
static string soundFilesConfig;
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
2019-09-15 01:44:12 +02:00
{
ui->setupUi(this);
2019-11-10 02:37:54 +01:00
2019-10-08 00:31:44 +02:00
ui->tabWidget->setTabsClosable(true);
ui->tabWidget->setMovable(true);
ui->stopButton->setDisabled(true);
2019-09-15 01:44:12 +02:00
ui->remoteVolumeSlider->setStyle(new ClickableSliderStyle(ui->remoteVolumeSlider->style()));
ui->localVolumeSlider->setStyle(new ClickableSliderStyle(ui->localVolumeSlider->style()));
// Set the config variables
configFolder = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation)[0].toStdString() + "/" + windowTitle().toStdString();
2020-03-24 21:18:12 +01:00
// Create config folder
QString configFolderQ = QString::fromStdString(configFolder);
QDir dir;
QFile configFolderFile(configFolderQ);
if (!configFolderFile.exists()) {
dir.mkdir(configFolderQ);
}
2020-03-24 21:18:12 +01:00
2019-10-08 00:31:44 +02:00
soundFilesConfig = configFolder + "/sounds.json";
2019-11-10 02:37:54 +01:00
soundPlayback = new SoundPlayback(this, ui);
settingsDialog = new SettingsDialog(this, configFolder, soundPlayback);
// We unload the modules first to remove any possible leftovers
2019-09-15 01:44:12 +02:00
//TODO: Only remove modules created by Soundboard
system("pacmd unload-module module-null-sink");
system("pacmd unload-module module-loopback");
// Create null sink
system("pacmd load-module module-null-sink sink_name=soundboard_sink sink_properties=device.description=Soundboard-Sink");
// get default input device
string defaultInput = "";
char cmd[] = "pacmd dump";
2019-11-10 02:37:54 +01:00
string result = soundPlayback->getCommandOutput(cmd);
regex reg(R"rgx(set-default-source (.+))rgx");
smatch sm;
regex_search(result, sm, reg);
defaultInput = sm[1].str();
2019-09-15 01:44:12 +02:00
// Create loopback for input
2019-10-08 00:31:44 +02:00
if (defaultInput != "")
{
cout << "Found default input device " << defaultInput << endl;
auto createLoopBack = "pacmd load-module module-loopback source=\"" + defaultInput + "\" sink=\"soundboard_sink\"";
system(createLoopBack.c_str());
}
2019-09-15 01:44:12 +02:00
loadSoundFiles();
2019-11-10 02:37:54 +01:00
soundPlayback->loadSources();
// we need to update the buttons if the program starts because the first tab may be a directory tab
this->on_tabWidget_currentChanged(0);
// add CTRL + Q shortcut: quit
auto shortcut = new QShortcut(this);
shortcut->setKey(Qt::CTRL + Qt::Key_Q);
connect(shortcut, SIGNAL(activated()), this, SLOT(slotShortcutCtrlQ()));
// add CTRL + F shortcut: search
auto searchShortcut = new QShortcut(this);
searchShortcut->setKey(Qt::CTRL + Qt::Key_F);
connect(searchShortcut, SIGNAL(activated()), SLOT(slotShortcutCtrlF()));
searchView = new SearchView(this, ui->tabWidget, soundPlayback);
searchView->hide();
searchView->setAllowedAreas(Qt::RightDockWidgetArea);
this->addDockWidget(Qt::RightDockWidgetArea, searchView);
connect(ui->searchButton, &QPushButton::clicked, [&]() {
if (searchView->isHidden()) {
searchView->show();
} else {
searchView->hide();
}
});
}
void MainWindow::slotShortcutCtrlQ()
{
close();
2019-09-15 01:44:12 +02:00
}
void MainWindow::slotShortcutCtrlF()
{
if (searchView->isHidden()) {
searchView->show();
} else {
searchView->hide();
}
}
2019-09-15 01:44:12 +02:00
void MainWindow::closeEvent(QCloseEvent *event)
{
//TODO: Only remove modules created by Soundboard
system("pacmd unload-module module-null-sink");
system("pacmd unload-module module-loopback");
//TODO: Switch all recording streams back to default device
2019-09-15 01:44:12 +02:00
event->accept();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::syncVolume(bool remote)
{
// Get volume from slider
int localValue = ui->localVolumeSlider->value();
int remoteValue = ui->remoteVolumeSlider->value();
if (ui->syncCheckBox->isChecked()) {
if (remote) {
ui->localVolumeSlider->setValue(remoteValue);
} else {
ui->remoteVolumeSlider->setValue(localValue);
}
}
// TODO: this is disabled until I find a good solution
/*
char cmd[] = "pacmd list-sink-inputs";
string result = getCommandOutput(cmd);
string delimiter = "\n";
size_t pos = 0;
string currentLine;
// Tell me if there is a better way to parse the pulseaudio output
regex reg(R"rgx(((index: (\d+)))|(driver: )(.*)|(state: )(.*)|(flags: )(.*)|(source: .*)(<(.*)>)|(muted: )(.{0,3})|([a-zA-Z-.0-9_]*)\ =\ (\"(.*)\"))rgx");
smatch sm;
PulseAudioPlaybackStream *current = nullptr;
while ((pos = result.find(delimiter)) != string::npos)
{
currentLine = result.substr(0, pos);
if (regex_search(currentLine, sm, reg))
{
auto index = sm[3];
if (index.length() > 0)
{
if (current)
{
2019-11-10 02:37:54 +01:00
soundPlayback->checkAndChangeVolume(current, localValue);
}
current = new PulseAudioPlaybackStream();
current->index = stoi(index);
}
else
{
auto propertyName = sm[15];
auto propertyValue = sm[17];
if (propertyName.length() > 0)
{
if (propertyName == "application.name")
{
current->applicationName = propertyValue.str();
}
}
}
}
result.erase(0, pos + delimiter.length());
}
2019-11-10 02:37:54 +01:00
soundPlayback->checkAndChangeVolume(current, localValue);
*/
}
// Sync volume when the slider value has changed
void MainWindow::on_localVolumeSlider_valueChanged(int value)
{
syncVolume(false);
}
void MainWindow::on_remoteVolumeSlider_valueChanged(int value)
{
syncVolume(true);
}
2019-09-15 01:44:12 +02:00
void MainWindow::on_refreshAppsButton_clicked()
{
2019-11-10 02:37:54 +01:00
soundPlayback->loadSources();
2019-09-15 01:44:12 +02:00
}
void MainWindow::on_stopButton_clicked()
{
2019-11-10 02:37:54 +01:00
soundPlayback->stopSound();
2019-09-15 01:44:12 +02:00
}
2019-10-09 22:14:11 +02:00
void MainWindow::on_addFolderButton_clicked()
{
auto selectedFolder = QFileDialog::getExistingDirectory(this, ("Select folder"), QDir::homePath());
if (selectedFolder != "")
{
QDir directory(selectedFolder);
QFileInfo fileInfo(selectedFolder);
auto created = createTab(fileInfo.fileName());
created->directory = directory.absolutePath().toStdString();
QStringList files = directory.entryList({"*.mp3", "*.wav", "*.ogg"}, QDir::Files);
2019-10-09 22:14:11 +02:00
for (auto fileName : files)
{
QFile file(directory.absoluteFilePath(fileName));
addSoundToView(file, created);
}
saveSoundFiles();
}
}
void MainWindow::on_refreshFolderButton_clicked()
{
auto view = getActiveView();
if (view) {
this->refreshFolder(view);
}
}
void MainWindow::refreshFolder(QSoundsList *view) {
QStringList items;
for (auto viewItem : view->findItems("*", Qt::MatchWildcard))
{
items.push_back(viewItem->toolTip());
}
const QDir directory(QString::fromStdString(view->directory));
const QStringList files = directory.entryList({"*.mp3", "*.wav", "*.ogg"}, QDir::Files);
QStringList filesAbsolute;
for (auto fileName : files)
{
const auto absolutePath = directory.absoluteFilePath(fileName);
filesAbsolute.push_back(absolutePath);
// add new ones
if (!LContains(items, absolutePath)) {
cout << "adding " << absolutePath.toStdString() << endl;
QFile file(absolutePath);
addSoundToView(file, view);
}
}
auto list = view->findItems("*", Qt::MatchWildcard);
// remove old ones
for (const auto item : list) {
if (!LContains(filesAbsolute, item->toolTip())) {
cout << "removing " << item->toolTip().toStdString() << endl;
this->removeSound((SoundListWidgetItem*) item);
}
}
saveSoundFiles();
}
2019-10-09 22:14:11 +02:00
void MainWindow::addSoundToView(QFile &file, QListWidget *widget)
{
QFileInfo fileInfo(file);
auto path = fileInfo.absoluteFilePath().toStdString();
for (QListWidgetItem *item : widget->findItems("*", Qt::MatchWildcard))
{
// Check if Sound is already added
if (path == item->toolTip().toStdString())
{
auto already = "The sound " + item->text().toStdString() + " is already in the list";
QMessageBox::warning(this, "", tr(already.c_str()), QMessageBox::Ok);
2019-10-09 22:14:11 +02:00
return;
}
}
auto item = new SoundListWidgetItem();
2019-11-10 22:19:22 +01:00
item->setText(fileInfo.completeBaseName());
2019-10-09 22:14:11 +02:00
item->setToolTip(fileInfo.absoluteFilePath());
widget->addItem(item);
}
2019-09-15 01:44:12 +02:00
void MainWindow::on_addSoundButton_clicked()
{
2019-10-08 00:31:44 +02:00
if (!getActiveView())
{
createTab("Main");
}
QStringList selectedFiles = QFileDialog::getOpenFileNames(this, tr("Select file"), QDir::homePath(), tr("Sound files (*.mp3 *.wav *.ogg)"));
2019-10-08 00:31:44 +02:00
for (auto selectedFile : selectedFiles)
{
if (selectedFile != "")
{
2019-10-06 23:25:17 +02:00
QFile file(selectedFile);
2019-10-09 22:14:11 +02:00
addSoundToView(file, getActiveView());
2019-10-06 23:25:17 +02:00
}
2019-09-15 01:44:12 +02:00
}
2019-10-09 22:14:11 +02:00
saveSoundFiles();
2019-09-15 01:44:12 +02:00
}
2019-11-10 02:37:54 +01:00
void MainWindow::on_settingsButton_clicked()
{
settingsDialog->exec();
}
2019-10-08 00:31:44 +02:00
void MainWindow::on_soundsListWidget_itemDoubleClicked(QListWidgetItem *listWidgetItem)
{
if (listWidgetItem)
{
2019-11-10 02:37:54 +01:00
soundPlayback->playSound(listWidgetItem->toolTip().toStdString());
}
}
2019-09-15 01:44:12 +02:00
void MainWindow::on_removeSoundButton_clicked()
{
2019-10-08 00:31:44 +02:00
if (getActiveView())
{
SoundListWidgetItem *it = getSelectedItem();
this->removeSound(it);
}
}
void MainWindow::removeSound(SoundListWidgetItem *it) {
if (it)
{
unregisterHotkey(it);
delete it;
saveSoundFiles();
2019-10-08 00:31:44 +02:00
}
2019-09-15 01:44:12 +02:00
}
void MainWindow::on_clearSoundsButton_clicked()
{
2019-10-08 00:31:44 +02:00
if (getActiveView())
{
QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Clear sounds", tr("Are you sure?\n"), QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
if (resBtn == QMessageBox::Yes)
{
clearSoundFiles();
saveSoundFiles();
}
}
2019-09-15 01:44:12 +02:00
}
SoundListWidgetItem *MainWindow::getSelectedItem()
2019-09-15 01:44:12 +02:00
{
2019-10-08 00:31:44 +02:00
if (getActiveView())
{
return (SoundListWidgetItem*) getActiveView()->item(getActiveView()->currentRow());
}
return nullptr;
}
void MainWindow::on_playSoundButton_clicked()
{
SoundListWidgetItem *it = getSelectedItem();
if (it)
{
2019-11-10 02:37:54 +01:00
soundPlayback->playSound(it->toolTip().toStdString());
2019-10-08 00:31:44 +02:00
}
}
void MainWindow::on_addTabButton_clicked()
{
bool ok;
QString text = QInputDialog::getText(this, "Add a tab", "Tab Text:", QLineEdit::Normal, "", &ok);
2019-10-08 00:31:44 +02:00
if (ok && !text.isEmpty())
{
createTab(text);
saveSoundFiles();
2019-09-15 01:44:12 +02:00
}
}
void MainWindow::on_setHotkeyButton_clicked()
{
SoundListWidgetItem *it = getSelectedItem();
if (it)
{
SetHotkeyDialog shd(this, it);
2019-11-22 21:05:57 +01:00
auto clicked = shd.exec();
if (clicked == 1)
{
auto given = shd.getSequence();
if (!given.isNull()) {
registerHotkey(it, given.toString());
} else {
unregisterHotkey(it);
}
2019-11-22 21:05:57 +01:00
saveSoundFiles();
}
}
}
void MainWindow::registerHotkey(SoundListWidgetItem* it, QString keys)
{
// Unregister previous hotkey
unregisterHotkey(it);
it->setHotkey(keys);
2019-11-22 20:11:34 +01:00
cout << "register " << keys.toStdString() << endl;
auto neger = QKeySequence(keys);
auto hotkey = new QHotkey(QKeySequence(keys), true, this);
if (hotkey->isRegistered())
{
hotkeys.push_back(hotkey);
auto toPlay = it->toolTip().toStdString();
connect(hotkey, &QHotkey::activated, this, [=]() {
2019-11-10 02:37:54 +01:00
soundPlayback->playSound(toPlay);
});
}
else
{
unregisterHotkey(it);
QMessageBox::warning(this, "Could not register " + keys, "Either the key combination is not valid or it's not possible to use this combination (Maybe another program is using it)", QMessageBox::Ok);
}
}
bool compareChar(char &c1, char &c2)
{
if (c1 == c2)
return true;
else if (toupper(c1) == toupper(c2))
return true;
return false;
}
bool caseInSensStringCompare(string &str1, string &str2)
{
return ((str1.size() == str2.size()) &&
equal(str1.begin(), str1.end(), str2.begin(), &compareChar));
}
void MainWindow::unregisterHotkey(SoundListWidgetItem *it)
{
auto previousHotkey = it->hotkey;
if (!previousHotkey.isNull())
{
auto previousHotkeyStr = previousHotkey.toString().toStdString();
2019-11-22 20:11:34 +01:00
cout << "unregister " << previousHotkeyStr << endl;
for (QHotkey *hotkey : hotkeys)
{
auto hotkeyStr = hotkey->shortcut().toString().toStdString();
if (caseInSensStringCompare(hotkeyStr, previousHotkeyStr))
{
delete hotkey;
}
}
// Reset Data
it->setHotkey(QVariant());
}
}
2019-10-08 00:31:44 +02:00
void MainWindow::on_tabWidget_tabBarDoubleClicked(int index)
{
bool ok;
QString text = QInputDialog::getText(this, "Rename tab", "Tab Text:", QLineEdit::Normal, ui->tabWidget->tabText(index), &ok);
2019-10-08 00:31:44 +02:00
if (ok && !text.isEmpty())
2019-09-15 01:44:12 +02:00
{
2019-10-08 00:31:44 +02:00
ui->tabWidget->setTabText(index, text);
saveSoundFiles();
2019-09-15 01:44:12 +02:00
}
}
2019-10-08 00:31:44 +02:00
void MainWindow::on_tabWidget_tabCloseRequested(int index)
{
QMessageBox::StandardButton resBtn = QMessageBox::question(this, "Delete tab", tr("Are you sure?\n"), QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes);
if (resBtn == QMessageBox::Yes)
{
ui->tabWidget->removeTab(index);
saveSoundFiles();
}
}
QSoundsList *MainWindow::createTab(QString title)
2019-10-08 00:31:44 +02:00
{
auto soundsListWidget = new QSoundsList();
// why do we set the object name?
2019-10-08 00:31:44 +02:00
soundsListWidget->setObjectName(title);
connect(soundsListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(on_soundsListWidget_itemDoubleClicked(QListWidgetItem *)));
ui->tabWidget->addTab(soundsListWidget, title);
return soundsListWidget;
}
void MainWindow::clearSoundFiles()
{
if (getActiveView())
{
while (getActiveView()->count() > 0)
{
getActiveView()->takeItem(0);
}
}
}
QSoundsList *MainWindow::getActiveView()
2019-10-08 00:31:44 +02:00
{
return (QSoundsList *)ui->tabWidget->widget(ui->tabWidget->currentIndex());
2019-10-08 00:31:44 +02:00
}
void MainWindow::saveSoundFiles()
{
json jsonTabs = json::array();
for (auto i = 0; i < ui->tabWidget->count(); i++)
{
auto title = ui->tabWidget->tabText(i).toStdString();
QSoundsList *listWidget = (QSoundsList *)ui->tabWidget->widget(i);
2019-10-08 00:31:44 +02:00
json tabJson;
json tabJsonSounds = json::array();
2019-10-08 00:31:44 +02:00
// if it is a directory we just save the path and update the sounds from there later
if (listWidget->directory.length() > 0) {
tabJson["directory"] = listWidget->directory;
}
for (auto *_item : listWidget->findItems("*", Qt::MatchWildcard))
{
auto item = (SoundListWidgetItem*) _item;
json j;
j["name"] = item->text().toStdString();
// TODO: make the path relative when it's a folder tab
j["path"] = item->toolTip().toStdString();
auto hotkey = item->hotkey;
if (!hotkey.isNull())
{
auto hotkeyStr = hotkey.toString().toStdString();
j["hotkey"] = hotkeyStr;
}
tabJsonSounds.push_back(j);
2019-10-08 00:31:44 +02:00
}
tabJson["title"] = title;
tabJson["sounds"] = tabJsonSounds;
2019-10-08 00:31:44 +02:00
jsonTabs.push_back(tabJson);
2019-09-15 01:44:12 +02:00
}
ofstream myfile;
myfile.open(soundFilesConfig);
2019-10-08 00:31:44 +02:00
myfile << jsonTabs.dump();
2019-09-15 01:44:12 +02:00
myfile.close();
}
2019-10-08 00:31:44 +02:00
void MainWindow::loadSoundFiles()
{
ifstream fileIn(soundFilesConfig);
if (fileIn.is_open())
{
2019-10-09 22:14:11 +02:00
clearSoundFiles();
2019-09-15 01:44:12 +02:00
json j = json::parse(fileIn);
2019-09-15 01:44:12 +02:00
for (auto& tabItem : j.items())
2019-10-08 00:31:44 +02:00
{
const auto item = tabItem.value();
2019-10-08 00:31:44 +02:00
const auto titleItem = item.find("title");
const auto directoryItem = item.find("directory");
const auto soundsItem = item.find("sounds");
if (titleItem == item.end() || soundsItem == item.end()) {
cout << item.dump() << " is not a valid tab" << endl;
continue;
}
const auto title = titleItem->get<string>();
const auto sounds = soundsItem->get<vector<json>>();
const auto soundsListWidget = createTab(title.c_str());
for (auto _child : sounds)
{
auto soundName = _child["name"];
auto soundPath = _child["path"];
remove(soundPath.begin(), soundPath.end(), '"');
auto item = new SoundListWidgetItem();
item->setText(QString::fromStdString(soundName));
item->setToolTip(QString::fromStdString(soundPath));
auto soundHotkey = _child["hotkey"];
if (!soundHotkey.is_null())
{
// Set hotkey back
registerHotkey(item, QString::fromStdString(soundHotkey));
2019-10-08 00:31:44 +02:00
}
soundsListWidget->addItem(item);
2019-09-15 01:44:12 +02:00
}
if (directoryItem != item.end()) {
const auto directory = directoryItem->get<string>();
// it is a directory category so we set the property
soundsListWidget->directory = directory;
this->refreshFolder(soundsListWidget);
}
2019-09-15 01:44:12 +02:00
}
fileIn.close();
}
}
// we need this to update the buttons if the tab switched
void MainWindow::on_tabWidget_currentChanged(int index)
{
QSoundsList* switchedTo = (QSoundsList *)ui->tabWidget->widget(index);
if (switchedTo) {
bool isFolderTab = switchedTo->directory.length() > 0;
this->ui->addSoundButton->setVisible(!isFolderTab);
this->ui->removeSoundButton->setVisible(!isFolderTab);
this->ui->clearSoundsButton->setVisible(!isFolderTab);
this->ui->refreshFolderButton->setVisible(isFolderTab);
this->ui->setHotkeyButton->setVisible(true);
} else {
this->ui->addSoundButton->setVisible(true);
this->ui->removeSoundButton->setVisible(false);
this->ui->clearSoundsButton->setVisible(false);
this->ui->refreshFolderButton->setVisible(false);
this->ui->setHotkeyButton->setVisible(false);
}
}