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>>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