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   444 use strict;
  1         1  
  1         22  
4 1     1   3 use warnings;
  1         0  
  1         37  
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   3 use base qw( App::Music::ChordPro::Wx::Main_wxg );
  1         0  
  1         310  
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)|*.cho;*.crd;*.chopro;*.chord;*.chordpro|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             $self->{t_source}->SaveFile($preview_cho);
149              
150             #### ChordPro
151              
152             @ARGV = (); # just to make sure
153             $::__EMBEDDED__ = 1;
154             my $options = App::Music::ChordPro::app_setup( "ChordPro", $VERSION );
155              
156             use App::Music::ChordPro::Output::PDF;
157             $options->{output} = $preview_pdf;
158             $options->{generate} = "PDF";
159             $options->{backend} = "App::Music::ChordPro::Output::PDF";
160             $options->{transpose} = $self->{prefs_xpose} if $self->{prefs_xpose};
161              
162             # Setup configuration.
163             use App::Music::ChordPro::Config;
164             $options->{nouserconfig} = 1;
165             if ( $self->{prefs_configfile} ) {
166             $options->{noconfig} = 0;
167             $options->{config} = $self->{prefs_configfile};
168             }
169             else {
170             $options->{noconfig} = 1;
171             }
172             $::config = App::Music::ChordPro::Config::configurator($options);
173              
174             # Parse the input.
175             use App::Music::ChordPro::Songbook;
176             my $s = App::Music::ChordPro::Songbook->new;
177              
178             my @msgs;
179             local $SIG{__WARN__} = sub {
180             push( @msgs, join("", @_) );
181             Wx::LogWarning($msgs[-1]);
182             };
183              
184             $options->{diagformat} = 'Line %n, %m';
185             $s->parsefile( $preview_cho, $options );
186              
187             if ( @msgs ) {
188             Wx::LogStatus( @msgs . " message" .
189             ( @msgs == 1 ? "" : "s" ) . "." );
190             Wx::LogError("Problems found!");
191             return;
192             }
193              
194             # Generate the songbook.
195             my $res = App::Music::ChordPro::Output::PDF->generate_songbook( $s, $options );
196              
197             if ( -e $preview_pdf ) {
198             Wx::LogStatus("Output generated, starting previewer");
199              
200             if ( my $cmd = $self->{prefs_pdfviewer} ) {
201             if ( $cmd =~ s/\%f/$preview_pdf/g ) {
202             $cmd .= " \"$preview_pdf\"";
203             }
204             elsif ( $cmd =~ /\%u/ ) {
205             my $u = _makeurl($preview_pdf);
206             $cmd =~ s/\%u/$u/g;
207             }
208             Wx::ExecuteCommand($cmd);
209             }
210             else {
211             my $wxTheMimeTypesManager = Wx::MimeTypesManager->new;
212             my $ft = $wxTheMimeTypesManager->GetFileTypeFromExtension("pdf");
213             if ( $ft && ( my $cmd = $ft->GetOpenCommand($preview_pdf) ) ) {
214             Wx::ExecuteCommand($cmd);
215             }
216             else {
217             Wx::LaunchDefaultBrowser($preview_pdf);
218             }
219             }
220             }
221             unlink( $preview_cho );
222             }
223              
224             sub _makeurl {
225             my $u = shift;
226             $u =~ s;\\;/;g;
227             $u =~ s/([^a-z0-9---_\/.~])/sprintf("%%%02X", ord($1))/ieg;
228             $u =~ s/^([a-z])%3a/\/$1:/i; # Windows
229             return "file://$u";
230             }
231              
232             sub saveas {
233             my ( $self, $file ) = @_;
234             $self->{t_source}->SaveFile($file);
235             Wx::LogStatus( "Saved." );
236             }
237              
238             sub GetPreferences {
239             my ( $self ) = @_;
240             my $conf = Wx::ConfigBase::Get;
241             for ( keys( %$prefctl ) ) {
242             $self->{"prefs_$_"} = $conf->Read( "preferences/$_", $prefctl->{$_} );
243             }
244             }
245              
246             sub SavePreferences {
247             my ( $self ) = @_;
248             return unless $self;
249             my $conf = Wx::ConfigBase::Get;
250             for ( keys( %$prefctl ) ) {
251             $conf->Write( "preferences/$_", $self->{"prefs_$_"} );
252             }
253             $conf->Flush;
254             }
255              
256             ################ Event handlers ################
257              
258             # Event handlers override the subs generated by wxGlade in the _wxg class.
259              
260             sub OnOpen {
261             my ( $self, $event, $create ) = @_;
262             if ( $self->{t_source} && $self->{t_source}->IsModified ) {
263             my $md = Wx::MessageDialog->new
264             ( $self,
265             "File " . $self->{_currentfile} . " has been changed.\n".
266             "Do you want to save your changes?",
267             "File has changed",
268             0 | wxCANCEL | wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION );
269             my $ret = $md->ShowModal;
270             $md->Destroy;
271             return if $ret == wxID_CANCEL;
272             if ( $ret == wxID_YES ) {
273             $self->saveas( $self->{_currentfile} );
274             }
275             }
276              
277             if ( $create ) {
278             $self->newfile;
279             }
280             else {
281             $self->opendialog;
282             }
283             }
284              
285             sub OnNew {
286             my( $self, $event ) = @_;
287             OnOpen( $self, $event, 1 );
288             }
289              
290             sub OnSaveAs {
291             my ($self, $event) = @_;
292             my $fd = Wx::FileDialog->new
293             ($self, _T("Choose output file"),
294             "", "",
295             "*.txt",
296             0|wxFD_SAVE|wxFD_OVERWRITE_PROMPT,
297             wxDefaultPosition);
298             my $ret = $fd->ShowModal;
299             if ( $ret == wxID_OK ) {
300             $self->export( $fd->GetPath );
301             }
302             $fd->Destroy;
303             }
304              
305             sub OnSave {
306             my ($self, $event) = @_;
307             $self->saveas( $self->{_currentfile} );
308             }
309              
310             sub OnPreview {
311             my ( $self, $event ) = @_;
312             $self->preview;
313             }
314              
315             sub OnQuit {
316             my ( $self, $event ) = @_;
317             $self->SavePreferences;
318             if ( $self->{t_source} && $self->{t_source}->IsModified ) {
319             my $md = Wx::MessageDialog->new
320             ( $self,
321             "File " . $self->{_currentfile} . " has been changed.\n".
322             "Do you want to save your changes?",
323             "File has changed",
324             0 | wxCANCEL | wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION );
325             my $ret = $md->ShowModal;
326             $md->Destroy;
327             return if $ret == wxID_CANCEL;
328             if ( $ret == wxID_YES ) {
329             $self->saveas( $self->{_currentfile} );
330             }
331             }
332             $self->Close;
333             }
334              
335             sub OnExit { # called implicitly
336             my ( $self, $event ) = @_;
337             }
338              
339             sub OnUndo {
340             my ($self, $event) = @_;
341             $self->{t_source}->CanUndo
342             ? $self->{t_source}->Undo
343             : Wx::LogStatus("Sorry, can't undo yet");
344             }
345              
346             sub OnRedo {
347             my ($self, $event) = @_;
348             $self->{t_source}->CanRedo
349             ? $self->{t_source}->Redo
350             : Wx::LogStatus("Sorry, can't redo yet");
351             }
352              
353             sub OnCut {
354             my ($self, $event) = @_;
355             $self->{t_source}->Cut;
356             }
357              
358             sub OnCopy {
359             my ($self, $event) = @_;
360             $self->{t_source}->Copy;
361             }
362              
363              
364             sub OnPaste {
365             my ($self, $event) = @_;
366             $self->{t_source}->Paste;
367             }
368              
369             sub OnDelete {
370             my ($self, $event) = @_;
371             my ( $from, $to ) = $self->{t_source}->GetSelection;
372             $self->{t_source}->Remove( $from, $to ) if $from < $to;
373             }
374              
375             sub OnHelp_ChordPro {
376             my ($self, $event) = @_;
377             Wx::LaunchDefaultBrowser("http://www.chordpro.org/chordpro/index.html");
378             }
379              
380             sub OnHelp_Config {
381             my ($self, $event) = @_;
382             Wx::LaunchDefaultBrowser("https://metacpan.org/pod/distribution/App-Music-ChordPro/res/pod/Config.pod");
383             }
384              
385             sub OnPreferences {
386             my ($self, $event) = @_;
387              
388             use App::Music::ChordPro::Wx::PreferencesDialog;
389             $self->{d_prefs} ||= App::Music::ChordPro::Wx::PreferencesDialog->new($self, -1, "Preferences");
390             my $ret = $self->{d_prefs}->ShowModal;
391             }
392              
393             sub OnAbout {
394             my ($self, $event) = @_;
395              
396             my $firstyear = 2016;
397             my $year = 1900 + (localtime(time))[5];
398             if ( $year != $firstyear ) {
399             $year = "$firstyear,$year";
400             }
401              
402             # Sometimes version numbers are localized...
403             my $dd = sub { my $v = $_[0]; $v =~ s/,/./g; $v };
404              
405             if ( rand > 0.5 ) {
406             my $ai = Wx::AboutDialogInfo->new;
407             $ai->SetName("ChordPro Preview Editor");
408             $ai->SetVersion( $dd->($VERSION) );
409             $ai->SetCopyright("Copyright $year Johan Vromans ");
410             $ai->AddDeveloper("Johan Vromans ");
411             $ai->AddDeveloper("ChordPro version " . $dd->($App::Music::ChordPro::VERSION));
412             $ai->AddDeveloper("Perl version " . $dd->(sprintf("%vd",$^V)));
413             $ai->AddDeveloper("wxWidgets version " . $dd->(Wx::wxVERSION));
414             $ai->AddDeveloper(App::Packager::Packager() . " version " . App::Packager::Version())
415             if $App::Packager::PACKAGED;
416             $ai->AddDeveloper("GUI design with wxGlade");
417             $ai->AddDeveloper("Some icons by www.flaticon.com");
418             $ai->SetWebSite("http://www.chordpro.org");
419             Wx::AboutBox($ai);
420             }
421             else {
422             my $md = Wx::MessageDialog->new
423             ($self, "ChordPro Preview Editor version " . $dd->($VERSION) . "\n".
424             "Copyright $year Johan Vromans \n".
425             "\n".
426             "GUI design with wxGlade, http://wxglade.sourceforge.net\n\n".
427             "ChordPro version " . $dd->($App::Music::ChordPro::VERSION) . "\n".
428             "Perl version " . $dd->(sprintf("%vd",$^V))."\n".
429             "wxPerl version " . $dd->($Wx::VERSION)."\n".
430             "wxWidgets version " . $dd->(Wx::wxVERSION)."\n".
431             ( $App::Packager::PACKAGED
432             ? App::Packager::Packager() . " version " . App::Packager::Version()."\n"
433             : "" ),
434             "About ChordPro",
435             wxOK|wxICON_INFORMATION,
436             wxDefaultPosition);
437             $md->ShowModal;
438             $md->Destroy;
439             }
440             }
441              
442             ################ End of Event handlers ################
443              
444             1;