diff --git a/Jamulus.pro b/Jamulus.pro index 3d1d5acd45..499a3dcfa8 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -1,10 +1,4 @@ -VERSION = 3.11.0dev - -# Using lrelease and embed_translations only works for Qt 5.12 or later. -# See https://github.com/jamulussoftware/jamulus/pull/3288 for these changes. -lessThan(QT_MAJOR_VERSION, 5) | equals(QT_MAJOR_VERSION, 5) : lessThan(QT_MINOR_VERSION, 12) { - error(Jamulus requires at least Qt5.12. See https://github.com/jamulussoftware/jamulus/pull/3288) -} +VERSION = 3.12.0qml # use target name which does not use a capital letter at the beginning contains(CONFIG, "noupcasename") { @@ -12,60 +6,23 @@ contains(CONFIG, "noupcasename") { TARGET = jamulus } -# allow detailed version info for intermediate builds (#475) -contains(VERSION, .*dev.*) { - exists(".git/config") { - GIT_DESCRIPTION=$$system(git describe --match=xxxxxxxxxxxxxxxxxxxx --always --abbrev --dirty) # the match should never match - VERSION = "$$VERSION"-$$GIT_DESCRIPTION - message("building version \"$$VERSION\" (intermediate in git repository)") - } else { - VERSION = "$$VERSION"-nogit - message("building version \"$$VERSION\" (intermediate without git repository)") - } -} else { - message("building version \"$$VERSION\" (release)") -} - CONFIG += qt \ thread \ - lrelease \ - embed_translations \ - debug_and_release + lrelease QT += network \ xml \ - concurrent - -contains(CONFIG, "nosound") { - CONFIG -= "nosound" - CONFIG += "serveronly" - warning("\"nosound\" is deprecated: please use \"serveronly\" for a server-only build.") -} + concurrent \ + svg contains(CONFIG, "headless") { message(Headless mode activated.) QT -= gui } else { - QT += widgets - QT += multimedia + QT += qml \ + quickcontrols2 } -# Do not set LRELEASE_DIR explicitly when using embed_translations. -# It doesn't work with multiple targets or architectures. -TRANSLATIONS = src/translation/translation_de_DE.ts \ - src/translation/translation_fr_FR.ts \ - src/translation/translation_ko_KR.ts \ - src/translation/translation_pt_PT.ts \ - src/translation/translation_pt_BR.ts \ - src/translation/translation_es_ES.ts \ - src/translation/translation_nb_NO.ts \ - src/translation/translation_nl_NL.ts \ - src/translation/translation_pl_PL.ts \ - src/translation/translation_sk_SK.ts \ - src/translation/translation_it_IT.ts \ - src/translation/translation_sv_SE.ts \ - src/translation/translation_zh_CN.ts - INCLUDEPATH += src INCLUDEPATH_OPUS = libs/opus/include \ @@ -75,135 +32,54 @@ INCLUDEPATH_OPUS = libs/opus/include \ libs/opus/silk/fixed \ libs/opus -# As JACK is used in multiple OS, we declare it globally -HEADERS_JACK = src/sound/jack/sound.h -SOURCES_JACK = src/sound/jack/sound.cpp - DEFINES += APP_VERSION=\\\"$$VERSION\\\" \ CUSTOM_MODES \ _REENTRANT -# some depreciated functions need to be kept for older versions to build -# TODO as soon as we drop support for the old Qt version, remove the following line -DEFINES += QT_NO_DEPRECATED_WARNINGS - win32 { DEFINES -= UNICODE # fixes issue with ASIO SDK (asiolist.cpp is not unicode compatible) - DEFINES += NOMINMAX # solves a compiler error in qdatetime.h (Qt5) - RC_FILE = src/res/win-mainicon.rc - mingw* { - DEFINES += _WIN32_WINNT=0x0600 # solves missing inet_pton in CSocket::SendPacket - LIBS += -lole32 \ - -luser32 \ - -ladvapi32 \ - -lwinmm \ - -lws2_32 - } else { - QMAKE_LFLAGS += /DYNAMICBASE:NO # fixes crash with libjack64.dll, see https://github.com/jamulussoftware/jamulus/issues/93 - LIBS += ole32.lib \ - user32.lib \ - advapi32.lib \ - winmm.lib \ - ws2_32.lib - greaterThan(QT_MAJOR_VERSION, 5) { - # Qt5 had a special qtmain library which took care of forwarding the MSVC default WinMain() entrypoint to - # the platform-agnostic main(). - # Qt6 is still supposed to have that lib under the new name QtEntryPoint. As it does not seem - # to be effective when building with qmake, we are rather instructing MSVC to use the platform-agnostic - # main() entrypoint directly: - QMAKE_LFLAGS += /subsystem:windows /ENTRY:mainCRTStartup - } - } - - contains(CONFIG, "serveronly") { - message(Restricting build to server-only due to CONFIG+=serveronly.) - DEFINES += SERVER_ONLY - } else { - contains(CONFIG, "jackonwindows") { - message(Using JACK.) - contains(QT_ARCH, "i386") { - exists("C:/Program Files (x86)") { - message("Cross compilation build") - programfilesdir = "C:/Program Files (x86)" - } else { - message("Native i386 build") - programfilesdir = "C:/Program Files" - } - libjackname = "libjack.lib" - } else { - message("Native x86_64 build") - programfilesdir = "C:/Program Files" - libjackname = "libjack64.lib" - } - !exists("$${programfilesdir}/JACK2/include/jack/jack.h") { - error("Error: jack.h was not found in the expected location ($${programfilesdir}). Ensure that the right JACK2 variant is installed (32 Bit vs. 64 Bit).") - } - - HEADERS += $$HEADERS_JACK - SOURCES += $$SOURCES_JACK - DEFINES += WITH_JACK - DEFINES += JACK_ON_WINDOWS - DEFINES += _STDINT_H # supposed to solve compilation error in systemdeps.h - INCLUDEPATH += "$${programfilesdir}/JACK2/include" - LIBS += "$${programfilesdir}/JACK2/lib/$${libjackname}" - } else { - message(Using native Windows MIDI.) + DEFINES += NOMINMAX # solves a compiler error with std::min/max + # RC_FILE = src/res/win-mainicon.rc - HEADERS += src/sound/midi-win/midi.h - SOURCES += src/sound/midi-win/midi.cpp + LIBS += ole32.lib \ + user32.lib \ + advapi32.lib \ + winmm.lib \ + ws2_32.lib - message(Using ASIO.) - message(Please review the ASIO SDK licence.) - - !exists(libs/ASIOSDK2/common) { - error("Error: ASIOSDK2 must be placed in Jamulus \\libs folder such that e.g. \\libs\ASIOSDK2\common exists.") - } - # Important: Keep those ASIO includes local to this build target in - # order to avoid poisoning other builds license-wise. - HEADERS += src/sound/asio/sound.h - SOURCES += src/sound/asio/sound.cpp \ - libs/ASIOSDK2/common/asio.cpp \ - libs/ASIOSDK2/host/asiodrivers.cpp \ - libs/ASIOSDK2/host/pc/asiolist.cpp - INCLUDEPATH += libs/ASIOSDK2/common \ - libs/ASIOSDK2/host \ - libs/ASIOSDK2/host/pc - } + !exists(windows/ASIOSDK2) { + error("Error: ASIOSDK2 must be placed in reporoot windows/ folder.") } -} else:macx { - contains(CONFIG, "server_bundle") { - message(The generated application bundle will run a server instance.) + # Important: Keep those ASIO includes local to this build target in + # order to avoid poisoning other builds license-wise. + HEADERS += src/sound/asio/sound.h + SOURCES += src/sound/asio/sound.cpp \ + windows/ASIOSDK2/common/asio.cpp \ + windows/ASIOSDK2/host/asiodrivers.cpp \ + windows/ASIOSDK2/host/pc/asiolist.cpp + INCLUDEPATH += windows/ASIOSDK2/common \ + windows/ASIOSDK2/host \ + windows/ASIOSDK2/host/pc - DEFINES += SERVER_BUNDLE - TARGET = $${TARGET}Server - MACOSX_BUNDLE_ICON.files = src/res/mac-jamulus-server.icns - RC_FILE = src/res/mac-jamulus-server.icns - } else { - MACOSX_BUNDLE_ICON.files = src/res/mac-mainicon.icns - RC_FILE = src/res/mac-mainicon.icns - } +} else:macx { + # MACOSX_BUNDLE_ICON.files = mac/mac-mainicon.icns HEADERS += src/mac/activity.h src/mac/badgelabel.h OBJECTIVE_SOURCES += src/mac/activity.mm src/mac/badgelabel.mm CONFIG += x86 - QMAKE_TARGET_BUNDLE_PREFIX = app.jamulussoftware + QMAKE_TARGET_BUNDLE_PREFIX = live.jamulus + QMAKE_INFO_PLIST = mac/Info-xcode.plist OSX_ENTITLEMENTS.files = mac/Jamulus.entitlements OSX_ENTITLEMENTS.path = Contents/Resources QMAKE_BUNDLE_DATA += OSX_ENTITLEMENTS - - macx-xcode { - # As of 2023-04-15 the macOS build with Xcode only fails. This is tracked in #1841 - QMAKE_INFO_PLIST = mac/Info-xcode.plist - XCODE_ENTITLEMENTS.name = CODE_SIGN_ENTITLEMENTS - XCODE_ENTITLEMENTS.value = mac/Jamulus.entitlements - QMAKE_MAC_XCODE_SETTINGS += XCODE_ENTITLEMENTS - MACOSX_BUNDLE_ICON.path = Contents/Resources - QMAKE_BUNDLE_DATA += MACOSX_BUNDLE_ICON - } else { - QMAKE_INFO_PLIST = mac/Info-make.plist - } + XCODE_ENTITLEMENTS.name = CODE_SIGN_ENTITLEMENTS + XCODE_ENTITLEMENTS.value = mac/Jamulus.entitlements + QMAKE_MAC_XCODE_SETTINGS += XCODE_ENTITLEMENTS + + MACOSX_BUNDLE_ICON.path = Contents/Resources + QMAKE_BUNDLE_DATA += MACOSX_BUNDLE_ICON LIBS += -framework CoreFoundation \ -framework CoreServices \ @@ -214,48 +90,75 @@ win32 { -framework Foundation \ -framework AppKit - contains(CONFIG, "jackonmac") { - message(Using JACK.) - !exists(/usr/include/jack/jack.h) { - !exists(/usr/local/include/jack/jack.h) { - error("Error: jack.h was not found at the usual place, maybe JACK is not installed") - } - } - HEADERS += $$HEADERS_JACK - SOURCES += $$SOURCES_JACK - DEFINES += WITH_JACK - DEFINES += JACK_REPLACES_COREAUDIO - INCLUDEPATH += /usr/local/include - LIBS += /usr/local/lib/libjack.dylib - } else { - message(Using CoreAudio.) - HEADERS += src/sound/coreaudio-mac/sound.h - SOURCES += src/sound/coreaudio-mac/sound.cpp - } + HEADERS += src/sound/coreaudio-mac/sound.h + SOURCES += src/sound/coreaudio-mac/sound.cpp } else:ios { - QMAKE_ASSET_CATALOGS += src/res/iOSIcons.xcassets - QMAKE_INFO_PLIST = ios/Info.plist - OBJECTIVE_SOURCES += src/ios/ios_app_delegate.mm - HEADERS += src/ios/ios_app_delegate.h + # reset TARGET for iOS only since rename + TARGET = Jamulus + QMAKE_INFO_PLIST = ios/Info-xcode.plist + QMAKE_ASSET_CATALOGS += ios/Images.xcassets + QMAKE_ASSET_CATALOGS_APP_ICON = "AppIcon" + ios_icon.files = $$files($$PWD/ios/AppIcon*.png) + QMAKE_BUNDLE_DATA += ios_icon + HEADERS += src/sound/coreaudio-ios/sound.h OBJECTIVE_SOURCES += src/sound/coreaudio-ios/sound.mm - QMAKE_TARGET_BUNDLE_PREFIX = app.jamulussoftware + + # PRODUCT_BUNDLE_IDENTIFIER is set like + # ${PRODUCT_BUNDLE_IDENTIFIER} = QMAKE_TARGET_BUNDLE_PREFIX.QMAKE_BUNDLE + QMAKE_TARGET_BUNDLE_PREFIX = live.jamulus + QMAKE_BUNDLE = Jamulus + LIBS += -framework AVFoundation \ -framework AudioToolbox + } else:android { - ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64 + # ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64 + # Build all targets, as per: https://developer.android.com/topic/arc/device-support + + # get ANDROID_ABIS from environment - passed directly to qmake + ANDROID_ABIS = $$getenv(ANDROID_ABIS) + + # if ANDROID_ABIS is passed as env var to qmake, will override this + # !defined(ANDROID_ABIS, var):ANDROID_ABIS = arm64-v8a + + # by default is 23 = Android 6 ! + # Update: try with min Android 8.1 - sdk27 + ANDROID_MIN_SDK_VERSION = 27 + ANDROID_TARGET_SDK_VERSION = 32 ANDROID_VERSION_NAME = $$VERSION - ANDROID_VERSION_CODE = $$system(git log --oneline | wc -l) + + ## FOR LOCAL DEV USE: + equals(QMAKE_HOST.os, Windows) { + ANDROID_ABIS = x86_64 + ANDROID_VERSION_CODE = 1234 # dummy int value + } else { + # date-based unique integer value for Play Store submission + !defined(ANDROID_VERSION_CODE, var):ANDROID_VERSION_CODE = $$system(date +%s | cut -c 2-) + } + + # make separate version codes for each abi build otherwise Play Store rejects + contains (ANDROID_ABIS, armeabi-v7a) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 1) + message("Setting for armeabi-v7a: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + contains (ANDROID_ABIS, x86) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 2) + message("Setting for x86: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + contains (ANDROID_ABIS, x86_64) { + ANDROID_VERSION_CODE = $$num_add($$ANDROID_VERSION_CODE, 3) + message("Setting for x86_64: ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") + } + message("Setting ANDROID_VERSION_NAME=$${ANDROID_VERSION_NAME} ANDROID_VERSION_CODE=$${ANDROID_VERSION_CODE}") # liboboe requires C++17 for std::timed_mutex CONFIG += c++17 - QT += androidextras - # enabled only for debugging on android devices - DEFINES += ANDROIDDEBUG + #DEFINES += ANDROIDDEBUG target.path = /tmp/your_executable # path on device INSTALLS += target @@ -286,15 +189,9 @@ win32 { HEADERS += $$OBOE_HEADERS SOURCES += $$OBOE_SOURCES DISTFILES += $$DISTFILES_OBOE -} else:unix { - # we want to compile with C++11 - CONFIG += c++11 - # --as-needed avoids linking the final binary against unnecessary runtime - # libs. Most g++ versions already do that by default. - # However, Debian buster does not and would link against libQt5Concurrent - # unnecessarily without this workaround (#741): - QMAKE_LFLAGS += -Wl,--as-needed +} else:unix { + CONFIG += c++17 # we assume to have lrintf() one moderately modern linux distributions # would be better to have that tested, though @@ -310,17 +207,12 @@ win32 { } else { message(JACK Audio Interface Enabled.) - HEADERS += $$HEADERS_JACK - SOURCES += $$SOURCES_JACK - - contains(CONFIG, "raspijamulus") { - message(Using JACK Audio in raspijamulus.sh mode.) - LIBS += -ljack - } else { - CONFIG += link_pkgconfig - PKGCONFIG += jack - } + HEADERS += src/sound/jack/sound.h + SOURCES += src/sound/jack/sound.cpp + CONFIG += link_pkgconfig + PKGCONFIG += jack + DEFINES += WITH_JACK } @@ -334,59 +226,17 @@ win32 { BINDIR = $$absolute_path($$BINDIR, $$PREFIX) target.path = $$BINDIR - contains(CONFIG, "headless") { - INSTALLS += target - } else { - isEmpty(APPSDIR) { - APPSDIR = share/applications - } - APPSDIR = $$absolute_path($$APPSDIR, $$PREFIX) - desktop.path = $$APPSDIR - QMAKE_SUBSTITUTES += linux/jamulus.desktop.in linux/jamulus-server.desktop.in - desktop.files = linux/jamulus.desktop linux/jamulus-server.desktop - - isEmpty(ICONSDIR) { - ICONSDIR = share/icons/hicolor/512x512/apps - } - ICONSDIR = $$absolute_path($$ICONSDIR, $$PREFIX) - icons.path = $$ICONSDIR - icons.files = src/res/io.jamulus.jamulus.png - - isEmpty(ICONSDIR_SVG) { - ICONSDIR_SVG = share/icons/hicolor/scalable/apps/ - } - ICONSDIR_SVG = $$absolute_path($$ICONSDIR_SVG, $$PREFIX) - icons_svg.path = $$ICONSDIR_SVG - icons_svg.files = src/res/io.jamulus.jamulus.svg src/res/io.jamulus.jamulusserver.svg - - isEmpty(MANDIR) { - MANDIR = share/man/man1 - } - MANDIR = $$absolute_path($$MANDIR, $$PREFIX) - man.path = $$MANDIR - man.files = linux/Jamulus.1 - - INSTALLS += target desktop icons icons_svg man - } + INSTALLS += target } -# Do not set RCC_DIR explicitly when using embed_translations. -# It doesn't work with multiple targets or architectures. +RCC_DIR = src/res RESOURCES += src/resources.qrc -FORMS_GUI = src/aboutdlgbase.ui \ - src/serverdlgbase.ui +#FORMS_GUI = src/serverdlgbase.ui -!contains(CONFIG, "serveronly") { - FORMS_GUI += src/clientdlgbase.ui \ - src/clientsettingsdlgbase.ui \ - src/chatdlgbase.ui \ - src/connectdlgbase.ui -} - -HEADERS += src/plugins/audioreverb.h \ - src/buffer.h \ +HEADERS += src/buffer.h \ src/channel.h \ + src/chatbox.h \ src/global.h \ src/protocol.h \ src/recorder/jamcontroller.h \ @@ -401,6 +251,7 @@ HEADERS += src/plugins/audioreverb.h \ src/recorder/creaperproject.h \ src/recorder/cwavestream.h \ src/signalhandler.h + # src/messagereceiver.h !contains(CONFIG, "serveronly") { HEADERS += src/client.h \ @@ -408,17 +259,12 @@ HEADERS += src/plugins/audioreverb.h \ src/testbench.h } -HEADERS_GUI = src/serverdlg.h +#HEADERS_GUI = src/serverdlg.h !contains(CONFIG, "serveronly") { HEADERS_GUI += src/audiomixerboard.h \ - src/chatdlg.h \ - src/clientsettingsdlg.h \ - src/connectdlg.h \ - src/clientdlg.h \ src/levelmeter.h \ - src/analyzerconsole.h \ - src/multicolorled.h + # src/analyzerconsole.h \ } HEADERS_OPUS = libs/opus/celt/arch.h \ @@ -492,9 +338,9 @@ HEADERS_OPUS_X86 = libs/opus/celt/x86/celt_lpc_sse.h \ libs/opus/celt/x86/x86cpu.h \ $$files(libs/opus/silk/x86/*.h) -SOURCES += src/plugins/audioreverb.cpp \ - src/buffer.cpp \ +SOURCES += src/buffer.cpp \ src/channel.cpp \ + src/chatbox.cpp \ src/main.cpp \ src/protocol.cpp \ src/recorder/jamcontroller.cpp \ @@ -508,23 +354,22 @@ SOURCES += src/plugins/audioreverb.cpp \ src/recorder/jamrecorder.cpp \ src/recorder/creaperproject.cpp \ src/recorder/cwavestream.cpp + # src/messagereceiver.cpp !contains(CONFIG, "serveronly") { SOURCES += src/client.cpp \ src/sound/soundbase.cpp \ } -SOURCES_GUI = src/serverdlg.cpp +#SOURCES_GUI = src/serverdlg.cpp !contains(CONFIG, "serveronly") { SOURCES_GUI += src/audiomixerboard.cpp \ - src/chatdlg.cpp \ - src/clientsettingsdlg.cpp \ - src/connectdlg.cpp \ - src/clientdlg.cpp \ - src/multicolorled.cpp \ + # src/clientdlg.cpp \ + # src/clientsettingsdlg.cpp \ + # src/multicolorled.cpp \ src/levelmeter.cpp \ - src/analyzerconsole.cpp + # src/analyzerconsole.cpp } SOURCES_OPUS = libs/opus/celt/bands.c \ @@ -1173,11 +1018,6 @@ contains(CONFIG, "opus_shared_lib") { } } -# disable version check if requested (#370) -contains(CONFIG, "disable_version_check") { - message(The version check is disabled.) - DEFINES += DISABLE_VERSION_CHECK -} # Enable formatting all code via `make clang_format`. # Note: When extending the list of file extensions or when adding new code directories, diff --git a/src/AppWindow.qml b/src/AppWindow.qml new file mode 100644 index 0000000000..07e943e0eb --- /dev/null +++ b/src/AppWindow.qml @@ -0,0 +1,98 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +ApplicationWindow { + visible: true + minimumWidth: 600 + minimumHeight: 490 + title: qsTr("Jamulus") + + ColumnLayout { + anchors.fill: parent + + // Tab Bar + TabBar { + id: tabBar + Layout.fillWidth: true + + TabButton { + text: qsTr("Home") + checked: stackLayout.currentIndex === 0 + onClicked: stackLayout.currentIndex = 0 + } + + TabButton { + text: qsTr("Settings") + checked: stackLayout.currentIndex === 1 + onClicked: stackLayout.currentIndex = 1 + } + } + + // Stack Layout for Tab Content + StackLayout { + id: stackLayout + Layout.fillWidth: true + Layout.fillHeight: true + + // Home Tab Content + Item { + id: homeTab + Layout.fillWidth: true + Layout.fillHeight: true + + MainView { + anchors.fill: parent + } + } + + // Settings Tab Content + Item { + id: settingsTab + Layout.fillWidth: true + Layout.fillHeight: true + + SettingsView { + // anchors.fill: parent + } + } + } + + Popup { + id: userPopup + x: (parent.width - width) / 2 + y: (parent.height - height) / 2 + width: 300 + height: 150 + visible: _main.userMsg !== "" // Show the popup when there's a message + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + + Rectangle { + anchors.fill: parent + color: "white" + + Label { + id: userMessage + text: _main.userMsg // Display the message from _main + anchors.centerIn: parent + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + width: parent.width - 20 // Ensure some padding from the edges + } + } + + onVisibleChanged: { + if (!visible) { + _main.userMsg = "" + } + } + } + } + + // React to changes in _client.userMsg + function onUserMsgChanged(newMsg) { + if (newMsg !== "") { + userPopup.open() + } + } +} diff --git a/src/ChannelFader.qml b/src/ChannelFader.qml new file mode 100644 index 0000000000..c19c9ca19a --- /dev/null +++ b/src/ChannelFader.qml @@ -0,0 +1,169 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Rectangle { + id: channelFader + border.width: 2 + radius: 5 + border.color: "#d9d9d9" + + property var channelModel + // use ? and ?? operators to suppress errors when channelModel is null + property string channelUserName: channelModel?.channelUserName ?? "" // channelUserNameText.text + property double channelLevel: channelModel?.channelMeter.doubleVal ?? 0 + property bool channelClipStatus: channelModel?.channelMeter.clipStatus ?? false + property double faderLevel: channelModel?.faderLevel ?? 0 // volumeFader.value + property double panLevel: channelModel?.panLevel ?? 0 // panKnob.value + property bool isMuted: channelModel?.isMuted ?? 0 // muteButton.checked + property bool isSolo: channelModel?.isSolo ?? 0 // soloButton.checked + property int groupID: channelModel?.groupID ?? 0 // groupId + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + anchors.centerIn: parent + spacing: 3 + + Text { + id: panKnobLabel + text: "PAN" + } + + // Pan Knob + Dial { + id: panKnob + from: 0 + to: 100 + value: panLevel // default - set to AUD_MIX_PAN_MAX / 2 + stepSize: 1 + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.bottomMargin: 4 + background: Rectangle { + // color: "white" + border.width: 1 + border.color: "#4e4e4e" + radius: width / 2 + } + + onMoved: { + channelModel.setPanLevel(value) + } + } + + RowLayout { + spacing: 5 + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: 200 + Layout.preferredWidth: 15 + + // Level Meter + SingleLevelMeter { + id: levelMeterRectangleUser + heightPercentage: channelLevel + chanClipStatus: channelClipStatus + } + + // Fader + Slider { + id: volumeFader + orientation: Qt.Vertical + from: 0.0 + to: 100.0 + value: faderLevel // FIXME - set to AUD_MIX_FADER_MAX + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: parent.height + + onValueChanged: { + channelModel.setFaderLevel(value) + } + } + } + + // GRPMTSOLO box + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + spacing: 5 + + RowLayout { + spacing: 5 + Layout.alignment: Qt.AlignHCenter + + Button { + id: muteButton + checkable: true + text: "M" + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + font.bold: true + checked: isMuted + onClicked: { + channelModel.setIsMuted(!isMuted) + } + } + + Button { + id: soloButton + checkable: true + text: "S" + Layout.preferredWidth: 30 + Layout.preferredHeight: 30 + font.bold: true + checked: isSolo + onClicked: { + channelModel.setIsSolo(!isSolo) + } + } + } + + Button { + id: groupChooser + text: groupID > 0 ? groupID.toString() : "GRP" + Layout.preferredWidth: 50 + Layout.preferredHeight: 25 + Layout.alignment: Qt.AlignHCenter + font.bold: true + onClicked: menu.open() + + Menu { + id: menu + y: groupChooser.height + + Repeater { + model: 9 // Total number of items + delegate: MenuItem { + property int groupId: index + text: index === 0 ? "No group" : "Group " + index + onTriggered: channelModel.setGroupID(groupId) + } + } + } + } + + } + + // Username label + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 50 + Layout.alignment: Qt.AlignHCenter + border.color: "#d9d9d9" + border.width: 1 + radius: 3 + + Label { + id: channelUserNameText + anchors.fill: parent + text: channelUserName + font.bold: true + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + } +} + diff --git a/src/ChatBox.qml b/src/ChatBox.qml new file mode 100644 index 0000000000..48346fe316 --- /dev/null +++ b/src/ChatBox.qml @@ -0,0 +1,75 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Item { + id: chatBox + width: 400 + height: 300 + + ColumnLayout { + anchors.fill: parent + spacing: 6 + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + + TextArea { + id: chatArea + Layout.fillWidth: true + Layout.fillHeight: true + readOnly: true + wrapMode: TextEdit.Wrap + textFormat: TextEdit.RichText // Enable HTML rendering + text: _chatBox.chatHistory + font.pixelSize: 12 + + // Add padding for better text display + leftPadding: 10 + rightPadding: 10 + topPadding: 10 + bottomPadding: 10 + + // Auto-scroll to bottom when new messages arrive + onTextChanged: { + cursorPosition = length + if (length > 0) { + // ensure cursor is visible + chatArea.flickableItem.contentY = chatArea.flickableItem.contentHeight - chatArea.flickableItem.height + } + } + } + } + + RowLayout { + Layout.fillWidth: true + spacing: 4 + + TextField { + id: chatInput + Layout.fillWidth: true + placeholderText: qsTr("Type a message…") + onAccepted: { + _chatBox.sendMessage(text) + text = "" + } + } + + Button { + text: qsTr("Send") + onClicked: { + _chatBox.sendMessage(chatInput.text) + chatInput.text = "" + } + } + } + + Button { + text: qsTr("Clear Chat") + onClicked: { + _chatBox.clearChat() + } + } + } +} diff --git a/src/MainView.qml b/src/MainView.qml new file mode 100644 index 0000000000..e56d9a6170 --- /dev/null +++ b/src/MainView.qml @@ -0,0 +1,346 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import "." + +Rectangle { + anchors.fill: parent + + RowLayout { + anchors.fill: parent + spacing: 0 + + // Left Control Panel + Rectangle { + Layout.preferredWidth: 150 + radius: 10 + border.color: "#d9d9d9" + Layout.fillHeight: true + + ColumnLayout { + anchors.fill: parent + + Text { + text: "INPUT" + font.pixelSize: 14 + Layout.alignment: Qt.AlignHCenter + } + + // input meter section + Rectangle { + Layout.alignment: Qt.AlignHCenter + Layout.preferredHeight: 200 + Layout.preferredWidth: 20 + + StereoLevelMeter { + id: levelMeterRectangle + anchors.fill: parent + Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter + levelValueL: _main.inputMeterL.doubleVal + levelValueR: _main.inputMeterR.doubleVal + } + } + + // mute / pan section + GridLayout { + Layout.alignment: Qt.AlignHCenter + columns: 2 + + Button { + text: "M" + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignHCenter + font.bold: true + checkable: true + checked: _main.muteOut + onClicked: _main.muteOut = checked + } + + Dial { + id: inPanDial + Layout.preferredWidth: 45 + Layout.preferredHeight: 45 + from: 0 // FIXME set from defines + to: 100 + value: _main.audioInPan + onMoved: _main.audioInPan = value + + //TODO: customise Dial something like this - need to set dimensions correctly + // background: Rectangle { + // x: inPanDial.width / 2 - width / 2 + // y: inPanDial.height / 2 - height / 2 + // implicitWidth: 140 + // implicitHeight: 140 + // width: Math.max(64, Math.min(inPanDial.width, inPanDial.height)) + // height: width + // color: "transparent" + // radius: width / 2 + // border.color: inPanDial.pressed ? "#17a81a" : "#21be2b" + // opacity: inPanDial.enabled ? 1 : 0.3 + // } + + // handle: Rectangle { + // id: handleItem + // x: inPanDial.background.x + inPanDial.background.width / 2 - width / 2 + // y: inPanDial.background.y + inPanDial.background.height / 2 - height / 2 + // width: 16 + // height: 16 + // color: inPanDial.pressed ? "#17a81a" : "#21be2b" + // radius: 8 + // antialiasing: true + // opacity: inPanDial.enabled ? 1 : 0.3 + // transform: [ + // Translate { + // y: -Math.min(inPanDial.background.width, inPanDial.background.height) * 0.4 + handleItem.height / 2 + // }, + // Rotation { + // angle: inPanDial.angle + // origin.x: handleItem.width / 2 + // origin.y: handleItem.height / 2 + // } + // ] + // } + + } + + Text { + text: "mute" + font.pixelSize: 10 + Layout.alignment: Qt.AlignHCenter + } + + Text { + text: "pan" + font.pixelSize: 10 + Layout.alignment: Qt.AlignHCenter + } + + } + + // Network Check Section + GridLayout { + Layout.alignment: Qt.AlignCenter + columns: 2 + Layout.leftMargin: 5 + + Text { + text: "PING" + } + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 15 + color: "#000000" + Label { + anchors.centerIn: parent + text: _main.pingVal + color: { _main.pingVal < 40 ? "green" : "red" } + font.bold: true + } + } + + Text { + text: "DELAY" + } + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 15 + color: "#000000" + Label { + anchors.centerIn: parent + color: _main.delayVal <= 43 ? "green" : + _main.delayVal <= 68 ? "yellow" : + "red" + text: _main.delayVal + font.bold: true + } + } + + Text { + text: "JITTER" + } + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 15 + color: "#000000" + Rectangle { + anchors.centerIn: parent + visible: _main.jitterWarn + width: 20 + height: 8 + radius: 2 + color: _main.jitterWarn ? "red" : "#000000" + } + } + } + + GridLayout { + id: sessionLinkInputBox + Layout.alignment: Qt.AlignHCenter + columns: 1 + + TextField { + id: sessionlinkText + Layout.preferredWidth: 110 + text: _main.sessionlinkText + onTextChanged: _main.sessionlinkText = text; + Layout.alignment: Qt.AlignHCenter + } + + Button { + id: connectButton + text: "Connect" + Layout.preferredWidth: 80 + Layout.preferredHeight: 30 + Layout.alignment: Qt.AlignHCenter + font.bold: true + onClicked: _main.onConnectButtonClicked() + visible: !_main.bConnected + } + + Button { + id: disconnectButton + text: "Disconnect" + Layout.preferredWidth: 80 + Layout.preferredHeight: 30 + Layout.alignment: Qt.AlignHCenter + font.bold: true + onClicked: _main.onDisconnectButtonClicked() + visible: _main.bConnected + } + } + } + } + + // Main Area + Rectangle { + radius: 10 + border.color: "#d9d9d9" + Layout.fillHeight: true + Layout.fillWidth: true + + ColumnLayout { + anchors.fill: parent + spacing: 20 + + // Connect / control area + RowLayout { + Layout.preferredHeight: 80 + spacing: 10 + + // Status area + Rectangle { + id: statusView + Layout.preferredHeight: 30 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 5 + border.width: 1 + border.color: "#d9d9d9" + + GridLayout { + columns: 2 + + Text { + text: "Session Status: " + } + Text { + text: _main.sessionStatus + } + + Text { + text: "Session Server: " + } + Text { + text: _main.sessionName + } + + Text { + text: "Recording: " + } + Text { + text: _main.recordingStatus + } + } + } + + } + + Item { + // spacer item + Layout.fillWidth: true + Layout.fillHeight: true + Rectangle { + anchors.fill: parent; + color: "white" + } // to visualize the spacer + } + + // Mixerboard area + Rectangle { + id: mixerboardView + Layout.fillWidth: true + Layout.preferredHeight: 385 + border.color: "#d9d9d9" + border.width: 2 + Layout.alignment: Qt.AlignBottom + + ListView { + id: listView + anchors.fill: parent + orientation: ListView.Horizontal + spacing: 5 + // snapMode: ListView.NoSnap + // boundsBehavior: Flickable.StopAtBounds + + model: _audioMixerBoard.channels + + delegate: ChannelFader { + id: channelFader + width: 85 + height: listView.height + channelModel: modelData + } + } + } + + } + } + + // // Splitter for resizing + // SplitHandle { + // id: splitter + // Layout.fillHeight: true + // Layout.preferredWidth: 4 + // // color: "#d9d9d9" + // } + + // Chat Panel + Rectangle { + id: chatPanel + Layout.fillHeight: true + Layout.preferredWidth: 300 + Layout.minimumWidth: 200 + Layout.maximumWidth: parent.width * 0.4 + visible: !btnShowChat.checked + radius: 10 + border.color: "#d9d9d9" + + ChatBox { + anchors.fill: parent + anchors.margins: 10 + } + } + + } + + // Toggle button + Button { + id: btnShowChat + text: checked ? qsTr("Show Chat") : qsTr("Hide Chat") + anchors.top: parent.top + anchors.right: parent.right + checkable: true + checked: false + onCheckedChanged: chatPanel.visible = !checked + } +} diff --git a/src/SettingsView.qml b/src/SettingsView.qml new file mode 100644 index 0000000000..307c8d7ed4 --- /dev/null +++ b/src/SettingsView.qml @@ -0,0 +1,290 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Item { + id: root + anchors.fill: parent + + ScrollView { + anchors.fill: parent + + ColumnLayout { + id: mainColumn + anchors.fill: parent + spacing: 10 + + // 1) + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 150 + border.color: "black" + border.width: 2 + radius: 10 + + // Stack items vertically inside + ColumnLayout { + id: profileColumn + anchors.centerIn: parent + anchors.margins: 10 + spacing: 10 + + Text { + text: "GENERAL" + Layout.alignment: Qt.AlignHCenter + font.bold: true + } + + GridLayout { + columns: 2 + rowSpacing: 6 + columnSpacing: 10 + + Text { text: "Username" } + TextField { + id: inputField + width: 200 + placeholderText: "Enter alias here" + text: _settings.pedtAlias + onTextChanged: _settings.pedtAlias = text + } + + Text { text: "Mixer Rows count" } + SpinBox { + id: spnMixerRows + value: _settings.spnMixerRows + onValueChanged: _settings.spnMixerRows = value + } + } + } + } + + // 2) + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 400 + border.color: "black" + border.width: 2 + radius: 10 + + ColumnLayout { + anchors.centerIn: parent + anchors.margins: 10 + spacing: 10 + + Text { + text: "AUDIO" + Layout.alignment: Qt.AlignHCenter + font.bold: true + } + + GridLayout { + columns: 2 + rowSpacing: 6 + columnSpacing: 10 + + Text { text: "Audio Device" } + ComboBox { + id: cbxSoundcard + model: _settings.slSndCrdDevNames + Component.onCompleted: { + let idx = _settings.slSndCrdDevNames.indexOf(_settings.slSndCrdDev) + if (idx !== -1) currentIndex = idx + } + onCurrentIndexChanged: { + if (currentIndex >= 0 && currentIndex < _settings.slSndCrdDevNames.length) { + _settings.slSndCrdDev = _settings.slSndCrdDevNames[currentIndex] + } + } + } + Connections { + target: _settings + function onSlSndCrdDevChanged() { + let newIdx = _settings.slSndCrdDevNames.indexOf(_settings.slSndCrdDev) + if (newIdx !== -1 && newIdx !== cbxSoundcard.currentIndex) { + cbxSoundcard.currentIndex = newIdx + } + } + } + + // Channel Mappings + Text { text: "Input channel - LEFT" } + ComboBox { + id: cbxLInChan + model: _settings.sndCrdInputChannelNames + displayText: _settings.sndCardLInChannel + onCurrentTextChanged: _settings.sndCardLInChannel = currentText + } + + Text { text: "Input channel - RIGHT" } + ComboBox { + id: cbxRInChan + model: _settings.sndCrdInputChannelNames + displayText: _settings.sndCardRInChannel + onCurrentTextChanged: _settings.sndCardRInChannel = currentText + } + + Text { text: "Output channel - LEFT" } + ComboBox { + id: cbxLOutChan + model: _settings.sndCrdOutputChannelNames + displayText: _settings.sndCardLOutChannel + onCurrentTextChanged: _settings.sndCardLOutChannel = currentText + } + + Text { text: "Output channel - RIGHT" } + ComboBox { + id: cbxROutChan + model: _settings.sndCrdOutputChannelNames + displayText: _settings.sndCardROutChannel + onCurrentTextChanged: _settings.sndCardRoutChannel = currentText + } + + Text { text: "Mono/Stereo mode" } + ComboBox { + id: cbxAudioChannels + model: ListModel { + ListElement { text: "Mono" } + ListElement { text: "Mono-in/Stereo-out" } + ListElement { text: "Stereo" } + } + currentIndex: _settings.cbxAudioChannels + onCurrentIndexChanged: _settings.cbxAudioChannels = currentIndex + } + + Text { text: "Audio quality" } + ComboBox { + id: cbxAudioQuality + model: ListModel { + ListElement { text: "Low" } + ListElement { text: "Normal" } + ListElement { text: "High" } + } + currentIndex: _settings.cbxAudioQuality + onActivated: _settings.cbxAudioQuality = currentIndex + } + + Text { text: "New client level: " + _settings.edtNewClientLevel } + Slider { + id: newInputLevelDial + from: 0 + to: 100 + value: _settings.edtNewClientLevel + onMoved: _settings.edtNewClientLevel = value + } + + Text { text: "Feedback Detection" } + CheckBox { + id: chbDetectFeedback + checked: _settings.chbDetectFeedback + } + + Text { text: "Buffer Size: " + _settings.bufSize } + ColumnLayout { + spacing: 4 + ButtonGroup { + id: bufferDelayGroup + exclusive: true + buttons: [ + rbtBufferDelayPreferred, + rbtBufferDelayDefault, + rbtBufferDelaySafe + ] + } + RadioButton { + id: rbtBufferDelayPreferred + text: _settings.sndCrdBufferDelayPreferred + enabled: _settings.fraSiFactPrefSupported + checked: _settings.rbtBufferDelayPreferred + onClicked: _settings.rbtBufferDelayPreferred = true + } + RadioButton { + id: rbtBufferDelayDefault + text: _settings.sndCrdBufferDelayDefault + enabled: _settings.fraSiFactDefSupported + checked: _settings.rbtBufferDelayDefault + onClicked: _settings.rbtBufferDelayDefault = true + } + RadioButton { + id: rbtBufferDelaySafe + text: _settings.sndCrdBufferDelaySafe + enabled: _settings.fraSiFactSafeSupported + checked: _settings.rbtBufferDelaySafe + onClicked: _settings.rbtBufferDelaySafe = true + } + } + } + } + + + } + + // 3) + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 200 + border.color: "black" + border.width: 2 + radius: 10 + + ColumnLayout { + anchors.centerIn: parent + anchors.margins: 10 + spacing: 10 + + Text { + text: "NETWORK" + Layout.alignment: Qt.AlignHCenter + font.bold: true + } + + GridLayout { + columns: 2 + rowSpacing: 6 + columnSpacing: 10 + + Text { text: "Jitter Buffer" } + CheckBox { + id: chbAutoJitBuf + checked: _settings.chbAutoJitBuf + onCheckedChanged: _settings.chbAutoJitBuf = checked + text: "Auto" + } + + Text { text: "Client buffer: " + _settings.sldNetBuf } + Slider { + id: sldNetBuf + from: _settings.sldNetBufMin + to: _settings.sldNetBufMax + value: _settings.sldNetBuf + onMoved: _settings.sldNetBuf = value + enabled: !chbAutoJitBuf.checked + } + + Text { text: "Server Buffer: " + _settings.sldNetBufServer } + Slider { + id: sldNetBufServer + from: _settings.sldNetBufMin + to: _settings.sldNetBufMax + value: _settings.sldNetBufServer + onMoved: _settings.sldNetBufServer = value + enabled: !chbAutoJitBuf.checked + } + + Text { text: "Small network buffers" } + CheckBox { + id: chbEnableOPUS64 + checked: _settings.chbEnableOPUS64 + onCheckStateChanged: _settings.chbEnableOPUS64 = checkState + } + + Text { text: "Upload rate:" } + Text { + text: _main.bConnected ? (_settings.uploadRate + " kbps") : "--" + } + } + } + } + } + } +} diff --git a/src/SingleLevelMeter.qml b/src/SingleLevelMeter.qml new file mode 100644 index 0000000000..cedaa08bd7 --- /dev/null +++ b/src/SingleLevelMeter.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Item { + id: singleLevelMeter + width: 10 // Set default width + height: 200 // Set default height + + property real heightPercentage: 0 + property bool chanClipStatus: false + + Rectangle { + id: backgroundBar + anchors.fill: parent + color: "black" + radius: 2 + } + + Rectangle { + id: levelBar + width: parent.width + height: parent.height * Math.min(heightPercentage, 1) + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + radius: 1 + // When chanClipStatus is true, show a thick red border + border.color: chanClipStatus ? "red" : "transparent" + border.width: chanClipStatus ? 4 : 0 + color: heightPercentage > 0.95 ? "#FF260A" : + heightPercentage > 0.85 ? "#FF8C0A" : + "#3AF864" + } +} diff --git a/src/SplitHandle.qml b/src/SplitHandle.qml new file mode 100644 index 0000000000..7d43419c91 --- /dev/null +++ b/src/SplitHandle.qml @@ -0,0 +1,19 @@ +import QtQuick 2.15 + +Rectangle { + id: handle + property int minimumX: 200 + property int maximumX: parent.width - 200 + + MouseArea { + anchors.fill: parent + anchors.margins: -5 + cursorShape: Qt.SplitHCursor + drag { + target: parent + axis: Drag.XAxis + minimumX: handle.minimumX + maximumX: handle.maximumX + } + } +} diff --git a/src/StereoLevelMeter.qml b/src/StereoLevelMeter.qml new file mode 100644 index 0000000000..b7ea2d2175 --- /dev/null +++ b/src/StereoLevelMeter.qml @@ -0,0 +1,31 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import "." // Ensure SingleLevelMeter.qml is in the import path + +Rectangle { + id: levelContainer + Layout.fillWidth: true + Layout.fillHeight: true + color: "black" + radius: 2 + + property real levelValueL: 0 + property real levelValueR: 0 + + RowLayout { + id: levelBarsLayout + anchors.fill: parent + spacing: 0 + + SingleLevelMeter { + id: levelBarL + heightPercentage: levelValueL + } + + SingleLevelMeter { + id: levelBarR + heightPercentage: levelValueR + } + } +} \ No newline at end of file diff --git a/src/aboutdlgbase.ui b/src/aboutdlgbase.ui deleted file mode 100644 index 229ef78e62..0000000000 --- a/src/aboutdlgbase.ui +++ /dev/null @@ -1,236 +0,0 @@ - - - CAboutDlgBase - - - - 0 - 0 - 630 - 465 - - - - - 0 - 0 - - - - About - - - - :/png/main/res/fronticon.png:/png/main/res/fronticon.png - - - true - - - - - - - - - 0 - 0 - - - - - - - :/png/main/res/fronticon.png - - - Qt::AlignCenter - - - - - - - 0 - - - - - TextLabelVersion - - - false - - - 2 - - - - - - - Copyright © 2005-2024 The Jamulus Development Team - - - false - - - 2 - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - 0 - - - - A&bout - - - - - - true - - - - - - - - &Libraries - - - - - - true - - - - - - - - &Contributors - - - - - - true - - - - - - - - &Translation - - - - - - true - - - - - - - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 20 - 20 - - - - - - - - &OK - - - true - - - true - - - - - - - - - buttonOk - - - - - - - buttonOk - clicked() - CAboutDlgBase - accept() - - - 20 - 20 - - - 20 - 20 - - - - - diff --git a/src/audiomixerboard.cpp b/src/audiomixerboard.cpp index db6cab14e4..afb4826044 100644 --- a/src/audiomixerboard.cpp +++ b/src/audiomixerboard.cpp @@ -25,388 +25,156 @@ #include "audiomixerboard.h" /******************************************************************************\ -* CChanneFader * +* CChannelFader * \******************************************************************************/ -CChannelFader::CChannelFader ( QWidget* pNW ) : - eDesign ( GD_STANDARD ), - BitmapMutedIcon ( QString::fromUtf8 ( ":/png/fader/res/mutediconorange.png" ) ), +CChannelFader::CChannelFader ( QObject* parent ) : + QObject(parent), + m_faderLevel(AUD_MIX_FADER_MAX), + m_panLevel(AUD_MIX_PAN_MAX / 2), + m_isMuted(false), + m_isSolo(false), bMIDICtrlUsed ( false ) { - // create new GUI control objects and store pointers to them (note that - // QWidget takes the ownership of the pMainGrid so that this only has - // to be created locally in this constructor) - pFrame = new QFrame ( pNW ); - - pLevelsBox = new QWidget ( pFrame ); - plbrChannelLevel = new CLevelMeter ( pLevelsBox ); - pFader = new QSlider ( Qt::Vertical, pLevelsBox ); - pPan = new QDial ( pLevelsBox ); - pPanLabel = new QLabel ( tr ( "Pan" ), pLevelsBox ); - pInfoLabel = new QLabel ( "", pLevelsBox ); - - pMuteSoloBox = new QWidget ( pFrame ); - pcbMute = new QCheckBox ( tr ( "Mute" ), pMuteSoloBox ); - pcbSolo = new QCheckBox ( tr ( "Solo" ), pMuteSoloBox ); - pcbGroup = new QCheckBox ( "", pMuteSoloBox ); - - pLabelInstBox = new QGroupBox ( pFrame ); - plblLabel = new QLabel ( "", pFrame ); - plblInstrument = new QLabel ( pFrame ); - plblCountryFlag = new QLabel ( pFrame ); - - QVBoxLayout* pMainGrid = new QVBoxLayout ( pFrame ); - QHBoxLayout* pLevelsGrid = new QHBoxLayout ( pLevelsBox ); - QVBoxLayout* pMuteSoloGrid = new QVBoxLayout ( pMuteSoloBox ); - pLabelGrid = new QHBoxLayout ( pLabelInstBox ); - pLabelPictGrid = new QVBoxLayout(); - QVBoxLayout* pPanGrid = new QVBoxLayout(); - QHBoxLayout* pPanInfoGrid = new QHBoxLayout(); - - // define the popup menu for the group checkbox - pGroupPopupMenu = new QMenu ( "", pcbGroup ); - pGroupPopupMenu->addAction ( tr ( "&No grouping" ), this, [=] { OnGroupMenuGrp ( INVALID_INDEX ); } ); - for ( int iGrp = 0; iGrp < MAX_NUM_FADER_GROUPS; iGrp++ ) - { - pGroupPopupMenu->addAction ( tr ( "Assign to group" ) + ( QString ( " &%1" ).arg ( iGrp + 1 ) ), this, [=] { OnGroupMenuGrp ( iGrp ); } ); - } -#if ( MAX_NUM_FADER_GROUPS != 8 ) -# error "MAX_NUM_FADER_GROUPS must be set to 8, see implementation in CChannelFader()" -#endif - - // setup channel level - plbrChannelLevel->setContentsMargins ( 0, 3, 2, 3 ); - - // setup slider - pFader->setPageStep ( 1 ); - pFader->setRange ( 0, AUD_MIX_FADER_MAX ); - pFader->setTickInterval ( AUD_MIX_FADER_MAX / 9 ); - - // setup panning control and info label - pPan->setRange ( 0, AUD_MIX_PAN_MAX ); - pPan->setValue ( AUD_MIX_PAN_MAX / 2 ); - pPan->setNotchesVisible ( true ); - pInfoLabel->setMinimumHeight ( 14 ); // prevents jitter when muting/unmuting (#811) - pInfoLabel->setAlignment ( Qt::AlignTop ); - pPanInfoGrid->addWidget ( pPanLabel, 0, Qt::AlignLeft | Qt::AlignTop ); - pPanInfoGrid->addWidget ( pInfoLabel, 0, Qt::AlignHCenter | Qt::AlignTop ); - pPanGrid->addLayout ( pPanInfoGrid ); - pPanGrid->addWidget ( pPan, 0, Qt::AlignHCenter ); - - // setup fader tag label (black bold text which is centered) - plblLabel->setTextFormat ( Qt::PlainText ); - plblLabel->setAlignment ( Qt::AlignHCenter | Qt::AlignVCenter ); - - // set margins of the layouts to zero to get maximum space for the controls - pMainGrid->setContentsMargins ( 0, 0, 0, 0 ); - - pPanGrid->setContentsMargins ( 0, 0, 0, 0 ); - pPanGrid->setSpacing ( 0 ); // only minimal space - - pLevelsGrid->setContentsMargins ( 0, 0, 0, 0 ); - pLevelsGrid->setSpacing ( 0 ); // only minimal space - - pMuteSoloGrid->setContentsMargins ( 0, 0, 0, 0 ); - pMuteSoloGrid->setSpacing ( 0 ); // only minimal space - - pLabelGrid->setContentsMargins ( 0, 0, 0, 0 ); - pLabelGrid->setSpacing ( 2 ); // only minimal space between picture and text - - // add user controls to the grids - pLabelPictGrid->addWidget ( plblCountryFlag, 0, Qt::AlignHCenter ); - pLabelPictGrid->addWidget ( plblInstrument, 0, Qt::AlignHCenter ); - - pLabelGrid->addLayout ( pLabelPictGrid ); - pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // note: just initial add, may be changed later - - pLevelsGrid->addWidget ( plbrChannelLevel, 0, Qt::AlignRight ); - pLevelsGrid->addWidget ( pFader, 0, Qt::AlignLeft ); - - pMuteSoloGrid->addWidget ( pcbGroup, 0, Qt::AlignLeft ); - pMuteSoloGrid->addWidget ( pcbMute, 0, Qt::AlignLeft ); - pMuteSoloGrid->addWidget ( pcbSolo, 0, Qt::AlignLeft ); - - pMainGrid->addLayout ( pPanGrid ); - pMainGrid->addWidget ( pLevelsBox, 0, Qt::AlignHCenter ); - pMainGrid->addWidget ( pMuteSoloBox, 0, Qt::AlignHCenter ); - pMainGrid->addWidget ( pLabelInstBox ); - - // reset current fader - strGroupBaseText = "Grp"; // this will most probably overwritten by SetGUIDesign() - iInstrPicMaxWidth = INVALID_INDEX; // this will most probably overwritten by SetGUIDesign() - Reset(); + // create levelmeter for this channelstrip + m_channelMeter = new CLevelMeter ( this ); - // add help text to controls - plbrChannelLevel->setWhatsThis ( "" + tr ( "Channel Level" ) + ": " + - tr ( "Displays the pre-fader audio level of this channel. All clients connected to the " - "server will be assigned an audio level, the same value for every client." ) ); - plbrChannelLevel->setAccessibleName ( tr ( "Input level of the current audio " - "channel at the server" ) ); - - pFader->setWhatsThis ( "" + tr ( "Mixer Fader" ) + ": " + - tr ( "Adjusts the audio level of this channel. All clients connected to the server " - "will be assigned an audio fader, displayed at each client, to adjust the local mix." ) ); - pFader->setAccessibleName ( tr ( "Local mix level setting of the current audio " - "channel at the server" ) ); - - pInfoLabel->setWhatsThis ( "" + tr ( "Status Indicator" ) + ": " + - tr ( "Shows a status indication about the client which is assigned to this channel. " - "Supported indicators are:" ) + - "" ); - pInfoLabel->setAccessibleName ( tr ( "Status indicator label" ) ); - - pPan->setWhatsThis ( "" + tr ( "Panning" ) + ": " + - tr ( "Sets the pan from Left to Right of the channel. " - "Works only in stereo or preferably mono in/stereo out mode." ) ); - pPan->setAccessibleName ( tr ( "Local panning position of the current audio channel at the server" ) ); - - pcbMute->setWhatsThis ( "" + tr ( "Mute" ) + ": " + tr ( "With the Mute checkbox, the audio channel can be muted." ) ); - pcbMute->setAccessibleName ( tr ( "Mute button" ) ); - - pcbSolo->setWhatsThis ( "" + tr ( "Solo" ) + ": " + - tr ( "With the Solo checkbox, the " - "audio channel can be set to solo which means that all other channels " - "except the soloed channel are muted. It is possible to set more than " - "one channel to solo." ) ); - pcbSolo->setAccessibleName ( tr ( "Solo button" ) ); - - pcbGroup->setWhatsThis ( "" + tr ( "Group" ) + ": " + - tr ( "With the Grp checkbox, a " - "group of audio channels can be defined. All channel faders in a group are moved " - "in proportional synchronization if any one of the group faders are moved." ) ); - pcbGroup->setAccessibleName ( tr ( "Group button" ) ); - - QString strFaderText = "" + tr ( "Fader Tag" ) + ": " + - tr ( "The fader tag " - "identifies the connected client. The tag name, a picture of your " - "instrument and the flag of your location can be set in the main window." ); - - plblInstrument->setWhatsThis ( strFaderText ); - plblInstrument->setAccessibleName ( tr ( "Mixer channel instrument picture" ) ); - plblLabel->setWhatsThis ( strFaderText ); - plblLabel->setAccessibleName ( tr ( "Mixer channel label (fader tag)" ) ); - plblCountryFlag->setWhatsThis ( strFaderText ); - plblCountryFlag->setAccessibleName ( tr ( "Mixer channel country/region flag" ) ); - - // Connections ------------------------------------------------------------- - QObject::connect ( pFader, &QSlider::valueChanged, this, &CChannelFader::OnLevelValueChanged ); - - QObject::connect ( pPan, &QDial::valueChanged, this, &CChannelFader::OnPanValueChanged ); - - QObject::connect ( pcbMute, &QCheckBox::stateChanged, this, &CChannelFader::OnMuteStateChanged ); - - QObject::connect ( pcbSolo, &QCheckBox::stateChanged, this, &CChannelFader::soloStateChanged ); - - QObject::connect ( pcbGroup, &QCheckBox::stateChanged, this, &CChannelFader::OnGroupStateChanged ); + Reset(); } -void CChannelFader::SetGUIDesign ( const EGUIDesign eNewDesign ) +QString CChannelFader::channelUserName() const { - eDesign = eNewDesign; + return this->cReceivedChanInfo.strName; +} - switch ( eNewDesign ) +void CChannelFader::setchannelUserName(const QString& name) +{ + if (this->cReceivedChanInfo.strName != name) { - case GD_ORIGINAL: - pFader->setStyleSheet ( "QSlider { width: 45px;" - " border-image: url(:/png/fader/res/faderbackground.png) repeat;" - " border-top: 10px transparent;" - " border-bottom: 10px transparent;" - " border-left: 20px transparent;" - " border-right: -25px transparent; }" - "QSlider::groove { image: url(:/png/fader/res/transparent1x1.png);" - " padding-left: -34px;" - " padding-top: -10px;" - " padding-bottom: -15px; }" - "QSlider::handle { image: url(:/png/fader/res/faderhandle.png); }" ); - - pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons - pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures - pPan->setFixedSize ( 50, 50 ); - pPanLabel->setText ( tr ( "PAN" ) ); - pcbMute->setText ( tr ( "MUTE" ) ); - pcbSolo->setText ( tr ( "SOLO" ) ); - strGroupBaseText = tr ( "GRP" ); - iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling - break; - - case GD_SLIMFADER: - pLabelPictGrid->addWidget ( plblLabel, 0, Qt::AlignHCenter ); // label below icons - pLabelInstBox->setMinimumHeight ( 130 ); // maximum height of the instrument+flag+label - pPan->setFixedSize ( 28, 28 ); - pFader->setTickPosition ( QSlider::NoTicks ); - pFader->setStyleSheet ( "" ); - pPanLabel->setText ( tr ( "Pan" ) ); - pcbMute->setText ( tr ( "M" ) ); - pcbSolo->setText ( tr ( "S" ) ); - strGroupBaseText = tr ( "G" ); - iInstrPicMaxWidth = 18; // scale instrument picture to avoid enlarging the width by the picture - break; - - default: - // reset style sheet and set original parameters - pFader->setTickPosition ( QSlider::TicksBothSides ); - pFader->setStyleSheet ( "" ); - pLabelGrid->addWidget ( plblLabel, 0, Qt::AlignVCenter ); // label next to icons - pLabelInstBox->setMinimumHeight ( 52 ); // maximum height of the instrument+flag pictures - pPan->setFixedSize ( 50, 50 ); - pPanLabel->setText ( tr ( "Pan" ) ); - pcbMute->setText ( tr ( "Mute" ) ); - pcbSolo->setText ( tr ( "Solo" ) ); - strGroupBaseText = tr ( "Grp" ); - iInstrPicMaxWidth = INVALID_INDEX; // no instrument picture scaling - break; + this->cReceivedChanInfo.strName = name; + emit channelUserNameChanged(); } - - // we need to update since we changed the checkbox text - UpdateGroupIDDependencies(); - - // the instrument picture might need scaling after a style change - SetChannelInfos ( cReceivedChanInfo ); } -void CChannelFader::SetMeterStyle ( const EMeterStyle eNewMeterStyle ) +double CChannelFader::faderLevel() const { - eMeterStyle = eNewMeterStyle; + return m_faderLevel; +} - switch ( eNewMeterStyle ) +void CChannelFader::setFaderLevel( const double dLevel ) +{ + // user has moved fader slider + if (!qFuzzyCompare(m_faderLevel, dLevel)) { - case MT_BAR_NARROW: - plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_BAR_NARROW ); - // Fader height controls the distribution of the LEDs, if the value is too small the fader might not be movable - pFader->setMinimumHeight ( 85 ); - break; - - case MT_BAR_WIDE: - plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_BAR_WIDE ); - // Fader height controls the distribution of the LEDs, if the value is too small the fader might not be movable - pFader->setMinimumHeight ( 120 ); - break; - - case MT_LED_ROUND_SMALL: - plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_SMALL ); - // Fader height controls the distribution of the LEDs, if the value is too small the fader might not be movable - pFader->setMinimumHeight ( 85 ); - break; - - case MT_LED_ROUND_BIG: - plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_BIG ); - // Fader height controls the distribution of the LEDs, if the value is too small the fader might not be movable - pFader->setMinimumHeight ( 162 ); - break; - - default: - // reset style sheet and set original parameters - plbrChannelLevel->SetLevelMeterType ( CLevelMeter::MT_LED_STRIPE ); - // Fader height controls the distribution of the LEDs, if the value is too small the fader might not be movable - pFader->setMinimumHeight ( 120 ); - break; + m_faderLevel = std::min ( AUD_MIX_FADER_MAX, MathUtils::round ( dLevel ) ); + emit faderLevelChanged(); + qDebug() << this->cReceivedChanInfo.strName << ": setFaderLevel: " << m_faderLevel; } + + SendFaderLevelToServer ( dLevel, + QGuiApplication::keyboardModifiers() == + Qt::ShiftModifier ); /* isolate a channel from the group temporarily with shift-click-drag (#695) */ + } -void CChannelFader::SetDisplayChannelLevel ( const bool eNDCL ) { plbrChannelLevel->setHidden ( !eNDCL ); } +void CChannelFader::setFaderLevelNoSend( const double dLevel ) +{ + // slider is being auto-moved + if (!qFuzzyCompare(m_faderLevel, dLevel)) + { + m_faderLevel = std::min ( AUD_MIX_FADER_MAX, MathUtils::round ( dLevel ) ); + emit faderLevelChanged(); + qDebug() << this->cReceivedChanInfo.strName << ": setFaderLevelNoSend: " << m_faderLevel ; + } -bool CChannelFader::GetDisplayChannelLevel() { return !plbrChannelLevel->isHidden(); } +} -void CChannelFader::SetDisplayPans ( const bool eNDP ) +double CChannelFader::panLevel() const { - pPanLabel->setHidden ( !eNDP ); - pPan->setHidden ( !eNDP ); + return m_panLevel; } -void CChannelFader::SetupFaderTag ( const ESkillLevel eSkillLevel ) +void CChannelFader::setPanLevel(double level) { - // Should never happen here - if ( iGroupID >= MAX_NUM_FADER_GROUPS ) + if (!qFuzzyCompare(m_panLevel, level)) { - SetGroupID ( INVALID_INDEX ); + m_panLevel = level; + emit panLevelChanged(); + qDebug() << this->cReceivedChanInfo.strName << ": setPanLevel: changing level to : " << level; + + SendPanValueToServer ( level ); } +} - // the group ID defines the border color and style - QString strBorderColor = "black"; - QString strBorderStyle = "solid"; +bool CChannelFader::isMuted() const +{ + return m_isMuted; +} - if ( iGroupID != INVALID_INDEX ) +void CChannelFader::setIsMuted(bool muted) +{ + qDebug() << "setIsMuted for channel: " << this->cReceivedChanInfo.strName << ": " << muted; + if (m_isMuted != muted) { - switch ( iGroupID % 4 ) - { - case 0: - strBorderColor = "#C43AC5"; - break; - - case 1: - strBorderColor = "#2B93D4"; - break; + m_isMuted = muted; + SetMute(muted); + emit isMutedChanged(); + } +} - case 2: - strBorderColor = "#3BC53A"; - break; +bool CChannelFader::isSolo() const +{ + return m_isSolo; +} - case 3: - strBorderColor = "#D46C2B"; - break; +void CChannelFader::setIsSolo(bool solo) +{ + qDebug() << "setIsSolo for channel: " << this->cReceivedChanInfo.strName << ": " << solo; + if (m_isSolo != solo) + { + m_isSolo = solo; + emit isSoloChanged(); - default: - break; - } + emit soloStateChanged(); + } +} - switch ( iGroupID / 4 ) - { - case 0: - strBorderStyle = "solid"; - break; +int CChannelFader::groupID() const +{ + return this->iGroupID; +} - case 1: - strBorderStyle = "dashed"; - break; +void CChannelFader::setGroupID ( const int iNGroupID ) +{ + iGroupID = iNGroupID; - case 2: - strBorderStyle = "dotted"; - break; + UpdateGroupIDDependencies(); - case 3: - strBorderStyle = "double"; - break; + emit groupIDChanged(); +} - default: - break; - } - } +bool CChannelFader::isRemoteMuted() const +{ + return m_isRemoteMuted; +} - // setup group box for label/instrument picture: set a thick black border - // with nice round edges - QString strStile = "QGroupBox { border: 2px " + strBorderStyle + " " + strBorderColor + - ";" - " border-radius: 4px;" - " padding: 3px;"; +void CChannelFader::setIsRemoteMuted( const bool remoteMuted ) +{ + if (isRemoteMuted() == remoteMuted) + return; + m_isRemoteMuted = remoteMuted; + emit isRemoteMutedChanged(); +} - // the background color depends on the skill level - switch ( eSkillLevel ) - { - case SL_BEGINNER: - strStile += - QString ( "background-color: rgb(%1, %2, %3); }" ).arg ( RGBCOL_R_SL_BEGINNER ).arg ( RGBCOL_G_SL_BEGINNER ).arg ( RGBCOL_B_SL_BEGINNER ); - break; - - case SL_INTERMEDIATE: - strStile += QString ( "background-color: rgb(%1, %2, %3); }" ) - .arg ( RGBCOL_R_SL_INTERMEDIATE ) - .arg ( RGBCOL_G_SL_INTERMEDIATE ) - .arg ( RGBCOL_B_SL_INTERMEDIATE ); - break; - - case SL_PROFESSIONAL: - strStile += QString ( "background-color: rgb(%1, %2, %3); }" ) - .arg ( RGBCOL_R_SL_SL_PROFESSIONAL ) - .arg ( RGBCOL_G_SL_SL_PROFESSIONAL ) - .arg ( RGBCOL_B_SL_SL_PROFESSIONAL ); - break; - - default: - strStile += - QString ( "background-color: rgb(%1, %2, %3); }" ).arg ( RGBCOL_R_SL_NOT_SET ).arg ( RGBCOL_G_SL_NOT_SET ).arg ( RGBCOL_B_SL_NOT_SET ); - break; - } +int CChannelFader::channelID() const +{ + return this->cReceivedChanInfo.iChanID; +} - pLabelInstBox->setStyleSheet ( strStile ); +void CChannelFader::setChannelID(int id) +{ + if ( channelID() == id ) + return; + this->cReceivedChanInfo.iChanID = id; } void CChannelFader::Reset() @@ -418,31 +186,12 @@ void CChannelFader::Reset() SetRemoteFaderIsMute ( false ); // init gain and pan value -> maximum value as definition according to server - pFader->setValue ( AUD_MIX_FADER_MAX ); dPreviousFaderLevel = AUD_MIX_FADER_MAX; - pPan->setValue ( AUD_MIX_PAN_MAX / 2 ); // reset mute/solo/group check boxes and level meter - pcbMute->setChecked ( false ); - pcbSolo->setChecked ( false ); - plbrChannelLevel->SetValue ( 0 ); - plbrChannelLevel->ClipReset(); - - // clear instrument picture, country flag, tool tips and label text - plblLabel->setText ( "" ); - plblLabel->setToolTip ( "" ); - plblInstrument->setVisible ( false ); - plblInstrument->setToolTip ( "" ); - plblCountryFlag->setVisible ( false ); - plblCountryFlag->setToolTip ( "" ); - cReceivedChanInfo = CChannelInfo(); - SetupFaderTag ( SL_NOT_SET ); + m_channelMeter->ClipReset(); - // set a defined tool tip time out - const int iToolTipDurMs = 30000; - plblLabel->setToolTipDuration ( iToolTipDurMs ); - plblInstrument->setToolTipDuration ( iToolTipDurMs ); - plblCountryFlag->setToolTipDuration ( iToolTipDurMs ); + cReceivedChanInfo = CChannelInfo(); bOtherChannelIsSolo = false; bIsMyOwnFader = false; @@ -452,17 +201,24 @@ void CChannelFader::Reset() UpdateGroupIDDependencies(); } +void CChannelFader::SetPanValue ( const int iPan ) +{ + // first make a range check + if ( ( iPan >= 0 ) && ( iPan <= AUD_MIX_PAN_MAX ) ) + { + // set new pan level in GUI + setPanLevel( iPan ); + } +} + void CChannelFader::SetFaderLevel ( const double dLevel, const bool bIsGroupUpdate ) { // first make a range check if ( dLevel >= 0 ) { // we set the new fader level in the GUI (slider control) and also tell the - // server about the change (block the signal of the fader since we want to - // call SendFaderLevelToServer with a special additional parameter) - pFader->blockSignals ( true ); - pFader->setValue ( std::min ( AUD_MIX_FADER_MAX, MathUtils::round ( dLevel ) ) ); - pFader->blockSignals ( false ); + // server about the change + setFaderLevelNoSend( dLevel ); SendFaderLevelToServer ( std::min ( static_cast ( AUD_MIX_FADER_MAX ), dLevel ), bIsGroupUpdate ); @@ -477,41 +233,19 @@ void CChannelFader::SetFaderLevel ( const double dLevel, const bool bIsGroupUpda } } -void CChannelFader::SetPanValue ( const int iPan ) -{ - // first make a range check - if ( ( iPan >= 0 ) && ( iPan <= AUD_MIX_PAN_MAX ) ) - { - // we set the new fader level in the GUI (slider control) which then - // emits to signal to tell the server about the change (implicitly) - pPan->setValue ( iPan ); - pPan->setAccessibleName ( QString::number ( iPan ) ); - } -} - void CChannelFader::SetFaderIsSolo ( const bool bIsSolo ) { - // changing the state automatically emits the signal, too - pcbSolo->setChecked ( bIsSolo ); + setIsSolo( bIsSolo ); } void CChannelFader::SetFaderIsMute ( const bool bIsMute ) { - // changing the state automatically emits the signal, too - pcbMute->setChecked ( bIsMute ); + setIsMuted( bIsMute ); } void CChannelFader::SetRemoteFaderIsMute ( const bool bIsMute ) { - if ( bIsMute ) - { - // show muted icon orange - pInfoLabel->setPixmap ( BitmapMutedIcon ); - } - else - { - pInfoLabel->setPixmap ( QPixmap() ); - } + setIsRemoteMuted( bIsMute ); } void CChannelFader::SendFaderLevelToServer ( const double dLevel, const bool bIsGroupUpdate ) @@ -519,7 +253,7 @@ void CChannelFader::SendFaderLevelToServer ( const double dLevel, const bool bIs // if mute flag is set or other channel is on solo, do not apply the new // fader value to the server (exception: we are on solo, in that case we // ignore the "other channel is on solo" flag) - const bool bSuppressServerUpdate = !( ( pcbMute->checkState() == Qt::Unchecked ) && ( !bOtherChannelIsSolo || IsSolo() ) ); + const bool bSuppressServerUpdate = !( ( m_isMuted == false ) && ( !bOtherChannelIsSolo || isSolo() ) ); // emit signal for new fader gain value emit gainValueChanged ( MathUtils::CalcFaderGain ( static_cast ( dLevel ) ), @@ -539,93 +273,27 @@ void CChannelFader::SendFaderLevelToServer ( const double dLevel, const bool bIs void CChannelFader::SendPanValueToServer ( const int iPan ) { emit panValueChanged ( static_cast ( iPan ) / AUD_MIX_PAN_MAX ); } -void CChannelFader::OnPanValueChanged ( int value ) -{ - // on shift-click the pan shall reset to 0 L/R (#707) - if ( QGuiApplication::keyboardModifiers() == Qt::ShiftModifier ) - { - // correct the value to the center position - value = AUD_MIX_PAN_MAX / 2; - - // set the GUI control in the center position while deactivating - // the signals to avoid an infinite loop - pPan->blockSignals ( true ); - pPan->setValue ( value ); - pPan->blockSignals ( false ); - } - - pPan->setAccessibleName ( QString::number ( value ) ); - SendPanValueToServer ( value ); -} - -void CChannelFader::OnMuteStateChanged ( int value ) -{ - // call muting function - SetMute ( static_cast ( value ) == Qt::Checked ); -} - -void CChannelFader::SetGroupID ( const int iNGroupID ) -{ - iGroupID = iNGroupID; - UpdateGroupIDDependencies(); -} - void CChannelFader::UpdateGroupIDDependencies() { - // update the group checkbox according the current group ID setting - pcbGroup->blockSignals ( true ); // make sure no signals are fired - if ( iGroupID == INVALID_INDEX ) - { - pcbGroup->setCheckState ( Qt::Unchecked ); - } - else - { - pcbGroup->setCheckState ( Qt::Checked ); - } - pcbGroup->blockSignals ( false ); - - // update group checkbox text - if ( iGroupID != INVALID_INDEX ) - { - pcbGroup->setText ( strGroupBaseText + QString::number ( iGroupID + 1 ) ); - } - else - { - pcbGroup->setText ( strGroupBaseText ); - } - // if the group is disable for this fader, reset the previous fader level if ( iGroupID == INVALID_INDEX ) { // for the special case that the fader is all the way down, use a small // value instead - if ( GetFaderLevel() > 0 ) + if ( faderLevel() > 0 ) { - dPreviousFaderLevel = GetFaderLevel(); + dPreviousFaderLevel = faderLevel(); } else { dPreviousFaderLevel = 1; // small value } } - - // the fader tag border color is set according to the selected group - SetupFaderTag ( cReceivedChanInfo.eSkillLevel ); -} - -void CChannelFader::OnGroupStateChanged ( int ) -{ - // we want a popup menu shown if the user presses the group checkbox but - // we want to make sure that the checkbox state represents the current group - // setting and not the current click state since the user might not click - // on the menu but at one other place and then the popup menu disappears but - // the checkobx state would be on an invalid state - UpdateGroupIDDependencies(); - pGroupPopupMenu->popup ( QCursor::pos() ); } void CChannelFader::SetMute ( const bool bState ) { + qDebug() << "SetMute for channel: " << this->cReceivedChanInfo.strName << ": " << bState; if ( bState ) { if ( !bIsMutedAtServer ) @@ -637,11 +305,11 @@ void CChannelFader::SetMute ( const bool bState ) } else { - // only unmute if we are not solot but an other channel is on solo - if ( ( !bOtherChannelIsSolo || IsSolo() ) && bIsMutedAtServer ) + // only unmute if we are solo OR another channel is NOT solo .... right ? // not solot but an other channel is on solo + if ( ( !bOtherChannelIsSolo || isSolo() ) && bIsMutedAtServer ) { // mute was unchecked, get current fader value and apply - emit gainValueChanged ( MathUtils::CalcFaderGain ( GetFaderLevel() ), + emit gainValueChanged ( MathUtils::CalcFaderGain ( faderLevel() ), bIsMyOwnFader, false, false, @@ -657,26 +325,24 @@ void CChannelFader::UpdateSoloState ( const bool bNewOtherSoloState ) bOtherChannelIsSolo = bNewOtherSoloState; // mute overwrites solo -> if mute is active, do not change anything - if ( !pcbMute->isChecked() ) + if ( !m_isMuted ) { // mute channel if we are not solo but another channel is solo - SetMute ( bOtherChannelIsSolo && !IsSolo() ); + SetMute ( bOtherChannelIsSolo && !isSolo() ); } } -void CChannelFader::SetChannelLevel ( const uint16_t iLevel ) { plbrChannelLevel->SetValue ( iLevel ); } +void CChannelFader::SetChannelLevel ( const uint16_t iLevel ) +{ + m_channelMeter->setDoubleVal ( iLevel ); +} void CChannelFader::SetChannelInfos ( const CChannelInfo& cChanInfo ) { // store received channel info cReceivedChanInfo = cChanInfo; - // init properties for the tool tip - int iTTInstrument = CInstPictures::GetNotUsedInstrument(); - QLocale::Country eTTCountry = QLocale::AnyCountry; - // Label text -------------------------------------------------------------- - QString strModText = cChanInfo.strName; // show channel numbers if --ctrlmidich is used (#241, #95) @@ -684,382 +350,143 @@ void CChannelFader::SetChannelInfos ( const CChannelInfo& cChanInfo ) { strModText.prepend ( QString().setNum ( cChanInfo.iChanID ) + ":" ); } - - QTextBoundaryFinder tbfName ( QTextBoundaryFinder::Grapheme, cChanInfo.strName ); - int iBreakPos; - - // apply break position and font size depending on the selected design - if ( eDesign == GD_SLIMFADER ) - { - // in slim mode use a non-bold font (smaller width font) - plblLabel->setStyleSheet ( "QLabel { color: black; }" ); - - // break at every 4th character - iBreakPos = 4; - } - else - { - // in normal mode use bold font - plblLabel->setStyleSheet ( "QLabel { color: black; font: bold; }" ); - - // break text at predefined position - iBreakPos = MAX_LEN_FADER_TAG / 2; - } - - int iInsPos = iBreakPos; - int iCount = 0; - int iLineNumber = 0; - while ( tbfName.toNextBoundary() != -1 ) - { - ++iCount; - if ( iCount == iInsPos && tbfName.position() + iLineNumber < strModText.length() ) - { - strModText.insert ( tbfName.position() + iLineNumber, QString ( "\n" ) ); - iLineNumber++; - iInsPos += iBreakPos; - } - } - - plblLabel->setText ( strModText ); - - // Instrument picture ------------------------------------------------------ - // get the resource reference string for this instrument - const QString strCurResourceRef = CInstPictures::GetResourceReference ( cChanInfo.iInstrument ); - - // first check if instrument picture is used or not and if it is valid - if ( CInstPictures::IsNotUsedInstrument ( cChanInfo.iInstrument ) || strCurResourceRef.isEmpty() ) - { - // disable instrument picture - plblInstrument->setVisible ( false ); - } - else - { - // set correct picture - QPixmap pixInstr ( strCurResourceRef ); - - if ( ( iInstrPicMaxWidth != INVALID_INDEX ) && ( pixInstr.width() > iInstrPicMaxWidth ) ) - { - // scale instrument picture on request (scale to the width with correct aspect ratio) - plblInstrument->setPixmap ( pixInstr.scaledToWidth ( iInstrPicMaxWidth, Qt::SmoothTransformation ) ); - } - else - { - plblInstrument->setPixmap ( pixInstr ); - } - iTTInstrument = cChanInfo.iInstrument; - - // enable instrument picture - plblInstrument->setVisible ( true ); - } - - // Country flag icon ------------------------------------------------------- - if ( cChanInfo.eCountry != QLocale::AnyCountry ) - { - // try to load the country flag icon - QPixmap CountryFlagPixmap ( CLocale::GetCountryFlagIconsResourceReference ( cChanInfo.eCountry ) ); - - // first check if resource reference was valid - if ( CountryFlagPixmap.isNull() ) - { - // disable country flag - plblCountryFlag->setVisible ( false ); - } - else - { - // set correct picture - plblCountryFlag->setPixmap ( CountryFlagPixmap ); - eTTCountry = cChanInfo.eCountry; - - // enable country flag - plblCountryFlag->setVisible ( true ); - } - } - else - { - // disable country flag - plblCountryFlag->setVisible ( false ); - } - - // Skill level background color -------------------------------------------- - SetupFaderTag ( cChanInfo.eSkillLevel ); - - // Tool tip ---------------------------------------------------------------- - // complete musician profile in the tool tip - QString strToolTip = ""; - QString strAliasAccessible = ""; - QString strInstrumentAccessible = ""; - QString strLocationAccessible = ""; - - // alias/name - if ( !cChanInfo.strName.isEmpty() ) - { - strToolTip += "

" + tr ( "Alias/Name" ) + "

" + cChanInfo.strName; - strAliasAccessible += cChanInfo.strName; - } - - // instrument - if ( !CInstPictures::IsNotUsedInstrument ( iTTInstrument ) ) - { - strToolTip += "

" + tr ( "Instrument" ) + "

" + CInstPictures::GetName ( iTTInstrument ); - - strInstrumentAccessible += CInstPictures::GetName ( iTTInstrument ); - } - - // location - if ( ( eTTCountry != QLocale::AnyCountry ) || ( !cChanInfo.strCity.isEmpty() ) ) - { - strToolTip += "

" + tr ( "Location" ) + "

"; - - if ( !cChanInfo.strCity.isEmpty() ) - { - strToolTip += cChanInfo.strCity; - strLocationAccessible += cChanInfo.strCity; - - if ( eTTCountry != QLocale::AnyCountry ) - { - strToolTip += ", "; - strLocationAccessible += ", "; - } - } - - if ( eTTCountry != QLocale::AnyCountry ) - { - strToolTip += QLocale::countryToString ( eTTCountry ); - strLocationAccessible += QLocale::countryToString ( eTTCountry ); - } - } - - // skill level - QString strSkillLevel; - - switch ( cChanInfo.eSkillLevel ) - { - case SL_BEGINNER: - strSkillLevel = tr ( "Beginner" ); - strToolTip += "

" + tr ( "Skill Level" ) + "

" + strSkillLevel; - strInstrumentAccessible += ", " + strSkillLevel; - break; - - case SL_INTERMEDIATE: - strSkillLevel = tr ( "Intermediate" ); - strToolTip += "

" + tr ( "Skill Level" ) + "

" + strSkillLevel; - strInstrumentAccessible += ", " + strSkillLevel; - break; - - case SL_PROFESSIONAL: - strSkillLevel = tr ( "Expert" ); - strToolTip += "

" + tr ( "Skill Level" ) + "

" + strSkillLevel; - strInstrumentAccessible += ", " + strSkillLevel; - break; - - case SL_NOT_SET: - // skill level not set, do not add this entry - break; - } - - // if no information is given, leave the tool tip empty, otherwise add header - if ( !strToolTip.isEmpty() ) - { - strToolTip.prepend ( "

" + tr ( "Musician Profile" ) + "

" ); - } - - plblCountryFlag->setToolTip ( strToolTip ); - plblCountryFlag->setAccessibleDescription ( strLocationAccessible ); - plblInstrument->setToolTip ( strToolTip ); - plblInstrument->setAccessibleDescription ( strInstrumentAccessible ); - plblLabel->setToolTip ( strToolTip ); - plblLabel->setAccessibleName ( strAliasAccessible ); - plblLabel->setAccessibleDescription ( tr ( "Alias" ) ); - pcbMute->setAccessibleName ( "Mute " + strAliasAccessible + ", " + strInstrumentAccessible ); - pcbSolo->setAccessibleName ( "Solo " + strAliasAccessible + ", " + strInstrumentAccessible ); - pcbGroup->setAccessibleName ( "Group " + strAliasAccessible + ", " + strInstrumentAccessible ); - dynamic_cast ( plblLabel->parent() ) - ->setAccessibleName ( strAliasAccessible + ", " + strInstrumentAccessible + ", " + strLocationAccessible ); } + /******************************************************************************\ * CAudioMixerBoard * \******************************************************************************/ -CAudioMixerBoard::CAudioMixerBoard ( QWidget* parent ) : - QGroupBox ( parent ), - pSettings ( nullptr ), +CAudioMixerBoard::CAudioMixerBoard ( CClientSettings* pSet, QObject* parent ) : + pSettings ( pSet ), bDisplayPans ( false ), - bIsPanSupported ( false ), - bNoFaderVisible ( true ), + bIsPanSupported ( true ), iMyChannelID ( INVALID_INDEX ), iRunningNewClientCnt ( 0 ), iNumMixerPanelRows ( 1 ), // pSettings->iNumMixerPanelRows is not yet available - strServerName ( "" ), eRecorderState ( RS_UNDEFINED ), eChSortType ( ST_NO_SORT ) { - // add group box and hboxlayout - QHBoxLayout* pGroupBoxLayout = new QHBoxLayout ( this ); - QWidget* pMixerWidget = new QWidget(); // will be added to the scroll area which is then the parent - pScrollArea = new CMixerBoardScrollArea ( this ); - pMainLayout = new QGridLayout ( pMixerWidget ); - - setAccessibleName ( "Personal Mix at the Server groupbox" ); - setWhatsThis ( "" + tr ( "Personal Mix at the Server" ) + ": " + - tr ( "When connected to a server, the controls here allow you to set your " - "local mix without affecting what others hear from you. The title shows " - "the server name and, when known, whether it is actively recording." ) ); - - // set title text (default: no server given) - SetServerName ( "" ); - - // create all mixer controls and make them invisible - vecpChanFader.Init ( MAX_NUM_CHANNELS ); - + // we need to init this vector vecAvgLevels.Init ( MAX_NUM_CHANNELS, 0.0f ); +} - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) +CAudioMixerBoard::~CAudioMixerBoard() +{ + for (CChannelFader* channelFader : vecpChanFader) { - vecpChanFader[i] = new CChannelFader ( this ); - vecpChanFader[i]->Hide(); + delete channelFader; } +} - // insert horizontal spacer (at position MAX_NUM_CHANNELS+1 which is index MAX_NUM_CHANNELS) - pMainLayout->addItem ( new QSpacerItem ( 0, 0, QSizePolicy::Expanding ), 0, MAX_NUM_CHANNELS ); - - // set margins of the layout to zero to get maximum space for the controls - pGroupBoxLayout->setContentsMargins ( 0, 0, 0, 1 ); // note: to avoid problems at the bottom, use a small margin for that +// Since we don't need append and clear functions, they can be set to nullptr +QQmlListProperty CAudioMixerBoard::channels() +{ + return QQmlListProperty( + this, + static_cast(this), + &CAudioMixerBoard::channelCount, + &CAudioMixerBoard::channelAt + ); +} - // add the group box to the scroll area - pScrollArea->setMinimumWidth ( 200 ); // at least two faders shall be visible - pScrollArea->setWidget ( pMixerWidget ); - pScrollArea->setWidgetResizable ( true ); // make sure it fills the entire scroll area - pScrollArea->setFrameShape ( QFrame::NoFrame ); - pGroupBoxLayout->addWidget ( pScrollArea ); +void CAudioMixerBoard::appendChannel(QQmlListProperty* list, CChannelFader* channel) +{ + CAudioMixerBoard* mixerBoard = static_cast(list->data); + mixerBoard->vecpChanFader.pushback(channel); + emit mixerBoard->channelsChanged(); +} - // Connections ------------------------------------------------------------- - connectFaderSignalsToMixerBoardSlots(); +qsizetype CAudioMixerBoard::channelCount(QQmlListProperty* list) +{ + CAudioMixerBoard* mixerBoard = static_cast(list->data); + return static_cast(mixerBoard->vecpChanFader.size()); } -CAudioMixerBoard::~CAudioMixerBoard() +CChannelFader* CAudioMixerBoard::channelAt(QQmlListProperty* list, qsizetype index) { - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + CAudioMixerBoard* mixerBoard = static_cast(list->data); + if (index >= 0 && index < static_cast(mixerBoard->vecpChanFader.size())) { - delete vecpChanFader[i]; + return mixerBoard->vecpChanFader.at(static_cast(index)); } + return nullptr; } -template -inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots() +void CAudioMixerBoard::clearChannels(QQmlListProperty* list) { - size_t iCurChanID = slotId - 1; - - void ( CAudioMixerBoard::*pGainValueChanged ) ( float, bool, bool, bool, double ) = &CAudioMixerBoardSlots::OnChGainValueChanged; - - void ( CAudioMixerBoard::*pPanValueChanged ) ( float ) = &CAudioMixerBoardSlots::OnChPanValueChanged; - - QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::soloStateChanged, this, &CAudioMixerBoard::UpdateSoloStates ); - - QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::gainValueChanged, this, pGainValueChanged ); - - QObject::connect ( vecpChanFader[iCurChanID], &CChannelFader::panValueChanged, this, pPanValueChanged ); - - connectFaderSignalsToMixerBoardSlots(); + CAudioMixerBoard* mixerBoard = static_cast(list->data); + mixerBoard->vecpChanFader.clear(); + emit mixerBoard->channelsChanged(); } -template<> -inline void CAudioMixerBoard::connectFaderSignalsToMixerBoardSlots<0>() -{} -void CAudioMixerBoard::SetServerName ( const QString& strNewServerName ) +void CAudioMixerBoard::addChannel(const CChannelInfo& chanInfo) { - // store the current server name - strServerName = strNewServerName; + // Check for maximum channel limit + if (vecpChanFader.size() >= MAX_NUM_CHANNELS) + return; - if ( strServerName.isEmpty() ) - { - // no connection or connection was reset: show default title - setTitle ( tr ( "Server" ) ); - } - else - { - // Do not set the server name directly but first show a label which indicates - // that we are trying to connect the server. First if a connected client - // list was received, the connection was successful and the title is updated - // with the correct server name. Make sure to choose a "try to connect" title - // which is most striking (we use filled blocks and upper case letters). - setTitle ( u8"\u2588\u2588\u2588\u2588\u2588 " + tr ( "T R Y I N G T O C O N N E C T" ) + u8" \u2588\u2588\u2588\u2588\u2588" ); - } -} + CChannelFader* channelFader = new CChannelFader(this); -void CAudioMixerBoard::SetGUIDesign ( const EGUIDesign eNewDesign ) -{ - // move the channels tighter together in slim fader mode - if ( eNewDesign == GD_SLIMFADER ) + // Set channel information + channelFader->SetChannelInfos(chanInfo); + + // Apply stored settings if any + QString sname = channelFader->channelUserName(); + if (m_faderSettingsMap.contains(sname)) { - pMainLayout->setSpacing ( 2 ); + FaderSettings settings = m_faderSettingsMap.value(sname); + channelFader->SetFaderLevel(settings.faderLevel); // FIXME - group update ???? + channelFader->setIsMuted(settings.isMuted); + // Apply other settings if needed } else { - pMainLayout->setSpacing ( 6 ); // Qt default spacing value + // Set default fader level if needed + channelFader->SetFaderLevel(pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX ); } - // apply GUI design to child GUI controls - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - vecpChanFader[i]->SetGUIDesign ( eNewDesign ); - } -} + // Connect ChannelFader signals to slots + // Solo requires no extra info + connect ( channelFader, &CChannelFader::soloStateChanged, this, &CAudioMixerBoard::UpdateSoloStates ); -void CAudioMixerBoard::SetMeterStyle ( const EMeterStyle eNewMeterStyle ) -{ - // apply GUI design to child GUI controls - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - vecpChanFader[i]->SetMeterStyle ( eNewMeterStyle ); - } -} - -void CAudioMixerBoard::SetDisplayPans ( const bool eNDP ) -{ - bDisplayPans = eNDP; + // For Gain and Pan, first make OnChGainValueChanged and OnChPanValueChanged in CChannelFader, to pick up the channel idx before calling the slots in CAudioMixerBoard + connect ( channelFader, &CChannelFader::gainValueChanged, channelFader, &CChannelFader::OnChGainValueChanged ); + connect ( channelFader, &CChannelFader::panValueChanged, channelFader, &CChannelFader::OnChPanValueChanged ); + // now connect related "secondary" signals to slots in audiomixerboard + connect ( channelFader, &CChannelFader::withIdxOnChGainValueChanged, this, &CAudioMixerBoard::OnChGainValueChanged ); + connect ( channelFader, &CChannelFader::withIdxOnChPanValueChanged, this, &CAudioMixerBoard::OnChPanValueChanged ); - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - vecpChanFader[i]->SetDisplayPans ( eNDP && bIsPanSupported ); - } -} + // Add to the vector + vecpChanFader.pushback(channelFader); -void CAudioMixerBoard::SetPanIsSupported() -{ - bIsPanSupported = true; - SetDisplayPans ( bDisplayPans ); + // Notify QML - or don't do here, do after batch update + // emit channelsChanged(); } -void CAudioMixerBoard::HideAll() +void CAudioMixerBoard::removeChannel(const QString& name) { - // before hiding the faders, store their settings - StoreAllFaderSettings(); - - // make all controls invisible - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (size_t i = 0; i < vecpChanFader.size(); ++i) { - vecpChanFader[i]->SetChannelLevel ( 0 ); - vecpChanFader[i]->SetDisplayChannelLevel ( false ); - vecpChanFader[i]->SetDisplayPans ( false ); - vecpChanFader[i]->Hide(); - } - - // initialize flags and other parameters - bIsPanSupported = false; - bNoFaderVisible = true; - eRecorderState = RS_UNDEFINED; - iMyChannelID = INVALID_INDEX; - iRunningNewClientCnt = 0; // reset running counter on new server connection - - // use original order of channel (by server ID) - ChangeFaderOrder ( ST_NO_SORT ); + if (vecpChanFader.at(i)->channelUserName() == name) + { + // Remove from vector + vecpChanFader.erase(vecpChanFader.begin() + i); + // Optionally delete the object if not managed elsewhere + // delete vecpChanFader.at(i); - // Reset recording indication styleSheet - setStyleSheet ( "" ); + // Notify QML - or don't do here, do after batch update ??? + emit channelsChanged(); + break; + } + } +} - // emit status of connected clients - emit NumClientsChanged ( 0 ); // -> no clients connected +void CAudioMixerBoard::clear() +{ + // remove all elements in vector and update GUI + this->vecpChanFader.clear(); + emit channelsChanged(); } void CAudioMixerBoard::SetNumMixerPanelRows ( const int iNNumMixerPanelRows ) @@ -1080,135 +507,9 @@ void CAudioMixerBoard::ChangeFaderOrder ( const EChSortType eChSortType ) { QMutexLocker locker ( &Mutex ); - // create a pair list of lower strings and fader ID for each channel - QList> PairList; - int iNumVisibleFaders = 0; - int iMyFader = -1; - - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - if ( vecpChanFader[i]->GetIsMyOwnFader() ) - { - iMyFader = static_cast ( i ); - } - - switch ( eChSortType ) - { - case ST_BY_NAME: - PairList << QPair ( vecpChanFader[i]->GetReceivedName().toLower(), i ); - break; - case ST_BY_CITY: - // sort first "by city" and second "by name" by adding the name after the city - PairList << QPair ( vecpChanFader[i]->GetReceivedCity().toLower() + vecpChanFader[i]->GetReceivedName().toLower(), i ); - break; - case ST_BY_INSTRUMENT: - // sort first "by instrument" and second "by name" by adding the name after the instrument - PairList << QPair ( CInstPictures::GetName ( vecpChanFader[i]->GetReceivedInstrument() ) + - vecpChanFader[i]->GetReceivedName().toLower(), - i ); - break; - case ST_BY_GROUPID: - // sort first "by group" and second "by name" by adding the name after the group - if ( vecpChanFader[i]->GetGroupID() == INVALID_INDEX ) - { - // put channels without a group at the end - PairList << QPair ( "999" + vecpChanFader[i]->GetReceivedName().toLower(), - i ); // worst case is one group per channel (current max is 8) - } - else - { - PairList << QPair ( QString ( "%1" ).arg ( vecpChanFader[i]->GetGroupID(), 3, 10, QLatin1Char ( '0' ) ) + - vecpChanFader[i]->GetReceivedName().toLower(), - i ); - } - break; - case ST_BY_SERVER_CHANNEL: - PairList << QPair ( QString ( "%1" ).arg ( vecpChanFader[i]->GetReceivedChID(), 3, 10, QLatin1Char ( '0' ) ) + - vecpChanFader[i]->GetReceivedName().toLower(), - i ); - break; - default: // ST_NO_SORT - // per definition for no sort: faders are sorted in the order they appeared (note that we - // pad to a total of 11 characters with zeros to make sure the sorting is done correctly) - PairList << QPair ( QString ( "%1" ).arg ( vecpChanFader[i]->GetRunningNewClientCnt(), 11, 10, QLatin1Char ( '0' ) ), - i ); - break; - } - - // count the number of visible faders - if ( vecpChanFader[i]->IsVisible() ) - { - iNumVisibleFaders++; - } - } - - // sort the channels according to the first of the pair - std::stable_sort ( PairList.begin(), PairList.end() ); - - // move my fader to first position - if ( pSettings->bOwnFaderFirst ) - { - for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - if ( iMyFader == static_cast ( PairList[i].second ) ) - { - PairList.move ( i, 0 ); - break; - } - } - } - - // we want to distribute iNumVisibleFaders across the first row, then the next, etc - // up to iNumMixerPanelRows. So row wants to start at 0 until we get to some number, - // then increase, where "some number" means we get no more than iNumMixerPanelRows. - const int iNumFadersFirstRow = ( iNumVisibleFaders + iNumMixerPanelRows - 1 ) / iNumMixerPanelRows; - - // add channels to the layout in the new order, note that it is not required to remove - // the widget from the layout first but it is moved to the new position automatically - int iVisibleFaderCnt = 0; - - for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) - { - const size_t iCurFaderID = PairList[i].second; - - if ( vecpChanFader[iCurFaderID]->IsVisible() ) - { - // channels are added row-first, up to iNumFadersFirstRow, then onto - // the next row. - pMainLayout->addWidget ( vecpChanFader[iCurFaderID]->GetMainWidget(), - iVisibleFaderCnt / iNumFadersFirstRow, - iVisibleFaderCnt % iNumFadersFirstRow ); - - iVisibleFaderCnt++; - } - } -} - -void CAudioMixerBoard::UpdateTitle() -{ - QString strTitlePrefix = ""; - - if ( eRecorderState == RS_RECORDING ) - { - strTitlePrefix = QString ( "[%1] " ).arg ( tr ( "RECORDING ACTIVE" ) ); - } - - // replace & signs with && (See Qt documentation for QLabel) - // if strServerName includes an "&" sign, this is interpreted as keyboard shortcut (#1886) - // it might be possible to find a more elegant solution here? - - QString strEscServerName = strServerName; - strEscServerName.replace ( "&", "&&" ); - - setTitle ( strTitlePrefix + tr ( "Personal Mix at: %1" ).arg ( strEscServerName ) ); - setAccessibleName ( title() ); -} - -void CAudioMixerBoard::SetRecorderState ( const ERecorderState newRecorderState ) -{ - // store the new recorder state and update the title - eRecorderState = newRecorderState; - UpdateTitle(); + // do Nothing for now + if (eChSortType == 3) + return; } void CAudioMixerBoard::ApplyNewConClientList ( CVector& vecChanInfo ) @@ -1218,119 +519,84 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector& vecChanInf Mutex.lock(); { - // we want to set the server name only if the very first faders appear - // in the audio mixer board to show a "try to connect" before - if ( bNoFaderVisible ) + // Step 1: Create a mapping of current channel IDs to their indices in vecpChanFader + std::unordered_map currentChannelsMap; + for (CChannelFader* channelFader : vecpChanFader) { - UpdateTitle(); + int chanID = channelFader->channelID(); + currentChannelsMap[chanID] = channelFader; + if ( static_cast ( chanID ) == iMyChannelID ) + { + // this is my own fader --> set fader property + channelFader->SetIsMyOwnFader(); + } } - // search for channels which are already present and preserve their gain - // setting, for all other channels reset gain + // Step 2: Prepare a new vector for updated channels + CVector newVecpChanFader; - // get all channels which are in use/not in use. - // We use the array index of vecChanInfo if the fader is in use, - // else INVALID_INDEX to specify it is not in use - // so must use "int" for the array type. - int iFaderNumber[MAX_NUM_CHANNELS]; - - for ( size_t iChanID = 0; iChanID < MAX_NUM_CHANNELS; iChanID++ ) + // Step 3: Process connected clients + for (const auto& chanInfo : vecChanInfo) { - iFaderNumber[iChanID] = INVALID_INDEX; - } + int chanID = chanInfo.iChanID; - for ( size_t iFader = 0; iFader < iNumConnectedClients; iFader++ ) - { - // ideally "iChanID" in CChannelInfo would be size_t if it can never be INVALID_INDEX - // as assumed here - iFaderNumber[vecChanInfo[iFader].iChanID] = static_cast ( iFader ); - } - - // Hide all unused faders and initialize used ones - for ( size_t iChanID = 0; iChanID < MAX_NUM_CHANNELS; iChanID++ ) - { - if ( iFaderNumber[iChanID] == INVALID_INDEX ) + if (currentChannelsMap.count(chanID)) { - // current fader is not used - StoreFaderSettings ( vecpChanFader[iChanID] ); + // Existing channel + CChannelFader* channelFader = currentChannelsMap[chanID]; + currentChannelsMap.erase(chanID); - vecpChanFader[iChanID]->Hide(); - continue; - } - size_t idxVecpChan = static_cast ( iFaderNumber[iChanID] ); + // Update channel info + channelFader->SetChannelInfos(chanInfo); - // current fader is used - if ( !vecpChanFader[iChanID]->IsVisible() ) + newVecpChanFader.pushback(channelFader); + } + else { - // the fader was not in use, - // reset everything for new client - vecpChanFader[iChanID]->Reset(); - vecAvgLevels[iChanID] = 0.0f; - - if ( static_cast ( iChanID ) == iMyChannelID ) - { - // this is my own fader --> set fader property - vecpChanFader[iChanID]->SetIsMyOwnFader(); - } + // New channel - use addChannel method + addChannel(chanInfo); - // keep track of each new client - // for "no sorting" channel sort order new clients are added - // to the right-hand side of the mixer (#673) - vecpChanFader[iChanID]->SetRunningNewClientCnt ( iRunningNewClientCnt++ ); - - // show fader - vecpChanFader[iChanID]->Show(); - - // Set the default initial fader level. Check first that - // this is not the initialization (i.e. previously there - // were no faders visible) to avoid that our own level is - // adjusted. If we have received our own channel ID, then - // we can adjust the level even if no fader was visible. - // The fader level of 100 % is the default in the - // server, in that case we do not have to do anything here. - if ( ( !bNoFaderVisible || ( ( iMyChannelID != INVALID_INDEX ) && ( iMyChannelID != static_cast ( iChanID ) ) ) ) && - ( pSettings->iNewClientFaderLevel != 100 ) ) - { - // the value is in percent -> convert range - vecpChanFader[iChanID]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX ); - } + // Retrieve the recently added channel and add to new vector + CChannelFader* channelFader = vecpChanFader.back(); // The new channel is at the end + newVecpChanFader.pushback(channelFader); } + } - if ( vecpChanFader[iChanID]->GetReceivedName().compare ( vecChanInfo[idxVecpChan].strName ) ) - { - // the text has actually changed, search in the list of - // stored settings if we have a matching entry - int iStoredFaderLevel; - int iStoredPanValue; - bool bStoredFaderIsSolo; - bool bStoredFaderIsMute; - int iGroupID; - - if ( GetStoredFaderSettings ( vecChanInfo[idxVecpChan].strName, - iStoredFaderLevel, - iStoredPanValue, - bStoredFaderIsSolo, - bStoredFaderIsMute, - iGroupID ) ) - { - vecpChanFader[iChanID]->SetFaderLevel ( iStoredFaderLevel, true ); // suppress group update - vecpChanFader[iChanID]->SetPanValue ( iStoredPanValue ); - vecpChanFader[iChanID]->SetFaderIsSolo ( bStoredFaderIsSolo ); - vecpChanFader[iChanID]->SetFaderIsMute ( bStoredFaderIsMute ); - vecpChanFader[iChanID]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader! - } - } + // Step 4: Remove disconnected channels + for (const auto& pair : currentChannelsMap) + { + CChannelFader* channelFader = pair.second; + + // Store settings + StoreFaderSettings(channelFader); - // set the channel infos - vecpChanFader[iChanID]->SetChannelInfos ( vecChanInfo[idxVecpChan] ); + // Delete channel fader + delete channelFader; } + // Step 5: Swap the old vector with the new one + vecpChanFader = std::move(newVecpChanFader); + + // Step 6: Notify QML that channels have changed + emit channelsChanged(); + + // Update any other UI elements or internal state as needed + + // updateStatusBar(); + + // Check for stored settings + // QString name = channelFader->channelUserName(); + // if (m_faderSettingsMap.contains(name)) + // { + // FaderSettings settings = m_faderSettingsMap.value(name); + // channelFader->SetFaderLevel(settings.faderLevel); + // channelFader->setIsMuted(settings.isMuted); + // // Apply other settings + // } + // update the solo states since if any channel was on solo and a new client // has just connected, the new channel must be muted UpdateSoloStates(); - - // update flag for "all faders are invisible" - bNoFaderVisible = ( iNumConnectedClients == 0 ); } Mutex.unlock(); // release mutex @@ -1343,50 +609,37 @@ void CAudioMixerBoard::ApplyNewConClientList ( CVector& vecChanInf void CAudioMixerBoard::SetFaderLevel ( const int iChannelIdx, const int iValue ) { - // only apply new fader level if channel index is valid and the fader is visible - if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) + // only apply new fader level if channel index is valid + if ( ( iChannelIdx >= 0 ) ) { - if ( vecpChanFader[static_cast ( iChannelIdx )]->IsVisible() ) - { - vecpChanFader[static_cast ( iChannelIdx )]->SetFaderLevel ( iValue ); - } + vecpChanFader[static_cast ( iChannelIdx )]->SetFaderLevel ( iValue ); } } void CAudioMixerBoard::SetPanValue ( const int iChannelIdx, const int iValue ) { - // only apply new pan value if channel index is valid and the panner is visible - if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) && bDisplayPans ) + // only apply new pan value if channel index is valid + if ( ( iChannelIdx >= 0 ) && bDisplayPans ) { - if ( vecpChanFader[static_cast ( iChannelIdx )]->IsVisible() ) - { - vecpChanFader[static_cast ( iChannelIdx )]->SetPanValue ( iValue ); - } + vecpChanFader[static_cast ( iChannelIdx )]->SetPanValue ( iValue ); } } void CAudioMixerBoard::SetFaderIsSolo ( const int iChannelIdx, const bool bIsSolo ) { - // only apply solo if channel index is valid and the fader is visible - if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) - + // only apply solo if channel index is valid + if ( ( iChannelIdx >= 0 ) ) { - if ( vecpChanFader[static_cast ( iChannelIdx )]->IsVisible() ) - { - vecpChanFader[static_cast ( iChannelIdx )]->SetFaderIsSolo ( bIsSolo ); - } + vecpChanFader[static_cast ( iChannelIdx )]->SetFaderIsSolo ( bIsSolo ); } } void CAudioMixerBoard::SetFaderIsMute ( const int iChannelIdx, const bool bIsMute ) { - // only apply mute if channel index is valid and the fader is visible - if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) + // only apply mute if channel index is valid + if ( ( iChannelIdx >= 0 ) ) { - if ( vecpChanFader[static_cast ( iChannelIdx )]->IsVisible() ) - { - vecpChanFader[static_cast ( iChannelIdx )]->SetFaderIsMute ( bIsMute ); - } + vecpChanFader[static_cast ( iChannelIdx )]->SetFaderIsMute ( bIsMute ); } } @@ -1394,15 +647,14 @@ void CAudioMixerBoard::SetAllFaderLevelsToNewClientLevel() { QMutexLocker locker ( &Mutex ); - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { - // only apply to visible faders and not to my own channel fader - if ( vecpChanFader[i]->IsVisible() && ( static_cast ( i ) != iMyChannelID ) ) + if ( ( static_cast ( channelFader->channelID() ) != iMyChannelID ) ) { // the value is in percent -> convert range, also use the group // update flag to make sure the group values are all set to the // same fader level now - vecpChanFader[i]->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX, true ); + channelFader->SetFaderLevel ( pSettings->iNewClientFaderLevel / 100.0 * AUD_MIX_FADER_MAX, true ); } } } @@ -1423,19 +675,20 @@ void CAudioMixerBoard::AutoAdjustAllFaderLevels() levels.resize ( MAX_NUM_FADER_GROUPS + 1 ); // compute min/max level per group and number of channels per group - for ( size_t i = 0; i < MAX_NUM_CHANNELS; ++i ) + for (CChannelFader* channelFader : vecpChanFader) { - // only apply to visible faders (and not to my own channel fader) - if ( vecpChanFader[i]->IsVisible() && ( static_cast ( i ) != iMyChannelID ) ) + // don't apply to my own channel fader) + if ( channelFader->channelID() != iMyChannelID ) { + int i = channelFader->channelID(); // map averaged meter output level to decibels // (invert CStereoSignalLevelMeter::CalcLogResultForMeter) float leveldB = vecAvgLevels[i] * ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ) / NUM_STEPS_LED_BAR + LOW_BOUND_SIG_METER; size_t group = MAX_NUM_FADER_GROUPS; - if ( vecpChanFader[i]->GetGroupID() != INVALID_INDEX ) + if ( channelFader->groupID() != INVALID_INDEX ) { - group = static_cast ( vecpChanFader[i]->GetGroupID() ); + group = static_cast ( channelFader->groupID() ); } if ( leveldB >= AUTO_FADER_NOISE_THRESHOLD_DB ) @@ -1513,16 +766,17 @@ void CAudioMixerBoard::AutoAdjustAllFaderLevels() } // adjust all levels - for ( size_t i = 0; i < MAX_NUM_CHANNELS; ++i ) + for (CChannelFader* channelFader : vecpChanFader) { - // only apply to visible faders (and not to my own channel fader) - if ( vecpChanFader[i]->IsVisible() && ( static_cast ( i ) != iMyChannelID ) ) + // don't apply to my own channel fader) + if ( channelFader->channelID() != iMyChannelID ) { + int i = channelFader->channelID(); // map averaged meter output level to decibels // (invert CStereoSignalLevelMeter::CalcLogResultForMeter) float leveldB = vecAvgLevels[i] * ( UPPER_BOUND_SIG_METER - LOW_BOUND_SIG_METER ) / NUM_STEPS_LED_BAR + LOW_BOUND_SIG_METER; - int group = vecpChanFader[i]->GetGroupID(); + int group = channelFader->groupID(); if ( group == INVALID_INDEX ) { if ( cntActiveGroups > 0 ) @@ -1551,7 +805,7 @@ void CAudioMixerBoard::AutoAdjustAllFaderLevels() newFaderLevel = fmin ( fmax ( newFaderLevel, 0.0f ), float ( AUD_MIX_FADER_MAX ) ); // set fader level - vecpChanFader[i]->SetFaderLevel ( newFaderLevel, true ); + channelFader->SetFaderLevel ( newFaderLevel, true ); } } } @@ -1561,9 +815,9 @@ void CAudioMixerBoard::SetMIDICtrlUsed ( const bool bMIDICtrlUsed ) { QMutexLocker locker ( &Mutex ); - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { - vecpChanFader[i]->SetMIDICtrlUsed ( bMIDICtrlUsed ); + channelFader->SetMIDICtrlUsed ( bMIDICtrlUsed ); } } @@ -1571,9 +825,9 @@ void CAudioMixerBoard::StoreAllFaderSettings() { QMutexLocker locker ( &Mutex ); - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { - StoreFaderSettings ( vecpChanFader[i] ); + StoreFaderSettings ( channelFader ); } } @@ -1587,33 +841,30 @@ void CAudioMixerBoard::LoadAllFaderSettings() bool bStoredFaderIsMute; int iGroupID; - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { - if ( GetStoredFaderSettings ( vecpChanFader[i]->GetReceivedName(), + if ( GetStoredFaderSettings ( channelFader->GetReceivedName(), iStoredFaderLevel, iStoredPanValue, bStoredFaderIsSolo, bStoredFaderIsMute, iGroupID ) ) { - vecpChanFader[i]->SetFaderLevel ( iStoredFaderLevel, true ); // suppress group update - vecpChanFader[i]->SetPanValue ( iStoredPanValue ); - vecpChanFader[i]->SetFaderIsSolo ( bStoredFaderIsSolo ); - vecpChanFader[i]->SetFaderIsMute ( bStoredFaderIsMute ); - vecpChanFader[i]->SetGroupID ( iGroupID ); // Must be the last to be set in the fader! + channelFader->SetFaderLevel ( iStoredFaderLevel, true ); // suppress group update + channelFader->SetPanValue ( iStoredPanValue ); + channelFader->SetFaderIsSolo ( bStoredFaderIsSolo ); + channelFader->SetFaderIsMute ( bStoredFaderIsMute ); + channelFader->setGroupID ( iGroupID ); // Must be the last to be set in the fader! } } } void CAudioMixerBoard::SetRemoteFaderIsMute ( const int iChannelIdx, const bool bIsMute ) { - // only apply remote mute state if channel index is valid and the fader is visible - if ( ( iChannelIdx >= 0 ) && ( iChannelIdx < MAX_NUM_CHANNELS ) ) + // only apply remote mute state if channel index is valid + if ( iChannelIdx >= 0 ) { - if ( vecpChanFader[static_cast ( iChannelIdx )]->IsVisible() ) - { - vecpChanFader[static_cast ( iChannelIdx )]->SetRemoteFaderIsMute ( bIsMute ); - } + vecpChanFader[static_cast ( iChannelIdx )]->SetRemoteFaderIsMute ( bIsMute ); } } @@ -1622,23 +873,20 @@ void CAudioMixerBoard::UpdateSoloStates() // first check if any channel has a solo state active bool bAnyChannelIsSolo = false; - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { - // check if fader is in use and has solo state active - if ( vecpChanFader[i]->IsVisible() && vecpChanFader[i]->IsSolo() ) + // check if fader has solo state active + if ( channelFader->isSolo() ) { bAnyChannelIsSolo = true; continue; } } - // now update the solo state of all active faders - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + // // now update the solo state of all active faders + for (CChannelFader* channelFader : vecpChanFader) { - if ( vecpChanFader[i]->IsVisible() ) - { - vecpChanFader[i]->UpdateSoloState ( bAnyChannelIsSolo ); - } + channelFader->UpdateSoloState ( bAnyChannelIsSolo ); } } @@ -1659,28 +907,31 @@ void CAudioMixerBoard::UpdateGainValue ( const int iChannelIdx, // if this fader is selected, all other in the group must be updated as // well (note that we do not have to update if this is already a group update // to avoid an infinite loop) - if ( ( vecpChanFader[stChannelIdx]->GetGroupID() != INVALID_INDEX ) && !bIsGroupUpdate ) + if ( ( vecpChanFader[stChannelIdx]->groupID() != INVALID_INDEX ) && !bIsGroupUpdate ) { - for ( size_t i = 0; i < MAX_NUM_CHANNELS; i++ ) + for (CChannelFader* channelFader : vecpChanFader) { // update rest of faders selected - if ( vecpChanFader[i]->IsVisible() && ( vecpChanFader[i]->GetGroupID() == vecpChanFader[stChannelIdx]->GetGroupID() ) && - ( i != stChannelIdx ) && ( dLevelRatio >= 0 ) ) + if (( channelFader->groupID() == vecpChanFader[stChannelIdx]->groupID() ) && ( channelFader->channelID() != stChannelIdx ) && ( dLevelRatio >= 0 )) { // synchronize faders with moving fader level (it is important // to set the group flag to avoid infinite looping) - vecpChanFader[i]->SetFaderLevel ( vecpChanFader[i]->GetPreviousFaderLevel() * dLevelRatio, true ); + channelFader->SetFaderLevel ( channelFader->GetPreviousFaderLevel() * dLevelRatio, true ); } } + } } -void CAudioMixerBoard::UpdatePanValue ( const int iChannelIdx, const float fValue ) { emit ChangeChanPan ( iChannelIdx, fValue ); } +void CAudioMixerBoard::UpdatePanValue ( const int iChannelIdx, const float fValue ) +{ + emit ChangeChanPan ( iChannelIdx, fValue ); +} void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader ) { - // if the fader was visible and the name is not empty, we store the old gain - if ( pChanFader->IsVisible() && !pChanFader->GetReceivedName().isEmpty() ) + // if the name is not empty, we store the old gain + if ( !pChanFader->GetReceivedName().isEmpty() ) { CVector viOldStoredFaderLevels ( pSettings->vecStoredFaderLevels ); CVector viOldStoredPanValues ( pSettings->vecStoredPanValues ); @@ -1692,11 +943,11 @@ void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader ) const int iOldIdx = pSettings->vecStoredFaderTags.StringFiFoWithCompare ( pChanFader->GetReceivedName() ); // current fader level and solo state is at the top of the list - pSettings->vecStoredFaderLevels[0] = pChanFader->GetFaderLevel(); + pSettings->vecStoredFaderLevels[0] = pChanFader->faderLevel(); pSettings->vecStoredPanValues[0] = pChanFader->GetPanValue(); - pSettings->vecStoredFaderIsSolo[0] = pChanFader->IsSolo(); - pSettings->vecStoredFaderIsMute[0] = pChanFader->IsMute(); - pSettings->vecStoredFaderGroupID[0] = pChanFader->GetGroupID(); + pSettings->vecStoredFaderIsSolo[0] = pChanFader->isSolo(); + pSettings->vecStoredFaderIsMute[0] = pChanFader->isMuted(); + pSettings->vecStoredFaderGroupID[0] = pChanFader->groupID(); size_t iTempListCnt = 1; // current fader is on top, other faders index start at 1 for ( size_t iIdx = 0; iIdx < MAX_NUM_STORED_FADER_SETTINGS; iIdx++ ) @@ -1722,6 +973,22 @@ void CAudioMixerBoard::StoreFaderSettings ( CChannelFader* pChanFader ) } } +// void CAudioMixerBoard::StoreFaderSettings(CChannelFader* pChanFader) +// { +// QString name = pChanFader->channelUserName(); +// if (name.isEmpty()) +// return; + +// // need to define FaderSettings as a struct and m_faderSettingsMap as a QMap. + +// FaderSettings fSettings; +// fSettings.faderLevel = pChanFader->faderLevel(); +// fSettings.isMuted = pChanFader->isMuted(); +// // Store other settings as needed + +// m_faderSettingsMap[name] = fSettings; +// } + bool CAudioMixerBoard::GetStoredFaderSettings ( const QString& strName, int& iStoredFaderLevel, int& iStoredPanValue, @@ -1759,23 +1026,19 @@ void CAudioMixerBoard::SetChannelLevels ( const CVector& vecChannelLev const size_t iNumChannelLevels = vecChannelLevel.size(); size_t i = 0; - for ( size_t iChId = 0; iChId < MAX_NUM_CHANNELS; iChId++ ) + for ( size_t iChId = 0; iChId < vecpChanFader.Size(); iChId++ ) { - if ( vecpChanFader[iChId]->IsVisible() && ( i < iNumChannelLevels ) ) + if ( i < iNumChannelLevels ) //FIXME - shouldn't need this { // compute exponential moving average vecAvgLevels[iChId] = ( 1.0f - AUTO_FADER_ADJUST_ALPHA ) * vecAvgLevels[iChId] + AUTO_FADER_ADJUST_ALPHA * vecChannelLevel[i]; vecpChanFader[iChId]->SetChannelLevel ( vecChannelLevel[i++] ); - - // show level only if we successfully received levels from the - // server (if server does not support levels, do not show levels) - if ( !vecpChanFader[iChId]->GetDisplayChannelLevel() ) - { - vecpChanFader[iChId]->SetDisplayChannelLevel ( true ); - } } } } -void CAudioMixerBoard::MuteMyChannel() { SetFaderIsMute ( iMyChannelID, true ); } +void CAudioMixerBoard::MuteMyChannel() +{ + SetFaderIsMute ( iMyChannelID, true ); +} diff --git a/src/audiomixerboard.h b/src/audiomixerboard.h index 6b42d35085..03680ebf34 100644 --- a/src/audiomixerboard.h +++ b/src/audiomixerboard.h @@ -24,21 +24,10 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include #include #include +#include #include "global.h" #include "util.h" #include "levelmeter.h" @@ -49,37 +38,57 @@ class CChannelFader : public QObject { Q_OBJECT + Q_PROPERTY(CLevelMeter* channelMeter READ channelMeter CONSTANT) + Q_PROPERTY(QString channelUserName READ channelUserName WRITE setchannelUserName NOTIFY channelUserNameChanged) + Q_PROPERTY(double faderLevel READ faderLevel WRITE setFaderLevel NOTIFY faderLevelChanged) + Q_PROPERTY(double panLevel READ panLevel WRITE setPanLevel NOTIFY panLevelChanged) + Q_PROPERTY(bool isMuted READ isMuted WRITE setIsMuted NOTIFY isMutedChanged) + Q_PROPERTY(bool isSolo READ isSolo WRITE setIsSolo NOTIFY isSoloChanged) + Q_PROPERTY(int groupID READ groupID WRITE setGroupID NOTIFY groupIDChanged FINAL) + Q_PROPERTY(bool isRemoteMuted READ isRemoteMuted WRITE setIsRemoteMuted NOTIFY isRemoteMutedChanged) + public: - CChannelFader ( QWidget* pNW ); + explicit CChannelFader(QObject* parent = nullptr); + + // QML accessors + QString channelUserName() const; + void setchannelUserName(const QString& name); + + double faderLevel() const; + Q_INVOKABLE void setFaderLevel(double dLevel); + // related helper function + void setFaderLevelNoSend(double dLevel); + + double panLevel() const; + Q_INVOKABLE void setPanLevel(double level); + + bool isMuted() const; + Q_INVOKABLE void setIsMuted(bool muted); + + bool isSolo() const; + Q_INVOKABLE void setIsSolo(bool solo); + + int groupID() const; + Q_INVOKABLE void setGroupID(int grpID); + bool isRemoteMuted() const; + void setIsRemoteMuted(bool remoteMuted); + + CLevelMeter* channelMeter() const { return m_channelMeter; } + // -------- + + int channelID() const; + void setChannelID(int id); QString GetReceivedName() { return cReceivedChanInfo.strName; } - int GetReceivedInstrument() { return cReceivedChanInfo.iInstrument; } - QString GetReceivedCity() { return cReceivedChanInfo.strCity; } - int GetReceivedChID() { return cReceivedChanInfo.iChanID; } void SetChannelInfos ( const CChannelInfo& cChanInfo ); - void Show() { pFrame->show(); } - void Hide() { pFrame->hide(); } - bool IsVisible() { return !pFrame->isHidden(); } - bool IsSolo() { return pcbSolo->isChecked(); } - bool IsMute() { return pcbMute->isChecked(); } - int GetGroupID() { return iGroupID; } - void SetGUIDesign ( const EGUIDesign eNewDesign ); - void SetMeterStyle ( const EMeterStyle eNewMeterStyle ); - void SetDisplayChannelLevel ( const bool eNDCL ); - bool GetDisplayChannelLevel(); - void SetDisplayPans ( const bool eNDP ); - QFrame* GetMainWidget() { return pFrame; } - void SetPanValue ( const int iPan ); void SetFaderIsSolo ( const bool bIsSolo ); void SetFaderIsMute ( const bool bIsMute ); - void SetGroupID ( const int iNGroupID ); void SetRemoteFaderIsMute ( const bool bIsMute ); void SetFaderLevel ( const double dLevel, const bool bIsGroupUpdate = false ); - int GetFaderLevel() { return pFader->value(); } double GetPreviousFaderLevel() { return dPreviousFaderLevel; } - int GetPanValue() { return pPan->value(); } + int GetPanValue() { return m_panLevel; } void Reset(); void SetRunningNewClientCnt ( const int iNRunningNewClientCnt ) { iRunningNewClientCnt = iNRunningNewClientCnt; } int GetRunningNewClientCnt() { return iRunningNewClientCnt; } @@ -91,34 +100,20 @@ class CChannelFader : public QObject void SetMIDICtrlUsed ( const bool isMIDICtrlUsed ) { bMIDICtrlUsed = isMIDICtrlUsed; } protected: + // for QML + double m_faderLevel; + double m_panLevel; + bool m_isMuted; + bool m_isSolo; + bool m_isRemoteMuted; + CLevelMeter* m_channelMeter; + // -------- + void UpdateGroupIDDependencies(); void SetMute ( const bool bState ); - void SetupFaderTag ( const ESkillLevel eSkillLevel ); void SendPanValueToServer ( const int iPan ); void SendFaderLevelToServer ( const double dLevel, const bool bIsGroupUpdate ); - QFrame* pFrame; - - QWidget* pLevelsBox; - QWidget* pMuteSoloBox; - CLevelMeter* plbrChannelLevel; - QSlider* pFader; - QDial* pPan; - QLabel* pPanLabel; - QLabel* pInfoLabel; - QHBoxLayout* pLabelGrid; - QVBoxLayout* pLabelPictGrid; - - QCheckBox* pcbMute; - QCheckBox* pcbSolo; - QCheckBox* pcbGroup; - QMenu* pGroupPopupMenu; - - QGroupBox* pLabelInstBox; - QLabel* plblLabel; - QLabel* plblInstrument; - QLabel* plblCountryFlag; - CChannelInfo cReceivedChanInfo; bool bOtherChannelIsSolo; @@ -128,87 +123,69 @@ class CChannelFader : public QObject int iGroupID; QString strGroupBaseText; int iRunningNewClientCnt; - int iInstrPicMaxWidth; - EGUIDesign eDesign; - EMeterStyle eMeterStyle; - QPixmap BitmapMutedIcon; bool bMIDICtrlUsed; public slots: - void OnLevelValueChanged ( int value ) + void OnChGainValueChanged ( float fValue, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ) { - SendFaderLevelToServer ( value, - QGuiApplication::keyboardModifiers() == - Qt::ShiftModifier ); /* isolate a channel from the group temporarily with shift-click-drag (#695) */ + // add in the channel index/id for audiomixerboard to process + emit withIdxOnChGainValueChanged( cReceivedChanInfo.iChanID, fValue, bIsMyOwnFader, bIsGroupUpdate, bSuppressServerUpdate, dLevelRatio ); } - void OnPanValueChanged ( int value ); - void OnMuteStateChanged ( int value ); - void OnGroupStateChanged ( int ); - - void OnGroupMenuGrp ( int iGrp ) { SetGroupID ( iGrp ); } - -signals: - void gainValueChanged ( float value, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ); - - void panValueChanged ( float value ); - void soloStateChanged ( int value ); -}; - -template -class CAudioMixerBoardSlots : public CAudioMixerBoardSlots -{ -public: - void OnChGainValueChanged ( float fValue, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ) + void OnChPanValueChanged ( float fValue ) { - UpdateGainValue ( slotId - 1, fValue, bIsMyOwnFader, bIsGroupUpdate, bSuppressServerUpdate, dLevelRatio ); + // add in the channel index/id for audiomixerboard to process + emit withIdxOnChPanValueChanged( cReceivedChanInfo.iChanID, fValue ); } - void OnChPanValueChanged ( float fValue ) { UpdatePanValue ( slotId - 1, fValue ); } - -protected: - virtual void UpdateGainValue ( const int iChannelIdx, - const float fValue, - const bool bIsMyOwnFader, - const bool bIsGroupUpdate, - const bool bSuppressServerUpdate, - const double dLevelRatio ) = 0; - - virtual void UpdatePanValue ( const int iChannelIdx, const float fValue ) = 0; +signals: + // update qml + void channelUserNameChanged(); + void faderLevelChanged(); + void panLevelChanged(); + void isMutedChanged(); + void isSoloChanged(); + void groupIDChanged(); + void isRemoteMutedChanged(); + + void gainValueChanged ( float value, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ); // sends to server + void panValueChanged ( float value ); // sends to server + void soloStateChanged (); // the int didn't do anything ?? int value ); + + // for audiomixerboard slots + void withIdxOnChGainValueChanged ( int chanIdx, float value, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ); + void withIdxOnChPanValueChanged ( int chanIdx, float value ); }; -template<> -class CAudioMixerBoardSlots<0> -{}; -class CAudioMixerBoard : public QGroupBox, public CAudioMixerBoardSlots +// -------------------------------------------------------------------------------------------------- + +class CAudioMixerBoard : public QObject { Q_OBJECT + Q_PROPERTY(QQmlListProperty channels READ channels NOTIFY channelsChanged) + public: - CAudioMixerBoard ( QWidget* parent = nullptr ); + CAudioMixerBoard ( CClientSettings* pSet, QObject* parent = nullptr ); virtual ~CAudioMixerBoard(); - void SetSettingsPointer ( CClientSettings* pNSet ) { pSettings = pNSet; } - void HideAll(); + QQmlListProperty channels(); + + Q_INVOKABLE void addChannel(const CChannelInfo& chanInfo); + Q_INVOKABLE void removeChannel(const QString& name); + + void clear(); + void ApplyNewConClientList ( CVector& vecChanInfo ); - void SetServerName ( const QString& strNewServerName ); - QString GetServerName() { return strServerName; } - void SetGUIDesign ( const EGUIDesign eNewDesign ); - void SetMeterStyle ( const EMeterStyle eNewMeterStyle ); - void SetDisplayPans ( const bool eNDP ); - void SetPanIsSupported(); void SetRemoteFaderIsMute ( const int iChannelIdx, const bool bIsMute ); void SetMyChannelID ( const int iChannelIdx ) { iMyChannelID = iChannelIdx; } int GetMyChannelID() const { return iMyChannelID; } void SetFaderLevel ( const int iChannelIdx, const int iValue ); - void SetPanValue ( const int iChannelIdx, const int iValue ); - void SetFaderIsSolo ( const int iChannelIdx, const bool bIsSolo ); - void SetFaderIsMute ( const int iChannelIdx, const bool bIsMute ); void SetNumMixerPanelRows ( const int iNNumMixerPanelRows ); @@ -219,9 +196,6 @@ class CAudioMixerBoard : public QGroupBox, public CAudioMixerBoardSlots& vecChannelLevel ); - void SetRecorderState ( const ERecorderState newRecorderState ); - ERecorderState GetRecorderState() { return eRecorderState; }; - void SetAllFaderLevelsToNewClientLevel(); void StoreAllFaderSettings(); void LoadAllFaderSettings(); @@ -232,64 +206,74 @@ class CAudioMixerBoard : public QGroupBox, public CAudioMixerBoardSlots* list, CChannelFader* channel); + static qsizetype channelCount(QQmlListProperty* list); + static CChannelFader* channelAt(QQmlListProperty* list, qsizetype index); + static void clearChannels(QQmlListProperty* list); + + // for fader val storage - FIXME maybe not using + struct FaderSettings { - public: - CMixerBoardScrollArea ( QWidget* parent = nullptr ) : QScrollArea ( parent ) {} - - protected: - virtual void resizeEvent ( QResizeEvent* event ) - { - // if after a resize of the main window a vertical scroll bar is required, make - // sure that the fader label is visible (scroll down completely) - ensureVisible ( 0, 2000 ); // use a large value here - QScrollArea::resizeEvent ( event ); - } + double faderLevel = 100.0; // Default fader level (e.g., max volume) + int panValue = 0; // Default pan value (center) + bool isMuted = false; + bool isSolo = false; + int groupID = -1; // Default group ID (or invalid) }; + QMap m_faderSettingsMap; + // ---- void ChangeFaderOrder ( const EChSortType eChSortType ); - bool GetStoredFaderSettings ( const QString& strName, int& iStoredFaderLevel, int& iStoredPanValue, bool& bStoredFaderIsSolo, bool& bStoredFaderIsMute, int& iGroupID ); - void StoreFaderSettings ( CChannelFader* pChanFader ); void UpdateSoloStates(); - void UpdateTitle(); + // void UpdateTitle(); CClientSettings* pSettings; CVector vecpChanFader; - CMixerBoardScrollArea* pScrollArea; - QGridLayout* pMainLayout; bool bDisplayPans; bool bIsPanSupported; - bool bNoFaderVisible; int iMyChannelID; // must use int (not size_t) so INVALID_INDEX can be stored int iRunningNewClientCnt; // integer type is sufficient, will never overrun for its purpose int iNumMixerPanelRows; - QString strServerName; + // QString strServerName; ERecorderState eRecorderState; QMutex Mutex; EChSortType eChSortType; CVector vecAvgLevels; - virtual void UpdateGainValue ( const int iChannelIdx, + void UpdateGainValue ( const int iChannelIdx, const float fValue, const bool bIsMyOwnFader, const bool bIsGroupUpdate, const bool bSuppressServerUpdate, const double dLevelRatio ); - virtual void UpdatePanValue ( const int iChannelIdx, const float fValue ); + void UpdatePanValue ( const int iChannelIdx, const float fValue ); - template - inline void connectFaderSignalsToMixerBoardSlots(); +public slots: + void OnChGainValueChanged ( int iChannelIdx, float fValue, bool bIsMyOwnFader, bool bIsGroupUpdate, bool bSuppressServerUpdate, double dLevelRatio ) + { + UpdateGainValue ( iChannelIdx, fValue, bIsMyOwnFader, bIsGroupUpdate, bSuppressServerUpdate, dLevelRatio ); + } + + void OnChPanValueChanged ( int iChannelIdx, float fValue ) + { + UpdatePanValue ( iChannelIdx, fValue ); + } signals: + // for QML + void channelsChanged(); + void ChangeChanGain ( int iId, float fGain, bool bIsMyOwnFader ); void ChangeChanPan ( int iId, float fPan ); void NumClientsChanged ( int iNewNumClients ); + }; diff --git a/src/chatbox.cpp b/src/chatbox.cpp new file mode 100644 index 0000000000..0324f963fd --- /dev/null +++ b/src/chatbox.cpp @@ -0,0 +1,111 @@ +/******************************************************************************\ + * Copyright (c) 2004-2024 + * + * Author(s): + * Volker Fischer + * + ****************************************************************************** + * + * This program 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 2 of the License, or (at your option) any later + * version. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#include "chatbox.h" + +/* Implementation *************************************************************/ +CChatBox::CChatBox(QObject* parent) + : QObject(parent) +{ +} + +QString CChatBox::chatHistory() const +{ + return m_chatHistory; +} + +void CChatBox::setChatHistory(const QString& newChat) +{ + if (m_chatHistory != newChat) + { + m_chatHistory = newChat; + emit chatHistoryChanged(); + } +} + +void CChatBox::sendMessage(const QString& msg) +{ + if (msg.isEmpty()) + return; + + emit NewLocalInputText ( msg ); + + qDebug() << "New message sent:" << msg; +} + + + +void CChatBox::clearChatHistory() +{ + // clear chat window + setChatHistory(""); +} + +void CChatBox::AddChatText ( QString strChatText ) +{ + // notify accessibility plugin that text has changed + // QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( txvChatWindow, strChatText ) ); + + // analyze strChatText to check if hyperlink (limit ourselves to http(s)://) but do not + // replace the hyperlinks if any HTML code for a hyperlink was found (the user has done the HTML + // coding hisself and we should not mess with that) + if ( !strChatText.contains ( QRegularExpression ( "href\\s*=|src\\s*=" ) ) ) + { + // searches for all occurrences of http(s) and cuts until a space (\S matches any non-white-space + // character and the + means that matches the previous element one or more times.) + // This regex now contains three parts: + // - https?://\\S+ matches as much non-whitespace as possible after the http:// or https://, + // subject to the next two parts, which exclude terminating punctuation + // - (??\\[\\]{}]) is a negative look-behind assertion that disallows the match + // from ending with one of the characters !"'()+,.:;<=>?[]{} + // - (??\\[\\]{}]) is a negative look-behind assertion that disallows the match + // from ending with a ? followed by one of the characters !"'()+,.:;<=>?[]{} + // These last two parts must be separate, as a look-behind assertion must be fixed length. +#define PUNCT_NOEND_URL "[!\"'()+,.:;<=>?\\[\\]{}]" + strChatText.replace ( QRegularExpression ( "(https?://\\S+(?\\1" ); + } + + // add new text in chat window + if (strChatText.isEmpty()) + return; + // Append new message to existing history + m_chatHistory.append(strChatText + "
"); + emit chatHistoryChanged(); +} + +// void CChatBox::OnAnchorClicked ( const QUrl& Url ) +// { +// // only allow http(s) URLs to be opened in an external browser +// if ( Url.scheme() == QLatin1String ( "https" ) || Url.scheme() == QLatin1String ( "http" ) ) +// { +// if ( QMessageBox::question ( this, +// APP_NAME, +// tr ( "Do you want to open the link '%1' in your browser?" ).arg ( "" + Url.toString() + "" ), +// QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes ) +// { +// QDesktopServices::openUrl ( Url ); +// } +// } +// } diff --git a/src/chatdlg.h b/src/chatbox.h similarity index 66% rename from src/chatdlg.h rename to src/chatbox.h index 39aa8c2a76..97d0f0396d 100644 --- a/src/chatdlg.h +++ b/src/chatbox.h @@ -24,40 +24,39 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// #include +// #include +// #include #include "global.h" #include "util.h" -#include "ui_chatdlgbase.h" /* Classes ********************************************************************/ -class CChatDlg : public CBaseDlg, private Ui_CChatDlgBase +class CChatBox: public QObject { Q_OBJECT + Q_PROPERTY(QString chatHistory READ chatHistory WRITE setChatHistory NOTIFY chatHistoryChanged) + public: - CChatDlg ( QWidget* parent = nullptr ); + explicit CChatBox(QObject* parent = nullptr); + + // Accessors for the Q_PROPERTY + QString chatHistory() const; + void setChatHistory(const QString& newChat); + + // QML calls this to send a message + Q_INVOKABLE void sendMessage(const QString& msg); + Q_INVOKABLE void clearChatHistory(); void AddChatText ( QString strChatText ); public slots: - void OnSendText(); - void OnLocalInputTextTextChanged ( const QString& strNewText ); - void OnClearChatHistory(); - void OnAnchorClicked ( const QUrl& Url ); -#if defined( Q_OS_IOS ) || defined( ANDROID ) || defined( Q_OS_ANDROID ) - void OnCloseClicked(); -#endif signals: void NewLocalInputText ( QString strNewText ); + void chatHistoryChanged(); + +private: + QString m_chatHistory; + }; diff --git a/src/chatdlg.cpp b/src/chatdlg.cpp deleted file mode 100644 index 4d7adcfd5c..0000000000 --- a/src/chatdlg.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#include "chatdlg.h" - -/* Implementation *************************************************************/ -CChatDlg::CChatDlg ( QWidget* parent ) : CBaseDlg ( parent, Qt::Window ) // use Qt::Window to get min/max window buttons -{ - setupUi ( this ); - - // Add help text to controls ----------------------------------------------- - // chat window - txvChatWindow->setWhatsThis ( "" + tr ( "Chat Window" ) + ": " + tr ( "The chat window shows a history of all chat messages." ) ); - - txvChatWindow->setAccessibleName ( tr ( "Chat history" ) ); - - // input message text - edtLocalInputText->setWhatsThis ( "" + tr ( "Input Message Text" ) + ": " + - tr ( "Enter the chat message text in the edit box and press enter to send the " - "message to the server which distributes the message to all connected " - "clients. Your message will then show up in the chat window." ) ); - - edtLocalInputText->setAccessibleName ( tr ( "New chat text edit box" ) ); - - // clear chat window and edit line - txvChatWindow->clear(); - edtLocalInputText->clear(); - - // we do not want to show a cursor in the chat history - txvChatWindow->setCursorWidth ( 0 ); - - // set a placeholder text to make sure where to type the message in (#384) - edtLocalInputText->setPlaceholderText ( tr ( "Type a message here" ) ); - - // Menu ------------------------------------------------------------------- - QMenuBar* pMenu = new QMenuBar ( this ); - QMenu* pEditMenu = new QMenu ( tr ( "&Edit" ), this ); - - pEditMenu->addAction ( tr ( "Cl&ear Chat History" ), this, SLOT ( OnClearChatHistory() ), QKeySequence ( Qt::CTRL + Qt::Key_E ) ); - - pMenu->addMenu ( pEditMenu ); -#if defined( Q_OS_IOS ) - QAction* closeAction = pMenu->addAction ( tr ( "&Close" ) ); -#endif - -#if defined( ANDROID ) || defined( Q_OS_ANDROID ) - pEditMenu->addAction ( tr ( "&Close" ), this, SLOT ( OnCloseClicked() ), QKeySequence ( Qt::CTRL + Qt::Key_W ) ); -#endif - - // Now tell the layout about the menu - layout()->setMenuBar ( pMenu ); - - // Connections ------------------------------------------------------------- - QObject::connect ( edtLocalInputText, &QLineEdit::textChanged, this, &CChatDlg::OnLocalInputTextTextChanged ); - - QObject::connect ( butSend, &QPushButton::clicked, this, &CChatDlg::OnSendText ); - - QObject::connect ( txvChatWindow, &QTextBrowser::anchorClicked, this, &CChatDlg::OnAnchorClicked ); - -#if defined( Q_OS_IOS ) - QObject::connect ( closeAction, &QAction::triggered, this, &CChatDlg::OnCloseClicked ); -#endif -} - -void CChatDlg::OnLocalInputTextTextChanged ( const QString& strNewText ) -{ - // check and correct length - if ( strNewText.length() > MAX_LEN_CHAT_TEXT ) - { - // text is too long, update control with shortened text - edtLocalInputText->setText ( strNewText.left ( MAX_LEN_CHAT_TEXT ) ); - } -} - -void CChatDlg::OnSendText() -{ - // send new text and clear line afterwards, do not send an empty message - if ( !edtLocalInputText->text().isEmpty() ) - { - emit NewLocalInputText ( edtLocalInputText->text() ); - edtLocalInputText->clear(); - } -} - -void CChatDlg::OnClearChatHistory() -{ - // clear chat window - txvChatWindow->clear(); -} - -void CChatDlg::AddChatText ( QString strChatText ) -{ - // notify accessibility plugin that text has changed - QAccessible::updateAccessibility ( new QAccessibleValueChangeEvent ( txvChatWindow, strChatText ) ); - - // analyze strChatText to check if hyperlink (limit ourselves to http(s)://) but do not - // replace the hyperlinks if any HTML code for a hyperlink was found (the user has done the HTML - // coding hisself and we should not mess with that) - if ( !strChatText.contains ( QRegularExpression ( "href\\s*=|src\\s*=" ) ) ) - { - // searches for all occurrences of http(s) and cuts until a space (\S matches any non-white-space - // character and the + means that matches the previous element one or more times.) - // This regex now contains three parts: - // - https?://\\S+ matches as much non-whitespace as possible after the http:// or https://, - // subject to the next two parts, which exclude terminating punctuation - // - (??\\[\\]{}]) is a negative look-behind assertion that disallows the match - // from ending with one of the characters !"'()+,.:;<=>?[]{} - // - (??\\[\\]{}]) is a negative look-behind assertion that disallows the match - // from ending with a ? followed by one of the characters !"'()+,.:;<=>?[]{} - // These last two parts must be separate, as a look-behind assertion must be fixed length. -#define PUNCT_NOEND_URL "[!\"'()+,.:;<=>?\\[\\]{}]" - strChatText.replace ( QRegularExpression ( "(https?://\\S+(?\\1" ); - } - - // add new text in chat window - txvChatWindow->append ( strChatText ); -} - -void CChatDlg::OnAnchorClicked ( const QUrl& Url ) -{ - // only allow http(s) URLs to be opened in an external browser - if ( Url.scheme() == QLatin1String ( "https" ) || Url.scheme() == QLatin1String ( "http" ) ) - { - if ( QMessageBox::question ( this, - APP_NAME, - tr ( "Do you want to open the link '%1' in your browser?" ).arg ( "" + Url.toString() + "" ), - QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes ) - { - QDesktopServices::openUrl ( Url ); - } - } -} - -#if defined( Q_OS_IOS ) || defined( ANDROID ) || defined( Q_OS_ANDROID ) -void CChatDlg::OnCloseClicked() -{ - // on mobile add a close button or menu entry -# if defined( Q_OS_IOS ) - // On Qt6, iOS crashes if we call close() due to unknown reasons, therefore we just hide() the dialog. A Qt bug is suspected. - // Checkout https://github.com/jamulussoftware/jamulus/pull/3413 - hide(); -# endif -# if defined( ANDROID ) || defined( Q_OS_ANDROID ) - close(); -# endif -} -#endif \ No newline at end of file diff --git a/src/chatdlgbase.ui b/src/chatdlgbase.ui deleted file mode 100644 index 7d59db9661..0000000000 --- a/src/chatdlgbase.ui +++ /dev/null @@ -1,75 +0,0 @@ - - - CChatDlgBase - - - - 0 - 0 - 435 - 405 - - - - - 0 - 0 - - - - Chat - - - - :/png/main/res/fronticon.png:/png/main/res/fronticon.png - - - true - - - - - - false - - - Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - false - - - false - - - - - - - - - - - - &Send - - - true - - - true - - - - - - - - - txvChatWindow - - - - - - diff --git a/src/client.cpp b/src/client.cpp index 8d61fd04eb..952c153750 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -32,17 +32,25 @@ CClient::CClient ( const quint16 iPortNumber, const bool bNoAutoJackConnect, const QString& strNClientName, const bool bNEnableIPv6, - const bool bNMuteMeInPersonalMix ) : + const bool bNMuteMeInPersonalMix, + const QString& strIniFileName, + const bool bMuteStream, // maybe + QList bCommandLineOptions + ) : ChannelInfo(), strClientName ( strNClientName ), - Channel ( false ), /* we need a client channel -> "false" */ + pSettings( this, strIniFileName ), + audioMixerBoard( &pSettings, this ), /* we need a client channel -> "false" */ + chatBox(), + CommandLineOptions(bCommandLineOptions), + Channel ( false ), CurOpusEncoder ( nullptr ), CurOpusDecoder ( nullptr ), eAudioCompressionType ( CT_OPUS ), - iCeltNumCodedBytes ( OPUS_NUM_BYTES_MONO_LOW_QUALITY ), - iOPUSFrameSizeSamples ( DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ), - eAudioQuality ( AQ_NORMAL ), - eAudioChannelConf ( CC_MONO ), + iCeltNumCodedBytes ( OPUS_NUM_BYTES_MONO_LOW_QUALITY ), // default should be high quality + iOPUSFrameSizeSamples ( DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ), // stereo as default + eAudioQuality ( AQ_HIGH ), + eAudioChannelConf ( CC_STEREO ), iNumAudioChannels ( 1 ), bIsInitializationPhase ( true ), bMuteOutStream ( false ), @@ -50,8 +58,6 @@ CClient::CClient ( const quint16 iPortNumber, Socket ( &Channel, iPortNumber, iQosNumber, "", bNEnableIPv6 ), Sound ( AudioCallback, this, strMIDISetup, bNoAutoJackConnect, strNClientName ), iAudioInFader ( AUD_FADER_IN_MIDDLE ), - bReverbOnLeftChan ( false ), - iReverbLevel ( 0 ), iInputBoost ( 1 ), iSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ), iSndCrdFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ), @@ -60,15 +66,16 @@ CClient::CClient ( const quint16 iPortNumber, bFraSiFactPrefSupported ( false ), bFraSiFactDefSupported ( false ), bFraSiFactSafeSupported ( false ), - eGUIDesign ( GD_ORIGINAL ), - eMeterStyle ( MT_LED_STRIPE ), bEnableAudioAlerts ( false ), bEnableOPUS64 ( false ), bJitterBufferOK ( true ), bEnableIPv6 ( bNEnableIPv6 ), bMuteMeInPersonalMix ( bNMuteMeInPersonalMix ), - iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), - pSignalHandler ( CSignalHandler::getSingletonP() ) + iServerSockBufNumFrames ( DEF_NET_BUF_SIZE_NUM_BL ), // Construct inputMeterL + pSignalHandler ( CSignalHandler::getSingletonP() ), // Construct inputMeterR + strServerName ( "" ), + m_inputMeterL(), // Construct mixerBoard object + m_inputMeterR() // start with clean chatbox { int iOpusError; @@ -120,12 +127,13 @@ CClient::CClient ( const quint16 iPortNumber, // The first ConClientListMesReceived handler performs the necessary cleanup and has to run first: QObject::connect ( &Channel, &CChannel::ConClientListMesReceived, this, &CClient::OnConClientListMesReceived ); + QObject::connect ( &Channel, &CChannel::ConClientListMesReceived, this, &CClient::ConClientListMesReceived ); QObject::connect ( &Channel, &CChannel::Disconnected, this, &CClient::Disconnected ); QObject::connect ( &Channel, &CChannel::NewConnection, this, &CClient::OnNewConnection ); - QObject::connect ( &Channel, &CChannel::ChatTextReceived, this, &CClient::ChatTextReceived ); + QObject::connect ( &Channel, &CChannel::ChatTextReceived, this, &CClient::OnChatTextReceived ); QObject::connect ( &Channel, &CChannel::ClientIDReceived, this, &CClient::OnClientIDReceived ); @@ -133,9 +141,9 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &Channel, &CChannel::LicenceRequired, this, &CClient::LicenceRequired ); - QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::VersionAndOSReceived ); + QObject::connect ( &Channel, &CChannel::VersionAndOSReceived, this, &CClient::OnVersionAndOSReceived ); - QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::RecorderStateReceived ); + QObject::connect ( &Channel, &CChannel::RecorderStateReceived, this, &CClient::OnRecorderStateReceived ); QObject::connect ( &ConnLessProtocol, &CProtocol::CLMessReadyForSending, this, &CClient::OnSendCLProtMessage ); @@ -151,7 +159,7 @@ CClient::CClient ( const quint16 iPortNumber, QObject::connect ( &ConnLessProtocol, &CProtocol::CLDisconnection, this, &CClient::OnCLDisconnection ); - QObject::connect ( &ConnLessProtocol, &CProtocol::CLVersionAndOSReceived, this, &CClient::CLVersionAndOSReceived ); + QObject::connect ( &ConnLessProtocol, &CProtocol::CLVersionAndOSReceived, this, &CClient::OnCLVersionAndOSReceived ); QObject::connect ( &ConnLessProtocol, &CProtocol::CLChannelLevelListReceived, this, &CClient::OnCLChannelLevelListReceived ); @@ -176,9 +184,9 @@ CClient::CClient ( const quint16 iPortNumber, PreciseTime.start(); // set gain delay timer to single-shot and connect handler function - TimerGainOrPan.setSingleShot ( true ); + TimerGain.setSingleShot ( true ); - QObject::connect ( &TimerGainOrPan, &QTimer::timeout, this, &CClient::OnTimerRemoteChanGainOrPan ); + QObject::connect ( &TimerGain, &QTimer::timeout, this, &CClient::OnTimerRemoteChanGain ); // start the socket (it is important to start the socket after all // initializations and connections) @@ -190,6 +198,61 @@ CClient::CClient ( const quint16 iPortNumber, SetServerAddr ( strConnOnStartupAddress ); Start(); } + + // ----------------------------------------------------------- + // NOW we do what WAS done (or equivalent) in clientdlg constructor... + + // CREATE inputMeterL and inputMeterR objects + m_inputMeterL = new CLevelMeter(this); + m_inputMeterR = new CLevelMeter(this); + + //IF load settings, do it here + pSettings.Load ( CommandLineOptions ); + emit pSettings.updateSettings(); + + // MainMixerBoard.SetNumMixerPanelRows ( pSettings->iNumMixerPanelRows ); + audioMixerBoard.SetMIDICtrlUsed ( !strMIDISetup.isEmpty() ); + + // setup timers + TimerCheckAudioDeviceOk.setSingleShot ( true ); // only check once after connection + TimerDetectFeedback.setSingleShot ( true ); + + // Connect on startup ------------------------------------------------------ + if ( !strConnOnStartupAddress.isEmpty() ) + { + // initiate connection (always show the address in the mixer board + // (no alias)) + Connect ( strConnOnStartupAddress ); + } + + // All Connections + // timers + QObject::connect ( &TimerSigMet, &QTimer::timeout, this, &CClient::OnTimerSigMet ); + QObject::connect ( &TimerBuffersLED, &QTimer::timeout, this, &CClient::OnTimerBuffersLED ); + QObject::connect ( &TimerPing, &QTimer::timeout, this, &CClient::OnTimerPing ); + QObject::connect ( &TimerCheckAudioDeviceOk, &QTimer::timeout, this, &CClient::OnTimerCheckAudioDeviceOk ); + QObject::connect ( &TimerDetectFeedback, &QTimer::timeout, this, &CClient::OnTimerDetectFeedback ); + QObject::connect ( &TimerStatus, &QTimer::timeout, this, &CClient::OnTimerStatus ); + // client-client connections //TODO: tidy + QObject::connect ( this, &CClient::Disconnected, this, &CClient::OnDisconnected ); + QObject::connect ( this, &CClient::PingTimeReceived, this, &CClient::OnPingTimeResult ); + QObject::connect ( this, &CClient::SoundDeviceChanged, this, &CClient::OnSoundDeviceChanged ); + // Mixerboard updates + QObject::connect ( &audioMixerBoard, &CAudioMixerBoard::ChangeChanGain, this, &CClient::OnChangeChanGain ); + QObject::connect ( &audioMixerBoard, &CAudioMixerBoard::ChangeChanPan, this, &CClient::OnChangeChanPan ); + QObject::connect ( &audioMixerBoard, &CAudioMixerBoard::NumClientsChanged, this, &CClient::OnNumClientsChanged ); + // chatbox + QObject::connect ( &chatBox, &CChatBox::NewLocalInputText, this, &CClient::OnNewLocalInputText ); + + // Post Connections init + // start timer for status bar + TimerStatus.start ( LED_BAR_UPDATE_TIME_MS ); + + if ( bMuteStream ) + { + this->setMuteOut(true); + } + } CClient::~CClient() @@ -215,6 +278,27 @@ CClient::~CClient() opus_custom_mode_destroy ( Opus64Mode ); } +bool CClient::jitterWarn() +{ + return m_jitterWarn; +} + +void CClient::setJitterWarn( bool warn ) +{ + if (m_jitterWarn != warn) { + m_jitterWarn = warn; + emit jitterWarnChanged(); + } +} + +void CClient::setSessionStatus(QString strName) +{ + if ( m_sessionStatus != strName ) { + m_sessionStatus = strName; + emit sessionStatusChanged(); + } +} + void CClient::OnSendProtMessage ( CVector vecMessage ) { // the protocol queries me to call the function to send the message @@ -258,10 +342,22 @@ void CClient::OnJittBufSizeChanged ( int iNewJitBufSize ) // to the server which is incorrect. iServerSockBufNumFrames = iNewJitBufSize; } + + // update QML + pSettings.UpdateJitterBufferFrame(); } void CClient::OnNewConnection() { + // The oldGain and newGain arrays are used to avoid sending duplicate gain change messages. + // As these values depend on the channel IDs of a specific server, we have + // to reset those upon connect. + // We reset to 1 because this is what the server part sets by default. + for ( int iId = 0; iId < MAX_NUM_CHANNELS; iId++ ) + { + oldGain[iId] = newGain[iId] = 1; + } + // a new connection was successfully initiated, send infos and request // connected clients list Channel.SetRemoteInfo ( ChannelInfo ); @@ -281,91 +377,36 @@ void CClient::OnNewConnection() //### TODO: END ###// } -void CClient::OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted ) -{ - // map iChanID from server channel ID to client channel ID - int iChanID = FindClientChannel ( iServerChanID, false ); - - if ( iChanID != INVALID_INDEX ) - { - emit MuteStateHasChangedReceived ( iChanID, bIsMuted ); - } -} - -void CClient::OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ) +void CClient::OnConClientListMesReceived ( CVector vecChanInfo ) { - // reorder levels from server channel order to client channel order + // Upon receiving a new client list, we have to reset oldGain and newGain + // entries for unused channels. This ensures that a disconnected channel + // does not leave behind wrong cached gain values which would leak into + // any new channel which reused this channel id. + int iNumConnectedClients = vecChanInfo.Size(); - if ( ReorderLevelList ( vecLevelList ) ) + // Save what channel IDs are in use: + bool bChanIdInUse[MAX_NUM_CHANNELS] = {}; + for ( int i = 0; i < iNumConnectedClients; i++ ) { - emit CLChannelLevelListReceived ( InetAddr, vecLevelList ); + bChanIdInUse[vecChanInfo[i].iChanID] = true; } -} - -void CClient::OnConClientListMesReceived ( CVector vecChanInfo ) -{ - // translate from server channel IDs to client channel IDs - // ALSO here is where we allocate and free client channels as required - - const int iNumConnectedClients = vecChanInfo.Size(); - int i, iSrvIdx; - - // on a new connection, a server sends an empty channel list before sending - // the real channel list (see #1010). To avoid this discarding "our" channel - // that we have just created, we skip this processing and just pass the empty - // list to the emitted signal. - if ( iNumConnectedClients != 0 ) + // Reset all gains for unused channel IDs: + for ( int iId = 0; iId < MAX_NUM_CHANNELS; iId++ ) { - // this relies on the received client list being in order of server channel ID - - for ( i = 0, iSrvIdx = 0; i < iNumConnectedClients && iSrvIdx < MAX_NUM_CHANNELS; ) - { - // server channel ID of this entry - const int iServerChannelID = vecChanInfo[i].iChanID; - - // find matching client channel ID, creating new if necessary, - // update channel number to be client-side - vecChanInfo[i].iChanID = FindClientChannel ( iServerChannelID, true ); - - // discard any lower server channels that are no longer in our local list - while ( iSrvIdx < iServerChannelID ) - { - const int iId = FindClientChannel ( iSrvIdx, false ); - - if ( iId != INVALID_INDEX ) - { - // iSrvIdx contains a server channel number that has now gone - FreeClientChannel ( iSrvIdx ); - } - iSrvIdx++; - } - - i++; // next list entry - iSrvIdx++; // next local server channel - } - - // have now run out of active channels, discard any remaining from our local list - // note that iActiveChannels will reduce as remaining channels are freed - - while ( iActiveChannels > iNumConnectedClients && iSrvIdx < MAX_NUM_CHANNELS ) + if ( !bChanIdInUse[iId] ) { - const int iId = FindClientChannel ( iSrvIdx, false ); - - if ( iId != INVALID_INDEX ) - { - // iSrvIdx contains a server channel number that has now gone - FreeClientChannel ( iSrvIdx ); - } - iSrvIdx++; + // reset oldGain and newGain as this channel id is currently unused and will + // start with a server-side gain at 1 (100%) again. + oldGain[iId] = newGain[iId] = 1; } - - Q_ASSERT ( iActiveChannels == iNumConnectedClients ); } - // pass the received list onwards, now containing client channel IDs - - emit ConClientListMesReceived ( vecChanInfo ); + // update mixer board with the additional client infos + audioMixerBoard.ApplyNewConClientList( vecChanInfo ); + // set session status + setSessionStatus("CONNECTED"); } void CClient::CreateServerJitterBufferMessage() @@ -431,30 +472,27 @@ void CClient::SetDoAutoSockBufSize ( const bool bValue ) CreateServerJitterBufferMessage(); } -// In order not to flood the server with gain or pan change messages, particularly when using +// In order not to flood the server with gain change messages, particularly when using // a MIDI controller, a timer is used to limit the rate at which such messages are generated. // This avoids a potential long backlog of messages, since each must be ACKed before the next // can be sent, and this ACK is subject to the latency of the server connection. // -// When the first gain or pan change message is requested after an idle period (i.e. the timer is not -// running), it will be sent immediately, and a timer started. The timer period is dependent on -// the current ping time to the remote server. +// When the first gain change message is requested after an idle period (i.e. the timer is not +// running), it will be sent immediately, and a 300ms timer started. // -// If a gain or pan change message is requested while the timer is still running, the new value is not sent, -// but just stored in newGain or newPan within clientChannels[iId], and the minGainOrPanId and maxGainOrPanId -// updated to note the range of IDs that must be checked when the time expires (this will usually be a single -// channel unless channel grouping is being used). This avoids having to check all possible channels. +// If a gain change message is requested while the timer is still running, the new gain is not sent, +// but just stored in newGain[iId], and the minGainId and maxGainId updated to note the range of +// IDs that must be checked when the time expires (this will usually be a single channel +// unless channel grouping is being used). This avoids having to check all possible channels. // -// When the timer fires, the channels minGainOrPanId <= iId < maxGainOrPanId are checked by comparing the -// last sent values in oldGain or oldPan with any pending values in newGain or newPan, and if they differ, -// the new value is sent, updating oldGain or oldPan with the sent value. If any new values are sent, -// the timer is restarted so that further immediate updates will be pended. +// When the timer fires, the channels minGainId <= iId < maxGainId are checked by comparing +// the last sent value in oldGain[iId] with any pending value in newGain[iId], and if they differ, +// the new value is sent, updating oldGain[iId] with the sent value. If any new values are +// sent, the timer is restarted so that further immediate updates will be pended. void CClient::SetRemoteChanGain ( const int iId, const float fGain, const bool bIsMyOwnFader ) { - QMutexLocker locker ( &MutexGainOrPan ); - - CClientChannel* clientChan = &clientChannels[iId]; + QMutexLocker locker ( &MutexGain ); // if this gain is for my own channel, apply the value for the Mute Myself function if ( bIsMyOwnFader ) @@ -462,108 +500,69 @@ void CClient::SetRemoteChanGain ( const int iId, const float fGain, const bool b fMuteOutStreamGain = fGain; } - if ( TimerGainOrPan.isActive() ) + if ( TimerGain.isActive() ) { // just update the new value for sending later; - // will compare with oldGain when the timer fires - clientChan->newGain = fGain; + // will compare with oldGain[iId] when the timer fires + newGain[iId] = fGain; // update range of channel IDs to check in the timer - if ( iId < minGainOrPanId ) - minGainOrPanId = iId; // first value to check - if ( iId >= maxGainOrPanId ) - maxGainOrPanId = iId + 1; // first value NOT to check + if ( iId < minGainId ) + minGainId = iId; // first value to check + if ( iId >= maxGainId ) + maxGainId = iId + 1; // first value NOT to check return; } // here the timer was not active: // send the actual gain and reset the range of channel IDs to empty - clientChan->oldGain = clientChan->newGain = fGain; - Channel.SetRemoteChanGain ( clientChan->iServerChannelID, fGain ); // translate client channel to server channel ID + oldGain[iId] = newGain[iId] = fGain; + Channel.SetRemoteChanGain ( iId, fGain ); - StartTimerGainOrPan(); + StartDelayTimer(); } -void CClient::OnTimerRemoteChanGainOrPan() +void CClient::OnTimerRemoteChanGain() { - QMutexLocker locker ( &MutexGainOrPan ); + QMutexLocker locker ( &MutexGain ); bool bSent = false; - for ( int iId = minGainOrPanId; iId < maxGainOrPanId; iId++ ) + for ( int iId = minGainId; iId < maxGainId; iId++ ) { - CClientChannel* clientChan = &clientChannels[iId]; - - if ( clientChan->newGain != clientChan->oldGain ) + if ( newGain[iId] != oldGain[iId] ) { // send new gain and record as old gain - float fGain = clientChan->oldGain = clientChan->newGain; - Channel.SetRemoteChanGain ( clientChan->iServerChannelID, fGain ); // translate client channel to server channel ID - bSent = true; - } - - if ( clientChan->newPan != clientChan->oldPan ) - { - // send new pan and record as old pan - float fPan = clientChan->oldPan = clientChan->newPan; - Channel.SetRemoteChanPan ( clientChan->iServerChannelID, fPan ); // translate client channel to server channel ID + float fGain = oldGain[iId] = newGain[iId]; + Channel.SetRemoteChanGain ( iId, fGain ); bSent = true; } } - // if a new gain or pan has been sent, reset the range of channel IDs to empty and start timer + // if a new gain has been sent, reset the range of channel IDs to empty and start timer if ( bSent ) { - StartTimerGainOrPan(); + StartDelayTimer(); } } // reset the range of channel IDs to check and start the delay timer -void CClient::StartTimerGainOrPan() +void CClient::StartDelayTimer() { - maxGainOrPanId = 0; - minGainOrPanId = MAX_NUM_CHANNELS; + maxGainId = 0; + minGainId = MAX_NUM_CHANNELS; // start timer to delay sending further updates // use longer delay when connected to server with higher ping time, // double the ping time in order to allow a bit of overhead for other messages if ( iCurPingTime < DEFAULT_GAIN_DELAY_PERIOD_MS / 2 ) { - TimerGainOrPan.start ( DEFAULT_GAIN_DELAY_PERIOD_MS ); + TimerGain.start ( DEFAULT_GAIN_DELAY_PERIOD_MS ); } else { - TimerGainOrPan.start ( iCurPingTime * 2 ); - } -} - -void CClient::SetRemoteChanPan ( const int iId, const float fPan ) -{ - QMutexLocker locker ( &MutexGainOrPan ); - - CClientChannel* clientChan = &clientChannels[iId]; - - if ( TimerGainOrPan.isActive() ) - { - // just update the new value for sending later; - // will compare with oldPan when the timer fires - clientChan->newPan = fPan; - - // update range of channel IDs to check in the timer - if ( iId < minGainOrPanId ) - minGainOrPanId = iId; // first value to check - if ( iId >= maxGainOrPanId ) - maxGainOrPanId = iId + 1; // first value NOT to check - - return; + TimerGain.start ( iCurPingTime * 2 ); } - - // here the timer was not active: - // send the actual gain and reset the range of channel IDs to empty - clientChan->oldPan = clientChan->newPan = fPan; - Channel.SetRemoteChanPan ( clientChan->iServerChannelID, fPan ); // translate client channel to server channel ID - - StartTimerGainOrPan(); } bool CClient::SetServerAddr ( QString strNAddr ) @@ -607,6 +606,7 @@ bool CClient::GetAndResetbJitterBufferOKFlag() // since per definition the jitter buffer status is OK if both the // put and get status are OK return bSocketJitBufOKFlag; + } void CClient::SetSndCrdPrefFrameSizeFactor ( const int iNewFactor ) @@ -722,6 +722,8 @@ QString CClient::SetSndCrdDev ( const QString strNewDev ) if ( !strError.isEmpty() ) { emit SoundDeviceChanged ( strError ); + + emit pSettings.slSndCrdDevChanged(); } return strError; @@ -893,7 +895,7 @@ void CClient::OnControllerInFaderLevel ( int iChannelIdx, int iValue ) } #endif - emit ControllerInFaderLevel ( iChannelIdx, iValue ); + audioMixerBoard.SetFaderLevel ( iChannelIdx, iValue ); } void CClient::OnControllerInPanValue ( int iChannelIdx, int iValue ) @@ -905,7 +907,7 @@ void CClient::OnControllerInPanValue ( int iChannelIdx, int iValue ) SetRemoteChanPan ( iChannelIdx, static_cast ( iValue ) / AUD_MIX_PAN_MAX ); #endif - emit ControllerInPanValue ( iChannelIdx, iValue ); + audioMixerBoard.SetPanValue ( iChannelIdx, iValue ); } void CClient::OnControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo ) @@ -916,7 +918,7 @@ void CClient::OnControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo ) // FIXME: no idea what to do here. #endif - emit ControllerInFaderIsSolo ( iChannelIdx, bIsSolo ); + audioMixerBoard.SetFaderIsSolo ( iChannelIdx, bIsSolo ); } void CClient::OnControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ) @@ -927,7 +929,7 @@ void CClient::OnControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ) // FIXME: no idea what to do here. #endif - emit ControllerInFaderIsMute ( iChannelIdx, bIsMute ); + audioMixerBoard.SetFaderIsMute ( iChannelIdx, bIsMute ); } void CClient::OnControllerInMuteMyself ( bool bMute ) @@ -938,23 +940,11 @@ void CClient::OnControllerInMuteMyself ( bool bMute ) // FIXME: no idea what to do here. #endif - emit ControllerInMuteMyself ( bMute ); + this->setMuteOut( bMute ); } -void CClient::OnClientIDReceived ( int iServerChanID ) +void CClient::OnClientIDReceived ( int iChanID ) { - // if we have just connected to a running server, iActiveChannels will be 0 - // if iActiveChannels is not 0, the server must have been restarted on the fly - // in that case, channels might have changed, so clear our list to get it afresh. - if ( iActiveChannels != 0 ) - { - qInfo() << "> Server restarted?"; - ClearClientChannels(); - } - - // allocate and map client-side channel 0 - int iChanID = FindClientChannel ( iServerChanID, true ); // should always return channel 0 - // for headless mode we support to mute our own signal in the personal mix // (note that the check for headless is done in the main.cpp and must not // be checked here) @@ -963,7 +953,7 @@ void CClient::OnClientIDReceived ( int iServerChanID ) SetRemoteChanGain ( iChanID, 0, false ); } - emit ClientIDReceived ( iChanID ); + audioMixerBoard.SetMyChannelID ( iChanID ); } void CClient::Start() @@ -971,9 +961,6 @@ void CClient::Start() // init object Init(); - // initialise client channels - ClearClientChannels(); - // enable channel Channel.SetEnable ( true ); @@ -1205,9 +1192,6 @@ void CClient::Init() // set the channel network properties Channel.SetAudioStreamProperties ( eAudioCompressionType, iCeltNumCodedBytes, iSndCrdFrameSizeFactor, iNumAudioChannels ); - // init reverberation - AudioReverb.Init ( eAudioChannelConf, iStereoBlockSizeSam, SYSTEM_SAMPLE_RATE_HZ ); - // init the sound card conversion buffers if ( bSndCrdConversionBufferRequired ) { @@ -1300,12 +1284,6 @@ void CClient::ProcessAudioDataIntern ( CVector& vecsStereoSndCrd ) SignalLevelMeter.Update ( vecsStereoSndCrd, iMonoBlockSizeSam, true ); #endif - // add reverberation effect if activated - if ( iReverbLevel != 0 ) - { - AudioReverb.Process ( vecsStereoSndCrd, bReverbOnLeftChan, static_cast ( iReverbLevel ) / AUD_REVERB_MAX / 4 ); - } - // apply pan (audio fader) and mix mono signals if ( !( ( iAudioInFader == AUD_FADER_IN_MIDDLE ) && ( eAudioChannelConf == CC_STEREO ) ) ) { @@ -1494,183 +1472,391 @@ int CClient::EstimatedOverallDelay ( const int iPingTimeMs ) return MathUtils::round ( fTotalBufferDelayMs + iPingTimeMs ); } -// Management of Client Channels and mapping to/from Server Channels +void CClient::onConnectButtonClicked() +{ + // set off the connect process + strSelectedAddress = NetworkUtil::FixAddress ( strSelectedAddress ); + + if ( !strSelectedAddress.isEmpty() ) + { + // store new address at the top of the list, if the list was already + // full, the last element is thrown out + pSettings.vstrIPAddress.StringFiFoWithCompare ( strSelectedAddress ); + } + + // first check if we are already connected, if this is the case we have to + // disconnect the old server first + if ( IsRunning() ) + { + Disconnect(); + } + + // initiate connection + Connect ( strSelectedAddress ); + +} + + +void CClient::onDisconnectButtonClicked() +{ + qDebug() << "Disconnecting..."; + + if ( IsRunning() ) + { + Disconnect(); + } +} -void CClient::ClearClientChannels() +void CClient::OnTimerSigMet() { - QMutexLocker locker ( &MutexChannels ); + // show current level + m_inputMeterL->setDoubleVal( GetLevelForMeterdBLeft() ); + m_inputMeterR->setDoubleVal( GetLevelForMeterdBRight() ); - iActiveChannels = 0; - iJoinSequence = 0; + if ( bDetectFeedback && + ( GetLevelForMeterdBLeft() > NUM_STEPS_LED_BAR - 0.5 || GetLevelForMeterdBRight() > NUM_STEPS_LED_BAR - 0.5 ) ) + { + // mute locally and mute channel + this->setMuteOut( true ); + audioMixerBoard.MuteMyChannel(); - for ( int i = 0; i < MAX_NUM_CHANNELS; i++ ) + qDebug() << "Feedback detected ... "; + // show message about feedback issue + setUserMsg( tr ( "Audio feedback or loud signal detected.\n\n" + "We muted your channel and activated 'Mute Myself'. Please solve " + "the feedback issue first and unmute yourself afterwards." ) ); + } +} + +void CClient::OnTimerBuffersLED() +{ + if ( GetAndResetbJitterBufferOKFlag() ) { - clientChannels[i].iServerChannelID = INVALID_INDEX; - // all other fields will be initialised on channel allocation + setJitterWarn( false ); // status OK, no warning + } + else + { + setJitterWarn( true ); // status BAD, give warning + } +} - clientChannelIDs[i] = INVALID_INDEX; +void CClient::OnTimerCheckAudioDeviceOk() +{ + // check if the audio device entered the audio callback after a pre-defined + // timeout to check if a valid device is selected and if we do not have + // fundamental settings errors (in which case the GUI would only show that + // it is trying to connect the server which does not help to solve the problem (#129)) + if ( !this->IsCallbackEntered() ) + { + setUserMsg(tr ( "Your sound card is not working correctly. " + "Please open the settings dialog and check the device selection and the driver settings." ) ); } +} - // qInfo() << "> Client channel list cleared"; +void CClient::OnTimerDetectFeedback() +{ + bDetectFeedback = false; } -void CClient::FreeClientChannel ( const int iServerChannelID ) + +void CClient::Connect( const QString& strAddress ) { - QMutexLocker locker ( &MutexChannels ); + // set address and check if address is valid + if ( SetServerAddr ( strAddress ) ) + { + // try to start client, if error occurred, do not go in + // running state but show error message + try + { + if ( !IsRunning() ) + { + Start(); + } + } - if ( iServerChannelID == INVALID_INDEX || iServerChannelID >= MAX_NUM_CHANNELS ) + catch ( const CGenErr& generr ) + { + // show error message and return the function + setUserMsg( generr.GetErrorText() ); + return; + } + + // set session status bar + emit sessionNameChanged(); + emit sessionStatusChanged(); + emit bConnectedChanged(); + + SetServerStatus ( "SESSION ACTIVE" ); + + // start timer for level meter bar and ping time measurement + TimerSigMet.start ( LEVELMETER_UPDATE_TIME_MS ); + TimerBuffersLED.start ( BUFFER_LED_UPDATE_TIME_MS ); + TimerPing.start ( PING_UPDATE_TIME_MS ); + TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS ); // is single shot timer + + // audio feedback detection + if ( pSettings.bEnableFeedbackDetection ) + { + TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS ); // single shot timer + bDetectFeedback = true; + } + + } +} + +void CClient::Disconnect() +{ + // only stop client if currently running, in case we received + // the stopped message, the client is already stopped but the + // connect/disconnect button and other GUI controls must be + // updated + if ( IsRunning() ) { - return; + Stop(); } - const int iClientChannelID = clientChannelIDs[iServerChannelID]; + SetServerStatus ( "" ); - Q_ASSERT ( clientChannels[iClientChannelID].iServerChannelID == iServerChannelID ); + // stop timer for level meter bars and reset them + TimerSigMet.stop(); + m_inputMeterL->setDoubleVal( 0 ); + m_inputMeterR->setDoubleVal( 0 ); + setJitterWarn(false); - clientChannelIDs[iServerChannelID] = INVALID_INDEX; - clientChannels[iClientChannelID].iServerChannelID = INVALID_INDEX; + // stop other timers + TimerBuffersLED.stop(); + TimerPing.stop(); + TimerCheckAudioDeviceOk.stop(); + TimerDetectFeedback.stop(); + bDetectFeedback = false; - iActiveChannels -= 1; + // immediately update status bar + OnTimerStatus(); - /* - qInfo() << qUtf8Printable ( QString ( "> Freed client ch %1 for server ch %2; chan count = %3" ) - .arg ( iClientChannelID ) - .arg ( iServerChannelID ) - .arg ( iActiveChannels ) ); - */ + SetPingTime (0,0); + + strSelectedAddress = "127.0.0.1"; //FIXME - temp default + emit sessionlinkTextChanged(); + emit sessionNameChanged(); + emit sessionStatusChanged(); + emit recordingStatusChanged(); + emit bConnectedChanged(); + + // clear mixer board (remove all faders) + audioMixerBoard.clear(); +} + +void CClient::SetPingTime(const int iPingTime, const int iOverallDelayMs) +{ + // apply values + m_pingVal = iPingTime; + m_delayVal = iOverallDelayMs; + + emit pingValChanged(); + emit delayValChanged(); } -// find, and optionally create, a client channel for the supplied server channel ID -// returns a client channel ID or INVALID_INDEX -int CClient::FindClientChannel ( const int iServerChannelID, const bool bCreateIfNew ) +void CClient::SetServerStatus ( const QString& strNewServerName ) { - QMutexLocker locker ( &MutexChannels ); + // store the current server name + strServerName = strNewServerName; - if ( iServerChannelID == INVALID_INDEX || iServerChannelID >= MAX_NUM_CHANNELS ) + if ( strServerName.isEmpty() ) { - return INVALID_INDEX; + // no connection or connection was reset: show default title + setSessionStatus("NO SESSION"); } + else + { + // Do not set the server name directly but first show a label which indicates + // that we are trying to connect the server + setSessionStatus("CONNECTING ..."); + } +} - int iClientChannelID = clientChannelIDs[iServerChannelID]; +QString CClient::sessionlinkText() +{ + return Channel.GetAddress().toString(); +} - if ( iClientChannelID != INVALID_INDEX ) - { - Q_ASSERT ( clientChannels[iClientChannelID].iServerChannelID == iServerChannelID ); - return iClientChannelID; +void CClient::setSessionlinkText( const QString& sessionUrlText ) +{ + if ( strSelectedAddress == sessionUrlText ) + return; + + strSelectedAddress = sessionUrlText; + qDebug() << "set strSelectedAddress to: " << sessionUrlText; +} + +QString CClient::userMsg() const +{ + return m_userMsg; +} + +void CClient::setUserMsg(const QString &msg) +{ + if (m_userMsg != msg) { + m_userMsg = msg; + emit userMsgChanged(); } +} - // no matching client channel - create new one if requested - if ( bCreateIfNew ) +void CClient::closeEvent ( QCloseEvent* Event ) +{ + // if connected, terminate connection + if ( IsRunning() ) { - // search clientChannels[] for a free client channel - for ( iClientChannelID = 0; iClientChannelID < MAX_NUM_CHANNELS; iClientChannelID++ ) - { - CClientChannel* clientChan = &clientChannels[iClientChannelID]; + Stop(); + } - if ( clientChan->iServerChannelID == INVALID_INDEX ) - { - clientChan->iServerChannelID = iServerChannelID; - clientChan->iJoinSequence = ++iJoinSequence; + // make sure all current fader settings are applied to the settings struct + audioMixerBoard.StoreAllFaderSettings(); - clientChan->oldGain = clientChan->newGain = 1.0f; - clientChan->oldPan = clientChan->newPan = 0.5f; + // pSettings->bConnectDlgShowAllMusicians = ConnectDlg.GetShowAllMusicians(); + // pSettings->eChannelSortType = MainMixerBoard.GetFaderSorting(); + // pSettings->iNumMixerPanelRows = MainMixerBoard.GetNumMixerPanelRows(); - clientChan->level = 0; + // default implementation of this event handler routine + Event->accept(); +} - clientChannelIDs[iServerChannelID] = iClientChannelID; - iActiveChannels += 1; +void CClient::OnTimerPing() +{ + // send ping message to the server + CreateCLPingMes(); +} - /* - qInfo() << qUtf8Printable ( QString ( "> Alloc client ch %1 for server ch %2; chan count = %3" ) - .arg ( iClientChannelID ) - .arg ( iServerChannelID ) - .arg ( iActiveChannels ) ); - */ +void CClient::OnPingTimeResult ( int iPingTime ) +{ + // // calculate overall delay + const int iOverallDelayMs = EstimatedOverallDelay ( iPingTime ); + + SetPingTime ( iPingTime, iOverallDelayMs ); +} - return iClientChannelID; // new client channel ID - } - } - } - return INVALID_INDEX; +void CClient::OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVersion ) +{ + ; // nothing for now } -// When the client receives a channel level list from the server, the list contains one value -// for each currently-active channel, ordered by the channel ID assigned by the server. -// The values will correspond to the active channels in the last client list that was sent. -// This list is passed up to the mixer board, which will interpret the values in the order -// of channels that it knows about. -// -// Since CClient is translating server channel IDs to local client channel IDs before -// passing the client list up to the mixer board, it is also necessary to re-order the values -// in the level list so that they are in order of mapped client channel ID. -// This function performs that re-ordering by scanning the server channels in order, once, -// for active channels, and storing the level value in the corresponding client channel. -// Then the function scans the client channels in order, fetching the level values and putting -// them back into vecLevelList in order of client channel. The mixer board will then display -// the levels against the correct channels. -// -// The list size is checked against the current number of active channels to guard against -// any unexpected temporary mismatch in size due to potential out-of-order message delivery. -// -// This function returns true if the list has been processed and should be passed on, -// or false if it was the wrong size and should be discarded. -bool CClient::ReorderLevelList ( CVector& vecLevelList ) +void CClient::OnCLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString strVersion ) { - QMutexLocker locker ( &MutexChannels ); + // update check + int mySuffixIndex; + QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &mySuffixIndex ); + + int serverSuffixIndex; + QVersionNumber serverVersion = QVersionNumber::fromString ( strVersion, &serverSuffixIndex ); - // vecLevelList is sent from server ordered by server channel ID - // re-order it by client channel ID before passing up to the GUI - // the list is passed in by reference and modified in situ + // only compare if the server version has no suffix (such as dev or beta) + if ( strVersion.size() == serverSuffixIndex && QVersionNumber::compare ( serverVersion, myVersion ) > 0 ) + { + // do nothing + ; + // show the label and hide it after one minute again + // lblUpdateCheck->show(); + // QTimer::singleShot ( 60000, [this]() { lblUpdateCheck->hide(); } ); + } +} - // check it is the right length - if ( vecLevelList.Size() != iActiveChannels ) +void CClient::OnChatTextReceived ( QString strChatText ) +{ + if ( pSettings.bEnableAudioAlerts ) { - return false; // tell caller to ignore it + // QSoundEffect* sf = new QSoundEffect(); + // sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_message.wav" ) ); + // sf->play(); + ; } + chatBox.AddChatText ( strChatText ); +} - int iClientCh; - int iServerCh = 0; +void CClient::OnClearAllStoredSoloMuteSettings() +{ + // if we are in an active connection, we first have to store all fader settings in + // the settings struct, clear the solo and mute states and then apply the settings again + audioMixerBoard.StoreAllFaderSettings(); + pSettings.vecStoredFaderIsSolo.Reset ( false ); + pSettings.vecStoredFaderIsMute.Reset ( false ); + audioMixerBoard.LoadAllFaderSettings(); +} - // fetch levels by server channel ID +void CClient::OnLoadChannelSetup() +{ + QString strFileName = "somefile.ini" ; //FIXME - QFileDialog::getOpenFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX ); - for ( int i = 0; i < iActiveChannels; i++ ) + if ( !strFileName.isEmpty() ) { - // find next active server channel - while ( iServerCh < MAX_NUM_CHANNELS ) - { - iClientCh = clientChannelIDs[iServerCh++]; - - if ( iClientCh != INVALID_INDEX ) - { - clientChannels[iClientCh].level = vecLevelList[i]; - break; - } - } + // first update the settings struct and then update the mixer panel + pSettings.LoadFaderSettings ( strFileName ); + audioMixerBoard.LoadAllFaderSettings(); } +} - // store levels by client channel ID - iClientCh = 0; +void CClient::OnSaveChannelSetup() +{ + QString strFileName = "somefile.ini" ; //FIXME - QFileDialog::getSaveFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX ); - for ( int i = 0; i < iActiveChannels; i++ ) + if ( !strFileName.isEmpty() ) { - while ( iClientCh < MAX_NUM_CHANNELS ) - { - uint16_t level = clientChannels[iClientCh].level; + // first store all current fader settings (in case we are in an active connection + // right now) and then save the information in the settings struct in the file + audioMixerBoard.StoreAllFaderSettings(); + pSettings.SaveFaderSettings ( strFileName ); + } +} - iServerCh = clientChannels[iClientCh++].iServerChannelID; - if ( iServerCh != INVALID_INDEX ) - { - vecLevelList[i] = level; - break; - } +void CClient::OnSoundDeviceChanged ( QString strError ) +{ + if ( !strError.isEmpty() ) + { + // the sound device setup has a problem, disconnect any active connection + if ( IsRunning() ) + { + Disconnect(); } + + // show the error message of the device setup + setUserMsg( strError ); + } + + // if the check audio device timer is running, it must be restarted on a device change + if ( TimerCheckAudioDeviceOk.isActive() ) + { + TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS ); + } + + if ( pSettings.bEnableFeedbackDetection && TimerDetectFeedback.isActive() ) + { + TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS ); + bDetectFeedback = true; + } +} + +void CClient::OnRecorderStateReceived ( const ERecorderState newRecorderState ) +{ + // update immediately here and inform QML + eRecorderState = newRecorderState; + emit recordingStatusChanged(); +} + +void CClient::OnNumClientsChanged ( int iNewNumClients ) +{ + if ( pSettings.bEnableAudioAlerts && iNewNumClients > iClients ) + { + // QSoundEffect* sf = new QSoundEffect(); + // sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_user.wav" ) ); + // sf->play(); + ; // do nothing for now } - return true; // tell caller to emit signal with new list + // iNewNumClients will be zero on the first trigger of this signal handler when connecting to a new server. + // Subsequent triggers will thus sound the alert (if enabled). + iClients = iNewNumClients; } diff --git a/src/client.h b/src/client.h index dea4b4ebbc..05743a1357 100644 --- a/src/client.h +++ b/src/client.h @@ -38,31 +38,25 @@ #include "socket.h" #include "channel.h" #include "util.h" -#include "plugins/audioreverb.h" #include "buffer.h" #include "signalhandler.h" +#include "audiomixerboard.h" +#include "levelmeter.h" +#include "chatbox.h" -#if defined( _WIN32 ) && !defined( JACK_ON_WINDOWS ) +#if defined( _WIN32 ) # include "sound/asio/sound.h" -#else -# if ( defined( Q_OS_MACOS ) ) && !defined( JACK_REPLACES_COREAUDIO ) -# include "sound/coreaudio-mac/sound.h" -# else -# if defined( Q_OS_IOS ) -# include "sound/coreaudio-ios/sound.h" -# else -# ifdef ANDROID -# include "sound/oboe/sound.h" -# else -# include "sound/jack/sound.h" -# ifndef JACK_ON_WINDOWS // these headers are not available in Windows OS -# include -# include -# endif -# include -# endif -# endif -# endif +#elif defined( Q_OS_MACOS ) +# include "sound/coreaudio-mac/sound.h" +#elif defined( Q_OS_IOS ) +# include "sound/coreaudio-ios/sound.h" +#elif defined (Q_OS_ANDROID) +# include "sound/oboe/sound.h" +#elif defined (Q_OS_LINUX) +# include "sound/jack/sound.h" +# include +# include +# include #endif /* Definitions ****************************************************************/ @@ -71,9 +65,6 @@ #define AUD_FADER_IN_MAX 100 #define AUD_FADER_IN_MIDDLE ( AUD_FADER_IN_MAX / 2 ) -// audio reverberation range -#define AUD_REVERB_MAX 100 - // default delay period between successive gain updates (ms) // this will be increased to double the ping time if connected to a distant server #define DEFAULT_GAIN_DELAY_PERIOD_MS 50 @@ -103,26 +94,45 @@ #define OPUS_NUM_BYTES_STEREO_NORMAL_QUALITY_DBLE_FRAMESIZE 71 #define OPUS_NUM_BYTES_STEREO_HIGH_QUALITY_DBLE_FRAMESIZE 165 -/* Classes ********************************************************************/ +// from clientdlg +/* Definitions ****************************************************************/ +// update time for GUI controls +#define LEVELMETER_UPDATE_TIME_MS 100 // ms +#define BUFFER_LED_UPDATE_TIME_MS 300 // ms +#define LED_BAR_UPDATE_TIME_MS 1000 // ms +#define CHECK_AUDIO_DEV_OK_TIME_MS 5000 // ms +#define DETECT_FEEDBACK_TIME_MS 3000 // ms -class CClientChannel -{ -public: - int iServerChannelID; // unused channels will contain INVALID_INDEX - int iJoinSequence; // order of joining of session participants +// number of ping times > upper bound until error message is shown +#define NUM_HIGH_PINGS_UNTIL_ERROR 5 - float oldGain, newGain; // for rate-limiting sending of gain messages - float oldPan, newPan; // for rate-limiting sending of pan messages +#define DISPLAY_UPDATE_TIME 1000 // ms - uint16_t level; // last value of level meter received for channel +#define SERV_LIST_REQ_UPDATE_TIME_MS 2000 // ms - // can store here other information about an active channel -}; +// ------------ +/* Classes ********************************************************************/ class CClient : public QObject { Q_OBJECT + Q_PROPERTY(QString sessionlinkText READ sessionlinkText WRITE setSessionlinkText NOTIFY sessionlinkTextChanged) + Q_PROPERTY(bool muteOut READ muteOut WRITE setMuteOut NOTIFY muteOutChanged) + Q_PROPERTY(int audioInPan READ audioInPan WRITE setAudioInPan NOTIFY audioInPanChanged FINAL) + Q_PROPERTY(int pingVal READ pingVal NOTIFY pingValChanged FINAL) + Q_PROPERTY(int delayVal READ delayVal NOTIFY delayValChanged FINAL) + Q_PROPERTY(bool jitterWarn READ jitterWarn WRITE setJitterWarn NOTIFY jitterWarnChanged FINAL) + Q_PROPERTY(QString sessionStatus READ sessionStatus NOTIFY sessionStatusChanged FINAL) + Q_PROPERTY(QString sessionName READ sessionName NOTIFY sessionNameChanged FINAL) + Q_PROPERTY(QString recordingStatus READ recordingStatus NOTIFY recordingStatusChanged FINAL) + Q_PROPERTY(bool bConnected READ bConnected NOTIFY bConnectedChanged FINAL) + // for Input LevelMeter L-R + Q_PROPERTY(CLevelMeter* inputMeterL READ inputMeterL CONSTANT) + Q_PROPERTY(CLevelMeter* inputMeterR READ inputMeterR CONSTANT) + // show user messages + Q_PROPERTY(QString userMsg READ userMsg WRITE setUserMsg NOTIFY userMsgChanged FINAL) + public: CClient ( const quint16 iPortNumber, const quint16 iQosNumber, @@ -131,15 +141,76 @@ class CClient : public QObject const bool bNoAutoJackConnect, const QString& strNClientName, const bool bNEnableIPv6, - const bool bNMuteMeInPersonalMix ); + const bool bNMuteMeInPersonalMix, + // additional from clientdlg: + const QString& strIniFileName, + const bool bMuteStream, // maybe + // for settings + const QList bCommandLineOptions + ); virtual ~CClient(); + // QML accessors ----------- + int audioInPan() const { return iAudioInFader; } + void setAudioInPan ( const int iNV ) { iAudioInFader = iNV; emit audioInPanChanged(); } + + int pingVal() { return m_pingVal; } + + int delayVal() { return m_delayVal; } + + bool jitterWarn(); + void setJitterWarn( bool warn ); + + QString sessionStatus() { return m_sessionStatus; } + void setSessionStatus( QString strName ); + // { if (IsRunning()) + // return "CONNECTED"; + // return "NO SESSION"; + // } + + QString sessionName() { return strSelectedAddress; } + + QString recordingStatus() + { + if (eRecorderState == 3) + return "ON"; + return "OFF"; + } + + QString sessionlinkText(); + void setSessionlinkText( const QString& strSelectedAddress ); + + bool muteOut() { return bMuteOutStream; } + void setMuteOut ( bool value ) + { + bMuteOutStream = value; + } + + CLevelMeter* inputMeterL() const { return m_inputMeterL; } + CLevelMeter* inputMeterR() const { return m_inputMeterR; } + + QString userMsg() const; + void setUserMsg( const QString &userMessage ); + + // -- QML ---- + + // things + CChannelCoreInfo ChannelInfo; + QString strClientName; + QString strIniFileName; + CClientSettings pSettings; + CAudioMixerBoard audioMixerBoard; + CChatBox chatBox; + QList CommandLineOptions; // for settings + // ---- + void Start(); void Stop(); bool IsRunning() { return Sound.IsRunning(); } bool IsCallbackEntered() const { return Sound.IsCallbackEntered(); } bool SetServerAddr ( QString strNAddr ); + void SetServerStatus ( const QString& strNewServerName ); double GetLevelForMeterdBLeft() { return SignalLevelMeter.GetLevelForMeterdBLeftOrMono(); } double GetLevelForMeterdBRight() { return SignalLevelMeter.GetLevelForMeterdBRight(); } @@ -148,30 +219,13 @@ class CClient : public QObject bool IsConnected() { return Channel.IsConnected(); } - EGUIDesign GetGUIDesign() const { return eGUIDesign; } - void SetGUIDesign ( const EGUIDesign eNGD ) { eGUIDesign = eNGD; } - - EMeterStyle GetMeterStyle() const { return eMeterStyle; } - void SetMeterStyle ( const EMeterStyle eNMT ) { eMeterStyle = eNMT; } - EAudioQuality GetAudioQuality() const { return eAudioQuality; } void SetAudioQuality ( const EAudioQuality eNAudioQuality ); EAudChanConf GetAudioChannels() const { return eAudioChannelConf; } void SetAudioChannels ( const EAudChanConf eNAudChanConf ); - int GetAudioInFader() const { return iAudioInFader; } - void SetAudioInFader ( const int iNV ) { iAudioInFader = iNV; } - - int GetReverbLevel() const { return iReverbLevel; } - void SetReverbLevel ( const int iNL ) { iReverbLevel = iNL; } - - bool IsReverbOnLeftChan() const { return bReverbOnLeftChan; } - void SetReverbOnLeftChan ( const bool bIL ) - { - bReverbOnLeftChan = bIL; - AudioReverb.Clear(); - } + bool bConnected() { return IsRunning(); } void SetDoAutoSockBufSize ( const bool bValue ); bool GetDoAutoSockBufSize() const { return Channel.GetDoAutoSockBufSize(); } @@ -256,9 +310,10 @@ class CClient : public QObject void SetMuteOutStream ( const bool bDoMute ) { bMuteOutStream = bDoMute; } void SetRemoteChanGain ( const int iId, const float fGain, const bool bIsMyOwnFader ); - void SetRemoteChanPan ( const int iId, const float fPan ); - void OnTimerRemoteChanGainOrPan(); - void StartTimerGainOrPan(); + void OnTimerRemoteChanGain(); + void StartDelayTimer(); + + void SetRemoteChanPan ( const int iId, const float fPan ) { Channel.SetRemoteChanPan ( iId, fPan ); } void SetInputBoost ( const int iNewBoost ) { iInputBoost = iNewBoost; } @@ -286,9 +341,6 @@ class CClient : public QObject Channel.GetBufErrorRates ( vecErrRates, dLimit, dMaxUpLimit ); } - // settings - CChannelCoreInfo ChannelInfo; - QString strClientName; protected: // callback function must be static, otherwise it does not work @@ -302,27 +354,10 @@ class CClient : public QObject int EvaluatePingMessage ( const int iMs ); void CreateServerJitterBufferMessage(); - void ClearClientChannels(); - void FreeClientChannel ( const int iServerChannelID ); - int FindClientChannel ( const int iServerChannelID, const bool bCreateIfNew ); // returns a client channel ID or INVALID_INDEX - bool ReorderLevelList ( CVector& vecLevelList ); // modifies vecLevelList, passed by reference - // only one channel is needed for client application CChannel Channel; CProtocol ConnLessProtocol; - // client channels, indexed by client channel ID, - // containing server channel ID (INVALID_INDEX if free) - CClientChannel clientChannels[MAX_NUM_CHANNELS]; - - // client channel IDs, indexed by server channel ID - // unused channels will contain INVALID_INDEX - int clientChannelIDs[MAX_NUM_CHANNELS]; - - int iActiveChannels; // number of active channels - int iJoinSequence; // order of joining of session participants - QMutex MutexChannels; - // audio encoder/decoder OpusCustomMode* Opus64Mode; OpusCustomEncoder* Opus64EncoderMono; @@ -354,9 +389,6 @@ class CClient : public QObject CVector vecbyNetwData; int iAudioInFader; - bool bReverbOnLeftChan; - int iReverbLevel; - CAudioReverb AudioReverb; int iInputBoost; int iSndCrdPrefFrameSizeFactor; @@ -377,8 +409,6 @@ class CClient : public QObject int iMonoBlockSizeSam; int iStereoBlockSizeSam; - EGUIDesign eGUIDesign; - EMeterStyle eMeterStyle; bool bEnableAudioAlerts; bool bEnableOPUS64; @@ -393,15 +423,58 @@ class CClient : public QObject // for ping measurement QElapsedTimer PreciseTime; - // for gain or pan rate limiting - QMutex MutexGainOrPan; - QTimer TimerGainOrPan; - int minGainOrPanId; - int maxGainOrPanId; + // for gain rate limiting + QMutex MutexGain; + QTimer TimerGain; + int minGainId; + int maxGainId; + float oldGain[MAX_NUM_CHANNELS]; + float newGain[MAX_NUM_CHANNELS]; int iCurPingTime; CSignalHandler* pSignalHandler; + // for join / connect + QString strSelectedAddress; + void Connect( const QString& strAddress ); + void Disconnect(); + QString strServerName; + + // timers + QTimer TimerSigMet; + QTimer TimerBuffersLED; + QTimer TimerStatus; + QTimer TimerPing; + QTimer TimerCheckAudioDeviceOk; + QTimer TimerDetectFeedback; + + void SetPingTime ( const int iPingTime, const int iOverallDelayMs ); + + int iClients; + // bool bConnected; + bool bConnectDlgWasShown; + bool bMIDICtrlUsed; + bool bDetectFeedback; + ERecorderState eLastRecorderState; + ERecorderState eRecorderState; + + virtual void closeEvent ( QCloseEvent* Event ); + + // QML props ................. + // object refs for input meter levels + CLevelMeter* m_inputMeterL; + CLevelMeter* m_inputMeterR; + // for ping/delay stat display + int m_pingVal; + int m_delayVal; + // for user messages + QString m_userMsg; + // handle jitterWarn state + bool m_jitterWarn = false; + // session status + QString m_sessionStatus = "NO SESSION"; + + protected slots: void OnHandledSignal ( int sigNum ); void OnSendProtMessage ( CVector vecMessage ); @@ -432,11 +505,63 @@ protected slots: void OnControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo ); void OnControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ); void OnControllerInMuteMyself ( bool bMute ); - void OnClientIDReceived ( int iServerChanID ); - void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted ); - void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); + void OnClientIDReceived ( int iChanID ); void OnConClientListMesReceived ( CVector vecChanInfo ); + +public slots: + // new client stuff + void onConnectButtonClicked(); + void onDisconnectButtonClicked(); + void OnTimerSigMet(); + void OnTimerBuffersLED(); + void OnTimerCheckAudioDeviceOk(); + void OnTimerDetectFeedback(); + void OnTimerStatus() { ; } // redundant ? + void OnTimerPing(); + void OnPingTimeResult ( int iPingTime ); + void OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVersion ); + void OnCLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString strVersion ); + void OnChatTextReceived ( QString strChatText ); + void OnLoadChannelSetup(); + void OnSaveChannelSetup(); + + void OnOwnFaderFirst() + { + pSettings.bOwnFaderFirst = !pSettings.bOwnFaderFirst; + audioMixerBoard.SetFaderSorting ( pSettings.eChannelSortType ); + } + void OnNoSortChannels() { audioMixerBoard.SetFaderSorting ( ST_NO_SORT ); } + void OnSortChannelsByName() { audioMixerBoard.SetFaderSorting ( ST_BY_NAME ); } + void OnSortChannelsByGroupID() { audioMixerBoard.SetFaderSorting ( ST_BY_GROUPID ); } + void OnClearAllStoredSoloMuteSettings(); + void OnSetAllFadersToNewClientLevel() { audioMixerBoard.SetAllFaderLevelsToNewClientLevel(); } + void OnAutoAdjustAllFaderLevels() { audioMixerBoard.AutoAdjustAllFaderLevels(); } + void OnNumMixerPanelRowsChanged ( int value ) { audioMixerBoard.SetNumMixerPanelRows ( value ); } + void OnNewLocalInputText ( QString strChatText ) { CreateChatTextMes ( strChatText ); } + + void OnSoundDeviceChanged ( QString strError ); + + void OnChangeChanGain ( int iId, float fGain, bool bIsMyOwnFader ) { SetRemoteChanGain ( iId, fGain, bIsMyOwnFader ); } + + void OnChangeChanPan ( int iId, float fPan ) { SetRemoteChanPan ( iId, fPan ); } + + void OnMuteStateHasChangedReceived ( int iChanID, bool bIsMuted ) + { + audioMixerBoard.SetRemoteFaderIsMute ( iChanID, bIsMuted ); + } + + void OnCLChannelLevelListReceived ( CHostAddress /* unused */, CVector vecLevelList ) + { + audioMixerBoard.SetChannelLevels ( vecLevelList ); + } + + void OnDisconnected() { Disconnect(); } + void OnRecorderStateReceived ( ERecorderState eRecorderState ); + void OnNumClientsChanged ( int iNewNumClients ); + // void accept() { close(); } // introduced by pljones + + signals: void ConClientListMesReceived ( CVector vecChanInfo ); void ChatTextReceived ( QString strChatText ); @@ -448,15 +573,10 @@ protected slots: void RecorderStateReceived ( ERecorderState eRecorderState ); void CLServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ); - void CLRedServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ); - void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ); - void CLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients ); - void CLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType eOSType, QString strVersion ); - void CLChannelLevelListReceived ( CHostAddress InetAddr, CVector vecLevelList ); void Disconnected(); @@ -466,4 +586,19 @@ protected slots: void ControllerInFaderIsSolo ( int iChannelIdx, bool bIsSolo ); void ControllerInFaderIsMute ( int iChannelIdx, bool bIsMute ); void ControllerInMuteMyself ( bool bMute ); + + // QML signals + void sessionlinkTextChanged(); + void muteOutChanged(); + void audioInPanChanged(); + void pingValChanged(); + void delayValChanged(); + void jitterWarnChanged(); + void inputLevelLChanged(); + void inputLevelRChanged(); + void sessionStatusChanged(); + void sessionNameChanged(); + void recordingStatusChanged(); + void bConnectedChanged(); + void userMsgChanged(); }; diff --git a/src/clientdlg.cpp b/src/clientdlg.cpp deleted file mode 100644 index cbfa04518d..0000000000 --- a/src/clientdlg.cpp +++ /dev/null @@ -1,1518 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#include "clientdlg.h" - -/* Implementation *************************************************************/ -CClientDlg::CClientDlg ( CClient* pNCliP, - CClientSettings* pNSetP, - const QString& strConnOnStartupAddress, - const QString& strMIDISetup, - const bool bNewShowComplRegConnList, - const bool bShowAnalyzerConsole, - const bool bMuteStream, - const bool bNEnableIPv6, - QWidget* parent ) : - CBaseDlg ( parent, Qt::Window ), // use Qt::Window to get min/max window buttons - pClient ( pNCliP ), - pSettings ( pNSetP ), - bConnectDlgWasShown ( false ), - bDetectFeedback ( false ), - bEnableIPv6 ( bNEnableIPv6 ), - eLastRecorderState ( RS_UNDEFINED ), // for SetMixerBoardDeco - eLastDesign ( GD_ORIGINAL ), // " - ClientSettingsDlg ( pNCliP, pNSetP, parent ), - ChatDlg ( parent ), - ConnectDlg ( pNSetP, bNewShowComplRegConnList, parent ), - AnalyzerConsole ( pNCliP, parent ) -{ - setupUi ( this ); - - // Add help text to controls ----------------------------------------------- - // input level meter - QString strInpLevH = "" + tr ( "Input Level Meter" ) + ": " + - tr ( "This shows " - "the level of the two stereo channels " - "for your audio input." ) + - "
" + - tr ( "Make sure not to clip the input signal to avoid distortions of the " - "audio signal." ); - - QString strInpLevHTT = tr ( "If the application " - "is connected to a server and " - "you play your instrument/sing into the microphone, the VU " - "meter should flicker. If this is not the case, you have " - "probably selected the wrong input channel (e.g. 'line in' instead " - "of the microphone input) or set the input gain too low in the " - "(Windows) audio mixer." ) + - "
" + - tr ( "For proper usage of the " - "application, you should not hear your singing/instrument through " - "the loudspeaker or your headphone when the software is not connected. " - "This can be achieved by muting your input audio channel in the " - "Playback mixer (not the Recording mixer!)." ) + - TOOLTIP_COM_END_TEXT; - - QString strInpLevHAccText = tr ( "Input level meter" ); - QString strInpLevHAccDescr = tr ( "Simulates an analog LED level meter." ); - - lblInputLEDMeter->setWhatsThis ( strInpLevH ); - lblLevelMeterLeft->setWhatsThis ( strInpLevH ); - lblLevelMeterRight->setWhatsThis ( strInpLevH ); - lbrInputLevelL->setWhatsThis ( strInpLevH ); - lbrInputLevelL->setAccessibleName ( strInpLevHAccText ); - lbrInputLevelL->setAccessibleDescription ( strInpLevHAccDescr ); - lbrInputLevelL->setToolTip ( strInpLevHTT ); - lbrInputLevelL->setEnabled ( false ); - lbrInputLevelR->setWhatsThis ( strInpLevH ); - lbrInputLevelR->setAccessibleName ( strInpLevHAccText ); - lbrInputLevelR->setAccessibleDescription ( strInpLevHAccDescr ); - lbrInputLevelR->setToolTip ( strInpLevHTT ); - lbrInputLevelR->setEnabled ( false ); - - // connect/disconnect button - butConnect->setWhatsThis ( "" + tr ( "Connect/Disconnect Button" ) + ": " + - tr ( "Opens a dialog where you can select a server to connect to. " - "If you are connected, pressing this button will end the session." ) ); - - butConnect->setAccessibleName ( tr ( "Connect and disconnect toggle button" ) ); - - // reverberation level - QString strAudReverb = "" + tr ( "Reverb effect" ) + ": " + - tr ( "Reverb can be applied to one local mono audio channel or to both " - "channels in stereo mode. The mono channel selection and the " - "reverb level can be modified. For example, if " - "a microphone signal is fed in to the right audio channel of the " - "sound card and a reverb effect needs to be applied, set the " - "channel selector to right and move the fader upwards until the " - "desired reverb level is reached." ); - - lblAudioReverb->setWhatsThis ( strAudReverb ); - sldAudioReverb->setWhatsThis ( strAudReverb ); - - sldAudioReverb->setAccessibleName ( tr ( "Reverb effect level setting" ) ); - - // reverberation channel selection - QString strRevChanSel = "" + tr ( "Reverb Channel Selection" ) + ": " + - tr ( "With these radio buttons the audio input channel on which the " - "reverb effect is applied can be chosen. Either the left " - "or right input channel can be selected." ); - - rbtReverbSelL->setWhatsThis ( strRevChanSel ); - rbtReverbSelL->setAccessibleName ( tr ( "Left channel selection for reverb" ) ); - rbtReverbSelR->setWhatsThis ( strRevChanSel ); - rbtReverbSelR->setAccessibleName ( tr ( "Right channel selection for reverb" ) ); - - // delay LED - QString strLEDDelay = "" + tr ( "Delay Status LED" ) + ": " + tr ( "Shows the current audio delay status:" ) + - "
    " - "
  • " - "" + - tr ( "Green" ) + ": " + - tr ( "The delay is perfect for a jam " - "session." ) + - "
  • " - "
  • " - "" + - tr ( "Yellow" ) + ": " + - tr ( "A session is still possible " - "but it may be harder to play." ) + - "
  • " - "
  • " - "" + - tr ( "Red" ) + ": " + - tr ( "The delay is too large for " - "jamming." ) + - "
  • " - "
"; - - lblDelay->setWhatsThis ( strLEDDelay ); - ledDelay->setWhatsThis ( strLEDDelay ); - ledDelay->setToolTip ( tr ( "If this LED indicator turns red, " - "you will not have much fun using %1." ) - .arg ( APP_NAME ) + - TOOLTIP_COM_END_TEXT ); - - ledDelay->setAccessibleName ( tr ( "Delay status LED indicator" ) ); - - // buffers LED - QString strLEDBuffers = "" + tr ( "Local Jitter Buffer Status LED" ) + ": " + - tr ( "The local jitter buffer status LED shows the current audio/streaming " - "status. If the light is red, the audio stream is interrupted. " - "This is caused by one of the following problems:" ) + - "
    " - "
  • " + - tr ( "The network jitter buffer is not large enough for the current " - "network/audio interface jitter." ) + - "
  • " - "
  • " + - tr ( "The sound card's buffer delay (buffer size) is too small " - "(see Settings window)." ) + - "
  • " - "
  • " + - tr ( "The upload or download stream rate is too high for your " - "internet bandwidth." ) + - "
  • " - "
  • " + - tr ( "The CPU of the client or server is at 100%." ) + - "
  • " - "
"; - - lblBuffers->setWhatsThis ( strLEDBuffers ); - ledBuffers->setWhatsThis ( strLEDBuffers ); - ledBuffers->setToolTip ( tr ( "If this LED indicator turns red, " - "the audio stream is interrupted." ) + - TOOLTIP_COM_END_TEXT ); - - ledBuffers->setAccessibleName ( tr ( "Local Jitter Buffer status LED indicator" ) ); - - // current connection status details - QString strConnStats = "" + tr ( "Current Connection Status" ) + ": " + - tr ( "The Ping Time is the time required for the audio " - "stream to travel from the client to the server and back again. This " - "delay is introduced by the network and should be about " - "20-30 ms. If this delay is higher than about 50 ms, your distance to " - "the server is too large or your internet connection is not " - "sufficient." ) + - "
" + - tr ( "Overall Delay is calculated from the current Ping Time and the " - "delay introduced by the current buffer settings." ); - - lblPing->setWhatsThis ( strConnStats ); - lblPingVal->setWhatsThis ( strConnStats ); - lblDelay->setWhatsThis ( strConnStats ); - lblDelayVal->setWhatsThis ( strConnStats ); - lblPingVal->setText ( "---" ); - lblPingUnit->setText ( "" ); - lblDelayVal->setText ( "---" ); - lblDelayUnit->setText ( "" ); - - // init GUI design - SetGUIDesign ( pClient->GetGUIDesign() ); - - // MeterStyle init - SetMeterStyle ( pClient->GetMeterStyle() ); - - // set the settings pointer to the mixer board (must be done early) - MainMixerBoard->SetSettingsPointer ( pSettings ); - MainMixerBoard->SetNumMixerPanelRows ( pSettings->iNumMixerPanelRows ); - - // Pass through flag for MIDICtrlUsed - MainMixerBoard->SetMIDICtrlUsed ( !strMIDISetup.isEmpty() ); - - // reset mixer board - MainMixerBoard->HideAll(); - - // init status label - OnTimerStatus(); - - // init connection button text - butConnect->setText ( tr ( "C&onnect" ) ); - - // init input level meter bars - lbrInputLevelL->SetValue ( 0 ); - lbrInputLevelR->SetValue ( 0 ); - - // init status LEDs - ledBuffers->Reset(); - ledDelay->Reset(); - - // init audio reverberation - sldAudioReverb->setRange ( 0, AUD_REVERB_MAX ); - const int iCurAudReverb = pClient->GetReverbLevel(); - sldAudioReverb->setValue ( iCurAudReverb ); - sldAudioReverb->setTickInterval ( AUD_REVERB_MAX / 5 ); - - // init input boost - pClient->SetInputBoost ( pSettings->iInputBoost ); - - // init reverb channel - UpdateRevSelection(); - - // init connect dialog - ConnectDlg.SetShowAllMusicians ( pSettings->bConnectDlgShowAllMusicians ); - - // set window title (with no clients connected -> "0") - SetMyWindowTitle ( 0 ); - - // track number of clients to detect joins/leaves for audio alerts - iClients = 0; - - // prepare Mute Myself info label (invisible by default) - lblGlobalInfoLabel->setStyleSheet ( ".QLabel { background: red; }" ); - lblGlobalInfoLabel->hide(); - - // prepare update check info label (invisible by default) - lblUpdateCheck->setOpenExternalLinks ( true ); // enables opening a web browser if one clicks on a html link - lblUpdateCheck->setText ( "" + APP_UPGRADE_AVAILABLE_MSG_TEXT.arg ( APP_NAME ).arg ( VERSION ) + "" ); - lblUpdateCheck->hide(); - - // setup timers - TimerCheckAudioDeviceOk.setSingleShot ( true ); // only check once after connection - TimerDetectFeedback.setSingleShot ( true ); - - // Connect on startup ------------------------------------------------------ - if ( !strConnOnStartupAddress.isEmpty() ) - { - // initiate connection (always show the address in the mixer board - // (no alias)) - Connect ( strConnOnStartupAddress, strConnOnStartupAddress ); - } - - // File menu -------------------------------------------------------------- - QMenu* pFileMenu = new QMenu ( tr ( "&File" ), this ); - - pFileMenu->addAction ( tr ( "&Connection Setup..." ), this, SLOT ( OnOpenConnectionSetupDialog() ), QKeySequence ( Qt::CTRL + Qt::Key_C ) ); - - pFileMenu->addSeparator(); - - pFileMenu->addAction ( tr ( "&Load Mixer Channels Setup..." ), this, SLOT ( OnLoadChannelSetup() ) ); - - pFileMenu->addAction ( tr ( "&Save Mixer Channels Setup..." ), this, SLOT ( OnSaveChannelSetup() ) ); - - pFileMenu->addSeparator(); - - pFileMenu->addAction ( tr ( "E&xit" ), this, SLOT ( close() ), QKeySequence ( Qt::CTRL + Qt::Key_Q ) ); - - // Edit menu -------------------------------------------------------------- - QMenu* pEditMenu = new QMenu ( tr ( "&Edit" ), this ); - - pEditMenu->addAction ( tr ( "Clear &All Stored Solo and Mute Settings" ), this, SLOT ( OnClearAllStoredSoloMuteSettings() ) ); - - pEditMenu->addAction ( tr ( "Set All Faders to New Client &Level" ), - this, - SLOT ( OnSetAllFadersToNewClientLevel() ), - QKeySequence ( Qt::CTRL + Qt::Key_L ) ); - - pEditMenu->addAction ( tr ( "Auto-Adjust all &Faders" ), this, SLOT ( OnAutoAdjustAllFaderLevels() ), QKeySequence ( Qt::CTRL + Qt::Key_F ) ); - - // View menu -------------------------------------------------------------- - QMenu* pViewMenu = new QMenu ( tr ( "&View" ), this ); - - // own fader first option: works from server version 3.5.5 which supports sending client ID back to client - QAction* OwnFaderFirstAction = - pViewMenu->addAction ( tr ( "O&wn Fader First" ), this, SLOT ( OnOwnFaderFirst() ), QKeySequence ( Qt::CTRL + Qt::Key_W ) ); - - pViewMenu->addSeparator(); - - QAction* NoSortAction = - pViewMenu->addAction ( tr ( "N&o User Sorting" ), this, SLOT ( OnNoSortChannels() ), QKeySequence ( Qt::CTRL + Qt::Key_O ) ); - - QAction* ByNameAction = - pViewMenu->addAction ( tr ( "Sort Users by &Name" ), this, SLOT ( OnSortChannelsByName() ), QKeySequence ( Qt::CTRL + Qt::Key_N ) ); - - QAction* ByInstrAction = pViewMenu->addAction ( tr ( "Sort Users by &Instrument" ), - this, - SLOT ( OnSortChannelsByInstrument() ), - QKeySequence ( Qt::CTRL + Qt::Key_I ) ); - - QAction* ByGroupAction = - pViewMenu->addAction ( tr ( "Sort Users by &Group" ), this, SLOT ( OnSortChannelsByGroupID() ), QKeySequence ( Qt::CTRL + Qt::Key_G ) ); - - QAction* ByCityAction = - pViewMenu->addAction ( tr ( "Sort Users by &City" ), this, SLOT ( OnSortChannelsByCity() ), QKeySequence ( Qt::CTRL + Qt::Key_T ) ); - - QAction* ByChannelAction = - pViewMenu->addAction ( tr ( "Sort Users by Chann&el" ), this, SLOT ( OnSortChannelsByChannel() ), QKeySequence ( Qt::CTRL + Qt::Key_E ) ); - - OwnFaderFirstAction->setCheckable ( true ); - OwnFaderFirstAction->setChecked ( pSettings->bOwnFaderFirst ); - - // the sorting menu entries shall be checkable and exclusive - QActionGroup* SortActionGroup = new QActionGroup ( this ); - SortActionGroup->setExclusive ( true ); - NoSortAction->setCheckable ( true ); - SortActionGroup->addAction ( NoSortAction ); - ByNameAction->setCheckable ( true ); - SortActionGroup->addAction ( ByNameAction ); - ByInstrAction->setCheckable ( true ); - SortActionGroup->addAction ( ByInstrAction ); - ByGroupAction->setCheckable ( true ); - SortActionGroup->addAction ( ByGroupAction ); - ByCityAction->setCheckable ( true ); - SortActionGroup->addAction ( ByCityAction ); - ByChannelAction->setCheckable ( true ); - SortActionGroup->addAction ( ByChannelAction ); - - // initialize sort type setting (i.e., recover stored setting) - switch ( pSettings->eChannelSortType ) - { - case ST_BY_NAME: - ByNameAction->setChecked ( true ); - break; - case ST_BY_INSTRUMENT: - ByInstrAction->setChecked ( true ); - break; - case ST_BY_GROUPID: - ByGroupAction->setChecked ( true ); - break; - case ST_BY_CITY: - ByCityAction->setChecked ( true ); - break; - case ST_BY_SERVER_CHANNEL: - ByChannelAction->setChecked ( true ); - break; - default: // ST_NO_SORT - NoSortAction->setChecked ( true ); - break; - } - MainMixerBoard->SetFaderSorting ( pSettings->eChannelSortType ); - - pViewMenu->addSeparator(); - - pViewMenu->addAction ( tr ( "C&hat..." ), this, SLOT ( OnOpenChatDialog() ), QKeySequence ( Qt::CTRL + Qt::Key_H ) ); - - // optionally show analyzer console entry - if ( bShowAnalyzerConsole ) - { - pViewMenu->addAction ( tr ( "&Analyzer Console..." ), this, SLOT ( OnOpenAnalyzerConsole() ) ); - } - - pViewMenu->addSeparator(); - - // Settings menu -------------------------------------------------------------- - QMenu* pSettingsMenu = new QMenu ( tr ( "Sett&ings" ), this ); - - pSettingsMenu->addAction ( tr ( "My &Profile..." ), this, SLOT ( OnOpenUserProfileSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_P ) ); - - pSettingsMenu->addAction ( tr ( "Audio/Network &Settings..." ), this, SLOT ( OnOpenAudioNetSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_S ) ); - - pSettingsMenu->addAction ( tr ( "A&dvanced Settings..." ), this, SLOT ( OnOpenAdvancedSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_D ) ); - - // Main menu bar ----------------------------------------------------------- - QMenuBar* pMenu = new QMenuBar ( this ); - - pMenu->addMenu ( pFileMenu ); - pMenu->addMenu ( pEditMenu ); - pMenu->addMenu ( pViewMenu ); - pMenu->addMenu ( pSettingsMenu ); - pMenu->addMenu ( new CHelpMenu ( true, this ) ); - - // Now tell the layout about the menu - layout()->setMenuBar ( pMenu ); - - // Window positions -------------------------------------------------------- - // main window - if ( !pSettings->vecWindowPosMain.isEmpty() && !pSettings->vecWindowPosMain.isNull() ) - { - restoreGeometry ( pSettings->vecWindowPosMain ); - } - - // settings window - if ( !pSettings->vecWindowPosSettings.isEmpty() && !pSettings->vecWindowPosSettings.isNull() ) - { - ClientSettingsDlg.restoreGeometry ( pSettings->vecWindowPosSettings ); - } - - if ( pSettings->bWindowWasShownSettings ) - { - ShowGeneralSettings ( pSettings->iSettingsTab ); - } - - // chat window - if ( !pSettings->vecWindowPosChat.isEmpty() && !pSettings->vecWindowPosChat.isNull() ) - { - ChatDlg.restoreGeometry ( pSettings->vecWindowPosChat ); - } - - if ( pSettings->bWindowWasShownChat ) - { - ShowChatWindow(); - } - - // connection setup window - if ( !pSettings->vecWindowPosConnect.isEmpty() && !pSettings->vecWindowPosConnect.isNull() ) - { - ConnectDlg.restoreGeometry ( pSettings->vecWindowPosConnect ); - } - - // Connections ------------------------------------------------------------- - // push buttons - QObject::connect ( butConnect, &QPushButton::clicked, this, &CClientDlg::OnConnectDisconBut ); - - // check boxes - QObject::connect ( chbSettings, &QCheckBox::stateChanged, this, &CClientDlg::OnSettingsStateChanged ); - - QObject::connect ( chbChat, &QCheckBox::stateChanged, this, &CClientDlg::OnChatStateChanged ); - - QObject::connect ( chbLocalMute, &QCheckBox::stateChanged, this, &CClientDlg::OnLocalMuteStateChanged ); - - // timers - QObject::connect ( &TimerSigMet, &QTimer::timeout, this, &CClientDlg::OnTimerSigMet ); - - QObject::connect ( &TimerBuffersLED, &QTimer::timeout, this, &CClientDlg::OnTimerBuffersLED ); - - QObject::connect ( &TimerStatus, &QTimer::timeout, this, &CClientDlg::OnTimerStatus ); - - QObject::connect ( &TimerPing, &QTimer::timeout, this, &CClientDlg::OnTimerPing ); - - QObject::connect ( &TimerCheckAudioDeviceOk, &QTimer::timeout, this, &CClientDlg::OnTimerCheckAudioDeviceOk ); - - QObject::connect ( &TimerDetectFeedback, &QTimer::timeout, this, &CClientDlg::OnTimerDetectFeedback ); - - QObject::connect ( sldAudioReverb, &QSlider::valueChanged, this, &CClientDlg::OnAudioReverbValueChanged ); - - // radio buttons - QObject::connect ( rbtReverbSelL, &QRadioButton::clicked, this, &CClientDlg::OnReverbSelLClicked ); - - QObject::connect ( rbtReverbSelR, &QRadioButton::clicked, this, &CClientDlg::OnReverbSelRClicked ); - - // other - QObject::connect ( pClient, &CClient::ConClientListMesReceived, this, &CClientDlg::OnConClientListMesReceived ); - - QObject::connect ( pClient, &CClient::Disconnected, this, &CClientDlg::OnDisconnected ); - - QObject::connect ( pClient, &CClient::ChatTextReceived, this, &CClientDlg::OnChatTextReceived ); - - QObject::connect ( pClient, &CClient::ClientIDReceived, this, &CClientDlg::OnClientIDReceived ); - - QObject::connect ( pClient, &CClient::MuteStateHasChangedReceived, this, &CClientDlg::OnMuteStateHasChangedReceived ); - - QObject::connect ( pClient, &CClient::RecorderStateReceived, this, &CClientDlg::OnRecorderStateReceived ); - - // This connection is a special case. On receiving a licence required message via the - // protocol, a modal licence dialog is opened. Since this blocks the thread, we need - // a queued connection to make sure the core protocol mechanism is not blocked, too. - qRegisterMetaType ( "ELicenceType" ); - QObject::connect ( pClient, &CClient::LicenceRequired, this, &CClientDlg::OnLicenceRequired, Qt::QueuedConnection ); - - QObject::connect ( pClient, &CClient::PingTimeReceived, this, &CClientDlg::OnPingTimeResult ); - - QObject::connect ( pClient, &CClient::CLServerListReceived, this, &CClientDlg::OnCLServerListReceived ); - - QObject::connect ( pClient, &CClient::CLRedServerListReceived, this, &CClientDlg::OnCLRedServerListReceived ); - - QObject::connect ( pClient, &CClient::CLConnClientsListMesReceived, this, &CClientDlg::OnCLConnClientsListMesReceived ); - - QObject::connect ( pClient, &CClient::CLPingTimeWithNumClientsReceived, this, &CClientDlg::OnCLPingTimeWithNumClientsReceived ); - - QObject::connect ( pClient, &CClient::ControllerInFaderLevel, this, &CClientDlg::OnControllerInFaderLevel ); - - QObject::connect ( pClient, &CClient::ControllerInPanValue, this, &CClientDlg::OnControllerInPanValue ); - - QObject::connect ( pClient, &CClient::ControllerInFaderIsSolo, this, &CClientDlg::OnControllerInFaderIsSolo ); - - QObject::connect ( pClient, &CClient::ControllerInFaderIsMute, this, &CClientDlg::OnControllerInFaderIsMute ); - - QObject::connect ( pClient, &CClient::ControllerInMuteMyself, this, &CClientDlg::OnControllerInMuteMyself ); - - QObject::connect ( pClient, &CClient::CLChannelLevelListReceived, this, &CClientDlg::OnCLChannelLevelListReceived ); - - QObject::connect ( pClient, &CClient::VersionAndOSReceived, this, &CClientDlg::OnVersionAndOSReceived ); - - QObject::connect ( pClient, &CClient::CLVersionAndOSReceived, this, &CClientDlg::OnCLVersionAndOSReceived ); - - QObject::connect ( pClient, &CClient::SoundDeviceChanged, this, &CClientDlg::OnSoundDeviceChanged ); - - QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::GUIDesignChanged, this, &CClientDlg::OnGUIDesignChanged ); - - QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::MeterStyleChanged, this, &CClientDlg::OnMeterStyleChanged ); - - QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::AudioChannelsChanged, this, &CClientDlg::OnAudioChannelsChanged ); - - QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::CustomDirectoriesChanged, &ConnectDlg, &CConnectDlg::OnCustomDirectoriesChanged ); - - QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::NumMixerPanelRowsChanged, this, &CClientDlg::OnNumMixerPanelRowsChanged ); - - QObject::connect ( this, &CClientDlg::SendTabChange, &ClientSettingsDlg, &CClientSettingsDlg::OnMakeTabChange ); - - QObject::connect ( MainMixerBoard, &CAudioMixerBoard::ChangeChanGain, this, &CClientDlg::OnChangeChanGain ); - - QObject::connect ( MainMixerBoard, &CAudioMixerBoard::ChangeChanPan, this, &CClientDlg::OnChangeChanPan ); - - QObject::connect ( MainMixerBoard, &CAudioMixerBoard::NumClientsChanged, this, &CClientDlg::OnNumClientsChanged ); - - QObject::connect ( &ChatDlg, &CChatDlg::NewLocalInputText, this, &CClientDlg::OnNewLocalInputText ); - - QObject::connect ( &ConnectDlg, &CConnectDlg::ReqServerListQuery, this, &CClientDlg::OnReqServerListQuery ); - - // note that this connection must be a queued connection, otherwise the server list ping - // times are not accurate and the client list may not be retrieved for all servers listed - // (it seems the sendto() function needs to be called from different threads to fire the - // packet immediately and do not collect packets before transmitting) - QObject::connect ( &ConnectDlg, &CConnectDlg::CreateCLServerListPingMes, this, &CClientDlg::OnCreateCLServerListPingMes, Qt::QueuedConnection ); - - QObject::connect ( &ConnectDlg, &CConnectDlg::CreateCLServerListReqVerAndOSMes, this, &CClientDlg::OnCreateCLServerListReqVerAndOSMes ); - - QObject::connect ( &ConnectDlg, - &CConnectDlg::CreateCLServerListReqConnClientsListMes, - this, - &CClientDlg::OnCreateCLServerListReqConnClientsListMes ); - - QObject::connect ( &ConnectDlg, &CConnectDlg::accepted, this, &CClientDlg::OnConnectDlgAccepted ); - - // Initializations which have to be done after the signals are connected --- - // start timer for status bar - TimerStatus.start ( LED_BAR_UPDATE_TIME_MS ); - - // restore connect dialog - if ( pSettings->bWindowWasShownConnect ) - { - ShowConnectionSetupDialog(); - } - - // mute stream on startup (must be done after the signal connections) - if ( bMuteStream ) - { - chbLocalMute->setCheckState ( Qt::Checked ); - } - - // query the update server version number needed for update check (note - // that the connection less message respond may not make it back but that - // is not critical since the next time Jamulus is started we have another - // chance and the update check is not time-critical at all) - CHostAddress UpdateServerHostAddress; - - // Send the request to two servers for redundancy if either or both of them - // has a higher release version number, the reply will trigger the notification. - - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) ) - { - pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); - } - - if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) ) - { - pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress ); - } -} - -void CClientDlg::closeEvent ( QCloseEvent* Event ) -{ - // store window positions - pSettings->vecWindowPosMain = saveGeometry(); - pSettings->vecWindowPosSettings = ClientSettingsDlg.saveGeometry(); - pSettings->vecWindowPosChat = ChatDlg.saveGeometry(); - pSettings->vecWindowPosConnect = ConnectDlg.saveGeometry(); - - pSettings->bWindowWasShownSettings = ClientSettingsDlg.isVisible(); - pSettings->bWindowWasShownChat = ChatDlg.isVisible(); - pSettings->bWindowWasShownConnect = ConnectDlg.isVisible(); - - // if settings/connect dialog or chat dialog is open, close it - ClientSettingsDlg.close(); - ChatDlg.close(); - ConnectDlg.close(); - AnalyzerConsole.close(); - - // if connected, terminate connection - if ( pClient->IsRunning() ) - { - pClient->Stop(); - } - - // make sure all current fader settings are applied to the settings struct - MainMixerBoard->StoreAllFaderSettings(); - - pSettings->bConnectDlgShowAllMusicians = ConnectDlg.GetShowAllMusicians(); - pSettings->eChannelSortType = MainMixerBoard->GetFaderSorting(); - pSettings->iNumMixerPanelRows = MainMixerBoard->GetNumMixerPanelRows(); - - // default implementation of this event handler routine - Event->accept(); -} - -void CClientDlg::ManageDragNDrop ( QDropEvent* Event, const bool bCheckAccept ) -{ - // we only want to use drag'n'drop with file URLs - QListIterator UrlIterator ( Event->mimeData()->urls() ); - - while ( UrlIterator.hasNext() ) - { - const QString strFileNameWithPath = UrlIterator.next().toLocalFile(); - - // check all given URLs and if any has the correct suffix - if ( !strFileNameWithPath.isEmpty() && ( QFileInfo ( strFileNameWithPath ).suffix() == MIX_SETTINGS_FILE_SUFFIX ) ) - { - if ( bCheckAccept ) - { - // only accept drops of supports file types - Event->acceptProposedAction(); - } - else - { - // load the first valid settings file and leave the loop - pSettings->LoadFaderSettings ( strFileNameWithPath ); - MainMixerBoard->LoadAllFaderSettings(); - break; - } - } - } -} - -void CClientDlg::UpdateRevSelection() -{ - if ( pClient->GetAudioChannels() == CC_STEREO ) - { - // for stereo make channel selection invisible since - // reverberation effect is always applied to both channels - rbtReverbSelL->setVisible ( false ); - rbtReverbSelR->setVisible ( false ); - } - else - { - // make radio buttons visible - rbtReverbSelL->setVisible ( true ); - rbtReverbSelR->setVisible ( true ); - - // update value - if ( pClient->IsReverbOnLeftChan() ) - { - rbtReverbSelL->setChecked ( true ); - } - else - { - rbtReverbSelR->setChecked ( true ); - } - } - - // update visibility of the pan controls in the audio mixer board (pan is not supported for mono) - MainMixerBoard->SetDisplayPans ( pClient->GetAudioChannels() != CC_MONO ); -} - -void CClientDlg::OnConnectDlgAccepted() -{ - // We had an issue that the accepted signal was emit twice if a list item was double - // clicked in the connect dialog. To avoid this we introduced a flag which makes sure - // we process the accepted signal only once after the dialog was initially shown. - if ( bConnectDlgWasShown ) - { - // get the address from the connect dialog - QString strSelectedAddress = ConnectDlg.GetSelectedAddress(); - - // only store new host address in our data base if the address is - // not empty and it was not a server list item (only the addresses - // typed in manually are stored by definition) - if ( !strSelectedAddress.isEmpty() && !ConnectDlg.GetServerListItemWasChosen() ) - { - // store new address at the top of the list, if the list was already - // full, the last element is thrown out - pSettings->vstrIPAddress.StringFiFoWithCompare ( strSelectedAddress ); - } - - // get name to be set in audio mixer group box title - QString strMixerBoardLabel; - - if ( ConnectDlg.GetServerListItemWasChosen() ) - { - // in case a server in the server list was chosen, - // display the server name of the server list - strMixerBoardLabel = ConnectDlg.GetSelectedServerName(); - } - else - { - // an item of the server address combo box was chosen, - // just show the address string as it was entered by the - // user - strMixerBoardLabel = strSelectedAddress; - - // special case: if the address is empty, we substitute the default - // directory address so that a user who just pressed the connect - // button without selecting an item in the table or manually entered an - // address gets a successful connection - if ( strSelectedAddress.isEmpty() ) - { - strSelectedAddress = DEFAULT_SERVER_ADDRESS; - strMixerBoardLabel = tr ( "%1 Directory" ).arg ( DirectoryTypeToString ( AT_DEFAULT ) ); - } - } - - // first check if we are already connected, if this is the case we have to - // disconnect the old server first - if ( pClient->IsRunning() ) - { - Disconnect(); - } - - // initiate connection - Connect ( strSelectedAddress, strMixerBoardLabel ); - - // reset flag - bConnectDlgWasShown = false; - } -} - -void CClientDlg::OnConnectDisconBut() -{ - // the connect/disconnect button implements a toggle functionality - if ( pClient->IsRunning() ) - { - Disconnect(); - SetMixerBoardDeco ( RS_UNDEFINED, pClient->GetGUIDesign() ); - } - else - { - ShowConnectionSetupDialog(); - } -} - -void CClientDlg::OnClearAllStoredSoloMuteSettings() -{ - // if we are in an active connection, we first have to store all fader settings in - // the settings struct, clear the solo and mute states and then apply the settings again - MainMixerBoard->StoreAllFaderSettings(); - pSettings->vecStoredFaderIsSolo.Reset ( false ); - pSettings->vecStoredFaderIsMute.Reset ( false ); - MainMixerBoard->LoadAllFaderSettings(); -} - -void CClientDlg::OnLoadChannelSetup() -{ - QString strFileName = QFileDialog::getOpenFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX ); - - if ( !strFileName.isEmpty() ) - { - // first update the settings struct and then update the mixer panel - pSettings->LoadFaderSettings ( strFileName ); - MainMixerBoard->LoadAllFaderSettings(); - } -} - -void CClientDlg::OnSaveChannelSetup() -{ - QString strFileName = QFileDialog::getSaveFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX ); - - if ( !strFileName.isEmpty() ) - { - // first store all current fader settings (in case we are in an active connection - // right now) and then save the information in the settings struct in the file - MainMixerBoard->StoreAllFaderSettings(); - pSettings->SaveFaderSettings ( strFileName ); - } -} - -void CClientDlg::OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVersion ) -{ - // check if Pan is supported by the server (minimum version is 3.5.4) -#if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) - if ( QVersionNumber::compare ( QVersionNumber::fromString ( strVersion ), QVersionNumber ( 3, 5, 4 ) ) >= 0 ) - { - MainMixerBoard->SetPanIsSupported(); - } -#endif -} - -void CClientDlg::OnCLVersionAndOSReceived ( CHostAddress InetAddr, COSUtil::EOpSystemType, QString strVersion ) -{ - // display version in connect dialog - ConnectDlg.SetServerVersionResult ( InetAddr, strVersion ); - - // update check -#if ( QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) ) && !defined( DISABLE_VERSION_CHECK ) - int mySuffixIndex; - QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &mySuffixIndex ); - - int serverSuffixIndex; - QVersionNumber serverVersion = QVersionNumber::fromString ( strVersion, &serverSuffixIndex ); - - // only compare if the server version has no suffix (such as dev or beta) - if ( strVersion.size() == serverSuffixIndex && QVersionNumber::compare ( serverVersion, myVersion ) > 0 ) - { - // show the label and hide it after one minute again - lblUpdateCheck->show(); - QTimer::singleShot ( 60000, [this]() { lblUpdateCheck->hide(); } ); - } -#endif -} - -void CClientDlg::OnChatTextReceived ( QString strChatText ) -{ - if ( pSettings->bEnableAudioAlerts ) - { - QSoundEffect* sf = new QSoundEffect(); - sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_message.wav" ) ); - sf->play(); - } - ChatDlg.AddChatText ( strChatText ); - - // Open chat dialog. If a server welcome message is received, we force - // the dialog to be upfront in case a licence text is shown. For all - // other new chat texts we do not want to force the dialog to be upfront - // always when a new message arrives since this is annoying. - ShowChatWindow ( ( strChatText.indexOf ( WELCOME_MESSAGE_PREFIX ) == 0 ) ); - - UpdateDisplay(); -} - -void CClientDlg::OnLicenceRequired ( ELicenceType eLicenceType ) -{ - // right now only the creative common licence is supported - if ( eLicenceType == LT_CREATIVECOMMONS ) - { - CLicenceDlg LicenceDlg; - - // mute the client output stream - pClient->SetMuteOutStream ( true ); - - // Open the licence dialog and check if the licence was accepted. In - // case the dialog is just closed or the decline button was pressed, - // disconnect from that server. - if ( !LicenceDlg.exec() ) - { - Disconnect(); - } - - // unmute the client output stream if local mute button is not pressed - if ( chbLocalMute->checkState() == Qt::Unchecked ) - { - pClient->SetMuteOutStream ( false ); - } - } -} - -void CClientDlg::OnConClientListMesReceived ( CVector vecChanInfo ) -{ - // update mixer board with the additional client infos - MainMixerBoard->ApplyNewConClientList ( vecChanInfo ); -} - -void CClientDlg::OnNumClientsChanged ( int iNewNumClients ) -{ - if ( pSettings->bEnableAudioAlerts && iNewNumClients > iClients ) - { - QSoundEffect* sf = new QSoundEffect(); - sf->setSource ( QUrl::fromLocalFile ( ":sounds/res/sounds/new_user.wav" ) ); - sf->play(); - } - - // iNewNumClients will be zero on the first trigger of this signal handler when connecting to a new server. - // Subsequent triggers will thus sound the alert (if enabled). - iClients = iNewNumClients; - - // update window title - SetMyWindowTitle ( iNewNumClients ); -} - -void CClientDlg::OnOpenAudioNetSettings() { ShowGeneralSettings ( SETTING_TAB_AUDIONET ); } - -void CClientDlg::OnOpenAdvancedSettings() { ShowGeneralSettings ( SETTING_TAB_ADVANCED ); } - -void CClientDlg::OnOpenUserProfileSettings() { ShowGeneralSettings ( SETTING_TAB_USER ); } - -void CClientDlg::SetMyWindowTitle ( const int iNumClients ) -{ - // set the window title (and therefore also the task bar icon text of the OS) - // according to the following specification (#559): - // - users - Jamulus - QString strWinTitle; - const bool bClientNameIsUsed = !pClient->strClientName.isEmpty(); - - if ( bClientNameIsUsed ) - { - // if --clientname is used, the APP_NAME must be the very first word in - // the title, otherwise some user scripts do not work anymore, see #789 - strWinTitle += QString ( APP_NAME ) + " - " + pClient->strClientName + " "; - } - - if ( iNumClients == 0 ) - { - // only application name - if ( !bClientNameIsUsed ) // if --clientname is used, the APP_NAME is the first word in title - { - strWinTitle += QString ( APP_NAME ); - } - } - else - { - strWinTitle += MainMixerBoard->GetServerName(); - - if ( iNumClients == 1 ) - { - strWinTitle += " - 1 " + tr ( "user" ); - } - else if ( iNumClients > 1 ) - { - strWinTitle += " - " + QString::number ( iNumClients ) + " " + tr ( "users" ); - } - - if ( !bClientNameIsUsed ) // if --clientname is used, the APP_NAME is the first word in title - { - strWinTitle += " - " + QString ( APP_NAME ); - } - } - - setWindowTitle ( strWinTitle ); - -#if defined( Q_OS_MACOS ) - // for MacOS only we show the number of connected clients as a - // badge label text if more than one user is connected - if ( iNumClients > 1 ) - { - // show the number of connected clients - SetMacBadgeLabelText ( QString ( "%1" ).arg ( iNumClients ) ); - } - else - { - // clear the text (apply an empty string) - SetMacBadgeLabelText ( "" ); - } -#endif -} - -void CClientDlg::ShowConnectionSetupDialog() -{ - // show connect dialog - bConnectDlgWasShown = true; - ConnectDlg.show(); - ConnectDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Connect" ), pClient->strClientName ) ); - - // make sure dialog is upfront and has focus - ConnectDlg.raise(); - ConnectDlg.activateWindow(); -} - -void CClientDlg::ShowGeneralSettings ( int iTab ) -{ - // open general settings dialog - emit SendTabChange ( iTab ); - ClientSettingsDlg.show(); - ClientSettingsDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Settings" ), pClient->strClientName ) ); - - // make sure dialog is upfront and has focus - ClientSettingsDlg.raise(); - ClientSettingsDlg.activateWindow(); -} - -void CClientDlg::ShowChatWindow ( const bool bForceRaise ) -{ - ChatDlg.show(); - ChatDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Chat" ), pClient->strClientName ) ); - - if ( bForceRaise ) - { - // make sure dialog is upfront and has focus - ChatDlg.showNormal(); - ChatDlg.raise(); - ChatDlg.activateWindow(); - } - - UpdateDisplay(); -} - -void CClientDlg::ShowAnalyzerConsole() -{ - // open analyzer console dialog - AnalyzerConsole.show(); - - // make sure dialog is upfront and has focus - AnalyzerConsole.raise(); - AnalyzerConsole.activateWindow(); -} - -void CClientDlg::OnSettingsStateChanged ( int value ) -{ - if ( value == Qt::Checked ) - { - ShowGeneralSettings ( SETTING_TAB_AUDIONET ); - } - else - { - ClientSettingsDlg.hide(); - } -} - -void CClientDlg::OnChatStateChanged ( int value ) -{ - if ( value == Qt::Checked ) - { - ShowChatWindow(); - } - else - { - ChatDlg.hide(); - } -} - -void CClientDlg::OnLocalMuteStateChanged ( int value ) -{ - pClient->SetMuteOutStream ( value == Qt::Checked ); - - // show/hide info label - if ( value == Qt::Checked ) - { - lblGlobalInfoLabel->show(); - } - else - { - lblGlobalInfoLabel->hide(); - } -} - -void CClientDlg::OnTimerSigMet() -{ - // show current level - lbrInputLevelL->SetValue ( pClient->GetLevelForMeterdBLeft() ); - lbrInputLevelR->SetValue ( pClient->GetLevelForMeterdBRight() ); - - if ( bDetectFeedback && - ( pClient->GetLevelForMeterdBLeft() > NUM_STEPS_LED_BAR - 0.5 || pClient->GetLevelForMeterdBRight() > NUM_STEPS_LED_BAR - 0.5 ) ) - { - // mute locally and mute channel - chbLocalMute->setCheckState ( Qt::Checked ); - MainMixerBoard->MuteMyChannel(); - - // show message box about feedback issue - QCheckBox* chb = new QCheckBox ( tr ( "Enable feedback detection" ) ); - chb->setCheckState ( pSettings->bEnableFeedbackDetection ? Qt::Checked : Qt::Unchecked ); - QMessageBox msgbox; - msgbox.setText ( tr ( "Audio feedback or loud signal detected.\n\n" - "We muted your channel and activated 'Mute Myself'. Please solve " - "the feedback issue first and unmute yourself afterwards." ) ); - msgbox.setIcon ( QMessageBox::Icon::Warning ); - msgbox.addButton ( QMessageBox::Ok ); - msgbox.setDefaultButton ( QMessageBox::Ok ); - msgbox.setCheckBox ( chb ); - - QObject::connect ( chb, &QCheckBox::stateChanged, this, &CClientDlg::OnFeedbackDetectionChanged ); - - msgbox.exec(); - } -} - -void CClientDlg::OnTimerBuffersLED() -{ - CMultiColorLED::ELightColor eCurStatus; - - // get and reset current buffer state and set LED from that flag - if ( pClient->GetAndResetbJitterBufferOKFlag() ) - { - eCurStatus = CMultiColorLED::RL_GREEN; - } - else - { - eCurStatus = CMultiColorLED::RL_RED; - } - - // update the buffer LED and the general settings dialog, too - ledBuffers->SetLight ( eCurStatus ); -} - -void CClientDlg::OnTimerPing() -{ - // send ping message to the server - pClient->CreateCLPingMes(); -} - -void CClientDlg::OnPingTimeResult ( int iPingTime ) -{ - // calculate overall delay - const int iOverallDelayMs = pClient->EstimatedOverallDelay ( iPingTime ); - - // color definition: <= 43 ms green, <= 68 ms yellow, otherwise red - CMultiColorLED::ELightColor eOverallDelayLEDColor; - - if ( iOverallDelayMs <= 43 ) - { - eOverallDelayLEDColor = CMultiColorLED::RL_GREEN; - } - else - { - if ( iOverallDelayMs <= 68 ) - { - eOverallDelayLEDColor = CMultiColorLED::RL_YELLOW; - } - else - { - eOverallDelayLEDColor = CMultiColorLED::RL_RED; - } - } - - // only update delay information on settings dialog if it is visible to - // avoid CPU load on working thread if not necessary - if ( ClientSettingsDlg.isVisible() ) - { - // set ping time result to general settings dialog - ClientSettingsDlg.UpdateUploadRate(); - } - SetPingTime ( iPingTime, iOverallDelayMs, eOverallDelayLEDColor ); - - // update delay LED on the main window - ledDelay->SetLight ( eOverallDelayLEDColor ); -} - -void CClientDlg::OnTimerCheckAudioDeviceOk() -{ - // check if the audio device entered the audio callback after a pre-defined - // timeout to check if a valid device is selected and if we do not have - // fundamental settings errors (in which case the GUI would only show that - // it is trying to connect the server which does not help to solve the problem (#129)) - if ( !pClient->IsCallbackEntered() ) - { - QMessageBox::warning ( this, - APP_NAME, - tr ( "Your sound card is not working correctly. " - "Please open the settings dialog and check the device selection and the driver settings." ) ); - } -} - -void CClientDlg::OnTimerDetectFeedback() { bDetectFeedback = false; } - -void CClientDlg::OnSoundDeviceChanged ( QString strError ) -{ - if ( !strError.isEmpty() ) - { - // the sound device setup has a problem, disconnect any active connection - if ( pClient->IsRunning() ) - { - Disconnect(); - } - - // show the error message of the device setup - QMessageBox::critical ( this, APP_NAME, strError, tr ( "Ok" ), nullptr ); - } - - // if the check audio device timer is running, it must be restarted on a device change - if ( TimerCheckAudioDeviceOk.isActive() ) - { - TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS ); - } - - if ( pSettings->bEnableFeedbackDetection && TimerDetectFeedback.isActive() ) - { - TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS ); - bDetectFeedback = true; - } - - // update the settings dialog - ClientSettingsDlg.UpdateSoundDeviceChannelSelectionFrame(); -} - -void CClientDlg::OnCLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients ) -{ - // update connection dialog server list - ConnectDlg.SetPingTimeAndNumClientsResult ( InetAddr, iPingTime, iNumClients ); -} - -void CClientDlg::Connect ( const QString& strSelectedAddress, const QString& strMixerBoardLabel ) -{ - // set address and check if address is valid - if ( pClient->SetServerAddr ( strSelectedAddress ) ) - { - // try to start client, if error occurred, do not go in - // running state but show error message - try - { - if ( !pClient->IsRunning() ) - { - pClient->Start(); - } - } - - catch ( const CGenErr& generr ) - { - // show error message and return the function - QMessageBox::critical ( this, APP_NAME, generr.GetErrorText(), "Close", nullptr ); - return; - } - - // hide label connect to server - lblConnectToServer->hide(); - lbrInputLevelL->setEnabled ( true ); - lbrInputLevelR->setEnabled ( true ); - - // change connect button text to "disconnect" - butConnect->setText ( tr ( "&Disconnect" ) ); - - // set server name in audio mixer group box title - MainMixerBoard->SetServerName ( strMixerBoardLabel ); - - // start timer for level meter bar and ping time measurement - TimerSigMet.start ( LEVELMETER_UPDATE_TIME_MS ); - TimerBuffersLED.start ( BUFFER_LED_UPDATE_TIME_MS ); - TimerPing.start ( PING_UPDATE_TIME_MS ); - TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS ); // is single shot timer - - // audio feedback detection - if ( pSettings->bEnableFeedbackDetection ) - { - TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS ); // single shot timer - bDetectFeedback = true; - } - } -} - -void CClientDlg::Disconnect() -{ - // only stop client if currently running, in case we received - // the stopped message, the client is already stopped but the - // connect/disconnect button and other GUI controls must be - // updated - if ( pClient->IsRunning() ) - { - pClient->Stop(); - } - - // change connect button text to "connect" - butConnect->setText ( tr ( "C&onnect" ) ); - - // reset server name in audio mixer group box title - MainMixerBoard->SetServerName ( "" ); - - // stop timer for level meter bars and reset them - TimerSigMet.stop(); - lbrInputLevelL->setEnabled ( false ); - lbrInputLevelR->setEnabled ( false ); - lbrInputLevelL->SetValue ( 0 ); - lbrInputLevelR->SetValue ( 0 ); - - // show connect to server message - lblConnectToServer->show(); - - // stop other timers - TimerBuffersLED.stop(); - TimerPing.stop(); - TimerCheckAudioDeviceOk.stop(); - TimerDetectFeedback.stop(); - bDetectFeedback = false; - - //### TODO: BEGIN ###// - // is this still required??? - // immediately update status bar - OnTimerStatus(); - //### TODO: END ###// - - // reset LEDs - ledBuffers->Reset(); - ledDelay->Reset(); - - // clear text labels with client parameters - lblPingVal->setText ( "---" ); - lblPingUnit->setText ( "" ); - lblDelayVal->setText ( "---" ); - lblDelayUnit->setText ( "" ); - - // clear mixer board (remove all faders) - MainMixerBoard->HideAll(); -} - -void CClientDlg::UpdateDisplay() -{ - // update settings/chat buttons (do not fire signals since it is an update) - if ( chbSettings->isChecked() && !ClientSettingsDlg.isVisible() ) - { - chbSettings->blockSignals ( true ); - chbSettings->setChecked ( false ); - chbSettings->blockSignals ( false ); - } - if ( !chbSettings->isChecked() && ClientSettingsDlg.isVisible() ) - { - chbSettings->blockSignals ( true ); - chbSettings->setChecked ( true ); - chbSettings->blockSignals ( false ); - } - - if ( chbChat->isChecked() && !ChatDlg.isVisible() ) - { - chbChat->blockSignals ( true ); - chbChat->setChecked ( false ); - chbChat->blockSignals ( false ); - } - if ( !chbChat->isChecked() && ChatDlg.isVisible() ) - { - chbChat->blockSignals ( true ); - chbChat->setChecked ( true ); - chbChat->blockSignals ( false ); - } -} - -void CClientDlg::SetGUIDesign ( const EGUIDesign eNewDesign ) -{ - // remove any styling from the mixer board - reapply after changing skin - MainMixerBoard->setStyleSheet ( "" ); - - // apply GUI design to current window - switch ( eNewDesign ) - { - case GD_ORIGINAL: - backgroundFrame->setStyleSheet ( - "QFrame#backgroundFrame { border-image: url(:/png/fader/res/mixerboardbackground.png) 34px 30px 40px 40px;" - " border-top: 34px transparent;" - " border-bottom: 40px transparent;" - " border-left: 30px transparent;" - " border-right: 40px transparent;" - " padding: -5px;" - " margin: -5px, -5px, 0px, 0px; }" - "QLabel { color: rgb(220, 220, 220);" - " font: bold; }" - "QRadioButton { color: rgb(220, 220, 220);" - " font: bold; }" - "QScrollArea { background: transparent; }" - ".QWidget { background: transparent; }" // note: matches instances of QWidget, but not of its subclasses - "QGroupBox { background: transparent; }" - "QGroupBox::title { color: rgb(220, 220, 220); }" - "QCheckBox::indicator { width: 38px;" - " height: 21px; }" - "QCheckBox::indicator:unchecked {" - " image: url(:/png/fader/res/ledbuttonnotpressed.png); }" - "QCheckBox::indicator:checked {" - " image: url(:/png/fader/res/ledbuttonpressed.png); }" - "QCheckBox { color: rgb(220, 220, 220);" - " font: bold; }" ); -#ifdef _WIN32 - // Workaround QT-Windows problem: This should not be necessary since in the - // background frame the style sheet for QRadioButton was already set. But it - // seems that it is only applied if the style was set to default and then back - // to GD_ORIGINAL. This seems to be a QT related issue... - rbtReverbSelL->setStyleSheet ( "color: rgb(220, 220, 220);" - "font: bold;" ); - rbtReverbSelR->setStyleSheet ( "color: rgb(220, 220, 220);" - "font: bold;" ); -#endif - - ledBuffers->SetType ( CMultiColorLED::MT_LED ); - ledDelay->SetType ( CMultiColorLED::MT_LED ); - break; - - default: - // reset style sheet and set original parameters - backgroundFrame->setStyleSheet ( "" ); - -#ifdef _WIN32 - // Workaround QT-Windows problem: See above description - rbtReverbSelL->setStyleSheet ( "" ); - rbtReverbSelR->setStyleSheet ( "" ); -#endif - - ledBuffers->SetType ( CMultiColorLED::MT_INDICATOR ); - ledDelay->SetType ( CMultiColorLED::MT_INDICATOR ); - break; - } - - // also apply GUI design to child GUI controls - MainMixerBoard->SetGUIDesign ( eNewDesign ); -} - -void CClientDlg::SetMeterStyle ( const EMeterStyle eNewMeterStyle ) -{ - // apply MeterStyle to current window - switch ( eNewMeterStyle ) - { - case MT_LED_STRIPE: - lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_LED_STRIPE ); - lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_LED_STRIPE ); - break; - - case MT_LED_ROUND_BIG: - lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_BIG ); - lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_BIG ); - break; - - case MT_BAR_WIDE: - lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_BAR_WIDE ); - lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_BAR_WIDE ); - break; - - case MT_BAR_NARROW: - lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_BAR_WIDE ); - lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_BAR_WIDE ); - break; - - case MT_LED_ROUND_SMALL: - lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_BIG ); - lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_LED_ROUND_BIG ); - break; - } - - // also apply MeterStyle to child GUI controls - MainMixerBoard->SetMeterStyle ( eNewMeterStyle ); -} - -void CClientDlg::OnRecorderStateReceived ( const ERecorderState newRecorderState ) -{ - MainMixerBoard->SetRecorderState ( newRecorderState ); - SetMixerBoardDeco ( newRecorderState, pClient->GetGUIDesign() ); -} - -void CClientDlg::OnGUIDesignChanged() -{ - SetGUIDesign ( pClient->GetGUIDesign() ); - SetMixerBoardDeco ( MainMixerBoard->GetRecorderState(), pClient->GetGUIDesign() ); -} - -void CClientDlg::OnMeterStyleChanged() { SetMeterStyle ( pClient->GetMeterStyle() ); } - -void CClientDlg::SetMixerBoardDeco ( const ERecorderState newRecorderState, const EGUIDesign eNewDesign ) -{ - // return if no change - if ( ( newRecorderState == eLastRecorderState ) && ( eNewDesign == eLastDesign ) ) - return; - eLastRecorderState = newRecorderState; - eLastDesign = eNewDesign; - - // set base style - QString sTitleStyle = "QGroupBox::title { subcontrol-origin: margin;" - " subcontrol-position: left top;" - " left: 7px;"; - - if ( newRecorderState == RS_RECORDING ) - { - sTitleStyle += "color: rgb(255,255,255);" - "background-color: rgb(255,0,0); }"; - } - else - { - if ( eNewDesign == GD_ORIGINAL ) - { - // no need to set the background color for dark mode in fancy skin, as the background is already dark. - sTitleStyle += "color: rgb(220,220,220); }"; - } - else - { - if ( palette().color ( QPalette::Window ) == QColor::fromRgbF ( 0.196078, 0.196078, 0.196078, 1 ) ) - { - // Dark mode on macOS/Linux needs a light color - - sTitleStyle += "color: rgb(220,220,220); }"; - } - else - { - sTitleStyle += "color: rgb(0,0,0); }"; - } - } - } - - MainMixerBoard->setStyleSheet ( sTitleStyle ); -} - -void CClientDlg::SetPingTime ( const int iPingTime, const int iOverallDelayMs, const CMultiColorLED::ELightColor eOverallDelayLEDColor ) -{ - // apply values to GUI labels, take special care if ping time exceeds - // a certain value - if ( iPingTime > 500 ) - { - const QString sErrorText = ">500"; - lblPingVal->setText ( sErrorText ); - lblDelayVal->setText ( sErrorText ); - } - else - { - lblPingVal->setText ( QString().setNum ( iPingTime ) ); - lblDelayVal->setText ( QString().setNum ( iOverallDelayMs ) ); - } - lblPingUnit->setText ( "ms" ); - lblDelayUnit->setText ( "ms" ); - - // set current LED status - ledDelay->SetLight ( eOverallDelayLEDColor ); -} diff --git a/src/clientdlg.h b/src/clientdlg.h deleted file mode 100644 index 2a9062a58d..0000000000 --- a/src/clientdlg.h +++ /dev/null @@ -1,248 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) -# include -#endif -#include "global.h" -#include "util.h" -#include "client.h" -#include "settings.h" -#include "multicolorled.h" -#include "audiomixerboard.h" -#include "clientsettingsdlg.h" -#include "chatdlg.h" -#include "connectdlg.h" -#include "analyzerconsole.h" -#include "ui_clientdlgbase.h" -#if defined( Q_OS_MACOS ) -# include "mac/badgelabel.h" -#endif - -/* Definitions ****************************************************************/ -// update time for GUI controls -#define LEVELMETER_UPDATE_TIME_MS 100 // ms -#define BUFFER_LED_UPDATE_TIME_MS 300 // ms -#define LED_BAR_UPDATE_TIME_MS 1000 // ms -#define CHECK_AUDIO_DEV_OK_TIME_MS 5000 // ms -#define DETECT_FEEDBACK_TIME_MS 3000 // ms - -// number of ping times > upper bound until error message is shown -#define NUM_HIGH_PINGS_UNTIL_ERROR 5 - -/* Classes ********************************************************************/ -class CClientDlg : public CBaseDlg, private Ui_CClientDlgBase -{ - Q_OBJECT - -public: - CClientDlg ( CClient* pNCliP, - CClientSettings* pNSetP, - const QString& strConnOnStartupAddress, - const QString& strMIDISetup, - const bool bNewShowComplRegConnList, - const bool bShowAnalyzerConsole, - const bool bMuteStream, - const bool bNEnableIPv6, - QWidget* parent = nullptr ); - -protected: - void SetGUIDesign ( const EGUIDesign eNewDesign ); - void SetMeterStyle ( const EMeterStyle eNewMeterStyle ); - void SetMyWindowTitle ( const int iNumClients ); - void ShowConnectionSetupDialog(); - void ShowGeneralSettings ( int iTab ); - void ShowChatWindow ( const bool bForceRaise = true ); - void ShowAnalyzerConsole(); - void UpdateAudioFaderSlider(); - void UpdateRevSelection(); - void Connect ( const QString& strSelectedAddress, const QString& strMixerBoardLabel ); - void Disconnect(); - void ManageDragNDrop ( QDropEvent* Event, const bool bCheckAccept ); - void SetPingTime ( const int iPingTime, const int iOverallDelayMs, const CMultiColorLED::ELightColor eOverallDelayLEDColor ); - - CClient* pClient; - CClientSettings* pSettings; - - int iClients; - bool bConnected; - bool bConnectDlgWasShown; - bool bDetectFeedback; - bool bEnableIPv6; - ERecorderState eLastRecorderState; - EGUIDesign eLastDesign; - QTimer TimerSigMet; - QTimer TimerBuffersLED; - QTimer TimerStatus; - QTimer TimerPing; - QTimer TimerCheckAudioDeviceOk; - QTimer TimerDetectFeedback; - - virtual void closeEvent ( QCloseEvent* Event ); - virtual void dragEnterEvent ( QDragEnterEvent* Event ) { ManageDragNDrop ( Event, true ); } - virtual void dropEvent ( QDropEvent* Event ) { ManageDragNDrop ( Event, false ); } - void UpdateDisplay(); - - CClientSettingsDlg ClientSettingsDlg; - CChatDlg ChatDlg; - CConnectDlg ConnectDlg; - CAnalyzerConsole AnalyzerConsole; - -public slots: - void OnConnectDisconBut(); - void OnTimerSigMet(); - void OnTimerBuffersLED(); - void OnTimerCheckAudioDeviceOk(); - void OnTimerDetectFeedback(); - - void OnTimerStatus() { UpdateDisplay(); } - - void OnTimerPing(); - void OnPingTimeResult ( int iPingTime ); - void OnCLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients ); - - void OnControllerInFaderLevel ( const int iChannelIdx, const int iValue ) { MainMixerBoard->SetFaderLevel ( iChannelIdx, iValue ); } - - void OnControllerInPanValue ( const int iChannelIdx, const int iValue ) { MainMixerBoard->SetPanValue ( iChannelIdx, iValue ); } - - void OnControllerInFaderIsSolo ( const int iChannelIdx, const bool bIsSolo ) { MainMixerBoard->SetFaderIsSolo ( iChannelIdx, bIsSolo ); } - - void OnControllerInFaderIsMute ( const int iChannelIdx, const bool bIsMute ) { MainMixerBoard->SetFaderIsMute ( iChannelIdx, bIsMute ); } - - void OnControllerInMuteMyself ( const bool bMute ) { chbLocalMute->setChecked ( bMute ); } - - void OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVersion ); - - void OnCLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString strVersion ); - - void OnLoadChannelSetup(); - void OnSaveChannelSetup(); - void OnOpenConnectionSetupDialog() { ShowConnectionSetupDialog(); } - void OnOpenUserProfileSettings(); - void OnOpenAudioNetSettings(); - void OnOpenAdvancedSettings(); - void OnOpenChatDialog() { ShowChatWindow(); } - void OnOpenAnalyzerConsole() { ShowAnalyzerConsole(); } - void OnOwnFaderFirst() - { - pSettings->bOwnFaderFirst = !pSettings->bOwnFaderFirst; - MainMixerBoard->SetFaderSorting ( pSettings->eChannelSortType ); - } - void OnNoSortChannels() { MainMixerBoard->SetFaderSorting ( ST_NO_SORT ); } - void OnSortChannelsByName() { MainMixerBoard->SetFaderSorting ( ST_BY_NAME ); } - void OnSortChannelsByInstrument() { MainMixerBoard->SetFaderSorting ( ST_BY_INSTRUMENT ); } - void OnSortChannelsByGroupID() { MainMixerBoard->SetFaderSorting ( ST_BY_GROUPID ); } - void OnSortChannelsByCity() { MainMixerBoard->SetFaderSorting ( ST_BY_CITY ); } - void OnSortChannelsByChannel() { MainMixerBoard->SetFaderSorting ( ST_BY_SERVER_CHANNEL ); } - void OnClearAllStoredSoloMuteSettings(); - void OnSetAllFadersToNewClientLevel() { MainMixerBoard->SetAllFaderLevelsToNewClientLevel(); } - void OnAutoAdjustAllFaderLevels() { MainMixerBoard->AutoAdjustAllFaderLevels(); } - void OnNumMixerPanelRowsChanged ( int value ) { MainMixerBoard->SetNumMixerPanelRows ( value ); } - - void OnSettingsStateChanged ( int value ); - void OnChatStateChanged ( int value ); - void OnLocalMuteStateChanged ( int value ); - - void OnAudioReverbValueChanged ( int value ) { pClient->SetReverbLevel ( value ); } - - void OnReverbSelLClicked() { pClient->SetReverbOnLeftChan ( true ); } - - void OnReverbSelRClicked() { pClient->SetReverbOnLeftChan ( false ); } - - void OnFeedbackDetectionChanged ( int state ) { ClientSettingsDlg.SetEnableFeedbackDetection ( state == Qt::Checked ); } - - void OnConClientListMesReceived ( CVector vecChanInfo ); - void OnChatTextReceived ( QString strChatText ); - void OnLicenceRequired ( ELicenceType eLicenceType ); - void OnSoundDeviceChanged ( QString strError ); - - void OnChangeChanGain ( int iId, float fGain, bool bIsMyOwnFader ) { pClient->SetRemoteChanGain ( iId, fGain, bIsMyOwnFader ); } - - void OnChangeChanPan ( int iId, float fPan ) { pClient->SetRemoteChanPan ( iId, fPan ); } - - void OnNewLocalInputText ( QString strChatText ) { pClient->CreateChatTextMes ( strChatText ); } - - void OnReqServerListQuery ( CHostAddress InetAddr ) { pClient->CreateCLReqServerListMes ( InetAddr ); } - - void OnCreateCLServerListPingMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListPingMes ( InetAddr ); } - - void OnCreateCLServerListReqVerAndOSMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListReqVerAndOSMes ( InetAddr ); } - - void OnCreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr ) { pClient->CreateCLServerListReqConnClientsListMes ( InetAddr ); } - - void OnCLServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ) - { - ConnectDlg.SetServerList ( InetAddr, vecServerInfo ); - } - - void OnCLRedServerListReceived ( CHostAddress InetAddr, CVector vecServerInfo ) - { - ConnectDlg.SetServerList ( InetAddr, vecServerInfo, true ); - } - - void OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector vecChanInfo ) - { - ConnectDlg.SetConnClientsList ( InetAddr, vecChanInfo ); - } - - void OnClientIDReceived ( int iChanID ) { MainMixerBoard->SetMyChannelID ( iChanID ); } - - void OnMuteStateHasChangedReceived ( int iChanID, bool bIsMuted ) { MainMixerBoard->SetRemoteFaderIsMute ( iChanID, bIsMuted ); } - - void OnCLChannelLevelListReceived ( CHostAddress /* unused */, CVector vecLevelList ) - { - MainMixerBoard->SetChannelLevels ( vecLevelList ); - } - - void OnConnectDlgAccepted(); - void OnDisconnected() { Disconnect(); } - void OnGUIDesignChanged(); - void OnMeterStyleChanged(); - void OnRecorderStateReceived ( ERecorderState eRecorderState ); - void SetMixerBoardDeco ( const ERecorderState newRecorderState, const EGUIDesign eNewDesign ); - void OnAudioChannelsChanged() { UpdateRevSelection(); } - void OnNumClientsChanged ( int iNewNumClients ); - - void accept() { close(); } // introduced by pljones - -signals: - void SendTabChange ( int iTabIdx ); -}; diff --git a/src/clientdlgbase.ui b/src/clientdlgbase.ui deleted file mode 100644 index 0c652d7325..0000000000 --- a/src/clientdlgbase.ui +++ /dev/null @@ -1,653 +0,0 @@ - - - CClientDlgBase - - - - 0 - 0 - 511 - 490 - - - - true - - - - - - - :/png/main/res/fronticon.png:/png/main/res/fronticon.png - - - - - - true - - - - 0 - - - 0 - - - 3 - - - 3 - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - - - - - - 6 - - - - - 3 - - - - - - - - - - 0 - 0 - - - - :/png/main/res/fronticon.png - - - Qt::AlignCenter - - - false - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 13 - 10 - - - - - - - - - - 3 - - - - - Reverb - - - Qt::AlignCenter - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 125 - - - - - 16777215 - 300 - - - - 1 - - - Qt::Vertical - - - QSlider::TicksBothSides - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 20 - - - - - - - - - - - - 3 - - - - - Left - - - - - - - Right - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 13 - 10 - - - - - - - - - - Qt::Vertical - - - - - - - - - Input - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - - 19 - 88 - - - - - - - - - 0 - 0 - - - - - 19 - 88 - - - - - - - - - - - - L - - - Qt::AlignCenter - - - - - - - R - - - Qt::AlignCenter - - - - - - - - - - - - - Qt::Horizontal - - - - - - - - - Jitter - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - - - - - Delay - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - - - - - Ping - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - false - - - - - - - - 34 - 0 - - - - Qt::LeftToRight - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 22 - 0 - - - - - 22 - 16777215 - - - - ms - - - - - - - - 34 - 0 - - - - Qt::LeftToRight - - - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 22 - 0 - - - - - 22 - 16777215 - - - - ms - - - - - - - - 0 - 0 - - - - - 14 - 14 - - - - - 14 - 14 - - - - - - - - - 0 - 0 - - - - - 14 - 14 - - - - - 14 - 14 - - - - - - - - - - Qt::Horizontal - - - - - - - &Mute Myself - - - - - - - &Settings - - - - - - - &Chat - - - - - - - - 0 - 0 - - - - - 120 - 45 - - - - C&onnect - - - false - - - false - - - - - - - - - Qt::Vertical - - - - - - - - - - - MUTED (Other people won't hear you) - - - Qt::AlignCenter - - - 6 - - - - - - - QLayout::SetMinimumSize - - - 0 - - - 9 - - - - - - 0 - 0 - - - - Set up your audio, connect to a server and start jamming! - - - Qt::AlignCenter - - - - - - - - - - 0 - 0 - - - - - - - - Update check - - - - - - - - - - - - - - - CMultiColorLED - QWidget -
multicolorled.h
-
- - CAudioMixerBoard - QWidget -
audiomixerboard.h
-
- - CLevelMeter - QWidget -
levelmeter.h
- 1 -
-
- - butConnect - chbLocalMute - chbSettings - chbChat - sldAudioReverb - rbtReverbSelL - rbtReverbSelR - - - - - -
diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp deleted file mode 100644 index c7458826fd..0000000000 --- a/src/clientsettingsdlg.cpp +++ /dev/null @@ -1,1218 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#include "clientsettingsdlg.h" - -/* Implementation *************************************************************/ -CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSetP, QWidget* parent ) : - CBaseDlg ( parent, Qt::Window ), // use Qt::Window to get min/max window buttons - pClient ( pNCliP ), - pSettings ( pNSetP ) -{ - setupUi ( this ); - -#if defined( Q_OS_IOS ) - // iOS needs menu to close - QMenuBar* pMenu = new QMenuBar ( this ); - QAction* action = pMenu->addAction ( tr ( "&Close" ) ); - connect ( action, SIGNAL ( triggered() ), this, SLOT ( close() ) ); - - // Now tell the layout about the menu - layout()->setMenuBar ( pMenu ); -#endif - -#if defined( Q_OS_ANDROID ) || defined( ANDROID ) - // Android too - QMenuBar* pMenu = new QMenuBar ( this ); - QMenu* pCloseMenu = new QMenu ( tr ( "&Close" ), this ); - pCloseMenu->addAction ( tr ( "&Close" ), this, SLOT ( close() ) ); - pMenu->addMenu ( pCloseMenu ); - - // Now tell the layout about the menu - layout()->setMenuBar ( pMenu ); -#endif - - // Add help text to controls ----------------------------------------------- - // local audio input fader - QString strAudFader = "" + tr ( "Local Audio Input Fader" ) + ": " + - tr ( "Controls the relative levels of the left and right local audio " - "channels. For a mono signal it acts as a pan between the two channels. " - "For example, if a microphone is connected to " - "the right input channel and an instrument is connected to the left " - "input channel which is much louder than the microphone, move the " - "audio fader in a direction where the label above the fader shows " - "%1, where %2 is the current attenuation indicator." ) - .arg ( "" + tr ( "L" ) + " -x", "x" ); - - lblAudioPan->setWhatsThis ( strAudFader ); - lblAudioPanValue->setWhatsThis ( strAudFader ); - sldAudioPan->setWhatsThis ( strAudFader ); - - sldAudioPan->setAccessibleName ( tr ( "Local audio input fader (left/right)" ) ); - - // jitter buffer - QString strJitterBufferSize = "" + tr ( "Jitter Buffer Size" ) + ": " + - tr ( "The jitter buffer compensates for network and sound card timing jitters. The " - "size of the buffer therefore influences the quality of " - "the audio stream (how many dropouts occur) and the overall delay " - "(the longer the buffer, the higher the delay)." ) + - "
" + - tr ( "You can set the jitter buffer size manually for the local client " - "and the remote server. For the local jitter buffer, dropouts in the " - "audio stream are indicated by the light below the " - "jitter buffer size faders. If the light turns to red, a buffer " - "overrun/underrun has taken place and the audio stream is interrupted." ) + - "
" + - tr ( "The jitter buffer setting is therefore a trade-off between audio " - "quality and overall delay." ) + - "
" + - tr ( "If the Auto setting is enabled, the jitter buffers of the local client and " - "the remote server are set automatically " - "based on measurements of the network and sound card timing jitter. If " - "Auto is enabled, the jitter buffer size faders are " - "disabled (they cannot be moved with the mouse)." ); - - QString strJitterBufferSizeTT = tr ( "If the Auto setting " - "is enabled, the network buffers of the local client and " - "the remote server are set to a conservative " - "value to minimize the audio dropout probability. To tweak the " - "audio delay/latency it is recommended to disable the Auto setting " - "and to lower the jitter buffer size manually by " - "using the sliders until your personal acceptable amount " - "of dropouts is reached. The LED indicator will display the audio " - "dropouts of the local jitter buffer with a red light." ) + - TOOLTIP_COM_END_TEXT; - - lblNetBuf->setWhatsThis ( strJitterBufferSize ); - lblNetBuf->setToolTip ( strJitterBufferSizeTT ); - grbJitterBuffer->setWhatsThis ( strJitterBufferSize ); - grbJitterBuffer->setToolTip ( strJitterBufferSizeTT ); - sldNetBuf->setWhatsThis ( strJitterBufferSize ); - sldNetBuf->setAccessibleName ( tr ( "Local jitter buffer slider control" ) ); - sldNetBuf->setToolTip ( strJitterBufferSizeTT ); - sldNetBufServer->setWhatsThis ( strJitterBufferSize ); - sldNetBufServer->setAccessibleName ( tr ( "Server jitter buffer slider control" ) ); - sldNetBufServer->setToolTip ( strJitterBufferSizeTT ); - chbAutoJitBuf->setAccessibleName ( tr ( "Auto jitter buffer check box" ) ); - chbAutoJitBuf->setToolTip ( strJitterBufferSizeTT ); - -#if !defined( WITH_JACK ) - // sound card device - lblSoundcardDevice->setWhatsThis ( "" + tr ( "Audio Device" ) + ": " + - tr ( "Under the Windows operating system the ASIO driver (sound card) can be " - "selected using %1. If the selected ASIO driver is not valid an error " - "message is shown and the previous valid driver is selected. " - "Under macOS the input and output hardware can be selected." ) - .arg ( APP_NAME ) + - "
" + - tr ( "If the driver is selected during an active connection, the connection " - "is stopped, the driver is changed and the connection is started again " - "automatically." ) ); - - cbxSoundcard->setAccessibleName ( tr ( "Sound card device selector combo box" ) ); - -# if defined( _WIN32 ) - // set Windows specific tool tip - cbxSoundcard->setToolTip ( tr ( "If the ASIO4ALL driver is used, " - "please note that this driver usually introduces approx. 10-30 ms of " - "additional audio delay. Using a sound card with a native ASIO driver " - "is therefore recommended." ) + - "
" + - tr ( "If you are using the kX ASIO " - "driver, make sure to connect the ASIO inputs in the kX DSP settings " - "panel." ) + - TOOLTIP_COM_END_TEXT ); -# endif - - // sound card input/output channel mapping - QString strSndCrdChanMapp = "" + tr ( "Sound Card Channel Mapping" ) + ": " + - tr ( "If the selected sound card device offers more than one " - "input or output channel, the Input Channel Mapping and Output " - "Channel Mapping settings are visible." ) + - "
" + - tr ( "For each %1 input/output channel (left and " - "right channel) a different actual sound card channel can be " - "selected." ) - .arg ( APP_NAME ); - - lblInChannelMapping->setWhatsThis ( strSndCrdChanMapp ); - lblOutChannelMapping->setWhatsThis ( strSndCrdChanMapp ); - cbxLInChan->setWhatsThis ( strSndCrdChanMapp ); - cbxLInChan->setAccessibleName ( tr ( "Left input channel selection combo box" ) ); - cbxRInChan->setWhatsThis ( strSndCrdChanMapp ); - cbxRInChan->setAccessibleName ( tr ( "Right input channel selection combo box" ) ); - cbxLOutChan->setWhatsThis ( strSndCrdChanMapp ); - cbxLOutChan->setAccessibleName ( tr ( "Left output channel selection combo box" ) ); - cbxROutChan->setWhatsThis ( strSndCrdChanMapp ); - cbxROutChan->setAccessibleName ( tr ( "Right output channel selection combo box" ) ); -#endif - - // enable OPUS64 - chbSmallNetworkBuffers->setWhatsThis ( - "" + tr ( "Small Network Buffers" ) + ": " + - tr ( "Enables support for very small network audio packets. These " - "network packets are only actually used if the sound card buffer delay is smaller than %1 samples. The " - "smaller the network buffers, the lower the audio latency. But at the same time " - "the network load and the probability of audio dropouts or sound artifacts increases." ) - .arg ( DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ) ); - - chbSmallNetworkBuffers->setAccessibleName ( tr ( "Small network buffers check box" ) ); - - // sound card buffer delay - QString strSndCrdBufDelay = - "" + tr ( "Sound Card Buffer Delay" ) + ": " + - tr ( "The buffer delay setting is a fundamental setting of %1. " - "This setting has an influence on many connection properties." ) - .arg ( APP_NAME ) + - "
" + tr ( "Three buffer sizes can be selected" ) + - ":
    " - "
  • " + - tr ( "64 samples: Provides the lowest latency but does not work with all sound cards." ) + - "
  • " - "
  • " + - tr ( "128 samples: Should work for most available sound cards." ) + - "
  • " - "
  • " + - tr ( "256 samples: Should only be used when 64 or 128 samples " - "is causing issues." ) + - "
  • " - "
" + - tr ( "Some sound card drivers do not allow the buffer delay to be changed from within %1. " - "In this case the buffer delay setting is disabled and has to be changed using the sound card driver. " - "Use the appropriate tool for the interface in use to adjust this buffer size. " - "For example, if using ASIO, use the \"ASIO Device Settings\" button to open the driver settings panel or if using JACK, use a tool " - "such as QjackCtl to adjust the buffer size. " - "Other interfaces, such as Pipewire, would require their appropriate tool being used. Please refer to the interface manual." ) - .arg ( APP_NAME ) + - "
" + - tr ( "If no buffer size is selected and all settings are disabled, this means a " - "buffer size in use by the driver which does not match the values. %1 " - "will still work with this setting but may have restricted " - "performance." ) - .arg ( APP_NAME ) + - "
" + - tr ( "The actual buffer delay has influence on the connection, the " - "current upload rate and the overall delay. The lower the buffer size, " - "the higher the probability of a red light in the status indicator (drop " - "outs) and the higher the upload rate and the lower the overall " - "delay." ) + - "
" + tr ( "The buffer setting is therefore a trade-off between audio quality and overall delay." ); - - QString strSndCrdBufDelayTT = - tr ( "Some sound card drivers do not allow the buffer delay to be changed from within %1. " - "In this case the buffer delay setting is disabled and has to be changed using the sound card driver. " - "Use the appropriate tool for the interface in use to adjust this buffer size. " - "For example, if using ASIO, use the \"ASIO Device Settings\" button to open the driver settings panel or if using JACK, use a tool " - "such as QjackCtl to adjust the buffer size. " - "Other interfaces, such as Pipewire, would require their appropriate tool being used. Please refer to the interface manual." ) - .arg ( APP_NAME ) + - TOOLTIP_COM_END_TEXT; - -#if defined( _WIN32 ) && !defined( WITH_JACK ) - // Driver setup button - QString strSndCardDriverSetup = "" + tr ( "Sound card driver settings" ) + ": " + - tr ( "This opens the driver settings of your sound card. Some drivers " - "allow you to change buffer settings, others like ASIO4ALL " - "lets you choose input or outputs of your device(s). " - "More information can be found on jamulus.io." ); - - QString strSndCardDriverSetupTT = tr ( "Opens the driver settings. Note: %1 currently only supports devices " - "with a sample rate of %2 Hz. " - "You will not be able to select a driver/device which doesn't. " - "For more help see jamulus.io." ) - .arg ( APP_NAME ) - .arg ( SYSTEM_SAMPLE_RATE_HZ ) + - TOOLTIP_COM_END_TEXT; -#endif - - rbtBufferDelayPreferred->setWhatsThis ( strSndCrdBufDelay ); - rbtBufferDelayPreferred->setAccessibleName ( tr ( "64 samples setting radio button" ) ); - rbtBufferDelayPreferred->setToolTip ( strSndCrdBufDelayTT ); - rbtBufferDelayDefault->setWhatsThis ( strSndCrdBufDelay ); - rbtBufferDelayDefault->setAccessibleName ( tr ( "128 samples setting radio button" ) ); - rbtBufferDelayDefault->setToolTip ( strSndCrdBufDelayTT ); - rbtBufferDelaySafe->setWhatsThis ( strSndCrdBufDelay ); - rbtBufferDelaySafe->setAccessibleName ( tr ( "256 samples setting radio button" ) ); - rbtBufferDelaySafe->setToolTip ( strSndCrdBufDelayTT ); - -#if defined( _WIN32 ) && !defined( WITH_JACK ) - butDriverSetup->setWhatsThis ( strSndCardDriverSetup ); - butDriverSetup->setAccessibleName ( tr ( "ASIO Device Settings push button" ) ); - butDriverSetup->setToolTip ( strSndCardDriverSetupTT ); -#endif - - // fancy skin - lblSkin->setWhatsThis ( "" + tr ( "Skin" ) + ": " + tr ( "Select the skin to be used for the main window." ) ); - - cbxSkin->setAccessibleName ( tr ( "Skin combo box" ) ); - - // MeterStyle - lblMeterStyle->setWhatsThis ( "" + tr ( "Meter Style" ) + ": " + - tr ( "Select the meter style to be used for the level meters. The " - "Bar (narrow) and LEDs (round, small) options only apply to the mixerboard. When " - "Bar (narrow) is selected, the input meters are set to Bar (wide). When " - "LEDs (round, small) is selected, the input meters are set to LEDs (round, big). " - "The remaining options apply to the mixerboard and input meters." ) ); - - cbxMeterStyle->setAccessibleName ( tr ( "Meter Style combo box" ) ); - - // Interface Language - lblLanguage->setWhatsThis ( "" + tr ( "Language" ) + ": " + tr ( "Select the language to be used for the user interface." ) ); - - cbxLanguage->setAccessibleName ( tr ( "Language combo box" ) ); - - // audio channels - QString strAudioChannels = "" + tr ( "Audio Channels" ) + ": " + - tr ( "Selects the number of audio channels to be used for communication between " - "client and server. There are three modes available:" ) + - "
    " - "
  • " - "" + - tr ( "Mono" ) + " " + tr ( "and" ) + " " + tr ( "Stereo" ) + ": " + - tr ( "These modes use " - "one and two audio channels respectively." ) + - "
  • " - "
  • " - "" + - tr ( "Mono in/Stereo-out" ) + ": " + - tr ( "The audio signal sent to the server is mono but the " - "return signal is stereo. This is useful if the " - "sound card has the instrument on one input channel and the " - "microphone on the other. In that case the two input signals " - "can be mixed to one mono channel but the server mix is heard in " - "stereo." ) + - "
  • " - "
  • " + - tr ( "Enabling " ) + "" + tr ( "Stereo" ) + " " + - tr ( " mode " - "will increase your stream's data rate. Make sure your upload rate does not " - "exceed the available upload speed of your internet connection." ) + - "
  • " - "
" + - tr ( "In stereo streaming mode, no audio channel selection " - "for the reverb effect will be available on the main window " - "since the effect is applied to both channels in this case." ); - - lblAudioChannels->setWhatsThis ( strAudioChannels ); - cbxAudioChannels->setWhatsThis ( strAudioChannels ); - cbxAudioChannels->setAccessibleName ( tr ( "Audio channels combo box" ) ); - - // audio quality - QString strAudioQuality = "" + tr ( "Audio Quality" ) + ": " + - tr ( "The higher the audio quality, the higher your audio stream's " - "data rate. Make sure your upload rate does not exceed the " - "available bandwidth of your internet connection." ); - - lblAudioQuality->setWhatsThis ( strAudioQuality ); - cbxAudioQuality->setWhatsThis ( strAudioQuality ); - cbxAudioQuality->setAccessibleName ( tr ( "Audio quality combo box" ) ); - - // new client fader level - QString strNewClientLevel = "" + tr ( "New Client Level" ) + ": " + - tr ( "This setting defines the fader level of a newly " - "connected client in percent. If a new client connects " - "to the current server, they will get the specified initial " - "fader level if no other fader level from a previous connection " - "of that client was already stored." ); - - lblNewClientLevel->setWhatsThis ( strNewClientLevel ); - edtNewClientLevel->setWhatsThis ( strNewClientLevel ); - edtNewClientLevel->setAccessibleName ( tr ( "New client level edit box" ) ); - - // input boost - QString strInputBoost = "" + tr ( "Input Boost" ) + ": " + - tr ( "This setting allows you to increase your input signal level " - "by factors up to 10 (+20dB). " - "If your sound is too quiet, first try to increase the level by " - "getting closer to the microphone, adjusting your sound equipment " - "or increasing levels in your operating system's input settings. " - "Only if this fails, set a factor here. " - "If your sound is too loud, sounds distorted and is clipping, this " - "option will not help. Do not use it. The distortion will still be " - "there. Instead, decrease your input level by getting farther away " - "from your microphone, adjusting your sound equipment " - "or by decreasing your operating system's input settings." ); - lblInputBoost->setWhatsThis ( strInputBoost ); - cbxInputBoost->setWhatsThis ( strInputBoost ); - cbxInputBoost->setAccessibleName ( tr ( "Input Boost combo box" ) ); - - // custom directories - QString strCustomDirectories = "" + tr ( "Custom Directories" ) + ": " + - tr ( "If you need to add additional directories to the Connect dialog Directory drop down, " - "you can enter the addresses here.
" ); - - lblCustomDirectories->setWhatsThis ( strCustomDirectories ); - cbxCustomDirectories->setWhatsThis ( strCustomDirectories ); - cbxCustomDirectories->setAccessibleName ( tr ( "Custom Directories combo box" ) ); - - tbtDeleteCustomDirectory->setAccessibleName ( tr ( "Delete custom directory button" ) ); - tbtDeleteCustomDirectory->setWhatsThis ( "" + tr ( "Delete Custom Directory" ) + ": " + - tr ( "Click the button to delete the currently selected custom directory." ) ); - tbtDeleteCustomDirectory->setText ( u8"\u232B" ); - - // current connection status parameter - QString strConnStats = "" + tr ( "Audio Upstream Rate" ) + ": " + - tr ( "Depends on the current audio packet size and " - "compression setting. Make sure that the upstream rate is not " - "higher than your available internet upload speed (check this with a " - "service such as speedtest.net)." ); - - lblUpstreamValue->setWhatsThis ( strConnStats ); - grbUpstreamValue->setWhatsThis ( strConnStats ); - - QString strNumMixerPanelRows = - "" + tr ( "Number of Mixer Panel Rows" ) + ": " + tr ( "Adjust the number of rows used to arrange the mixer panel." ); - lblMixerRows->setWhatsThis ( strNumMixerPanelRows ); - spnMixerRows->setWhatsThis ( strNumMixerPanelRows ); - spnMixerRows->setAccessibleName ( tr ( "Number of Mixer Panel Rows spin box" ) ); - - chbDetectFeedback->setWhatsThis ( "" + tr ( "Feedback Protection" ) + ": " + - tr ( "Prevents acoustic feedback between microphone and speakers." ) ); - chbDetectFeedback->setAccessibleName ( tr ( "Feedback Protection check box" ) ); - - // audio alerts - chbAudioAlerts->setWhatsThis ( "" + tr ( "Audio Alerts" ) + ": " + - tr ( "Trigger an audio alert when receiving a chat message and when a new client joins the session. " - "A second sound device may be required to hear the alerts." ) ); - chbAudioAlerts->setAccessibleName ( tr ( "Audio Alerts check box" ) ); - - // init driver button -#if defined( _WIN32 ) && !defined( WITH_JACK ) - butDriverSetup->setText ( tr ( "ASIO Device Settings" ) ); -#else - // no use for this button for MacOS/Linux right now or when using JACK -> hide it - butDriverSetup->hide(); -#endif - - // init audio in fader - sldAudioPan->setRange ( AUD_FADER_IN_MIN, AUD_FADER_IN_MAX ); - sldAudioPan->setTickInterval ( AUD_FADER_IN_MAX / 5 ); - UpdateAudioFaderSlider(); - - // init delay and other information controls - lblUpstreamValue->setText ( "---" ); - lblUpstreamUnit->setText ( "" ); - edtNewClientLevel->setValidator ( new QIntValidator ( 0, 100, this ) ); // % range from 0-100 - - // init slider controls --- - // network buffer sliders - sldNetBuf->setRange ( MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL ); - sldNetBufServer->setRange ( MIN_NET_BUF_SIZE_NUM_BL, MAX_NET_BUF_SIZE_NUM_BL ); - UpdateJitterBufferFrame(); - - // init sound card channel selection frame - UpdateSoundDeviceChannelSelectionFrame(); - - // Audio Channels combo box - cbxAudioChannels->clear(); - cbxAudioChannels->addItem ( tr ( "Mono" ) ); // CC_MONO - cbxAudioChannels->addItem ( tr ( "Mono-in/Stereo-out" ) ); // CC_MONO_IN_STEREO_OUT - cbxAudioChannels->addItem ( tr ( "Stereo" ) ); // CC_STEREO - cbxAudioChannels->setCurrentIndex ( static_cast ( pClient->GetAudioChannels() ) ); - - // Audio Quality combo box - cbxAudioQuality->clear(); - cbxAudioQuality->addItem ( tr ( "Low" ) ); // AQ_LOW - cbxAudioQuality->addItem ( tr ( "Normal" ) ); // AQ_NORMAL - cbxAudioQuality->addItem ( tr ( "High" ) ); // AQ_HIGH - cbxAudioQuality->setCurrentIndex ( static_cast ( pClient->GetAudioQuality() ) ); - - // GUI design (skin) combo box - cbxSkin->clear(); - cbxSkin->addItem ( tr ( "Normal" ) ); // GD_STANDARD - cbxSkin->addItem ( tr ( "Fancy" ) ); // GD_ORIGINAL - cbxSkin->addItem ( tr ( "Compact" ) ); // GD_SLIMFADER - cbxSkin->setCurrentIndex ( static_cast ( pClient->GetGUIDesign() ) ); - - // MeterStyle combo box - cbxMeterStyle->clear(); - cbxMeterStyle->addItem ( tr ( "Bar (narrow)" ) ); // MT_BAR_NARROW - cbxMeterStyle->addItem ( tr ( "Bar (wide)" ) ); // MT_BAR_WIDE - cbxMeterStyle->addItem ( tr ( "LEDs (stripe)" ) ); // MT_LED_STRIPE - cbxMeterStyle->addItem ( tr ( "LEDs (round, small)" ) ); // MT_LED_ROUND_SMALL - cbxMeterStyle->addItem ( tr ( "LEDs (round, big)" ) ); // MT_LED_ROUND_BIG - cbxMeterStyle->setCurrentIndex ( static_cast ( pClient->GetMeterStyle() ) ); - - // language combo box (corrects the setting if language not found) - cbxLanguage->Init ( pSettings->strLanguage ); - - // init custom directories combo box (max MAX_NUM_SERVER_ADDR_ITEMS entries) - cbxCustomDirectories->setMaxCount ( MAX_NUM_SERVER_ADDR_ITEMS ); - cbxCustomDirectories->setInsertPolicy ( QComboBox::NoInsert ); - - // update new client fader level edit box - edtNewClientLevel->setText ( QString::number ( pSettings->iNewClientFaderLevel ) ); - - // Input Boost combo box - cbxInputBoost->clear(); - cbxInputBoost->addItem ( tr ( "None" ) ); - for ( int i = 2; i <= 10; i++ ) - { - cbxInputBoost->addItem ( QString ( "%1x" ).arg ( i ) ); - } - // factor is 1-based while index is 0-based: - cbxInputBoost->setCurrentIndex ( pSettings->iInputBoost - 1 ); - - // init number of mixer rows - spnMixerRows->setValue ( pSettings->iNumMixerPanelRows ); - - // init audio alerts - chbAudioAlerts->setCheckState ( pSettings->bEnableAudioAlerts ? Qt::Checked : Qt::Unchecked ); - - // update feedback detection - chbDetectFeedback->setCheckState ( pSettings->bEnableFeedbackDetection ? Qt::Checked : Qt::Unchecked ); - - // update enable small network buffers check box - chbSmallNetworkBuffers->setCheckState ( pClient->GetEnableOPUS64() ? Qt::Checked : Qt::Unchecked ); - - // set text for sound card buffer delay radio buttons - rbtBufferDelayPreferred->setText ( GenSndCrdBufferDelayString ( FRAME_SIZE_FACTOR_PREFERRED * SYSTEM_FRAME_SIZE_SAMPLES ) ); - - rbtBufferDelayDefault->setText ( GenSndCrdBufferDelayString ( FRAME_SIZE_FACTOR_DEFAULT * SYSTEM_FRAME_SIZE_SAMPLES ) ); - - rbtBufferDelaySafe->setText ( GenSndCrdBufferDelayString ( FRAME_SIZE_FACTOR_SAFE * SYSTEM_FRAME_SIZE_SAMPLES ) ); - - // sound card buffer delay inits - SndCrdBufferDelayButtonGroup.addButton ( rbtBufferDelayPreferred ); - SndCrdBufferDelayButtonGroup.addButton ( rbtBufferDelayDefault ); - SndCrdBufferDelayButtonGroup.addButton ( rbtBufferDelaySafe ); - - UpdateSoundCardFrame(); - - // Add help text to controls ----------------------------------------------- - // Musician Profile - QString strFaderTag = "" + tr ( "Musician Profile" ) + ": " + - tr ( "Write your name or an alias here so the other musicians you want to " - "play with know who you are. You may also add a picture of the instrument " - "you play and a flag of the country or region you are located in. " - "Your city and skill level playing your instrument may also be added." ) + - "
" + - tr ( "What you set here will appear at your fader on the mixer " - "board when you are connected to a %1 server. This tag will " - "also be shown at each client which is connected to the same server as " - "you." ) - .arg ( APP_NAME ); - - plblAlias->setWhatsThis ( strFaderTag ); - pedtAlias->setAccessibleName ( tr ( "Alias or name edit box" ) ); - plblInstrument->setWhatsThis ( strFaderTag ); - pcbxInstrument->setAccessibleName ( tr ( "Instrument picture button" ) ); - plblCountry->setWhatsThis ( strFaderTag ); - pcbxCountry->setAccessibleName ( tr ( "Country/region flag button" ) ); - plblCity->setWhatsThis ( strFaderTag ); - pedtCity->setAccessibleName ( tr ( "City edit box" ) ); - plblSkill->setWhatsThis ( strFaderTag ); - pcbxSkill->setAccessibleName ( tr ( "Skill level combo box" ) ); - - // Instrument pictures combo box ------------------------------------------- - // add an entry for all known instruments - for ( int iCurInst = 0; iCurInst < CInstPictures::GetNumAvailableInst(); iCurInst++ ) - { - // create a combo box item with text, image and background color - QColor InstrColor; - - pcbxInstrument->addItem ( QIcon ( CInstPictures::GetResourceReference ( iCurInst ) ), CInstPictures::GetName ( iCurInst ), iCurInst ); - - switch ( CInstPictures::GetCategory ( iCurInst ) ) - { - case CInstPictures::IC_OTHER_INSTRUMENT: - InstrColor = QColor ( Qt::blue ); - break; - case CInstPictures::IC_WIND_INSTRUMENT: - InstrColor = QColor ( Qt::green ); - break; - case CInstPictures::IC_STRING_INSTRUMENT: - InstrColor = QColor ( Qt::red ); - break; - case CInstPictures::IC_PLUCKING_INSTRUMENT: - InstrColor = QColor ( Qt::cyan ); - break; - case CInstPictures::IC_PERCUSSION_INSTRUMENT: - InstrColor = QColor ( Qt::white ); - break; - case CInstPictures::IC_KEYBOARD_INSTRUMENT: - InstrColor = QColor ( Qt::yellow ); - break; - case CInstPictures::IC_MULTIPLE_INSTRUMENT: - InstrColor = QColor ( Qt::black ); - break; - } - - InstrColor.setAlpha ( 10 ); - pcbxInstrument->setItemData ( iCurInst, InstrColor, Qt::BackgroundRole ); - } - - // sort the items in alphabetical order - pcbxInstrument->model()->sort ( 0 ); - - // Country flag icons combo box -------------------------------------------- - // add an entry for all known country flags - for ( int iCurCntry = static_cast ( QLocale::AnyCountry ); iCurCntry < static_cast ( QLocale::LastCountry ); iCurCntry++ ) - { - // exclude the "None" entry since it is added after the sorting - if ( static_cast ( iCurCntry ) == QLocale::AnyCountry ) - { - continue; - } - - if ( !CLocale::IsCountryCodeSupported ( iCurCntry ) ) - { - // The current Qt version which is the base for the loop may support - // more country codes than our protocol does. Therefore, skip - // the unsupported options to avoid surprises. - continue; - } - - // get current country enum - QLocale::Country eCountry = static_cast ( iCurCntry ); - - // try to load icon from resource file name - QIcon CurFlagIcon; - CurFlagIcon.addFile ( CLocale::GetCountryFlagIconsResourceReference ( eCountry ) ); - - // only add the entry if a flag is available - if ( !CurFlagIcon.isNull() ) - { - // create a combo box item with text and image - pcbxCountry->addItem ( QIcon ( CurFlagIcon ), QLocale::countryToString ( eCountry ), iCurCntry ); - } - } - - // sort country combo box items in alphabetical order - pcbxCountry->model()->sort ( 0, Qt::AscendingOrder ); - - // the "None" country gets a special icon and is the very first item - QIcon FlagNoneIcon; - FlagNoneIcon.addFile ( ":/png/flags/res/flags/flagnone.png" ); - pcbxCountry->insertItem ( 0, FlagNoneIcon, tr ( "None" ), static_cast ( QLocale::AnyCountry ) ); - - // Skill level combo box --------------------------------------------------- - // create a pixmap showing the skill level colors - QPixmap SLPixmap ( 16, 11 ); // same size as the country flags - - SLPixmap.fill ( QColor::fromRgb ( RGBCOL_R_SL_NOT_SET, RGBCOL_G_SL_NOT_SET, RGBCOL_B_SL_NOT_SET ) ); - - pcbxSkill->addItem ( QIcon ( SLPixmap ), tr ( "None" ), SL_NOT_SET ); - - SLPixmap.fill ( QColor::fromRgb ( RGBCOL_R_SL_BEGINNER, RGBCOL_G_SL_BEGINNER, RGBCOL_B_SL_BEGINNER ) ); - - pcbxSkill->addItem ( QIcon ( SLPixmap ), tr ( "Beginner" ), SL_BEGINNER ); - - SLPixmap.fill ( QColor::fromRgb ( RGBCOL_R_SL_INTERMEDIATE, RGBCOL_G_SL_INTERMEDIATE, RGBCOL_B_SL_INTERMEDIATE ) ); - - pcbxSkill->addItem ( QIcon ( SLPixmap ), tr ( "Intermediate" ), SL_INTERMEDIATE ); - - SLPixmap.fill ( QColor::fromRgb ( RGBCOL_R_SL_SL_PROFESSIONAL, RGBCOL_G_SL_SL_PROFESSIONAL, RGBCOL_B_SL_SL_PROFESSIONAL ) ); - - pcbxSkill->addItem ( QIcon ( SLPixmap ), tr ( "Expert" ), SL_PROFESSIONAL ); - - // Connections ------------------------------------------------------------- - // timers - QObject::connect ( &TimerStatus, &QTimer::timeout, this, &CClientSettingsDlg::OnTimerStatus ); - - // slider controls - QObject::connect ( sldNetBuf, &QSlider::valueChanged, this, &CClientSettingsDlg::OnNetBufValueChanged ); - - QObject::connect ( sldNetBufServer, &QSlider::valueChanged, this, &CClientSettingsDlg::OnNetBufServerValueChanged ); - - // check boxes - QObject::connect ( chbAutoJitBuf, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnAutoJitBufStateChanged ); - - QObject::connect ( chbSmallNetworkBuffers, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnEnableOPUS64StateChanged ); - - QObject::connect ( chbDetectFeedback, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnFeedbackDetectionChanged ); - - QObject::connect ( chbAudioAlerts, &QCheckBox::stateChanged, this, &CClientSettingsDlg::OnAudioAlertsChanged ); - - // line edits - QObject::connect ( edtNewClientLevel, &QLineEdit::editingFinished, this, &CClientSettingsDlg::OnNewClientLevelEditingFinished ); - - // combo boxes - QObject::connect ( cbxSoundcard, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnSoundcardActivated ); - - QObject::connect ( cbxLInChan, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnLInChanActivated ); - - QObject::connect ( cbxRInChan, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnRInChanActivated ); - - QObject::connect ( cbxLOutChan, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnLOutChanActivated ); - - QObject::connect ( cbxROutChan, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnROutChanActivated ); - - QObject::connect ( cbxAudioChannels, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnAudioChannelsActivated ); - - QObject::connect ( cbxAudioQuality, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnAudioQualityActivated ); - - QObject::connect ( cbxSkin, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnGUIDesignActivated ); - - QObject::connect ( cbxMeterStyle, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnMeterStyleActivated ); - - QObject::connect ( cbxCustomDirectories->lineEdit(), &QLineEdit::editingFinished, this, [this] { - CClientSettingsDlg::OnCustomDirectoriesChanged ( false ); - } ); - - QObject::connect ( cbxLanguage, &CLanguageComboBox::LanguageChanged, this, &CClientSettingsDlg::OnLanguageChanged ); - - QObject::connect ( cbxInputBoost, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnInputBoostChanged ); - - // buttons -#if defined( _WIN32 ) && !defined( WITH_JACK ) - // Driver Setup button is only available for Windows when JACK is not used - QObject::connect ( butDriverSetup, &QPushButton::clicked, this, &CClientSettingsDlg::OnDriverSetupClicked ); -#endif - - // tool buttons - QObject::connect ( tbtDeleteCustomDirectory, &QToolButton::clicked, this, [this] { CClientSettingsDlg::OnCustomDirectoriesChanged ( true ); } ); - - // misc - // sliders - QObject::connect ( sldAudioPan, &QSlider::valueChanged, this, &CClientSettingsDlg::OnAudioPanValueChanged ); - - QObject::connect ( &SndCrdBufferDelayButtonGroup, - static_cast ( &QButtonGroup::buttonClicked ), - this, - &CClientSettingsDlg::OnSndCrdBufferDelayButtonGroupClicked ); - - // spinners - QObject::connect ( spnMixerRows, - static_cast ( &QSpinBox::valueChanged ), - this, - &CClientSettingsDlg::NumMixerPanelRowsChanged ); - - // Musician Profile - QObject::connect ( pedtAlias, &QLineEdit::textChanged, this, &CClientSettingsDlg::OnAliasTextChanged ); - - QObject::connect ( pcbxInstrument, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnInstrumentActivated ); - - QObject::connect ( pcbxCountry, - static_cast ( &QComboBox::activated ), - this, - &CClientSettingsDlg::OnCountryActivated ); - - QObject::connect ( pedtCity, &QLineEdit::textChanged, this, &CClientSettingsDlg::OnCityTextChanged ); - - QObject::connect ( pcbxSkill, static_cast ( &QComboBox::activated ), this, &CClientSettingsDlg::OnSkillActivated ); - - QObject::connect ( tabSettings, &QTabWidget::currentChanged, this, &CClientSettingsDlg::OnTabChanged ); - - tabSettings->setCurrentIndex ( pSettings->iSettingsTab ); - - // Timers ------------------------------------------------------------------ - // start timer for status bar - TimerStatus.start ( DISPLAY_UPDATE_TIME ); -} - -void CClientSettingsDlg::showEvent ( QShowEvent* ) -{ - UpdateDisplay(); - UpdateDirectoryComboBox(); - - // set the name - pedtAlias->setText ( pClient->ChannelInfo.strName ); - - // select current instrument - pcbxInstrument->setCurrentIndex ( pcbxInstrument->findData ( pClient->ChannelInfo.iInstrument ) ); - - // select current country - pcbxCountry->setCurrentIndex ( pcbxCountry->findData ( static_cast ( pClient->ChannelInfo.eCountry ) ) ); - - // set the city - pedtCity->setText ( pClient->ChannelInfo.strCity ); - - // select the skill level - pcbxSkill->setCurrentIndex ( pcbxSkill->findData ( static_cast ( pClient->ChannelInfo.eSkillLevel ) ) ); -} - -void CClientSettingsDlg::UpdateJitterBufferFrame() -{ - // update slider value and text - const int iCurNumNetBuf = pClient->GetSockBufNumFrames(); - sldNetBuf->setValue ( iCurNumNetBuf ); - lblNetBuf->setText ( tr ( "Size: " ) + QString::number ( iCurNumNetBuf ) ); - - const int iCurNumNetBufServer = pClient->GetServerSockBufNumFrames(); - sldNetBufServer->setValue ( iCurNumNetBufServer ); - lblNetBufServer->setText ( tr ( "Size: " ) + QString::number ( iCurNumNetBufServer ) ); - - // if auto setting is enabled, disable slider control - const bool bIsAutoSockBufSize = pClient->GetDoAutoSockBufSize(); - - chbAutoJitBuf->setChecked ( bIsAutoSockBufSize ); - sldNetBuf->setEnabled ( !bIsAutoSockBufSize ); - lblNetBuf->setEnabled ( !bIsAutoSockBufSize ); - lblNetBufLabel->setEnabled ( !bIsAutoSockBufSize ); - sldNetBufServer->setEnabled ( !bIsAutoSockBufSize ); - lblNetBufServer->setEnabled ( !bIsAutoSockBufSize ); - lblNetBufServerLabel->setEnabled ( !bIsAutoSockBufSize ); -} - -QString CClientSettingsDlg::GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText ) -{ - // use two times the buffer delay for the entire delay since - // we have input and output - return QString().setNum ( static_cast ( iFrameSize ) * 2 * 1000 / SYSTEM_SAMPLE_RATE_HZ, 'f', 2 ) + " ms (" + - QString().setNum ( iFrameSize ) + strAddText + ")"; -} - -void CClientSettingsDlg::UpdateSoundCardFrame() -{ - // get current actual buffer size value - const int iCurActualBufSize = pClient->GetSndCrdActualMonoBlSize(); - - // check which predefined size is used (it is possible that none is used) - const bool bPreferredChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED ); - const bool bDefaultChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT ); - const bool bSafeChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE ); - - // Set radio buttons according to current value (To make it possible - // to have all radio buttons unchecked, we have to disable the - // exclusive check for the radio button group. We require all radio - // buttons to be unchecked in the case when the sound card does not - // support any of the buffer sizes and therefore all radio buttons - // are disabeld and unchecked.). - SndCrdBufferDelayButtonGroup.setExclusive ( false ); - rbtBufferDelayPreferred->setChecked ( bPreferredChecked ); - rbtBufferDelayDefault->setChecked ( bDefaultChecked ); - rbtBufferDelaySafe->setChecked ( bSafeChecked ); - SndCrdBufferDelayButtonGroup.setExclusive ( true ); - - // disable radio buttons which are not supported by audio interface - rbtBufferDelayPreferred->setEnabled ( pClient->GetFraSiFactPrefSupported() ); - rbtBufferDelayDefault->setEnabled ( pClient->GetFraSiFactDefSupported() ); - rbtBufferDelaySafe->setEnabled ( pClient->GetFraSiFactSafeSupported() ); - - // If any of our predefined sizes is chosen, use the regular group box - // title text. If not, show the actual buffer size. Otherwise the user - // would not know which buffer size is actually used. - if ( bPreferredChecked || bDefaultChecked || bSafeChecked ) - { - // default title text - grbSoundCrdBufDelay->setTitle ( tr ( "Buffer Delay" ) ); - } - else - { - // special title text with buffer size information added - grbSoundCrdBufDelay->setTitle ( tr ( "Buffer Delay: " ) + GenSndCrdBufferDelayString ( iCurActualBufSize ) ); - } -} - -void CClientSettingsDlg::UpdateSoundDeviceChannelSelectionFrame() -{ - // update combo box containing all available sound cards in the system - QStringList slSndCrdDevNames = pClient->GetSndCrdDevNames(); - cbxSoundcard->clear(); - - foreach ( QString strDevName, slSndCrdDevNames ) - { - cbxSoundcard->addItem ( strDevName ); - } - - cbxSoundcard->setCurrentText ( pClient->GetSndCrdDev() ); - - // update input/output channel selection -#if defined( _WIN32 ) || defined( __APPLE__ ) || defined( __MACOSX ) - - // Definition: The channel selection frame shall only be visible, - // if more than two input or output channels are available - const int iNumInChannels = pClient->GetSndCrdNumInputChannels(); - const int iNumOutChannels = pClient->GetSndCrdNumOutputChannels(); - - if ( ( iNumInChannels <= 2 ) && ( iNumOutChannels <= 2 ) ) - { - // as defined, make settings invisible - FrameSoundcardChannelSelection->setVisible ( false ); - } - else - { - // update combo boxes - FrameSoundcardChannelSelection->setVisible ( true ); - - // input - cbxLInChan->clear(); - cbxRInChan->clear(); - - for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumInputChannels(); iSndChanIdx++ ) - { - cbxLInChan->addItem ( pClient->GetSndCrdInputChannelName ( iSndChanIdx ) ); - cbxRInChan->addItem ( pClient->GetSndCrdInputChannelName ( iSndChanIdx ) ); - } - if ( pClient->GetSndCrdNumInputChannels() > 0 ) - { - cbxLInChan->setCurrentIndex ( pClient->GetSndCrdLeftInputChannel() ); - cbxRInChan->setCurrentIndex ( pClient->GetSndCrdRightInputChannel() ); - } - - // output - cbxLOutChan->clear(); - cbxROutChan->clear(); - for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumOutputChannels(); iSndChanIdx++ ) - { - cbxLOutChan->addItem ( pClient->GetSndCrdOutputChannelName ( iSndChanIdx ) ); - cbxROutChan->addItem ( pClient->GetSndCrdOutputChannelName ( iSndChanIdx ) ); - } - if ( pClient->GetSndCrdNumOutputChannels() > 0 ) - { - cbxLOutChan->setCurrentIndex ( pClient->GetSndCrdLeftOutputChannel() ); - cbxROutChan->setCurrentIndex ( pClient->GetSndCrdRightOutputChannel() ); - } - } -#else - // for other OS, no sound card channel selection is supported - FrameSoundcardChannelSelection->setVisible ( false ); -#endif -} - -void CClientSettingsDlg::SetEnableFeedbackDetection ( bool enable ) -{ - pSettings->bEnableFeedbackDetection = enable; - chbDetectFeedback->setCheckState ( pSettings->bEnableFeedbackDetection ? Qt::Checked : Qt::Unchecked ); -} - -#if defined( _WIN32 ) && !defined( WITH_JACK ) -void CClientSettingsDlg::OnDriverSetupClicked() { pClient->OpenSndCrdDriverSetup(); } -#endif - -void CClientSettingsDlg::OnNetBufValueChanged ( int value ) -{ - pClient->SetSockBufNumFrames ( value, true ); - UpdateJitterBufferFrame(); -} - -void CClientSettingsDlg::OnNetBufServerValueChanged ( int value ) -{ - pClient->SetServerSockBufNumFrames ( value ); - UpdateJitterBufferFrame(); -} - -void CClientSettingsDlg::OnSoundcardActivated ( int iSndDevIdx ) -{ - pClient->SetSndCrdDev ( cbxSoundcard->itemText ( iSndDevIdx ) ); - - UpdateSoundDeviceChannelSelectionFrame(); - UpdateDisplay(); -} - -void CClientSettingsDlg::OnLInChanActivated ( int iChanIdx ) -{ - pClient->SetSndCrdLeftInputChannel ( iChanIdx ); - UpdateSoundDeviceChannelSelectionFrame(); -} - -void CClientSettingsDlg::OnRInChanActivated ( int iChanIdx ) -{ - pClient->SetSndCrdRightInputChannel ( iChanIdx ); - UpdateSoundDeviceChannelSelectionFrame(); -} - -void CClientSettingsDlg::OnLOutChanActivated ( int iChanIdx ) -{ - pClient->SetSndCrdLeftOutputChannel ( iChanIdx ); - UpdateSoundDeviceChannelSelectionFrame(); -} - -void CClientSettingsDlg::OnROutChanActivated ( int iChanIdx ) -{ - pClient->SetSndCrdRightOutputChannel ( iChanIdx ); - UpdateSoundDeviceChannelSelectionFrame(); -} - -void CClientSettingsDlg::OnAudioChannelsActivated ( int iChanIdx ) -{ - pClient->SetAudioChannels ( static_cast ( iChanIdx ) ); - emit AudioChannelsChanged(); - UpdateDisplay(); // upload rate will be changed -} - -void CClientSettingsDlg::OnAudioQualityActivated ( int iQualityIdx ) -{ - pClient->SetAudioQuality ( static_cast ( iQualityIdx ) ); - UpdateDisplay(); // upload rate will be changed -} - -void CClientSettingsDlg::OnGUIDesignActivated ( int iDesignIdx ) -{ - pClient->SetGUIDesign ( static_cast ( iDesignIdx ) ); - emit GUIDesignChanged(); - UpdateDisplay(); -} - -void CClientSettingsDlg::OnMeterStyleActivated ( int iMeterStyleIdx ) -{ - pClient->SetMeterStyle ( static_cast ( iMeterStyleIdx ) ); - emit MeterStyleChanged(); - UpdateDisplay(); -} - -void CClientSettingsDlg::OnAudioAlertsChanged ( int value ) { pSettings->bEnableAudioAlerts = value == Qt::Checked; } - -void CClientSettingsDlg::OnAutoJitBufStateChanged ( int value ) -{ - pClient->SetDoAutoSockBufSize ( value == Qt::Checked ); - UpdateJitterBufferFrame(); -} - -void CClientSettingsDlg::OnEnableOPUS64StateChanged ( int value ) -{ - pClient->SetEnableOPUS64 ( value == Qt::Checked ); - UpdateDisplay(); -} - -void CClientSettingsDlg::OnFeedbackDetectionChanged ( int value ) { pSettings->bEnableFeedbackDetection = value == Qt::Checked; } - -void CClientSettingsDlg::OnCustomDirectoriesChanged ( bool bDelete ) -{ - if ( bDelete ) - { - if ( !cbxCustomDirectories->currentData().isValid() ) - { - // no selected directory to delete - return; - } - // delete the selected directory - pSettings->vstrDirectoryAddress[cbxCustomDirectories->currentData().toInt()] = QString(); - } - else - { - - if ( cbxCustomDirectories->currentText().isEmpty() || - ( cbxCustomDirectories->currentData().isValid() && pSettings->vstrDirectoryAddress[cbxCustomDirectories->currentData().toInt()].compare ( - NetworkUtil::FixAddress ( cbxCustomDirectories->currentText() ) ) == 0 ) ) - { - // no need to add a already added directory - return; - } - // store new address at the top of the list, if the list was already - // full, the last element is thrown out - pSettings->vstrDirectoryAddress.StringFiFoWithCompare ( NetworkUtil::FixAddress ( cbxCustomDirectories->currentText() ) ); - } - // update combo box list and inform connect dialog about the new address - UpdateDirectoryComboBox(); - emit CustomDirectoriesChanged(); -} - -void CClientSettingsDlg::OnSndCrdBufferDelayButtonGroupClicked ( QAbstractButton* button ) -{ - if ( button == rbtBufferDelayPreferred ) - { - pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ); - } - - if ( button == rbtBufferDelayDefault ) - { - pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ); - } - - if ( button == rbtBufferDelaySafe ) - { - pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_SAFE ); - } - - UpdateDisplay(); -} - -/// @method -/// @brief Sets upstream rate label to current upload rate if the client is connected, else resets label -void CClientSettingsDlg::UpdateUploadRate() -{ - // update upstream rate information label if needed - if ( pClient->IsConnected() ) - { - lblUpstreamValue->setText ( QString().setNum ( pClient->GetUploadRateKbps() ) ); - lblUpstreamUnit->setText ( "kbps" ); - } - else - { - // clear text labels with client parameters - lblUpstreamValue->setText ( "---" ); - lblUpstreamUnit->setText ( "" ); - } -} - -/// @method -/// @brief Updates slider controls (settings might have been changed) and upstream rate information label -void CClientSettingsDlg::UpdateDisplay() -{ - UpdateJitterBufferFrame(); - UpdateSoundCardFrame(); - UpdateUploadRate(); -} - -void CClientSettingsDlg::UpdateDirectoryComboBox() -{ - cbxCustomDirectories->clear(); - cbxCustomDirectories->clearEditText(); - - for ( int iLEIdx = 0; iLEIdx < MAX_NUM_SERVER_ADDR_ITEMS; iLEIdx++ ) - { - if ( !pSettings->vstrDirectoryAddress[iLEIdx].isEmpty() ) - { - // store the index as user data to the combo box item, too - cbxCustomDirectories->addItem ( pSettings->vstrDirectoryAddress[iLEIdx], iLEIdx ); - } - } -} - -void CClientSettingsDlg::OnInputBoostChanged() -{ - // index is zero-based while boost factor must be 1-based: - pSettings->iInputBoost = cbxInputBoost->currentIndex() + 1; - pClient->SetInputBoost ( pSettings->iInputBoost ); -} - -void CClientSettingsDlg::OnAliasTextChanged ( const QString& strNewName ) -{ - // check length - if ( strNewName.length() <= MAX_LEN_FADER_TAG ) - { - // refresh internal name parameter - pClient->ChannelInfo.strName = strNewName; - - // update channel info at the server - pClient->SetRemoteInfo(); - } - else - { - // text is too long, update control with shortened text - pedtAlias->setText ( TruncateString ( strNewName, MAX_LEN_FADER_TAG ) ); - } -} - -void CClientSettingsDlg::OnInstrumentActivated ( int iCntryListItem ) -{ - // set the new value in the data base - pClient->ChannelInfo.iInstrument = pcbxInstrument->itemData ( iCntryListItem ).toInt(); - - // update channel info at the server - pClient->SetRemoteInfo(); -} - -void CClientSettingsDlg::OnCountryActivated ( int iCntryListItem ) -{ - // set the new value in the data base - pClient->ChannelInfo.eCountry = static_cast ( pcbxCountry->itemData ( iCntryListItem ).toInt() ); - - // update channel info at the server - pClient->SetRemoteInfo(); -} - -void CClientSettingsDlg::OnCityTextChanged ( const QString& strNewCity ) -{ - // check length - if ( strNewCity.length() <= MAX_LEN_SERVER_CITY ) - { - // refresh internal name parameter - pClient->ChannelInfo.strCity = strNewCity; - - // update channel info at the server - pClient->SetRemoteInfo(); - } - else - { - // text is too long, update control with shortened text - pedtCity->setText ( strNewCity.left ( MAX_LEN_SERVER_CITY ) ); - } -} - -void CClientSettingsDlg::OnSkillActivated ( int iCntryListItem ) -{ - // set the new value in the data base - pClient->ChannelInfo.eSkillLevel = static_cast ( pcbxSkill->itemData ( iCntryListItem ).toInt() ); - - // update channel info at the server - pClient->SetRemoteInfo(); -} - -void CClientSettingsDlg::OnMakeTabChange ( int iTab ) -{ - tabSettings->setCurrentIndex ( iTab ); - - pSettings->iSettingsTab = iTab; -} - -void CClientSettingsDlg::OnTabChanged ( void ) { pSettings->iSettingsTab = tabSettings->currentIndex(); } - -void CClientSettingsDlg::UpdateAudioFaderSlider() -{ - // update slider and label of audio fader - const int iCurAudInFader = pClient->GetAudioInFader(); - sldAudioPan->setValue ( iCurAudInFader ); - - // show in label the center position and what channel is - // attenuated - if ( iCurAudInFader == AUD_FADER_IN_MIDDLE ) - { - lblAudioPanValue->setText ( tr ( "Center" ) ); - } - else - { - if ( iCurAudInFader > AUD_FADER_IN_MIDDLE ) - { - // attenuation on right channel - lblAudioPanValue->setText ( tr ( "L" ) + " -" + QString().setNum ( iCurAudInFader - AUD_FADER_IN_MIDDLE ) ); - } - else - { - // attenuation on left channel - lblAudioPanValue->setText ( tr ( "R" ) + " -" + QString().setNum ( AUD_FADER_IN_MIDDLE - iCurAudInFader ) ); - } - } -} - -void CClientSettingsDlg::OnAudioPanValueChanged ( int value ) -{ - pClient->SetAudioInFader ( value ); - UpdateAudioFaderSlider(); -} diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h deleted file mode 100644 index 4678aeb32f..0000000000 --- a/src/clientsettingsdlg.h +++ /dev/null @@ -1,122 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "global.h" -#include "util.h" -#include "client.h" -#include "settings.h" -#include "multicolorled.h" -#include "ui_clientsettingsdlgbase.h" - -/* Definitions ****************************************************************/ -// update time for GUI controls -#define DISPLAY_UPDATE_TIME 1000 // ms - -/* Classes ********************************************************************/ -class CClientSettingsDlg : public CBaseDlg, private Ui_CClientSettingsDlgBase -{ - Q_OBJECT - -public: - CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSetP, QWidget* parent = nullptr ); - - void UpdateUploadRate(); - void UpdateDisplay(); - void UpdateSoundDeviceChannelSelectionFrame(); - - void SetEnableFeedbackDetection ( bool enable ); - -protected: - void UpdateJitterBufferFrame(); - void UpdateSoundCardFrame(); - void UpdateDirectoryComboBox(); - void UpdateAudioFaderSlider(); - QString GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText = "" ); - - virtual void showEvent ( QShowEvent* ); - - CClient* pClient; - CClientSettings* pSettings; - QTimer TimerStatus; - QButtonGroup SndCrdBufferDelayButtonGroup; - -public slots: - void OnTimerStatus() { UpdateDisplay(); } - void OnNetBufValueChanged ( int value ); - void OnNetBufServerValueChanged ( int value ); - void OnAutoJitBufStateChanged ( int value ); - void OnEnableOPUS64StateChanged ( int value ); - void OnFeedbackDetectionChanged ( int value ); - void OnCustomDirectoriesChanged ( bool bDelete ); - void OnNewClientLevelEditingFinished() { pSettings->iNewClientFaderLevel = edtNewClientLevel->text().toInt(); } - void OnInputBoostChanged(); - void OnSndCrdBufferDelayButtonGroupClicked ( QAbstractButton* button ); - void OnSoundcardActivated ( int iSndDevIdx ); - void OnLInChanActivated ( int iChanIdx ); - void OnRInChanActivated ( int iChanIdx ); - void OnLOutChanActivated ( int iChanIdx ); - void OnROutChanActivated ( int iChanIdx ); - void OnAudioChannelsActivated ( int iChanIdx ); - void OnAudioQualityActivated ( int iQualityIdx ); - void OnGUIDesignActivated ( int iDesignIdx ); - void OnMeterStyleActivated ( int iMeterStyleIdx ); - void OnAudioAlertsChanged ( int value ); - void OnLanguageChanged ( QString strLanguage ) { pSettings->strLanguage = strLanguage; } - void OnAliasTextChanged ( const QString& strNewName ); - void OnInstrumentActivated ( int iCntryListItem ); - void OnCountryActivated ( int iCntryListItem ); - void OnCityTextChanged ( const QString& strNewName ); - void OnSkillActivated ( int iCntryListItem ); - void OnTabChanged(); - void OnMakeTabChange ( int iTabIdx ); - void OnAudioPanValueChanged ( int value ); - -#if defined( _WIN32 ) && !defined( WITH_JACK ) - // Only include this slot for Windows when JACK is NOT used - void OnDriverSetupClicked(); -#endif - -signals: - void GUIDesignChanged(); - void MeterStyleChanged(); - void AudioAlertsChanged(); - void AudioChannelsChanged(); - void CustomDirectoriesChanged(); - void NumMixerPanelRowsChanged ( int value ); -}; diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui deleted file mode 100644 index 7ee311701a..0000000000 --- a/src/clientsettingsdlgbase.ui +++ /dev/null @@ -1,1387 +0,0 @@ - - - CClientSettingsDlgBase - - - - 0 - 0 - 436 - 524 - - - - Settings - - - - :/png/main/res/fronticon.png:/png/main/res/fronticon.png - - - true - - - - - - - 0 - 0 - - - - 1 - - - true - - - - My Profile - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Preferred - - - - 20 - 13 - - - - - - - - Musician's Profile - - - - - - - - - - Alias/Name - - - pedtAlias - - - - - - - Instrument - - - pcbxInstrument - - - - - - - Country/Region - - - pcbxCountry - - - - - - - City - - - pedtCity - - - - - - - Skill - - - pcbxSkill - - - - - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - - - - - - 150 - 0 - - - - - - - - - 150 - 0 - - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - - - - - - 150 - 0 - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - User Interface - - - - - - - - - - Skin - - - cbxSkin - - - - - - - Meter Style - - - cbxMeterStyle - - - - - - - Language - - - cbxLanguage - - - - - - - Mixer Rows - - - spnMixerRows - - - - - - - - - - - - - - - - - - - - - - - - - 150 - 0 - - - - - - - - - 0 - 0 - - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 1 - - - 8 - - - - - - - Audio Alerts - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 14 - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - Audio/Network Setup - - - - - - - - - - Audio Device - - - cbxSoundcard - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 10 - - - - - - - - - 0 - 0 - - - - Driver Setup - - - false - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Input Channel Mapping - - - - - - - 3 - - - - - - - - 0 - 0 - - - - L - - - cbxLInChan - - - - - - - - 0 - 0 - - - - R - - - cbxRInChan - - - - - - - - - 3 - - - - - - - - - - - - - - - Output Channel Mapping - - - - - - - 3 - - - - - - - - 0 - 0 - - - - L - - - cbxLOutChan - - - - - - - - 0 - 0 - - - - R - - - cbxROutChan - - - - - - - - - 3 - - - - - - - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::Expanding - - - - 20 - 10 - - - - - - - - - - - - Audio Channels - - - cbxAudioChannels - - - - - - - Audio Quality - - - cbxAudioQuality - - - - - - - - - - - - - - - - - - - - - Qt::Vertical - - - - 20 - 10 - - - - - - - - Buffer Delay - - - - - - - 0 - 0 - - - - (preferred) - - - - - - - - 0 - 0 - - - - (default) - - - - - - - - 0 - 0 - - - - (safe) - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 0 - 20 - - - - - - - - - - Jitter Buffer - - - - - - - 0 - 0 - - - - Auto - - - - - - - - - Local - - - Qt::AlignCenter - - - false - - - - - - - Server - - - Qt::AlignCenter - - - false - - - - - - - - - - - Size - - - Qt::AlignCenter - - - false - - - - - - - Size - - - Qt::AlignCenter - - - false - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 0 - - - - - - - - 1 - - - 20 - - - 1 - - - Qt::Vertical - - - QSlider::TicksBothSides - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 0 - - - - - - - - 1 - - - 20 - - - 1 - - - Qt::Vertical - - - QSlider::TicksBothSides - - - - - - - - - - - - - 0 - 0 - - - - Small Network Buffers - - - - - - - Audio Stream Rate - - - - 2 - - - - - - - Qt::Horizontal - - - - 1 - 5 - - - - - - - - kbps - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - val - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - 2 - - - - - - - Qt::Horizontal - - - - 1 - 5 - - - - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 20 - - - - - - - - - - - Advanced Setup - - - - - - - - - - Qt::Horizontal - - - - 10 - 20 - - - - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 17 - 20 - - - - - - - - Custom Directories: - - - cbxCustomDirectories - - - - - - - - - true - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - New Client Level - - - edtNewClientLevel - - - - - - - - - - - - % - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Input Boost - - - cbxInputBoost - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - Feedback Protection - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - Qt::Horizontal - - - - 10 - 20 - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 20 - 20 - - - - - - - - Input Balance - - - - - - 3 - - - - - Pan - - - Qt::AlignCenter - - - sldAudioPan - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - - - 1 - - - Qt::Horizontal - - - QSlider::TicksBothSides - - - - - - - Qt::Horizontal - - - QSizePolicy::Minimum - - - - 0 - 20 - - - - - - - - - - - 50 - 0 - - - - Center - - - Qt::AlignCenter - - - false - - - - - - - - - - - - Qt::Horizontal - - - QSizePolicy::Preferred - - - - 20 - 20 - - - - - - - - - - - - Qt::Vertical - - - - 367 - 13 - - - - - - - - - - - - - CLanguageComboBox - QComboBox -
util.h
-
-
- - pedtAlias - pcbxInstrument - pcbxCountry - pedtCity - pcbxSkill - cbxSkin - cbxMeterStyle - cbxLanguage - spnMixerRows - chbAudioAlerts - cbxSoundcard - butDriverSetup - cbxLInChan - cbxRInChan - cbxLOutChan - cbxROutChan - cbxAudioChannels - cbxAudioQuality - rbtBufferDelayPreferred - rbtBufferDelayDefault - rbtBufferDelaySafe - chbAutoJitBuf - sldNetBuf - sldNetBufServer - chbSmallNetworkBuffers - cbxCustomDirectories - tbtDeleteCustomDirectory - edtNewClientLevel - cbxInputBoost - chbDetectFeedback - sldAudioPan - - - - - -
diff --git a/src/connectdlg.cpp b/src/connectdlg.cpp deleted file mode 100644 index 06c0dfccfc..0000000000 --- a/src/connectdlg.cpp +++ /dev/null @@ -1,1034 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#include "connectdlg.h" - -/* Implementation *************************************************************/ -CConnectDlg::CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, const bool bNEnableIPv6, QWidget* parent ) : - CBaseDlg ( parent, Qt::Dialog ), - pSettings ( pNSetP ), - strSelectedAddress ( "" ), - strSelectedServerName ( "" ), - bShowCompleteRegList ( bNewShowCompleteRegList ), - bServerListReceived ( false ), - bReducedServerListReceived ( false ), - bServerListItemWasChosen ( false ), - bListFilterWasActive ( false ), - bShowAllMusicians ( true ), - bEnableIPv6 ( bNEnableIPv6 ) -{ - setupUi ( this ); - - // Add help text to controls ----------------------------------------------- - // directory - QString strDirectoryWT = "" + tr ( "Directory" ) + ": " + - tr ( "Shows the servers listed by the selected directory. " - "You can add custom directories in Advanced Settings." ); - QString strDirectoryAN = tr ( "Directory combo box" ); - - lblList->setWhatsThis ( strDirectoryWT ); - lblList->setAccessibleName ( strDirectoryAN ); - cbxDirectory->setWhatsThis ( strDirectoryWT ); - cbxDirectory->setAccessibleName ( strDirectoryAN ); - - // filter - QString strFilterWT = "" + tr ( "Filter" ) + ": " + - tr ( "Filters the server list by the given text. Note that the filter is case insensitive. " - "A single # character will filter for those servers with at least one person connected." ); - QString strFilterAN = tr ( "Filter edit box" ); - lblFilter->setWhatsThis ( strFilterWT ); - edtFilter->setWhatsThis ( strFilterWT ); - - lblFilter->setAccessibleName ( strFilterAN ); - edtFilter->setAccessibleName ( strFilterAN ); - - // show all mucisians - chbExpandAll->setWhatsThis ( "" + tr ( "Show All Musicians" ) + ": " + - tr ( "Uncheck to collapse the server list to show just the server details. " - "Check to show everyone on the servers." ) ); - - chbExpandAll->setAccessibleName ( tr ( "Show all musicians check box" ) ); - - // server list view - lvwServers->setWhatsThis ( "" + tr ( "Server List" ) + ": " + - tr ( "The Connection Setup window lists the available servers registered with " - "the selected directory. Use the Directory dropdown to change the directory, " - "find the server you want to join in the server list, click on it, and " - "then click the Connect button to connect. Alternatively, double click on " - "the server name to connect." ) + - "
" + tr ( "Permanent servers (those that have been listed for longer than 48 hours) are shown in bold." ) + - "
" + tr ( "You can add custom directories in Advanced Settings." ) ); - - lvwServers->setAccessibleName ( tr ( "Server list view" ) ); - - // server address - QString strServAddrH = "" + tr ( "Server Address" ) + ": " + - tr ( "If you know the server address, you can connect to it " - "using the Server name/Address field. An optional port number can be added after the server " - "address using a colon as a separator, e.g. %1. " - "The field will also show a list of the most recently used server addresses." ) - .arg ( QString ( "example.org:%1" ).arg ( DEFAULT_PORT_NUMBER ) ); - - lblServerAddr->setWhatsThis ( strServAddrH ); - cbxServerAddr->setWhatsThis ( strServAddrH ); - - cbxServerAddr->setAccessibleName ( tr ( "Server address edit box" ) ); - cbxServerAddr->setAccessibleDescription ( tr ( "Holds the current server address. It also stores old addresses in the combo box list." ) ); - - tbtDeleteServerAddr->setAccessibleName ( tr ( "Delete server address button" ) ); - tbtDeleteServerAddr->setWhatsThis ( "" + tr ( "Delete Server Address" ) + ": " + - tr ( "Click the button to clear the currently selected server address " - "and delete it from the list of stored servers." ) ); - tbtDeleteServerAddr->setText ( u8"\u232B" ); - - UpdateDirectoryComboBox(); - - // init server address combo box (max MAX_NUM_SERVER_ADDR_ITEMS entries) - cbxServerAddr->setMaxCount ( MAX_NUM_SERVER_ADDR_ITEMS ); - cbxServerAddr->setInsertPolicy ( QComboBox::NoInsert ); - - // set up list view for connected clients (note that the last column size - // must not be specified since this column takes all the remaining space) -#ifdef ANDROID - // for Android we need larger numbers because of the default font size - lvwServers->setColumnWidth ( LVC_NAME, 200 ); - lvwServers->setColumnWidth ( LVC_PING, 130 ); - lvwServers->setColumnWidth ( LVC_CLIENTS, 100 ); - lvwServers->setColumnWidth ( LVC_VERSION, 110 ); -#else - lvwServers->setColumnWidth ( LVC_NAME, 180 ); - lvwServers->setColumnWidth ( LVC_PING, 75 ); - lvwServers->setColumnWidth ( LVC_CLIENTS, 70 ); - lvwServers->setColumnWidth ( LVC_LOCATION, 220 ); - lvwServers->setColumnWidth ( LVC_VERSION, 65 ); -#endif - lvwServers->clear(); - - // make sure we do not get a too long horizontal scroll bar - lvwServers->header()->setStretchLastSection ( false ); - - // add invisible columns which are used for sorting the list and storing - // the current/maximum number of clients - // 0: server name - // 1: ping time - // 2: number of musicians (including additional strings like " (full)") - // 3: location - // 4: server version - // 5: minimum ping time (invisible) - // 6: maximum number of clients (invisible) - // (see EConnectListViewColumns in connectdlg.h, which must match the above) - - lvwServers->setColumnCount ( LVC_COLUMNS ); - lvwServers->hideColumn ( LVC_PING_MIN_HIDDEN ); - lvwServers->hideColumn ( LVC_CLIENTS_MAX_HIDDEN ); - - // per default the root shall not be decorated (to save space) - lvwServers->setRootIsDecorated ( false ); - - // make sure the connect button has the focus - butConnect->setFocus(); - - // for "show all servers" mode make sort by click on header possible - if ( bShowCompleteRegList ) - { - lvwServers->setSortingEnabled ( true ); - lvwServers->sortItems ( LVC_NAME, Qt::AscendingOrder ); - } - - // set a placeholder text to explain how to filter occupied servers (#397) - edtFilter->setPlaceholderText ( tr ( "Filter text, or # for occupied servers" ) ); - - // setup timers - TimerInitialSort.setSingleShot ( true ); // only once after list request - -#if defined( ANDROID ) || defined( Q_OS_IOS ) - // for the Android and iOS version maximize the window - setWindowState ( Qt::WindowMaximized ); -#endif - - // Connections ------------------------------------------------------------- - // list view - QObject::connect ( lvwServers, &QTreeWidget::itemDoubleClicked, this, &CConnectDlg::OnServerListItemDoubleClicked ); - - // to get default return key behaviour working - QObject::connect ( lvwServers, &QTreeWidget::activated, this, &CConnectDlg::OnConnectClicked ); - - // line edit - QObject::connect ( edtFilter, &QLineEdit::textEdited, this, &CConnectDlg::OnFilterTextEdited ); - - // combo boxes - QObject::connect ( cbxServerAddr, &QComboBox::editTextChanged, this, &CConnectDlg::OnServerAddrEditTextChanged ); - - QObject::connect ( cbxDirectory, static_cast ( &QComboBox::activated ), this, &CConnectDlg::OnDirectoryChanged ); - - // check boxes - QObject::connect ( chbExpandAll, &QCheckBox::stateChanged, this, &CConnectDlg::OnExpandAllStateChanged ); - - // buttons - QObject::connect ( butCancel, &QPushButton::clicked, this, &CConnectDlg::close ); - - QObject::connect ( butConnect, &QPushButton::clicked, this, &CConnectDlg::OnConnectClicked ); - - // tool buttons - QObject::connect ( tbtDeleteServerAddr, &QToolButton::clicked, this, &CConnectDlg::OnDeleteServerAddrClicked ); - - // timers - QObject::connect ( &TimerPing, &QTimer::timeout, this, &CConnectDlg::OnTimerPing ); - - QObject::connect ( &TimerReRequestServList, &QTimer::timeout, this, &CConnectDlg::OnTimerReRequestServList ); -} - -void CConnectDlg::showEvent ( QShowEvent* ) -{ - // load stored IP addresses in combo box - cbxServerAddr->clear(); - cbxServerAddr->clearEditText(); - - for ( int iLEIdx = 0; iLEIdx < MAX_NUM_SERVER_ADDR_ITEMS; iLEIdx++ ) - { - if ( !pSettings->vstrIPAddress[iLEIdx].isEmpty() ) - { - cbxServerAddr->addItem ( pSettings->vstrIPAddress[iLEIdx] ); - } - } - - // on opening the connect dialg, we always want to request a - // new updated server list per definition - RequestServerList(); -} - -void CConnectDlg::RequestServerList() -{ - // reset flags - bServerListReceived = false; - bReducedServerListReceived = false; - bServerListItemWasChosen = false; - bListFilterWasActive = false; - - // clear current address and name - strSelectedAddress = ""; - strSelectedServerName = ""; - - // clear server list view - lvwServers->clear(); - - // update list combo box (disable events to avoid a signal) - cbxDirectory->blockSignals ( true ); - if ( pSettings->eDirectoryType == AT_CUSTOM ) - { - // iCustomDirectoryIndex is non-zero only if eDirectoryType == AT_CUSTOM - // find the combobox item that corresponds to vstrDirectoryAddress[iCustomDirectoryIndex] - // (the current selected custom directory) - cbxDirectory->setCurrentIndex ( cbxDirectory->findData ( QVariant ( pSettings->iCustomDirectoryIndex ) ) ); - } - else - { - cbxDirectory->setCurrentIndex ( static_cast ( pSettings->eDirectoryType ) ); - } - cbxDirectory->blockSignals ( false ); - - // Get the IP address of the directory server (using the ParseNetworAddress - // function) when the connect dialog is opened, this seems to be the correct - // time to do it. Note that in case of custom directories we - // use iCustomDirectoryIndex as an index into the vector. - - // Allow IPv4 only for communicating with Directories - if ( NetworkUtil().ParseNetworkAddress ( - NetworkUtil::GetDirectoryAddress ( pSettings->eDirectoryType, pSettings->vstrDirectoryAddress[pSettings->iCustomDirectoryIndex] ), - haDirectoryAddress, - false ) ) - { - // send the request for the server list - emit ReqServerListQuery ( haDirectoryAddress ); - - // start timer, if this message did not get any respond to retransmit - // the server list request message - TimerReRequestServList.start ( SERV_LIST_REQ_UPDATE_TIME_MS ); - TimerInitialSort.start ( SERV_LIST_REQ_UPDATE_TIME_MS ); // reuse the time value - } -} - -void CConnectDlg::hideEvent ( QHideEvent* ) -{ - // if window is closed, stop timers - TimerPing.stop(); - TimerReRequestServList.stop(); -} - -void CConnectDlg::OnDirectoryChanged ( int iTypeIdx ) -{ - // store the new directory type and request new list - // if iTypeIdx == AT_CUSTOM, then iCustomDirectoryIndex is the index into the vector holding the user's custom directory servers - // if iTypeIdx != AT_CUSTOM, then iCustomDirectoryIndex MUST be 0; - if ( iTypeIdx >= AT_CUSTOM ) - { - // the value for the index into the vector vstrDirectoryAddress is in the user data of the combobox item - pSettings->iCustomDirectoryIndex = cbxDirectory->itemData ( iTypeIdx ).toInt(); - iTypeIdx = AT_CUSTOM; - } - else - { - pSettings->iCustomDirectoryIndex = 0; - } - pSettings->eDirectoryType = static_cast ( iTypeIdx ); - RequestServerList(); -} - -void CConnectDlg::OnTimerReRequestServList() -{ - // if the server list is not yet received, retransmit the request for the - // server list - if ( !bServerListReceived ) - { - // note that this is a connection less message which may get lost - // and therefore it makes sense to re-transmit it - emit ReqServerListQuery ( haDirectoryAddress ); - } -} - -void CConnectDlg::SetServerList ( const CHostAddress& InetAddr, const CVector& vecServerInfo, const bool bIsReducedServerList ) -{ - // If the normal list was received, we do not accept any further list - // updates (to avoid the reduced list overwrites the normal list (#657)). Also, - // we only accept a server list from the server address we have sent the - // request for this to (note that we cannot use the port number since the - // receive port and send port might be different at the directory server). - if ( bServerListReceived || ( InetAddr.InetAddr != haDirectoryAddress.InetAddr ) ) - { - return; - } - - // special treatment if a reduced server list was received - if ( bIsReducedServerList ) - { - // make sure we only apply the reduced version list once - if ( bReducedServerListReceived ) - { - // do nothing - return; - } - else - { - bReducedServerListReceived = true; - } - } - else - { - // set flag and disable timer for resend server list request if full list - // was received (i.e. not the reduced list) - bServerListReceived = true; - TimerReRequestServList.stop(); - } - - // first clear list - lvwServers->clear(); - - // add list item for each server in the server list - const int iServerInfoLen = vecServerInfo.Size(); - - for ( int iIdx = 0; iIdx < iServerInfoLen; iIdx++ ) - { - // get the host address, note that for the very first entry which is - // the directory server, we have to use the receive host address - // instead - CHostAddress CurHostAddress; - - if ( iIdx > 0 ) - { - CurHostAddress = vecServerInfo[iIdx].HostAddr; - } - else - { - // substitute the receive host address for directory server - CurHostAddress = InetAddr; - } - - // create new list view item - QTreeWidgetItem* pNewListViewItem = new QTreeWidgetItem ( lvwServers ); - - // make the entry invisible (will be set to visible on successful ping - // result) if the complete list of registered servers shall not be shown - if ( !bShowCompleteRegList ) - { - pNewListViewItem->setHidden ( true ); - } - - // server name (if empty, show host address instead) - if ( !vecServerInfo[iIdx].strName.isEmpty() ) - { - pNewListViewItem->setText ( LVC_NAME, vecServerInfo[iIdx].strName ); - } - else - { - // IP address and port (use IP number without last byte) - // Definition: If the port number is the default port number, we do - // not show it. - if ( vecServerInfo[iIdx].HostAddr.iPort == DEFAULT_PORT_NUMBER ) - { - // only show IP number, no port number - pNewListViewItem->setText ( LVC_NAME, CurHostAddress.toString ( CHostAddress::SM_IP_NO_LAST_BYTE ) ); - } - else - { - // show IP number and port - pNewListViewItem->setText ( LVC_NAME, CurHostAddress.toString ( CHostAddress::SM_IP_NO_LAST_BYTE_PORT ) ); - } - } - - // in case of all servers shown, add the registration number at the beginning - if ( bShowCompleteRegList ) - { - pNewListViewItem->setText ( LVC_NAME, QString ( "%1: " ).arg ( 1 + iIdx, 3 ) + pNewListViewItem->text ( LVC_NAME ) ); - } - - // show server name in bold font if it is a permanent server - QFont CurServerNameFont = pNewListViewItem->font ( LVC_NAME ); - CurServerNameFont.setBold ( vecServerInfo[iIdx].bPermanentOnline ); - pNewListViewItem->setFont ( LVC_NAME, CurServerNameFont ); - - // the ping time shall be shown in bold font - QFont CurPingTimeFont = pNewListViewItem->font ( LVC_PING ); - CurPingTimeFont.setBold ( true ); - pNewListViewItem->setFont ( LVC_PING, CurPingTimeFont ); - - // server location (city and country) - QString strLocation = vecServerInfo[iIdx].strCity; - - if ( ( !strLocation.isEmpty() ) && ( vecServerInfo[iIdx].eCountry != QLocale::AnyCountry ) ) - { - strLocation += ", "; - } - - if ( vecServerInfo[iIdx].eCountry != QLocale::AnyCountry ) - { - QString strCountryToString = QLocale::countryToString ( vecServerInfo[iIdx].eCountry ); - - // Qt countryToString does not use spaces in between country name - // parts but they use upper case letters which we can detect and - // insert spaces as a post processing -#if QT_VERSION >= QT_VERSION_CHECK( 5, 0, 0 ) - if ( !strCountryToString.contains ( " " ) ) - { - QRegularExpressionMatchIterator reMatchIt = QRegularExpression ( "[A-Z][^A-Z]*" ).globalMatch ( strCountryToString ); - QStringList slNames; - while ( reMatchIt.hasNext() ) - { - slNames << reMatchIt.next().capturedTexts(); - } - strCountryToString = slNames.join ( " " ); - } -#endif - - strLocation += strCountryToString; - } - - pNewListViewItem->setText ( LVC_LOCATION, strLocation ); - - // init the minimum ping time with a large number (note that this number - // must fit in an integer type) - pNewListViewItem->setText ( LVC_PING_MIN_HIDDEN, "99999999" ); - - // store the maximum number of clients - pNewListViewItem->setText ( LVC_CLIENTS_MAX_HIDDEN, QString().setNum ( vecServerInfo[iIdx].iMaxNumClients ) ); - - // store host address - pNewListViewItem->setData ( LVC_NAME, Qt::UserRole, CurHostAddress.toString() ); - - // per default expand the list item (if not "show all servers") - if ( bShowAllMusicians ) - { - lvwServers->expandItem ( pNewListViewItem ); - } - } - - // immediately issue the ping measurements and start the ping timer since - // the server list is filled now - OnTimerPing(); - TimerPing.start ( PING_UPDATE_TIME_SERVER_LIST_MS ); -} - -void CConnectDlg::SetConnClientsList ( const CHostAddress& InetAddr, const CVector& vecChanInfo ) -{ - // find the server with the correct address - QTreeWidgetItem* pCurListViewItem = FindListViewItem ( InetAddr ); - - if ( pCurListViewItem ) - { - // first remove any existing children - DeleteAllListViewItemChilds ( pCurListViewItem ); - - // get number of connected clients - const int iNumConnectedClients = vecChanInfo.Size(); - - for ( int i = 0; i < iNumConnectedClients; i++ ) - { - // create new list view item - QTreeWidgetItem* pNewChildListViewItem = new QTreeWidgetItem ( pCurListViewItem ); - - // child items shall use only one column - pNewChildListViewItem->setFirstColumnSpanned ( true ); - - // set the clients name - QString sClientText = vecChanInfo[i].strName; - - // set the icon: country flag has priority over instrument - bool bCountryFlagIsUsed = false; - - if ( vecChanInfo[i].eCountry != QLocale::AnyCountry ) - { - // try to load the country flag icon - QPixmap CountryFlagPixmap ( CLocale::GetCountryFlagIconsResourceReference ( vecChanInfo[i].eCountry ) ); - - // first check if resource reference was valid - if ( !CountryFlagPixmap.isNull() ) - { - // set correct picture - pNewChildListViewItem->setIcon ( LVC_NAME, QIcon ( CountryFlagPixmap ) ); - - bCountryFlagIsUsed = true; - } - } - - if ( !bCountryFlagIsUsed ) - { - // get the resource reference string for this instrument - const QString strCurResourceRef = CInstPictures::GetResourceReference ( vecChanInfo[i].iInstrument ); - - // first check if instrument picture is used or not and if it is valid - if ( !( CInstPictures::IsNotUsedInstrument ( vecChanInfo[i].iInstrument ) || strCurResourceRef.isEmpty() ) ) - { - // set correct picture - pNewChildListViewItem->setIcon ( LVC_NAME, QIcon ( QPixmap ( strCurResourceRef ) ) ); - } - } - - // add the instrument information as text - if ( !CInstPictures::IsNotUsedInstrument ( vecChanInfo[i].iInstrument ) ) - { - sClientText.append ( " (" + CInstPictures::GetName ( vecChanInfo[i].iInstrument ) + ")" ); - } - - // apply the client text to the list view item - pNewChildListViewItem->setText ( LVC_NAME, sClientText ); - - // add the new child to the corresponding server item - pCurListViewItem->addChild ( pNewChildListViewItem ); - - // at least one server has children now, show decoration to be able - // to show the children - lvwServers->setRootIsDecorated ( true ); - } - - // the clients list may have changed, update the filter selection - UpdateListFilter(); - } -} - -void CConnectDlg::OnServerListItemDoubleClicked ( QTreeWidgetItem* Item, int ) -{ - // if a server list item was double clicked, it is the same as if the - // connect button was clicked - if ( Item != nullptr ) - { - OnConnectClicked(); - } -} - -void CConnectDlg::OnServerAddrEditTextChanged ( const QString& ) -{ - // in the server address combo box, a text was changed, remove selection - // in the server list (if any) - lvwServers->clearSelection(); -} - -void CConnectDlg::OnCustomDirectoriesChanged() -{ - - QString strPreviousSelection = cbxDirectory->currentText(); - UpdateDirectoryComboBox(); - // after updating the combobox, we must re-select the previous directory selection - - if ( pSettings->eDirectoryType == AT_CUSTOM ) - { - // check if the currently select custom directory still exists in the now potentially re-ordered vector, - // if so, then change to its new index. (addresses Issue #1899) - int iNewIndex = cbxDirectory->findText ( strPreviousSelection, Qt::MatchExactly ); - if ( iNewIndex == INVALID_INDEX ) - { - // previously selected custom directory has been deleted. change to default directory - pSettings->eDirectoryType = static_cast ( AT_DEFAULT ); - pSettings->iCustomDirectoryIndex = 0; - RequestServerList(); - } - else - { - // find previously selected custom directory in the now potentially re-ordered vector - pSettings->eDirectoryType = static_cast ( AT_CUSTOM ); - pSettings->iCustomDirectoryIndex = cbxDirectory->itemData ( iNewIndex ).toInt(); - cbxDirectory->blockSignals ( true ); - cbxDirectory->setCurrentIndex ( cbxDirectory->findData ( QVariant ( pSettings->iCustomDirectoryIndex ) ) ); - cbxDirectory->blockSignals ( false ); - } - } - else - { - // selected directory was not a custom directory - cbxDirectory->blockSignals ( true ); - cbxDirectory->setCurrentIndex ( static_cast ( pSettings->eDirectoryType ) ); - cbxDirectory->blockSignals ( false ); - } -} - -void CConnectDlg::ShowAllMusicians ( const bool bState ) -{ - bShowAllMusicians = bState; - - // update list - if ( bState ) - { - lvwServers->expandAll(); - } - else - { - lvwServers->collapseAll(); - } - - // update check box if necessary - if ( ( chbExpandAll->checkState() == Qt::Checked && !bShowAllMusicians ) || ( chbExpandAll->checkState() == Qt::Unchecked && bShowAllMusicians ) ) - { - chbExpandAll->setCheckState ( bState ? Qt::Checked : Qt::Unchecked ); - } -} - -void CConnectDlg::UpdateListFilter() -{ - const QString sFilterText = edtFilter->text(); - - if ( !sFilterText.isEmpty() ) - { - bListFilterWasActive = true; - const int iServerListLen = lvwServers->topLevelItemCount(); - - for ( int iIdx = 0; iIdx < iServerListLen; iIdx++ ) - { - QTreeWidgetItem* pCurListViewItem = lvwServers->topLevelItem ( iIdx ); - bool bFilterFound = false; - - // DEFINITION: if "#" is set at the beginning of the filter text, we show - // occupied servers (#397) - if ( ( sFilterText.indexOf ( "#" ) == 0 ) && ( sFilterText.length() == 1 ) ) - { - // special case: filter for occupied servers - if ( pCurListViewItem->childCount() > 0 ) - { - bFilterFound = true; - } - } - else - { - // search server name - if ( pCurListViewItem->text ( LVC_NAME ).indexOf ( sFilterText, 0, Qt::CaseInsensitive ) >= 0 ) - { - bFilterFound = true; - } - - // search location - if ( pCurListViewItem->text ( LVC_LOCATION ).indexOf ( sFilterText, 0, Qt::CaseInsensitive ) >= 0 ) - { - bFilterFound = true; - } - - // search children - for ( int iCCnt = 0; iCCnt < pCurListViewItem->childCount(); iCCnt++ ) - { - if ( pCurListViewItem->child ( iCCnt )->text ( LVC_NAME ).indexOf ( sFilterText, 0, Qt::CaseInsensitive ) >= 0 ) - { - bFilterFound = true; - } - } - } - - // only update Hide state if ping time was received - if ( !pCurListViewItem->text ( LVC_PING ).isEmpty() || bShowCompleteRegList ) - { - // only update hide and expand status if the hide state has to be changed to - // preserve if user clicked on expand icon manually - if ( ( pCurListViewItem->isHidden() && bFilterFound ) || ( !pCurListViewItem->isHidden() && !bFilterFound ) ) - { - pCurListViewItem->setHidden ( !bFilterFound ); - pCurListViewItem->setExpanded ( bShowAllMusicians ); - } - } - } - } - else - { - // if the filter was active but is now disabled, we have to update all list - // view items for the "ping received" hide state - if ( bListFilterWasActive ) - { - const int iServerListLen = lvwServers->topLevelItemCount(); - - for ( int iIdx = 0; iIdx < iServerListLen; iIdx++ ) - { - QTreeWidgetItem* pCurListViewItem = lvwServers->topLevelItem ( iIdx ); - - // if ping time is empty, hide item (if not already hidden) - if ( pCurListViewItem->text ( LVC_PING ).isEmpty() && !bShowCompleteRegList ) - { - pCurListViewItem->setHidden ( true ); - } - else - { - // in case it was hidden, show it and take care of expand - if ( pCurListViewItem->isHidden() ) - { - pCurListViewItem->setHidden ( false ); - pCurListViewItem->setExpanded ( bShowAllMusicians ); - } - } - } - - bListFilterWasActive = false; - } - } -} - -void CConnectDlg::OnConnectClicked() -{ - // get the IP address to be used according to the following definitions: - // - if the list has focus and a line is selected, use this line - // - if the list has no focus, use the current combo box text - QList CurSelListItemList = lvwServers->selectedItems(); - - if ( CurSelListItemList.count() > 0 ) - { - // get the parent list view item - QTreeWidgetItem* pCurSelTopListItem = GetParentListViewItem ( CurSelListItemList[0] ); - - // get host address from selected list view item as a string - strSelectedAddress = pCurSelTopListItem->data ( LVC_NAME, Qt::UserRole ).toString(); - - // store selected server name - strSelectedServerName = pCurSelTopListItem->text ( LVC_NAME ); - - // set flag that a server list item was chosen to connect - bServerListItemWasChosen = true; - } - else - { - strSelectedAddress = NetworkUtil::FixAddress ( cbxServerAddr->currentText() ); - } - - // tell the parent window that the connection shall be initiated - done ( QDialog::Accepted ); -} - -void CConnectDlg::OnDeleteServerAddrClicked() -{ - if ( cbxServerAddr->currentText().isEmpty() ) - { - return; - } - - // move later items down one - for ( int iLEIdx = 0; iLEIdx < MAX_NUM_SERVER_ADDR_ITEMS - 1; iLEIdx++ ) - { - while ( pSettings->vstrIPAddress[iLEIdx].compare ( cbxServerAddr->currentText() ) == 0 ) - { - for ( int jLEIdx = iLEIdx + 1; jLEIdx < MAX_NUM_SERVER_ADDR_ITEMS; jLEIdx++ ) - { - pSettings->vstrIPAddress[jLEIdx - 1] = pSettings->vstrIPAddress[jLEIdx]; - } - } - } - // empty last entry - pSettings->vstrIPAddress[MAX_NUM_SERVER_ADDR_ITEMS - 1] = QString(); - - // redisplay to pick up updated list - showEvent ( nullptr ); -} - -void CConnectDlg::OnTimerPing() -{ - // send ping messages to the servers in the list - const int iServerListLen = lvwServers->topLevelItemCount(); - - for ( int iIdx = 0; iIdx < iServerListLen; iIdx++ ) - { - QTreeWidgetItem* pCurListViewItem = lvwServers->topLevelItem ( iIdx ); - - // we need to ask for the server version only if we have not received it - const bool bNeedVersion = pCurListViewItem->text ( LVC_VERSION ).isEmpty(); - - CHostAddress haServerAddress; - - // try to parse host address string which is stored as user data - // in the server list item GUI control element - if ( NetworkUtil().ParseNetworkAddress ( pCurListViewItem->data ( LVC_NAME, Qt::UserRole ).toString(), haServerAddress, bEnableIPv6 ) ) - { - // if address is valid, send ping message using a new thread -#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 ) - QFuture f = QtConcurrent::run ( &CConnectDlg::EmitCLServerListPingMes, this, haServerAddress, bNeedVersion ); - Q_UNUSED ( f ); -#else - QtConcurrent::run ( this, &CConnectDlg::EmitCLServerListPingMes, haServerAddress, bNeedVersion ); -#endif - } - } -} - -void CConnectDlg::EmitCLServerListPingMes ( const CHostAddress& haServerAddress, const bool bNeedVersion ) -{ - // The ping time messages for all servers should not be sent all in a very - // short time since it showed that this leads to errors in the ping time - // measurement (#49). We therefore introduce a short delay for each server - // (since we are doing this in a separate thread for each server, we do not - // block the GUI). - QThread::msleep ( 11 ); - - // first request the server version if we have not already received it - if ( bNeedVersion ) - { - emit CreateCLServerListReqVerAndOSMes ( haServerAddress ); - } - - emit CreateCLServerListPingMes ( haServerAddress ); -} - -void CConnectDlg::SetPingTimeAndNumClientsResult ( const CHostAddress& InetAddr, const int iPingTime, const int iNumClients ) -{ - // apply the received ping time to the correct server list entry - QTreeWidgetItem* pCurListViewItem = FindListViewItem ( InetAddr ); - - if ( pCurListViewItem ) - { - // check if this is the first time a ping time is set - const bool bIsFirstPing = pCurListViewItem->text ( LVC_PING ).isEmpty(); - bool bDoSorting = false; - - // update minimum ping time column (invisible, used for sorting) if - // the new value is smaller than the old value - int iMinPingTime = pCurListViewItem->text ( LVC_PING_MIN_HIDDEN ).toInt(); - - if ( iMinPingTime > iPingTime ) - { - // update the minimum ping time with the new lowest value - iMinPingTime = iPingTime; - - // we pad to a total of 8 characters with zeros to make sure the - // sorting is done correctly - pCurListViewItem->setText ( LVC_PING_MIN_HIDDEN, QString ( "%1" ).arg ( iPingTime, 8, 10, QLatin1Char ( '0' ) ) ); - - // update the sorting (lowest number on top) - bDoSorting = true; - } - - // for debugging it is good to see the current ping time in the list - // and not the minimum ping time -> overwrite the value for debugging - if ( bShowCompleteRegList ) - { - iMinPingTime = iPingTime; - } - - // Only show minimum ping time in the list since this is the important - // value. Temporary bad ping measurements are of no interest. - // Color definition: <= 25 ms green, <= 50 ms yellow, otherwise red - if ( iMinPingTime <= 25 ) - { - pCurListViewItem->setForeground ( LVC_PING, Qt::darkGreen ); - } - else - { - if ( iMinPingTime <= 50 ) - { - pCurListViewItem->setForeground ( LVC_PING, Qt::darkYellow ); - } - else - { - pCurListViewItem->setForeground ( LVC_PING, Qt::red ); - } - } - - // update ping text, take special care if ping time exceeds a - // certain value - if ( iMinPingTime > 500 ) - { - pCurListViewItem->setText ( LVC_PING, ">500 ms" ); - } - else - { - // prepend spaces so that we can sort correctly (fieldWidth of - // 4 is sufficient since the maximum width is ">500") (#201) - pCurListViewItem->setText ( LVC_PING, QString ( "%1 ms" ).arg ( iMinPingTime, 4, 10, QLatin1Char ( ' ' ) ) ); - } - - // update number of clients text - if ( pCurListViewItem->text ( LVC_CLIENTS_MAX_HIDDEN ).toInt() == 0 ) - { - // special case: reduced server list - pCurListViewItem->setText ( LVC_CLIENTS, QString().setNum ( iNumClients ) ); - } - else if ( iNumClients >= pCurListViewItem->text ( LVC_CLIENTS_MAX_HIDDEN ).toInt() ) - { - pCurListViewItem->setText ( LVC_CLIENTS, QString().setNum ( iNumClients ) + " (full)" ); - } - else - { - pCurListViewItem->setText ( LVC_CLIENTS, QString().setNum ( iNumClients ) + "/" + pCurListViewItem->text ( LVC_CLIENTS_MAX_HIDDEN ) ); - } - - // check if the number of child list items matches the number of - // connected clients, if not then request the client names - if ( iNumClients != pCurListViewItem->childCount() ) - { - emit CreateCLServerListReqConnClientsListMes ( InetAddr ); - } - - // this is the first time a ping time was received, set item to visible - if ( bIsFirstPing ) - { - pCurListViewItem->setHidden ( false ); - } - - // Update sorting. Note that the sorting must be the last action for the - // current item since the topLevelItem(iIdx) is then no longer valid. - // To avoid that the list is sorted shortly before a double click (which - // could lead to connecting an incorrect server) the sorting is disabled - // as long as the mouse is over the list (but it is not disabled for the - // initial timer of about 2s, see TimerInitialSort) (#293). - if ( bDoSorting && !bShowCompleteRegList && - ( TimerInitialSort.isActive() || !lvwServers->underMouse() ) ) // do not sort if "show all servers" - { - lvwServers->sortByColumn ( LVC_PING_MIN_HIDDEN, Qt::AscendingOrder ); - } - } - - // if no server item has children, do not show decoration - bool bAnyListItemHasChilds = false; - const int iServerListLen = lvwServers->topLevelItemCount(); - - for ( int iIdx = 0; iIdx < iServerListLen; iIdx++ ) - { - // check if the current list item has children - if ( lvwServers->topLevelItem ( iIdx )->childCount() > 0 ) - { - bAnyListItemHasChilds = true; - } - } - - if ( !bAnyListItemHasChilds ) - { - lvwServers->setRootIsDecorated ( false ); - } - - // we may have changed the Hidden state for some items, if a filter was active, we now - // have to update it to void lines appear which do not satisfy the filter criteria - UpdateListFilter(); -} - -void CConnectDlg::SetServerVersionResult ( const CHostAddress& InetAddr, const QString& strVersion ) -{ - // apply the received server version to the correct server list entry - QTreeWidgetItem* pCurListViewItem = FindListViewItem ( InetAddr ); - - if ( pCurListViewItem ) - { - pCurListViewItem->setText ( LVC_VERSION, strVersion ); - } -} - -QTreeWidgetItem* CConnectDlg::FindListViewItem ( const CHostAddress& InetAddr ) -{ - const int iServerListLen = lvwServers->topLevelItemCount(); - - for ( int iIdx = 0; iIdx < iServerListLen; iIdx++ ) - { - // compare the received address with the user data string of the - // host address by a string compare - if ( !lvwServers->topLevelItem ( iIdx )->data ( LVC_NAME, Qt::UserRole ).toString().compare ( InetAddr.toString() ) ) - { - return lvwServers->topLevelItem ( iIdx ); - } - } - - return nullptr; -} - -QTreeWidgetItem* CConnectDlg::GetParentListViewItem ( QTreeWidgetItem* pItem ) -{ - // check if the current item is already the top item, i.e. the parent - // query fails and returns null - if ( pItem->parent() ) - { - // we only have maximum one level, i.e. if we call the parent function - // we are at the top item - return pItem->parent(); - } - else - { - // this item is already the top item - return pItem; - } -} - -void CConnectDlg::DeleteAllListViewItemChilds ( QTreeWidgetItem* pItem ) -{ - // loop over all children - while ( pItem->childCount() > 0 ) - { - // get the first child in the list - QTreeWidgetItem* pCurChildItem = pItem->child ( 0 ); - - // remove it from the item (note that the object is not deleted) - pItem->removeChild ( pCurChildItem ); - - // delete the object to avoid a memory leak - delete pCurChildItem; - } -} - -void CConnectDlg::UpdateDirectoryComboBox() -{ - // directory type combo box - cbxDirectory->clear(); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_DEFAULT ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_ANY_GENRE2 ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_ANY_GENRE3 ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_ROCK ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_JAZZ ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_CLASSICAL_FOLK ) ); - cbxDirectory->addItem ( DirectoryTypeToString ( AT_GENRE_CHORAL ) ); - - // because custom directories are always added to the top of the vector, add the vector - // contents to the combobox in reverse order - for ( int i = MAX_NUM_SERVER_ADDR_ITEMS - 1; i >= 0; i-- ) - { - if ( pSettings->vstrDirectoryAddress[i] != "" ) - { - // add vector index (i) to the combobox as user data - cbxDirectory->addItem ( pSettings->vstrDirectoryAddress[i], i ); - } - } -} diff --git a/src/connectdlg.h b/src/connectdlg.h deleted file mode 100644 index 9be91cf657..0000000000 --- a/src/connectdlg.h +++ /dev/null @@ -1,128 +0,0 @@ -/******************************************************************************\ - * Copyright (c) 2004-2024 - * - * Author(s): - * Volker Fischer - * - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include "global.h" -#include "util.h" -#include "settings.h" -#include "multicolorled.h" -#include "ui_connectdlgbase.h" - -/* Definitions ****************************************************************/ -// defines the time interval at which the request server list message is re- -// transmitted until it is received -#define SERV_LIST_REQ_UPDATE_TIME_MS 2000 // ms - -/* Classes ********************************************************************/ -class CConnectDlg : public CBaseDlg, private Ui_CConnectDlgBase -{ - Q_OBJECT - -public: - CConnectDlg ( CClientSettings* pNSetP, const bool bNewShowCompleteRegList, const bool bNEnableIPv6, QWidget* parent = nullptr ); - - void SetShowAllMusicians ( const bool bState ) { ShowAllMusicians ( bState ); } - bool GetShowAllMusicians() { return bShowAllMusicians; } - - void SetServerList ( const CHostAddress& InetAddr, const CVector& vecServerInfo, const bool bIsReducedServerList = false ); - - void SetConnClientsList ( const CHostAddress& InetAddr, const CVector& vecChanInfo ); - - void SetPingTimeAndNumClientsResult ( const CHostAddress& InetAddr, const int iPingTime, const int iNumClients ); - void SetServerVersionResult ( const CHostAddress& InetAddr, const QString& strVersion ); - - bool GetServerListItemWasChosen() const { return bServerListItemWasChosen; } - QString GetSelectedAddress() const { return strSelectedAddress; } - QString GetSelectedServerName() const { return strSelectedServerName; } - -protected: - // NOTE: This enum must list the columns in the same order - // as their column headings in connectdlgbase.ui - enum EConnectListViewColumns - { - LVC_NAME, // server name - LVC_PING, // ping time - LVC_CLIENTS, // number of connected clients (including additional strings like " (full)") - LVC_LOCATION, // location - LVC_VERSION, // server version - LVC_PING_MIN_HIDDEN, // minimum ping time (invisible) - LVC_CLIENTS_MAX_HIDDEN, // maximum number of clients (invisible) - LVC_COLUMNS // total number of columns - }; - - virtual void showEvent ( QShowEvent* ); - virtual void hideEvent ( QHideEvent* ); - - QTreeWidgetItem* FindListViewItem ( const CHostAddress& InetAddr ); - QTreeWidgetItem* GetParentListViewItem ( QTreeWidgetItem* pItem ); - void DeleteAllListViewItemChilds ( QTreeWidgetItem* pItem ); - void UpdateListFilter(); - void ShowAllMusicians ( const bool bState ); - void RequestServerList(); - void EmitCLServerListPingMes ( const CHostAddress& haServerAddress, const bool bNeedVersion ); - void UpdateDirectoryComboBox(); - - CClientSettings* pSettings; - - QTimer TimerPing; - QTimer TimerReRequestServList; - QTimer TimerInitialSort; - CHostAddress haDirectoryAddress; - QString strSelectedAddress; - QString strSelectedServerName; - bool bShowCompleteRegList; - bool bServerListReceived; - bool bReducedServerListReceived; - bool bServerListItemWasChosen; - bool bListFilterWasActive; - bool bShowAllMusicians; - bool bEnableIPv6; - -public slots: - void OnServerListItemDoubleClicked ( QTreeWidgetItem* Item, int ); - void OnServerAddrEditTextChanged ( const QString& ); - void OnDirectoryChanged ( int iTypeIdx ); - void OnFilterTextEdited ( const QString& ) { UpdateListFilter(); } - void OnExpandAllStateChanged ( int value ) { ShowAllMusicians ( value == Qt::Checked ); } - void OnCustomDirectoriesChanged(); - void OnConnectClicked(); - void OnDeleteServerAddrClicked(); - void OnTimerPing(); - void OnTimerReRequestServList(); - -signals: - void ReqServerListQuery ( CHostAddress InetAddr ); - void CreateCLServerListPingMes ( CHostAddress InetAddr ); - void CreateCLServerListReqVerAndOSMes ( CHostAddress InetAddr ); - void CreateCLServerListReqConnClientsListMes ( CHostAddress InetAddr ); -}; diff --git a/src/connectdlgbase.ui b/src/connectdlgbase.ui deleted file mode 100644 index f12cdbe39a..0000000000 --- a/src/connectdlgbase.ui +++ /dev/null @@ -1,160 +0,0 @@ - - - CConnectDlgBase - - - - 0 - 0 - 648 - 493 - - - - Connection Setup - - - - :/png/main/res/fronticon.png:/png/main/res/fronticon.png - - - - - - true - - - - - - 0 - - - - - Directory - - - - - - - - - - Filter - - - - - - - - - - Show All Musicians - - - - - - - - - QAbstractItemView::NoEditTriggers - - - true - - - - Server Name - - - - - Ping Time - - - - - Musicians - - - - - Location - - - - - Version - - - - - - - - - - Server Address - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - true - - - - - - - - - - - - - - Qt::Horizontal - - - - 351 - 25 - - - - - - - - C&ancel - - - - - - - &Connect - - - true - - - - - - - - - - - - diff --git a/src/global.h b/src/global.h index 12c2bde9c2..170cef628f 100644 --- a/src/global.h +++ b/src/global.h @@ -1,376 +1,376 @@ -/******************************************************************************\ - * \Copyright (c) 2004-2024 - * \author Volker Fischer - * - -\mainpage Jamulus source code documentation - -\section intro_sec Introduction - -The Jamulus software enables musicians to perform real-time jam sessions over the -internet. There is one server running the Jamulus server software which collects -the audio data from each Jamulus client, mixes the audio data and sends the mix -back to each client. - - -Prefix definitions for the GUI: - -label: lbl -combo box: cbx -line edit: edt -list view: lvw -check box: chb -radio button: rbt -button: but -text view: txv -slider: sld -LED: led -group box: grb -pixmap label: pxl -LED bar: lbr - - ****************************************************************************** - * - * This program 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 2 of the License, or (at your option) any later - * version. - * - * This program 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 - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - * -\******************************************************************************/ - -#pragma once - -#if _WIN32 -# define _CRT_SECURE_NO_WARNINGS -#endif - -#include -#include -#include -#include -#include -#include -#ifndef _WIN32 -# include // solves a compile problem on recent Ubuntu -#endif -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -/* Definitions ****************************************************************/ -// define this macro to get debug output -//#define _DEBUG_ -#undef _DEBUG_ - -// version and application name (use version from qt prject file) -#undef VERSION -#define VERSION APP_VERSION -#define APP_NAME "Jamulus" - -// Windows registry key name of auto run entry for the server -#define AUTORUN_SERVER_REG_NAME "Jamulus server" - -// default names of the ini-file for client and server -#define DEFAULT_INI_FILE_NAME "Jamulus.ini" -#define DEFAULT_INI_FILE_NAME_SERVER "Jamulusserver.ini" - -// file name for logging file -#define DEFAULT_LOG_FILE_NAME "Jamulussrvlog.txt" - -// System block size, this is the block size on which the audio coder works. -// All other block sizes must be a multiple of this size. -// Note that the UpdateAutoSetting() function assumes a value of 128. -#define SYSTEM_FRAME_SIZE_SAMPLES 64 -#define DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ( 2 * SYSTEM_FRAME_SIZE_SAMPLES ) - -// additional buffer for delay panning -#define MAX_DELAY_PANNING_SAMPLES 64 - -// default server address and port numbers -#define DEFAULT_QOS_NUMBER 128 // CS4 (Quality of Service) -#define DEFAULT_SERVER_ADDRESS "anygenre1.jamulus.io" -#define DEFAULT_PORT_NUMBER 22124 -#define CENTSERV_ANY_GENRE2 "anygenre2.jamulus.io:22224" -#define CENTSERV_ANY_GENRE3 "anygenre3.jamulus.io:22624" -#define CENTSERV_GENRE_ROCK "rock.jamulus.io:22424" -#define CENTSERV_GENRE_JAZZ "jazz.jamulus.io:22324" -#define CENTSERV_GENRE_CLASSICAL_FOLK "classical.jamulus.io:22524" -#define CENTSERV_GENRE_CHORAL "choral.jamulus.io:22724" - -// specify an invalid port to disable the server -#define INVALID_PORT -1 - -// servers to check for new versions -#define UPDATECHECK1_ADDRESS "updatecheck1.jamulus.io" -#define UPDATECHECK2_ADDRESS "updatecheck2.jamulus.io" - -// getting started and software manual URL -#define CLIENT_GETTING_STARTED_URL "https://jamulus.io/wiki/Getting-Started" -#define SERVER_GETTING_STARTED_URL "https://jamulus.io/wiki/Running-a-Server" -#define SOFTWARE_MANUAL_URL "https://jamulus.io/wiki/Software-Manual" - -// app update message -#define APP_UPGRADE_AVAILABLE_MSG_TEXT \ - QCoreApplication::translate ( \ - "global", \ - "A %1 upgrade is available: go to details and downloads" ) - -// determining server internal address uses well-known host and port -// We just need a valid, public Internet IP here. We will not send any -// traffic there as we will only set up an UDP socket without sending any -// data. -#define WELL_KNOWN_HOST "1.1.1.1" // CloudFlare -#define WELL_KNOWN_HOST6 "2606:4700:4700::1111" // CloudFlare -#define WELL_KNOWN_PORT DEFAULT_PORT_NUMBER -#define IP_LOOKUP_TIMEOUT 500 // ms - -// system sample rate (the sound card and audio coder works on this sample rate) -#define SYSTEM_SAMPLE_RATE_HZ 48000 // Hz - -// define the allowed audio frame size factors (since the -// "SYSTEM_FRAME_SIZE_SAMPLES" is quite small, it may be that on some -// computers a larger value is required) -#define FRAME_SIZE_FACTOR_PREFERRED 1 // 64 samples accumulated frame size -#define FRAME_SIZE_FACTOR_DEFAULT 2 // 128 samples accumulated frame size -#define FRAME_SIZE_FACTOR_SAFE 4 // 256 samples accumulated frame size - -// define the minimum allowed number of coded bytes for CELT (the encoder -// gets in trouble if the value is too low) -#define CELT_MINIMUM_NUM_BYTES 10 - -// Maximum block size for network input buffer. It is defined by the longest -// protocol message which is PROTMESSID_CLM_SERVER_LIST: Worst case: -// (2+2+1+2+2)+200*(4+2+2+1+1+2+20+2+32+2+20)=17609 -// We add some headroom to that value. -#define MAX_SIZE_BYTES_NETW_BUF 20000 - -// minimum/maximum network buffer size (which can be chosen by slider) -#define MIN_NET_BUF_SIZE_NUM_BL 1 // number of blocks -#define MAX_NET_BUF_SIZE_NUM_BL 20 // number of blocks -#define AUTO_NET_BUF_SIZE_FOR_PROTOCOL ( MAX_NET_BUF_SIZE_NUM_BL + 1 ) // auto set parameter (only used for protocol) - -// default network buffer size -#define DEF_NET_BUF_SIZE_NUM_BL 10 // number of blocks - -// audio mixer fader and panning maximum value -#define AUD_MIX_FADER_MAX 100 -#define AUD_MIX_PAN_MAX 100 - -// range of audio mixer fader -#define AUD_MIX_FADER_RANGE_DB 35.0f - -// coefficient for averaging channel levels for automatic fader adjustment -#define AUTO_FADER_ADJUST_ALPHA 0.2f - -// target level for auto fader adjustment in decibels -#define AUTO_FADER_TARGET_LEVEL_DB -30.0f - -// threshold in decibels below which the channel is considered as noise -// and not adjusted -#define AUTO_FADER_NOISE_THRESHOLD_DB -40.0f - -// maximum number of fader groups (must be consistent to audiomixerboard implementation) -#define MAX_NUM_FADER_GROUPS 8 - -// maximum number of recognized sound cards installed in the system -#define MAX_NUMBER_SOUND_CARDS 129 // e.g. 16 inputs, 8 outputs + default entry (MacOS) - -// define the maximum number of audio channel for input/output we can store -// channel infos for (and therefore this is the maximum number of entries in -// the channel selection combo box regardless of the actual available number -// of channels by the audio device) -#define MAX_NUM_IN_OUT_CHANNELS 64 - -// maximum number of elemts in the server address combo box -#define MAX_NUM_SERVER_ADDR_ITEMS 12 - -// maximum number of fader settings to be stored (together with the fader tags) -#define MAX_NUM_STORED_FADER_SETTINGS 250 - -// range for signal level meter -#define LOW_BOUND_SIG_METER ( -50.0 ) // dB -#define UPPER_BOUND_SIG_METER ( 0.0 ) // dB - -// defines for LED level meter CLevelMeter -#define NUM_STEPS_LED_BAR 8 -#define RED_BOUND_LED_BAR 7 -#define YELLOW_BOUND_LED_BAR 5 - -// maximum number of connected clients at the server (must not be larger than 256) -#define MAX_NUM_CHANNELS 150 // max number channels for server - -// actual number of used channels in the server -// this parameter can safely be changed from 1 to MAX_NUM_CHANNELS -// without any other changes in the code -#define DEFAULT_USED_NUM_CHANNELS 10 // default used number channels for server - -// Maximum number of servers registered in the server list. If you want to -// change this parameter, you most probably have to adjust MAX_SIZE_BYTES_NETW_BUF. -#define MAX_NUM_SERVERS_IN_SERVER_LIST 150 // reduced to 150 because we now have genre-based server lists - -// defines the time interval at which the ping time is updated in the GUI -#define PING_UPDATE_TIME_MS 500 // ms - -// defines the time interval at which the ping time is updated for the server list -#define PING_UPDATE_TIME_SERVER_LIST_MS 2500 // ms - -// defines the interval between Channel Level updates from the server -#define CHANNEL_LEVEL_UPDATE_INTERVAL 200 // number of frames at 64 samples frame size - -// time-out until a registered server is deleted from the server list if no -// new registering was made in minutes -#define SERVLIST_TIME_OUT_MINUTES 33 // minutes (should include 3 UDP registration messages) - -// poll time for server list (to check if entries are time-out) -#define SERVLIST_POLL_TIME_MINUTES 1 // minute - -// time interval for sending ping messages to servers in the server list -#define SERVLIST_UPDATE_PING_SERVERS_MS 59000 // ms - -// time between server registration refreshes -#define SERVLIST_REGIST_INTERV_MINUTES 15 // minutes - -// defines the minimum time a server must run to be a permanent server -#define SERVLIST_TIME_PERMSERV_MINUTES 2880 // minutes, 2880 = 60 min * 24 h * 2 d - -// registration response timeout -#define REGISTER_SERVER_TIME_OUT_MS 500 // ms - -// defines the maximum number of times to retry server registration -// when no response is received within the timeout (before reverting -// to SERVLIST_REGIST_INTERV_MINUTES) -#define REGISTER_SERVER_RETRY_LIMIT 5 // count - -// Maximum length of fader tag and text message strings (Since for chat messages -// some HTML code is added, we also have to define a second length which includes -// this additionl HTML code. Right now the length of the HTML code is approx. 66 -// characters. Here, we add some headroom to this number) -#define MAX_LEN_FADER_TAG 16 -#define MAX_LEN_CHAT_TEXT 1600 -#define MAX_LEN_CHAT_TEXT_PLUS_HTML 1800 -#define MAX_LEN_SERVER_NAME 20 -#define MAX_LEN_IP_ADDRESS 15 -#define MAX_LEN_SERVER_CITY 20 -#define MAX_LEN_VERSION_TEXT 30 - -// define Settings tab indexes -#define SETTING_TAB_USER 0 -#define SETTING_TAB_AUDIONET 1 -#define SETTING_TAB_ADVANCED 2 - -// common tool tip bottom line text -#define TOOLTIP_COM_END_TEXT \ - "
" + \ - QCoreApplication::translate ( "global", \ - "For more information use the \"What's " \ - "This\" help (help menu, right mouse button or Shift+F1)" ) + \ - "
" - -// server welcome message title (do not change for compatibility!) -#define WELCOME_MESSAGE_PREFIX "Server Welcome Message: " - -// mixer settings file name suffix -#define MIX_SETTINGS_FILE_SUFFIX "jch" - -// minimum length of JSON-RPC secret string (main.cpp) -#define JSON_RPC_MINIMUM_SECRET_LENGTH 16 - -// JSON-RPC listen address (Default) -#define DEFAULT_JSON_RPC_LISTEN_ADDRESS "127.0.0.1" - -#define _MAXSHORT 32767 -#define _MINSHORT ( -32768 ) -#define INVALID_INDEX -1 // define invalid index as a negative value (a valid index must always be >= 0) - -#if HAVE_STDINT_H -# include -#elif HAVE_INTTYPES_H -# include -#elif defined( _WIN32 ) -typedef __int64 int64_t; -typedef __int32 int32_t; -typedef __int16 int16_t; -typedef unsigned __int64 uint64_t; -typedef unsigned __int32 uint32_t; -typedef unsigned __int16 uint16_t; -typedef unsigned __int8 uint8_t; -#elif defined( ANDROID ) -// don't redfine types for android as these ones below don't work -#else -typedef long long int64_t; -typedef int int32_t; -typedef short int16_t; -typedef unsigned long long uint64_t; -typedef unsigned int uint32_t; -typedef unsigned short uint16_t; -typedef unsigned char uint8_t; -#endif - -/* Pseudo enum definitions -------------------------------------------------- */ -// definition for custom event -#define MS_PACKET_RECEIVED 0 - -/* Classes ********************************************************************/ -class CGenErr -{ -public: - CGenErr ( QString strNewErrorMsg, QString strNewErrorType = "" ) : strErrorMsg ( strNewErrorMsg ), strErrorType ( strNewErrorType ) {} - - QString GetErrorText() const - { - // return formatted error text - if ( strErrorType.isEmpty() ) - { - return strErrorMsg; - } - else - { - return strErrorType + ": " + strErrorMsg; - } - } - -protected: - QString strErrorMsg; - QString strErrorType; -}; - -class CCustomEvent : public QEvent -{ -public: - CCustomEvent ( int iNewMeTy, int iNewSt, int iNewChN = 0 ) : - QEvent ( QEvent::Type ( QEvent::User + 11 ) ), - iMessType ( iNewMeTy ), - iStatus ( iNewSt ), - iChanNum ( iNewChN ) - {} - - int iMessType; - int iStatus; - int iChanNum; -}; - -/* Prototypes for global functions ********************************************/ -// command line parsing, TODO do not declare functions globally but in a class -QString UsageArguments ( char** argv ); - -bool GetFlagArgument ( char** argv, int& i, QString strShortOpt, QString strLongOpt ); - -bool GetStringArgument ( int argc, char** argv, int& i, QString strShortOpt, QString strLongOpt, QString& strArg ); - -bool GetNumericArgument ( int argc, - char** argv, - int& i, - QString strShortOpt, - QString strLongOpt, - double rRangeStart, - double rRangeStop, - double& rValue ); +/******************************************************************************\ + * \Copyright (c) 2004-2024 + * \author Volker Fischer + * + +\mainpage Jamulus source code documentation + +\section intro_sec Introduction + +The Jamulus software enables musicians to perform real-time jam sessions over the +internet. There is one server running the Jamulus server software which collects +the audio data from each Jamulus client, mixes the audio data and sends the mix +back to each client. + + +Prefix definitions for the GUI: + +label: lbl +combo box: cbx +line edit: edt +list view: lvw +check box: chb +radio button: rbt +button: but +text view: txv +slider: sld +LED: led +group box: grb +pixmap label: pxl +LED bar: lbr + + ****************************************************************************** + * + * This program 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 2 of the License, or (at your option) any later + * version. + * + * This program 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 + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + * +\******************************************************************************/ + +#pragma once + +#if _WIN32 +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +# include // solves a compile problem on recent Ubuntu +#endif +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* Definitions ****************************************************************/ +// define this macro to get debug output +//#define _DEBUG_ +#undef _DEBUG_ + +// version and application name (use version from qt prject file) +#undef VERSION +#define VERSION APP_VERSION +#define APP_NAME "Jamulus" + +// Windows registry key name of auto run entry for the server +#define AUTORUN_SERVER_REG_NAME "Jamulus server" + +// default names of the ini-file for client and server +#define DEFAULT_INI_FILE_NAME "Jamulus.ini" +#define DEFAULT_INI_FILE_NAME_SERVER "Jamulusserver.ini" + +// file name for logging file +#define DEFAULT_LOG_FILE_NAME "Jamulussrvlog.txt" + +// System block size, this is the block size on which the audio coder works. +// All other block sizes must be a multiple of this size. +// Note that the UpdateAutoSetting() function assumes a value of 128. +#define SYSTEM_FRAME_SIZE_SAMPLES 64 +#define DOUBLE_SYSTEM_FRAME_SIZE_SAMPLES ( 2 * SYSTEM_FRAME_SIZE_SAMPLES ) + +// additional buffer for delay panning +#define MAX_DELAY_PANNING_SAMPLES 64 + +// default server address and port numbers +#define DEFAULT_QOS_NUMBER 128 // CS4 (Quality of Service) +#define DEFAULT_SERVER_ADDRESS "anygenre1.jamulus.io" +#define DEFAULT_PORT_NUMBER 22124 +#define CENTSERV_ANY_GENRE2 "anygenre2.jamulus.io:22224" +#define CENTSERV_ANY_GENRE3 "anygenre3.jamulus.io:22624" +#define CENTSERV_GENRE_ROCK "rock.jamulus.io:22424" +#define CENTSERV_GENRE_JAZZ "jazz.jamulus.io:22324" +#define CENTSERV_GENRE_CLASSICAL_FOLK "classical.jamulus.io:22524" +#define CENTSERV_GENRE_CHORAL "choral.jamulus.io:22724" + +// specify an invalid port to disable the server +#define INVALID_PORT -1 + +// servers to check for new versions +#define UPDATECHECK1_ADDRESS "updatecheck1.jamulus.io" +#define UPDATECHECK2_ADDRESS "updatecheck2.jamulus.io" + +// getting started and software manual URL +#define CLIENT_GETTING_STARTED_URL "https://jamulus.io/wiki/Getting-Started" +#define SERVER_GETTING_STARTED_URL "https://jamulus.io/wiki/Running-a-Server" +#define SOFTWARE_MANUAL_URL "https://jamulus.io/wiki/Software-Manual" + +// app update message +#define APP_UPGRADE_AVAILABLE_MSG_TEXT \ + QCoreApplication::translate ( \ + "global", \ + "A %1 upgrade is available: go to details and downloads" ) + +// determining server internal address uses well-known host and port +// We just need a valid, public Internet IP here. We will not send any +// traffic there as we will only set up an UDP socket without sending any +// data. +#define WELL_KNOWN_HOST "1.1.1.1" // CloudFlare +#define WELL_KNOWN_HOST6 "2606:4700:4700::1111" // CloudFlare +#define WELL_KNOWN_PORT DEFAULT_PORT_NUMBER +#define IP_LOOKUP_TIMEOUT 500 // ms + +// system sample rate (the sound card and audio coder works on this sample rate) +#define SYSTEM_SAMPLE_RATE_HZ 48000 // Hz + +// define the allowed audio frame size factors (since the +// "SYSTEM_FRAME_SIZE_SAMPLES" is quite small, it may be that on some +// computers a larger value is required) +#define FRAME_SIZE_FACTOR_PREFERRED 1 // 64 samples accumulated frame size +#define FRAME_SIZE_FACTOR_DEFAULT 2 // 128 samples accumulated frame size +#define FRAME_SIZE_FACTOR_SAFE 4 // 256 samples accumulated frame size + +// define the minimum allowed number of coded bytes for CELT (the encoder +// gets in trouble if the value is too low) +#define CELT_MINIMUM_NUM_BYTES 10 + +// Maximum block size for network input buffer. It is defined by the longest +// protocol message which is PROTMESSID_CLM_SERVER_LIST: Worst case: +// (2+2+1+2+2)+200*(4+2+2+1+1+2+20+2+32+2+20)=17609 +// We add some headroom to that value. +#define MAX_SIZE_BYTES_NETW_BUF 20000 + +// minimum/maximum network buffer size (which can be chosen by slider) +#define MIN_NET_BUF_SIZE_NUM_BL 1 // number of blocks +#define MAX_NET_BUF_SIZE_NUM_BL 20 // number of blocks +#define AUTO_NET_BUF_SIZE_FOR_PROTOCOL ( MAX_NET_BUF_SIZE_NUM_BL + 1 ) // auto set parameter (only used for protocol) + +// default network buffer size +#define DEF_NET_BUF_SIZE_NUM_BL 10 // number of blocks + +// audio mixer fader and panning maximum value +#define AUD_MIX_FADER_MAX 100 +#define AUD_MIX_PAN_MAX 100 + +// range of audio mixer fader +#define AUD_MIX_FADER_RANGE_DB 35.0f + +// coefficient for averaging channel levels for automatic fader adjustment +#define AUTO_FADER_ADJUST_ALPHA 0.2f + +// target level for auto fader adjustment in decibels +#define AUTO_FADER_TARGET_LEVEL_DB -30.0f + +// threshold in decibels below which the channel is considered as noise +// and not adjusted +#define AUTO_FADER_NOISE_THRESHOLD_DB -40.0f + +// maximum number of fader groups (must be consistent to audiomixerboard implementation) +#define MAX_NUM_FADER_GROUPS 8 + +// maximum number of recognized sound cards installed in the system +#define MAX_NUMBER_SOUND_CARDS 129 // e.g. 16 inputs, 8 outputs + default entry (MacOS) + +// define the maximum number of audio channel for input/output we can store +// channel infos for (and therefore this is the maximum number of entries in +// the channel selection combo box regardless of the actual available number +// of channels by the audio device) +#define MAX_NUM_IN_OUT_CHANNELS 64 + +// maximum number of elemts in the server address combo box +#define MAX_NUM_SERVER_ADDR_ITEMS 12 + +// maximum number of fader settings to be stored (together with the fader tags) +#define MAX_NUM_STORED_FADER_SETTINGS 250 + +// range for signal level meter +#define LOW_BOUND_SIG_METER ( -50.0 ) // dB +#define UPPER_BOUND_SIG_METER ( 0.0 ) // dB + +// defines for LED level meter CLevelMeter +#define NUM_STEPS_LED_BAR 8 +#define RED_BOUND_LED_BAR 7 +#define YELLOW_BOUND_LED_BAR 5 + +// maximum number of connected clients at the server (must not be larger than 256) +#define MAX_NUM_CHANNELS 150 // max number channels for server + +// actual number of used channels in the server +// this parameter can safely be changed from 1 to MAX_NUM_CHANNELS +// without any other changes in the code +#define DEFAULT_USED_NUM_CHANNELS 10 // default used number channels for server + +// Maximum number of servers registered in the server list. If you want to +// change this parameter, you most probably have to adjust MAX_SIZE_BYTES_NETW_BUF. +#define MAX_NUM_SERVERS_IN_SERVER_LIST 150 // reduced to 150 because we now have genre-based server lists + +// defines the time interval at which the ping time is updated in the GUI +#define PING_UPDATE_TIME_MS 500 // ms + +// defines the time interval at which the ping time is updated for the server list +#define PING_UPDATE_TIME_SERVER_LIST_MS 2500 // ms + +// defines the interval between Channel Level updates from the server +#define CHANNEL_LEVEL_UPDATE_INTERVAL 200 // number of frames at 64 samples frame size + +// time-out until a registered server is deleted from the server list if no +// new registering was made in minutes +#define SERVLIST_TIME_OUT_MINUTES 33 // minutes (should include 3 UDP registration messages) + +// poll time for server list (to check if entries are time-out) +#define SERVLIST_POLL_TIME_MINUTES 1 // minute + +// time interval for sending ping messages to servers in the server list +#define SERVLIST_UPDATE_PING_SERVERS_MS 59000 // ms + +// time between server registration refreshes +#define SERVLIST_REGIST_INTERV_MINUTES 15 // minutes + +// defines the minimum time a server must run to be a permanent server +#define SERVLIST_TIME_PERMSERV_MINUTES 2880 // minutes, 2880 = 60 min * 24 h * 2 d + +// registration response timeout +#define REGISTER_SERVER_TIME_OUT_MS 500 // ms + +// defines the maximum number of times to retry server registration +// when no response is received within the timeout (before reverting +// to SERVLIST_REGIST_INTERV_MINUTES) +#define REGISTER_SERVER_RETRY_LIMIT 5 // count + +// Maximum length of fader tag and text message strings (Since for chat messages +// some HTML code is added, we also have to define a second length which includes +// this additionl HTML code. Right now the length of the HTML code is approx. 66 +// characters. Here, we add some headroom to this number) +#define MAX_LEN_FADER_TAG 16 +#define MAX_LEN_CHAT_TEXT 1600 +#define MAX_LEN_CHAT_TEXT_PLUS_HTML 1800 +#define MAX_LEN_SERVER_NAME 20 +#define MAX_LEN_IP_ADDRESS 15 +#define MAX_LEN_SERVER_CITY 20 +#define MAX_LEN_VERSION_TEXT 30 + +// define Settings tab indexes +#define SETTING_TAB_USER 0 +#define SETTING_TAB_AUDIONET 1 +#define SETTING_TAB_ADVANCED 2 + +// common tool tip bottom line text +#define TOOLTIP_COM_END_TEXT \ + "
" + \ + QCoreApplication::translate ( "global", \ + "For more information use the \"What's " \ + "This\" help (help menu, right mouse button or Shift+F1)" ) + \ + "
" + +// server welcome message title (do not change for compatibility!) +#define WELCOME_MESSAGE_PREFIX "Server Welcome Message: " + +// mixer settings file name suffix +#define MIX_SETTINGS_FILE_SUFFIX "jch" + +// minimum length of JSON-RPC secret string (main.cpp) +#define JSON_RPC_MINIMUM_SECRET_LENGTH 16 + +// JSON-RPC listen address (Default) +#define DEFAULT_JSON_RPC_LISTEN_ADDRESS "127.0.0.1" + +#define _MAXSHORT 32767 +#define _MINSHORT ( -32768 ) +#define INVALID_INDEX -1 // define invalid index as a negative value (a valid index must always be >= 0) + +#if HAVE_STDINT_H +# include +#elif HAVE_INTTYPES_H +# include +#elif defined( _WIN32 ) +typedef __int64 int64_t; +typedef __int32 int32_t; +typedef __int16 int16_t; +typedef unsigned __int64 uint64_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int8 uint8_t; +#elif defined( ANDROID ) +// don't redfine types for android as these ones below don't work +#else +typedef long long int64_t; +typedef int int32_t; +typedef short int16_t; +typedef unsigned long long uint64_t; +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef unsigned char uint8_t; +#endif + +/* Pseudo enum definitions -------------------------------------------------- */ +// definition for custom event +#define MS_PACKET_RECEIVED 0 + +/* Classes ********************************************************************/ +class CGenErr +{ +public: + CGenErr ( QString strNewErrorMsg, QString strNewErrorType = "" ) : strErrorMsg ( strNewErrorMsg ), strErrorType ( strNewErrorType ) {} + + QString GetErrorText() const + { + // return formatted error text + if ( strErrorType.isEmpty() ) + { + return strErrorMsg; + } + else + { + return strErrorType + ": " + strErrorMsg; + } + } + +protected: + QString strErrorMsg; + QString strErrorType; +}; + +class CCustomEvent : public QEvent +{ +public: + CCustomEvent ( int iNewMeTy, int iNewSt, int iNewChN = 0 ) : + QEvent ( QEvent::Type ( QEvent::User + 11 ) ), + iMessType ( iNewMeTy ), + iStatus ( iNewSt ), + iChanNum ( iNewChN ) + {} + + int iMessType; + int iStatus; + int iChanNum; +}; + +/* Prototypes for global functions ********************************************/ +// command line parsing, TODO do not declare functions globally but in a class +QString UsageArguments ( char** argv ); + +bool GetFlagArgument ( char** argv, int& i, QString strShortOpt, QString strLongOpt ); + +bool GetStringArgument ( int argc, char** argv, int& i, QString strShortOpt, QString strLongOpt, QString& strArg ); + +bool GetNumericArgument ( int argc, + char** argv, + int& i, + QString strShortOpt, + QString strLongOpt, + double rRangeStart, + double rRangeStop, + double& rValue ); diff --git a/src/levelmeter.cpp b/src/levelmeter.cpp index 11adc17921..b5c47bcbf1 100644 --- a/src/levelmeter.cpp +++ b/src/levelmeter.cpp @@ -28,53 +28,11 @@ #include "levelmeter.h" /* Implementation *************************************************************/ -CLevelMeter::CLevelMeter ( QWidget* parent ) : QWidget ( parent ), eLevelMeterType ( MT_BAR_WIDE ) +CLevelMeter::CLevelMeter ( QObject* parent ) + : QObject(parent), + m_doubleVal(0.0) { - // initialize LED meter - QWidget* pLEDMeter = new QWidget(); - QVBoxLayout* pLEDLayout = new QVBoxLayout ( pLEDMeter ); - pLEDLayout->setAlignment ( Qt::AlignHCenter ); - pLEDLayout->setContentsMargins ( 0, 0, 0, 0 ); - pLEDLayout->setSpacing ( 0 ); - - // create LEDs plus the clip LED - vecpLEDs.Init ( NUM_LEDS_INCL_CLIP_LED ); - - for ( int iLEDIdx = NUM_LEDS_INCL_CLIP_LED - 1; iLEDIdx >= 0; iLEDIdx-- ) - { - // create LED object - vecpLEDs[iLEDIdx] = new cLED ( parent ); - - // add LED to layout with spacer (do not add spacer on the bottom of the first LED) - if ( iLEDIdx < NUM_LEDS_INCL_CLIP_LED - 1 ) - { - pLEDLayout->addStretch(); - } - - pLEDLayout->addWidget ( vecpLEDs[iLEDIdx]->GetLabelPointer() ); - } - - // initialize bar meter - pBarMeter = new QProgressBar(); - pBarMeter->setOrientation ( Qt::Vertical ); - pBarMeter->setRange ( 0, 100 * NUM_STEPS_LED_BAR ); // use factor 100 to reduce quantization (bar is continuous) - pBarMeter->setFormat ( "" ); // suppress percent numbers - - // setup stacked layout for meter type switching mechanism - pMinStackedLayout = new CMinimumStackedLayout ( this ); - pMinStackedLayout->addWidget ( pLEDMeter ); - pMinStackedLayout->addWidget ( pBarMeter ); - - // according to QScrollArea description: "When using a scroll area to display the - // contents of a custom widget, it is important to ensure that the size hint of - // the child widget is set to a suitable value." - pBarMeter->setMinimumSize ( QSize ( 1, 1 ) ); - pLEDMeter->setMinimumSize ( QSize ( 1, 1 ) ); - - // update the meter type (using the default value of the meter type) - SetLevelMeterType ( eLevelMeterType ); - - // setup clip indicator timer + // setup clip indicator timer TimerClip.setSingleShot ( true ); TimerClip.setInterval ( CLIP_IND_TIME_OUT_MS ); @@ -84,243 +42,38 @@ CLevelMeter::CLevelMeter ( QWidget* parent ) : QWidget ( parent ), eLevelMeterTy CLevelMeter::~CLevelMeter() { - // clean up the LED objects - for ( int iLEDIdx = 0; iLEDIdx < NUM_LEDS_INCL_CLIP_LED; iLEDIdx++ ) - { - delete vecpLEDs[iLEDIdx]; - } + // nothing for now + ; } -void CLevelMeter::SetLevelMeterType ( const ELevelMeterType eNType ) +bool CLevelMeter::clipStatus() { - eLevelMeterType = eNType; - - switch ( eNType ) - { - case MT_LED_STRIPE: - // initialize all LEDs - for ( int iLEDIdx = 0; iLEDIdx < NUM_LEDS_INCL_CLIP_LED; iLEDIdx++ ) - { - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_BLACK ); - } - pMinStackedLayout->setCurrentIndex ( 0 ); - break; - - case MT_LED_ROUND_BIG: - // initialize all LEDs - for ( int iLEDIdx = 0; iLEDIdx < NUM_LEDS_INCL_CLIP_LED; iLEDIdx++ ) - { - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_BIG_BLACK ); - } - pMinStackedLayout->setCurrentIndex ( 0 ); - break; - - case MT_LED_ROUND_SMALL: - // initialize all LEDs - for ( int iLEDIdx = 0; iLEDIdx < NUM_LEDS_INCL_CLIP_LED; iLEDIdx++ ) - { - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_SMALL_BLACK ); - } - pMinStackedLayout->setCurrentIndex ( 0 ); - break; - - case MT_BAR_WIDE: - case MT_BAR_NARROW: - pMinStackedLayout->setCurrentIndex ( 1 ); - break; - } - - // update bar meter style and reset clip state - SetBarMeterStyleAndClipStatus ( eNType, false ); + return m_clipStatus; } -void CLevelMeter::SetBarMeterStyleAndClipStatus ( const ELevelMeterType eNType, const bool bIsClip ) +void CLevelMeter::setClipStatus( const bool bIsClip ) { - switch ( eNType ) - { - case MT_BAR_NARROW: - if ( bIsClip ) - { - pBarMeter->setStyleSheet ( "QProgressBar { border: 0px solid red;" - " margin: 0px;" - " padding: 0px;" - " width: 4px;" - " background: red; }" - "QProgressBar::chunk { background: green; }" ); - } - else - { - pBarMeter->setStyleSheet ( "QProgressBar { border: 0px;" - " margin: 0px;" - " padding: 0px;" - " width: 4px; }" - "QProgressBar::chunk { background: green; }" ); - } - break; + m_clipStatus = bIsClip; - default: /* MT_BAR_WIDE */ - if ( bIsClip ) - { - pBarMeter->setStyleSheet ( "QProgressBar { border: 2px solid red;" - " margin: 1px;" - " padding: 1px;" - " width: 15px;" - " background: transparent; }" - "QProgressBar::chunk { background: green; }" ); - } - else - { - pBarMeter->setStyleSheet ( "QProgressBar { margin: 1px;" - " padding: 1px;" - " width: 15px; }" - "QProgressBar::chunk { background: green; }" ); - } - break; - } + emit clipStatusChanged(); } -void CLevelMeter::SetValue ( const double dValue ) +void CLevelMeter::setDoubleVal ( const double value ) { - switch ( eLevelMeterType ) - { - case MT_LED_STRIPE: - // update state of all LEDs for current level value (except of the clip LED) - for ( int iLEDIdx = 0; iLEDIdx < NUM_STEPS_LED_BAR; iLEDIdx++ ) - { - // set active LED color if value is above current LED index - if ( iLEDIdx < dValue ) - { - // check which color we should use (green, yellow or red) - if ( iLEDIdx < YELLOW_BOUND_LED_BAR ) - { - // green region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_GREEN ); - } - else - { - if ( iLEDIdx < RED_BOUND_LED_BAR ) - { - // yellow region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_YELLOW ); - } - else - { - // red region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_RED ); - } - } - } - else - { - // we use black LED for inactive state - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_BLACK ); - } - } - break; - - case MT_LED_ROUND_BIG: - // update state of all LEDs for current level value (except of the clip LED) - for ( int iLEDIdx = 0; iLEDIdx < NUM_STEPS_LED_BAR; iLEDIdx++ ) - { - // set active LED color if value is above current LED index - if ( iLEDIdx < dValue ) - { - // check which color we should use (green, yellow or red) - if ( iLEDIdx < YELLOW_BOUND_LED_BAR ) - { - // green region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_BIG_GREEN ); - } - else - { - if ( iLEDIdx < RED_BOUND_LED_BAR ) - { - // yellow region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_BIG_YELLOW ); - } - else - { - // red region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_BIG_RED ); - } - } - } - else - { - // we use black LED for inactive state - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_BIG_BLACK ); - } - } - break; - - case MT_LED_ROUND_SMALL: - // update state of all LEDs for current level value (except of the clip LED) - for ( int iLEDIdx = 0; iLEDIdx < NUM_STEPS_LED_BAR; iLEDIdx++ ) - { - // set active LED color if value is above current LED index - if ( iLEDIdx < dValue ) - { - // check which color we should use (green, yellow or red) - if ( iLEDIdx < YELLOW_BOUND_LED_BAR ) - { - // green region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_SMALL_GREEN ); - } - else - { - if ( iLEDIdx < RED_BOUND_LED_BAR ) - { - // yellow region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_SMALL_YELLOW ); - } - else - { - // red region - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_SMALL_RED ); - } - } - } - else - { - // we use black LED for inactive state - vecpLEDs[iLEDIdx]->SetColor ( cLED::RL_ROUND_SMALL_BLACK ); - } - } - break; - - case MT_BAR_WIDE: - case MT_BAR_NARROW: - pBarMeter->setValue ( 100 * dValue ); - break; - } + m_doubleVal = value / NUM_STEPS_LED_BAR; + emit doubleValChanged(); + // qDebug() << "Level doubleVal: " << m_doubleVal; // clip indicator management (note that in case of clipping, i.e. full // scale level, the value is above NUM_STEPS_LED_BAR since the minimum // value of int16 is -32768 but we normalize with 32767 -> therefore // we really only show the clipping indicator, if actually the largest // value of int16 is used) - if ( dValue > NUM_STEPS_LED_BAR ) + if ( value > NUM_STEPS_LED_BAR ) { - switch ( eLevelMeterType ) - { - case MT_LED_STRIPE: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_RED ); - break; - - case MT_LED_ROUND_BIG: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_ROUND_BIG_RED ); - break; - - case MT_LED_ROUND_SMALL: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_ROUND_SMALL_RED ); - break; - - case MT_BAR_WIDE: - case MT_BAR_NARROW: - SetBarMeterStyleAndClipStatus ( eLevelMeterType, true ); - break; - } - + qDebug() << "Level value: " << value; + setClipStatus(true); + emit clipStatusChanged(); TimerClip.start(); } } @@ -330,110 +83,6 @@ void CLevelMeter::ClipReset() // we manually want to reset the clipping indicator: stop timer and reset // clipping indicator GUI element TimerClip.stop(); - - switch ( eLevelMeterType ) - { - case MT_LED_STRIPE: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_BLACK ); - break; - - case MT_LED_ROUND_BIG: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_ROUND_BIG_BLACK ); - break; - - case MT_LED_ROUND_SMALL: - vecpLEDs[NUM_STEPS_LED_BAR]->SetColor ( cLED::RL_ROUND_SMALL_BLACK ); - break; - - case MT_BAR_WIDE: - case MT_BAR_NARROW: - SetBarMeterStyleAndClipStatus ( eLevelMeterType, false ); - break; - } -} - -CLevelMeter::cLED::cLED ( QWidget* parent ) : - BitmCubeLedBlack ( QString::fromUtf8 ( ":/png/LEDs/res/HLEDBlack.png" ) ), - BitmCubeLedGreen ( QString::fromUtf8 ( ":/png/LEDs/res/HLEDGreen.png" ) ), - BitmCubeLedYellow ( QString::fromUtf8 ( ":/png/LEDs/res/HLEDYellow.png" ) ), - BitmCubeLedRed ( QString::fromUtf8 ( ":/png/LEDs/res/HLEDRed.png" ) ), - BitmCubeRoundSmallLedBlack ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDBlackSmall.png" ) ), - BitmCubeRoundSmallLedGreen ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDGreenSmall.png" ) ), - BitmCubeRoundSmallLedYellow ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDYellowSmall.png" ) ), - BitmCubeRoundSmallLedRed ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDRedSmall.png" ) ), - BitmCubeRoundBigLedBlack ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDBlackBig.png" ) ), - BitmCubeRoundBigLedGreen ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDGreenBig.png" ) ), - BitmCubeRoundBigLedYellow ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDYellowBig.png" ) ), - BitmCubeRoundBigLedRed ( QString::fromUtf8 ( ":/png/LEDs/res/CLEDRedBig.png" ) ) -{ - // create LED label - pLEDLabel = new QLabel ( "", parent ); - - // set initial bitmap - pLEDLabel->setPixmap ( BitmCubeLedBlack ); - eCurLightColor = RL_BLACK; -} - -void CLevelMeter::cLED::SetColor ( const ELightColor eNewColor ) -{ - // only update LED if color has changed - if ( eNewColor != eCurLightColor ) - { - switch ( eNewColor ) - { - case RL_DISABLED: - // note that this is required for the compact channel mode - pLEDLabel->setPixmap ( QPixmap() ); - break; - - case RL_BLACK: - pLEDLabel->setPixmap ( BitmCubeLedBlack ); - break; - - case RL_GREEN: - pLEDLabel->setPixmap ( BitmCubeLedGreen ); - break; - - case RL_YELLOW: - pLEDLabel->setPixmap ( BitmCubeLedYellow ); - break; - - case RL_RED: - pLEDLabel->setPixmap ( BitmCubeLedRed ); - break; - - case RL_ROUND_SMALL_BLACK: - pLEDLabel->setPixmap ( BitmCubeRoundSmallLedBlack ); - break; - - case RL_ROUND_SMALL_GREEN: - pLEDLabel->setPixmap ( BitmCubeRoundSmallLedGreen ); - break; - - case RL_ROUND_SMALL_YELLOW: - pLEDLabel->setPixmap ( BitmCubeRoundSmallLedYellow ); - break; - - case RL_ROUND_SMALL_RED: - pLEDLabel->setPixmap ( BitmCubeRoundSmallLedRed ); - break; - - case RL_ROUND_BIG_BLACK: - pLEDLabel->setPixmap ( BitmCubeRoundBigLedBlack ); - break; - - case RL_ROUND_BIG_GREEN: - pLEDLabel->setPixmap ( BitmCubeRoundBigLedGreen ); - break; - - case RL_ROUND_BIG_YELLOW: - pLEDLabel->setPixmap ( BitmCubeRoundBigLedYellow ); - break; - - case RL_ROUND_BIG_RED: - pLEDLabel->setPixmap ( BitmCubeRoundBigLedRed ); - break; - } - eCurLightColor = eNewColor; - } + setClipStatus(false); + emit clipStatusChanged(); } diff --git a/src/levelmeter.h b/src/levelmeter.h index 819882d475..0627e7ef0d 100644 --- a/src/levelmeter.h +++ b/src/levelmeter.h @@ -24,11 +24,7 @@ #pragma once -#include -#include #include -#include -#include #include "util.h" #include "global.h" @@ -37,82 +33,35 @@ #define CLIP_IND_TIME_OUT_MS 20000 /* Classes ********************************************************************/ -class CLevelMeter : public QWidget +class CLevelMeter : public QObject { Q_OBJECT -public: - enum ELevelMeterType - { - MT_BAR_NARROW, - MT_BAR_WIDE, - MT_LED_STRIPE, - MT_LED_ROUND_SMALL, - MT_LED_ROUND_BIG - }; + Q_PROPERTY(double doubleVal READ doubleVal WRITE setDoubleVal NOTIFY doubleValChanged) + Q_PROPERTY(bool clipStatus READ clipStatus WRITE setClipStatus NOTIFY clipStatusChanged) - CLevelMeter ( QWidget* parent = nullptr ); +public: + CLevelMeter ( QObject* parent = nullptr ); virtual ~CLevelMeter(); - void SetValue ( const double dValue ); - void SetLevelMeterType ( const ELevelMeterType eNType ); + double doubleVal() const { return m_doubleVal; } + bool clipStatus(); // const { return m_clipStatus; } protected: - class cLED - { - public: - enum ELightColor - { - RL_DISABLED, - RL_BLACK, - RL_GREEN, - RL_YELLOW, - RL_RED, - RL_ROUND_SMALL_BLACK, - RL_ROUND_SMALL_GREEN, - RL_ROUND_SMALL_YELLOW, - RL_ROUND_SMALL_RED, - RL_ROUND_BIG_BLACK, - RL_ROUND_BIG_GREEN, - RL_ROUND_BIG_YELLOW, - RL_ROUND_BIG_RED - }; - - cLED ( QWidget* parent ); - - void SetColor ( const ELightColor eNewColor ); - ELightColor GetColor() { return eCurLightColor; }; - QLabel* GetLabelPointer() { return pLEDLabel; } - - protected: - QPixmap BitmCubeLedBlack; - QPixmap BitmCubeLedGreen; - QPixmap BitmCubeLedYellow; - QPixmap BitmCubeLedRed; - QPixmap BitmCubeRoundSmallLedBlack; - QPixmap BitmCubeRoundSmallLedGreen; - QPixmap BitmCubeRoundSmallLedYellow; - QPixmap BitmCubeRoundSmallLedRed; - QPixmap BitmCubeRoundBigLedBlack; - QPixmap BitmCubeRoundBigLedGreen; - QPixmap BitmCubeRoundBigLedYellow; - QPixmap BitmCubeRoundBigLedRed; - - ELightColor eCurLightColor; - QLabel* pLEDLabel; - }; + // virtual void mousePressEvent ( QMouseEvent* ) override { ClipReset(); } + QTimer TimerClip; - virtual void mousePressEvent ( QMouseEvent* ) override { ClipReset(); } + double m_doubleVal; // level of this meter + bool m_clipStatus; - void SetBarMeterStyleAndClipStatus ( const ELevelMeterType eNType, const bool bIsClip ); +public slots: + void ClipReset(); - CMinimumStackedLayout* pMinStackedLayout; - ELevelMeterType eLevelMeterType; - CVector vecpLEDs; - QProgressBar* pBarMeter; + void setDoubleVal(double value); + void setClipStatus( bool clipStatus ); - QTimer TimerClip; -public slots: - void ClipReset(); +signals: + void doubleValChanged(); + void clipStatusChanged(); }; diff --git a/src/main.cpp b/src/main.cpp index e9f05c4413..9cddb8a3cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,28 +23,27 @@ \******************************************************************************/ #include +#include #include #include #include "global.h" #ifndef HEADLESS -# include -# include -# include "serverdlg.h" -# ifndef SERVER_ONLY -# include "clientdlg.h" -# endif +# include +# include "QQmlContext" +# include +# include #endif #include "settings.h" +#include "client.h" #ifndef SERVER_ONLY # include "testbench.h" #endif #include "util.h" -#ifdef ANDROID -# include -#endif #if defined( Q_OS_MACOS ) # include "mac/activity.h" extern void qt_set_sequence_auto_mnemonic ( bool bEnable ); +# include "mac/activity.h" +# include #endif #include #ifndef NO_JSON_RPC @@ -117,6 +116,8 @@ int main ( int argc, char** argv ) QString strWelcomeMessage = ""; QString strClientName = ""; QString strJsonRpcSecretFileName = ""; + // handle primary / secondary instances + // MessageReceiver msgReceiver; #if defined( HEADLESS ) || defined( SERVER_ONLY ) Q_UNUSED ( bStartMinimized ) @@ -137,6 +138,8 @@ int main ( int argc, char** argv ) freopen ( "CONOUT$", "w", stdout ); freopen ( "CONOUT$", "w", stderr ); } + + #endif // When adding new options, follow the same order as --help output @@ -342,9 +345,6 @@ int main ( int argc, char** argv ) // HTML status file ---------------------------------------------------- if ( GetStringArgument ( argc, argv, i, "-m", "--htmlstatus", strArgument ) ) { - qWarning() << qUtf8Printable ( - QString ( "- The HTML status file option (\"--htmlstatus\" or \"-m\") is deprecated and will be removed soon. Please use JSON-RPC " - "instead. See https://github.com/jamulussoftware/jamulus/blob/main/docs/JSON-RPC.md" ) ); strHTMLStatusFileName = strArgument; qInfo() << qUtf8Printable ( QString ( "- HTML status file name: %1" ).arg ( strHTMLStatusFileName ) ); CommandLineOptions << "--htmlstatus"; @@ -639,7 +639,6 @@ int main ( int argc, char** argv ) else #endif { - if ( ClientOnlyOptions.size() != 0 ) { qCritical() << qUtf8Printable ( @@ -816,31 +815,14 @@ int main ( int argc, char** argv ) #ifdef HEADLESS QCoreApplication* pApp = new QCoreApplication ( argc, argv ); #else -# if defined( Q_OS_IOS ) - bUseGUI = true; - bIsClient = true; // Client only - TODO: maybe a switch in interface to change to server? - - // bUseMultithreading = true; - QApplication* pApp = new QApplication ( argc, argv ); -# else - QCoreApplication* pApp = bUseGUI ? new QApplication ( argc, argv ) : new QCoreApplication ( argc, argv ); -# endif + QGuiApplication* pApp = new QGuiApplication ( argc, argv ); #endif -#ifdef ANDROID - // special Android coded needed for record audio permission handling - auto result = QtAndroid::checkPermission ( QString ( "android.permission.RECORD_AUDIO" ) ); - - if ( result == QtAndroid::PermissionResult::Denied ) + if (bUseGUI == true) { - QtAndroid::PermissionResultMap resultHash = QtAndroid::requestPermissionsSync ( QStringList ( { "android.permission.RECORD_AUDIO" } ) ); - - if ( resultHash["android.permission.RECORD_AUDIO"] == QtAndroid::PermissionResult::Denied ) - { - return 0; - } + // pApp->setStyle("fusion"); + // QQuickStyle::setStyle("Fusion"); } -#endif #ifdef _WIN32 // set application priority class -> high priority @@ -849,7 +831,7 @@ int main ( int argc, char** argv ) // For accessible support we need to add a plugin to qt. The plugin has to // be located in the install directory of the software by the installer. // Here, we set the path to our application path. - QDir ApplDir ( QApplication::applicationDirPath() ); + QDir ApplDir ( QGuiApplication::applicationDirPath() ); pApp->addLibraryPath ( QString ( ApplDir.absolutePath() ) ); #endif @@ -929,11 +911,15 @@ int main ( int argc, char** argv ) bNoAutoJackConnect, strClientName, bEnableIPv6, - bMuteMeInPersonalMix ); + bMuteMeInPersonalMix, + strIniFileName, + false, + CommandLineOptions + ); // load settings from init-file (command line options override) - CClientSettings Settings ( &Client, strIniFileName ); - Settings.Load ( CommandLineOptions ); + // CClientSettings Settings ( &Client, strIniFileName ); + // Settings.Load ( CommandLineOptions ); # ifndef NO_JSON_RPC if ( pRpcServer ) @@ -946,26 +932,34 @@ int main ( int argc, char** argv ) if ( bUseGUI ) { // load translation - if ( bUseTranslation ) - { - CLocale::LoadTranslation ( Settings.strLanguage, pApp ); - CInstPictures::UpdateTableOnLanguageChange(); - } + // if ( bUseTranslation ) + // { + // CLocale::LoadTranslation ( Settings.strLanguage, pApp ); + // CInstPictures::UpdateTableOnLanguageChange(); + // } + + qmlRegisterType("Jamulus", 1, 0, "ChannelFader"); + + // audioMixerBoard.testAddChannel(); + + // Create the QML application engine + QQmlApplicationEngine engine; - // GUI object - CClientDlg ClientDlg ( &Client, - &Settings, - strConnOnStartupAddress, - strMIDISetup, - bShowComplRegConnList, - bShowAnalyzerConsole, - bMuteStream, - bEnableIPv6, - nullptr ); + // Expose C++ objects to QML + QQmlContext* context = engine.rootContext(); + context->setContextProperty("_main", &Client); + context->setContextProperty("_settings", &Client.pSettings); + context->setContextProperty("_audioMixerBoard", &Client.audioMixerBoard); + context->setContextProperty("_chatBox", &Client.chatBox); - // show dialog - ClientDlg.show(); + // Load the main QML file + engine.load(QUrl(QStringLiteral("qrc:/AppWindow.qml"))); + // engine.load(QUrl(QStringLiteral("qrc:/testQml.qml"))); + if (engine.rootObjects().isEmpty()) + return -1; + + // In main client mode - so call run() rather than exec() to setup url handler stuff pApp->exec(); } else @@ -973,7 +967,6 @@ int main ( int argc, char** argv ) { // only start application without using the GUI qInfo() << qUtf8Printable ( GetVersionAndNameStr ( false ) ); - pApp->exec(); } } @@ -1008,29 +1001,30 @@ int main ( int argc, char** argv ) { new CServerRpc ( &Server, pRpcServer, pRpcServer ); } - #endif + + #ifndef HEADLESS if ( bUseGUI ) { // load settings from init-file (command line options override) - CServerSettings Settings ( &Server, strIniFileName ); - Settings.Load ( CommandLineOptions ); + // CServerSettings Settings ( &Server, strIniFileName ); + // Settings.Load ( CommandLineOptions ); // load translation - if ( bUseTranslation ) - { - CLocale::LoadTranslation ( Settings.strLanguage, pApp ); - } + // if ( bUseTranslation ) + // { + // CLocale::LoadTranslation ( Settings.strLanguage, pApp ); + // } // GUI object for the server - CServerDlg ServerDlg ( &Server, &Settings, bStartMinimized, nullptr ); +// CServerDlg ServerDlg ( &Server, &Settings, bStartMinimized, nullptr ); - // show dialog (if not the minimized flag is set) - if ( !bStartMinimized ) - { - ServerDlg.show(); - } +// // show dialog (if not the minimized flag is set) +// if ( !bStartMinimized ) +// { +// ServerDlg.show(); +// } pApp->exec(); } @@ -1058,7 +1052,7 @@ int main ( int argc, char** argv ) #ifndef HEADLESS if ( bUseGUI ) { - QMessageBox::critical ( nullptr, APP_NAME, generr.GetErrorText(), "Quit", nullptr ); + // FIXME - show error here in QML // generr.GetErrorText(), } else #endif @@ -1105,32 +1099,32 @@ QString UsageArguments ( char** argv ) " -6, --enableipv6 enable IPv6 addressing (IPv4 is always enabled)\n" "\n" "Server only:\n" - " -d, --discononquit disconnect all Clients on quit\n" - " -e, --directoryaddress address of the Directory with which to register\n" - " (or 'localhost' to run as a Directory)\n" - " --directoryfile File to hold server list across Directory restarts. Directories only.\n" - " -f, --listfilter Server list whitelist filter. Directories only. Format:\n" - " [IP address 1];[IP address 2];[IP address 3]; ...\n" - " -F, --fastupdate use 64 samples frame size mode\n" - " -l, --log enable logging, set file name\n" - " -L, --licence show an agreement window before users can connect\n" - " -m, --htmlstatus deprecated, please use JSON-RPC instead\n" - " -o, --serverinfo registration info for this Server. Format:\n" - " [name];[city];[country as two-letter ISO country code or Qt5 QLocale ID]\n" - " --serverpublicip public IP address for this Server. Needed when\n" - " registering with a server list hosted\n" - " behind the same NAT\n" - " -P, --delaypan start with delay panning enabled\n" - " -R, --recording set server recording directory; server will record when a session is active by default\n" - " --norecord set server not to record by default when recording is configured\n" - " -s, --server start Server\n" - " --serverbindip IP address the Server will bind to (rather than all)\n" - " -T, --multithreading use multithreading to make better use of\n" - " multi-core CPUs and support more Clients\n" - " -u, --numchannels maximum number of channels\n" - " -w, --welcomemessage welcome message to display on connect\n" - " (string or filename, HTML supported)\n" - " -z, --startminimized start minimizied\n" + " -d, --discononquit disconnect all Clients on quit\n" + " -e, --directoryserver address of the directory Server with which to register\n" + " (or 'localhost' to host a server list on this Server)\n" + " --directoryfile Remember registered Servers even if the Directory is restarted. Directory Servers only.\n" + " -f, --listfilter Server list whitelist filter. Format:\n" + " [IP address 1];[IP address 2];[IP address 3]; ...\n" + " -F, --fastupdate use 64 samples frame size mode\n" + " -l, --log enable logging, set file name\n" + " -L, --licence show an agreement window before users can connect\n" + " -m, --htmlstatus enable HTML status file, set file name\n" + " -o, --serverinfo registration info for this Server. Format:\n" + " [name];[city];[country as two-letter ISO country code or Qt5 QLocale ID]\n" + " --serverpublicip public IP address for this Server. Needed when\n" + " registering with a server list hosted\n" + " behind the same NAT\n" + " -P, --delaypan start with delay panning enabled\n" + " -R, --recording set server recording directory; server will record when a session is active by default\n" + " --norecord set server not to record by default when recording is configured\n" + " -s, --server start Server\n" + " --serverbindip IP address the Server will bind to (rather than all)\n" + " -T, --multithreading use multithreading to make better use of\n" + " multi-core CPUs and support more Clients\n" + " -u, --numchannels maximum number of channels\n" + " -w, --welcomemessage welcome message to display on connect\n" + " (string or filename, HTML supported)\n" + " -z, --startminimized start minimizied\n" "\n" "Client only:\n" " -c, --connect connect to given Server address on startup\n" diff --git a/src/resources.qrc b/src/resources.qrc index 502a459b64..56bddf12ed 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,338 +1,18 @@ - - res/CLEDDisabled.png - res/CLEDGrey.png - res/CLEDGreenBig.png - res/CLEDRedBig.png - res/CLEDYellowBig.png - res/CLEDBlackBig.png - res/IndicatorGreen.png - res/IndicatorYellow.png - res/IndicatorRed.png - res/HLEDGreen.png - res/HLEDBlack.png - res/HLEDRed.png - res/HLEDYellow.png - res/IndicatorRedFancy.png - res/IndicatorYellowFancy.png - res/CLEDGreenSmall.png - res/CLEDBlackSmall.png - res/CLEDRedSmall.png - res/CLEDYellowSmall.png - - - res/faderbackground.png - res/faderhandle.png - res/faderhandlesmall.png - res/ledbuttonnotpressed.png - res/ledbuttonpressed.png - res/mixerboardbackground.png - res/transparent1x1.png - res/mutediconorange.png - - - res/instruments/none.png - res/instruments/bassguitar.png - res/instruments/clarinet.png - res/instruments/drumset.png - res/instruments/eguitar.png - res/instruments/saxophone.png - res/instruments/scratching.png - res/instruments/rapping.png - res/instruments/trumpet.png - res/instruments/microphone.png - res/instruments/mountaindulcimer.png - res/instruments/keyboard.png - res/instruments/violin.png - res/instruments/aguitar.png - res/instruments/flute.png - res/instruments/accordeon.png - res/instruments/cello.png - res/instruments/trombone.png - res/instruments/frenchhorn.png - res/instruments/tuba.png - res/instruments/doublebass.png - res/instruments/grandpiano.png - res/instruments/synthesizer.png - res/instruments/vocal.png - res/instruments/djembe.png - res/instruments/harmonica.png - res/instruments/recorder.png - res/instruments/listener.png - res/instruments/streamer.png - res/instruments/guitarvocal.png - res/instruments/keyboardvocal.png - res/instruments/bodhran.png - res/instruments/bassoon.png - res/instruments/oboe.png - res/instruments/harp.png - res/instruments/viola.png - res/instruments/congas.png - res/instruments/bongo.png - res/instruments/vocalbass.png - res/instruments/vocalbaritone.png - res/instruments/vocallead.png - res/instruments/vocaltenor.png - res/instruments/vocalalto.png - res/instruments/vocalsoprano.png - res/instruments/banjo.png - res/instruments/mandolin.png - res/instruments/ukulele.png - res/instruments/bassukulele.png - res/instruments/vibraphone.png - res/instruments/conductor.png - - - res/fronticon.png - res/fronticonserver.png - res/servertrayiconactive.png - res/servertrayiconinactive.png - - - res/flags/flagnone.png - res/flags/ad.png - res/flags/ae.png - res/flags/af.png - res/flags/ag.png - res/flags/ai.png - res/flags/al.png - res/flags/am.png - res/flags/an.png - res/flags/ao.png - res/flags/ar.png - res/flags/as.png - res/flags/at.png - res/flags/au.png - res/flags/aw.png - res/flags/ax.png - res/flags/az.png - res/flags/ba.png - res/flags/bb.png - res/flags/bd.png - res/flags/be.png - res/flags/bf.png - res/flags/bg.png - res/flags/bh.png - res/flags/bi.png - res/flags/bj.png - res/flags/bm.png - res/flags/bn.png - res/flags/bo.png - res/flags/br.png - res/flags/bs.png - res/flags/bt.png - res/flags/bv.png - res/flags/bw.png - res/flags/by.png - res/flags/bz.png - res/flags/ca.png - res/flags/cc.png - res/flags/cd.png - res/flags/cf.png - res/flags/cg.png - res/flags/ch.png - res/flags/ci.png - res/flags/ck.png - res/flags/cl.png - res/flags/cm.png - res/flags/cn.png - res/flags/co.png - res/flags/cr.png - res/flags/cs.png - res/flags/cu.png - res/flags/cv.png - res/flags/cx.png - res/flags/cy.png - res/flags/cz.png - res/flags/de.png - res/flags/dj.png - res/flags/dk.png - res/flags/dm.png - res/flags/do.png - res/flags/dz.png - res/flags/ec.png - res/flags/ee.png - res/flags/eg.png - res/flags/eh.png - res/flags/er.png - res/flags/es.png - res/flags/et.png - res/flags/fam.png - res/flags/fi.png - res/flags/fj.png - res/flags/fk.png - res/flags/fm.png - res/flags/fo.png - res/flags/fr.png - res/flags/ga.png - res/flags/gb.png - res/flags/gd.png - res/flags/ge.png - res/flags/gf.png - res/flags/gh.png - res/flags/gi.png - res/flags/gl.png - res/flags/gm.png - res/flags/gn.png - res/flags/gp.png - res/flags/gq.png - res/flags/gr.png - res/flags/gs.png - res/flags/gt.png - res/flags/gu.png - res/flags/gw.png - res/flags/gy.png - res/flags/hk.png - res/flags/hm.png - res/flags/hn.png - res/flags/hr.png - res/flags/ht.png - res/flags/hu.png - res/flags/id.png - res/flags/ie.png - res/flags/il.png - res/flags/in.png - res/flags/io.png - res/flags/iq.png - res/flags/ir.png - res/flags/is.png - res/flags/it.png - res/flags/jm.png - res/flags/jo.png - res/flags/jp.png - res/flags/ke.png - res/flags/kg.png - res/flags/kh.png - res/flags/ki.png - res/flags/km.png - res/flags/kn.png - res/flags/kp.png - res/flags/kr.png - res/flags/kw.png - res/flags/ky.png - res/flags/kz.png - res/flags/la.png - res/flags/lb.png - res/flags/lc.png - res/flags/li.png - res/flags/lk.png - res/flags/lr.png - res/flags/ls.png - res/flags/lt.png - res/flags/lu.png - res/flags/lv.png - res/flags/ly.png - res/flags/ma.png - res/flags/mc.png - res/flags/md.png - res/flags/me.png - res/flags/mg.png - res/flags/mh.png - res/flags/mk.png - res/flags/ml.png - res/flags/mm.png - res/flags/mn.png - res/flags/mo.png - res/flags/mp.png - res/flags/mq.png - res/flags/mr.png - res/flags/ms.png - res/flags/mt.png - res/flags/mu.png - res/flags/mv.png - res/flags/mw.png - res/flags/mx.png - res/flags/my.png - res/flags/mz.png - res/flags/na.png - res/flags/nc.png - res/flags/ne.png - res/flags/nf.png - res/flags/ng.png - res/flags/ni.png - res/flags/nl.png - res/flags/no.png - res/flags/np.png - res/flags/nr.png - res/flags/nu.png - res/flags/nz.png - res/flags/om.png - res/flags/pa.png - res/flags/pe.png - res/flags/pf.png - res/flags/pg.png - res/flags/ph.png - res/flags/pk.png - res/flags/pl.png - res/flags/pm.png - res/flags/pn.png - res/flags/pr.png - res/flags/ps.png - res/flags/pt.png - res/flags/pw.png - res/flags/py.png - res/flags/qa.png - res/flags/re.png - res/flags/ro.png - res/flags/rs.png - res/flags/ru.png - res/flags/rw.png - res/flags/sa.png - res/flags/sb.png - res/flags/sc.png - res/flags/sd.png - res/flags/se.png - res/flags/sg.png - res/flags/sh.png - res/flags/si.png - res/flags/sj.png - res/flags/sk.png - res/flags/sl.png - res/flags/sm.png - res/flags/sn.png - res/flags/so.png - res/flags/sr.png - res/flags/st.png - res/flags/sv.png - res/flags/sy.png - res/flags/sz.png - res/flags/tc.png - res/flags/td.png - res/flags/tf.png - res/flags/tg.png - res/flags/th.png - res/flags/tj.png - res/flags/tk.png - res/flags/tl.png - res/flags/tm.png - res/flags/tn.png - res/flags/to.png - res/flags/tr.png - res/flags/tt.png - res/flags/tv.png - res/flags/tw.png - res/flags/tz.png - res/flags/ua.png - res/flags/ug.png - res/flags/um.png - res/flags/us.png - res/flags/uy.png - res/flags/uz.png - res/flags/va.png - res/flags/vc.png - res/flags/ve.png - res/flags/vg.png - res/flags/vi.png - res/flags/vn.png - res/flags/vu.png - res/flags/wf.png - res/flags/ws.png - res/flags/ye.png - res/flags/yt.png - res/flags/za.png - res/flags/zm.png - res/flags/zw.png + + + + + SettingsView.qml + MainView.qml + StereoLevelMeter.qml + ChannelFader.qml + AppWindow.qml + SingleLevelMeter.qml + ChatBox.qml + SplitHandle.qml + res/sounds/new_user.wav res/sounds/new_message.wav diff --git a/src/serverdlgbase.ui b/src/serverdlgbase.ui deleted file mode 100644 index 2ad8a79498..0000000000 --- a/src/serverdlgbase.ui +++ /dev/null @@ -1,377 +0,0 @@ - - - CServerDlgBase - - - - 0 - 0 - 588 - 560 - - - - - - - - :/png/main/res/fronticonserver.png:/png/main/res/fronticonserver.png - - - true - - - - - - false - - - 4 - - - - Client IP:Port - - - - - Name - - - - - Jitter Buffer Size - - - - - Channels - - - - - - - - - 0 - 0 - - - - 0 - - - - Server Setup - - - - - - - - Directory - - - - - - - - - - STATUS - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 20 - 20 - - - - - - - - - - My Server Info - - - - - - - - Name - - - - - - - Location: City - - - - - - - Location: Region - - - - - - - - - - - - - - - - - - - - - - - - - - - Jam Recorder - - - - - - - New Recording - - - - - - - - 0 - 0 - - - - STATUS - - - - - - - - - - - Session - - - - - - - true - - - - - - - - - Chat Window Welcome (HTML/CSS Supported) - - - - - - - - 0 - 0 - - - - - - - - - Options - - - - - - - - Language - - - - - - - - 0 - 0 - - - - - - - - - - - - Recording Directory - - - - - - - true - - - - - - - - - - - - - - Custom Directory address - - - - - - - - - - - - - - Server List Filename - - - - - - - true - - - - - - - - - - - - Start Minimized on Windows Start - - - - - - - Delay panning - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Update check - - - - - - - - CLanguageComboBox - QComboBox -
util.h
-
-
- - lvwClients - tabWidget - cbxDirectoryType - edtServerName - edtLocationCity - cbxLocationCountry - chbJamRecorder - edtCurrentSessionDir - pbtNewRecording - tedWelcomeMessage - cbxLanguage - pbtRecordingDir - edtRecordingDir - tbtClearRecordingDir - edtCustomDirectory - pbtServerListPersistence - edtServerListPersistence - tbtClearServerListPersistence - chbStartOnOSStart - chbDelayPanning - - - - - -
diff --git a/src/settings.cpp b/src/settings.cpp index 20a89b2ad4..962100d74e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -23,6 +23,35 @@ \******************************************************************************/ #include "settings.h" +#include "client.h" + + +CClientSettings::CClientSettings ( CClient* pNCliP, const QString& sNFiName, QObject* parent ) : + CSettings(), + vecStoredFaderTags ( MAX_NUM_STORED_FADER_SETTINGS, "" ), + vecStoredFaderLevels ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_FADER_MAX ), + vecStoredPanValues ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_PAN_MAX / 2 ), + vecStoredFaderIsSolo ( MAX_NUM_STORED_FADER_SETTINGS, false ), + vecStoredFaderIsMute ( MAX_NUM_STORED_FADER_SETTINGS, false ), + vecStoredFaderGroupID ( MAX_NUM_STORED_FADER_SETTINGS, INVALID_INDEX ), + vstrIPAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), + iNewClientFaderLevel ( 100 ), + iInputBoost ( 1 ), + iSettingsTab ( SETTING_TAB_AUDIONET ), + bConnectDlgShowAllMusicians ( true ), + eChannelSortType ( ST_NO_SORT ), + iNumMixerPanelRows ( 1 ), + vstrDirectoryAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), + eDirectoryType ( AT_DEFAULT ), + bEnableFeedbackDetection ( true ), + bEnableAudioAlerts ( false ), + bOwnFaderFirst ( false ), + pClient ( pNCliP ) +{ + SetFileName ( sNFiName, DEFAULT_INI_FILE_NAME ); + +} + /* Implementation *************************************************************/ void CSettings::Load ( const QList& CommandLineOptions ) @@ -201,7 +230,49 @@ void CSettings::PutIniSetting ( QDomDocument& xmlFile, const QString& sSection, } #ifndef SERVER_ONLY + // Client settings ------------------------------------------------------------- +int CClientSettings::uploadRate() const +{ + // update upstream rate information + return pClient->GetUploadRateKbps(); + +} + +void CClientSettings::updateSettings() +{ + emit slSndCrdDevChanged(); + + // as the soundcard has changed, we need to update all the dependent stuff too + emit sndCrdInputChannelNamesChanged(); + emit sndCardLInChannelChanged(); + emit sndCardRInChannelChanged(); + + emit sndCrdOutputChannelNamesChanged(); + emit sndCardLOutChannelChanged(); + emit sndCardROutChannelChanged(); + + // + emit cbxAudioQualityChanged(); + emit cbxAudioChannelsChanged(); + +} + +void CClientSettings::UpdateUploadRate() +{ + // update upstream rate information label + + // Here we just need to notify QML to update by reading uploadRate() + emit uploadRateChanged(); +} + +void CClientSettings::UpdateDisplay() +{ + UpdateJitterBufferFrame(); + UpdateSoundCardFrame(); + UpdateUploadRate(); +} + void CClientSettings::LoadFaderSettings ( const QString& strCurFileName ) { // prepare file name for loading initialization data from XML file and read @@ -266,7 +337,7 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, GetIniSetting ( IniXMLDocument, "client", "language", CLocale::FindSysLangTransFileName ( CLocale::GetAvailableTranslations() ).first ); // fader channel sorting - if ( GetNumericIniSet ( IniXMLDocument, "client", "channelsort", 0, 5 /* ST_BY_SERVER_CHANNEL */, iValue ) ) + if ( GetNumericIniSet ( IniXMLDocument, "client", "channelsort", 0, 4 /* ST_BY_CITY */, iValue ) ) { eChannelSortType = static_cast ( iValue ); } @@ -293,48 +364,10 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, pClient->ChannelInfo.strName = FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "name_base64", ToBase64 ( QCoreApplication::translate ( "CMusProfDlg", "No Name" ) ) ) ); - // instrument - if ( GetNumericIniSet ( IniXMLDocument, "client", "instrument", 0, CInstPictures::GetNumAvailableInst() - 1, iValue ) ) - { - pClient->ChannelInfo.iInstrument = iValue; - } - - // country - if ( GetNumericIniSet ( IniXMLDocument, "client", "country", 0, static_cast ( QLocale::LastCountry ), iValue ) ) - { - pClient->ChannelInfo.eCountry = CLocale::WireFormatCountryCodeToQtCountry ( iValue ); - } - else - { - // if no country is given, use the one from the operating system - pClient->ChannelInfo.eCountry = QLocale::system().country(); - } - - // city - pClient->ChannelInfo.strCity = FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "city_base64" ) ); - - // skill level - if ( GetNumericIniSet ( IniXMLDocument, "client", "skill", 0, 3 /* SL_PROFESSIONAL */, iValue ) ) - { - pClient->ChannelInfo.eSkillLevel = static_cast ( iValue ); - } - // audio fader if ( GetNumericIniSet ( IniXMLDocument, "client", "audfad", AUD_FADER_IN_MIN, AUD_FADER_IN_MAX, iValue ) ) { - pClient->SetAudioInFader ( iValue ); - } - - // reverberation level - if ( GetNumericIniSet ( IniXMLDocument, "client", "revlev", 0, AUD_REVERB_MAX, iValue ) ) - { - pClient->SetReverbLevel ( iValue ); - } - - // reverberation channel assignment - if ( GetFlagIniSet ( IniXMLDocument, "client", "reverblchan", bValue ) ) - { - pClient->SetReverbOnLeftChan ( bValue ); + pClient->setAudioInPan( iValue ); } // sound card selection @@ -345,7 +378,17 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, # ifndef HEADLESS // special case: when settings are loaded no GUI is yet created, therefore // we have to create a warning message box here directly - QMessageBox::warning ( nullptr, APP_NAME, strError ); + pClient->setUserMsg( strError ); + + // make sure we update GUI - FIXME - what are we updating here? + emit slSndCrdDevChanged(); + // as the soundcard has changed, we need to update all the dependent stuff too + emit sndCrdInputChannelNamesChanged(); + emit sndCardLInChannelChanged(); + emit sndCardRInChannelChanged(); + emit sndCrdOutputChannelNamesChanged(); + emit sndCardLOutChannelChanged(); + emit sndCardROutChannelChanged(); # endif } @@ -412,43 +455,6 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, pClient->SetEnableOPUS64 ( bValue ); } - // GUI design - if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) - { - pClient->SetGUIDesign ( static_cast ( iValue ) ); - } - - // MeterStyle - if ( GetNumericIniSet ( IniXMLDocument, "client", "meterstyle", 0, 4 /* MT_LED_ROUND_BIG */, iValue ) ) - { - pClient->SetMeterStyle ( static_cast ( iValue ) ); - } - else - { - // if MeterStyle is not found in the ini, set it based on the GUI design - if ( GetNumericIniSet ( IniXMLDocument, "client", "guidesign", 0, 2 /* GD_SLIMFADER */, iValue ) ) - { - switch ( iValue ) - { - case GD_STANDARD: - pClient->SetMeterStyle ( MT_BAR_WIDE ); - break; - - case GD_ORIGINAL: - pClient->SetMeterStyle ( MT_LED_STRIPE ); - break; - - case GD_SLIMFADER: - pClient->SetMeterStyle ( MT_BAR_NARROW ); - break; - - default: - pClient->SetMeterStyle ( MT_LED_STRIPE ); - break; - } - } - } - // audio channels if ( GetNumericIniSet ( IniXMLDocument, "client", "audiochannels", 0, 2 /* CC_STEREO */, iValue ) ) { @@ -520,32 +526,8 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, // window position of the main window vecWindowPosMain = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "client", "winposmain_base64" ) ); - // window position of the settings window - vecWindowPosSettings = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "client", "winposset_base64" ) ); - - // window position of the chat window - vecWindowPosChat = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "client", "winposchat_base64" ) ); - - // window position of the connect window - vecWindowPosConnect = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "client", "winposcon_base64" ) ); - - // visibility state of the settings window - if ( GetFlagIniSet ( IniXMLDocument, "client", "winvisset", bValue ) ) - { - bWindowWasShownSettings = bValue; - } - - // visibility state of the chat window - if ( GetFlagIniSet ( IniXMLDocument, "client", "winvischat", bValue ) ) - { - bWindowWasShownChat = bValue; - } - - // visibility state of the connect window - if ( GetFlagIniSet ( IniXMLDocument, "client", "winviscon", bValue ) ) - { - bWindowWasShownConnect = bValue; - } + // set Test setting + strTestMode = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "client", "test_setting" ) ); // selected Settings Tab if ( GetNumericIniSet ( IniXMLDocument, "client", "settingstab", 0, 2, iValue ) ) @@ -668,13 +650,7 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is SetNumericIniSet ( IniXMLDocument, "client", "skill", static_cast ( pClient->ChannelInfo.eSkillLevel ) ); // audio fader - SetNumericIniSet ( IniXMLDocument, "client", "audfad", pClient->GetAudioInFader() ); - - // reverberation level - SetNumericIniSet ( IniXMLDocument, "client", "revlev", pClient->GetReverbLevel() ); - - // reverberation channel assignment - SetFlagIniSet ( IniXMLDocument, "client", "reverblchan", pClient->IsReverbOnLeftChan() ); + SetNumericIniSet ( IniXMLDocument, "client", "audfad", pClient->audioInPan() ); // sound card selection PutIniSetting ( IniXMLDocument, "client", "auddev_base64", ToBase64 ( pClient->GetSndCrdDev() ) ); @@ -706,12 +682,6 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is // enable OPUS64 setting SetFlagIniSet ( IniXMLDocument, "client", "enableopussmall", pClient->GetEnableOPUS64() ); - // GUI design - SetNumericIniSet ( IniXMLDocument, "client", "guidesign", static_cast ( pClient->GetGUIDesign() ) ); - - // MeterStyle - SetNumericIniSet ( IniXMLDocument, "client", "meterstyle", static_cast ( pClient->GetMeterStyle() ) ); - // audio channels SetNumericIniSet ( IniXMLDocument, "client", "audiochannels", static_cast ( pClient->GetAudioChannels() ) ); @@ -733,23 +703,8 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is // window position of the main window PutIniSetting ( IniXMLDocument, "client", "winposmain_base64", ToBase64 ( vecWindowPosMain ) ); - // window position of the settings window - PutIniSetting ( IniXMLDocument, "client", "winposset_base64", ToBase64 ( vecWindowPosSettings ) ); - - // window position of the chat window - PutIniSetting ( IniXMLDocument, "client", "winposchat_base64", ToBase64 ( vecWindowPosChat ) ); - - // window position of the connect window - PutIniSetting ( IniXMLDocument, "client", "winposcon_base64", ToBase64 ( vecWindowPosConnect ) ); - - // visibility state of the settings window - SetFlagIniSet ( IniXMLDocument, "client", "winvisset", bWindowWasShownSettings ); - - // visibility state of the chat window - SetFlagIniSet ( IniXMLDocument, "client", "winvischat", bWindowWasShownChat ); - - // visibility state of the connect window - SetFlagIniSet ( IniXMLDocument, "client", "winviscon", bWindowWasShownConnect ); + // save if in Test mode + PutIniSetting ( IniXMLDocument, "client", "test_setting", ToBase64 ( strTestMode ) ); // Settings Tab SetNumericIniSet ( IniXMLDocument, "client", "settingstab", iSettingsTab ); @@ -783,212 +738,451 @@ void CClientSettings::WriteFaderSettingsToXML ( QDomDocument& IniXMLDocument ) SetNumericIniSet ( IniXMLDocument, "client", QString ( "storedgroupid%1" ).arg ( iIdx ), vecStoredFaderGroupID[iIdx] ); } } -#endif -// Server settings ------------------------------------------------------------- -// that this gets called means we are not headless -void CServerSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, const QList& CommandLineOptions ) +void CClientSettings::UpdateJitterBufferFrame() { - int iValue; - bool bValue; - // window position of the main window - vecWindowPosMain = FromBase64ToByteArray ( GetIniSetting ( IniXMLDocument, "server", "winposmain_base64" ) ); + emit sldNetBufChanged(); - // name/city/country - if ( !CommandLineOptions.contains ( "--serverinfo" ) ) - { - // name - pServer->SetServerName ( GetIniSetting ( IniXMLDocument, "server", "name" ) ); + emit sldNetBufServerChanged(); - // city - pServer->SetServerCity ( GetIniSetting ( IniXMLDocument, "server", "city" ) ); + emit chbAutoJitBufChanged(); +} - // country - if ( GetNumericIniSet ( IniXMLDocument, "server", "country", 0, static_cast ( QLocale::LastCountry ), iValue ) ) - { - pServer->SetServerCountry ( CLocale::WireFormatCountryCodeToQtCountry ( iValue ) ); - } - } +void CClientSettings::UpdateSoundCardFrame() +{ + // get current actual buffer size value + const int iCurActualBufSize = pClient->GetSndCrdActualMonoBlSize(); - // norecord flag - if ( !CommandLineOptions.contains ( "--norecord" ) ) - { - if ( GetFlagIniSet ( IniXMLDocument, "server", "norecord", bValue ) ) - { - pServer->SetEnableRecording ( !bValue ); - } - } + // check which predefined size is used (it is possible that none is used) + const bool bPreferredChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED ); + const bool bDefaultChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT ); + const bool bSafeChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE ); - // welcome message - if ( !CommandLineOptions.contains ( "--welcomemessage" ) ) - { - pServer->SetWelcomeMessage ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "server", "welcome" ) ) ); - } + setRbtBufferDelayPreferred( bPreferredChecked ); + setRbtBufferDelayDefault( bDefaultChecked ); + setRbtBufferDelaySafe( bSafeChecked ); - // language - strLanguage = - GetIniSetting ( IniXMLDocument, "server", "language", CLocale::FindSysLangTransFileName ( CLocale::GetAvailableTranslations() ).first ); + emit fraSiFactSafeSupportedChanged(); + emit fraSiFactDefSupportedChanged(); + emit fraSiFactPrefSupportedChanged(); - // base recording directory - if ( !CommandLineOptions.contains ( "--recording" ) ) - { - pServer->SetRecordingDir ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "server", "recordingdir_base64" ) ) ); - } + emit bufSizeChanged(); +} - // to avoid multiple registrations, must do this after collecting serverinfo - if ( !CommandLineOptions.contains ( "--centralserver" ) && // for backwards compatibility - !CommandLineOptions.contains ( "--directoryserver" ) && // also for backwards compatibility - !CommandLineOptions.contains ( "--directoryaddress" ) ) - { - // custom directory - // CServerListManager defaults to command line argument (or "" if not passed) - // Server GUI defaults to "" - QString directoryAddress = ""; +int CClientSettings::edtNewClientLevel() const +{ + return iNewClientFaderLevel; +} - //### TODO: BEGIN ###// - // compatibility to old version < 3.8.2 - directoryAddress = GetIniSetting ( IniXMLDocument, "server", "centralservaddr", directoryAddress ); - //### TODO: END ###// +void CClientSettings::setEdtNewClientLevel(const int newClientLevel ) +{ + iNewClientFaderLevel = newClientLevel; + emit edtNewClientLevelChanged(); +} - directoryAddress = GetIniSetting ( IniXMLDocument, "server", "directoryaddress", directoryAddress ); +int CClientSettings::sldNetBuf() const +{ + return pClient->GetSockBufNumFrames(); +} - pServer->SetDirectoryAddress ( directoryAddress ); - } +void CClientSettings::setSldNetBuf( const int setBufVal ) +{ + pClient->SetSockBufNumFrames ( setBufVal, true ); + emit sldNetBufChanged(); + UpdateJitterBufferFrame(); // FIXME - this repeats previous signal +} - // directory type - // CServerListManager defaults to AT_NONE - // Because type could be AT_CUSTOM, it has to be set after the address to avoid multiple registrations - EDirectoryType directoryType = AT_NONE; +int CClientSettings::sldNetBufServer() const +{ - // if a command line Directory address is set, set the Directory Type (genre) to AT_CUSTOM so it's used - if ( CommandLineOptions.contains ( "--centralserver" ) || CommandLineOptions.contains ( "--directoryserver" ) || - CommandLineOptions.contains ( "--directoryaddress" ) ) - { - directoryType = AT_CUSTOM; + return pClient->GetServerSockBufNumFrames(); +} + +void CClientSettings::setSldNetBufServer( const int setServerBufVal ) +{ + pClient->SetServerSockBufNumFrames( setServerBufVal); + emit sldNetBufServerChanged(); + UpdateJitterBufferFrame(); // FIXME - this repeats previous signal +} + + +int CClientSettings::cbxAudioChannels() const +{ + return pClient->GetAudioChannels(); +} + +void CClientSettings::setCbxAudioChannels( const int iChanIdx ) +{ + if ( pClient->GetAudioChannels() == static_cast ( iChanIdx ) ) + return; + + pClient->SetAudioChannels ( static_cast ( iChanIdx ) ); + emit cbxAudioChannelsChanged(); + + qDebug() << "setCbxAudioChannels to: " << iChanIdx; + UpdateDisplay(); // upload rate will be changed +} + +int CClientSettings::cbxAudioQuality() const +{ + return pClient->GetAudioQuality(); +} + +void CClientSettings::setCbxAudioQuality( const int qualityIdx ) +{ + pClient->SetAudioQuality ( static_cast ( qualityIdx ) ); + emit cbxAudioQualityChanged(); + + qDebug() << "setCbxAudioQuality to: " << qualityIdx; + UpdateDisplay(); // upload rate will be changed + +} + +int CClientSettings::dialInputBoost() const +{ + return iInputBoost; +} + +void CClientSettings::setDialInputBoost( const int inputBoost ) +{ + iInputBoost = inputBoost; + pClient->SetInputBoost ( iInputBoost ); + emit dialInputBoostChanged(); +} + +int CClientSettings::spnMixerRows() const +{ + return iNumMixerPanelRows; +} + +void CClientSettings::setSpnMixerRows( const int mixerRows ) +{ + if ( iNumMixerPanelRows == mixerRows ) + return; + + iNumMixerPanelRows = mixerRows; + emit spnMixerRowsChanged(); +} + +QString CClientSettings::pedtAlias() const +{ + return pClient->ChannelInfo.strName; +} + +void CClientSettings::setPedtAlias( QString strAlias ) +{ + // truncate string if necessary + const QString thisStr = TruncateString ( strAlias, MAX_LEN_FADER_TAG ); + + if (pClient->ChannelInfo.strName == thisStr) + return; + + pClient->ChannelInfo.strName = thisStr; + pClient->SetRemoteInfo(); + + emit pedtAliasChanged(); + qDebug() << "pedt alias changed: " << thisStr; + +} + +bool CClientSettings::chbDetectFeedback() +{ + return bEnableFeedbackDetection; +} + +void CClientSettings::setChbDetectFeedback( bool detectFeedback ) +{ + if ( bEnableFeedbackDetection == detectFeedback ) + return; + + bEnableFeedbackDetection = detectFeedback; + emit chbDetectFeedbackChanged(); +} + +bool CClientSettings::chbEnableOPUS64() +{ + return pClient->GetEnableOPUS64(); +} + +void CClientSettings::setChbEnableOPUS64( bool enableOPUS64 ) +{ + if ( pClient->GetEnableOPUS64() == enableOPUS64 ) + return; + + pClient->SetEnableOPUS64 ( enableOPUS64 ); + emit chbEnableOPUS64Changed(); + UpdateDisplay(); +} + +bool CClientSettings::rbtBufferDelayPreferred() +{ + // get current actual buffer size value + const int iCurActualBufSize = pClient->GetSndCrdActualMonoBlSize(); + + // check which predefined size is used (it is possible that none is used) + const bool bPreferredChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_PREFERRED ); + return bPreferredChecked; +} + +void CClientSettings::setRbtBufferDelayPreferred( bool enableBufDelPref ) +{ + pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED ); + qDebug() << "SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_PREFERRED )"; + emit rbtBufferDelayPreferredChanged(); + emit bufSizeChanged(); +} + +bool CClientSettings::rbtBufferDelayDefault() +{ + // get current actual buffer size value + const int iCurActualBufSize = pClient->GetSndCrdActualMonoBlSize(); + + // check which predefined size is used (it is possible that none is used) + const bool bDefaultChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_DEFAULT ); + return bDefaultChecked; +} + +void CClientSettings::setRbtBufferDelayDefault( bool enableBufDelDef ) +{ + pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT ); + qDebug() << "SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_DEFAULT )"; + emit rbtBufferDelayDefaultChanged(); + emit bufSizeChanged(); +} + +bool CClientSettings::rbtBufferDelaySafe() +{ + // get current actual buffer size value + const int iCurActualBufSize = pClient->GetSndCrdActualMonoBlSize(); + + // check which predefined size is used (it is possible that none is used) + const bool bSafeChecked = ( iCurActualBufSize == SYSTEM_FRAME_SIZE_SAMPLES * FRAME_SIZE_FACTOR_SAFE ); + return bSafeChecked; +} + +void CClientSettings::setRbtBufferDelaySafe( bool enableBufDelSafe ) +{ + pClient->SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_SAFE ); + qDebug() << "SetSndCrdPrefFrameSizeFactor ( FRAME_SIZE_FACTOR_SAFE )"; + emit rbtBufferDelaySafeChanged(); + emit bufSizeChanged(); +} + +bool CClientSettings::fraSiFactPrefSupported() +{ + return pClient->GetFraSiFactPrefSupported(); +} + +bool CClientSettings::fraSiFactDefSupported() +{ + return pClient->GetFraSiFactDefSupported(); +} + +bool CClientSettings::fraSiFactSafeSupported() +{ + return pClient->GetFraSiFactSafeSupported(); +} + +QString CClientSettings::sndCrdBufferDelayPreferred() +{ + return GenSndCrdBufferDelayString( FRAME_SIZE_FACTOR_PREFERRED * SYSTEM_FRAME_SIZE_SAMPLES ); +} + +QString CClientSettings::sndCrdBufferDelaySafe() +{ + return GenSndCrdBufferDelayString( FRAME_SIZE_FACTOR_SAFE * SYSTEM_FRAME_SIZE_SAMPLES ); +} + +QString CClientSettings::sndCrdBufferDelayDefault() +{ + return GenSndCrdBufferDelayString( FRAME_SIZE_FACTOR_DEFAULT * SYSTEM_FRAME_SIZE_SAMPLES ); +} + +QString CClientSettings::GenSndCrdBufferDelayString( const int iFrameSize, const QString strAddText ) +{ + // use two times the buffer delay for the entire delay since + // we have input and output + return QString().setNum ( static_cast ( iFrameSize ) * 2 * 1000 / SYSTEM_SAMPLE_RATE_HZ, 'f', 2 ) + " ms (" + + QString().setNum ( iFrameSize ) + strAddText + ")"; + +} + +QString CClientSettings::bufSize() +{ + return GenSndCrdBufferDelayString ( pClient->GetSndCrdActualMonoBlSize() ); +} + +bool CClientSettings::chbAutoJitBuf() +{ + return pClient->GetDoAutoSockBufSize(); +} + +void CClientSettings::setChbAutoJitBuf( bool autoJit ) +{ + if ( pClient->GetDoAutoSockBufSize() == autoJit ) + return; + + pClient->SetDoAutoSockBufSize ( autoJit ); + qDebug() << "setChbAutoJitBuf changed to: " << pClient->GetDoAutoSockBufSize(); + emit chbAutoJitBufChanged(); + + UpdateJitterBufferFrame(); // FIXME - this repeats previous signal +} + +// soundcard box +QStringList CClientSettings::slSndCrdDevNames() +{ + return pClient->GetSndCrdDevNames(); +} + +QStringList CClientSettings::sndCrdInputChannelNames() +{ + QStringList inputChannelNames; + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumInputChannels(); iSndChanIdx++ ) { + QString inputChannelName = pClient->GetSndCrdInputChannelName ( iSndChanIdx ); + inputChannelNames.append(inputChannelName); } - else - { - //### TODO: BEGIN ###// - // compatibility to old version < 3.4.7 - if ( GetFlagIniSet ( IniXMLDocument, "server", "defcentservaddr", bValue ) ) - { - directoryType = bValue ? AT_DEFAULT : AT_CUSTOM; - } - else - { - //### TODO: END ###// - - // if "directorytype" itself is set, use it (note "AT_NONE", "AT_DEFAULT" and "AT_CUSTOM" are min/max directory type here) - - //### TODO: BEGIN ###// - // compatibility to old version < 3.8.2 - if ( GetNumericIniSet ( IniXMLDocument, - "server", - "centservaddrtype", - static_cast ( AT_DEFAULT ), - static_cast ( AT_CUSTOM ), - iValue ) ) - { - directoryType = static_cast ( iValue ); - } - //### TODO: END ###// - - else - { - if ( GetNumericIniSet ( IniXMLDocument, - "server", - "directorytype", - static_cast ( AT_NONE ), - static_cast ( AT_CUSTOM ), - iValue ) ) - { - directoryType = static_cast ( iValue ); - } - } - } + return inputChannelNames; +} - //### TODO: BEGIN ###// - // compatibility to old version < 3.9.0 - // override type to AT_NONE if servlistenabled exists and is false - if ( GetFlagIniSet ( IniXMLDocument, "server", "servlistenabled", bValue ) && !bValue ) - { - directoryType = AT_NONE; - } - //### TODO: END ###// +QStringList CClientSettings::sndCrdOutputChannelNames() +{ + QStringList outputChannelNames; + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumOutputChannels(); iSndChanIdx++ ) { + QString inputChannelName = pClient->GetSndCrdOutputChannelName ( iSndChanIdx ); + outputChannelNames.append(inputChannelName); } + return outputChannelNames; +} - pServer->SetDirectoryType ( directoryType ); +QString CClientSettings::slSndCrdDev() +{ + return pClient->GetSndCrdDev(); +} - // server list persistence file name - if ( !CommandLineOptions.contains ( "--directoryfile" ) ) +void CClientSettings::setSlSndCrdDev( const QString& sndCardDev ) +{ + qDebug() << "setSlSndCrdDev: Passed sndCardDev value: " << sndCardDev; + qDebug() << "setSlSndCrdDev: Current sndCardDev value: " << pClient->GetSndCrdDev(); + if ( pClient->GetSndCrdDev() == sndCardDev ) { - pServer->SetServerListFileName ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "server", "directoryfile_base64" ) ) ); + qDebug() << "setSlSndCrdDev: NOT SETTING ANYTHING"; + return; } - // start minimized on OS start - if ( !CommandLineOptions.contains ( "--startminimized" ) ) + qDebug() << "setSlSndCrdDev: CHANGING sndcarddev to " << sndCardDev ; // on console + pClient->SetSndCrdDev ( sndCardDev ); + QString success = pClient->SetSndCrdDev(sndCardDev); + if (success != "") { - if ( GetFlagIniSet ( IniXMLDocument, "server", "autostartmin", bValue ) ) - { - pServer->SetAutoRunMinimized ( bValue ); - } + qWarning() << "Failed to set soundcard device. Defaulting to " << pClient->GetSndCrdDev(); } + emit slSndCrdDevChanged(); + emit slSndCrdDevNamesChanged(); - // delay panning - if ( !CommandLineOptions.contains ( "--delaypan" ) ) - { - if ( GetFlagIniSet ( IniXMLDocument, "server", "delaypan", bValue ) ) - { - pServer->SetEnableDelayPanning ( bValue ); - } - } + // as the soundcard has changed, we need to update all the dependent stuff too + emit sndCrdInputChannelNamesChanged(); + emit sndCardLInChannelChanged(); + emit sndCardRInChannelChanged(); + + emit sndCrdOutputChannelNamesChanged(); + emit sndCardLOutChannelChanged(); + emit sndCardROutChannelChanged(); + + // update buffer stuff + UpdateDisplay(); } -void CServerSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool isAboutToQuit ) +// channel selectors +int CClientSettings::sndCardNumInputChannels() { - // window position of the main window - PutIniSetting ( IniXMLDocument, "server", "winposmain_base64", ToBase64 ( vecWindowPosMain ) ); + return pClient->GetSndCrdNumInputChannels(); +} - // directory type - SetNumericIniSet ( IniXMLDocument, "server", "directorytype", static_cast ( pServer->GetDirectoryType() ) ); +int CClientSettings::sndCardNumOutputChannels() +{ + return pClient->GetSndCrdNumOutputChannels(); +} - // name - PutIniSetting ( IniXMLDocument, "server", "name", pServer->GetServerName() ); +QString CClientSettings::sndCardLInChannel() +{ + return pClient->GetSndCrdInputChannelName( pClient->GetSndCrdLeftInputChannel() ); +} - // city - PutIniSetting ( IniXMLDocument, "server", "city", pServer->GetServerCity() ); +void CClientSettings::setSndCardLInChannel( QString chanName ) +{ + if ( sndCardLInChannel() == chanName ) + return; - // country - SetNumericIniSet ( IniXMLDocument, "server", "country", CLocale::QtCountryToWireFormatCountryCode ( pServer->GetServerCountry() ) ); + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumInputChannels(); iSndChanIdx++ ) { + if ( chanName == pClient->GetSndCrdInputChannelName( iSndChanIdx ) ) { + pClient->SetSndCrdLeftInputChannel( iSndChanIdx ); + break; + } + } - // norecord flag - SetFlagIniSet ( IniXMLDocument, "server", "norecord", pServer->GetDisableRecording() ); + emit sndCardLInChannelChanged(); +} - // welcome message - PutIniSetting ( IniXMLDocument, "server", "welcome", ToBase64 ( pServer->GetWelcomeMessage() ) ); +QString CClientSettings::sndCardRInChannel() +{ + return pClient->GetSndCrdInputChannelName( pClient->GetSndCrdRightInputChannel() ); +} - // language - PutIniSetting ( IniXMLDocument, "server", "language", strLanguage ); +void CClientSettings::setSndCardRInChannel( QString chanName ) +{ + if ( sndCardRInChannel() == chanName ) + return; + + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumInputChannels(); iSndChanIdx++ ) { + if ( chanName == pClient->GetSndCrdInputChannelName( iSndChanIdx ) ) { + pClient->SetSndCrdRightInputChannel( iSndChanIdx ); + break; + } + } - // base recording directory - PutIniSetting ( IniXMLDocument, "server", "recordingdir_base64", ToBase64 ( pServer->GetRecordingDir() ) ); + emit sndCardRInChannelChanged(); +} - // custom directory - PutIniSetting ( IniXMLDocument, "server", "directoryaddress", pServer->GetDirectoryAddress() ); +QString CClientSettings::sndCardLOutChannel() +{ + return pClient->GetSndCrdOutputChannelName( pClient->GetSndCrdLeftOutputChannel() ); +} - // server list persistence file name - PutIniSetting ( IniXMLDocument, "server", "directoryfile_base64", ToBase64 ( pServer->GetServerListFileName() ) ); +void CClientSettings::setSndCardLOutChannel( QString chanName ) +{ + if ( sndCardLOutChannel() == chanName ) + return; - // start minimized on OS start - SetFlagIniSet ( IniXMLDocument, "server", "autostartmin", pServer->GetAutoRunMinimized() ); + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumOutputChannels(); iSndChanIdx++ ) { + if ( chanName == pClient->GetSndCrdOutputChannelName( iSndChanIdx ) ) { + pClient->SetSndCrdLeftOutputChannel( iSndChanIdx ); + break; + } + } - // delay panning - SetFlagIniSet ( IniXMLDocument, "server", "delaypan", pServer->IsDelayPanningEnabled() ); + emit sndCardLOutChannelChanged(); +} - // we MUST do this after saving the value and Save() is called OnAboutToQuit() - if ( isAboutToQuit ) - { - pServer->SetDirectoryType ( AT_NONE ); +QString CClientSettings::sndCardROutChannel() +{ + return pClient->GetSndCrdOutputChannelName( pClient->GetSndCrdRightOutputChannel() ); +} + +void CClientSettings::setSndCardROutChannel( QString chanName ) +{ + if ( sndCardROutChannel() == chanName ) + return; + + for ( int iSndChanIdx = 0; iSndChanIdx < pClient->GetSndCrdNumOutputChannels(); iSndChanIdx++ ) { + if ( chanName == pClient->GetSndCrdOutputChannelName( iSndChanIdx ) ) { + pClient->SetSndCrdRightOutputChannel( iSndChanIdx ); + break; + } } + + emit sndCardROutChannelChanged(); } + +#endif diff --git a/src/settings.h b/src/settings.h index a7d7564519..c28fe6cfe0 100644 --- a/src/settings.h +++ b/src/settings.h @@ -29,13 +29,12 @@ #include #include #ifndef HEADLESS -# include -# include +# include #endif #include "global.h" -#ifndef SERVER_ONLY -# include "client.h" -#endif +// #ifndef SERVER_ONLY +// # include "client.h" +// #endif #include "server.h" #include "util.h" @@ -90,9 +89,7 @@ class CSettings : public QObject virtual void ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, const QList& CommandLineOptions ) = 0; void ReadFromFile ( const QString& strCurFileName, QDomDocument& XMLDocument ); - void WriteToFile ( const QString& strCurFileName, const QDomDocument& XMLDocument ); - void SetFileName ( const QString& sNFiName, const QString& sDefaultFileName ); // The following functions implement the conversion from the general string @@ -134,40 +131,146 @@ public slots: void OnAboutToQuit() { Save ( true ); } }; + #ifndef SERVER_ONLY +class CClient; // Forward declaration + class CClientSettings : public CSettings { + Q_OBJECT + + Q_PROPERTY(int edtNewClientLevel READ edtNewClientLevel WRITE setEdtNewClientLevel NOTIFY edtNewClientLevelChanged) + Q_PROPERTY(int uploadRate READ uploadRate NOTIFY uploadRateChanged) + Q_PROPERTY(int sldNetBuf READ sldNetBuf WRITE setSldNetBuf NOTIFY sldNetBufChanged) + Q_PROPERTY(int sldNetBufMin READ sldNetBufMin CONSTANT) + Q_PROPERTY(int sldNetBufMax READ sldNetBufMax CONSTANT) + Q_PROPERTY(int sldNetBufServer READ sldNetBufServer WRITE setSldNetBufServer NOTIFY sldNetBufServerChanged) + Q_PROPERTY(QString bufSize READ bufSize NOTIFY bufSizeChanged) + Q_PROPERTY(int cbxAudioChannels READ cbxAudioChannels WRITE setCbxAudioChannels NOTIFY cbxAudioChannelsChanged) + Q_PROPERTY(int cbxAudioQuality READ cbxAudioQuality WRITE setCbxAudioQuality NOTIFY cbxAudioQualityChanged) + Q_PROPERTY(int dialInputBoost READ dialInputBoost WRITE setDialInputBoost NOTIFY dialInputBoostChanged) + Q_PROPERTY(QString pedtAlias READ pedtAlias WRITE setPedtAlias NOTIFY pedtAliasChanged) + Q_PROPERTY(int spnMixerRows READ spnMixerRows WRITE setSpnMixerRows NOTIFY spnMixerRowsChanged) + Q_PROPERTY(bool chbDetectFeedback READ chbDetectFeedback WRITE setChbDetectFeedback NOTIFY chbDetectFeedbackChanged) + Q_PROPERTY(bool chbEnableOPUS64 READ chbEnableOPUS64 WRITE setChbEnableOPUS64 NOTIFY chbEnableOPUS64Changed) + Q_PROPERTY(QString sndCrdBufferDelayPreferred READ sndCrdBufferDelayPreferred NOTIFY sndCrdBufferDelayPreferredChanged) + Q_PROPERTY(QString sndCrdBufferDelaySafe READ sndCrdBufferDelaySafe NOTIFY sndCrdBufferDelaySafeChanged) + Q_PROPERTY(QString sndCrdBufferDelayDefault READ sndCrdBufferDelayDefault NOTIFY sndCrdBufferDelayDefaultChanged) + Q_PROPERTY(bool rbtBufferDelayPreferred READ rbtBufferDelayPreferred WRITE setRbtBufferDelayPreferred NOTIFY rbtBufferDelayPreferredChanged) + Q_PROPERTY(bool rbtBufferDelayDefault READ rbtBufferDelayDefault WRITE setRbtBufferDelayDefault NOTIFY rbtBufferDelayDefaultChanged) + Q_PROPERTY(bool rbtBufferDelaySafe READ rbtBufferDelaySafe WRITE setRbtBufferDelaySafe NOTIFY rbtBufferDelaySafeChanged) + Q_PROPERTY(bool fraSiFactPrefSupported READ fraSiFactPrefSupported NOTIFY fraSiFactPrefSupportedChanged) + Q_PROPERTY(bool fraSiFactDefSupported READ fraSiFactDefSupported NOTIFY fraSiFactDefSupportedChanged) + Q_PROPERTY(bool fraSiFactSafeSupported READ fraSiFactSafeSupported NOTIFY fraSiFactSafeSupportedChanged) + Q_PROPERTY(bool chbAutoJitBuf READ chbAutoJitBuf WRITE setChbAutoJitBuf NOTIFY chbAutoJitBufChanged) + Q_PROPERTY(QStringList slSndCrdDevNames READ slSndCrdDevNames NOTIFY slSndCrdDevNamesChanged) + Q_PROPERTY(QStringList sndCrdInputChannelNames READ sndCrdInputChannelNames NOTIFY sndCrdInputChannelNamesChanged) + Q_PROPERTY(QStringList sndCrdOutputChannelNames READ sndCrdOutputChannelNames NOTIFY sndCrdOutputChannelNamesChanged) + Q_PROPERTY(QString slSndCrdDev READ slSndCrdDev WRITE setSlSndCrdDev NOTIFY slSndCrdDevChanged) + Q_PROPERTY(int sndCardNumInputChannels READ sndCardNumInputChannels NOTIFY sndCardNumInputChannelsChanged) + Q_PROPERTY(int sndCardNumOutputChannels READ sndCardNumOutputChannels NOTIFY sndCardNumOutputChannelsChanged) + Q_PROPERTY(QString sndCardLInChannel READ sndCardLInChannel WRITE setSndCardLInChannel NOTIFY sndCardLInChannelChanged) + Q_PROPERTY(QString sndCardRInChannel READ sndCardRInChannel WRITE setSndCardRInChannel NOTIFY sndCardRInChannelChanged) + Q_PROPERTY(QString sndCardLOutChannel READ sndCardLOutChannel WRITE setSndCardLOutChannel NOTIFY sndCardLOutChannelChanged) + Q_PROPERTY(QString sndCardROutChannel READ sndCardROutChannel WRITE setSndCardROutChannel NOTIFY sndCardROutChannelChanged) + + public: - CClientSettings ( CClient* pNCliP, const QString& sNFiName ) : - CSettings(), - vecStoredFaderTags ( MAX_NUM_STORED_FADER_SETTINGS, "" ), - vecStoredFaderLevels ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_FADER_MAX ), - vecStoredPanValues ( MAX_NUM_STORED_FADER_SETTINGS, AUD_MIX_PAN_MAX / 2 ), - vecStoredFaderIsSolo ( MAX_NUM_STORED_FADER_SETTINGS, false ), - vecStoredFaderIsMute ( MAX_NUM_STORED_FADER_SETTINGS, false ), - vecStoredFaderGroupID ( MAX_NUM_STORED_FADER_SETTINGS, INVALID_INDEX ), - vstrIPAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), - iNewClientFaderLevel ( 100 ), - iInputBoost ( 1 ), - iSettingsTab ( SETTING_TAB_AUDIONET ), - bConnectDlgShowAllMusicians ( true ), - eChannelSortType ( ST_NO_SORT ), - iNumMixerPanelRows ( 1 ), - vstrDirectoryAddress ( MAX_NUM_SERVER_ADDR_ITEMS, "" ), - eDirectoryType ( AT_DEFAULT ), - bEnableFeedbackDetection ( true ), - bEnableAudioAlerts ( false ), - vecWindowPosSettings(), // empty array - vecWindowPosChat(), // empty array - vecWindowPosConnect(), // empty array - bWindowWasShownSettings ( false ), - bWindowWasShownChat ( false ), - bWindowWasShownConnect ( false ), - bOwnFaderFirst ( false ), - pClient ( pNCliP ) - { - SetFileName ( sNFiName, DEFAULT_INI_FILE_NAME ); - } + CClientSettings(CClient* pNCliP, const QString& sNFiName, QObject* parent = nullptr); + + int edtNewClientLevel() const; + void setEdtNewClientLevel(const int newClientLevel ); + + QString bufSize(); + + int sldNetBuf() const; + void setSldNetBuf( const int setBufVal ); + + int sldNetBufServer() const; + void setSldNetBufServer( const int setServerBufVal ); + + int sldNetBufMin() const { return MIN_NET_BUF_SIZE_NUM_BL; } + int sldNetBufMax() const { return MAX_NET_BUF_SIZE_NUM_BL; } + + int cbxAudioChannels() const; + void setCbxAudioChannels( const int chanIdx ); + + int cbxAudioQuality() const; + void setCbxAudioQuality( const int qualityIdx ); + + int dialInputBoost() const; + void setDialInputBoost( const int inputBoost ); + + QString pedtAlias() const; + void setPedtAlias( QString strAlias ); + + int spnMixerRows() const; + void setSpnMixerRows( const int mixerRows ); + + bool chbDetectFeedback(); + void setChbDetectFeedback( bool detectFeedback ); + + bool chbEnableOPUS64(); + void setChbEnableOPUS64( bool enableOPUS64 ); + + QString sndCrdBufferDelayPreferred(); + QString sndCrdBufferDelaySafe(); + QString sndCrdBufferDelayDefault(); + + bool rbtBufferDelayPreferred(); + void setRbtBufferDelayPreferred( bool enableBufDelPref ); + + bool rbtBufferDelayDefault(); + void setRbtBufferDelayDefault( bool enableBufDelDef ); + + bool rbtBufferDelaySafe(); + void setRbtBufferDelaySafe( bool enableBufDelSafe ); + + bool fraSiFactPrefSupported(); + bool fraSiFactDefSupported(); // { return bFraSiFactDefSupported; } + bool fraSiFactSafeSupported(); // { return bFraSiFactSafeSupported; } + + bool chbAutoJitBuf(); + void setChbAutoJitBuf( bool autoJit ); + + QStringList slSndCrdDevNames(); + QStringList sndCrdInputChannelNames (); + QStringList sndCrdOutputChannelNames (); + + QString slSndCrdDev(); + void setSlSndCrdDev( const QString& sndCardDev ); + + int sndCardNumInputChannels(); + int sndCardNumOutputChannels(); + + QString sndCardLInChannel(); + void setSndCardLInChannel( QString chanName ); + + QString sndCardRInChannel(); + void setSndCardRInChannel( QString chanName ); + + QString sndCardLOutChannel(); + void setSndCardLOutChannel( QString chanName ); + + QString sndCardROutChannel(); + void setSndCardROutChannel( QString chanName ); + + void UpdateUploadRate(); // maintained for now + void UpdateDisplay(); + void UpdateSoundCardFrame(); + + int uploadRate() const; + // void setUploadRate(); + + void updateSettings(); + + // DO THIS LATER, A BIT MORE INVOLVED + // void UpdateSoundDeviceChannelSelectionFrame(); + + // void SetEnableFeedbackDetection ( bool enable ); + // endof TODO + + QString GenSndCrdBufferDelayString ( const int iFrameSize, const QString strAddText = "" ); void LoadFaderSettings ( const QString& strCurFileName ); void SaveFaderSettings ( const QString& strCurFileName ); @@ -193,14 +296,16 @@ class CClientSettings : public CSettings bool bEnableAudioAlerts; // window position/state settings - QByteArray vecWindowPosSettings; - QByteArray vecWindowPosChat; QByteArray vecWindowPosConnect; - bool bWindowWasShownSettings; - bool bWindowWasShownChat; - bool bWindowWasShownConnect; bool bOwnFaderFirst; + // for Test mode setting + QByteArray strTestMode; + + // for QML display + void UpdateJitterBufferFrame(); + + protected: virtual void WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool isAboutToQuit ) override; virtual void ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, const QList& ) override; @@ -209,9 +314,50 @@ class CClientSettings : public CSettings void WriteFaderSettingsToXML ( QDomDocument& IniXMLDocument ); CClient* pClient; + + // void UpdateAudioFaderSlider(); + +public slots: + +signals: + void edtNewClientLevelChanged(); + void bufSizeChanged(); + void sldNetBufChanged(); + void sldNetBufServerChanged(); + void uploadRateChanged(); + void cbxAudioChannelsChanged(); + void cbxAudioQualityChanged(); + void dialInputBoostChanged(); + void spnMixerRowsChanged(); + void pedtAliasChanged(); + void chbDetectFeedbackChanged(); + void chbEnableOPUS64Changed(); + void rbtBufferDelayPreferredChanged(); + void rbtBufferDelayDefaultChanged(); + void rbtBufferDelaySafeChanged(); + void fraSiFactPrefSupportedChanged(); + void fraSiFactDefSupportedChanged(); + void fraSiFactSafeSupportedChanged(); + void sndCrdBufferDelayPreferredChanged(); + void sndCrdBufferDelaySafeChanged(); + void sndCrdBufferDelayDefaultChanged(); + void chbAutoJitBufChanged(); + void slSndCrdDevChanged(); + void slSndCrdDevNamesChanged(); + void sndCardNumInputChannelsChanged(); + void sndCardNumOutputChannelsChanged(); + void sndCardLInChannelChanged(); + void sndCardRInChannelChanged(); + void sndCardLOutChannelChanged(); + void sndCardROutChannelChanged(); + void sndCrdInputChannelNamesChanged(); + void sndCrdOutputChannelNamesChanged(); + }; #endif + + class CServerSettings : public CSettings { public: diff --git a/src/sound/asio/sound.cpp b/src/sound/asio/sound.cpp index a1d1274b69..f900c9b162 100644 --- a/src/sound/asio/sound.cpp +++ b/src/sound/asio/sound.cpp @@ -101,10 +101,6 @@ QString CSound::LoadAndInitializeDriver ( QString strDriverName, bool bOpenDrive if ( bOpenDriverSetup ) { OpenDriverSetup(); - QMessageBox::question ( nullptr, - APP_NAME, - "Are you done with your ASIO driver settings of " + GetDeviceName ( iDriverIdx ) + "?", - QMessageBox::Yes ); } // driver cannot be used, clean up @@ -313,9 +309,8 @@ int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono ) //### TEST: BEGIN ###// /* - #include - QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). - arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) + QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4"). + arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) ); _exit(1); */ @@ -554,7 +549,7 @@ CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ) throw CGenErr ( "" + tr ( "No ASIO audio device driver found." ) + "

" + QString ( tr ( "Please install an ASIO driver before running %1. " "If you own a device with ASIO support, install its official ASIO driver. " - "If not, you'll need to install a universal driver like ASIO4ALL." ) ) + "If not, you'll need to install a universal driver like KorASIO." ) ) .arg ( APP_NAME ) ); } asioDrivers->removeCurrentDriver(); @@ -577,23 +572,6 @@ CSound::CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ) asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo; - - // Optional MIDI initialization -------------------------------------------- - if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) - { - Midi.MidiStart(); - } -} - -CSound::~CSound() -{ - // stop MIDI if running - if ( iCtrlMIDIChannel != INVALID_MIDI_CH ) - { - Midi.MidiStop(); - } - - UnloadCurrentDriver(); } void CSound::ResetChannelMapping() diff --git a/src/sound/asio/sound.h b/src/sound/asio/sound.h index efd08cfbe5..c5c06107f0 100644 --- a/src/sound/asio/sound.h +++ b/src/sound/asio/sound.h @@ -25,11 +25,9 @@ #pragma once #include -#include #include "../../util.h" #include "../../global.h" #include "../soundbase.h" -#include "../midi-win/midi.h" // The following includes require the ASIO SDK to be placed in // libs/ASIOSDK2 during build. @@ -57,7 +55,7 @@ class CSound : public CSoundBase public: CSound ( void ( *fpNewCallback ) ( CVector& psData, void* arg ), void* arg, const QString& strMIDISetup, const bool, const QString& ); - virtual ~CSound(); + virtual ~CSound() { UnloadCurrentDriver(); } virtual int Init ( const int iNewPrefMonoBufferSize ); virtual void Start(); @@ -135,7 +133,4 @@ class CSound : public CSoundBase static long asioMessages ( long selector, long value, void* message, double* opt ); char* cDriverNames[MAX_NUMBER_SOUND_CARDS]; - - // Windows native MIDI support - CMidi Midi; }; diff --git a/src/sound/coreaudio-mac/sound.h b/src/sound/coreaudio-mac/sound.h index cc2c97876c..5dbb177d29 100644 --- a/src/sound/coreaudio-mac/sound.h +++ b/src/sound/coreaudio-mac/sound.h @@ -28,7 +28,6 @@ #include #include #include -#include #include "../soundbase.h" #include "../../global.h" diff --git a/src/sound/soundbase.cpp b/src/sound/soundbase.cpp index 3d3be8d8a2..6e8aa3ed1f 100644 --- a/src/sound/soundbase.cpp +++ b/src/sound/soundbase.cpp @@ -33,7 +33,6 @@ char const sMidiCtlChar[] = { /* [EMidiCtlType::Solo] = */ 's', /* [EMidiCtlType::Mute] = */ 'm', /* [EMidiCtlType::MuteMyself] = */ 'o', - /* [EMidiCtlType::Device] = */ 'd', /* [EMidiCtlType::None] = */ '\0' }; /* Implementation *************************************************************/ @@ -182,7 +181,8 @@ QString CSoundBase::SetDev ( const QString strDevName ) // ASIO drivers sErrorMessage += "
" + tr ( "You may be able to fix errors in the driver settings. Do you want to open these settings now?" ); - if ( QMessageBox::Yes == QMessageBox::information ( nullptr, APP_NAME, sErrorMessage, QMessageBox::Yes | QMessageBox::No ) ) + //FIXME - show messagebox in QML world with sErrorMessage + if ( true ) { LoadAndInitializeFirstValidDriver ( true ); } @@ -310,24 +310,16 @@ void CSoundBase::ParseCommandLineArgument ( const QString& strMIDISetup ) continue; EMidiCtlType eTyp = static_cast ( iCtrl ); - if ( eTyp == Device ) + const QStringList slP = sParm.mid ( 1 ).split ( '*' ); + int iFirst = slP[0].toUInt(); + int iNum = ( slP.count() > 1 ) ? slP[1].toUInt() : 1; + for ( int iOff = 0; iOff < iNum; iOff++ ) { - // save MIDI device name to select - strMIDIDevice = sParm.mid ( 1 ); - } - else - { - const QStringList slP = sParm.mid ( 1 ).split ( '*' ); - int iFirst = slP[0].toUInt(); - int iNum = ( slP.count() > 1 ) ? slP[1].toUInt() : 1; - for ( int iOff = 0; iOff < iNum; iOff++ ) - { - if ( iOff >= MAX_NUM_CHANNELS ) - break; - if ( iFirst + iOff >= 128 ) - break; - aMidiCtls[iFirst + iOff] = { eTyp, iOff }; - } + if ( iOff >= MAX_NUM_CHANNELS ) + break; + if ( iFirst + iOff >= 128 ) + break; + aMidiCtls[iFirst + iOff] = { eTyp, iOff }; } } } diff --git a/src/sound/soundbase.h b/src/sound/soundbase.h index bcfa21243a..6f28e55c11 100644 --- a/src/sound/soundbase.h +++ b/src/sound/soundbase.h @@ -28,7 +28,6 @@ #include #include #ifndef HEADLESS -# include #endif #include "../global.h" #include "../util.h" @@ -49,7 +48,6 @@ enum EMidiCtlType Solo, Mute, MuteMyself, - Device, None }; @@ -107,8 +105,6 @@ class CSoundBase : public QThread virtual void OpenDriverSetup() {} - virtual const QString& GetMIDIDevice() { return strMIDIDevice; } - bool IsRunning() const { return bRun; } bool IsCallbackEntered() const { return bCallbackEntered; } @@ -116,9 +112,6 @@ class CSoundBase : public QThread // in a callback function it has to be public -> better solution void EmitReinitRequestSignal ( const ESndCrdResetType eSndCrdResetType ) { emit ReinitRequest ( eSndCrdResetType ); } - // this needs to be public so that it can be called from CMidi - void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); - protected: virtual QString LoadAndInitializeDriver ( QString, bool ) { return ""; } virtual void UnloadCurrentDriver() {} @@ -157,6 +150,8 @@ class CSoundBase : public QThread ( *fpProcessCallback ) ( psData, pProcessCallbackArg ); } + void ParseMIDIMessage ( const CVector& vMIDIPaketBytes ); + bool bRun; bool bCallbackEntered; QMutex MutexAudioProcessCallback; @@ -170,8 +165,6 @@ class CSoundBase : public QThread QString strCurDevName; QString strDriverNames[MAX_NUMBER_SOUND_CARDS]; - QString strMIDIDevice; - signals: void ReinitRequest ( int iSndCrdResetType ); void ControllerInFaderLevel ( int iChannelIdx, int iValue ); diff --git a/src/util.cpp b/src/util.cpp index e4e961fe13..b9974445cc 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -358,361 +358,6 @@ void CHighPrecisionTimer::run() /******************************************************************************\ * GUI Utilities * \******************************************************************************/ -// About dialog ---------------------------------------------------------------- -#ifndef HEADLESS -CAboutDlg::CAboutDlg ( QWidget* parent ) : CBaseDlg ( parent ) -{ - setupUi ( this ); - - // general description of software - txvAbout->setText ( "

" + - tr ( "This app enables musicians to perform real-time jam sessions " - "over the internet." ) + - "
" + - tr ( "There is a server which collects " - " the audio data from each client, mixes the audio data and sends the mix " - " back to each client." ) + - "

" - "

" // GPL header text - "This program 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 2 of the License, or " - "(at your option) any later version.
This program 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 his program; if not, write to the Free Software " - "Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 " - "USA" - "

" ); - - // libraries used by this compilation - txvLibraries->setText ( tr ( "This app uses the following libraries, resources or code snippets:" ) + "

" + - tr ( "Qt cross-platform application framework" ) + QString ( " %1 " ).arg ( QT_VERSION_STR ) + tr ( "(build)" ) + - QString ( ", %1 " ).arg ( qVersion() ) + tr ( "(runtime)" ) + - ", https://www.qt.io" - "

" - "

" - "Opus Interactive Audio Codec" - ", https://www.opus-codec.org" - "

" -# ifndef SERVER_ONLY -# if defined( _WIN32 ) && !defined( WITH_JACK ) - "

" - "ASIO (Audio Stream I/O) SDK" - ", https://www.steinberg.net/developers" - "
" - "ASIO is a trademark and software of Steinberg Media Technologies GmbH" - "

" -# endif -# ifndef HEADLESS - "

" + - tr ( "Audio reverberation code by Perry R. Cook and Gary P. Scavone" ) + - ", 1995 - 2021" - ", The Synthesis ToolKit in C++ (STK)" - ", https://ccrma.stanford.edu/software/stk" - "

" - "

" + - QString ( tr ( "Some pixmaps are from the %1" ) ).arg ( "Open Clip Art Library (OCAL)" ) + - ", https://openclipart.org" - "

" - "

" + - tr ( "Flag icons by Mark James" ) + - ", http://www.famfamfam.com" - "

" - "

" + - QString ( tr ( "Some sound samples are from %1" ) ).arg ( "Freesound" ) + - ", https://freesound.org" - "

" -# endif -# endif - ); - - // contributors list - txvContributors->setText ( - "

Volker Fischer (corrados)

" - "

Peter L. Jones (pljones)

" - "

Jonathan Baker-Bates (gilgongo)

" - "

ann0see (ann0see)

" - "

Daniele Masato (doloopuntil)

" - "

Martin Schilde (geheimerEichkater)

" - "

Simon Tomlinson (sthenos)

" - "

Marc jr. Landolt (braindef)

" - "

Olivier Humbert (trebmuh)

" - "

Tarmo Johannes (tarmoj)

" - "

mirabilos (mirabilos)

" - "

Hector Martin (marcan)

" - "

newlaurent62 (newlaurent62)

" - "

AronVietti (AronVietti)

" - "

Emlyn Bolton (emlynmac)

" - "

Jos van den Oever (vandenoever)

" - "

Tormod Volden (tormodvolden)

" - "

Alberstein8 (Alberstein8)

" - "

Gauthier Fleutot Östervall (fleutot)

" - "

Tony Mountifield (softins)

" - "

HPS (hselasky)

" - "

Stanislas Michalak (stanislas-m)

" - "

JP Cimalando (jpcima)

" - "

Adam Sampson (atsampson)

" - "

Jakob Jarmar (jarmar)

" - "

Stefan Weil (stweil)

" - "

Nils Brederlow (dingodoppelt)

" - "

Sebastian Krzyszkowiak (dos1)

" - "

Bryan Flamig (bflamig)

" - "

Kris Raney (kraney)

" - "

dszgit (dszgit)

" - "

nefarius2001 (nefarius2001)

" - "

jc-Rosichini (jc-Rosichini)

" - "

Julian Santander (j-santander)

" - "

chigkim (chigkim)

" - "

Bodo (bomm)

" - "

Christian Hoffmann (hoffie)

" - "

jp8 (jp8)

" - "

James (jdrage)

" - "

ranfdev (ranfdev)

" - "

bspeer (bspeer)

" - "

Martin Passing (passing)

" - "

DonC (dcorson-ticino-com)

" - "

David Kastrup (dakhubgit)

" - "

Jordan Lum (mulyaj)

" - "

Noam Postavsky (npostavs)

" - "

David Savinkoff (DavidSavinkoff)

" - "

Johannes Brauers (JohannesBrx)

" - "

Henk De Groot (henkdegroot)

" - "

Ferenc Wágner (wferi)

" - "

Martin Kaistra (djfun)

" - "

Burkhard Volkemer (buv)

" - "

Magnus Groß (vimpostor)

" - "

Julien Taverna (jujudusud)

" - "

Detlef Hennings (DetlefHennings)

" - "

drummer1154 (drummer1154)

" - "

helgeerbe (helgeerbe)

" - "

Hk1020 (Hk1020)

" - "

Jeroen van Veldhuizen (jeroenvv)

" - "

Reinhard (reinhardwh)

" - "

Stefan Menzel (menzels)

" - "

Dau Huy Ngoc (ngocdh)

" - "

Jiri Popek (jardous)

" - "

Gary Wang (BLumia)

" - "

RobyDati (RobyDati)

" - "

Rob-NY (Rob-NY)

" - "

Thai Pangsakulyanont (dtinth)

" - "

Peter Goderie (pgScorpio)

" - "

Dan Garton (danryu)

" - "
" + - tr ( "For details on the contributions check out the %1" ) - .arg ( "" + tr ( "Github Contributors list" ) + "." ) ); - - // translators - txvTranslation->setText ( "

" + tr ( "Spanish" ) + - "

" - "

Daryl Hanlon (ignotus666)

" - "

" + - tr ( "French" ) + - "

" - "

Olivier Humbert (trebmuh)

" - "

Julien Taverna (jujudusud)

" - "

" + - tr ( "Portuguese" ) + - "

" - "

Miguel de Matos (Snayler)

" - "

Melcon Moraes (melcon)

" - "

Manuela Silva (mansil)

" - "

gbonaspetti (gbonaspetti)

" - "

" + - tr ( "Dutch" ) + - "

" - "

Jeroen Geertzen (jerogee)

" - "

Henk De Groot (henkdegroot)

" - "

" + - tr ( "Italian" ) + - "

" - "

Giuseppe Sapienza (dzpex)

" - "

" + - tr ( "German" ) + - "

" - "

Volker Fischer (corrados)

" - "

Roland Moschel (rolamos)

" - "

" + - tr ( "Polish" ) + - "

" - "

Martyna Danysz (Martyna27)

" - "

Tomasz Bojczuk (SeeLook)

" - "

" + - tr ( "Swedish" ) + - "

" - "

Daniel (genesisproject2020)

" - "

tygyh (tygyh)

" - "

Allan Nordhøy (kingu)

" - "

" + - tr ( "Korean" ) + - "

" - "

Jung-Kyu Park (bagjunggyu)

" - "

이정희 (MarongHappy)

" - "

" + - tr ( "Slovak" ) + - "

" - "

Jose Riha (jose1711)

" + - "

" + tr ( "Simplified Chinese" ) + - "

" - "

Gary Wang (BLumia)

" + - "

" + tr ( "Norwegian Bokmål" ) + - "

" - "

Allan Nordhøy (kingu)

" ); - - // set version number in about dialog - lblVersion->setText ( GetVersionAndNameStr() ); - - // set window title - setWindowTitle ( tr ( "About %1" ).arg ( APP_NAME ) ); - - //### TODO: BEGIN ###// - // Test if the window also needs to be maximized on Android. - // Android has not been tested -# if defined( ANDROID ) || defined( Q_OS_IOS ) - // for mobile version maximize the window - setWindowState ( Qt::WindowMaximized ); -# endif - //### TODO: END ###// -} - -// Licence dialog -------------------------------------------------------------- -CLicenceDlg::CLicenceDlg ( QWidget* parent ) : CBaseDlg ( parent ) -{ - /* - The licence dialog is structured as follows: - - text box with the licence text on the top - - check box: I &agree to the above licence terms - - Accept button (disabled if check box not checked) - - Decline button - */ - setWindowIcon ( QIcon ( QString::fromUtf8 ( ":/png/main/res/fronticon.png" ) ) ); - - QVBoxLayout* pLayout = new QVBoxLayout ( this ); - QHBoxLayout* pSubLayout = new QHBoxLayout; - QLabel* lblLicence = - new QLabel ( tr ( "This server requires you accept conditions before you can join. Please read these in the chat window." ), this ); - QCheckBox* chbAgree = new QCheckBox ( tr ( "I have read the conditions and &agree." ), this ); - butAccept = new QPushButton ( tr ( "Accept" ), this ); - QPushButton* butDecline = new QPushButton ( tr ( "Decline" ), this ); - - pSubLayout->addStretch(); - pSubLayout->addWidget ( chbAgree ); - pSubLayout->addWidget ( butAccept ); - pSubLayout->addWidget ( butDecline ); - pLayout->addWidget ( lblLicence ); - pLayout->addLayout ( pSubLayout ); - - // set some properties - butAccept->setEnabled ( false ); - butAccept->setDefault ( true ); - - QObject::connect ( chbAgree, &QCheckBox::stateChanged, this, &CLicenceDlg::OnAgreeStateChanged ); - - QObject::connect ( butAccept, &QPushButton::clicked, this, &CLicenceDlg::accept ); - - QObject::connect ( butDecline, &QPushButton::clicked, this, &CLicenceDlg::reject ); -} - -// Help menu ------------------------------------------------------------------- -CHelpMenu::CHelpMenu ( const bool bIsClient, QWidget* parent ) : QMenu ( tr ( "&Help" ), parent ) -{ - QAction* pAction; - - // standard help menu consists of about and what's this help - if ( bIsClient ) - { - addAction ( tr ( "Getting &Started..." ), this, SLOT ( OnHelpClientGetStarted() ) ); - addAction ( tr ( "Software &Manual..." ), this, SLOT ( OnHelpSoftwareMan() ) ); - } - else - { - addAction ( tr ( "Getting &Started..." ), this, SLOT ( OnHelpServerGetStarted() ) ); - } - addSeparator(); - addAction ( tr ( "What's &This" ), this, SLOT ( OnHelpWhatsThis() ), QKeySequence ( Qt::SHIFT + Qt::Key_F1 ) ); - addSeparator(); - pAction = addAction ( tr ( "&About Jamulus..." ), this, SLOT ( OnHelpAbout() ) ); - pAction->setMenuRole ( QAction::AboutRole ); // required for Mac - pAction = addAction ( tr ( "About &Qt..." ), this, SLOT ( OnHelpAboutQt() ) ); - pAction->setMenuRole ( QAction::AboutQtRole ); // required for Mac -} - -// Language combo box ---------------------------------------------------------- -CLanguageComboBox::CLanguageComboBox ( QWidget* parent ) : QComboBox ( parent ), iIdxSelectedLanguage ( INVALID_INDEX ) -{ - // This requires a Qt::QueuedConnection on iOS due to https://bugreports.qt.io/browse/QTBUG-64577 - QObject::connect ( this, - static_cast ( &QComboBox::activated ), - this, - &CLanguageComboBox::OnLanguageActivated, - Qt::QueuedConnection ); -} - -void CLanguageComboBox::Init ( QString& strSelLanguage ) -{ - // load available translations - const QMap TranslMap = CLocale::GetAvailableTranslations(); - QMapIterator MapIter ( TranslMap ); - - // add translations to the combobox list - clear(); - int iCnt = 0; - int iIdxOfEnglishLanguage = 0; - iIdxSelectedLanguage = INVALID_INDEX; - - while ( MapIter.hasNext() ) - { - MapIter.next(); - addItem ( QLocale ( MapIter.key() ).nativeLanguageName() + " (" + MapIter.key() + ")", MapIter.key() ); - - // store the combo box index of the default english language - if ( MapIter.key().compare ( "en" ) == 0 ) - { - iIdxOfEnglishLanguage = iCnt; - } - - // if the selected language is found, store the combo box index - if ( MapIter.key().compare ( strSelLanguage ) == 0 ) - { - iIdxSelectedLanguage = iCnt; - } - - iCnt++; - } - - // if the selected language was not found, use the english language - if ( iIdxSelectedLanguage == INVALID_INDEX ) - { - strSelLanguage = "en"; - iIdxSelectedLanguage = iIdxOfEnglishLanguage; - } - - setCurrentIndex ( iIdxSelectedLanguage ); -} - -void CLanguageComboBox::OnLanguageActivated ( int iLanguageIdx ) -{ - // only update if the language selection is different from the current selected language - if ( iIdxSelectedLanguage != iLanguageIdx ) - { - QMessageBox::information ( this, tr ( "Restart Required" ), tr ( "Please restart the application for the language change to take effect." ) ); - - emit LanguageChanged ( itemData ( iLanguageIdx ).toString() ); - } -} - -QSize CMinimumStackedLayout::sizeHint() const -{ - // always use the size of the currently visible widget: - if ( currentWidget() ) - { - return currentWidget()->sizeHint(); - } - return QStackedLayout::sizeHint(); -} -#endif /******************************************************************************\ * Other Classes * @@ -817,19 +462,19 @@ bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAd QHostAddress InetAddr; unsigned int iNetPort = DEFAULT_PORT_NUMBER; - // qInfo() << qUtf8Printable ( QString ( "Parsing network address %1" ).arg ( strAddress ) ); + // qInfo() << qUtf8Printable ( QString ( "Parsing network address %1" ).arg ( strAddress ) ); - // init requested host address with invalid address first + // init requested host address with invalid address first HostAddress = CHostAddress(); - // Allow the following address formats: - // [addr4or6] - // [addr4or6]:port - // addr4 - // addr4:port - // hostname - // hostname:port - // (where addr4or6 is a literal IPv4 or IPv6 address, and addr4 is a literal IPv4 address + // Allow the following address formats: + // [addr4or6] + // [addr4or6]:port + // addr4 + // addr4:port + // hostname + // hostname:port + // (where addr4or6 is a literal IPv4 or IPv6 address, and addr4 is a literal IPv4 address bool bLiteralAddr = false; QRegularExpression rx1 ( "^\\[([^]]*)\\](?::(\\d+))?$" ); // [addr4or6] or [addr4or6]:port @@ -840,7 +485,7 @@ bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAd QRegularExpressionMatch rx1match = rx1.match ( strAddress ); QRegularExpressionMatch rx2match = rx2.match ( strAddress ); - // parse input address with rx1 and rx2 in turn, capturing address/host and port + // parse input address with rx1 and rx2 in turn, capturing address/host and port if ( rx1match.capturedStart() == 0 ) { // literal address within [] @@ -874,7 +519,7 @@ bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAd } } - // first try if this is an IP number an can directly applied to QHostAddress + // first try if this is an IP number an can directly applied to QHostAddress if ( InetAddr.setAddress ( strAddress ) ) { if ( !bEnableIPv6 && InetAddr.protocol() == QAbstractSocket::IPv6Protocol ) @@ -901,7 +546,7 @@ bool NetworkUtil::ParseNetworkAddress ( QString strAddress, CHostAddress& HostAd } } - // qInfo() << qUtf8Printable ( QString ( "Parsed network address %1" ).arg ( InetAddr.toString() ) ); + // qInfo() << qUtf8Printable ( QString ( "Parsed network address %1" ).arg ( InetAddr.toString() ) ); HostAddress = CHostAddress ( InetAddr, iNetPort ); @@ -1096,156 +741,12 @@ CVector& CInstPictures::GetTable ( const bool bRe // NOTE: Do not change the order of any instrument in the future! // NOTE: The very first entry is the "not used" element per definition. vecDataBase.Init ( 0 ); // first clear all existing data since we create the list be adding entries - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "None" ), - ":/png/instr/res/instruments/none.png", - IC_OTHER_INSTRUMENT ) ); // special first element - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Drum Set" ), - ":/png/instr/res/instruments/drumset.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Djembe" ), - ":/png/instr/res/instruments/djembe.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Electric Guitar" ), - ":/png/instr/res/instruments/eguitar.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Acoustic Guitar" ), - ":/png/instr/res/instruments/aguitar.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Bass Guitar" ), - ":/png/instr/res/instruments/bassguitar.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Keyboard" ), - ":/png/instr/res/instruments/keyboard.png", - IC_KEYBOARD_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Synthesizer" ), - ":/png/instr/res/instruments/synthesizer.png", - IC_KEYBOARD_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Grand Piano" ), - ":/png/instr/res/instruments/grandpiano.png", - IC_KEYBOARD_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Accordion" ), - ":/png/instr/res/instruments/accordeon.png", - IC_KEYBOARD_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal" ), - ":/png/instr/res/instruments/vocal.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Microphone" ), - ":/png/instr/res/instruments/microphone.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Harmonica" ), - ":/png/instr/res/instruments/harmonica.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Trumpet" ), - ":/png/instr/res/instruments/trumpet.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Trombone" ), - ":/png/instr/res/instruments/trombone.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "French Horn" ), - ":/png/instr/res/instruments/frenchhorn.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Tuba" ), - ":/png/instr/res/instruments/tuba.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Saxophone" ), - ":/png/instr/res/instruments/saxophone.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Clarinet" ), - ":/png/instr/res/instruments/clarinet.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Flute" ), - ":/png/instr/res/instruments/flute.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Violin" ), - ":/png/instr/res/instruments/violin.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Cello" ), - ":/png/instr/res/instruments/cello.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Double Bass" ), - ":/png/instr/res/instruments/doublebass.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Recorder" ), - ":/png/instr/res/instruments/recorder.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Streamer" ), - ":/png/instr/res/instruments/streamer.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Listener" ), - ":/png/instr/res/instruments/listener.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Guitar+Vocal" ), - ":/png/instr/res/instruments/guitarvocal.png", - IC_MULTIPLE_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Keyboard+Vocal" ), - ":/png/instr/res/instruments/keyboardvocal.png", - IC_MULTIPLE_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Bodhran" ), - ":/png/instr/res/instruments/bodhran.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Bassoon" ), - ":/png/instr/res/instruments/bassoon.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Oboe" ), - ":/png/instr/res/instruments/oboe.png", - IC_WIND_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Harp" ), - ":/png/instr/res/instruments/harp.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Viola" ), - ":/png/instr/res/instruments/viola.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Congas" ), - ":/png/instr/res/instruments/congas.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Bongo" ), - ":/png/instr/res/instruments/bongo.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Bass" ), - ":/png/instr/res/instruments/vocalbass.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Tenor" ), - ":/png/instr/res/instruments/vocaltenor.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Alto" ), - ":/png/instr/res/instruments/vocalalto.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Soprano" ), - ":/png/instr/res/instruments/vocalsoprano.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Banjo" ), - ":/png/instr/res/instruments/banjo.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Mandolin" ), - ":/png/instr/res/instruments/mandolin.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Ukulele" ), - ":/png/instr/res/instruments/ukulele.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Bass Ukulele" ), - ":/png/instr/res/instruments/bassukulele.png", - IC_PLUCKING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Baritone" ), - ":/png/instr/res/instruments/vocalbaritone.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vocal Lead" ), - ":/png/instr/res/instruments/vocallead.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Mountain Dulcimer" ), - ":/png/instr/res/instruments/mountaindulcimer.png", - IC_STRING_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Scratching" ), - ":/png/instr/res/instruments/scratching.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Rapping" ), - ":/png/instr/res/instruments/rapping.png", - IC_OTHER_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Vibraphone" ), - ":/png/instr/res/instruments/vibraphone.png", - IC_PERCUSSION_INSTRUMENT ) ); - vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Conductor" ), - ":/png/instr/res/instruments/conductor.png", - IC_OTHER_INSTRUMENT ) ); + + // DON'T actually do anything here, we don't use the instruments images now! + +// vecDataBase.Add ( CInstPictProps ( QCoreApplication::translate ( "CClientSettingsDlg", "Conductor" ), +// ":/png/instr/res/instruments/conductor.png", +// IC_OTHER_INSTRUMENT ) ); // now the table is initialized TableIsInitialized = true; @@ -1556,16 +1057,6 @@ QString GetVersionAndNameStr ( const bool bDisplayInGui ) strVersionText += "\n *** "; # endif # ifndef HEADLESS - strVersionText += "\n *** " + QCoreApplication::tr ( "Audio reverberation code by Perry R. Cook and Gary P. Scavone" ) + - ", 1995 - 2021, The Synthesis ToolKit in C++ (STK)"; - strVersionText += "\n *** "; - strVersionText += "\n *** "; - strVersionText += "\n *** " + QString ( QCoreApplication::tr ( "Some pixmaps are from the %1" ) ).arg ( " Open Clip Art Library (OCAL)" ); - strVersionText += "\n *** "; - strVersionText += "\n *** "; - strVersionText += "\n *** " + QCoreApplication::tr ( "Flag icons by Mark James" ); - strVersionText += "\n *** "; - strVersionText += "\n *** "; strVersionText += "\n *** " + QString ( QCoreApplication::tr ( "Some sound samples are from %1" ) ).arg ( "Freesound" ); strVersionText += "\n *** "; strVersionText += "\n *** "; diff --git a/src/util.h b/src/util.h index 8628d5596f..ef72d0dd99 100644 --- a/src/util.h +++ b/src/util.h @@ -60,19 +60,9 @@ # include #endif #ifndef HEADLESS -# include -# include -# include -# include -# include -# include -# include -# include # include # include # include -# include -# include "ui_aboutdlgbase.h" #endif #include "global.h" @@ -149,6 +139,29 @@ class CVector : public std::vector // this function simply converts the type of size to integer inline int Size() const { return static_cast ( std::vector::size() ); } + + // new world additions + // this function simply converts the type of size to integer + // size(): Returns the number of elements. + inline int size() const { return static_cast ( std::vector::size() ); } + + // pushback(element): Adds an element to the end. + void pushback ( const TData& tI ) + { + Enlarge ( 1 ); + std::vector::back() = tI; + } + + // at(index): Returns the element at the specified index. + TData& at(int index) { + return std::vector::at(index); + } + + // clear(): Removes all elements. + void clear() { + std::vector::clear(); + } + }; /* Implementation *************************************************************/ @@ -369,103 +382,60 @@ void CMovingAv::Add ( const TData tNewD ) \******************************************************************************/ #ifndef HEADLESS // Dialog base class ----------------------------------------------------------- -class CBaseDlg : public QDialog -{ - Q_OBJECT +// class CBaseDlg : public QDialog +// { +// Q_OBJECT + +// public: +// CBaseDlg ( QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags() ) : QDialog ( parent, flags ) {} + +// public slots: +// void keyPressEvent ( QKeyEvent* pEvent ) +// { +// // block escape key +// if ( pEvent->key() != Qt::Key_Escape ) +// { +// # if defined ( Q_OS_ANDROID ) +// if ( pEvent->key() == Qt::Key_Back ) +// { +// close(); // otherwise, dialog does not show properly again in android (nefarius2001, #832) +// return; +// } +// # endif +// QDialog::keyPressEvent ( pEvent ); +// } +// } +// }; -public: - CBaseDlg ( QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags() ) : QDialog ( parent, flags ) {} - -public slots: - void keyPressEvent ( QKeyEvent* pEvent ) - { - // block escape key - if ( pEvent->key() != Qt::Key_Escape ) - { -# ifdef ANDROID - if ( pEvent->key() == Qt::Key_Back ) - { - close(); // otherwise, dialog does not show properly again in android (nefarius2001, #832) - return; - } -# endif - QDialog::keyPressEvent ( pEvent ); - } - } -}; - -// About dialog ---------------------------------------------------------------- -class CAboutDlg : public CBaseDlg, private Ui_CAboutDlgBase -{ - Q_OBJECT - -public: - CAboutDlg ( QWidget* parent = nullptr ); -}; - -// Licence dialog -------------------------------------------------------------- -class CLicenceDlg : public CBaseDlg -{ - Q_OBJECT - -public: - CLicenceDlg ( QWidget* parent = nullptr ); - -protected: - QPushButton* butAccept; - -public slots: - void OnAgreeStateChanged ( int value ) { butAccept->setEnabled ( value == Qt::Checked ); } -}; - -// Help menu ------------------------------------------------------------------- -class CHelpMenu : public QMenu -{ - Q_OBJECT - -public: - CHelpMenu ( const bool bIsClient, QWidget* parent = nullptr ); - -protected: - CAboutDlg AboutDlg; - -public slots: - void OnHelpWhatsThis() { QWhatsThis::enterWhatsThisMode(); } - void OnHelpAbout() { AboutDlg.exec(); } - void OnHelpAboutQt() { QMessageBox::aboutQt ( nullptr, QString ( tr ( "About Qt" ) ) ); } - void OnHelpClientGetStarted() { QDesktopServices::openUrl ( QUrl ( CLIENT_GETTING_STARTED_URL ) ); } - void OnHelpServerGetStarted() { QDesktopServices::openUrl ( QUrl ( SERVER_GETTING_STARTED_URL ) ); } - void OnHelpSoftwareMan() { QDesktopServices::openUrl ( QUrl ( SOFTWARE_MANUAL_URL ) ); } -}; // Language combo box ---------------------------------------------------------- -class CLanguageComboBox : public QComboBox -{ - Q_OBJECT +// class CLanguageComboBox : public QComboBox +// { +// Q_OBJECT -public: - CLanguageComboBox ( QWidget* parent = nullptr ); +// public: +// CLanguageComboBox ( QWidget* parent = nullptr ); - void Init ( QString& strSelLanguage ); +// void Init ( QString& strSelLanguage ); -protected: - int iIdxSelectedLanguage; +// protected: +// int iIdxSelectedLanguage; -public slots: - void OnLanguageActivated ( int iLanguageIdx ); +// public slots: +// void OnLanguageActivated ( int iLanguageIdx ); -signals: - void LanguageChanged ( QString strLanguage ); -}; +// signals: +// void LanguageChanged ( QString strLanguage ); +// }; // StackedLayout which auto-reduces to the size of the currently visible widget -class CMinimumStackedLayout : public QStackedLayout -{ - Q_OBJECT -public: - CMinimumStackedLayout ( QWidget* parent = nullptr ) : QStackedLayout ( parent ) {} - virtual QSize sizeHint() const override; -}; +// class CMinimumStackedLayout : public QStackedLayout +// { +// Q_OBJECT +// public: +// CMinimumStackedLayout ( QWidget* parent = nullptr ) : QStackedLayout ( parent ) {} +// virtual QSize sizeHint() const override; +// }; #endif /******************************************************************************\ @@ -558,12 +528,11 @@ enum ERecorderState enum EChSortType { // used for settings -> enum values should be fixed - ST_NO_SORT = 0, - ST_BY_NAME = 1, - ST_BY_INSTRUMENT = 2, - ST_BY_GROUPID = 3, - ST_BY_CITY = 4, - ST_BY_SERVER_CHANNEL = 5 + ST_NO_SORT = 0, + ST_BY_NAME = 1, + ST_BY_INSTRUMENT = 2, + ST_BY_GROUPID = 3, + ST_BY_CITY = 4 }; // Directory type -------------------------------------------------------------- @@ -588,29 +557,11 @@ inline QString DirectoryTypeToString ( EDirectoryType eAddrType ) case AT_NONE: return QCoreApplication::translate ( "CServerDlg", "None" ); - case AT_ANY_GENRE2: - return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre 2" ); - - case AT_ANY_GENRE3: - return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre 3" ); - - case AT_GENRE_ROCK: - return QCoreApplication::translate ( "CClientSettingsDlg", "Genre Rock" ); - - case AT_GENRE_JAZZ: - return QCoreApplication::translate ( "CClientSettingsDlg", "Genre Jazz" ); - - case AT_GENRE_CLASSICAL_FOLK: - return QCoreApplication::translate ( "CClientSettingsDlg", "Genre Classical/Folk" ); - - case AT_GENRE_CHORAL: - return QCoreApplication::translate ( "CClientSettingsDlg", "Genre Choral/Barbershop" ); - case AT_CUSTOM: - return QCoreApplication::translate ( "CClientSettingsDlg", "Custom" ); + return QCoreApplication::translate ( "CClientDlg", "Custom" ); default: // AT_DEFAULT - return QCoreApplication::translate ( "CClientSettingsDlg", "Any Genre 1" ); + return QCoreApplication::translate ( "CClientDlg", "Any Genre 1" ); } } @@ -1099,11 +1050,9 @@ class COSUtil { #ifdef _WIN32 return OT_WINDOWS; -#elif defined( Q_OS_MACOS ) +#elif defined( __APPLE__ ) || defined( __MACOSX ) return OT_MAC_OS; -#elif defined( Q_OS_IOS ) - return OT_I_OS; -#elif defined( Q_OS_ANDROID ) || defined( ANDROID ) +#elif defined( ANDROID ) return OT_ANDROID; #else return OT_LINUX;