1 /******************************************************************************\
2  * Copyright (c) 2004-2020
3  *
4  * Author(s):
5  *  Volker Fischer
6  *
7  ******************************************************************************
8  *
9  * This program is free software; you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation; either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program; if not, write to the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
22  *
23 \******************************************************************************/
24 
25 #include "clientdlg.h"
26 
27 /* Implementation *************************************************************/
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)28 CClientDlg::CClientDlg ( CClient*         pNCliP,
29                          CClientSettings* pNSetP,
30                          const QString&   strConnOnStartupAddress,
31                          const QString&   strMIDISetup,
32                          const bool       bNewShowComplRegConnList,
33                          const bool       bShowAnalyzerConsole,
34                          const bool       bMuteStream,
35                          const bool       bNEnableIPv6,
36                          QWidget*         parent ) :
37     CBaseDlg ( parent, Qt::Window ), // use Qt::Window to get min/max window buttons
38     pClient ( pNCliP ),
39     pSettings ( pNSetP ),
40     bConnectDlgWasShown ( false ),
41     bMIDICtrlUsed ( !strMIDISetup.isEmpty() ),
42     bEnableIPv6 ( bNEnableIPv6 ),
43     eLastRecorderState ( RS_UNDEFINED ), // for SetMixerBoardDeco
44     eLastDesign ( GD_ORIGINAL ),         //          "
45     ClientSettingsDlg ( pNCliP, pNSetP, parent ),
46     ChatDlg ( parent ),
47     ConnectDlg ( pNSetP, bNewShowComplRegConnList, parent ),
48     AnalyzerConsole ( pNCliP, parent )
49 {
50     setupUi ( this );
51 
52     // Add help text to controls -----------------------------------------------
53     // input level meter
54     QString strInpLevH = "<b>" + tr ( "Input Level Meter" ) + ":</b> " +
55                          tr ( "This shows "
56                               "the level of the two stereo channels "
57                               "for your audio input." ) +
58                          "<br>" +
59                          tr ( "Make sure not to clip the input signal to avoid distortions of the "
60                               "audio signal." );
61 
62     QString strInpLevHTT = tr ( "If the application "
63                                 "is connected to a server and "
64                                 "you play your instrument/sing into the microphone, the VU "
65                                 "meter should flicker. If this is not the case, you have "
66                                 "probably selected the wrong input channel (e.g. 'line in' instead "
67                                 "of the microphone input) or set the input gain too low in the "
68                                 "(Windows) audio mixer." ) +
69                            "<br>" +
70                            tr ( "For proper usage of the "
71                                 "application, you should not hear your singing/instrument through "
72                                 "the loudspeaker or your headphone when the software is not connected."
73                                 "This can be achieved by muting your input audio channel in the "
74                                 "Playback mixer (not the Recording mixer!)." ) +
75                            TOOLTIP_COM_END_TEXT;
76 
77     QString strInpLevHAccText  = tr ( "Input level meter" );
78     QString strInpLevHAccDescr = tr ( "Simulates an analog LED level meter." );
79 
80     lblInputLEDMeter->setWhatsThis ( strInpLevH );
81     lblLevelMeterLeft->setWhatsThis ( strInpLevH );
82     lblLevelMeterRight->setWhatsThis ( strInpLevH );
83     lbrInputLevelL->setWhatsThis ( strInpLevH );
84     lbrInputLevelL->setAccessibleName ( strInpLevHAccText );
85     lbrInputLevelL->setAccessibleDescription ( strInpLevHAccDescr );
86     lbrInputLevelL->setToolTip ( strInpLevHTT );
87     lbrInputLevelL->setEnabled ( false );
88     lbrInputLevelR->setWhatsThis ( strInpLevH );
89     lbrInputLevelR->setAccessibleName ( strInpLevHAccText );
90     lbrInputLevelR->setAccessibleDescription ( strInpLevHAccDescr );
91     lbrInputLevelR->setToolTip ( strInpLevHTT );
92     lbrInputLevelR->setEnabled ( false );
93 
94     // connect/disconnect button
95     butConnect->setWhatsThis ( "<b>" + tr ( "Connect/Disconnect Button" ) + ":</b> " +
96                                tr ( "Opens a dialog where you can select a server to connect to. "
97                                     "If you are connected, pressing this button will end the session." ) );
98 
99     butConnect->setAccessibleName ( tr ( "Connect and disconnect toggle button" ) );
100 
101     // reverberation level
102     QString strAudReverb = "<b>" + tr ( "Reverb effect" ) + ":</b> " +
103                            tr ( "Reverb can be applied to one local mono audio channel or to both "
104                                 "channels in stereo mode. The mono channel selection and the "
105                                 "reverb level can be modified. For example, if "
106                                 "a microphone signal is fed in to the right audio channel of the "
107                                 "sound card and a reverb effect needs to be applied, set the "
108                                 "channel selector to right and move the fader upwards until the "
109                                 "desired reverb level is reached." );
110 
111     lblAudioReverb->setWhatsThis ( strAudReverb );
112     sldAudioReverb->setWhatsThis ( strAudReverb );
113 
114     sldAudioReverb->setAccessibleName ( tr ( "Reverb effect level setting" ) );
115 
116     // reverberation channel selection
117     QString strRevChanSel = "<b>" + tr ( "Reverb Channel Selection" ) + ":</b> " +
118                             tr ( "With these radio buttons the audio input channel on which the "
119                                  "reverb effect is applied can be chosen. Either the left "
120                                  "or right input channel can be selected." );
121 
122     rbtReverbSelL->setWhatsThis ( strRevChanSel );
123     rbtReverbSelL->setAccessibleName ( tr ( "Left channel selection for reverb" ) );
124     rbtReverbSelR->setWhatsThis ( strRevChanSel );
125     rbtReverbSelR->setAccessibleName ( tr ( "Right channel selection for reverb" ) );
126 
127     // delay LED
128     QString strLEDDelay = "<b>" + tr ( "Delay Status LED" ) + ":</b> " + tr ( "Shows the current audio delay status:" ) +
129                           "<ul>"
130                           "<li>"
131                           "<b>" +
132                           tr ( "Green" ) + ":</b> " +
133                           tr ( "The delay is perfect for a jam "
134                                "session." ) +
135                           "</li>"
136                           "<li>"
137                           "<b>" +
138                           tr ( "Yellow" ) + ":</b> " +
139                           tr ( "A session is still possible "
140                                "but it may be harder to play." ) +
141                           "</li>"
142                           "<li>"
143                           "<b>" +
144                           tr ( "Red" ) + ":</b> " +
145                           tr ( "The delay is too large for "
146                                "jamming." ) +
147                           "</li>"
148                           "</ul>";
149 
150     lblDelay->setWhatsThis ( strLEDDelay );
151     ledDelay->setWhatsThis ( strLEDDelay );
152     ledDelay->setToolTip ( tr ( "If this LED indicator turns red, "
153                                 "you will not have much fun using the application." ) +
154                            TOOLTIP_COM_END_TEXT );
155 
156     ledDelay->setAccessibleName ( tr ( "Delay status LED indicator" ) );
157 
158     // buffers LED
159     QString strLEDBuffers = "<b>" + tr ( "Local Jitter Buffer Status LED" ) + ":</b> " +
160                             tr ( "The local jitter buffer status LED shows the current audio/streaming "
161                                  "status. If the light is red, the audio stream is interrupted. "
162                                  "This is caused by one of the following problems:" ) +
163                             "<ul>"
164                             "<li>" +
165                             tr ( "The network jitter buffer is not large enough for the current "
166                                  "network/audio interface jitter." ) +
167                             "</li>"
168                             "<li>" +
169                             tr ( "The sound card's buffer delay (buffer size) is too small "
170                                  "(see Settings window)." ) +
171                             "</li>"
172                             "<li>" +
173                             tr ( "The upload or download stream rate is too high for your "
174                                  "internet bandwidth." ) +
175                             "</li>"
176                             "<li>" +
177                             tr ( "The CPU of the client or server is at 100%." ) +
178                             "</li>"
179                             "</ul>";
180 
181     lblBuffers->setWhatsThis ( strLEDBuffers );
182     ledBuffers->setWhatsThis ( strLEDBuffers );
183 
184     ledBuffers->setAccessibleName ( tr ( "Local Jitter Buffer status LED indicator" ) );
185 
186     // current connection status parameter
187     QString strConnStats = "<b>" +
188                            tr ( "Current Connection Status "
189                                 "Parameter" ) +
190                            ":</b> " +
191                            tr ( "The Ping Time is the time required for the audio "
192                                 "stream to travel from the client to the server and back again. This "
193                                 "delay is introduced by the network and should be about "
194                                 "20-30 ms. If this delay is higher than about 50 ms, your distance to "
195                                 "the server is too large or your internet connection is not "
196                                 "sufficient." ) +
197                            "<br>" +
198                            tr ( "Overall Delay is calculated from the current Ping Time and the "
199                                 "delay introduced by the current buffer settings." );
200 
201     lblPing->setWhatsThis ( strConnStats );
202     lblPingVal->setWhatsThis ( strConnStats );
203     lblDelay->setWhatsThis ( strConnStats );
204     lblDelayVal->setWhatsThis ( strConnStats );
205     ledDelay->setWhatsThis ( strConnStats );
206     ledDelay->setToolTip ( tr ( "If this LED indicator turns red, "
207                                 "you will not have much fun using the " ) +
208                            APP_NAME + tr ( " software." ) + TOOLTIP_COM_END_TEXT );
209     lblPingVal->setText ( "---" );
210     lblPingUnit->setText ( "" );
211     lblDelayVal->setText ( "---" );
212     lblDelayUnit->setText ( "" );
213 
214     // init GUI design
215     SetGUIDesign ( pClient->GetGUIDesign() );
216 
217     // set the settings pointer to the mixer board (must be done early)
218     MainMixerBoard->SetSettingsPointer ( pSettings );
219     MainMixerBoard->SetNumMixerPanelRows ( pSettings->iNumMixerPanelRows );
220 
221     // reset mixer board
222     MainMixerBoard->HideAll();
223 
224     // init status label
225     OnTimerStatus();
226 
227     // init connection button text
228     butConnect->setText ( tr ( "C&onnect" ) );
229 
230     // init input level meter bars
231     lbrInputLevelL->SetValue ( 0 );
232     lbrInputLevelR->SetValue ( 0 );
233 
234     // init status LEDs
235     ledBuffers->Reset();
236     ledDelay->Reset();
237 
238     // init audio reverberation
239     sldAudioReverb->setRange ( 0, AUD_REVERB_MAX );
240     const int iCurAudReverb = pClient->GetReverbLevel();
241     sldAudioReverb->setValue ( iCurAudReverb );
242     sldAudioReverb->setTickInterval ( AUD_REVERB_MAX / 5 );
243 
244     // init input boost
245     pClient->SetInputBoost ( pSettings->iInputBoost );
246 
247     // init reverb channel
248     UpdateRevSelection();
249 
250     // init connect dialog
251     ConnectDlg.SetShowAllMusicians ( pSettings->bConnectDlgShowAllMusicians );
252 
253     // set window title (with no clients connected -> "0")
254     SetMyWindowTitle ( 0 );
255 
256     // prepare Mute Myself info label (invisible by default)
257     lblGlobalInfoLabel->setStyleSheet ( ".QLabel { background: red; }" );
258     lblGlobalInfoLabel->hide();
259 
260     // prepare update check info label (invisible by default)
261     lblUpdateCheck->setOpenExternalLinks ( true ); // enables opening a web browser if one clicks on a html link
262     lblUpdateCheck->setText ( "<font color=\"red\"><b>" + APP_UPGRADE_AVAILABLE_MSG_TEXT.arg ( APP_NAME ).arg ( VERSION ) + "</b></font>" );
263     lblUpdateCheck->hide();
264 
265     // setup timers
266     TimerCheckAudioDeviceOk.setSingleShot ( true ); // only check once after connection
267     TimerDetectFeedback.setSingleShot ( true );
268 
269     // Connect on startup ------------------------------------------------------
270     if ( !strConnOnStartupAddress.isEmpty() )
271     {
272         // initiate connection (always show the address in the mixer board
273         // (no alias))
274         Connect ( strConnOnStartupAddress, strConnOnStartupAddress );
275     }
276 
277     // File menu  --------------------------------------------------------------
278     QMenu* pFileMenu = new QMenu ( tr ( "&File" ), this );
279 
280     pFileMenu->addAction ( tr ( "&Connection Setup..." ), this, SLOT ( OnOpenConnectionSetupDialog() ), QKeySequence ( Qt::CTRL + Qt::Key_C ) );
281 
282     pFileMenu->addSeparator();
283 
284     pFileMenu->addAction ( tr ( "&Load Mixer Channels Setup..." ), this, SLOT ( OnLoadChannelSetup() ) );
285 
286     pFileMenu->addAction ( tr ( "&Save Mixer Channels Setup..." ), this, SLOT ( OnSaveChannelSetup() ) );
287 
288     pFileMenu->addSeparator();
289 
290     pFileMenu->addAction ( tr ( "E&xit" ), this, SLOT ( close() ), QKeySequence ( Qt::CTRL + Qt::Key_Q ) );
291 
292     // Edit menu  --------------------------------------------------------------
293     QMenu* pEditMenu = new QMenu ( tr ( "&Edit" ), this );
294 
295     pEditMenu->addAction ( tr ( "Clear &All Stored Solo and Mute Settings" ), this, SLOT ( OnClearAllStoredSoloMuteSettings() ) );
296 
297     pEditMenu->addAction ( tr ( "Set All Faders to New Client &Level" ),
298                            this,
299                            SLOT ( OnSetAllFadersToNewClientLevel() ),
300                            QKeySequence ( Qt::CTRL + Qt::Key_L ) );
301 
302     pEditMenu->addAction ( tr ( "Auto-Adjust all &Faders" ), this, SLOT ( OnAutoAdjustAllFaderLevels() ), QKeySequence ( Qt::CTRL + Qt::Key_F ) );
303 
304     // View menu  --------------------------------------------------------------
305     QMenu* pViewMenu = new QMenu ( tr ( "&View" ), this );
306 
307     QAction* NoSortAction =
308         pViewMenu->addAction ( tr ( "N&o User Sorting" ), this, SLOT ( OnNoSortChannels() ), QKeySequence ( Qt::CTRL + Qt::Key_O ) );
309 
310     QAction* ByNameAction =
311         pViewMenu->addAction ( tr ( "Sort Users by &Name" ), this, SLOT ( OnSortChannelsByName() ), QKeySequence ( Qt::CTRL + Qt::Key_N ) );
312 
313     QAction* ByInstrAction = pViewMenu->addAction ( tr ( "Sort Users by &Instrument" ),
314                                                     this,
315                                                     SLOT ( OnSortChannelsByInstrument() ),
316                                                     QKeySequence ( Qt::CTRL + Qt::Key_I ) );
317 
318     QAction* ByGroupAction =
319         pViewMenu->addAction ( tr ( "Sort Users by &Group" ), this, SLOT ( OnSortChannelsByGroupID() ), QKeySequence ( Qt::CTRL + Qt::Key_G ) );
320 
321     QAction* ByCityAction =
322         pViewMenu->addAction ( tr ( "Sort Users by &City" ), this, SLOT ( OnSortChannelsByCity() ), QKeySequence ( Qt::CTRL + Qt::Key_T ) );
323 
324     // the sorting menu entries shall be checkable and exclusive
325     QActionGroup* SortActionGroup = new QActionGroup ( this );
326     SortActionGroup->setExclusive ( true );
327     NoSortAction->setCheckable ( true );
328     SortActionGroup->addAction ( NoSortAction );
329     ByNameAction->setCheckable ( true );
330     SortActionGroup->addAction ( ByNameAction );
331     ByInstrAction->setCheckable ( true );
332     SortActionGroup->addAction ( ByInstrAction );
333     ByGroupAction->setCheckable ( true );
334     SortActionGroup->addAction ( ByGroupAction );
335     ByCityAction->setCheckable ( true );
336     SortActionGroup->addAction ( ByCityAction );
337 
338     // initialize sort type setting (i.e., recover stored setting)
339     switch ( pSettings->eChannelSortType )
340     {
341     case ST_NO_SORT:
342         NoSortAction->setChecked ( true );
343         break;
344     case ST_BY_NAME:
345         ByNameAction->setChecked ( true );
346         break;
347     case ST_BY_INSTRUMENT:
348         ByInstrAction->setChecked ( true );
349         break;
350     case ST_BY_GROUPID:
351         ByGroupAction->setChecked ( true );
352         break;
353     case ST_BY_CITY:
354         ByCityAction->setChecked ( true );
355         break;
356     }
357     MainMixerBoard->SetFaderSorting ( pSettings->eChannelSortType );
358 
359     pViewMenu->addSeparator();
360 
361     pViewMenu->addAction ( tr ( "C&hat..." ), this, SLOT ( OnOpenChatDialog() ), QKeySequence ( Qt::CTRL + Qt::Key_H ) );
362 
363     // optionally show analyzer console entry
364     if ( bShowAnalyzerConsole )
365     {
366         pViewMenu->addAction ( tr ( "&Analyzer Console..." ), this, SLOT ( OnOpenAnalyzerConsole() ) );
367     }
368 
369     pViewMenu->addSeparator();
370 
371     // Settings menu  --------------------------------------------------------------
372     QMenu* pSettingsMenu = new QMenu ( tr ( "&Settings" ), this );
373 
374     pSettingsMenu->addAction ( tr ( "My &Profile..." ), this, SLOT ( OnOpenUserProfileSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_P ) );
375 
376     pSettingsMenu->addAction ( tr ( "Audio/Network &Settings..." ), this, SLOT ( OnOpenAudioNetSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_S ) );
377 
378     pSettingsMenu->addAction ( tr ( "A&dvanced Settings..." ), this, SLOT ( OnOpenAdvancedSettings() ), QKeySequence ( Qt::CTRL + Qt::Key_D ) );
379 
380     // Main menu bar -----------------------------------------------------------
381     QMenuBar* pMenu = new QMenuBar ( this );
382 
383     pMenu->addMenu ( pFileMenu );
384     pMenu->addMenu ( pEditMenu );
385     pMenu->addMenu ( pViewMenu );
386     pMenu->addMenu ( pSettingsMenu );
387     pMenu->addMenu ( new CHelpMenu ( true, this ) );
388 
389     // Now tell the layout about the menu
390     layout()->setMenuBar ( pMenu );
391 
392     // Window positions --------------------------------------------------------
393     // main window
394     if ( !pSettings->vecWindowPosMain.isEmpty() && !pSettings->vecWindowPosMain.isNull() )
395     {
396         restoreGeometry ( pSettings->vecWindowPosMain );
397     }
398 
399     // settings window
400     if ( !pSettings->vecWindowPosSettings.isEmpty() && !pSettings->vecWindowPosSettings.isNull() )
401     {
402         ClientSettingsDlg.restoreGeometry ( pSettings->vecWindowPosSettings );
403     }
404 
405     if ( pSettings->bWindowWasShownSettings )
406     {
407         ShowGeneralSettings ( pSettings->iSettingsTab );
408     }
409 
410     // chat window
411     if ( !pSettings->vecWindowPosChat.isEmpty() && !pSettings->vecWindowPosChat.isNull() )
412     {
413         ChatDlg.restoreGeometry ( pSettings->vecWindowPosChat );
414     }
415 
416     if ( pSettings->bWindowWasShownChat )
417     {
418         ShowChatWindow();
419     }
420 
421     // connection setup window
422     if ( !pSettings->vecWindowPosConnect.isEmpty() && !pSettings->vecWindowPosConnect.isNull() )
423     {
424         ConnectDlg.restoreGeometry ( pSettings->vecWindowPosConnect );
425     }
426 
427     // Connections -------------------------------------------------------------
428     // push buttons
429     QObject::connect ( butConnect, &QPushButton::clicked, this, &CClientDlg::OnConnectDisconBut );
430 
431     // check boxes
432     QObject::connect ( chbSettings, &QCheckBox::stateChanged, this, &CClientDlg::OnSettingsStateChanged );
433 
434     QObject::connect ( chbChat, &QCheckBox::stateChanged, this, &CClientDlg::OnChatStateChanged );
435 
436     QObject::connect ( chbLocalMute, &QCheckBox::stateChanged, this, &CClientDlg::OnLocalMuteStateChanged );
437 
438     // timers
439     QObject::connect ( &TimerSigMet, &QTimer::timeout, this, &CClientDlg::OnTimerSigMet );
440 
441     QObject::connect ( &TimerBuffersLED, &QTimer::timeout, this, &CClientDlg::OnTimerBuffersLED );
442 
443     QObject::connect ( &TimerStatus, &QTimer::timeout, this, &CClientDlg::OnTimerStatus );
444 
445     QObject::connect ( &TimerPing, &QTimer::timeout, this, &CClientDlg::OnTimerPing );
446 
447     QObject::connect ( &TimerCheckAudioDeviceOk, &QTimer::timeout, this, &CClientDlg::OnTimerCheckAudioDeviceOk );
448 
449     QObject::connect ( &TimerDetectFeedback, &QTimer::timeout, this, &CClientDlg::OnTimerDetectFeedback );
450 
451     QObject::connect ( sldAudioReverb, &QSlider::valueChanged, this, &CClientDlg::OnAudioReverbValueChanged );
452 
453     // radio buttons
454     QObject::connect ( rbtReverbSelL, &QRadioButton::clicked, this, &CClientDlg::OnReverbSelLClicked );
455 
456     QObject::connect ( rbtReverbSelR, &QRadioButton::clicked, this, &CClientDlg::OnReverbSelRClicked );
457 
458     // other
459     QObject::connect ( pClient, &CClient::ConClientListMesReceived, this, &CClientDlg::OnConClientListMesReceived );
460 
461     QObject::connect ( pClient, &CClient::Disconnected, this, &CClientDlg::OnDisconnected );
462 
463     QObject::connect ( pClient, &CClient::ChatTextReceived, this, &CClientDlg::OnChatTextReceived );
464 
465     QObject::connect ( pClient, &CClient::ClientIDReceived, this, &CClientDlg::OnClientIDReceived );
466 
467     QObject::connect ( pClient, &CClient::MuteStateHasChangedReceived, this, &CClientDlg::OnMuteStateHasChangedReceived );
468 
469     QObject::connect ( pClient, &CClient::RecorderStateReceived, this, &CClientDlg::OnRecorderStateReceived );
470 
471     // This connection is a special case. On receiving a licence required message via the
472     // protocol, a modal licence dialog is opened. Since this blocks the thread, we need
473     // a queued connection to make sure the core protocol mechanism is not blocked, too.
474     qRegisterMetaType<ELicenceType> ( "ELicenceType" );
475     QObject::connect ( pClient, &CClient::LicenceRequired, this, &CClientDlg::OnLicenceRequired, Qt::QueuedConnection );
476 
477     QObject::connect ( pClient, &CClient::PingTimeReceived, this, &CClientDlg::OnPingTimeResult );
478 
479     QObject::connect ( pClient, &CClient::CLServerListReceived, this, &CClientDlg::OnCLServerListReceived );
480 
481     QObject::connect ( pClient, &CClient::CLRedServerListReceived, this, &CClientDlg::OnCLRedServerListReceived );
482 
483     QObject::connect ( pClient, &CClient::CLConnClientsListMesReceived, this, &CClientDlg::OnCLConnClientsListMesReceived );
484 
485     QObject::connect ( pClient, &CClient::CLPingTimeWithNumClientsReceived, this, &CClientDlg::OnCLPingTimeWithNumClientsReceived );
486 
487     QObject::connect ( pClient, &CClient::ControllerInFaderLevel, this, &CClientDlg::OnControllerInFaderLevel );
488 
489     QObject::connect ( pClient, &CClient::ControllerInPanValue, this, &CClientDlg::OnControllerInPanValue );
490 
491     QObject::connect ( pClient, &CClient::ControllerInFaderIsSolo, this, &CClientDlg::OnControllerInFaderIsSolo );
492 
493     QObject::connect ( pClient, &CClient::ControllerInFaderIsMute, this, &CClientDlg::OnControllerInFaderIsMute );
494 
495     QObject::connect ( pClient, &CClient::CLChannelLevelListReceived, this, &CClientDlg::OnCLChannelLevelListReceived );
496 
497     QObject::connect ( pClient, &CClient::VersionAndOSReceived, this, &CClientDlg::OnVersionAndOSReceived );
498 
499     QObject::connect ( pClient, &CClient::CLVersionAndOSReceived, this, &CClientDlg::OnCLVersionAndOSReceived );
500 
501     QObject::connect ( pClient, &CClient::SoundDeviceChanged, this, &CClientDlg::OnSoundDeviceChanged );
502 
503     QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::GUIDesignChanged, this, &CClientDlg::OnGUIDesignChanged );
504 
505     QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::AudioChannelsChanged, this, &CClientDlg::OnAudioChannelsChanged );
506 
507     QObject::connect ( &ClientSettingsDlg,
508                        &CClientSettingsDlg::CustomCentralServerAddrChanged,
509                        &ConnectDlg,
510                        &CConnectDlg::OnCustomCentralServerAddrChanged );
511 
512     QObject::connect ( &ClientSettingsDlg, &CClientSettingsDlg::NumMixerPanelRowsChanged, this, &CClientDlg::OnNumMixerPanelRowsChanged );
513 
514     QObject::connect ( this, &CClientDlg::SendTabChange, &ClientSettingsDlg, &CClientSettingsDlg::OnMakeTabChange );
515 
516     QObject::connect ( MainMixerBoard, &CAudioMixerBoard::ChangeChanGain, this, &CClientDlg::OnChangeChanGain );
517 
518     QObject::connect ( MainMixerBoard, &CAudioMixerBoard::ChangeChanPan, this, &CClientDlg::OnChangeChanPan );
519 
520     QObject::connect ( MainMixerBoard, &CAudioMixerBoard::NumClientsChanged, this, &CClientDlg::OnNumClientsChanged );
521 
522     QObject::connect ( &ChatDlg, &CChatDlg::NewLocalInputText, this, &CClientDlg::OnNewLocalInputText );
523 
524     QObject::connect ( &ConnectDlg, &CConnectDlg::ReqServerListQuery, this, &CClientDlg::OnReqServerListQuery );
525 
526     // note that this connection must be a queued connection, otherwise the server list ping
527     // times are not accurate and the client list may not be retrieved for all servers listed
528     // (it seems the sendto() function needs to be called from different threads to fire the
529     // packet immediately and do not collect packets before transmitting)
530     QObject::connect ( &ConnectDlg, &CConnectDlg::CreateCLServerListPingMes, this, &CClientDlg::OnCreateCLServerListPingMes, Qt::QueuedConnection );
531 
532     QObject::connect ( &ConnectDlg, &CConnectDlg::CreateCLServerListReqVerAndOSMes, this, &CClientDlg::OnCreateCLServerListReqVerAndOSMes );
533 
534     QObject::connect ( &ConnectDlg,
535                        &CConnectDlg::CreateCLServerListReqConnClientsListMes,
536                        this,
537                        &CClientDlg::OnCreateCLServerListReqConnClientsListMes );
538 
539     QObject::connect ( &ConnectDlg, &CConnectDlg::accepted, this, &CClientDlg::OnConnectDlgAccepted );
540 
541     // Initializations which have to be done after the signals are connected ---
542     // start timer for status bar
543     TimerStatus.start ( LED_BAR_UPDATE_TIME_MS );
544 
545     // restore connect dialog
546     if ( pSettings->bWindowWasShownConnect )
547     {
548         ShowConnectionSetupDialog();
549     }
550 
551     // mute stream on startup (must be done after the signal connections)
552     if ( bMuteStream )
553     {
554         chbLocalMute->setCheckState ( Qt::Checked );
555     }
556 
557     // query the update server version number needed for update check (note
558     // that the connection less message respond may not make it back but that
559     // is not critical since the next time Jamulus is started we have another
560     // chance and the update check is not time-critical at all)
561     CHostAddress UpdateServerHostAddress;
562 
563     // Send the request to two servers for redundancy if either or both of them
564     // has a higher release version number, the reply will trigger the notification.
565 
566     if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK1_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) )
567     {
568         pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress );
569     }
570 
571     if ( NetworkUtil().ParseNetworkAddress ( UPDATECHECK2_ADDRESS, UpdateServerHostAddress, bEnableIPv6 ) )
572     {
573         pClient->CreateCLServerListReqVerAndOSMes ( UpdateServerHostAddress );
574     }
575 }
576 
closeEvent(QCloseEvent * Event)577 void CClientDlg::closeEvent ( QCloseEvent* Event )
578 {
579     // store window positions
580     pSettings->vecWindowPosMain     = saveGeometry();
581     pSettings->vecWindowPosSettings = ClientSettingsDlg.saveGeometry();
582     pSettings->vecWindowPosChat     = ChatDlg.saveGeometry();
583     pSettings->vecWindowPosConnect  = ConnectDlg.saveGeometry();
584 
585     pSettings->bWindowWasShownSettings = ClientSettingsDlg.isVisible();
586     pSettings->bWindowWasShownChat     = ChatDlg.isVisible();
587     pSettings->bWindowWasShownConnect  = ConnectDlg.isVisible();
588 
589     // if settings/connect dialog or chat dialog is open, close it
590     ClientSettingsDlg.close();
591     ChatDlg.close();
592     ConnectDlg.close();
593     AnalyzerConsole.close();
594 
595     // if connected, terminate connection
596     if ( pClient->IsRunning() )
597     {
598         pClient->Stop();
599     }
600 
601     // make sure all current fader settings are applied to the settings struct
602     MainMixerBoard->StoreAllFaderSettings();
603 
604     pSettings->bConnectDlgShowAllMusicians = ConnectDlg.GetShowAllMusicians();
605     pSettings->eChannelSortType            = MainMixerBoard->GetFaderSorting();
606     pSettings->iNumMixerPanelRows          = MainMixerBoard->GetNumMixerPanelRows();
607 
608     // default implementation of this event handler routine
609     Event->accept();
610 }
611 
ManageDragNDrop(QDropEvent * Event,const bool bCheckAccept)612 void CClientDlg::ManageDragNDrop ( QDropEvent* Event, const bool bCheckAccept )
613 {
614     // we only want to use drag'n'drop with file URLs
615     QListIterator<QUrl> UrlIterator ( Event->mimeData()->urls() );
616 
617     while ( UrlIterator.hasNext() )
618     {
619         const QString strFileNameWithPath = UrlIterator.next().toLocalFile();
620 
621         // check all given URLs and if any has the correct suffix
622         if ( !strFileNameWithPath.isEmpty() && ( QFileInfo ( strFileNameWithPath ).suffix() == MIX_SETTINGS_FILE_SUFFIX ) )
623         {
624             if ( bCheckAccept )
625             {
626                 // only accept drops of supports file types
627                 Event->acceptProposedAction();
628             }
629             else
630             {
631                 // load the first valid settings file and leave the loop
632                 pSettings->LoadFaderSettings ( strFileNameWithPath );
633                 MainMixerBoard->LoadAllFaderSettings();
634                 break;
635             }
636         }
637     }
638 }
639 
UpdateRevSelection()640 void CClientDlg::UpdateRevSelection()
641 {
642     if ( pClient->GetAudioChannels() == CC_STEREO )
643     {
644         // for stereo make channel selection invisible since
645         // reverberation effect is always applied to both channels
646         rbtReverbSelL->setVisible ( false );
647         rbtReverbSelR->setVisible ( false );
648     }
649     else
650     {
651         // make radio buttons visible
652         rbtReverbSelL->setVisible ( true );
653         rbtReverbSelR->setVisible ( true );
654 
655         // update value
656         if ( pClient->IsReverbOnLeftChan() )
657         {
658             rbtReverbSelL->setChecked ( true );
659         }
660         else
661         {
662             rbtReverbSelR->setChecked ( true );
663         }
664     }
665 
666     // update visibility of the pan controls in the audio mixer board (pan is not supported for mono)
667     MainMixerBoard->SetDisplayPans ( pClient->GetAudioChannels() != CC_MONO );
668 }
669 
OnConnectDlgAccepted()670 void CClientDlg::OnConnectDlgAccepted()
671 {
672     // We had an issue that the accepted signal was emit twice if a list item was double
673     // clicked in the connect dialog. To avoid this we introduced a flag which makes sure
674     // we process the accepted signal only once after the dialog was initially shown.
675     if ( bConnectDlgWasShown )
676     {
677         // get the address from the connect dialog
678         QString strSelectedAddress = ConnectDlg.GetSelectedAddress();
679 
680         // only store new host address in our data base if the address is
681         // not empty and it was not a server list item (only the addresses
682         // typed in manually are stored by definition)
683         if ( !strSelectedAddress.isEmpty() && !ConnectDlg.GetServerListItemWasChosen() )
684         {
685             // store new address at the top of the list, if the list was already
686             // full, the last element is thrown out
687             pSettings->vstrIPAddress.StringFiFoWithCompare ( strSelectedAddress );
688         }
689 
690         // get name to be set in audio mixer group box title
691         QString strMixerBoardLabel;
692 
693         if ( ConnectDlg.GetServerListItemWasChosen() )
694         {
695             // in case a server in the server list was chosen,
696             // display the server name of the server list
697             strMixerBoardLabel = ConnectDlg.GetSelectedServerName();
698         }
699         else
700         {
701             // an item of the server address combo box was chosen,
702             // just show the address string as it was entered by the
703             // user
704             strMixerBoardLabel = strSelectedAddress;
705 
706             // special case: if the address is empty, we substitute the default
707             // directory server address so that a user which just pressed the connect
708             // button without selecting an item in the table or manually entered an
709             // address gets a successful connection
710             if ( strSelectedAddress.isEmpty() )
711             {
712                 strSelectedAddress = DEFAULT_SERVER_ADDRESS;
713                 strMixerBoardLabel = tr ( "Directory Server" );
714             }
715         }
716 
717         // first check if we are already connected, if this is the case we have to
718         // disconnect the old server first
719         if ( pClient->IsRunning() )
720         {
721             Disconnect();
722         }
723 
724         // initiate connection
725         Connect ( strSelectedAddress, strMixerBoardLabel );
726 
727         // reset flag
728         bConnectDlgWasShown = false;
729     }
730 }
731 
OnConnectDisconBut()732 void CClientDlg::OnConnectDisconBut()
733 {
734     // the connect/disconnect button implements a toggle functionality
735     if ( pClient->IsRunning() )
736     {
737         Disconnect();
738         SetMixerBoardDeco ( RS_UNDEFINED, pClient->GetGUIDesign() );
739     }
740     else
741     {
742         ShowConnectionSetupDialog();
743     }
744 }
745 
OnClearAllStoredSoloMuteSettings()746 void CClientDlg::OnClearAllStoredSoloMuteSettings()
747 {
748     // if we are in an active connection, we first have to store all fader settings in
749     // the settings struct, clear the solo and mute states and then apply the settings again
750     MainMixerBoard->StoreAllFaderSettings();
751     pSettings->vecStoredFaderIsSolo.Reset ( false );
752     pSettings->vecStoredFaderIsMute.Reset ( false );
753     MainMixerBoard->LoadAllFaderSettings();
754 }
755 
OnLoadChannelSetup()756 void CClientDlg::OnLoadChannelSetup()
757 {
758     QString strFileName = QFileDialog::getOpenFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX );
759 
760     if ( !strFileName.isEmpty() )
761     {
762         // first update the settings struct and then update the mixer panel
763         pSettings->LoadFaderSettings ( strFileName );
764         MainMixerBoard->LoadAllFaderSettings();
765     }
766 }
767 
OnSaveChannelSetup()768 void CClientDlg::OnSaveChannelSetup()
769 {
770     QString strFileName = QFileDialog::getSaveFileName ( this, tr ( "Select Channel Setup File" ), "", QString ( "*." ) + MIX_SETTINGS_FILE_SUFFIX );
771 
772     if ( !strFileName.isEmpty() )
773     {
774         // first store all current fader settings (in case we are in an active connection
775         // right now) and then save the information in the settings struct in the file
776         MainMixerBoard->StoreAllFaderSettings();
777         pSettings->SaveFaderSettings ( strFileName );
778     }
779 }
780 
OnVersionAndOSReceived(COSUtil::EOpSystemType,QString strVersion)781 void CClientDlg::OnVersionAndOSReceived ( COSUtil::EOpSystemType, QString strVersion )
782 {
783     // check if Pan is supported by the server (minimum version is 3.5.4)
784 #if QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 )
785     if ( QVersionNumber::compare ( QVersionNumber::fromString ( strVersion ), QVersionNumber ( 3, 5, 4 ) ) >= 0 )
786     {
787         MainMixerBoard->SetPanIsSupported();
788     }
789 #endif
790 }
791 
OnCLVersionAndOSReceived(CHostAddress,COSUtil::EOpSystemType,QString strVersion)792 void CClientDlg::OnCLVersionAndOSReceived ( CHostAddress, COSUtil::EOpSystemType, QString strVersion )
793 {
794     // update check
795 #if ( QT_VERSION >= QT_VERSION_CHECK( 5, 6, 0 ) ) && !defined( DISABLE_VERSION_CHECK )
796     int            mySuffixIndex;
797     QVersionNumber myVersion = QVersionNumber::fromString ( VERSION, &mySuffixIndex );
798 
799     int            serverSuffixIndex;
800     QVersionNumber serverVersion = QVersionNumber::fromString ( strVersion, &serverSuffixIndex );
801 
802     // only compare if the server version has no suffix (such as dev or beta)
803     if ( strVersion.size() == serverSuffixIndex && QVersionNumber::compare ( serverVersion, myVersion ) > 0 )
804     {
805         // show the label and hide it after one minute again
806         lblUpdateCheck->show();
807         QTimer::singleShot ( 60000, [this]() { lblUpdateCheck->hide(); } );
808     }
809 #endif
810 }
811 
OnChatTextReceived(QString strChatText)812 void CClientDlg::OnChatTextReceived ( QString strChatText )
813 {
814     ChatDlg.AddChatText ( strChatText );
815 
816     // Open chat dialog. If a server welcome message is received, we force
817     // the dialog to be upfront in case a licence text is shown. For all
818     // other new chat texts we do not want to force the dialog to be upfront
819     // always when a new message arrives since this is annoying.
820     ShowChatWindow ( ( strChatText.indexOf ( WELCOME_MESSAGE_PREFIX ) == 0 ) );
821 
822     UpdateDisplay();
823 }
824 
OnLicenceRequired(ELicenceType eLicenceType)825 void CClientDlg::OnLicenceRequired ( ELicenceType eLicenceType )
826 {
827     // right now only the creative common licence is supported
828     if ( eLicenceType == LT_CREATIVECOMMONS )
829     {
830         CLicenceDlg LicenceDlg;
831 
832         // mute the client output stream
833         pClient->SetMuteOutStream ( true );
834 
835         // Open the licence dialog and check if the licence was accepted. In
836         // case the dialog is just closed or the decline button was pressed,
837         // disconnect from that server.
838         if ( !LicenceDlg.exec() )
839         {
840             Disconnect();
841         }
842 
843         // unmute the client output stream if local mute button is not pressed
844         if ( chbLocalMute->checkState() == Qt::Unchecked )
845         {
846             pClient->SetMuteOutStream ( false );
847         }
848     }
849 }
850 
OnConClientListMesReceived(CVector<CChannelInfo> vecChanInfo)851 void CClientDlg::OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo )
852 {
853     // show channel numbers if --ctrlmidich is used (#241, #95)
854     if ( bMIDICtrlUsed )
855     {
856         for ( int i = 0; i < vecChanInfo.Size(); i++ )
857         {
858             vecChanInfo[i].strName.prepend ( QString().setNum ( vecChanInfo[i].iChanID ) + ":" );
859         }
860     }
861 
862     // update mixer board with the additional client infos
863     MainMixerBoard->ApplyNewConClientList ( vecChanInfo );
864 }
865 
OnNumClientsChanged(int iNewNumClients)866 void CClientDlg::OnNumClientsChanged ( int iNewNumClients )
867 {
868     // update window title
869     SetMyWindowTitle ( iNewNumClients );
870 }
871 
OnOpenAudioNetSettings()872 void CClientDlg::OnOpenAudioNetSettings() { ShowGeneralSettings ( SETTING_TAB_AUDIONET ); }
873 
OnOpenAdvancedSettings()874 void CClientDlg::OnOpenAdvancedSettings() { ShowGeneralSettings ( SETTING_TAB_ADVANCED ); }
875 
OnOpenUserProfileSettings()876 void CClientDlg::OnOpenUserProfileSettings() { ShowGeneralSettings ( SETTING_TAB_USER ); }
877 
SetMyWindowTitle(const int iNumClients)878 void CClientDlg::SetMyWindowTitle ( const int iNumClients )
879 {
880     // set the window title (and therefore also the task bar icon text of the OS)
881     // according to the following specification (#559):
882     // <ServerName> - <N> users - Jamulus
883     QString    strWinTitle;
884     const bool bClientNameIsUsed = !pClient->strClientName.isEmpty();
885 
886     if ( bClientNameIsUsed )
887     {
888         // if --clientname is used, the APP_NAME must be the very first word in
889         // the title, otherwise some user scripts do not work anymore, see #789
890         strWinTitle += QString ( APP_NAME ) + " - " + pClient->strClientName + " ";
891     }
892 
893     if ( iNumClients == 0 )
894     {
895         // only application name
896         if ( !bClientNameIsUsed ) // if --clientname is used, the APP_NAME is the first word in title
897         {
898             strWinTitle += QString ( APP_NAME );
899         }
900     }
901     else
902     {
903         strWinTitle += MainMixerBoard->GetServerName();
904 
905         if ( iNumClients == 1 )
906         {
907             strWinTitle += " - 1 " + tr ( "user" );
908         }
909         else if ( iNumClients > 1 )
910         {
911             strWinTitle += " - " + QString::number ( iNumClients ) + " " + tr ( "users" );
912         }
913 
914         if ( !bClientNameIsUsed ) // if --clientname is used, the APP_NAME is the first word in title
915         {
916             strWinTitle += " - " + QString ( APP_NAME );
917         }
918     }
919 
920     setWindowTitle ( strWinTitle );
921 
922 #if defined( Q_OS_MACX )
923     // for MacOS only we show the number of connected clients as a
924     // badge label text if more than one user is connected
925     // (only available in Qt5.2)
926 #    if QT_VERSION >= QT_VERSION_CHECK( 5, 2, 0 )
927     if ( iNumClients > 1 )
928     {
929         // show the number of connected clients
930         QtMac::setBadgeLabelText ( QString ( "%1" ).arg ( iNumClients ) );
931     }
932     else
933     {
934         // clear the text (apply an empty string)
935         QtMac::setBadgeLabelText ( "" );
936     }
937 #    endif
938 #endif
939 }
940 
ShowConnectionSetupDialog()941 void CClientDlg::ShowConnectionSetupDialog()
942 {
943     // show connect dialog
944     bConnectDlgWasShown = true;
945     ConnectDlg.show();
946     ConnectDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Connect" ), pClient->strClientName ) );
947 
948     // make sure dialog is upfront and has focus
949     ConnectDlg.raise();
950     ConnectDlg.activateWindow();
951 }
952 
ShowGeneralSettings(int iTab)953 void CClientDlg::ShowGeneralSettings ( int iTab )
954 {
955     // open general settings dialog
956     emit SendTabChange ( iTab );
957     ClientSettingsDlg.show();
958     ClientSettingsDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Settings" ), pClient->strClientName ) );
959 
960     // make sure dialog is upfront and has focus
961     ClientSettingsDlg.raise();
962     ClientSettingsDlg.activateWindow();
963 }
964 
ShowChatWindow(const bool bForceRaise)965 void CClientDlg::ShowChatWindow ( const bool bForceRaise )
966 {
967     ChatDlg.show();
968     ChatDlg.setWindowTitle ( MakeClientNameTitle ( tr ( "Chat" ), pClient->strClientName ) );
969 
970     if ( bForceRaise )
971     {
972         // make sure dialog is upfront and has focus
973         ChatDlg.showNormal();
974         ChatDlg.raise();
975         ChatDlg.activateWindow();
976     }
977 
978     UpdateDisplay();
979 }
980 
ShowAnalyzerConsole()981 void CClientDlg::ShowAnalyzerConsole()
982 {
983     // open analyzer console dialog
984     AnalyzerConsole.show();
985 
986     // make sure dialog is upfront and has focus
987     AnalyzerConsole.raise();
988     AnalyzerConsole.activateWindow();
989 }
990 
OnSettingsStateChanged(int value)991 void CClientDlg::OnSettingsStateChanged ( int value )
992 {
993     if ( value == Qt::Checked )
994     {
995         ShowGeneralSettings ( SETTING_TAB_AUDIONET );
996     }
997     else
998     {
999         ClientSettingsDlg.hide();
1000     }
1001 }
1002 
OnChatStateChanged(int value)1003 void CClientDlg::OnChatStateChanged ( int value )
1004 {
1005     if ( value == Qt::Checked )
1006     {
1007         ShowChatWindow();
1008     }
1009     else
1010     {
1011         ChatDlg.hide();
1012     }
1013 }
1014 
OnLocalMuteStateChanged(int value)1015 void CClientDlg::OnLocalMuteStateChanged ( int value )
1016 {
1017     pClient->SetMuteOutStream ( value == Qt::Checked );
1018 
1019     // show/hide info label
1020     if ( value == Qt::Checked )
1021     {
1022         lblGlobalInfoLabel->show();
1023     }
1024     else
1025     {
1026         lblGlobalInfoLabel->hide();
1027     }
1028 }
1029 
OnTimerSigMet()1030 void CClientDlg::OnTimerSigMet()
1031 {
1032     // show current level
1033     lbrInputLevelL->SetValue ( pClient->GetLevelForMeterdBLeft() );
1034     lbrInputLevelR->SetValue ( pClient->GetLevelForMeterdBRight() );
1035 
1036     if ( bDetectFeedback &&
1037          ( pClient->GetLevelForMeterdBLeft() > NUM_STEPS_LED_BAR - 0.5 || pClient->GetLevelForMeterdBRight() > NUM_STEPS_LED_BAR - 0.5 ) )
1038     {
1039         // mute locally and mute channel
1040         chbLocalMute->setCheckState ( Qt::Checked );
1041         MainMixerBoard->MuteMyChannel();
1042 
1043         // show message box about feedback issue
1044         QCheckBox* chb = new QCheckBox ( tr ( "Enable feedback detection" ) );
1045         chb->setCheckState ( pSettings->bEnableFeedbackDetection ? Qt::Checked : Qt::Unchecked );
1046         QMessageBox msgbox;
1047         msgbox.setText ( tr ( "Audio feedback or loud signal detected.\n\n"
1048                               "We muted your channel and activated 'Mute Myself'. Please solve "
1049                               "the feedback issue first and unmute yourself afterwards." ) );
1050         msgbox.setIcon ( QMessageBox::Icon::Warning );
1051         msgbox.addButton ( QMessageBox::Ok );
1052         msgbox.setDefaultButton ( QMessageBox::Ok );
1053         msgbox.setCheckBox ( chb );
1054 
1055         QObject::connect ( chb, &QCheckBox::stateChanged, this, &CClientDlg::OnFeedbackDetectionChanged );
1056 
1057         msgbox.exec();
1058     }
1059 }
1060 
OnTimerBuffersLED()1061 void CClientDlg::OnTimerBuffersLED()
1062 {
1063     CMultiColorLED::ELightColor eCurStatus;
1064 
1065     // get and reset current buffer state and set LED from that flag
1066     if ( pClient->GetAndResetbJitterBufferOKFlag() )
1067     {
1068         eCurStatus = CMultiColorLED::RL_GREEN;
1069     }
1070     else
1071     {
1072         eCurStatus = CMultiColorLED::RL_RED;
1073     }
1074 
1075     // update the buffer LED and the general settings dialog, too
1076     ledBuffers->SetLight ( eCurStatus );
1077 }
1078 
OnTimerPing()1079 void CClientDlg::OnTimerPing()
1080 {
1081     // send ping message to the server
1082     pClient->CreateCLPingMes();
1083 }
1084 
OnPingTimeResult(int iPingTime)1085 void CClientDlg::OnPingTimeResult ( int iPingTime )
1086 {
1087     // calculate overall delay
1088     const int iOverallDelayMs = pClient->EstimatedOverallDelay ( iPingTime );
1089 
1090     // color definition: <= 43 ms green, <= 68 ms yellow, otherwise red
1091     CMultiColorLED::ELightColor eOverallDelayLEDColor;
1092 
1093     if ( iOverallDelayMs <= 43 )
1094     {
1095         eOverallDelayLEDColor = CMultiColorLED::RL_GREEN;
1096     }
1097     else
1098     {
1099         if ( iOverallDelayMs <= 68 )
1100         {
1101             eOverallDelayLEDColor = CMultiColorLED::RL_YELLOW;
1102         }
1103         else
1104         {
1105             eOverallDelayLEDColor = CMultiColorLED::RL_RED;
1106         }
1107     }
1108 
1109     // only update delay information on settings dialog if it is visible to
1110     // avoid CPU load on working thread if not necessary
1111     if ( ClientSettingsDlg.isVisible() )
1112     {
1113         // set ping time result to general settings dialog
1114         ClientSettingsDlg.UpdateUploadRate();
1115     }
1116     SetPingTime ( iPingTime, iOverallDelayMs, eOverallDelayLEDColor );
1117 
1118     // update delay LED on the main window
1119     ledDelay->SetLight ( eOverallDelayLEDColor );
1120 }
1121 
OnTimerCheckAudioDeviceOk()1122 void CClientDlg::OnTimerCheckAudioDeviceOk()
1123 {
1124     // check if the audio device entered the audio callback after a pre-defined
1125     // timeout to check if a valid device is selected and if we do not have
1126     // fundamental settings errors (in which case the GUI would only show that
1127     // it is trying to connect the server which does not help to solve the problem (#129))
1128     if ( !pClient->IsCallbackEntered() )
1129     {
1130         QMessageBox::warning ( this,
1131                                APP_NAME,
1132                                tr ( "Your sound card is not working correctly. "
1133                                     "Please open the settings dialog and check the device selection and the driver settings." ) );
1134     }
1135 }
1136 
OnTimerDetectFeedback()1137 void CClientDlg::OnTimerDetectFeedback() { bDetectFeedback = false; }
1138 
OnSoundDeviceChanged(QString strError)1139 void CClientDlg::OnSoundDeviceChanged ( QString strError )
1140 {
1141     if ( !strError.isEmpty() )
1142     {
1143         // the sound device setup has a problem, disconnect any active connection
1144         if ( pClient->IsRunning() )
1145         {
1146             Disconnect();
1147         }
1148 
1149         // show the error message of the device setup
1150         QMessageBox::critical ( this, APP_NAME, strError, tr ( "Ok" ), nullptr );
1151     }
1152 
1153     // if the check audio device timer is running, it must be restarted on a device change
1154     if ( TimerCheckAudioDeviceOk.isActive() )
1155     {
1156         TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS );
1157     }
1158 
1159     if ( pSettings->bEnableFeedbackDetection && TimerDetectFeedback.isActive() )
1160     {
1161         TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS );
1162         bDetectFeedback = true;
1163     }
1164 
1165     // update the settings dialog
1166     ClientSettingsDlg.UpdateSoundDeviceChannelSelectionFrame();
1167 }
1168 
OnCLPingTimeWithNumClientsReceived(CHostAddress InetAddr,int iPingTime,int iNumClients)1169 void CClientDlg::OnCLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients )
1170 {
1171     // update connection dialog server list
1172     ConnectDlg.SetPingTimeAndNumClientsResult ( InetAddr, iPingTime, iNumClients );
1173 }
1174 
Connect(const QString & strSelectedAddress,const QString & strMixerBoardLabel)1175 void CClientDlg::Connect ( const QString& strSelectedAddress, const QString& strMixerBoardLabel )
1176 {
1177     // set address and check if address is valid
1178     if ( pClient->SetServerAddr ( strSelectedAddress ) )
1179     {
1180         // try to start client, if error occurred, do not go in
1181         // running state but show error message
1182         try
1183         {
1184             if ( !pClient->IsRunning() )
1185             {
1186                 pClient->Start();
1187             }
1188         }
1189 
1190         catch ( const CGenErr& generr )
1191         {
1192             // show error message and return the function
1193             QMessageBox::critical ( this, APP_NAME, generr.GetErrorText(), "Close", nullptr );
1194             return;
1195         }
1196 
1197         // hide label connect to server
1198         lblConnectToServer->hide();
1199         lbrInputLevelL->setEnabled ( true );
1200         lbrInputLevelR->setEnabled ( true );
1201 
1202         // change connect button text to "disconnect"
1203         butConnect->setText ( tr ( "D&isconnect" ) );
1204 
1205         // set server name in audio mixer group box title
1206         MainMixerBoard->SetServerName ( strMixerBoardLabel );
1207 
1208         // start timer for level meter bar and ping time measurement
1209         TimerSigMet.start ( LEVELMETER_UPDATE_TIME_MS );
1210         TimerBuffersLED.start ( BUFFER_LED_UPDATE_TIME_MS );
1211         TimerPing.start ( PING_UPDATE_TIME_MS );
1212         TimerCheckAudioDeviceOk.start ( CHECK_AUDIO_DEV_OK_TIME_MS ); // is single shot timer
1213 
1214         // audio feedback detection
1215         if ( pSettings->bEnableFeedbackDetection )
1216         {
1217             TimerDetectFeedback.start ( DETECT_FEEDBACK_TIME_MS ); // single shot timer
1218             bDetectFeedback = true;
1219         }
1220     }
1221 }
1222 
Disconnect()1223 void CClientDlg::Disconnect()
1224 {
1225     // only stop client if currently running, in case we received
1226     // the stopped message, the client is already stopped but the
1227     // connect/disconnect button and other GUI controls must be
1228     // updated
1229     if ( pClient->IsRunning() )
1230     {
1231         pClient->Stop();
1232     }
1233 
1234     // change connect button text to "connect"
1235     butConnect->setText ( tr ( "C&onnect" ) );
1236 
1237     // reset server name in audio mixer group box title
1238     MainMixerBoard->SetServerName ( "" );
1239 
1240     // stop timer for level meter bars and reset them
1241     TimerSigMet.stop();
1242     lbrInputLevelL->setEnabled ( false );
1243     lbrInputLevelR->setEnabled ( false );
1244     lbrInputLevelL->SetValue ( 0 );
1245     lbrInputLevelR->SetValue ( 0 );
1246 
1247     // show connect to server message
1248     lblConnectToServer->show();
1249 
1250     // stop other timers
1251     TimerBuffersLED.stop();
1252     TimerPing.stop();
1253     TimerCheckAudioDeviceOk.stop();
1254     TimerDetectFeedback.stop();
1255     bDetectFeedback = false;
1256 
1257     // clang-format off
1258 // TODO is this still required???
1259 // immediately update status bar
1260 OnTimerStatus();
1261     // clang-format on
1262 
1263     // reset LEDs
1264     ledBuffers->Reset();
1265     ledDelay->Reset();
1266 
1267     // clear text labels with client parameters
1268     lblPingVal->setText ( "---" );
1269     lblPingUnit->setText ( "" );
1270     lblDelayVal->setText ( "---" );
1271     lblDelayUnit->setText ( "" );
1272 
1273     // clear mixer board (remove all faders)
1274     MainMixerBoard->HideAll();
1275 }
1276 
UpdateDisplay()1277 void CClientDlg::UpdateDisplay()
1278 {
1279     // update settings/chat buttons (do not fire signals since it is an update)
1280     if ( chbSettings->isChecked() && !ClientSettingsDlg.isVisible() )
1281     {
1282         chbSettings->blockSignals ( true );
1283         chbSettings->setChecked ( false );
1284         chbSettings->blockSignals ( false );
1285     }
1286     if ( !chbSettings->isChecked() && ClientSettingsDlg.isVisible() )
1287     {
1288         chbSettings->blockSignals ( true );
1289         chbSettings->setChecked ( true );
1290         chbSettings->blockSignals ( false );
1291     }
1292 
1293     if ( chbChat->isChecked() && !ChatDlg.isVisible() )
1294     {
1295         chbChat->blockSignals ( true );
1296         chbChat->setChecked ( false );
1297         chbChat->blockSignals ( false );
1298     }
1299     if ( !chbChat->isChecked() && ChatDlg.isVisible() )
1300     {
1301         chbChat->blockSignals ( true );
1302         chbChat->setChecked ( true );
1303         chbChat->blockSignals ( false );
1304     }
1305 }
1306 
SetGUIDesign(const EGUIDesign eNewDesign)1307 void CClientDlg::SetGUIDesign ( const EGUIDesign eNewDesign )
1308 {
1309     // remove any styling from the mixer board - reapply after changing skin
1310     MainMixerBoard->setStyleSheet ( "" );
1311 
1312     // apply GUI design to current window
1313     switch ( eNewDesign )
1314     {
1315     case GD_ORIGINAL:
1316         backgroundFrame->setStyleSheet (
1317             "QFrame#backgroundFrame { border-image:  url(:/png/fader/res/mixerboardbackground.png) 34px 30px 40px 40px;"
1318             "                         border-top:    34px transparent;"
1319             "                         border-bottom: 40px transparent;"
1320             "                         border-left:   30px transparent;"
1321             "                         border-right:  40px transparent;"
1322             "                         padding:       -5px;"
1323             "                         margin:        -5px, -5px, 0px, 0px; }"
1324             "QLabel {                 color:          rgb(220, 220, 220);"
1325             "                         font:           bold; }"
1326             "QRadioButton {           color:          rgb(220, 220, 220);"
1327             "                         font:           bold; }"
1328             "QScrollArea {            background:     transparent; }"
1329             ".QWidget {               background:     transparent; }" // note: matches instances of QWidget, but not of its subclasses
1330             "QGroupBox {              background:     transparent; }"
1331             "QGroupBox::title {       color:          rgb(220, 220, 220); }"
1332             "QCheckBox::indicator {   width:          38px;"
1333             "                         height:         21px; }"
1334             "QCheckBox::indicator:unchecked {"
1335             "                         image:          url(:/png/fader/res/ledbuttonnotpressed.png); }"
1336             "QCheckBox::indicator:checked {"
1337             "                         image:          url(:/png/fader/res/ledbuttonpressed.png); }"
1338             "QCheckBox {              color:          rgb(220, 220, 220);"
1339             "                         font:           bold; }" );
1340 
1341 #ifdef _WIN32
1342         // Workaround QT-Windows problem: This should not be necessary since in the
1343         // background frame the style sheet for QRadioButton was already set. But it
1344         // seems that it is only applied if the style was set to default and then back
1345         // to GD_ORIGINAL. This seems to be a QT related issue...
1346         rbtReverbSelL->setStyleSheet ( "color: rgb(220, 220, 220);"
1347                                        "font:  bold;" );
1348         rbtReverbSelR->setStyleSheet ( "color: rgb(220, 220, 220);"
1349                                        "font:  bold;" );
1350 #endif
1351 
1352         lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_LED );
1353         lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_LED );
1354         ledBuffers->SetType ( CMultiColorLED::MT_LED );
1355         ledDelay->SetType ( CMultiColorLED::MT_LED );
1356         break;
1357 
1358     default:
1359         // reset style sheet and set original parameters
1360         backgroundFrame->setStyleSheet ( "" );
1361 
1362 #ifdef _WIN32
1363         // Workaround QT-Windows problem: See above description
1364         rbtReverbSelL->setStyleSheet ( "" );
1365         rbtReverbSelR->setStyleSheet ( "" );
1366 #endif
1367 
1368         lbrInputLevelL->SetLevelMeterType ( CLevelMeter::MT_BAR );
1369         lbrInputLevelR->SetLevelMeterType ( CLevelMeter::MT_BAR );
1370         ledBuffers->SetType ( CMultiColorLED::MT_INDICATOR );
1371         ledDelay->SetType ( CMultiColorLED::MT_INDICATOR );
1372         break;
1373     }
1374 
1375     // also apply GUI design to child GUI controls
1376     MainMixerBoard->SetGUIDesign ( eNewDesign );
1377 }
1378 
OnRecorderStateReceived(const ERecorderState newRecorderState)1379 void CClientDlg::OnRecorderStateReceived ( const ERecorderState newRecorderState )
1380 {
1381     MainMixerBoard->SetRecorderState ( newRecorderState );
1382     SetMixerBoardDeco ( newRecorderState, pClient->GetGUIDesign() );
1383 }
1384 
OnGUIDesignChanged()1385 void CClientDlg::OnGUIDesignChanged()
1386 {
1387     SetGUIDesign ( pClient->GetGUIDesign() );
1388     SetMixerBoardDeco ( MainMixerBoard->GetRecorderState(), pClient->GetGUIDesign() );
1389 }
1390 
SetMixerBoardDeco(const ERecorderState newRecorderState,const EGUIDesign eNewDesign)1391 void CClientDlg::SetMixerBoardDeco ( const ERecorderState newRecorderState, const EGUIDesign eNewDesign )
1392 {
1393     // return if no change
1394     if ( ( newRecorderState == eLastRecorderState ) && ( eNewDesign == eLastDesign ) )
1395         return;
1396     eLastRecorderState = newRecorderState;
1397     eLastDesign        = eNewDesign;
1398 
1399     if ( newRecorderState == RS_RECORDING )
1400     {
1401         MainMixerBoard->setStyleSheet ( "QGroupBox::title { subcontrol-origin: margin; "
1402                                         "                   subcontrol-position: left top;"
1403                                         "                   left: 7px;"
1404                                         "                   color: rgb(255,255,255);"
1405                                         "                   background-color: rgb(255,0,0); }" );
1406     }
1407     else
1408     {
1409         if ( eNewDesign == GD_ORIGINAL )
1410         {
1411             MainMixerBoard->setStyleSheet ( "QGroupBox::title { subcontrol-origin: margin;"
1412                                             "                   subcontrol-position: left top;"
1413                                             "                   left: 7px;"
1414                                             "                   color: rgb(220,220,220); }" );
1415         }
1416         else
1417         {
1418             MainMixerBoard->setStyleSheet ( "QGroupBox::title { subcontrol-origin: margin;"
1419                                             "                   subcontrol-position: left top;"
1420                                             "                   left: 7px;"
1421                                             "                   color: rgb(0,0,0); }" );
1422         }
1423     }
1424 }
1425 
SetPingTime(const int iPingTime,const int iOverallDelayMs,const CMultiColorLED::ELightColor eOverallDelayLEDColor)1426 void CClientDlg::SetPingTime ( const int iPingTime, const int iOverallDelayMs, const CMultiColorLED::ELightColor eOverallDelayLEDColor )
1427 {
1428     // apply values to GUI labels, take special care if ping time exceeds
1429     // a certain value
1430     if ( iPingTime > 500 )
1431     {
1432         const QString sErrorText = "<font color=\"red\"><b>&#62;500</b></font>";
1433         lblPingVal->setText ( sErrorText );
1434         lblDelayVal->setText ( sErrorText );
1435     }
1436     else
1437     {
1438         lblPingVal->setText ( QString().setNum ( iPingTime ) );
1439         lblDelayVal->setText ( QString().setNum ( iOverallDelayMs ) );
1440     }
1441     lblPingUnit->setText ( "ms" );
1442     lblDelayUnit->setText ( "ms" );
1443 
1444     // set current LED status
1445     ledDelay->SetLight ( eOverallDelayLEDColor );
1446 }
1447