Prima

 view release on metacpan or  search on metacpan

Prima/Menus.pm  view on Meta::CPAN

		if ($desired_item < 0 || $desired_item > $n) {
			$desired_item = 0 if $desired_item > $n;
			$desired_item = $n if $desired_item < 0;
			while (1) {
				my $object = $c->[$desired_item]->[OBJECT];
				last if $object && $object->selectable && $object->enabled;

				$desired_item += $direction_if_not_selectable;
				return if $desired_item < 0 || $desired_item > $n;
			}
		}
	}

	return if $desired_item == $self->selectedItem;
	$self->selectedItem($desired_item);
	return 1;
}

sub selectedItem
{
	return $_[0]->{selectedItem} unless $#_;
	my ( $self, $i) = @_;
	my $n = @{ $self->{cache} } - 1;
	$i = -1 if $i < 0;
	$i = $n if $i > $n;

	return if $i == $self->{selectedItem};
	my $old = $self->{selectedItem};
	$self->{selectedItem} = $i;
	$self->invalidate_rect( $self->get_item_rect($old) ) if $old >= 0;
	$self->invalidate_rect( $self->get_item_rect($i) )   if $i   >= 0;
}

sub enter_submenu
{
	my $self = shift;
	my $i = $self->selectedItem;
	return if $i < 0;

	if ( $self->{submenu} ) {
		return if $i == $self->{submenu_index};
		$self->{submenu}->cancel;
		undef $self->{submenu};
		undef $self->{submenu_index};
	}

	my ($itemid, $index);
	if ( defined ( $itemid = $self->{cache}->[$i]->[ITEMID] )) {
		return unless $self-> menu-> is_submenu($itemid);
		$index = 0;
	} else {
		$itemid = $self-> parent;
		$index  = $self-> parentIndex + @{ $self->{cache} } - 1;
	}

	$self->notify(qw(Submenu), $i);
	return unless $self->alive;

	$self->{submenu_index} = $i;

	my $submenu = $self->{submenu} = $self->insert( $self->root->popupClass,
		visible        => 0,
		root           => $self->root,
		parent         => $itemid,
		parentIndex    => $index,
		menu           => $self->menu,
		ownerColor     => 1,
		ownerBackColor => 1,
		ownerFont      => 1,
		( map { $_ , $self->$_() } 
			qw(hiliteColor hiliteBackColor disabledColor disabledBackColor)),
	);
	$submenu->origin($self->position_submenu);
	$submenu->show;
	$submenu->bring_to_front;
}

sub execute_selected
{
	my $self = shift;
	my $i = $self->selectedItem;
	return if $i < 0;

	my $cache = $self->{cache}->[$i];
	return $self-> enter_submenu if ! defined $cache->[ITEMID] or $self-> menu-> is_submenu($cache->[ITEMID]);

	$self->root->cancel;
	$cache->[OBJECT]-> execute if $cache->[OBJECT];
}

sub execute_item
{
	my ($self, $itemid) = @_;
	return unless defined $itemid;
	return $self-> enter_submenu if $self-> menu-> is_submenu($itemid);

	$self->root->cancel;
	$self->new_from_itemid( $itemid )->execute;
}

sub xy2item
{
	my ( $self, $x, $y ) = @_;

	my $c = $self->{cache};
	for ( my $i = 0; $i < @$c; $i++) {
		my ($X1,$Y1,$X2,$Y2) = @{ $c->[$i] };
		$X2 += $X1;
		$Y2 += $Y1;
		return $i if $x >= $X1 && $y >= $Y1 && $x <= $X2 && $y < $Y2;
	}

	return -1;
}

sub on_keydown
{
	my ( $self, $code, $key, $mod, $repeat) = @_;

	my $submenu = $self;
	my $level = 0;

Prima/Menus.pm  view on Meta::CPAN

sub on_itemchanged
{
	my $self = shift;
	$self-> reset;
}

sub on_paint
{
	my ( $self, $canvas ) = @_;
	my @c = map { $self->colorIndex($_) } ci::NormalText .. ci::Dark3DColor;
	@c[ci::NormalText, ci::Normal] = @c[ci::DisabledText, ci::Disabled] unless $self->enabled;
	my @sz = $self->size;
	my $caches = $self->{cache};
	if ( $self->{vertical} ) {
		$self->rect_bevel( $canvas, 0, 0, $sz[0]-1, $sz[1]-1,
			width => $self->{borderSize}, 
			fill => $c[ci::Normal],
		);
	} else {
		$self->clear;
	}

	for ( my $i = 0; $i < @$caches; $i++) {
		my $cache = $caches->[$i];
		next unless $cache->[OBJECT];
		my @o = @{$cache}[X, Y];
		my @s = @{$cache}[WIDTH, HEIGHT];
		$cache->[OBJECT]->draw( $canvas, \@c, @$cache[X,Y,WIDTH,HEIGHT], $i == $self->{selectedItem});
	}
}

sub new_from_itemid
{
	my ( $self, $itemid ) = @_;
	return $self->root->{itemClass}-> new_from_itemid(
		$self-> root->menu, $itemid,
		vertical => $self->vertical,
	);
}

sub new_guillemots
{
	my ( $self ) = @_;
	return $self->root->{itemClass}-> new_from_itemid(
		$self->root->menu, undef,
		vertical => $self->vertical,
	);
}

package Prima::Menu::Transient;
use vars qw(@ISA);
@ISA = qw(Prima::Menu::Common);

BEGIN { Prima::Menu::Common::_export_constants(); };

sub profile_default
{
	my %def = %{$_[ 0]-> SUPER::profile_default};
	return {
		%def,
		font           => Prima::Widget->get_default_popup_font,
		widgetClass    => wc::Popup,
		clipOwner      => 0,
		selectable     => 0,
	}
}

sub profile_check_in
{
	my ( $self, $p, $default) = @_;
	$p->{font} //= Prima::Widget->get_default_popup_font;
	$self-> SUPER::profile_check_in( $p, $default);
}

sub init
{
	my $self = shift;
	$self->{vertical} = 1;
	my %profile = $self-> SUPER::init(@_);
	$self->root($profile{root});
	$self->reset;
	return %profile;
}

sub root        { $#_ ? $_[0]->{root}        = $_[1] : $_[0]->{root} }
sub menu        { shift->root->menu }

sub cancel
{
	my $self = shift;
	my $o = $self->owner;
	if ( $o && $o->isa('Prima::Menu::Common')) {
		undef $o->{submenu};
		$self->destroy;
	}
}

sub reset
{
	my $self = shift;
	return if $self->{lock_reset};

	my @cache;
	my @size = $self-> size;
	$self-> begin_paint_info;
	my $space_width = $self->get_text_width(' ');
	my $menu = $self-> menu or return;

	my $items = $menu-> get_children( $self-> parent );
	splice( @$items, 0, $self->parentIndex ) if $self->parentIndex > 0;
	my $borderSize = $self->{borderSize};
	my ($x, $y) = (0,0);
	my @ds = $::application->size;
	my $r = $ds[0] - $borderSize * 2;
	my $h = $ds[1] - $borderSize * 2;
	my @max = (0,0);
	my %storage;
	for my $itemid ( @$items ) {
		my $object = $self->new_from_itemid( $itemid );
		my ($W, $H) = $object-> size($self, \%storage);
		if ( $y + $H > $h ) {
			$object = $self->new_guillemots;
			($W, $H) = $object-> size($self);
			$max[0] = $W if $max[0] < $W;
			if ( @cache ) {
				@{$cache[-1]}[WIDTH,HEIGHT,ITEMID,OBJECT] = ($W, $H, undef, $object);
			} else {
				push @cache, [ $x, $y, $W, $H, undef, $object ];
			};
			last;
		}

Prima/Menus.pm  view on Meta::CPAN

sub on_menuchange
{
	my ( $self, $menu, $what, @params) = @_;
	$self->reset;
}

sub set_menu
{
	my ( $self, $menu ) = @_;
	if ($self->{menu}) {
		if ($self->{menu_hook}) {
			$self-> {menu}-> remove_notification( $self-> {menu_hook} );
			undef $self->{menu_hook};
		}
		$self->cancel;
	}
	$self->{menu} = $menu;
	$self->{menu_hook} = $menu-> add_notification( Change => \&on_menuchange, $self )
		if $menu;
	$self->reset;
	$self->repaint;
}

sub on_menuenter
{
	my $self = shift;
	my $owner = $self->owner;

	$self-> {hooks} = [];
	my $cancel = sub { shift-> cancel };
	while ( $owner && $owner != $::application) {
		push @{$self->{hooks}}, $owner, $owner->add_notification( Move => $cancel, $self);
		$owner = $owner->owner;
	}
}

sub on_menuleave
{
	my $self = shift;
	my $hooks = $self->{hooks} // [];
	for ( my $i = 0; $i < @$hooks; $i += 2 ) {
		my ( $owner, $id ) = @{$hooks}[$i,$i+1];
		$owner->remove_notification($id);
	}
	$self->{hooks} = undef;
}

sub on_leave
{
	my $self = shift;

	alarm( 0.2, sub {
		my $f = $::application->get_focused_widget;
		$self->cancel unless $f && $f->isa('Prima::Menu::Common') && $f->root == $self->root;
	});
}

sub on_destroy { undef $_[0]->{hooks} }

sub itemClass   { $#_ ? $_[0]->{itemClass}   = $_[1] : $_[0]->{itemClass}  }
sub popupClass  { $#_ ? $_[0]->{popupClass}  = $_[1] : $_[0]->{popupClass}  }

package Prima::Menu::Popup;
use vars qw(@ISA);
@ISA = qw(Prima::Menu::Transient Prima::Menu::Root);

{
my %RNT = (
	%{Prima::Menu::Common-> notification_types()},
	MenuEnter  => nt::Default,
	MenuLeave  => nt::Default,
);

sub notification_types { return \%RNT; }
}

sub profile_default
{
	my %def = %{$_[ 0]-> SUPER::profile_default};
	return {
		%def,
		visible        => 0,
		itemClass      => 'Prima::Menu::Item',
		popupClass     => 'Prima::Menu::Transient',
		parent         => "",
		parentIndex    => 0,
		selectable     => 1,
	}
}

sub init
{
	my $self = shift;

	my %profile = $self-> SUPER::init(@_);
	$self->$_($profile{$_}) for qw(itemClass popupClass menu);
	return %profile;
}

sub root { $_[0] }
sub menu { $#_ ? $_[0]->set_menu($_[1]) : $_[0]->{menu} }

sub cancel
{
	my $self = shift;
	my $s = $self->{submenu};
	while ( $s ) {
		my $ss = $s->{submenu};
		$s->destroy;
		$s = $ss;
	}
	$self->{submenu} = undef;
	$self->{selectedItem} = -1;
	$self-> notify(q(MenuLeave));
	$self-> hide;
}

sub popup
{
	my ( $self, $x, $y ) = @_;
	unless ( defined($x) && defined($y)) {
		my @p = $self->owner->pointerPos;
		$x //= $p[0];
		$y //= $p[1];
	}
	my @sz = $self->size;
	my @dp = $::application->size;

	if ( $x + $sz[0] > $dp[0]) {
		if ( $x > $sz[0] ) {
			$x -= $sz[0];
		} else {
			$x = 0;
		}
	}

	if ( $y > $sz[1] ) {
		$y -= $sz[1];
	} elsif ( $y + $sz[1] > $dp[1] ) {
		$y = 0;
	}

	$self-> notify(q(MenuEnter));
	$self-> origin($x, $y);
	$self-> show;
	$self-> bring_to_front;
	$self-> focus;
}

package Prima::Menu::Bar;
use vars qw(@ISA);
@ISA = qw(Prima::Menu::Common Prima::Menu::Root);

BEGIN { Prima::Menu::Common::_export_constants(); };

{
my %RNT = (
	%{Prima::Menu::Common-> notification_types()},
	MenuEnter  => nt::Default,
	MenuLeave  => nt::Default,
);

sub notification_types { return \%RNT; }
}

sub profile_default
{
	my %def = %{$_[ 0]-> SUPER::profile_default};
	return {
		%def,
		font           => Prima::Window->get_default_menu_font,
		autoHeight     => 1,
		itemClass      => 'Prima::Menu::Item',
		popupClass     => 'Prima::Menu::Transient',
		parent         => "", # root menu item name
		selectable     => 1,
		widgetClass    => wc::Menu,
	}
}

sub profile_check_in
{
	my ( $self, $p, $default) = @_;
	$p-> {autoHeight} = 0 if !exists $p->{autoHeight} && (
		exists $p-> {height} || exists $p-> {size} || exists $p-> {rect} || ( exists $p-> {top} && exists $p-> {bottom})
	);
	$p->{font} //= Prima::Window->get_default_menu_font;
	$self-> SUPER::profile_check_in( $p, $default);
}

sub init
{
	my $self = shift;

	$self->{autoHeight} = 1;
	$self->{vertical} = 0;
	my %profile = $self-> SUPER::init(@_);
	$self-> {lock_reset} = 1;
	$self->$_($profile{$_}) for qw(autoHeight menu itemClass popupClass);
	delete $self-> {lock_reset};
	$self->reset;

	return %profile;
}

sub root { $_[0] }
sub menu { $#_ ? $_[0]->set_menu($_[1]) : $_[0]->{menu} }

sub autoHeight
{
	return $_[0]->{autoHeight} unless $#_;
	my ( $self, $autoHeight ) = @_;
	$self->{autoHeight} = $autoHeight;
	$self-> geomHeight( $self-> font-> height + 10 + $self-> {borderSize} * 2 )
		if $autoHeight;
}

sub cancel
{
	my $self = shift;
	my $s = $self->{submenu};
	while ( $s ) {
		my $ss = $s->{submenu};
		$s->destroy;
		$s = $ss;
	}
	$self->{submenu} = undef;
	$self->{selectedItem} = -1;
	$self->focused(0);
	$self->repaint;
	$self-> notify(q(MenuLeave));
}


sub reset
{
	my $self = shift;
	return if $self->{lock_reset};

	my @cache;
	my @size = $self-> size;
	$self-> begin_paint_info;
	my $space_width = $self->get_text_width(' ');
	my $menu = $self->menu or return;

	my $items = $menu-> get_children( $self-> parent );
	splice( @$items, 0, $self->parentIndex ) if $self->parentIndex > 0;
	my $right_align;
	my ($x, $y) = ($self->{borderSize}) x 2;
	my $h = $size[1] - $y * 2;
	my $r = $size[0] - $x * 2;
	my %storage;
	for my $itemid ( @$items ) {
		if ( $self-> menu-> is_separator($itemid)) {
			push @cache, $right_align //= [0,0,0,0,undef,undef];
			next;
		}

		my $object = $self->new_from_itemid($itemid);

Prima/Menus.pm  view on Meta::CPAN

	return if $i < 0;
	my $o = $self->{cache}->[$i]->[OBJECT];
	return unless $o && $o->selectable && $o->enabled;

	if ( $i != $self->selectedItem ) {
		$self->selectedItem($i);
		$self->enter_submenu;
	}
}

sub on_enter
{
	my $self = shift;
	$self->selectedItem(0) if
		$self->selectedItem < 0 && $self->enabled && @{ $self->{cache} // [] };
	$self->repaint;
}

sub on_disable
{
	my $self = shift;
	$self->cancel;
	$self->repaint;
}

sub on_enable
{
	my $self = shift;
	$self->repaint;
}

sub on_size
{
	my $self = $_[0];
	$self->cancel;
	$self->reset;
}

1;

=pod

=head1 NAME

Prima::Menus - menu widgets

=head1 DESCRIPTION

This module contains classes that can create menu widgets used as
normal widget, without special consideration about system-depended
menus.

=head1 SYNOPSIS

	use Prima qw(Application Menus);
	my $w = Prima::MainWindow->new(
		accelItems => [['~File' => [
			['Exit' => sub { exit } ],
		]]],
		onMouseDown => sub {
			Prima::Menu::Popup->new(menu => $_[0]-> accelTable)->popup;
		},
		height => 100,
	);
	$w->insert( 'Prima::Menu::Bar',
		pack  => { fill => 'x', expand => 1},
		menu  => $w-> accelTable,
	);
	run Prima;

=for podview <img src="Prima/menu.gif">

=for html <p><img src="https://raw.githubusercontent.com/dk/Prima/master/pod/Prima/menu.gif">

=head1 AUTHOR

Dmitry Karasik, E<lt>dmitry@karasik.eu.orgE<gt>.

=head1 SEE ALSO

L<Prima>, L<Prima::Menu>, F<examples/menu.pl>

=cut



( run in 2.159 seconds using v1.01-cache-2.11-cpan-364913b4093 )