File Coverage

blib/lib/App/Music/ChordPro/Wx/Main.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             #! perl
2              
3 1     1   459 use strict;
  1         2  
  1         28  
4 1     1   4 use warnings;
  1         2  
  1         33  
5              
6             # Implementation of App::Music::ChordPro::Wx::Main_wxg details.
7              
8             package App::Music::ChordPro::Wx::Main;
9              
10             # App::Music::ChordPro::Wx::Main_wxg is generated by wxGlade and contains
11             # all UI associated code.
12              
13 1     1   4 use base qw( App::Music::ChordPro::Wx::Main_wxg );
  1         2  
  1         364  
14              
15             use Wx qw[:everything];
16             use Wx::Locale gettext => '_T';
17              
18             use App::Music::ChordPro::Wx;
19             use App::Music::ChordPro;
20             use File::Temp qw( tempfile );
21              
22             our $VERSION = $App::Music::ChordPro::Wx::VERSION;
23              
24             sub new {
25             my $self = bless $_[0]->SUPER::new(), __PACKAGE__;
26              
27             $self;
28             }
29              
30             my $prefctl;
31              
32             # Explicit (re)initialisation of this class.
33             sub init {
34             my ( $self ) = @_;
35              
36             $prefctl ||=
37             {
38             configfile => "",
39             pdfviewer => "",
40             };
41              
42             if ( $^O =~ /^mswin/i ) {
43             Wx::ConfigBase::Get->SetPath("/wxchordpro");
44             }
45             else {
46             my $cb;
47             if ( -d "$ENV{HOME}/.config" ) {
48             $cb = "$ENV{HOME}/.config/wxchordpro/wxchordpro";
49             mkdir("$ENV{HOME}/.config/wxchordpro");
50             }
51             else {
52             $cb = "$ENV{HOME}/.wxchordpro";
53             }
54             unless ( -f $cb ) {
55             open( my $fd, '>', $cb );
56             }
57             Wx::ConfigBase::Set
58             (Wx::FileConfig->new
59             ( "WxChordPro",
60             "Squirrel Consultancy",
61             $cb,
62             '',
63             wxCONFIG_USE_LOCAL_FILE,
64             ));
65             }
66              
67             $self->GetPreferences;
68             my $font = Wx::Font->new( 12, wxFONTFAMILY_MODERN, wxFONTSTYLE_NORMAL,
69             wxFONTWEIGHT_NORMAL );
70             $self->{t_source}->SetFont($font);
71             Wx::Log::SetTimestamp(' ');
72             if ( @ARGV && -s $ARGV[0] ) {
73             $self->openfile( shift(@ARGV) );
74             return 1;
75             }
76              
77             $self->opendialog;
78             $self->newfile unless $self->{_currentfile};
79             return 1;
80             }
81              
82             ################ Internal methods ################
83              
84             sub opendialog {
85             my ($self) = @_;
86             my $fd = Wx::FileDialog->new
87             ($self, _T("Choose ChordPro file"),
88             "", "",
89             "ChordPro files (*.cho,*.crd,*.chopro,*.chord,*.chordpro,*.pro)|*.cho;*.crd;*.chopro;*.chord;*.chordpro;*.pro|All files|*.*",
90             0|wxFD_OPEN|wxFD_FILE_MUST_EXIST,
91             wxDefaultPosition);
92             my $ret = $fd->ShowModal;
93             if ( $ret == wxID_OK ) {
94             $self->openfile( $fd->GetPath );
95             }
96             $fd->Destroy;
97             }
98              
99             sub openfile {
100             my ( $self, $file ) = @_;
101             unless ( $self->{t_source}->LoadFile($file) ) {
102             my $md = Wx::MessageDialog( $self,
103             "Error opening $file: $!",
104             "File open error",
105             wxOK | wxICON_ERROR );
106             $md->ShowModal;
107             $md->Destroy;
108             return;
109             }
110             #### TODO: Get rid of selection on Windows
111             $self->{_currentfile} = $file;
112             if ( $self->{t_source}->GetValue =~ /^\{\s*title[: ]+([^\}]*)\}/m ) {
113             my $n = $self->{t_source}->GetNumberOfLines;
114             Wx::LogStatus("Loaded: $1 ($n line" .
115             ( $n == 1 ? "" : "s" ) .
116             ")");
117             $self->{sz_source}->GetStaticBox->SetLabel($1);
118             }
119              
120             $self->{prefs_xpose} = 0;
121             }
122              
123             sub newfile {
124             my ( $self ) = @_;
125             undef $self->{_currentfile};
126             $self->{t_source}->SetValue( <
127             {title: New Song}
128              
129             EOD
130             Wx::LogStatus("New file");
131             $self->{prefs_xpose} = 0;
132             }
133              
134             my ( $preview_cho, $preview_pdf );
135              
136             sub preview {
137             my ( $self ) = @_;
138              
139             # We can not unlink temps because we do not know when the viewer
140             # is ready. So the best we can do is reuse the files.
141             unless ( $preview_cho ) {
142             ( undef, $preview_cho ) = tempfile( OPEN => 0 );
143             $preview_pdf = $preview_cho . ".pdf";
144             $preview_cho .= ".cho";
145             unlink( $preview_cho, $preview_pdf );
146             }
147              
148             my $mod = $self->{t_source}->IsModified;
149             $self->{t_source}->SaveFile($preview_cho);
150             $self->{t_source}->SetModified($mod);
151              
152             #### ChordPro
153              
154             @ARGV = (); # just to make sure
155             $::__EMBEDDED__ = 1;
156             my $options = App::Music::ChordPro::app_setup( "ChordPro", $VERSION );
157              
158             use App::Music::ChordPro::Output::PDF;
159             $options->{output} = $preview_pdf;
160             $options->{generate} = "PDF";
161             $options->{backend} = "App::Music::ChordPro::Output::PDF";
162             $options->{transpose} = $self->{prefs_xpose} if $self->{prefs_xpose};
163              
164             # Setup configuration.
165             use App::Music::ChordPro::Config;
166             $options->{nouserconfig} = 1;
167             if ( $self->{prefs_configfile} ) {
168             $options->{noconfig} = 0;
169             $options->{config} = $self->{prefs_configfile};
170             }
171             else {
172             $options->{noconfig} = 1;
173             }
174             $::config = App::Music::ChordPro::Config::configurator($options);
175              
176             # Parse the input.
177             use App::Music::ChordPro::Songbook;
178             my $s = App::Music::ChordPro::Songbook->new;
179              
180             my @msgs;
181             local $SIG{__WARN__} = sub {
182             push( @msgs, join("", @_) );
183             Wx::LogWarning($msgs[-1]);
184             };
185              
186             $options->{diagformat} = 'Line %n, %m';
187             $s->parsefile( $preview_cho, $options );
188              
189             if ( @msgs ) {
190             Wx::LogStatus( @msgs . " message" .
191             ( @msgs == 1 ? "" : "s" ) . "." );
192             Wx::LogError("Problems found!");
193             return;
194             }
195              
196             # Generate the songbook.
197             my $res = App::Music::ChordPro::Output::PDF->generate_songbook( $s, $options );
198              
199             if ( -e $preview_pdf ) {
200             Wx::LogStatus("Output generated, starting previewer");
201              
202             if ( my $cmd = $self->{prefs_pdfviewer} ) {
203             if ( $cmd =~ s/\%f/$preview_pdf/g ) {
204             $cmd .= " \"$preview_pdf\"";
205             }
206             elsif ( $cmd =~ /\%u/ ) {
207             my $u = _makeurl($preview_pdf);
208             $cmd =~ s/\%u/$u/g;
209             }
210             Wx::ExecuteCommand($cmd);
211             }
212             else {
213             my $wxTheMimeTypesManager = Wx::MimeTypesManager->new;
214             my $ft = $wxTheMimeTypesManager->GetFileTypeFromExtension("pdf");
215             if ( $ft && ( my $cmd = $ft->GetOpenCommand($preview_pdf) ) ) {
216             Wx::ExecuteCommand($cmd);
217             }
218             else {
219             Wx::LaunchDefaultBrowser($preview_pdf);
220             }
221             }
222             }
223             unlink( $preview_cho );
224             }
225              
226             sub _makeurl {
227             my $u = shift;
228             $u =~ s;\\;/;g;
229             $u =~ s/([^a-z0-9---_\/.~])/sprintf("%%%02X", ord($1))/ieg;
230             $u =~ s/^([a-z])%3a/\/$1:/i; # Windows
231             return "file://$u";
232             }
233              
234             sub checksaved {
235             my ( $self ) = @_;
236             return 1 unless ( $self->{t_source} && $self->{t_source}->IsModified );
237             if ( $self->{_currentfile} ) {
238             my $md = Wx::MessageDialog->new
239             ( $self,
240             "File " . $self->{_currentfile} . " has been changed.\n".
241             "Do you want to save your changes?",
242             "File has changed",
243             0 | wxCANCEL | wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION );
244             my $ret = $md->ShowModal;
245             $md->Destroy;
246             return if $ret == wxID_CANCEL;
247             if ( $ret == wxID_YES ) {
248             $self->saveas( $self->{_currentfile} );
249             }
250             }
251             else {
252             my $md = Wx::MessageDialog->new
253             ( $self,
254             "Do you want to save your changes?",
255             "Contents has changed",
256             0 | wxCANCEL | wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION );
257             my $ret = $md->ShowModal;
258             $md->Destroy;
259             return if $ret == wxID_CANCEL;
260             if ( $ret == wxID_YES ) {
261             return if $self->OnSaveAs == wxID_CANCEL;
262             }
263             }
264             return 1;
265             }
266              
267             sub saveas {
268             my ( $self, $file ) = @_;
269             $self->{t_source}->SaveFile($file);
270             Wx::LogStatus( "Saved." );
271             }
272              
273             sub GetPreferences {
274             my ( $self ) = @_;
275             my $conf = Wx::ConfigBase::Get;
276             for ( keys( %$prefctl ) ) {
277             $self->{"prefs_$_"} = $conf->Read( "preferences/$_", $prefctl->{$_} );
278             }
279             }
280              
281             sub SavePreferences {
282             my ( $self ) = @_;
283             return unless $self;
284             my $conf = Wx::ConfigBase::Get;
285             for ( keys( %$prefctl ) ) {
286             $conf->Write( "preferences/$_", $self->{"prefs_$_"} );
287             }
288             $conf->Flush;
289             }
290              
291             ################ Event handlers ################
292              
293             # Event handlers override the subs generated by wxGlade in the _wxg class.
294              
295             sub OnOpen {
296             my ( $self, $event, $create ) = @_;
297             return unless $self->checksaved;
298              
299             if ( $create ) {
300             $self->newfile;
301             }
302             else {
303             $self->opendialog;
304             }
305             }
306              
307             sub OnNew {
308             my( $self, $event ) = @_;
309             OnOpen( $self, $event, 1 );
310             }
311              
312             sub OnSaveAs {
313             my ($self, $event) = @_;
314             my $fd = Wx::FileDialog->new
315             ($self, _T("Choose output file"),
316             "", "",
317             "*.cho",
318             0|wxFD_SAVE|wxFD_OVERWRITE_PROMPT,
319             wxDefaultPosition);
320             my $ret = $fd->ShowModal;
321             if ( $ret == wxID_OK ) {
322             $self->{t_source}->SaveFile($fd->GetPath);
323             Wx::LogStatus( "Saved." );
324             }
325             $fd->Destroy;
326             return $ret;
327             }
328              
329             sub OnSave {
330             my ($self, $event) = @_;
331             $self->saveas( $self->{_currentfile} );
332             }
333              
334             sub OnPreview {
335             my ( $self, $event ) = @_;
336             $self->preview;
337             }
338              
339             sub OnQuit {
340             my ( $self, $event ) = @_;
341             return unless $self->checksaved;
342             $self->SavePreferences;
343             $self->Close;
344             }
345              
346             sub OnExit { # called implicitly
347             my ( $self, $event ) = @_;
348             }
349              
350             sub OnUndo {
351             my ($self, $event) = @_;
352             $self->{t_source}->CanUndo
353             ? $self->{t_source}->Undo
354             : Wx::LogStatus("Sorry, can't undo yet");
355             }
356              
357             sub OnRedo {
358             my ($self, $event) = @_;
359             $self->{t_source}->CanRedo
360             ? $self->{t_source}->Redo
361             : Wx::LogStatus("Sorry, can't redo yet");
362             }
363              
364             sub OnCut {
365             my ($self, $event) = @_;
366             $self->{t_source}->Cut;
367             }
368              
369             sub OnCopy {
370             my ($self, $event) = @_;
371             $self->{t_source}->Copy;
372             }
373              
374             sub OnPaste {
375             my ($self, $event) = @_;
376             $self->{t_source}->Paste;
377             }
378              
379             sub OnDelete {
380             my ($self, $event) = @_;
381             my ( $from, $to ) = $self->{t_source}->GetSelection;
382             $self->{t_source}->Remove( $from, $to ) if $from < $to;
383             }
384              
385             sub OnHelp_ChordPro {
386             my ($self, $event) = @_;
387             Wx::LaunchDefaultBrowser("http://www.chordpro.org/chordpro/index.html");
388             }
389              
390             sub OnHelp_Config {
391             my ($self, $event) = @_;
392             Wx::LaunchDefaultBrowser("https://metacpan.org/pod/distribution/App-Music-ChordPro/res/pod/Config.pod");
393             }
394              
395             sub OnHelp_Example {
396             my ($self, $event) = @_;
397             return unless $self->checksaved;
398             $self->openfile( ::findlib( "res/examples/swinglow.cho" ) );
399             undef $self->{_currentfile};
400             $self->{t_source}->SetModified(1);
401             }
402              
403             sub OnPreferences {
404             my ($self, $event) = @_;
405              
406             use App::Music::ChordPro::Wx::PreferencesDialog;
407             $self->{d_prefs} ||= App::Music::ChordPro::Wx::PreferencesDialog->new($self, -1, "Preferences");
408             my $ret = $self->{d_prefs}->ShowModal;
409             }
410              
411             sub OnAbout {
412             my ($self, $event) = @_;
413              
414             my $firstyear = 2016;
415             my $year = 1900 + (localtime(time))[5];
416             if ( $year != $firstyear ) {
417             $year = "$firstyear,$year";
418             }
419              
420             # Sometimes version numbers are localized...
421             my $dd = sub { my $v = $_[0]; $v =~ s/,/./g; $v };
422              
423             if ( rand > 0.5 ) {
424             my $ai = Wx::AboutDialogInfo->new;
425             $ai->SetName("ChordPro Preview Editor");
426             $ai->SetVersion( $dd->($VERSION) );
427             $ai->SetCopyright("Copyright $year Johan Vromans ");
428             $ai->AddDeveloper("Johan Vromans ");
429             $ai->AddDeveloper("ChordPro version " . $dd->($App::Music::ChordPro::VERSION));
430             $ai->AddDeveloper("Perl version " . $dd->(sprintf("%vd",$^V)));
431             $ai->AddDeveloper("wxWidgets version " . $dd->(Wx::wxVERSION));
432             $ai->AddDeveloper(App::Packager::Packager() . " version " . App::Packager::Version())
433             if $App::Packager::PACKAGED;
434             $ai->AddDeveloper("GUI design with wxGlade");
435             $ai->AddDeveloper("Some icons by www.flaticon.com");
436             $ai->SetWebSite("http://www.chordpro.org");
437             Wx::AboutBox($ai);
438             }
439             else {
440             my $md = Wx::MessageDialog->new
441             ($self, "ChordPro Preview Editor version " . $dd->($VERSION) . "\n".
442             "Copyright $year Johan Vromans \n".
443             "\n".
444             "GUI design with wxGlade, http://wxglade.sourceforge.net\n\n".
445             "ChordPro version " . $dd->($App::Music::ChordPro::VERSION) . "\n".
446             "Perl version " . $dd->(sprintf("%vd",$^V))."\n".
447             "wxPerl version " . $dd->($Wx::VERSION)."\n".
448             "wxWidgets version " . $dd->(Wx::wxVERSION)."\n".
449             ( $App::Packager::PACKAGED
450             ? App::Packager::Packager() . " version " . App::Packager::Version()."\n"
451             : "" ),
452             "About ChordPro",
453             wxOK|wxICON_INFORMATION,
454             wxDefaultPosition);
455             $md->ShowModal;
456             $md->Destroy;
457             }
458             }
459              
460             ################ End of Event handlers ################
461              
462             1;