package Kephra::Menu; our $VERSION = '0.18'; use strict; use warnings; my %menu; sub _all { \%menu } sub _ref { if ( is($_[1]) ) { $menu{$_[0]}{ref} = $_[1] } elsif ( exists $menu{$_[0]}{ref} ) { $menu{$_[0]}{ref} } } sub _data { $menu{$_[0]} if stored($_[0]) } sub is { 1 if ref $_[0] eq 'Wx::Menu' } sub stored { 1 if ref $menu{$_[0]} eq 'HASH'} sub set_absolete { $menu{$_[0]}{absolete} = 1 } sub not_absolete { $menu{$_[0]}{absolete} = 0 } sub is_absolete { $menu{$_[0]}{absolete} } sub set_update { $menu{$_[0]}{update} = $_[1] if ref $_[1] eq 'CODE' } sub get_update { $menu{$_[0]}{update} } sub no_update { delete $menu{$_[0]}{update} if stored($_[0]) } sub add_onopen_check { return until ref $_[2] eq 'CODE'; $menu{ $_[0] }{onopen}{ $_[1] } = $_[2]; } sub del_onopen_check { return until $_[1]; delete $menu{$_[0]}{onopen}{$_[1]} if exists $menu{$_[0]}{onopen}{$_[1]}; } sub ready { # make menu ready for display my $id = shift; if ( stored($id) ){ my $menu = _data($id); if ($menu->{absolete} and $menu->{update}) { $menu->{absolete} = 0 if $menu->{update}() } if (ref $menu->{onopen} eq 'HASH') { $_->() for values %{$menu->{onopen}} } _ref($id); } } sub create_dynamic { # create on runtime changeable menus my ( $menu_id, $menu_name ) = @_ ; if ($menu_name eq '&insert_templates') { set_absolete($menu_id); set_update($menu_id, sub { my $cfg = Kephra::API::settings()->{file}{templates}; my $file = Kephra::Config::filepath($cfg->{directory}, $cfg->{file}); my $tmp = Kephra::Config::File::load( $file ); my @menu_data; if (exists $tmp->{template}){ $tmp = Kephra::Config::Tree::_convert_node_2_AoH(\$tmp->{template}); my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled}; my $filepath = Kephra::Document::Data::get_file_path() || "<$untitled>"; my $filename = Kephra::Document::Data::file_name() || "<$untitled>"; my $firstname = Kephra::Document::Data::first_name() || "<$untitled>"; for my $template ( @{$tmp} ) { my %item; $item{type} = 'item'; $item{label}= $template->{name}; $item{call} = sub { my $content = $template->{content}; $content =~ s/\[\$\$firstname\]/$firstname/g; $content =~ s/\[\$\$filename\]/$filename/g; $content =~ s/\[\$\$filepath\]/$filepath/g; Kephra::Edit::insert_text($content); }; $item{help} = $template->{description}; push @menu_data, \%item; eval_data($menu_id, \@menu_data); } return 1; } }); } elsif ($menu_name eq '&file_history'){ set_absolete($menu_id); set_update($menu_id, sub { my @menu_data = @{assemble_data_from_def ( ['item file-session-history-open-all', undef] )}; my $history = Kephra::File::History::get(); if (ref $history eq 'ARRAY') { my $nr = 0; for ( @$history ) { my $file = $_->{file_path}; push @menu_data, { type => 'item', label => ( File::Spec->splitpath( $file ) )[2], help => $file, call => eval 'sub {Kephra::File::History::open( '.$nr++.' )}', }; } } eval_data($menu_id, \@menu_data); return Kephra::File::History::had_init() ? 1 : 0; 1; # it was successful }); Kephra::EventTable::add_call ( 'document.list', 'menu_'.$menu_id, sub { set_absolete( $menu_id ) if Kephra::File::History::update(); } ); } elsif ($menu_name eq '&document_change') { set_update( $menu_id, sub { return unless exists $Kephra::temp{document}{buffer}; my $filenames = Kephra::Document::Data::all_file_names(); my $pathes = Kephra::Document::Data::all_file_pathes(); my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled}; my $space = ' '; my @menu_data; for my $nr (0 .. @$filenames-1){ my $item = \%{$menu_data[$nr]}; $space = '' if $nr == 9; $item->{type} = 'radioitem'; $item->{label} = $filenames->[$nr] ? $space.($nr+1)." - $filenames->[$nr] \t - $pathes->[$nr]" : $space.($nr+1)." - <$untitled> \t -"; $item->{call} = eval 'sub {Kephra::Document::Change::to_nr('.$nr.')}'; } }); #add_onopen_check( $menu_id, 'select', sub { # my $menu = _ref($menu_id); # $menu->FindItemByPosition # ( Kephra::Document::Data::current_nr() )->Check(1) if $menu; #}); #Kephra::EventTable::add_call ( # 'document.list', 'menu_'.$menu_id, sub { set_absolete($menu_id) } #); } } sub create_static { # create solid, not on runtime changeable menus my ($menu_id, $menu_def) = @_; return unless ref $menu_def eq 'ARRAY'; not_absolete($menu_id); eval_data($menu_id, assemble_data_from_def($menu_def)); } sub create_menubar { #my $menubar = Wx::MenuBar->new(); #my $m18n = Kephra::Config::Localisation::strings()->{app}{menu}; #my ($pos, $menu_name); #for my $menu_def ( @$menubar_def ){ #for my $menu_id (keys %$menu_def){ # removing the menu command if there is one #$pos = index $menu_id, ' '; #if ($pos > -1){ #if ('menu' eq substr $menu_id, 0, $pos ){ #$menu_name = substr ($menu_id, $pos+1); # ignoring menu structure when command other that menu or blank #} else { next } #} else { #$menu_name = $menu_id; #} #$menubar->Append( #Kephra::Menu::create_static( $menu_name, $menu_def->{$menu_id}), #$m18n->{label}{$menu_name} #); #} #} } # create menu data structures (MDS) from menu skeleton definitions (command list) sub assemble_data_from_def { my $menu_def = shift; return unless ref $menu_def eq 'ARRAY'; my $menu_l18n = Kephra::Config::Localisation::strings()->{app}{menu}; my ($cmd_name, $cmd_data, $type_name, $pos, $sub_id); my @mds = (); # menu data structure for my $item_def (@$menu_def){ my %item; # creating separator if (not defined $item_def){ $item{type} = '' # sorting commented lines out } elsif (substr($item_def, -1) eq '#'){ next; # creating separator } elsif ($item_def eq '' or $item_def eq 'separator') { $item{type} = '' # eval a sublist } elsif (ref $item_def eq 'HASH'){ $sub_id = $_ for keys %$item_def; $pos = index $sub_id, ' '; # make submenu if keyname is without command if ($pos == -1){ $item{type} = 'menu'; $item{id} = $sub_id; $item{label} = $menu_l18n->{label}{$sub_id}; $item{help} = $menu_l18n->{help}{$sub_id} || ''; $item{data} = assemble_data_from_def($item_def->{$sub_id}); } else { my @id_parts = split / /, $sub_id; $item{type} = $id_parts[0]; # make submenu when finding the menu command if ($item{type} eq 'menu'){ $item{id} = $id_parts[1]; $item{label}= $menu_l18n->{label}{$id_parts[1]}; $item{help} = $menu_l18n->{help}{$id_parts[1]} || ''; $item{data} = assemble_data_from_def($item_def->{$sub_id}); $item{icon} = $id_parts[2] if $id_parts[2]; } } # menu items } else { $pos = index $item_def, ' '; next if $pos == -1; $item{type} = substr $item_def, 0, $pos; $cmd_name = substr $item_def, $pos+1; if ($item{type} eq 'menu'){ $item{id} = $cmd_name; $item{label} = $menu_l18n->{label}{$cmd_name}; } else { $cmd_data = Kephra::CommandList::get_cmd_properties( $cmd_name ); # skipping when command call is missing next unless ref $cmd_data and exists $cmd_data->{call}; for ('call','enable','state','label','help','icon'){ $item{$_} = $cmd_data->{$_} if $cmd_data->{$_}; } $item{label} .= "\t " . $cmd_data->{key} . "`" if $cmd_data->{key}; } } push @mds, \%item; } return \@mds; } sub eval_data { # eval menu data structures (MDS) to wxMenus my $menu_id = shift; return unless defined $menu_id; #emty the old or create new menu under the given ID my $menu = _ref($menu_id); if (defined $menu and $menu) { $menu->Delete( $_ ) for $menu->GetMenuItems } else { $menu = Wx::Menu->new() } my $menu_data = shift; unless (ref $menu_data eq 'ARRAY') { _ref($menu_id, $menu); return $menu; } my $win = Kephra::App::Window::_ref(); my $kind; my $item_id = defined $menu{$menu_id}{item_id} ? $menu{$menu_id}{item_id} : $Kephra::app{GUI}{masterID}++ * 100; $menu{$menu_id}{item_id} = $item_id; for my $item_data (@$menu_data){ if (not $item_data->{type} or $item_data->{type} eq 'separator'){ $menu->AppendSeparator; } elsif ($item_data->{type} eq 'menu'){ my $submenu = ref $item_data->{data} eq 'ARRAY' ? eval_data( $item_data->{id}, $item_data->{data} ) : ready( $item_data->{id} ); $item_data->{help} = '' unless defined $item_data->{help}; my @params = ( $menu, $item_id++, $item_data->{label},$item_data->{help}, &Wx::wxITEM_NORMAL ); push @params, $submenu if is ($submenu); my $menu_item = Wx::MenuItem->new( @params ); if (defined $item_data->{icon}) { my $bmp = Kephra::CommandList::get_cmd_property ( $item_data->{icon}, 'icon' ); $menu_item->SetBitmap( $bmp ) if ref $bmp eq 'Wx::Bitmap' and not Wx::wxMAC(); } #Wx::Event::EVT_MENU_HIGHLIGHT($win, $item_id-1, sub { # Kephra::App::StatusBar::info_msg( $item_data->{help} ) #}); $menu->Append($menu_item); } else { # create normal items if ($item_data->{type} eq 'checkitem'){$kind = &Wx::wxITEM_CHECK} elsif ($item_data->{type} eq 'radioitem'){$kind = &Wx::wxITEM_RADIO} elsif ($item_data->{type} eq 'item') {$kind = &Wx::wxITEM_NORMAL} else { next; } my $menu_item = Wx::MenuItem->new ($menu, $item_id, $item_data->{label}||'', '', $kind); if ($item_data->{type} eq 'item') { if (ref $item_data->{icon} eq 'Wx::Bitmap') { $menu_item->SetBitmap( $item_data->{icon} ) unless Wx::wxMAC(); } else { # insert fake empty icons # $menu_item->SetBitmap($Kephra::temp{icon}{empty}) } } add_onopen_check( $menu_id, 'enable_'.$item_id, sub { $menu_item->Enable( $item_data->{enable}() ); } ) if ref $item_data->{enable} eq 'CODE'; add_onopen_check( $menu_id, 'check_'.$item_id, sub { $menu_item->Check( $item_data->{state}() ) } ) if ref $item_data->{state} eq 'CODE'; Wx::Event::EVT_MENU ($win, $menu_item, $item_data->{call} ); Wx::Event::EVT_MENU_HIGHLIGHT($win, $menu_item, sub { Kephra::App::StatusBar::info_msg( $item_data->{help} ) }) if $item_data->{help} ; $menu->Append( $menu_item ); $item_id++; } 1; # sucess } Kephra::EventTable::add_call('menu.open', 'menu_'.$menu, sub {ready($menu_id)}); _ref($menu_id, $menu); return $menu; } sub destroy { my $menu_ID = shift; my $menu = _ref( $menu_ID ); return unless $menu; $menu->Destroy; Kephra::EventTable::del_own_subscriptions( $menu_ID ); } 1;