1 /******************************************************************************
2  * src/WMain.cpp
3  *
4  * Implementation of the main window's functions.
5  *
6  ******************************************************************************
7  * Copyright (C) 2013-2014 Timo Bingmann <tb@panthema.net>
8  *
9  * This program is free software: you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the Free
11  * Software Foundation, either version 3 of the License, or (at your option)
12  * any later 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
16  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
17  * more details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  *****************************************************************************/
22 
23 #include "WMain.h"
24 #include "SortAlgo.h"
25 #include <SDL.h>
26 
27 #include "wxg/WAbout_wxg.h"
28 
WMain(wxWindow * parent)29 WMain::WMain(wxWindow* parent)
30     : WMain_wxg(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE)
31 {
32     m_thread = NULL;
33     g_sound_on = false;
34 
35     recordButton->Hide();
36     panelQuickSortPivot->Hide();
37     infoTextctrl->Hide();
38 
39     // program icon
40     {
41         #include "sos.xpm"
42 	SetIcon( wxIcon(sos) );
43     }
44 
45     // program version
46     SetTitle(_("The Sound of Sorting " PACKAGE_VERSION " - http://panthema.net/2013/sound-of-sorting"));
47 
48     // resize right split window
49     splitter_0->SetSashPosition(GetSize().x - 280);
50 
51     // insert list of algorithms into wxListBox
52     for (const AlgoEntry* ae = g_algolist; ae != g_algolist_end; ++ae)
53         algoList->Append(ae->name);
54 
55     algoList->SetSelection(0);
56 
57     // insert list of data templates into wxChoice
58     wxArrayString inputList;
59     sortview->m_array.FillInputlist(inputList);
60     inputTypeChoice->Append(inputList);
61 
62     sortview->m_array.FillData(0, 100);
63     inputTypeChoice->SetSelection(0);
64     arraySizeSlider->SetValue(100);
65     SetArraySize(100);
66 
67     // insert quicksort pivot rules into wxChoice
68     pivotRuleChoice->Append( QuickSortPivotText() );
69     pivotRuleChoice->SetSelection(0);
70 
71     // set default speed
72     speedSlider->SetValue(1000);
73     SetDelay(1000);
74 
75     // set default sound sustain
76     soundSustainSlider->SetValue(700);
77     SetSoundSustain(700);
78 
79     // create audio output
80     SDL_AudioSpec sdlaudiospec;
81 
82     // Set the audio format
83     sdlaudiospec.freq = 44100;
84     sdlaudiospec.format = AUDIO_S16SYS;
85     sdlaudiospec.channels = 1;    	/* 1 = mono, 2 = stereo */
86     sdlaudiospec.samples = 4096;  	/* Good low-latency value for callback */
87     sdlaudiospec.callback = SoundCallback;
88     sdlaudiospec.userdata = sortview;
89 
90     // Open the audio device, forcing the desired format
91     if ( SDL_OpenAudio(&sdlaudiospec, NULL) < 0 ) {
92         wxLogError(_("Couldn't open audio: ") + wxString(SDL_GetError(), wxConvISO8859_1));
93         soundButton->Disable();
94     }
95     soundButton->SetValue(false);
96 
97     Show();
98 
99     // start refreshing timer
100     m_reftimer = new RefreshTimer(this);
101     m_reftimer->Start(1000 / g_framerate);
102 }
103 
~WMain()104 WMain::~WMain()
105 {
106     AbortAlgorithm();
107 
108     SDL_CloseAudio();
109 }
110 
111 BEGIN_EVENT_TABLE(WMain, WMain_wxg)
112 
113     EVT_TOGGLEBUTTON(ID_RUN_BUTTON, WMain::OnRunButton)
114     EVT_BUTTON(ID_RESET_BUTTON, WMain::OnResetButton)
115     EVT_BUTTON(ID_STEP_BUTTON, WMain::OnStepButton)
116     EVT_TOGGLEBUTTON(ID_SOUND_BUTTON, WMain::OnSoundButton)
117     EVT_BUTTON(ID_RANDOM_BUTTON, WMain::OnRandomButton)
118     EVT_BUTTON(wxID_ABOUT, WMain::OnAboutButton)
119 
120     EVT_COMMAND_SCROLL(ID_SPEED_SLIDER, WMain::OnSpeedSliderChange)
121     EVT_COMMAND_SCROLL(ID_SOUND_SUSTAIN_SLIDER, WMain::OnSoundSustainSliderChange)
122     EVT_COMMAND_SCROLL(ID_ARRAY_SIZE_SLIDER, WMain::OnArraySizeSliderChange)
123     EVT_LISTBOX(ID_ALGO_LIST, WMain::OnAlgoList)
124     EVT_LISTBOX_DCLICK(ID_ALGO_LIST, WMain::OnAlgoListDClick)
125 
126     EVT_COMMAND(ID_RUN_FINISHED, wxEVT_COMMAND_BUTTON_CLICKED, WMain::OnRunFinished)
127 
128     EVT_COMMAND(ID_INVERSION_LABEL, wxEVT_COMMAND_BUTTON_CLICKED, WMain::OnInversionLabelClick)
129 
130 END_EVENT_TABLE();
131 
RunAlgorithm()132 bool WMain::RunAlgorithm()
133 {
134     ASSERT(!m_thread);
135 
136     if (algoList->GetSelection() == wxNOT_FOUND)
137     {
138         wxLogError(_("Please select a sorting algorithm"));
139         runButton->SetValue(false);
140         return false;
141     }
142     else
143     {
144         if (sortview->m_array.IsSorted())
145             sortview->m_array.FillData( inputTypeChoice->GetSelection(), m_array_size );
146 
147         sortview->SetStepwise(false);
148 
149         g_algo_name = algoList->GetStringSelection();
150         g_quicksort_pivot = (QuickSortPivotType)pivotRuleChoice->GetSelection();
151 
152         m_thread = new SortAlgoThread(this, *sortview, algoList->GetSelection());
153 
154         m_thread_terminate = false;
155         m_thread->Create();
156 
157         g_algo_running = true;
158         m_thread->Run();
159 
160         runButton->SetValue(true);
161         return true;
162     }
163 }
164 
AbortAlgorithm()165 void WMain::AbortAlgorithm()
166 {
167     if (!m_thread) return;
168 
169     m_thread_terminate = true;
170     if (m_thread->IsPaused()) m_thread->Resume();
171     sortview->SetStepwise(false);
172 
173     m_thread->Wait();
174     g_algo_running = false;
175 
176     delete m_thread;
177     m_thread = NULL;
178 }
179 
OnRunButton(wxCommandEvent & event)180 void WMain::OnRunButton(wxCommandEvent &event)
181 {
182     // join finished thread
183     if (m_thread && !m_thread->IsAlive())
184     {
185         m_thread->Wait();
186         g_algo_running = false;
187 
188         delete m_thread;
189         m_thread = NULL;
190     }
191 
192     if (event.IsChecked())
193     {
194         if (!m_thread)
195         {
196             RunAlgorithm();
197         }
198         else
199         {
200             g_algo_running = true;
201             sortview->SetStepwise(false);
202             if (m_thread->IsPaused()) m_thread->Resume();
203         }
204     }
205     else
206     {
207         if (m_thread)
208         {
209             m_thread->Pause();
210             g_algo_running = false;
211         }
212     }
213 }
214 
OnRunFinished(wxCommandEvent &)215 void WMain::OnRunFinished(wxCommandEvent&)
216 {
217     // join finished thread
218     if (m_thread)
219     {
220         m_thread->Wait();
221         g_algo_running = false;
222 
223         delete m_thread;
224         m_thread = NULL;
225     }
226 
227     runButton->SetValue(false);
228 }
229 
OnResetButton(wxCommandEvent &)230 void WMain::OnResetButton(wxCommandEvent&)
231 {
232     // terminate running algorithm.
233     AbortAlgorithm();
234 
235     runButton->SetValue(false);
236 
237     sortview->m_array.FillData( inputTypeChoice->GetSelection(), m_array_size );
238 }
239 
OnStepButton(wxCommandEvent &)240 void WMain::OnStepButton(wxCommandEvent&)
241 {
242     // lock thread to one step
243     if (!m_thread)
244     {
245         if (!RunAlgorithm()) return;
246         sortview->SetStepwise(true);
247     }
248     else
249     {
250         if (m_thread->IsPaused()) m_thread->Resume();
251         sortview->SetStepwise(true);    // in case not already set
252         runButton->SetValue(false);
253         g_algo_running = true;
254     }
255 
256     // signal to perform one step
257     sortview->DoStepwise();
258 }
259 
OnSoundButton(wxCommandEvent &)260 void WMain::OnSoundButton(wxCommandEvent&)
261 {
262     if (soundButton->GetValue())
263     {
264         SoundReset();
265         SDL_PauseAudio(0);
266         g_sound_on = true;
267     }
268     else
269     {
270         g_sound_on = false;
271         SDL_PauseAudio(1);
272     }
273 }
274 
OnRandomButton(wxCommandEvent &)275 void WMain::OnRandomButton(wxCommandEvent&)
276 {
277     AbortAlgorithm();
278 
279     algoList->SetSelection( rand() % algoList->GetCount() );
280     sortview->m_array.FillData( inputTypeChoice->GetSelection(), m_array_size );
281 
282     RunAlgorithm();
283 
284     algoList->SetSelection(wxNOT_FOUND);
285 }
286 
287 class WAbout : public WAbout_wxg
288 {
289 public:
WAbout(wxWindow * parent)290     WAbout(wxWindow* parent)
291         : WAbout_wxg(parent, wxID_ANY, wxEmptyString)
292     {
293         labelTitle->SetLabel(_("The Sound of Sorting " PACKAGE_VERSION));
294         labelBuildDate->SetLabel(_("Build Date: " __DATE__));
295 
296         GetSizer()->Fit(this);
297         Layout();
298         Centre();
299     }
300 };
301 
OnAboutButton(wxCommandEvent &)302 void WMain::OnAboutButton(wxCommandEvent&)
303 {
304     WAbout dlg(this);
305     dlg.ShowModal();
306 }
307 
OnSpeedSliderChange(wxScrollEvent & event)308 void WMain::OnSpeedSliderChange(wxScrollEvent &event)
309 {
310     SetDelay(event.GetPosition());
311 }
312 
SetDelay(size_t pos)313 void WMain::SetDelay(size_t pos)
314 {
315     // scale slider with this base to 10 seconds = 2 * 1000 ms
316     const double base = 4;
317 
318     // different slider scale for Linux/GTK: (faster)
319 #if __WXGTK__ || MSW_PERFORMANCECOUNTER
320     g_delay = pow(base, pos / 2000.0 * log(2 * 1000.0 * 10.0) / log(base)) / 10.0;
321 #else
322     // other systems probably have sucking real-time performance anyway
323     g_delay = pow(base, pos / 2000.0 * log(2 * 1000.0) / log(base));
324 #endif
325     if (pos == 0) g_delay = 0;
326 
327     if (g_delay > 10)
328         labelDelayValue->SetLabel(wxString::Format(_("%.0f ms"), g_delay));
329     else if (g_delay > 1)
330         labelDelayValue->SetLabel(wxString::Format(_("%.1f ms"), g_delay));
331     else
332         labelDelayValue->SetLabel(wxString::Format(_("%.2f ms"), g_delay));
333 }
334 
OnSoundSustainSliderChange(wxScrollEvent & event)335 void WMain::OnSoundSustainSliderChange(wxScrollEvent &event)
336 {
337     SetSoundSustain(event.GetPosition());
338 }
339 
OnInversionLabelClick(wxCommandEvent &)340 void WMain::OnInversionLabelClick(wxCommandEvent &)
341 {
342     sortview->m_array.ToggleCalcInversions();
343 }
344 
SetSoundSustain(size_t pos)345 void WMain::SetSoundSustain(size_t pos)
346 {
347     // scale slider with this base to 50
348     const double base = 4;
349 
350     g_sound_sustain = pow(base, pos / 2000.0 * log(50) / log(base));
351 
352     labelSoundSustainValue->SetLabel(wxString::Format(_("%.1f"), g_sound_sustain));
353 
354     labelSoundSustainValue->GetContainingSizer()->Layout();
355 }
356 
OnArraySizeSliderChange(wxScrollEvent & event)357 void WMain::OnArraySizeSliderChange(wxScrollEvent &event)
358 {
359     SetArraySize(event.GetPosition());
360 }
361 
SetArraySize(size_t pos)362 void WMain::SetArraySize(size_t pos)
363 {
364     // scale slider with this base to 2048
365     //const double base = 2;
366     //m_array_size = pow(base, pos / 10000.0 * log(2048) / log(base));
367     m_array_size = pos;
368 
369     labelArraySizeValue->SetLabel(wxString::Format(_("%4lu"), (long unsigned)m_array_size));
370 
371     labelArraySizeValue->GetContainingSizer()->Layout();
372 }
373 
OnAlgoList(wxCommandEvent &)374 void WMain::OnAlgoList(wxCommandEvent&)
375 {
376     int sel = algoList->GetSelection();
377     wxString text;
378 
379     bool isQuickSort = (algoList->GetStringSelection().Contains(_("Quick Sort")));
380     panelQuickSortPivot->Show(isQuickSort);
381 
382     if (sel >= 0 && sel < (int)g_algolist_size && !g_algolist[sel].text.IsEmpty())
383     {
384         text = g_algolist[sel].text;
385 
386         infoTextctrl->SetValue(text);
387 
388         infoTextctrl->Show();
389         infoTextctrl->GetContainingSizer()->Layout();
390     }
391     else
392     {
393         infoTextctrl->Hide();
394         infoTextctrl->GetContainingSizer()->Layout();
395     }
396 }
397 
OnAlgoListDClick(wxCommandEvent &)398 void WMain::OnAlgoListDClick(wxCommandEvent&)
399 {
400     // terminate running algorithm.
401     if (m_thread)
402     {
403         AbortAlgorithm();
404 
405         sortview->m_array.FillData( inputTypeChoice->GetSelection(), m_array_size );
406     }
407 
408     // start new one
409     RunAlgorithm();
410 }
411 
412 // ----------------------------------------------------------------------------
413 
RefreshTimer(WMain * wmain)414 WMain::RefreshTimer::RefreshTimer(WMain* wmain)
415     : wm(*wmain)
416 {
417 }
418 
Notify()419 void WMain::RefreshTimer::Notify()
420 {
421     // update some labels with current values
422     long int accesses = g_access_count;
423     wm.labelAccessCount->SetLabel(wxString::Format(_("%ld"), accesses));
424 
425     long int compares = g_compare_count;
426     wm.labelComparisonsValue->SetLabel(wxString::Format(_("%ld"), compares));
427 
428     long int inversions = wm.sortview->m_array.GetInversions();
429     if (inversions >= 0)
430         wm.labelInversionCount->SetLabel(wxString::Format(_("%ld"), inversions));
431     else
432         wm.labelInversionCount->SetLabel(_("skipped"));
433 
434     long int runs = wm.sortview->m_array.GetRuns();
435     wm.labelRunsCount->SetLabel(wxString::Format(_("%ld"), runs));
436 
437     // repaint sortview
438     wm.sortview->Refresh(false);
439 }
440 
441 // ----------------------------------------------------------------------------
442 
443 class MyApp : public wxApp
444 {
445 public:
446 
OnInit()447     virtual bool OnInit()
448     {
449         if (SDL_Init(SDL_INIT_AUDIO) < 0)
450         {
451             wxLogError(wxT("Couldn't initialize SDL: ") + wxString(SDL_GetError(), wxConvISO8859_1));
452             wxLog::FlushActive();
453             return false;
454         }
455 
456         srand((int)wxGetLocalTime());
457 
458         WMain* wmain = new WMain(NULL);
459         SetTopWindow(wmain);
460         wmain->Show();
461 
462         return true;
463     }
464 
OnExit()465     virtual int OnExit()
466     {
467         SDL_Quit();
468 
469         // return the standard exit code
470         return wxApp::OnExit();
471     }
472 };
473 
474 IMPLEMENT_APP(MyApp)
475 
476 // ----------------------------------------------------------------------------
477