1 /* spek-window.cc
2  *
3  * Copyright (C) 2010-2013  Alexander Kojevnikov <alexander@kojevnikov.com>
4  *
5  * Spek 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 3 of the License, or
8  * (at your option) any later version.
9  *
10  * Spek 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 Spek.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <wx/aboutdlg.h>
20 #include <wx/artprov.h>
21 #include <wx/dnd.h>
22 #include <wx/filename.h>
23 #include <wx/protocol/http.h>
24 #include <wx/sstream.h>
25 
26 // WX on WIN doesn't like it when pthread.h is included first.
27 #include <pthread.h>
28 
29 #include <spek-utils.h>
30 
31 #include "spek-artwork.h"
32 #include "spek-preferences-dialog.h"
33 #include "spek-preferences.h"
34 #include "spek-spectrogram.h"
35 
36 #include "spek-window.h"
37 
38 DECLARE_EVENT_TYPE(SPEK_NOTIFY_EVENT, -1)
39 DEFINE_EVENT_TYPE(SPEK_NOTIFY_EVENT)
40 
41 BEGIN_EVENT_TABLE(SpekWindow, wxFrame)
42     EVT_MENU(wxID_OPEN, SpekWindow::on_open)
43     EVT_MENU(wxID_SAVE, SpekWindow::on_save)
44     EVT_MENU(wxID_EXIT, SpekWindow::on_exit)
45     EVT_MENU(wxID_PREFERENCES, SpekWindow::on_preferences)
46     EVT_MENU(wxID_HELP, SpekWindow::on_help)
47     EVT_MENU(wxID_ABOUT, SpekWindow::on_about)
48     EVT_COMMAND(-1, SPEK_NOTIFY_EVENT, SpekWindow::on_notify)
49 END_EVENT_TABLE()
50 
51 // Forward declarations.
52 static void * check_version(void *);
53 
54 class SpekDropTarget : public wxFileDropTarget
55 {
56 public:
SpekDropTarget(SpekWindow * window)57     SpekDropTarget(SpekWindow *window) : wxFileDropTarget(), window(window) {}
58 
59 protected:
OnDropFiles(wxCoord,wxCoord,const wxArrayString & filenames)60     virtual bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames){
61         if (filenames.GetCount() == 1) {
62             window->open(filenames[0]);
63             return true;
64         }
65         return false;
66     }
67 
68 private:
69     SpekWindow *window;
70 };
71 
SpekWindow(const wxString & path)72 SpekWindow::SpekWindow(const wxString& path) :
73     wxFrame(NULL, -1, wxEmptyString, wxDefaultPosition, wxSize(640, 480)), path(path)
74 {
75     this->description = _("Spek - Acoustic Spectrum Analyser");
76     SetTitle(this->description);
77 
78 #if wxCHECK_VERSION(2, 9, 0)
79 #ifndef OS_OSX
80     SetIcons(wxArtProvider::GetIconBundle(ART_SPEK, wxART_FRAME_ICON));
81 #endif
82 #else
83     SetIcon(wxArtProvider::GetIcon(ART_SPEK, wxART_FRAME_ICON, wxSize(48, 48)));
84 #endif
85 
86     wxMenuBar *menu = new wxMenuBar();
87 
88     wxMenu *menu_file = new wxMenu();
89     wxMenuItem *menu_file_open = new wxMenuItem(menu_file, wxID_OPEN);
90     menu_file->Append(menu_file_open);
91     wxMenuItem *menu_file_save = new wxMenuItem(menu_file, wxID_SAVE);
92     menu_file->Append(menu_file_save);
93     menu_file->AppendSeparator();
94     menu_file->Append(wxID_EXIT);
95     menu->Append(menu_file, _("&File"));
96 
97     wxMenu *menu_edit = new wxMenu();
98     wxMenuItem *menu_edit_prefs = new wxMenuItem(menu_edit, wxID_PREFERENCES);
99     menu_edit_prefs->SetItemLabel(menu_edit_prefs->GetItemLabelText() + wxT("\tCtrl-E"));
100     menu_edit->Append(menu_edit_prefs);
101     menu->Append(menu_edit, _("&Edit"));
102 
103     wxMenu *menu_help = new wxMenu();
104     wxMenuItem *menu_help_contents = new wxMenuItem(
105         menu_help, wxID_HELP, wxString(_("&Help")) + wxT("\tF1"));
106     menu_help->Append(menu_help_contents);
107     wxMenuItem *menu_help_about = new wxMenuItem(menu_help, wxID_ABOUT);
108     menu_help_about->SetItemLabel(menu_help_about->GetItemLabelText() + wxT("\tShift-F1"));
109     menu_help->Append(menu_help_about);
110     menu->Append(menu_help, _("&Help"));
111 
112     SetMenuBar(menu);
113 
114     wxToolBar *toolbar = CreateToolBar();
115     toolbar->AddTool(
116         wxID_OPEN,
117         wxEmptyString,
118         wxArtProvider::GetBitmap(ART_OPEN, wxART_TOOLBAR),
119         menu_file_open->GetItemLabelText()
120     );
121     toolbar->AddTool(
122         wxID_SAVE,
123         wxEmptyString,
124         wxArtProvider::GetBitmap(ART_SAVE, wxART_TOOLBAR),
125         menu_file_save->GetItemLabelText()
126     );
127 #if wxCHECK_VERSION(2, 9, 1)
128     toolbar->AddStretchableSpace();
129     toolbar->AddTool(
130         wxID_HELP,
131         wxEmptyString,
132         wxArtProvider::GetBitmap(ART_HELP, wxART_TOOLBAR),
133         _("Help")
134     );
135 #endif
136     toolbar->Realize();
137 
138     wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
139 
140     // wxInfoBar is too limited, construct a custom one.
141     wxPanel *info_bar = new wxPanel(this);
142     info_bar->Hide();
143     info_bar->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOTEXT));
144     info_bar->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_INFOBK));
145     wxSizer *info_sizer = new wxBoxSizer(wxHORIZONTAL);
146     wxStaticText *label = new wxStaticText(
147         info_bar, -1, _("A new version of Spek is available, click to download."));
148     label->SetCursor(*new wxCursor(wxCURSOR_HAND));
149     label->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(SpekWindow::on_visit));
150     // This second Connect() handles clicks on the border
151     info_bar->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(SpekWindow::on_visit));
152     info_sizer->Add(label, 1, wxALIGN_CENTER_VERTICAL | wxALL, 6);
153     wxBitmapButton *button = new wxBitmapButton(
154         info_bar, -1, wxArtProvider::GetBitmap(ART_CLOSE, wxART_BUTTON),
155         wxDefaultPosition, wxDefaultSize, wxNO_BORDER);
156     button->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SpekWindow::on_close));
157     info_sizer->Add(button, 0, wxALIGN_CENTER_VERTICAL);
158     info_bar->SetSizer(info_sizer);
159     sizer->Add(info_bar, 0, wxEXPAND);
160 
161     this->spectrogram = new SpekSpectrogram(this);
162     sizer->Add(this->spectrogram, 1, wxEXPAND);
163 
164     this->cur_dir = wxGetHomeDir();
165 
166     if (!path.IsEmpty()) {
167         open(path);
168     }
169 
170     SetDropTarget(new SpekDropTarget(this));
171 
172     SetSizer(sizer);
173 
174     pthread_t thread;
175     pthread_create(&thread, NULL, &check_version, this);
176 }
177 
open(const wxString & path)178 void SpekWindow::open(const wxString& path)
179 {
180     wxFileName file_name(path);
181     if (file_name.FileExists()) {
182         this->path = path;
183         wxString full_name = file_name.GetFullName();
184         // TRANSLATORS: window title, %s is replaced with the file name
185         wxString title = wxString::Format(_("Spek - %s"), full_name.c_str());
186         SetTitle(title);
187 
188         this->spectrogram->open(path);
189     }
190 }
191 
192 // TODO: s/audio/media/
193 static const char *audio_extensions[] = {
194     "3gp",
195     "aac",
196     "aif",
197     "aifc",
198     "aiff",
199     "amr",
200     "awb",
201     "ape",
202     "au",
203     "dts",
204     "flac",
205     "gsm",
206     "m4a",
207     "m4p",
208     "mp3",
209     "mp4",
210     "mp+",
211     "mpc",
212     "mpp",
213     "oga",
214     "ogg",
215     "opus",
216     "ra",
217     "ram",
218     "snd",
219     "wav",
220     "wma",
221     "wv",
222     NULL
223 };
224 
on_open(wxCommandEvent &)225 void SpekWindow::on_open(wxCommandEvent&)
226 {
227     static wxString filters = wxEmptyString;
228     static int filter_index = 1;
229 
230     if (filters.IsEmpty()) {
231         filters.Alloc(1024);
232         filters += _("All files");
233         filters += wxT("|*.*|");
234         filters += _("Audio files");
235         filters += wxT("|");
236         for (int i = 0; audio_extensions[i]; ++i) {
237             if (i) {
238                 filters += wxT(";");
239             }
240             filters += wxT("*.");
241             filters += wxString::FromAscii(audio_extensions[i]);
242         }
243         filters.Shrink();
244     }
245 
246     wxFileDialog *dlg = new wxFileDialog(
247         this,
248         _("Open File"),
249         this->cur_dir,
250         wxEmptyString,
251         filters,
252         wxFD_OPEN
253     );
254     dlg->SetFilterIndex(filter_index);
255 
256     if (dlg->ShowModal() == wxID_OK) {
257         this->cur_dir = dlg->GetDirectory();
258         filter_index = dlg->GetFilterIndex();
259         open(dlg->GetPath());
260     }
261 
262     dlg->Destroy();
263 }
264 
on_save(wxCommandEvent &)265 void SpekWindow::on_save(wxCommandEvent&)
266 {
267     static wxString filters = wxEmptyString;
268 
269     if (filters.IsEmpty()) {
270         filters = _("PNG images");
271         filters += wxT("|*.png");
272     }
273 
274     wxFileDialog *dlg = new wxFileDialog(
275         this,
276         _("Save Spectrogram"),
277         this->cur_dir,
278         wxEmptyString,
279         filters,
280         wxFD_SAVE
281     );
282 
283     // Suggested name is <file_name>.png
284     wxString name = _("Untitled");
285     if (!this->path.IsEmpty()) {
286         wxFileName file_name(this->path);
287         name = file_name.GetFullName();
288     }
289     name += wxT(".png");
290     dlg->SetFilename(name);
291 
292     if (dlg->ShowModal() == wxID_OK) {
293         this->cur_dir = dlg->GetDirectory();
294         this->spectrogram->save(dlg->GetPath());
295     }
296 
297     dlg->Destroy();
298 }
299 
on_exit(wxCommandEvent &)300 void SpekWindow::on_exit(wxCommandEvent&)
301 {
302     Close(true);
303 }
304 
on_preferences(wxCommandEvent &)305 void SpekWindow::on_preferences(wxCommandEvent&)
306 {
307     SpekPreferencesDialog dlg(this);
308     dlg.ShowModal();
309 }
310 
on_help(wxCommandEvent &)311 void SpekWindow::on_help(wxCommandEvent&)
312 {
313     wxLaunchDefaultBrowser(
314         wxString::Format(wxT("http://spek-project.org/man-%s.html"), wxT(PACKAGE_VERSION))
315     );
316 }
317 
on_about(wxCommandEvent &)318 void SpekWindow::on_about(wxCommandEvent&)
319 {
320     wxAboutDialogInfo info;
321     info.AddDeveloper(wxT("Alexander Kojevnikov"));
322     info.AddDeveloper(wxT("Colin Watson"));
323     info.AddDeveloper(wxT("Daniel Hams"));
324     info.AddDeveloper(wxT("Fabian Deutsch"));
325     info.AddDeveloper(wxT("Jonathan Gonzalez V"));
326     info.AddDeveloper(wxT("Simon Ruderich"));
327     info.AddDeveloper(wxT("Stefan Kost"));
328     info.AddDeveloper(wxT("Thibault North"));
329     info.AddArtist(wxT("Olga Vasylevska"));
330     // TRANSLATORS: Add your name here
331     wxString translator = _("translator-credits");
332     if (translator != wxT("translator-credits")) {
333         info.AddTranslator(translator);
334     }
335     info.SetName(wxT("Spek"));
336     info.SetVersion(wxT(PACKAGE_VERSION));
337     info.SetCopyright(_("Copyright (c) 2010-2013 Alexander Kojevnikov and contributors"));
338     info.SetDescription(this->description);
339 #ifdef OS_UNIX
340     info.SetWebSite(wxT("http://spek-project.org/"), _("Spek Website"));
341     info.SetIcon(wxArtProvider::GetIcon(wxT("spek"), wxART_OTHER, wxSize(128, 128)));
342 #endif
343     wxAboutBox(info);
344 }
345 
on_notify(wxCommandEvent &)346 void SpekWindow::on_notify(wxCommandEvent&)
347 {
348     this->GetSizer()->Show((size_t)0);
349     this->Layout();
350 }
351 
on_visit(wxCommandEvent &)352 void SpekWindow::on_visit(wxCommandEvent&)
353 {
354     wxLaunchDefaultBrowser(wxT("http://spek-project.org"));
355 }
356 
on_close(wxCommandEvent & event)357 void SpekWindow::on_close(wxCommandEvent& event)
358 {
359     wxWindow *self = ((wxWindow *)event.GetEventObject())->GetGrandParent();
360     self->GetSizer()->Hide((size_t)0);
361     self->Layout();
362 }
363 
check_version(void * p)364 static void * check_version(void *p)
365 {
366     // Does the user want to check for updates?
367     SpekPreferences& prefs = SpekPreferences::get();
368     if (!prefs.get_check_update()) {
369         return NULL;
370     }
371 
372     // Calculate the number of days since 0001-01-01, borrowed from GLib.
373     wxDateTime now = wxDateTime::Now();
374     int year = now.GetYear() - 1;
375     int days = year * 365;
376     days += (year >>= 2); // divide by 4 and add
377     days -= (year /= 25); // divides original # years by 100
378     days += year >> 2; // divides by 4, which divides original by 400
379     days += now.GetDayOfYear();
380 
381     // When was the last update?
382     int diff = days - prefs.get_last_update();
383     if (diff < 7) {
384         return NULL;
385     }
386 
387     // Get the version number.
388     wxString version;
389     wxHTTP http;
390     if (http.Connect(wxT("spek-project.org"))) {
391         wxInputStream *stream = http.GetInputStream(wxT("/version"));
392         if (stream) {
393             wxStringOutputStream out(&version);
394             stream->Read(out);
395             version.Trim();
396             delete stream;
397         }
398     }
399 
400     if (version.IsEmpty()) {
401         return NULL;
402     }
403 
404     if (1 == spek_vercmp(version.mb_str(wxConvLibc), PACKAGE_VERSION)) {
405         SpekWindow *self = (SpekWindow *)p;
406         wxCommandEvent event(SPEK_NOTIFY_EVENT, -1);
407         event.SetEventObject(self);
408         wxPostEvent(self, event);
409     }
410 
411     // Update the preferences.
412     prefs.set_check_update(true);
413     prefs.set_last_update(days);
414     return NULL;
415 }
416