diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e0b0886..72f7903 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,15 +34,15 @@ build linux x86_64: - clang --version # Extract BASS - #- mkdir bass - #- cd bass - #- curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip - #- unzip bass.zip - #- cp x64/libbass.so ../lib - #- curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip - #- unzip bassopus.zip - #- cp x64/libbassopus.so ../lib - #- cd .. + - mkdir bass + - cd bass + - curl http://www.un4seen.com/files/bass24-linux.zip -o bass.zip + - unzip bass.zip + - cp x64/libbass.so ../lib + - curl http://www.un4seen.com/files/bassopus24-linux.zip -o bassopus.zip + - unzip bassopus.zip + - cp x64/libbassopus.so ../lib + - cd .. # Extract Discord RPC - mkdir discord-rpc @@ -61,7 +61,7 @@ build linux x86_64: - cd .. # Build - - qmake -spec linux-clang "DEFINES += DISCORD QTAUDIO" + - qmake -spec linux-clang "DEFINES += DISCORD" - make -j4 # Post-processing @@ -109,7 +109,7 @@ build windows i686: - ls lib # Build - - /opt/mxe/usr/${TARGET_SPEC}/qt5/bin/qmake "DEFINES += DISCORD BASSAUDIO" + - /opt/mxe/usr/${TARGET_SPEC}/qt5/bin/qmake "DEFINES += DISCORD" - make -j4 # Post-processing diff --git a/Attorney_Online.pro b/Attorney_Online.pro index 28a1e7c..a1f01d4 100644 --- a/Attorney_Online.pro +++ b/Attorney_Online.pro @@ -22,35 +22,15 @@ contains(DEFINES, DISCORD) { LIBS += -ldiscord-rpc } -# Uncomment to enable the BASS audio engine -# (Recommended for Windows) -# DEFINES += BASSAUDIO - -contains(DEFINES, BASSAUDIO) { - LIBS += -lbass - LIBS += -lbassopus -} - -# Uncomment to enable the Qt audio engine -# (Recommended for non-Windows platforms) -# DEFINES += QTAUDIO - -contains(DEFINES, QTAUDIO) { - QT += multimedia -} - -AUDIO_DEFINES = $$find(DEFINES, BASSAUDIO) $$find(DEFINES, QTAUDIO) -count(AUDIO_DEFINES, 0) { - warning("No audio system selected. Your build will not have audio.") -} - -count(AUDIO_DEFINES, 2) { - error("More than one audio system selected.") -} +# As of 2.8.5, BASS and BASSOPUS are required for all platforms. Qt Multimedia +# is no longer an option due to outdated code and lack of versatility. +# Download at un4seen.com and place the DLLs in the "lib" and "bin" folders. +DEFINES += BASSAUDIO +LIBS += -lbass +LIBS += -lbassopus macx:LIBS += -framework CoreFoundation -framework Foundation -framework CoreServices - CONFIG += c++14 RESOURCES += resources.qrc diff --git a/include/aoapplication.h b/include/aoapplication.h index b44c8a8..f0e0545 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -5,6 +5,9 @@ #include "datatypes.h" #include "discord_rich_presence.h" +#include "bass.h" +#include "bassopus.h" + #include #include #include @@ -23,9 +26,6 @@ #include #include #include -#ifdef QTAUDIO -#include -#endif class NetworkManager; class Lobby; @@ -202,7 +202,8 @@ public: // Returns the value of whether custom chatboxes should be a thing. // from the config.ini. - // I am increasingly maddened by the lack of dynamic auto-generation system for settings. + // I am increasingly maddened by the lack of dynamic auto-generation system + // for settings. bool is_customchat_enabled(); // Returns the value of the maximum amount of lines the IC chatlog @@ -227,9 +228,6 @@ public: // Returns the audio device used for the client. QString get_audio_output_device(); -#ifdef QTAUDIO - QAudioDeviceInfo QtAudioDevice; -#endif // Returns whether the user would like to have custom shownames on by default. bool get_showname_enabled_by_default(); @@ -442,6 +440,12 @@ public: // The file name of the log file in base/logs. QString log_filename; + void initBASS(); + static void load_bass_opus_plugin(); + static void CALLBACK BASSreset(HSTREAM handle, DWORD channel, DWORD data, + void *user); + static void doBASSreset(); + private: const int RELEASE = 2; const int MAJOR_VERSION = 8; diff --git a/include/aoblipplayer.h b/include/aoblipplayer.h index 5a10471..4d3b5f1 100644 --- a/include/aoblipplayer.h +++ b/include/aoblipplayer.h @@ -1,12 +1,8 @@ #ifndef AOBLIPPLAYER_H #define AOBLIPPLAYER_H -#if defined(BASSAUDIO) #include "bass.h" #include "bassopus.h" -#elif defined(QTAUDIO) -#include -#endif #include "aoapplication.h" @@ -35,11 +31,7 @@ private: void set_volume_internal(qreal p_volume); -#if defined(BASSAUDIO) HSTREAM m_stream_list[5]; -#elif defined(QTAUDIO) - QSoundEffect m_blips; -#endif }; #endif // AOBLIPPLAYER_H diff --git a/include/aomusicplayer.h b/include/aomusicplayer.h index 82751b6..36031f1 100644 --- a/include/aomusicplayer.h +++ b/include/aomusicplayer.h @@ -2,12 +2,8 @@ #define AOMUSICPLAYER_H #include "file_functions.h" -#if defined(BASSAUDIO) #include "bass.h" #include "bassopus.h" -#elif defined(QTAUDIO) -#include -#endif #include "aoapplication.h" @@ -44,12 +40,8 @@ private: // Channel 1 = ambience // Channel 2 = extra // Channel 3 = extra - #if defined(BASSAUDIO) HSTREAM m_stream_list[4]; HSYNC loop_sync[4]; - #elif defined(QTAUDIO) - QMediaPlayer m_stream_list[4]; - #endif }; #endif // AOMUSICPLAYER_H diff --git a/include/aosfxplayer.h b/include/aosfxplayer.h index 9918871..0a5fffa 100644 --- a/include/aosfxplayer.h +++ b/include/aosfxplayer.h @@ -1,12 +1,8 @@ #ifndef AOSFXPLAYER_H #define AOSFXPLAYER_H -#if defined(BASSAUDIO) #include "bass.h" #include "bassopus.h" -#elif defined(QTAUDIO) -#include -#endif #include "aoapplication.h" @@ -37,11 +33,7 @@ private: const int m_channelmax = 5; -#if defined(BASSAUDIO) HSTREAM m_stream_list[5]; -#elif defined(QTAUDIO) - QSoundEffect m_stream_list[5]; -#endif }; #endif // AOSFXPLAYER_H diff --git a/include/courtroom.h b/include/courtroom.h index 979650a..5b5ff6c 100644 --- a/include/courtroom.h +++ b/include/courtroom.h @@ -828,8 +828,6 @@ private slots: void on_casing_clicked(); void ping_server(); - - void load_bass_opus_plugin(); }; #endif // COURTROOM_H diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index e1e9e7f..fa58ab8 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -177,3 +177,64 @@ void AOApplication::call_announce_menu(Courtroom *court) AOCaseAnnouncerDialog announcer(nullptr, this, court); announcer.exec(); } + +// Callback for when BASS device is lost +void CALLBACK AOApplication::BASSreset(HSTREAM handle, DWORD channel, + DWORD data, void *user) +{ + doBASSreset(); +} + +void AOApplication::doBASSreset() +{ + BASS_Free(); + BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr); + load_bass_opus_plugin(); +} + +void AOApplication::initBASS() +{ + BASS_Free(); + // Change the default audio output device to be the one the user has given + // in his config.ini file for now. + unsigned int a = 0; + BASS_DEVICEINFO info; + + if (get_audio_output_device() == "default") { + BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr); + load_bass_opus_plugin(); + } + else { + for (a = 0; BASS_GetDeviceInfo(a, &info); a++) { + if (get_audio_output_device() == info.name) { + BASS_SetDevice(a); + BASS_Init(static_cast(a), 48000, BASS_DEVICE_LATENCY, nullptr, + nullptr); + load_bass_opus_plugin(); + qDebug() << info.name << "was set as the default audio output device."; + return; + } + } + BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr); + load_bass_opus_plugin(); + } +} + +#if (defined(_WIN32) || defined(_WIN64)) +void AOApplication::load_bass_opus_plugin() +{ + BASS_PluginLoad("bassopus.dll", 0); +} +#elif (defined(LINUX) || defined(__linux__)) +void AOApplication::load_bass_opus_plugin() +{ + BASS_PluginLoad("libbassopus.so", 0); +} +#elif defined __APPLE__ +void AOApplication::load_bass_opus_plugin() +{ + BASS_PluginLoad("libbassopus.dylib", 0); +} +#else +#error This operating system is unsupported for BASS plugins. +#endif diff --git a/src/aoblipplayer.cpp b/src/aoblipplayer.cpp index 57b2d27..5b4d625 100644 --- a/src/aoblipplayer.cpp +++ b/src/aoblipplayer.cpp @@ -1,6 +1,5 @@ #include "aoblipplayer.h" -#if defined(BASSAUDIO) // Using bass.dll for the blips AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) { m_parent = parent; @@ -37,8 +36,14 @@ void AOBlipPlayer::blip_tick() m_cycle = 0; HSTREAM f_stream = m_stream_list[f_cycle]; - if (ao_app->get_audio_output_device() != "default") + + BASS_ChannelSetDevice(f_stream, BASS_GetDevice()); + int f_bass_error = BASS_ErrorGetCode(); + if (f_bass_error == BASS_ERROR_DEVICE) { + ao_app->doBASSreset(); BASS_ChannelSetDevice(f_stream, BASS_GetDevice()); + } + BASS_ChannelPlay(f_stream, false); } @@ -56,51 +61,3 @@ void AOBlipPlayer::set_volume_internal(qreal p_value) BASS_ChannelSetAttribute(m_stream_list[n_stream], BASS_ATTRIB_VOL, volume); } } -#elif defined(QTAUDIO) // Using Qt's QSoundEffect class -AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) -{ - m_parent = parent; - ao_app = p_ao_app; -} - -void AOBlipPlayer::set_blips(QString p_sfx) -{ - QString f_path = ao_app->get_sounds_path(p_sfx); - - for (int n_stream = 0; n_stream < 5; ++n_stream) { - m_blips.setSource(QUrl::fromLocalFile(f_path)); - } - - set_volume(m_volume); -} - -void AOBlipPlayer::blip_tick() -{ - int f_cycle = m_cycle++; - - if (m_cycle == 5) - m_cycle = 0; - - m_blips.play(); -} - -void AOBlipPlayer::set_volume(int p_value) -{ - m_volume = p_value; - m_blips.setVolume(m_volume); -} -#else // No audio -AOBlipPlayer::AOBlipPlayer(QWidget *parent, AOApplication *p_ao_app) -{ - m_parent = parent; - ao_app = p_ao_app; -} - -void AOBlipPlayer::set_blips(QString p_sfx) {} - -void AOBlipPlayer::blip_tick() {} - -void AOBlipPlayer::set_volume(int p_value) {} - -void AOBlipPlayer::set_volume_internal(qreal p_value) {} -#endif diff --git a/src/aomusicplayer.cpp b/src/aomusicplayer.cpp index 249e01e..6c61b9a 100644 --- a/src/aomusicplayer.cpp +++ b/src/aomusicplayer.cpp @@ -1,14 +1,11 @@ #include "aomusicplayer.h" - AOMusicPlayer::AOMusicPlayer(QWidget *parent, AOApplication *p_ao_app) { m_parent = parent; ao_app = p_ao_app; } -#ifdef BASSAUDIO - AOMusicPlayer::~AOMusicPlayer() { for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { @@ -111,8 +108,11 @@ void AOMusicPlayer::play(QString p_song, int channel, bool loop, else this->set_volume(m_volume[channel], channel); + BASS_ChannelSetSync(m_stream_list[channel], BASS_SYNC_DEV_FAIL, 0, + ao_app->BASSreset, 0); + this->set_looping(loop, channel); // Have to do this here due to any - // crossfading-related changes, etc. + // crossfading-related changes, etc. } void AOMusicPlayer::stop(int channel) @@ -164,62 +164,13 @@ void AOMusicPlayer::set_looping(bool toggle, int channel) } if (loop_start[channel] > 0) { if (loop_end[channel] == 0) - loop_end[channel] = BASS_ChannelGetLength(m_stream_list[channel], BASS_POS_BYTE); - if (loop_end[channel] > 0) // Don't loop zero length songs even if we're asked to + loop_end[channel] = + BASS_ChannelGetLength(m_stream_list[channel], BASS_POS_BYTE); + if (loop_end[channel] > + 0) // Don't loop zero length songs even if we're asked to loop_sync[channel] = BASS_ChannelSetSync( - m_stream_list[channel], BASS_SYNC_POS | BASS_SYNC_MIXTIME, loop_end[channel], - loopProc, &loop_start[channel]); + m_stream_list[channel], BASS_SYNC_POS | BASS_SYNC_MIXTIME, + loop_end[channel], loopProc, &loop_start[channel]); } } } -#elif defined(QTAUDIO) - -AOMusicPlayer::~AOMusicPlayer() { - for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { - m_stream_list[n_stream].stop(); - } -} - -void AOMusicPlayer::play(QString p_song, int channel, bool loop, - int effect_flags) -{ - channel = channel % m_channelmax; - if (channel < 0) // wtf? - return; - QString f_path = ao_app->get_music_path(p_song); - - m_stream_list[channel].stop(); - - m_stream_list[channel].setMedia(QUrl::fromLocalFile(f_path)); - - this->set_volume(m_volume[channel], channel); - - m_stream_list[channel].play(); -} - -void AOMusicPlayer::stop(int channel) -{ - m_stream_list[channel].stop(); -} - -void AOMusicPlayer::set_volume(int p_value, int channel) -{ - m_volume[channel] = p_value; - m_stream_list[channel].setVolume(m_volume[channel]); -} - -#else - -AOMusicPlayer::~AOMusicPlayer() {} - -void AOMusicPlayer::play(QString p_song, int channel, bool loop, - int effect_flags) {} - -void AOMusicPlayer::stop(int channel) {} - -void AOMusicPlayer::set_volume(int p_value, int channel) {} - -void loopProc(int handle, int channel, int data, int *user) {} - -void AOMusicPlayer::set_looping(bool toggle, int channel) {} -#endif diff --git a/src/aooptionsdialog.cpp b/src/aooptionsdialog.cpp index 4e6aca6..6fc4d03 100644 --- a/src/aooptionsdialog.cpp +++ b/src/aooptionsdialog.cpp @@ -445,7 +445,6 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_audio_device_combobox->addItem("default"); //TODO translate this without breaking the default audio device } -#ifdef BASSAUDIO BASS_DEVICEINFO info; for (a = 0; BASS_GetDeviceInfo(a, &info); a++) { ui_audio_device_combobox->addItem(info.name); @@ -453,15 +452,6 @@ AOOptionsDialog::AOOptionsDialog(QWidget *parent, AOApplication *p_ao_app) ui_audio_device_combobox->setCurrentIndex( ui_audio_device_combobox->count() - 1); } -#elif defined QTAUDIO - foreach (const QAudioDeviceInfo &deviceInfo, - QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { - ui_audio_device_combobox->addItem(deviceInfo.deviceName()); - if (p_ao_app->get_audio_output_device() == deviceInfo.deviceName()) - ui_audio_device_combobox->setCurrentIndex( - ui_audio_device_combobox->count() - 1); - } -#endif ui_audio_layout->setWidget(row, QFormLayout::FieldRole, ui_audio_device_combobox); @@ -816,6 +806,7 @@ void AOOptionsDialog::save_pressed() configini->setValue("casing_can_host_cases", ui_casing_cm_cases_textbox->text()); + ao_app->initBASS(); callwordsini->close(); done(0); } diff --git a/src/aosfxplayer.cpp b/src/aosfxplayer.cpp index 127bda6..8c4f3c8 100644 --- a/src/aosfxplayer.cpp +++ b/src/aosfxplayer.cpp @@ -1,14 +1,12 @@ #include "aosfxplayer.h" #include "file_functions.h" - AOSfxPlayer::AOSfxPlayer(QWidget *parent, AOApplication *p_ao_app) { m_parent = parent; ao_app = p_ao_app; } -#if defined(BASSAUDIO) // Using bass.dll for sfx void AOSfxPlayer::clear() { for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { @@ -68,9 +66,16 @@ void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout, set_volume_internal(m_volume); - if (ao_app->get_audio_output_device() != "default") + BASS_ChannelSetDevice(m_stream_list[m_channel], BASS_GetDevice()); + int f_bass_error = BASS_ErrorGetCode(); + if (f_bass_error == BASS_ERROR_DEVICE) { + ao_app->doBASSreset(); BASS_ChannelSetDevice(m_stream_list[m_channel], BASS_GetDevice()); + } + BASS_ChannelPlay(m_stream_list[m_channel], false); + BASS_ChannelSetSync(m_stream_list[channel], BASS_SYNC_DEV_FAIL, 0, + ao_app->BASSreset, 0); } void AOSfxPlayer::stop(int channel) @@ -112,100 +117,3 @@ void AOSfxPlayer::set_looping(bool toggle, int channel) BASS_SAMPLE_LOOP); // set the LOOP flag } } -#elif defined(QTAUDIO) // Using Qt's QSoundEffect class - -void AOSfxPlayer::clear() -{ - for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { - m_stream_list[n_stream].stop(); - } - set_volume_internal(m_volume); -} - -void AOSfxPlayer::loop_clear() -{ - for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { - m_stream_list[n_stream].stop(); - } - set_volume_internal(m_volume); -} - -void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout, - int channel) -{ - m_stream_list[channel].stop(); - - QString misc_path = ""; - QString char_path = ""; - QString sound_path = ao_app->get_sounds_path(p_sfx); - - if (shout != "") - misc_path = ao_app->get_base_path() + "misc/" + shout + "/" + p_sfx; - if (p_char != "") - char_path = ao_app->get_character_path(p_char, p_sfx); - - QString f_path; - - if (file_exists(char_path)) - f_path = char_path; - else if (file_exists(misc_path)) - f_path = misc_path; - else - f_path = sound_path; - - if (file_exists(f_path)) // if its missing, it will glitch out - { - m_stream_list[channel].setSource(QUrl::fromLocalFile(f_path)); - - set_volume_internal(m_volume); - - m_stream_list[channel].play(); - } -} - -void AOSfxPlayer::stop(int channel) -{ - if (channel == -1) { - channel = m_channel; - } - m_stream_list[channel].stop(); -} - -void AOSfxPlayer::set_volume(qreal p_value) -{ - m_volume = p_value / 100; - set_volume_internal(m_volume); -} - -void AOSfxPlayer::set_volume_internal(qreal p_value) -{ - float volume = static_cast(p_value); - for (int n_stream = 0; n_stream < m_channelmax; ++n_stream) { - m_stream_list[n_stream].setVolume(volume); - } -} - -void AOSfxPlayer::set_looping(bool toggle, int channel) -{ - if (channel == -1) { - channel = m_channel; - } - m_looping = toggle; - // TODO -} -#else -void AOSfxPlayer::clear() {} - -void AOSfxPlayer::loop_clear() {} - -void AOSfxPlayer::play(QString p_sfx, QString p_char, QString shout, - int channel) {} - -void AOSfxPlayer::stop(int channel) {} - -void AOSfxPlayer::set_volume(qreal p_value) {} - -void AOSfxPlayer::set_volume_internal(qreal p_value) {} - -void AOSfxPlayer::set_looping(bool toggle, int channel) {} -#endif diff --git a/src/courtroom.cpp b/src/courtroom.cpp index fe10e13..2484bcb 100644 --- a/src/courtroom.cpp +++ b/src/courtroom.cpp @@ -3,42 +3,7 @@ Courtroom::Courtroom(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; -#ifdef BASSAUDIO - // Change the default audio output device to be the one the user has given - // in his config.ini file for now. - unsigned int a = 0; - BASS_DEVICEINFO info; - - if (ao_app->get_audio_output_device() == "default") { - BASS_Init(-1, 48000, BASS_DEVICE_LATENCY, nullptr, nullptr); - load_bass_opus_plugin(); - } - else { - for (a = 0; BASS_GetDeviceInfo(a, &info); a++) { - if (ao_app->get_audio_output_device() == info.name) { - BASS_SetDevice(a); - BASS_Init(static_cast(a), 48000, BASS_DEVICE_LATENCY, nullptr, - nullptr); - load_bass_opus_plugin(); - qDebug() << info.name << "was set as the default audio output device."; - break; - } - } - } -#elif defined QTAUDIO - - if (ao_app->get_audio_output_device() != "default") { - foreach (const QAudioDeviceInfo &deviceInfo, - QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { - if (ao_app->get_audio_output_device() == deviceInfo.deviceName()) { - ao_app->QtAudioDevice = deviceInfo; - qDebug() << deviceInfo.deviceName() - << "was set as the default audio output device."; - break; - } - } - } -#endif + ao_app->initBASS(); qsrand(static_cast(QDateTime::currentMSecsSinceEpoch() / 1000)); @@ -4750,28 +4715,3 @@ Courtroom::~Courtroom() delete objection_player; delete blip_player; } - -#if (defined(_WIN32) || defined(_WIN64)) -void Courtroom::load_bass_opus_plugin() -{ -#ifdef BASSAUDIO - BASS_PluginLoad("bassopus.dll", 0); -#endif -} -#elif (defined(LINUX) || defined(__linux__)) -void Courtroom::load_bass_opus_plugin() -{ -#ifdef BASSAUDIO - BASS_PluginLoad("libbassopus.so", 0); -#endif -} -#elif defined __APPLE__ -void Courtroom::load_bass_opus_plugin() -{ -#ifdef BASSAUDIO - BASS_PluginLoad("libbassopus.dylib", 0); -#endif -} -#else -#error This operating system is unsupported for bass plugins. -#endif diff --git a/src/lobby.cpp b/src/lobby.cpp index 3aa488a..093b0f7 100644 --- a/src/lobby.cpp +++ b/src/lobby.cpp @@ -5,6 +5,8 @@ #include "debug_functions.h" #include "networkmanager.h" +#include + Lobby::Lobby(AOApplication *p_ao_app) : QMainWindow() { ao_app = p_ao_app; @@ -353,13 +355,7 @@ void Lobby::on_connect_released() void Lobby::on_about_clicked() { -#ifdef BASSAUDIO - const QString audio = "BASS"; -#elif defined(QTAUDIO) - const QString audio = "Qt Multimedia"; -#else - const QString audio = "null"; -#endif + const bool hasApng = QImageReader::supportedImageFormats().contains("APNG"); QString msg = tr("

Attorney Online %1

" @@ -389,11 +385,12 @@ void Lobby::on_about_clicked() "is copyright (c) 2016-2020 Attorney Online developers. Open-source " "licenses apply. All other assets are the property of their " "respective owners." - "

Running on Qt version %2 with the %3 audio engine." + "

Running on Qt version %2 with the BASS audio engine.
" + "APNG plugin loaded: %3" "

Built on %4") .arg(ao_app->get_version_string()) .arg(QLatin1String(QT_VERSION_STR)) - .arg(audio) + .arg(hasApng ? tr("Yes") : tr("No")) .arg(QLatin1String(__DATE__)); QMessageBox::about(this, tr("About"), msg); }