File Coverage

lib/Kephra/Menu.pm
Criterion Covered Total %
statement 6 190 3.1
branch 0 114 0.0
condition 0 33 0.0
subroutine 2 30 6.6
pod 0 17 0.0
total 8 384 2.0


line stmt bran cond sub pod time code
1             package Kephra::Menu;
2             our $VERSION = '0.18';
3            
4 1     1   1071 use strict;
  1         2  
  1         27  
5 1     1   4 use warnings;
  1         1  
  1         2506  
6            
7             my %menu;
8 0     0     sub _all { \%menu }
9             sub _ref {
10 0 0   0     if ( is($_[1]) ) { $menu{$_[0]}{ref} = $_[1] }
  0 0          
11 0           elsif ( exists $menu{$_[0]}{ref} ) { $menu{$_[0]}{ref} }
12             }
13 0 0   0     sub _data { $menu{$_[0]} if stored($_[0]) }
14 0 0   0 0   sub is { 1 if ref $_[0] eq 'Wx::Menu' }
15 0 0   0 0   sub stored { 1 if ref $menu{$_[0]} eq 'HASH'}
16 0     0 0   sub set_absolete { $menu{$_[0]}{absolete} = 1 }
17 0     0 0   sub not_absolete { $menu{$_[0]}{absolete} = 0 }
18 0     0 0   sub is_absolete { $menu{$_[0]}{absolete} }
19 0 0   0 0   sub set_update { $menu{$_[0]}{update} = $_[1] if ref $_[1] eq 'CODE' }
20 0     0 0   sub get_update { $menu{$_[0]}{update} }
21 0 0   0 0   sub no_update { delete $menu{$_[0]}{update} if stored($_[0]) }
22             sub add_onopen_check {
23 0     0 0   return until ref $_[2] eq 'CODE';
24 0           $menu{ $_[0] }{onopen}{ $_[1] } = $_[2];
25             }
26             sub del_onopen_check {
27 0     0 0   return until $_[1];
28 0 0         delete $menu{$_[0]}{onopen}{$_[1]} if exists $menu{$_[0]}{onopen}{$_[1]};
29             }
30            
31            
32             sub ready { # make menu ready for display
33 0     0 0   my $id = shift;
34 0 0         if ( stored($id) ){
35 0           my $menu = _data($id);
36 0 0 0       if ($menu->{absolete} and $menu->{update}) {
37 0 0         $menu->{absolete} = 0 if $menu->{update}() }
38 0 0         if (ref $menu->{onopen} eq 'HASH')
39 0           { $_->() for values %{$menu->{onopen}} }
  0            
40 0           _ref($id);
41             }
42             }
43            
44            
45             sub create_dynamic { # create on runtime changeable menus
46 0     0 0   my ( $menu_id, $menu_name ) = @_ ;
47            
48 0 0         if ($menu_name eq '&insert_templates') {
    0          
    0          
49            
50 0           set_absolete($menu_id);
51             set_update($menu_id, sub {
52 0     0     my $cfg = Kephra::API::settings()->{file}{templates};
53 0           my $file = Kephra::Config::filepath($cfg->{directory}, $cfg->{file});
54 0           my $tmp = Kephra::Config::File::load( $file );
55 0           my @menu_data;
56 0 0         if (exists $tmp->{template}){
57 0           $tmp = Kephra::Config::Tree::_convert_node_2_AoH(\$tmp->{template});
58 0           my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled};
59 0   0       my $filepath = Kephra::Document::Data::get_file_path() || "<$untitled>";
60 0   0       my $filename = Kephra::Document::Data::file_name() || "<$untitled>";
61 0   0       my $firstname = Kephra::Document::Data::first_name() || "<$untitled>";
62 0           for my $template ( @{$tmp} ) {
  0            
63 0           my %item;
64 0           $item{type} = 'item';
65 0           $item{label}= $template->{name};
66             $item{call} = sub {
67 0           my $content = $template->{content};
68 0           $content =~ s/\[\$\$firstname\]/$firstname/g;
69 0           $content =~ s/\[\$\$filename\]/$filename/g;
70 0           $content =~ s/\[\$\$filepath\]/$filepath/g;
71 0           Kephra::Edit::insert_text($content);
72 0           };
73 0           $item{help} = $template->{description};
74 0           push @menu_data, \%item;
75 0           eval_data($menu_id, \@menu_data);
76             }
77 0           return 1;
78             }
79 0           });
80            
81             } elsif ($menu_name eq '&file_history'){
82            
83 0           set_absolete($menu_id);
84             set_update($menu_id, sub {
85 0     0     my @menu_data = @{assemble_data_from_def
  0            
86             ( ['item file-session-history-open-all', undef] )};
87 0           my $history = Kephra::File::History::get();
88 0 0         if (ref $history eq 'ARRAY') {
89 0           my $nr = 0;
90 0           for ( @$history ) {
91 0           my $file = $_->{file_path};
92 0           push @menu_data, {
93             type => 'item',
94             label => ( File::Spec->splitpath( $file ) )[2],
95             help => $file,
96             call => eval 'sub {Kephra::File::History::open( '.$nr++.' )}',
97             };
98             }
99             }
100 0           eval_data($menu_id, \@menu_data);
101 0 0         return Kephra::File::History::had_init() ? 1 : 0;
102 0           1; # it was successful
103 0           });
104            
105             Kephra::EventTable::add_call (
106             'document.list', 'menu_'.$menu_id, sub {
107 0 0   0     set_absolete( $menu_id ) if Kephra::File::History::update();
108             }
109 0           );
110             }
111             elsif ($menu_name eq '&document_change') {
112            
113             set_update( $menu_id, sub {
114 0 0   0     return unless exists $Kephra::temp{document}{buffer};
115 0           my $filenames = Kephra::Document::Data::all_file_names();
116 0           my $pathes = Kephra::Document::Data::all_file_pathes();
117 0           my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled};
118 0           my $space = ' ';
119 0           my @menu_data;
120 0           for my $nr (0 .. @$filenames-1){
121 0           my $item = \%{$menu_data[$nr]};
  0            
122 0 0         $space = '' if $nr == 9;
123 0           $item->{type} = 'radioitem';
124 0 0         $item->{label} = $filenames->[$nr]
125             ? $space.($nr+1)." - $filenames->[$nr] \t - $pathes->[$nr]"
126             : $space.($nr+1)." - <$untitled> \t -";
127 0           $item->{call} = eval 'sub {Kephra::Document::Change::to_nr('.$nr.')}';
128             }
129 0           });
130            
131             #add_onopen_check( $menu_id, 'select', sub {
132             # my $menu = _ref($menu_id);
133             # $menu->FindItemByPosition
134             # ( Kephra::Document::Data::current_nr() )->Check(1) if $menu;
135             #});
136             #Kephra::EventTable::add_call (
137             # 'document.list', 'menu_'.$menu_id, sub { set_absolete($menu_id) }
138             #);
139             }
140             }
141            
142            
143             sub create_static { # create solid, not on runtime changeable menus
144 0     0 0   my ($menu_id, $menu_def) = @_;
145 0 0         return unless ref $menu_def eq 'ARRAY';
146 0           not_absolete($menu_id);
147 0           eval_data($menu_id, assemble_data_from_def($menu_def));
148             }
149            
150 0     0 0   sub create_menubar {
151             #my $menubar = Wx::MenuBar->new();
152             #my $m18n = Kephra::Config::Localisation::strings()->{app}{menu};
153             #my ($pos, $menu_name);
154             #for my $menu_def ( @$menubar_def ){
155             #for my $menu_id (keys %$menu_def){
156             # removing the menu command if there is one
157             #$pos = index $menu_id, ' ';
158             #if ($pos > -1){
159             #if ('menu' eq substr $menu_id, 0, $pos ){
160             #$menu_name = substr ($menu_id, $pos+1);
161             # ignoring menu structure when command other that menu or blank
162             #} else { next }
163             #} else {
164             #$menu_name = $menu_id;
165             #}
166             #$menubar->Append(
167             #Kephra::Menu::create_static( $menu_name, $menu_def->{$menu_id}),
168             #$m18n->{label}{$menu_name}
169             #);
170             #}
171             #}
172             }
173            
174             # create menu data structures (MDS) from menu skeleton definitions (command list)
175             sub assemble_data_from_def {
176 0     0 0   my $menu_def = shift;
177 0 0         return unless ref $menu_def eq 'ARRAY';
178            
179 0           my $menu_l18n = Kephra::Config::Localisation::strings()->{app}{menu};
180 0           my ($cmd_name, $cmd_data, $type_name, $pos, $sub_id);
181 0           my @mds = (); # menu data structure
182 0           for my $item_def (@$menu_def){
183 0           my %item;
184             # creating separator
185 0 0 0       if (not defined $item_def){
    0          
    0          
    0          
186 0           $item{type} = ''
187             # sorting commented lines out
188             } elsif (substr($item_def, -1) eq '#'){
189 0           next;
190             # creating separator
191             } elsif ($item_def eq '' or $item_def eq 'separator') {
192 0           $item{type} = ''
193             # eval a sublist
194             } elsif (ref $item_def eq 'HASH'){
195 0           $sub_id = $_ for keys %$item_def;
196 0           $pos = index $sub_id, ' ';
197             # make submenu if keyname is without command
198 0 0         if ($pos == -1){
199 0           $item{type} = 'menu';
200 0           $item{id} = $sub_id;
201 0           $item{label} = $menu_l18n->{label}{$sub_id};
202 0   0       $item{help} = $menu_l18n->{help}{$sub_id} || '';
203 0           $item{data} = assemble_data_from_def($item_def->{$sub_id});
204             } else {
205 0           my @id_parts = split / /, $sub_id;
206 0           $item{type} = $id_parts[0];
207             # make submenu when finding the menu command
208 0 0         if ($item{type} eq 'menu'){
209 0           $item{id} = $id_parts[1];
210 0           $item{label}= $menu_l18n->{label}{$id_parts[1]};
211 0   0       $item{help} = $menu_l18n->{help}{$id_parts[1]} || '';
212 0           $item{data} = assemble_data_from_def($item_def->{$sub_id});
213 0 0         $item{icon} = $id_parts[2] if $id_parts[2];
214             }
215             }
216             # menu items
217             } else {
218 0           $pos = index $item_def, ' ';
219 0 0         next if $pos == -1;
220 0           $item{type} = substr $item_def, 0, $pos;
221 0           $cmd_name = substr $item_def, $pos+1;
222 0 0         if ($item{type} eq 'menu'){
223 0           $item{id} = $cmd_name;
224 0           $item{label} = $menu_l18n->{label}{$cmd_name};
225             } else {
226 0           $cmd_data = Kephra::CommandList::get_cmd_properties( $cmd_name );
227             # skipping when command call is missing
228 0 0 0       next unless ref $cmd_data and exists $cmd_data->{call};
229 0           for ('call','enable','state','label','help','icon'){
230 0 0         $item{$_} = $cmd_data->{$_} if $cmd_data->{$_};
231             }
232 0 0         $item{label} .= "\t " . $cmd_data->{key} . "`" if $cmd_data->{key};
233             }
234             }
235 0           push @mds, \%item;
236             }
237 0           return \@mds;
238             }
239            
240             sub eval_data { # eval menu data structures (MDS) to wxMenus
241 0     0 0   my $menu_id = shift;
242 0 0         return unless defined $menu_id;
243             #emty the old or create new menu under the given ID
244 0           my $menu = _ref($menu_id);
245 0 0 0       if (defined $menu and $menu) { $menu->Delete( $_ ) for $menu->GetMenuItems }
  0            
246 0           else { $menu = Wx::Menu->new() }
247            
248 0           my $menu_data = shift;
249 0 0         unless (ref $menu_data eq 'ARRAY') {
250 0           _ref($menu_id, $menu);
251 0           return $menu;
252             }
253            
254 0           my $win = Kephra::App::Window::_ref();
255 0           my $kind;
256 0 0         my $item_id = defined $menu{$menu_id}{item_id}
257             ? $menu{$menu_id}{item_id}
258             : $Kephra::app{GUI}{masterID}++ * 100;
259 0           $menu{$menu_id}{item_id} = $item_id;
260            
261 0           for my $item_data (@$menu_data){
262 0 0 0       if (not $item_data->{type} or $item_data->{type} eq 'separator'){
    0          
263 0           $menu->AppendSeparator;
264             }
265             elsif ($item_data->{type} eq 'menu'){
266 0 0         my $submenu = ref $item_data->{data} eq 'ARRAY'
267             ? eval_data( $item_data->{id}, $item_data->{data} )
268             : ready( $item_data->{id} );
269 0 0         $item_data->{help} = '' unless defined $item_data->{help};
270 0           my @params = ( $menu, $item_id++, $item_data->{label},$item_data->{help},
271             &Wx::wxITEM_NORMAL
272             );
273 0 0         push @params, $submenu if is ($submenu);
274 0           my $menu_item = Wx::MenuItem->new( @params );
275 0 0         if (defined $item_data->{icon}) {
276 0           my $bmp = Kephra::CommandList::get_cmd_property
277             ( $item_data->{icon}, 'icon' );
278 0 0 0       $menu_item->SetBitmap( $bmp )
279             if ref $bmp eq 'Wx::Bitmap' and not Wx::wxMAC();
280             }
281             #Wx::Event::EVT_MENU_HIGHLIGHT($win, $item_id-1, sub {
282             # Kephra::App::StatusBar::info_msg( $item_data->{help} )
283             #});
284 0           $menu->Append($menu_item);
285             }
286             else { # create normal items
287 0 0         if ($item_data->{type} eq 'checkitem'){$kind = &Wx::wxITEM_CHECK}
  0 0          
  0 0          
288 0           elsif ($item_data->{type} eq 'radioitem'){$kind = &Wx::wxITEM_RADIO}
289             elsif ($item_data->{type} eq 'item') {$kind = &Wx::wxITEM_NORMAL}
290 0           else { next; }
291            
292 0   0       my $menu_item = Wx::MenuItem->new
293             ($menu, $item_id, $item_data->{label}||'', '', $kind);
294 0 0         if ($item_data->{type} eq 'item') {
295 0 0         if (ref $item_data->{icon} eq 'Wx::Bitmap') {
296 0 0         $menu_item->SetBitmap( $item_data->{icon} ) unless Wx::wxMAC();
297             }
298             else {
299             # insert fake empty icons
300             # $menu_item->SetBitmap($Kephra::temp{icon}{empty})
301             }
302             }
303            
304             add_onopen_check( $menu_id, 'enable_'.$item_id, sub {
305 0     0     $menu_item->Enable( $item_data->{enable}() );
306 0 0         } ) if ref $item_data->{enable} eq 'CODE';
307             add_onopen_check( $menu_id, 'check_'.$item_id, sub {
308 0     0     $menu_item->Check( $item_data->{state}() )
309 0 0         } ) if ref $item_data->{state} eq 'CODE';
310            
311 0           Wx::Event::EVT_MENU ($win, $menu_item, $item_data->{call} );
312             Wx::Event::EVT_MENU_HIGHLIGHT($win, $menu_item, sub {
313 0     0     Kephra::App::StatusBar::info_msg( $item_data->{help} )
314 0 0         }) if $item_data->{help} ;
315 0           $menu->Append( $menu_item );
316 0           $item_id++;
317             }
318 0           1; # sucess
319             }
320            
321 0     0     Kephra::EventTable::add_call('menu.open', 'menu_'.$menu, sub {ready($menu_id)});
  0            
322 0           _ref($menu_id, $menu);
323 0           return $menu;
324             }
325            
326             sub destroy {
327 0     0 0   my $menu_ID = shift;
328 0           my $menu = _ref( $menu_ID );
329 0 0         return unless $menu;
330 0           $menu->Destroy;
331 0           Kephra::EventTable::del_own_subscriptions( $menu_ID );
332             }
333            
334             1;
335