Release version 0.99.2-1
[fmit.git] / src / CustomInstrumentTunerForm.cpp
1 // Copyright 2004 "Gilles Degottex"
2
3 // This file is part of "fmit"
4
5 // "fmit" is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // "fmit" is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
19 #include "CustomInstrumentTunerForm.h"
20
21 #include <cassert>
22 #include <iostream>
23 using namespace std;
24 #include <qstring.h>
25 #include <qaction.h>
26 #include <qlabel.h>
27 #include <qgl.h>
28 #include <qlayout.h>
29 #include <qlcdnumber.h>
30 #include <qdial.h>
31 #include <qspinbox.h>
32 #include <qcombobox.h>
33 #include <qsplitter.h>
34 #include <qprogressbar.h>
35 #include <qmessagebox.h>
36 #include <qmenubar.h>
37 #include <qpushbutton.h>
38 #include <qcheckbox.h>
39 #include <qgroupbox.h>
40 #include <qbuttongroup.h>
41 #include <qlineedit.h>
42 #include <qstatusbar.h>
43 #include <qdatetime.h>
44 #include <qradiobutton.h>
45
46 #include <qtextbrowser.h>
47 #include <qpushbutton.h>
48 #include <qwidgetaction.h>
49 #include <qevent.h>
50 #include <qabstracttextdocumentlayout.h>
51 #include <Music/Convolution.h>
52 using namespace Music;
53 #include "modules/View.h"
54
55 CustomInstrumentTunerForm::CustomInstrumentTunerForm()
56 : m_config_form(this)
57 , m_capture_thread("fmit")
58 , m_timer_refresh(this)
59 , m_algo_combedfft(NULL)
60 , m_range_filter(&m_dummy_range_filter)
61 , m_quantizer(&m_latency_quantizer)
62 , m_settings("fmit", "fmit", "009801")  // not necessarily equal to the soft version
63 {
64         setupUi(this);
65
66         /*QWidgetAction* viewsCaption = new QWidgetAction(ui_tbViews);
67         viewsCaption->setDefaultWidget(new QLabel(tr("Views"), ui_tbViews));
68         ui_tbViews->addAction(viewsCaption);*/
69
70         View::s_settings = &m_settings;
71         m_settings.add(m_config_form.ui_chkFullScreen);
72         m_settings.add(m_config_form.ui_chkAutoSaveOnExit);
73         m_settings.add(m_config_form.ui_chkShowA4Offset);
74
75         m_settings.add(m_config_form.ui_cbTuning);
76         m_settings.add(m_config_form.ui_cbTonality);
77         m_settings.add(m_config_form.ui_cbNotesName);
78         m_settings.add(ui_spinAFreq);
79         m_settings.add(ui_spinA3Offset);
80
81         m_settings.add(m_config_form.ui_chkAutoDetect);
82 #ifdef CAPTURE_JACK
83         m_settings.add(m_config_form.ui_chkJACKAutoConnect);
84         m_settings.add(m_config_form.ui_txtJACKSourcePort);
85 #endif
86 #ifdef CAPTURE_ALSA
87         m_settings.add(m_config_form.ui_chkALSASamplingRateMax);
88         m_settings.add(m_config_form.ui_spinALSASamplingRate);
89         m_settings.add(m_config_form.ui_chkALSAMixMultipleChannels);
90         m_settings.add(m_config_form.ui_txtALSAPCMName);
91 #endif
92 #ifdef CAPTURE_OSS
93         m_settings.add(m_config_form.ui_chkOSSSamplingRateMax);
94         m_settings.add(m_config_form.ui_spinOSSSamplingRate);
95         m_settings.add(m_config_form.ui_chkOSSMixMultipleChannels);
96         m_settings.add(m_config_form.ui_txtOSSPCMName);
97 #endif
98 #ifdef CAPTURE_PORTAUDIO
99         m_settings.add(m_config_form.ui_chkPortAudioSamplingRateMax);
100         m_settings.add(m_config_form.ui_spinPortAudioSamplingRate);
101         m_settings.add(m_config_form.ui_chkPortAudioMixMultipleChannels);
102 #endif
103
104         m_settings.add(m_config_form.ui_spinRefreshTime);
105         m_settings.add(m_config_form.ui_spinMinHT);
106         m_settings.add(m_config_form.ui_spinMaxHT);
107
108         m_settings.add(m_config_form.ui_grpRangeFiltering);
109         m_settings.add(m_config_form.ui_rdRangeFilteringRectangular);
110         m_settings.add(m_config_form.ui_rdRangeFilteringFIR);
111
112         m_settings.add(m_config_form.ui_spinVolumeTreshold);
113         m_settings.add(m_config_form.ui_spinWindowSizeFactor);
114         m_settings.add(m_config_form.ui_chkAlgoUseSubHarmTresh);
115         m_settings.add(m_config_form.ui_spinCombedFFTAudibilityRatio);
116
117         m_settings.add(m_config_form.up_grpFreqRefinement);
118         m_settings.add(m_config_form.ui_rdUseFreqRefinement);
119         m_settings.add(m_config_form.ui_spinFreqRefinMaxHarm);
120         m_settings.add(m_config_form.ui_rdUseTimeRefinement);
121         m_settings.add(m_config_form.ui_spinTimeRefinMaxPeriod);
122
123         m_settings.add(m_config_form.ui_grpQuantizer);
124         m_settings.add(m_config_form.ui_spinErrorLatency);
125
126         m_algo_combedfft = new CombedFT();
127
128         for(int i=0; i<m_capture_thread.getTransports().size(); i++)
129                 m_config_form.ui_cbTransports->addItem(m_capture_thread.getTransports()[i]->getName());
130
131         if(m_capture_thread.getTransports().empty())
132                 QMessageBox::critical(this, "Error", "Please compile me with a capture system ...");
133         if(m_capture_thread.getTransports().size()==1)
134         {
135                 m_config_form.ui_lblSelectedCaptureSystem->hide();
136                 m_config_form.ui_btnAutoDetect->hide();
137                 m_config_form.ui_chkAutoDetect->hide();
138                 m_config_form.ui_cbTransports->hide();
139         }
140         m_config_form.ui_grpALSA->hide();
141         m_config_form.ui_grpJACK->hide();
142         m_config_form.ui_grpPortAudio->hide();
143         m_config_form.ui_grpOSS->hide();
144
145         ui_lblA3Offset->hide();
146         ui_spinA3Offset->hide();
147
148         connect(&m_capture_thread, SIGNAL(samplingRateChanged(int)), this, SLOT(samplingRateChanged(int)));
149         connect(&m_capture_thread, SIGNAL(errorRaised(const QString&)), this, SLOT(errorRaised(const QString&)));
150         connect(&m_capture_thread, SIGNAL(transportChanged(const QString&)), this, SLOT(transportChanged(const QString&)));
151
152         connect(&m_latency_quantizer, SIGNAL(noteStarted(double,double)), this, SLOT(noteStarted(double,double)));
153         connect(&m_latency_quantizer, SIGNAL(noteFinished(double,double)), this, SLOT(noteFinished(double,double)));
154         connect(&m_dummy_quantizer, SIGNAL(noteStarted(double,double)), this, SLOT(noteStarted(double,double)));
155         connect(&m_dummy_quantizer, SIGNAL(noteFinished(double,double)), this, SLOT(noteFinished(double,double)));
156
157         m_dialTune = new DialView(centralWidget());
158         ui_dialTuneLayout->addWidget(m_dialTune);
159
160         m_glGraph = new GLGraph(centralWidget());
161         connect(m_glGraph->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
162         connect(m_glGraph->setting_spinMaxHeight, SIGNAL(valueChanged(int)), this, SLOT(update_views()));
163         ui_tbViews->addAction(m_glGraph->setting_show);
164         ui_graphLayout->addWidget(m_glGraph);
165
166         m_glErrorHistory = new GLErrorHistory(centralWidget());
167         connect(m_glErrorHistory->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
168         ui_tbViews->addAction(m_glErrorHistory->setting_show);
169         ui_errorLayout->addWidget(m_glErrorHistory);
170
171         // link scales
172         connect(m_dialTune->setting_spinScale, SIGNAL(valueChanged(int)), m_glErrorHistory->setting_spinScale, SLOT(setValue(int)));
173         connect(m_glErrorHistory->setting_spinScale, SIGNAL(valueChanged(int)), m_dialTune->setting_spinScale, SLOT(setValue(int)));
174         connect(m_dialTune->setting_useCents, SIGNAL(toggled(bool)), m_glErrorHistory->setting_useCents, SLOT(setOn(bool)));
175         connect(m_glErrorHistory->setting_useCents, SIGNAL(toggled(bool)), m_dialTune->setting_useCents, SLOT(setOn(bool)));
176
177         m_glVolumeHistory = new GLVolumeHistory(centralWidget());
178         connect(m_config_form.ui_spinVolumeTreshold, SIGNAL(valueChanged(int)), m_glVolumeHistory, SLOT(setVolumeTreshold(int)));
179         connect(m_glVolumeHistory->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
180         ui_tbViews->addAction(m_glVolumeHistory->setting_show);
181         ui_volumeLayout->addWidget(m_glVolumeHistory);
182
183         // link keep settings
184         connect(ui_btnKeepErrorHistory, SIGNAL(toggled(bool)), m_glErrorHistory->setting_keep, SLOT(setOn(bool)));
185         connect(m_glErrorHistory->setting_keep, SIGNAL(toggled(bool)), m_glVolumeHistory->setting_keep, SLOT(setOn(bool)));
186         connect(m_glErrorHistory->setting_keep, SIGNAL(toggled(bool)), ui_btnKeepErrorHistory, SLOT(setOn(bool)));
187
188         m_glSample = new GLSample(centralWidget());
189         connect(m_glSample->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
190         ui_tbViews->addAction(m_glSample->setting_show);
191         ui_sampleLayout->addWidget(m_glSample);
192
193         m_glFreqStruct = new GLFreqStruct(centralWidget());
194         connect(m_glFreqStruct->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
195         ui_tbViews->addAction(m_glFreqStruct->setting_show);
196         ui_formantsLayout->addWidget(m_glFreqStruct);
197
198         m_glFT = new GLFT(centralWidget());
199         connect(m_glFT->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
200         ui_tbViews->addAction(m_glFT->setting_show);
201         ui_FT->addWidget(m_glFT);
202
203         m_microtonalView = new MicrotonalView(centralWidget());
204         connect(m_microtonalView->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
205         connect(m_microtonalView, SIGNAL(tuningFreqChanged(float)), this, SLOT(tuningFreqChanged(float)));
206         ui_tbViews->addAction(m_microtonalView->setting_show);
207         ui_microtonalLayout->addWidget(m_microtonalView);
208
209         m_glStatistics = new GLStatistics(centralWidget());
210         connect(m_glStatistics->setting_show, SIGNAL(toggled(bool)), this, SLOT(update_views()));
211         ui_tbViews->addAction(m_glStatistics->setting_show);
212         ui_microtonalLayout->addWidget(m_glStatistics);
213
214         connect(m_dialTune->setting_spinScale, SIGNAL(valueChanged(int)), m_glStatistics->setting_spinScale, SLOT(setValue(int)));
215         connect(m_glStatistics->setting_spinScale, SIGNAL(valueChanged(int)), m_dialTune->setting_spinScale, SLOT(setValue(int)));
216         connect(m_dialTune->setting_useCents, SIGNAL(toggled(bool)), m_glStatistics->setting_useCents, SLOT(setOn(bool)));
217         connect(m_glStatistics->setting_useCents, SIGNAL(toggled(bool)), m_dialTune->setting_useCents, SLOT(setOn(bool)));
218
219         connect(m_dialTune->setting_showTolerance, SIGNAL(toggled(bool)), m_glStatistics->setting_showTolerance, SLOT(setOn(bool)));
220         connect(m_glStatistics->setting_showTolerance, SIGNAL(toggled(bool)), m_dialTune->setting_showTolerance, SLOT(setOn(bool)));
221
222     connect(&m_config_form, SIGNAL(accepted()), this, SLOT(configure_ok()));
223     connect(&m_config_form, SIGNAL(rejected()), this, SLOT(configure_cancel()));
224         connect(m_config_form.ui_btnRestoreFactorySettings, SIGNAL(clicked()), this, SLOT(restoreFactorySettings()));
225         connect(m_config_form.ui_spinMinHT, SIGNAL(valueChanged(int)), this, SLOT(noteRangeChanged()));
226         connect(m_config_form.ui_spinMaxHT, SIGNAL(valueChanged(int)), this, SLOT(noteRangeChanged()));
227         connect(m_config_form.ui_cbTuning, SIGNAL(highlighted(int)), this, SLOT(noteRangeChanged()));
228         connect(m_config_form.ui_cbTonality, SIGNAL(highlighted(int)), this, SLOT(noteRangeChanged()));
229         connect(m_config_form.ui_cbNotesName, SIGNAL(highlighted(int)), this, SLOT(noteRangeChanged()));
230         connect(m_config_form.ui_btnAutoDetect, SIGNAL(clicked()), this, SLOT(autoDetectTransport()));
231         connect(m_config_form.ui_cbTransports, SIGNAL(activated(const QString&)), this, SLOT(selectTransport(const QString&)));
232         connect(m_config_form.ui_chkALSAMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
233         connect(m_config_form.ui_chkOSSMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
234         connect(m_config_form.ui_chkPortAudioMixMultipleChannels, SIGNAL(toggled(bool)), &m_capture_thread, SLOT(setMixMultipleChannels(bool)));
235
236         loadSettings();
237
238         if(m_config_form.ui_chkAutoDetect->isChecked())
239                 m_capture_thread.autoDetectTransport();
240
241         configure_ok();
242
243         if(m_config_form.ui_chkFullScreen->isChecked())
244                 toggleFullScreen();
245
246         m_time_refresh_views.start();
247         m_time_refresh.start();
248         m_time.start();
249         connect((QObject*)&m_timer_refresh, SIGNAL(timeout()), this, SLOT(refresh()));
250         m_timer_refresh.start(m_config_form.ui_spinRefreshTime->value());
251
252 //     cerr << __FILE__ << " " << __LINE__ << endl;
253 }
254
255 void CustomInstrumentTunerForm::transportChanged(const QString& name)
256 {
257         selectTransport(name);
258
259         if(m_capture_thread.getCurrentTransportIndex()!=m_config_form.ui_cbTransports->currentIndex())
260                 m_config_form.ui_cbTransports->setCurrentIndex(m_capture_thread.getCurrentTransportIndex());
261 }
262 void CustomInstrumentTunerForm::selectTransport(const QString& name)
263 {
264         m_config_form.ui_grpALSA->hide();
265         m_config_form.ui_grpJACK->hide();
266         m_config_form.ui_grpPortAudio->hide();
267         m_config_form.ui_grpOSS->hide();
268
269         if(name=="ALSA")                        m_config_form.ui_grpALSA->show();
270         else if(name=="JACK")           m_config_form.ui_grpJACK->show();
271         else if(name=="PortAudio")      m_config_form.ui_grpPortAudio->show();
272         else if(name=="OSS")            m_config_form.ui_grpOSS->show();
273 }
274 void CustomInstrumentTunerForm::autoDetectTransport()
275 {
276         m_capture_thread.autoDetectTransport();
277
278         // here transportChanged will be called
279 }
280
281 void CustomInstrumentTunerForm::toggleFullScreen()
282 {
283         static bool fs = true;
284         if(fs)
285         {
286                 m_config_form.ui_chkFullScreen->setChecked(true);
287                 showFullScreen();
288         }
289         else
290         {
291                 m_config_form.ui_chkFullScreen->setChecked(false);
292                 showNormal();
293         }
294         fs = !fs;
295 }
296
297 void CustomInstrumentTunerForm::noteRangeChanged()
298 {
299         //      cerr << "CustomInstrumentTunerForm::noteRangeChanged" << endl;
300
301         m_config_form.ui_txtMinHT->setText(QString::fromStdString(h2n(m_config_form.ui_spinMinHT->value())) + " = " + QString::number(h2f(m_config_form.ui_spinMinHT->value())) + " hz");
302         m_config_form.ui_txtMaxHT->setText(QString::fromStdString(h2n(m_config_form.ui_spinMaxHT->value())) + " = " + QString::number(h2f(m_config_form.ui_spinMaxHT->value())) + " hz");
303 }
304
305 void CustomInstrumentTunerForm::errorRaised(const QString& error)
306 {
307         //      cerr << "CustomInstrumentTunerForm::errorRaised " << error << endl;
308
309         statusBar()->showMessage(QString("ERROR: ")+error);
310
311         QPalette palette;
312         palette.setColor(ui_lblSoundStability->backgroundRole(), QColor(180,74,74));
313         ui_lblSoundStability->setPalette(palette);
314 }
315
316 void CustomInstrumentTunerForm::samplingRateChanged(int sampling_rate)
317 {
318         cerr << "CustomInstrumentTunerForm::samplingRateChanged " << sampling_rate << endl;
319
320         Music::SetSamplingRate(sampling_rate);
321
322         m_rect_range_filter.reset(int(GetSamplingRate()/2000.0));
323 //         m_fir_range_filter.setImpulseResponse(fir1_lowpass(400, 2/400));
324 //      m_rect_range_filter.reset(int(GetSamplingRate()/h2f(GetSemitoneMin())));
325     m_glFT->spinWinLengthChanged(m_glFT->setting_winlen->value());
326     m_glFT->setSamplingRate(sampling_rate);
327
328         if(m_config_form.ui_cbTransports->currentText()=="JACK")
329                 m_config_form.ui_lblJACKSamplingRate->setText(QString::number(sampling_rate));
330 }
331
332 void CustomInstrumentTunerForm::ui_spinAFreq_valueChanged(int AFreq)
333 {
334         double A = AFreq;
335         if(m_config_form.ui_chkShowA4Offset->isChecked())
336                 A = h2f(ui_spinA3Offset->value()*1/100.0f, A);
337         Music::SetAFreq(A);
338 //      cerr << A << endl;
339 }
340 void CustomInstrumentTunerForm::ui_spinAOffset_valueChanged(int offset)
341 {
342         double A = ui_spinAFreq->value();
343         if(m_config_form.ui_chkShowA4Offset->isChecked())
344                 A = h2f(offset*1/100.0f, ui_spinAFreq->value());
345         Music::SetAFreq(A);
346 //      cerr << A << endl;
347 }
348
349 void CustomInstrumentTunerForm::tuningFreqChanged(float freq)
350 {
351         //      cerr << "CustomInstrumentTunerForm::tuningFreqChanged " << freq << endl;
352
353         if(freq==0.0f)
354         {
355                 if(m_compared_freq!=0.0f)
356                 {
357                         ui_txtNoteFreq->display(m_compared_freq);
358                         ui_txtNote->setText(QString::fromStdString(h2n(f2h(m_compared_freq))));
359                 }
360         }
361         else
362         {
363                 m_compared_freq = freq;
364                 ui_txtNoteFreq->display(int(freq*100)/100.0f);
365                 ui_txtNote->setText(m_microtonalView->getTuningNoteName());
366         }
367
368         m_quantizer->reset();
369
370         //      m_dialTune->setError(-10.0f);
371 }
372
373 void CustomInstrumentTunerForm::pause(bool on)
374 {
375         m_capture_thread.togglePause(on);
376
377         if(on)  m_timer_refresh.stop();
378         else    m_timer_refresh.start(m_config_form.ui_spinRefreshTime->value());
379 }
380
381 void CustomInstrumentTunerForm::refresh()
382 {
383         double elapsed_time = m_time_refresh.elapsed();
384         m_time_refresh.start();
385
386         QColor capture_failed_color(180,74,74);
387         QColor prb_color(208,146,0);
388         QColor empty_color(128,128,128);
389         QColor ok_color(83,165,105);
390
391         // 1/{time between refresh} = {nb refresh by seconds}
392         // limit the nb of new data by fs/{nb refresh by seconds}
393         // add 1 to {nb refresh by second} to eventualy recover small lags
394     // (a big lag is managed by m_values.size()>getPacketSizeSinceLastLock())
395         int limit = int( m_capture_thread.getSamplingRate() /
396                         (1.0/(m_config_form.ui_spinRefreshTime->value()/1000.0) - 1));
397
398 //      cerr << "REFRESH ";
399
400         m_capture_thread.lock();
401 //     cerr << "CustomInstrumentTunerForm::refresh locked, values to read=" << m_capture_thread.m_values.size() << endl;
402         int nb_new_data = 0;
403         while(!m_capture_thread.m_values.empty() &&
404                           (m_capture_thread.m_values.size()>m_capture_thread.getPacketSizeSinceLastLock() || nb_new_data<limit))
405         {
406 //              cerr << m_capture_thread.m_values.back() << " ";
407                 double value = (*m_range_filter)(m_capture_thread.m_values.back());
408 //              cerr << value << " ";
409                 m_capture_thread.m_values.pop_back();
410
411                 m_queue.push_front(value);
412                 if(m_glGraph)   m_glGraph->addValue(value);
413                 if(m_glFT)              m_glFT->buff.push_front(value);
414
415                 nb_new_data++;
416         }
417         m_capture_thread.unlock();
418 //     cerr << "CustomInstrumentTunerForm::refresh unlocked" << endl;
419
420 //      cerr << endl;
421
422         int max_size = max(m_range_filter->getLength(), max(m_glGraph->getLength(), m_algo_combedfft->getMinSize()));
423         while(!m_queue.empty() && int(m_queue.size())>max_size)
424                 m_queue.pop_back();
425
426         // refresh graph data
427         m_glGraph->refreshGraph();      // TODO refresh the view each time ??
428         m_glFT->refreshGraph();
429
430         // ------- Analysis stage -------
431
432         // if something goes wrong in the capture system
433         if(nb_new_data==0 || m_algo_combedfft==NULL || elapsed_time>8*m_config_form.ui_spinRefreshTime->value())
434         {
435                 QPalette palette;
436                 palette.setColor(ui_lblSoundStability->backgroundRole(), capture_failed_color);
437                 ui_lblSoundStability->setPalette(palette);
438         }
439         else
440         {
441                 m_algo_combedfft->apply(m_queue);
442
443         // TODO hem ... use energy
444                 double max_component = 20*log10(m_algo_combedfft->getComponentsMax());
445                 ui_pgbVolume->setValue(100+int(max_component));
446
447                 double freq = 0.0;
448                 if(m_algo_combedfft->hasNoteRecognized())
449                         freq = m_algo_combedfft->getFondamentalFreq();
450
451                 double freq_rel = freq*m_algo_combedfft->m_plan.in.size()/double(GetSamplingRate());
452                 if(freq_rel<1 || freq_rel>(m_algo_combedfft->m_plan.out.size()/2))
453                         freq = 0.0;
454
455                 // frequency refinement
456                 if(freq>0.0 && m_config_form.up_grpFreqRefinement->isChecked())
457                 {
458                         if(m_config_form.ui_rdUseFreqRefinement->isChecked())
459                         {
460                                 freq = FundFreqRefinementOfHarmonicStruct(m_algo_combedfft->m_plan.out, freq, m_config_form.ui_spinFreqRefinMaxHarm->value(), m_algo_combedfft->getZeroPaddingFactor());
461
462                         }
463                         else if(m_config_form.ui_rdUseTimeRefinement->isChecked())
464                         {
465                                 double period = GetAveragePeriodFromApprox(m_queue, int(GetSamplingRate()/freq), m_config_form.ui_spinTimeRefinMaxPeriod->value());
466                                 if(period>0.0)
467                                         freq = GetSamplingRate()/period;
468                         }
469                 }
470
471 //              cerr << "2) test freq=" << m_test_freq <<endl;
472
473                 m_quantizer->quantize(freq);
474
475                 if(!m_quantizer->isPlaying())
476                 {
477                         QPalette palette;
478                         palette.setColor(ui_lblSoundStability->backgroundRole(), empty_color);
479                         ui_lblSoundStability->setPalette(palette);
480                 }
481                 else
482                 {
483             // Use 100ms rect window
484             int max_amplitude_limit = min(m_queue.size(),size_t(m_capture_thread.getSamplingRate()*100.0/1000));
485             double max_amplitude = 0.0;
486             for(int i=0;i<max_amplitude_limit; i++)
487                 max_amplitude = max(max_amplitude, fabs(m_queue[i]));
488
489                         if(max_amplitude>=1.0)
490                         {
491                                 QPalette palette;
492                                 palette.setColor(ui_lblSoundStability->backgroundRole(), prb_color);
493                                 ui_lblSoundStability->setPalette(palette);
494                         }
495                         else
496                         {
497                                 QPalette palette;
498                                 palette.setColor(ui_lblSoundStability->backgroundRole(), ok_color);
499                                 ui_lblSoundStability->setPalette(palette);
500                         }
501
502                         m_freq = m_quantizer->getAverageFrequency();
503                         m_error = f2hf(m_freq, m_compared_freq);
504
505                         // refresh error
506                         m_glErrorHistory->addError(m_error);
507                         m_dialTune->setError(m_error);
508                         m_dialTune->m_avg_error = m_glErrorHistory->m_notes.back().avg_err;
509                         m_dialTune->m_min_error = m_glErrorHistory->m_notes.back().min_err;
510                         m_dialTune->m_max_error = m_glErrorHistory->m_notes.back().max_err;
511                         ui_txtFreq->display(m_freq);
512
513                         // refresh intonal tuning cursor
514                         m_microtonalView->setAFreq(Music::GetAFreq());
515                         m_microtonalView->updateCursor(m_freq);
516
517                         // volume
518                         m_glVolumeHistory->addVolume(max_component);
519
520                         // refresh sample data
521                         refresh_data_sample();
522
523                         // refresh formants data
524                         refresh_data_harmonics();
525
526                         m_glStatistics->addNote(f2h(m_compared_freq), m_error);
527                 }
528         }
529
530         if(m_time_refresh_views.elapsed()>50)   // 20 images/second max
531                 refresh_views();
532 }
533
534 void CustomInstrumentTunerForm::noteStarted(double freq, double dt)
535 {
536 //      cerr << "CustomInstrumentTunerForm::noteStarted " << freq << "," << dt << endl;
537
538         // set the compared freq
539         if(m_microtonalView->setting_show->isChecked() && m_microtonalView->hasTuningFreqSelected())
540                 m_compared_freq = m_microtonalView->getTuningFreq();
541         else
542                 m_compared_freq = m_quantizer->getCenterFrequency();    // h2f(f2h(freq));
543
544         if(m_microtonalView->setting_show->isChecked() && m_microtonalView->hasTuningFreqSelected())
545         {
546                 ui_txtNoteFreq->display(int(m_microtonalView->getTuningFreq()*100)/100.0);
547                 ui_txtNote->setText(m_microtonalView->getTuningNoteName());
548                 if(m_microtonalView->m_selected_jivalue->is_ratio)
549                 {
550                         m_glErrorHistory->addNote(GLErrorHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->num, m_microtonalView->m_selected_jivalue->den));
551                         m_glVolumeHistory->addNote(GLVolumeHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->num, m_microtonalView->m_selected_jivalue->den));
552                 }
553                 else
554                 {
555                         m_glErrorHistory->addNote(GLErrorHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->cents));
556                         m_glVolumeHistory->addNote(GLVolumeHistory::Note(m_microtonalView->setting_selectedRoot, m_microtonalView->m_selected_jivalue->cents));
557                 }
558         }
559         else
560         {
561                 ui_txtNoteFreq->display(m_compared_freq);
562                 ui_txtNote->setText(QString::fromStdString(h2n(f2h(m_compared_freq))));
563                 m_glErrorHistory->addNote(GLErrorHistory::Note(f2h(m_compared_freq)));
564                 m_glVolumeHistory->addNote(GLVolumeHistory::Note(f2h(m_compared_freq)));
565         }
566 }
567 void CustomInstrumentTunerForm::noteFinished(double freq, double dt)
568 {
569         m_compared_freq = 0.0;
570 //      cerr << "CustomInstrumentTunerForm::noteFinished " << freq << "," << dt << endl;
571 }
572
573 void CustomInstrumentTunerForm::refresh_data_sample()
574 {
575         if(m_freq==0.0f || !m_glSample->setting_show->isChecked())
576         {
577                 m_glSample->clear();
578                 return;
579         }
580
581         deque<double> sample;
582         GetWaveSample(m_queue, size_t(m_capture_thread.getSamplingRate()/m_freq), sample);
583         m_glSample->add(m_time.elapsed(), sample);
584 }
585
586 void CustomInstrumentTunerForm::refresh_data_harmonics()
587 {
588         if(!(m_algo_combedfft!=NULL &&
589                         m_freq>0.0f &&
590                         m_glFreqStruct->setting_show->isChecked()))
591                 return;
592
593         vector<Harmonic> harms = GetHarmonicStruct(m_algo_combedfft->m_plan.out, m_freq, m_glFreqStruct->m_components.size(), m_algo_combedfft->getZeroPaddingFactor());
594
595         m_glFreqStruct->m_components_max = 0.0;
596         for(int i=0; i<harms.size(); i++)
597         {
598                 if(harms[i].harm_number<m_glFreqStruct->m_components.size())
599                 {
600                         m_glFreqStruct->m_components[harms[i].harm_number-1] = 20*log10(harms[i].mod/0.001);
601
602                         m_glFreqStruct->m_components_max = max(m_glFreqStruct->m_components_max, m_glFreqStruct->m_components[i]);
603                 }
604         }
605 }
606
607 void CustomInstrumentTunerForm::refresh_views()
608 {
609 //      cerr << "CustomInstrumentTunerForm::refresh_views " << endl;
610
611 //      m_dialTune->repaint();
612
613         if(m_glGraph->setting_show->isChecked())
614                 m_glGraph->updateGL();
615
616         if(m_glErrorHistory->setting_show->isChecked())
617                 m_glErrorHistory->updateGL();
618
619         if(m_glVolumeHistory->setting_show->isChecked())
620                 m_glVolumeHistory->updateGL();
621
622         if(m_microtonalView->setting_show->isChecked())
623                 m_microtonalView->update();
624
625         if(m_glSample->setting_show->isChecked())
626                 m_glSample->updateGL();
627
628         if(m_glFreqStruct->setting_show->isChecked())
629                 m_glFreqStruct->updateGL();
630
631         if(m_glFT->setting_show->isChecked())
632                 m_glFT->updateGL();
633
634         if(m_glStatistics->setting_show->isChecked())
635                 m_glStatistics->updateGL();
636
637         m_time_refresh_views.start();
638 }
639
640 void CustomInstrumentTunerForm::keyPressEvent(QKeyEvent * e)
641 {
642         if(e->key()==Qt::Key_F)
643                 toggleFullScreen();
644 }
645
646 void CustomInstrumentTunerForm::resizeEvent(QResizeEvent* e)
647 {
648         update_views();
649 }
650
651 void CustomInstrumentTunerForm::update_views()
652 {
653         if(     !m_glGraph->setting_show->isChecked() &&
654                 !m_glErrorHistory->setting_show->isChecked() &&
655                 !m_glVolumeHistory->setting_show->isChecked() &&
656                 !m_glSample->setting_show->isChecked() &&
657                 !m_glFreqStruct->setting_show->isChecked() &&
658                 !m_glFT->setting_show->isChecked())
659                         m_dialTune->setMaximumWidth(size().width());
660         else
661                 m_dialTune->setMaximumWidth(ui_rightLayout->minimumSize().width());
662
663         if(m_glGraph->setting_show->isChecked() &&
664                         !m_glErrorHistory->setting_show->isChecked() &&
665                         !m_glVolumeHistory->setting_show->isChecked() &&
666                         !m_glSample->setting_show->isChecked() &&
667                         !m_glFreqStruct->setting_show->isChecked() &&
668                         !m_glFT->setting_show->isChecked())
669                 m_glGraph->setMaximumHeight(size().height());
670         else
671                 m_glGraph->setMaximumHeight(m_glGraph->setting_spinMaxHeight->value());
672
673         if(!m_glErrorHistory->setting_show->isChecked() && !m_glVolumeHistory->setting_show->isChecked())
674                 ui_btnKeepErrorHistory->hide();
675         else
676                 ui_btnKeepErrorHistory->show();
677 }
678
679 void CustomInstrumentTunerForm::configure()
680 {
681     // Checking PortAudio devices while capturing using ALSA seems to crash ALSA
682     // thus, stop everything while configuring and capture again when exiting the configuration panel
683     m_capture_thread.stopCapture();
684     
685         noteRangeChanged();
686
687         if(m_capture_thread.getCurrentTransportIndex()<m_config_form.ui_cbTransports->count());
688                 m_config_form.ui_cbTransports->setCurrentIndex(m_capture_thread.getCurrentTransportIndex());
689
690 #ifdef CAPTURE_JACK
691         // TODO set descr
692         m_config_form.ui_grpJACK->setTitle(m_capture_thread.getTransport("JACK")->getDescription());
693         m_config_form.ui_lblJACKSamplingRate->setText(QString::number(m_capture_thread.getSamplingRate()));
694 #endif
695
696 #ifdef CAPTURE_PORTAUDIO
697     m_config_form.ui_grpPortAudio->setTitle(m_capture_thread.getTransport("PortAudio")->getDescription());
698         m_config_form.ui_spinPortAudioSamplingRate->setValue(m_capture_thread.getSamplingRate());
699         if(m_capture_thread.getTransport("PortAudio"))
700         {
701                 try
702                 {
703                         PaError err;
704                         err = Pa_Initialize();
705                         if(err != paNoError)
706                                 throw QString("PortAudio: CustomInstrumentTunerForm::configure:Pa_Initialize ")+Pa_GetErrorText(err);
707                         int     numDevices = Pa_GetDeviceCount();
708                         int current_index = -1;
709                         m_config_form.ui_cbPortAudioDeviceName->clear();
710                         const PaDeviceInfo* deviceInfo;
711                         for(int i=0; i<numDevices; i++)
712                         {
713                                 deviceInfo = Pa_GetDeviceInfo(i);
714                                 m_config_form.ui_cbPortAudioDeviceName->addItem(QString(deviceInfo->name));
715                                 if(QString(deviceInfo->name)==m_capture_thread.getTransport("PortAudio")->getSource())
716                                         current_index = i;
717                         }
718             if(current_index!=-1)
719                 m_config_form.ui_cbPortAudioDeviceName->setCurrentIndex(current_index);
720                 }
721                 catch(QString error)
722                 {
723                         cerr << "CustomInstrumentTunerForm: ERROR: " << error.toStdString() << endl;
724                 }
725                 Pa_Terminate();
726         }
727 #endif
728
729 #ifdef CAPTURE_ALSA
730         m_config_form.ui_grpALSA->setTitle(m_capture_thread.getTransport("ALSA")->getDescription());
731         m_config_form.ui_spinALSASamplingRate->setValue(m_capture_thread.getSamplingRate());
732 #endif
733 #ifdef CAPTURE_OSS
734         m_config_form.ui_grpOSS->setTitle(m_capture_thread.getTransport("OSS")->getDescription());
735         m_config_form.ui_spinOSSSamplingRate->setValue(m_capture_thread.getSamplingRate());
736 #endif
737
738     m_config_form.adjustSize();
739         m_config_form.show();
740 }
741 void CustomInstrumentTunerForm::configure_cancel()
742 {
743     if(!pauseAction->isChecked() && !m_capture_thread.isCapturing())
744         m_capture_thread.startCapture();
745 }
746 void CustomInstrumentTunerForm::configure_ok()
747 {
748     switch(m_config_form.ui_cbTuning->currentIndex())
749         {
750         case 0:
751                 SetTuning(CHROMATIC);
752                 break;
753         case 1:
754                 SetTuning(WERCKMEISTER3);
755                 break;
756         case 2:
757                 SetTuning(KIRNBERGER3);
758                 break;
759         case 3:
760                 SetTuning(DIATONIC);
761                 break;
762         case 4:
763                 SetTuning(MEANTONE);
764                 break;
765         default:
766                 SetTuning(CHROMATIC);
767                 break;
768         }
769
770         if(m_config_form.ui_cbTonality->currentIndex()==0)              SetTonality(0);
771         else if(m_config_form.ui_cbTonality->currentIndex()==1) SetTonality(+2);
772         else                                                                                                    SetTonality(-3);
773
774         if(m_config_form.ui_cbNotesName->currentIndex()==0)             SetNotesName(LOCAL_ANGLO);
775         else                                                                                                    SetNotesName(LOCAL_LATIN);
776         m_microtonalView->notesNameChanged();
777         m_microtonalView->setAFreq(Music::GetAFreq());
778
779         SetSemitoneBounds(m_config_form.ui_spinMinHT->value(), m_config_form.ui_spinMaxHT->value());
780
781         ui_spinA3Offset->setShown(m_config_form.ui_chkShowA4Offset->isChecked());
782         ui_lblA3Offset->setShown(m_config_form.ui_chkShowA4Offset->isChecked());
783
784         //      if(m_note!=-1000)
785         //              ui_txtNote->setText(h2n(m_note));
786
787         //      m_dialTune->setError(-10.0f);
788
789 //      cerr << "b" << endl;
790
791         // Capture
792 #ifdef CAPTURE_ALSA
793         if(m_config_form.ui_cbTransports->currentText()=="ALSA")
794         {
795                 m_capture_thread.selectTransport("ALSA");
796                 m_capture_thread.setSource(m_config_form.ui_txtALSAPCMName->text());
797                 if(m_config_form.ui_chkALSASamplingRateMax->isChecked())
798                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
799                 else
800                         m_capture_thread.setSamplingRate(m_config_form.ui_spinALSASamplingRate->value());
801         }
802 #endif
803 #ifdef CAPTURE_JACK
804         if(m_config_form.ui_cbTransports->currentText()=="JACK")
805         {
806                 m_capture_thread.selectTransport("JACK");
807                 if(m_config_form.ui_chkJACKAutoConnect->isChecked())
808                         m_capture_thread.setSource(m_config_form.ui_txtJACKSourcePort->text());
809                 else
810                         m_capture_thread.setSource("");
811                 m_config_form.ui_lblJACKSamplingRate->setText(QString::number(m_capture_thread.getSamplingRate()));
812         }
813 #endif
814 #ifdef CAPTURE_PORTAUDIO
815         if(m_config_form.ui_cbTransports->currentText()=="PortAudio")
816         {
817                 m_capture_thread.selectTransport("PortAudio");
818                 m_capture_thread.setSource(m_config_form.ui_cbPortAudioDeviceName->currentText());
819                 if(m_config_form.ui_chkPortAudioSamplingRateMax->isChecked())
820                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
821                 else
822                         m_capture_thread.setSamplingRate(m_config_form.ui_spinPortAudioSamplingRate->value());
823         }
824 #endif
825 #ifdef CAPTURE_OSS
826         if(m_config_form.ui_cbTransports->currentText()=="OSS")
827         {
828                 m_capture_thread.selectTransport("OSS");
829                 m_capture_thread.setSource(m_config_form.ui_txtOSSPCMName->text());
830                 if(m_config_form.ui_chkOSSSamplingRateMax->isChecked())
831                         m_capture_thread.setSamplingRate(CaptureThread::SAMPLING_RATE_MAX);
832                 else
833                         m_capture_thread.setSamplingRate(m_config_form.ui_spinOSSSamplingRate->value());
834         }
835 #endif
836         m_timer_refresh.setInterval(m_config_form.ui_spinRefreshTime->value());
837
838         // Views
839         m_glGraph->m_treshold = invlp(m_config_form.ui_spinVolumeTreshold->value());
840         m_glGraph->clearValues();
841
842 //      cerr << "c" << endl;
843
844         if(m_config_form.ui_grpRangeFiltering->isChecked())
845         {
846 //         cout << "set filters " << GetSamplingRate() << endl;
847 //         m_rect_range_filter.reset(int(GetSamplingRate()/6000.0));
848                 m_rect_range_filter.reset(int(GetSamplingRate()/h2f(GetSemitoneMin())));
849 //         m_fir_range_filter.setImpulseResponse(fir1_bandpass(104, 2*1000.0/GetSamplingRate(), 2*2000.0/GetSamplingRate()));
850 //              m_fir_range_filter.setImpulseResponse(fir1_highpass(int(GetSamplingRate()/h2f(GetSemitoneMin())), 0.5));
851
852                 if(m_config_form.ui_rdRangeFilteringRectangular->isChecked())
853                         m_range_filter = &m_rect_range_filter;
854                 else if(m_config_form.ui_rdRangeFilteringFIR->isChecked())
855                         m_range_filter = &m_fir_range_filter;
856         }
857         else
858                 m_range_filter = &m_dummy_range_filter;
859
860         m_algo_combedfft->setWindowFactor(m_config_form.ui_spinWindowSizeFactor->value());
861 //      m_glFT->m_zp_factor = m_config_form.ui_spinWindowSizeFactor->value();
862         m_algo_combedfft->useAudibilityRatio(m_config_form.ui_chkAlgoUseSubHarmTresh->isChecked());
863         m_algo_combedfft->setAudibilityRatio(invlp(-m_config_form.ui_spinCombedFFTAudibilityRatio->value()));
864         m_algo_combedfft->setAmplitudeTreshold(invlp(double(m_config_form.ui_spinVolumeTreshold->value())));
865         m_algo_combedfft->setComponentTreshold(invlp(double(m_config_form.ui_spinVolumeTreshold->value())));
866
867 //      cerr << "d" << endl;
868
869         // Quantizers
870         m_quantizer->reset();
871         if(m_config_form.ui_grpQuantizer->isChecked())
872         {
873                 m_latency_quantizer.setLatency(m_config_form.ui_spinErrorLatency->value());
874                 m_quantizer = &m_latency_quantizer;
875         }
876         else
877                 m_quantizer = &m_dummy_quantizer;
878
879 //      cerr << invlp(-m_config_form.ui_spinCombedFFTAudibilityRatio->value()) << endl;
880
881         if(!pauseAction->isChecked() && !m_capture_thread.isCapturing())
882                 m_capture_thread.startCapture();
883 }
884
885 void CustomInstrumentTunerForm::saveSettings()
886 {
887         m_settings.save();
888         View::saveAll();
889
890         // views
891         m_settings.setValue("width", width());
892         m_settings.setValue("height", height());
893         //m_settings.setValue("ui_tbViews", QVariant(ui_tbViews->isVisible()));
894         //m_settings.setValue("ui_tbButtons", ui_tbButtons->isVisible());
895
896         // sound capture
897         m_settings.setValue(m_config_form.ui_cbTransports->objectName(), m_config_form.ui_cbTransports->currentText());
898 #ifdef CAPTURE_PORTAUDIO
899         m_settings.setValue(m_config_form.ui_cbPortAudioDeviceName->objectName(), m_config_form.ui_cbPortAudioDeviceName->currentText());
900 #endif
901 }
902 void CustomInstrumentTunerForm::loadSettings()
903 {
904 //     cerr << __FILE__ << ":" << __LINE__ << endl;
905
906         m_settings.load();
907         View::loadAll();
908
909         // views
910         resize(m_settings.value("width", 800).toInt(), m_settings.value("height", 600).toInt());
911         //ui_tbViews->setVisible(m_settings.value("ui_tbViews", ui_tbViews->isVisible()).toBool());
912         //ui_tbButtons->setVisible(m_settings.value("ui_tbButtons", ui_tbButtons->isVisible()).toBool());
913
914         // sound capture
915         QString saved_transport = m_settings.value(m_config_form.ui_cbTransports->objectName(), "").toString();
916         if(saved_transport!="")
917                 for(int i=0; i<m_config_form.ui_cbTransports->count(); i++)
918                         if(m_config_form.ui_cbTransports->itemText(i)==saved_transport)
919                                 m_config_form.ui_cbTransports->setCurrentIndex(i);
920
921 #ifdef CAPTURE_PORTAUDIO
922         QString saved_device = m_settings.value(m_config_form.ui_cbPortAudioDeviceName->objectName(), "default").toString();
923         try
924         {
925                 PaError err;
926                 err = Pa_Initialize();
927                 if(err != paNoError)
928                         throw QString("PortAudio: CustomInstrumentTunerForm::loadSettings:Pa_Initialize ")+Pa_GetErrorText(err);
929                 int     numDevices = Pa_GetDeviceCount();
930 //         cerr << "PortAudio devices:"<< endl;
931         int saved_index = -1;
932                 m_config_form.ui_cbPortAudioDeviceName->clear();
933                 const PaDeviceInfo* deviceInfo;
934                 for(int i=0; i<numDevices; i++)
935                 {
936                         deviceInfo = Pa_GetDeviceInfo(i);
937 //             cerr << "    " << QString(deviceInfo->name).toStdString() << endl;
938                         m_config_form.ui_cbPortAudioDeviceName->addItem(QString(deviceInfo->name));
939                         if(QString(deviceInfo->name)==saved_device)
940                                 saved_index = i;
941                 }
942                 if(saved_index!=-1)
943                         m_config_form.ui_cbPortAudioDeviceName->setCurrentIndex(saved_index);
944     }
945         catch(QString error)
946         {
947 //              cerr << "CustomInstrumentTunerForm: ERROR: " << error << endl;
948         }
949         Pa_Terminate();
950 #endif
951 }
952
953 void CustomInstrumentTunerForm::restoreFactorySettings()
954 {
955         if(QMessageBox::question(this, tr("Restore Factory Settings"), tr("This operation is NOT reversible.\nAre you sure you want to lose all your current settings ?"), QMessageBox::Yes, QMessageBox::No)==QMessageBox::Yes)
956         {
957                 m_settings.clear();
958                 View::clearAllSettings();
959
960                 m_settings.remove("width");
961                 m_settings.remove("height");
962         m_settings.remove("ui_cbTransports");
963                 //m_settings.remove("ui_tbViews");
964                 //m_settings.remove("ui_tbButtons");
965
966                 m_settings.remove(m_config_form.ui_cbPortAudioDeviceName->objectName());
967
968                 QMessageBox::information(this, tr("Restore Factory Settings"), tr("You can now restart FMIT to get back factory settings"), QMessageBox::Ok, QMessageBox::NoButton);
969         }
970 }
971
972 void CustomInstrumentTunerForm::helpAbout()
973 {
974         QString text;
975         text = "<h2>Free Music Instrument Tuner</h2>";
976         text += tr("<h3>Version ")+PACKAGE_VERSION;
977         text += tr("</h3><p><h3>Website:</h3><p>homepage: <a href=\"http://home.gna.org/fmit\">http://home.gna.org/fmit</a>");
978         text += tr("<p>development site: <a href=\"http://gna.org/projects/fmit\">http://gna.org/projects/fmit</a>");
979         text += tr("<p>donation link: <a href=\"http://home.gna.org/fmit/donation.html\">http://home.gna.org/fmit/donation.html</a>");
980         text += tr("<p><h3>Author:</h3><p>Gilles Degottex <a href=\"mailto:gilles.degottex@gmail.com\">gilles.degottex@gmail.com</a>");
981 #ifdef PACKAGER_STRING
982         if(PACKAGER_STRING!="")
983                 text += tr("<p><h3>Packager:</h3><p>")+QString(PACKAGER_STRING).replace(QChar('<'),"[").replace(QChar('>'),"]");
984 #endif
985
986         QDialog about_dlg(this);
987
988         QTextBrowser* textBrowser1;
989         QPushButton* pushButton1;
990         QVBoxLayout* Form2Layout;
991         QHBoxLayout* layout1;
992         QSpacerItem* spacer1;
993         QSpacerItem* spacer2;
994
995         about_dlg.setObjectName( tr("about_box") );
996         about_dlg.setWindowTitle( tr("About Free Music Instrument Tuner") );
997
998         Form2Layout = new QVBoxLayout( &about_dlg );
999         Form2Layout->setMargin(11);
1000         Form2Layout->setSpacing(6);
1001         about_dlg.setLayout( Form2Layout );
1002
1003         textBrowser1 = new QTextBrowser( &about_dlg );
1004         textBrowser1->setHtml(text);
1005         textBrowser1->setOpenExternalLinks(true);
1006         textBrowser1->setWordWrapMode(QTextOption::NoWrap);
1007         textBrowser1->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1008         textBrowser1->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1009         textBrowser1->document()->adjustSize();
1010         textBrowser1->setMinimumSize(textBrowser1->document()->size().toSize() + QSize(10, 10));
1011         Form2Layout->addWidget( textBrowser1 );
1012
1013         layout1 = new QHBoxLayout();
1014         layout1->setMargin(0);
1015         layout1->setSpacing(6);
1016
1017         spacer1 = new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
1018         layout1->addItem( spacer1 );
1019
1020         pushButton1 = new QPushButton(&about_dlg);
1021         pushButton1->setText( tr( "&OK" ) );
1022         layout1->addWidget( pushButton1 );
1023         connect(pushButton1, SIGNAL(clicked()), &about_dlg, SLOT(close()));
1024
1025         spacer2 = new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
1026         layout1->addItem( spacer2 );
1027
1028         Form2Layout->addLayout( layout1 );
1029
1030         about_dlg.exec();
1031 }
1032
1033 CustomInstrumentTunerForm::~CustomInstrumentTunerForm()
1034 {
1035         if(m_config_form.ui_chkAutoSaveOnExit->isChecked())
1036                 saveSettings();
1037         else
1038         {
1039                 m_settings.beginGroup("Auto/");
1040                 m_settings.save(m_config_form.ui_chkAutoSaveOnExit);
1041                 m_settings.endGroup();
1042         }
1043 }
1044
1045 /*
1046 bool displayInBrowser(const QString& theURL)
1047 {
1048 #ifdef _WIN32
1049         //TODO replace with less buggy ShellExecuteEx?
1050         if ((unsigned
1051 int)::ShellExecute(qApp->mainWidget()->winId(),NULL,theURL,NULL,NULL,SW_SHOW)
1052 <= 32)
1053 {
1054                 OFMessageBox::criticalMessageOK(QMessageBox::tr("Unable to display a
1055 web browser. Ensure that you have a web browser installed."));
1056 }
1057 #else
1058         //TODO warn if netscape not installed
1059         QString aCommand("netscape ");
1060         aCommand += theURL;
1061         aCommand += " &";
1062     if (system(aCommand) != 0)
1063 {
1064                 OFMessageBox::criticalMessageOK(QMessageBox::tr("Unable to display a
1065 netscape browser. You need to have netscape installed and in the
1066 path."));
1067 }
1068
1069 return true;
1070 }
1071 */