#!/usr/bin/perl -w use strict; use Glib qw/TRUE FALSE/; # this packaged based on work of Dirk van der Walt's # package Gtk2::Ex::Notify; package Gtk2::Ex::ZNotify; use Gtk2; use Glib qw/TRUE FALSE/; use constant wbsize => 16; #window border size use constant messageTextWidth => 300; use Glib::Object::Subclass Gtk2::Window:: , # parent class, derived from Glib::Object signals => { show => \&on_show, size_allocate => \&on_size_allocate, style_set => \&on_style_set, expose_event => \&on_expose_event, enter_notify_event => \&on_enter_notify_event, leave_notify_event => \&on_leave_notify_event, }, properties => [ Glib::ParamSpec->object( 'parent_widget', 'parent_widget', 'The parent widget on which this popup will show', 'Gtk2::Widget', [ qw/readable writable/ ] ), Glib::ParamSpec->string( 'message', 'message', 'The heading for this message', 'This is sample heading', [ qw/readable writable/ ] ), Glib::ParamSpec->enum( 'messageType', 'messageType', 'Type of message this is', 'Gtk2::MessageType', 'info', [ qw/readable writable/ ] ), Glib::ParamSpec->int( 'timeout', 'timeout', 'Timeout in milli-seconds before hide, 0 = never hide', 0, 10000000, 0, [ qw/readable writable/ ] ), Glib::ParamSpec->int( 'x', 'x', 'x position for pointer', 0, 64000, 0, [ qw/readable writable/ ] ), Glib::ParamSpec->int( 'y', 'y', 'y position for pointer', 0, 64000, 0, [ qw/readable writable/ ] ), ]; sub INIT_INSTANCE { my $self = shift; $self->{ activeBackgroundColor } = new Gtk2::Gdk::Color( ( 249 * 257 ), ( 253 * 257 ), ( 202 * 257 ) ); #Remember to * with 257!! $self->{ inactiveBackgroundColor } = new Gtk2::Gdk::Color( ( 255 * 257 ), ( 255 * 257 ), ( 255 * 257 ) ); #Initial setup $self->app_paintable( TRUE ); $self->{ activebackground } = undef; $self->{ inactivebackground } = undef; #Create the layout of the window my $outBox = new Gtk2::HBox(); $outBox->set_border_width( wbsize ); $self->add( $outBox ); my $closeBox = new Gtk2::VBox(); $closeBox->set_border_width( 3 ); $outBox->pack_end( $closeBox, TRUE, TRUE, 0 ); my $eBox = new Gtk2::EventBox(); #Add event handler for the "close" event box $eBox->signal_connect( 'button-press-event' => sub { $self->hide(); $self->destroy(); } ); my $closeImg = new Gtk2::Image(); $closeImg->set_from_stock( 'gtk-close', 'menu' ); $eBox->add( $closeImg ); $closeBox->pack_start( $eBox, FALSE, FALSE, 0 ); my $padder = new Gtk2::Label( "" ); $outBox->pack_start( $padder, FALSE, FALSE, 5 ); my $vbox = new Gtk2::VBox(); $vbox->set_border_width( 10 ); $outBox->pack_start( $vbox, TRUE, TRUE, 0 ); my $hbox = new Gtk2::HBox(); $hbox->set_spacing( 5 ); my $iconVBox = new Gtk2::VBox(); my $msgImage = new Gtk2::Image(); $self->{ msgImage } = $msgImage; #Needed in order to configure after creation! $iconVBox->pack_start( $msgImage, FALSE, FALSE, 0 ); $hbox->pack_start( $iconVBox, FALSE, FALSE, 0 ); $vbox->pack_start( $hbox, FALSE, FALSE, 0 ); my $messageVBox = new Gtk2::VBox(); $hbox->pack_start( $messageVBox, TRUE, TRUE, 0 ); my $l = new Gtk2::Label(); $l->set_line_wrap( FALSE ); $l->set_use_markup( TRUE ); $l->set_selectable( FALSE ); $l->set_alignment( 0, 0 ); $l->set_line_wrap( TRUE ); $l->{ width_request } = messageTextWidth; $self->{ lblHeading } = $l; #Needed in order to configure after creation! $messageVBox->pack_start( $l, FALSE, TRUE, 0 ); $self->{ msgVBox } = $messageVBox; #Needed in order to configure after creation! my $spacer = new Gtk2::Label(); $spacer->set_markup( "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" ); $messageVBox->pack_end( $spacer, FALSE, FALSE, 0 ); # the timeout eventhandler ID $self->{ closeWindowTimeoutID } = 0; #Add the background property in order to change it $self->{ background } = undef; } #Chose this event to set up the various widgets in order so they can be in place BEFORE #The size_allocation happens sub on_style_set() { my ( $self, $event ) = @_; #Set up the heading $self->{ lblHeading }->set_markup( "" . $self->{ message } . "" ); #set up the icon to display ( $self->{ messageType } =~ m/info/ ) && ( $self->{ msgImage }->set_from_stock( 'gtk-dialog-info', 'button' ) ); ( $self->{ messageType } =~ m/warning/ ) && ( $self->{ msgImage }->set_from_stock( 'gtk-dialog-warning', 'button' ) ); ( $self->{ messageType } =~ m/question/ ) && ( $self->{ msgImage }->set_from_stock( 'gtk-dialog-question', 'button' ) ); ( $self->{ messageType } =~ m/error/ ) && ( $self->{ msgImage }->set_from_stock( 'gtk-dialog-error', 'button' ) ); $self->signal_chain_from_overridden( $event ); return FALSE; } #This will start the timeout handler if the timeout value was more than 0 sub on_show() { my ( $self, $event ) = @_; $self->signal_chain_from_overridden; if ( ( $self->{ closeWindowTimeoutID } == 0 ) && $self->{ timeout } > 0 ) { $self->{ closeWindowTimeoutID } = Glib::Timeout->add( $self->{ timeout }, sub { hide_window_callback( $self ); return FALSE; } ); } } sub hide_window_callback() { my ( $self ) = @_; $self->hide(); $self->destroy(); return FALSE; } #this will do some magick to create the outline for the notification window sub on_size_allocate() { my ( $self, $sized ) = @_; #Very important to be at the start :) $self->signal_chain_from_overridden( $sized ); if ( !( $self->{ background } ) ) { my $mask; #Set the values for the active and inactive backgrounds ( $self->{ activebackground }, $self->{ inactivebackground }, $mask ) = RenderBubbles( $self, $sized ); $self->{ background } = $self->{ inactivebackground }; if ( $mask ) { $self->shape_combine_mask( $mask, 0, 0 ); #Shape the window } else { print "mask was null, could not shape the window\n"; } } } sub on_expose_event() { my ( $self, $event ) = @_; my $state = $self->state; #print "EXPOSE EVENT $state\n"; if ( $state eq 'active' ) { $self->{ background } = $self->{ activebackground }; } else { $self->{ background } = $self->{ inactivebackground }; } my $gc = Gtk2::Gdk::GC->new( $self->window ); $self->window->draw_pixbuf( $gc, $self->{ background }, 0, 0, 0, 0, $self->{ background }->get_width(), $self->{ background }->get_height(), 'none', 0, 0 ); $self->signal_chain_from_overridden( $event ); return FALSE; } sub on_enter_notify_event() { my ( $self, $event ) = @_; $self->signal_chain_from_overridden( $event ); if ( $self->{ closeWindowTimeoutID } != 0 ) { #Remove the timeout cause the user is interested in the message Glib::Source->remove( $self->{ closeWindowTimeoutID } ); $self->{ closeWindowTimeoutID } = 0; } $self->set_state( 'active' ); return FALSE; } sub on_leave_notify_event() { my ( $self, $event ) = @_; $self->signal_chain_from_overridden( $event ); $self->set_state( 'normal' ); $self->queue_draw(); if ( $self->{ closeWindowTimeoutID } != 0 ) { Glib::Source->remove( $self->{ closeWindowTimeoutID } ); $self->{ closeWindowTimeoutID } = 0; } if ( $self->{ timeout } > 0 ) { #restart the timeout they lost interest $self->{ closeWindowTimeoutID } = Glib::Timeout->add( $self->{ timeout }, sub { &hide_window_callback( $self ); return FALSE; } ); } return FALSE; } sub RenderBubbles() { my ( $win, $size ) = @_; my ( $pbactive, $pbinactive, $pbbm ); my ( $pmHeight, $pmWidth ); my $daPixmap; #Drawing Area pixmap my $daBitmap; #Drawing Area bitmap my $self = $win; $win->realize(); $pmHeight = $size->height - ( wbsize * 2 ); $pmWidth = $size->width - ( wbsize * 2 ); my $gc = Gtk2::Gdk::GC->new( $win->window ); #Create a Graphic Context #print $win->window->get_window_type; #----------------------------------------------------- #--------- Build active Pixbuf------------------------ #----------------------------------------------------- my $pm = Gtk2::Gdk::Pixmap->new( $win->window, $size->width, $size->height, -1 ); # Paint the background white $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'white' ) ); $pm->draw_rectangle( $gc, TRUE, 0, 0, $size->width, $size->height ); #/*********************************** # draw painted oval window #***********************************/ # Paint the inside of the window $gc->set_rgb_fg_color( $self->{ activeBackgroundColor } ); $pm->draw_polygon( $gc, TRUE, CalculateRect( wbsize, wbsize, $pmHeight, $pmWidth ) ); # Paint the border of the window $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'black' ) ); $pm->draw_polygon( $gc, FALSE, CalculateRect( wbsize, wbsize, $pmHeight - 1, $pmWidth - 1 ) ); #/*********************************** # add tab to bitmap #***********************************/ #// Draw colored pointer $gc->set_rgb_fg_color( $self->{ activeBackgroundColor } ); my @list = CalcPointerMoveWindow( $self, $size->width, $size->height, wbsize, $self->{ parent_widget } ); $pm->draw_polygon( $gc, TRUE, @list ); $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'black' ) ); #// subtract one because the fill above used and extra line $pm->draw_line( $gc, $list[ 0 ], ( $list[ 1 ] - 1 ), $list[ 2 ], $list[ 3 ] ); $pm->draw_line( $gc, $list[ 2 ], $list[ 3 ], $list[ 4 ], ( $list[ 5 ] - 1 ) ); my $pb = Gtk2::Gdk::Pixbuf->new( 'rgb', TRUE, 8, $size->width, $size->height ); $pb->get_from_drawable( $pm, $pm->get_colormap, 0, 0, 0, 0, $size->width, $size->height ); $pb = $pb->add_alpha( TRUE, 255, 255, 255 ); ( $daPixmap, $daBitmap ) = $pb->render_pixmap_and_mask( 255 ); $pbactive = $pb; #The active layout $pbbm = $daBitmap; #The mask #----------------------------------------------------- #--------- Build INACATIVE Pixbuf--------------------- #----------------------------------------------------- #// Reset backgound to white and get next bitmap (The inactive one) $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'white' ) ); $pm->draw_rectangle( $gc, TRUE, 0, 0, $size->width, $size->height ); #// Paint the border of the window $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'black' ) ); $pm->draw_polygon( $gc, FALSE, CalculateRect( wbsize, wbsize, $pmHeight - 1, $pmWidth - 1 ) ); #// Draw white pointer $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'white' ) ); $pm->draw_polygon( $gc, TRUE, @list ); $gc->set_rgb_fg_color( Gtk2::Gdk::Color->parse( 'black' ) ); #// subtract one because the fill above used and extra line $pm->draw_line( $gc, $list[ 0 ], ( $list[ 1 ] - 1 ), $list[ 2 ], $list[ 3 ] ); $pm->draw_line( $gc, $list[ 2 ], $list[ 3 ], $list[ 4 ], ( $list[ 5 ] - 1 ) ); $pb = Gtk2::Gdk::Pixbuf->get_from_drawable( $pm, $pm->get_colormap, 0, 0, 0, 0, $size->width, $size->height ); $pbinactive = $pb; #fetch the incative bixbuf my @return_value = ( $pbactive, $pbinactive, $pbbm ); return @return_value; } sub CalcPointerMoveWindow() { my ( $self, $width, $height, $wbsize, $parentWidget ) = @_; my ( $parentX, $parentY, $parentWidth, $parentHeight, $parentDepth ); my ( $midParentX, $midParentY, $posX, $posY ); my $ptsize = $wbsize; my ( $drawRight, $drawDown ); # test for a real window or an x y option if( defined $parentWidget ){ ( $parentX, $parentY, $parentWidth, $parentHeight, $parentDepth ) = $parentWidget->window->get_geometry(); ( $parentX, $parentY ) = $parentWidget->window->get_origin(); $midParentX = $parentX + ( $parentWidth / 2 ); $midParentY = $parentY + ( $parentHeight / 2 ); }else{ $parentX = $self->{x}; $parentY = $self->{y}; $midParentX = $self->{x}; $midParentY = $self->{y}; } my ($x0, $y0, $width0, $height0, $depth) = Gtk2::Gdk::Screen->get_default->get_root_window->get_geometry; #print "$x0, $y0, $width0, $height0, $depth\n"; # Do we draw to the left or to the right of the icon/window if ( $parentX >= $width0/2 ) { $drawRight = FALSE; $posX = $midParentX - $width; } else { $drawRight = TRUE; $posX = $midParentX; } # Do we draw above or below the icon/window if ( $parentY >= $height0/2 ) { $drawDown = FALSE; $posY = $midParentY - $height; } else { $drawDown = TRUE; $posY = $midParentY; } $self->move( $posX, $posY ); $self->show_all(); my @list; if ( $drawRight ) { if ( $drawDown ) { push @list, $wbsize; push @list, ( $wbsize + $ptsize ); push @list, 0; push @list, 0; push @list, ( $wbsize + $ptsize ); push @list, ( $wbsize ); } else { push @list, ( $wbsize + $ptsize ); push @list, ( $height - $wbsize ); push @list, 0; push @list, $height; push @list, $wbsize; push @list, ( $height - $wbsize - $ptsize ); } } else { if ( $drawDown ) { push @list, ( $width - $wbsize - $ptsize ); push @list, $wbsize; push @list, $width; push @list, 0; push @list, ( $width - $wbsize ); push @list, ( $wbsize + $ptsize ); } else { push @list, ( $width - $wbsize - $ptsize ); push @list, ( $height - $wbsize ); push @list, $width; push @list, $height; push @list, ( $width - $wbsize ); push @list, ( $height - $wbsize - $ptsize ); } } return @list; } sub CalculateRect() { my ( $xorg, $yorg, $width, $height ) = @_; my @list = ( # top left corner $xorg => ( $yorg + 4 ), ( $xorg + 1 ) => ( $yorg + 4 ), ( $xorg + 1 ) => ( $yorg + 2 ), ( $xorg + 2 ) => ( $yorg + 2 ), ( $xorg + 2 ) => ( $yorg + 1 ), ( $xorg + 4 ) => ( $yorg + 1 ), ( $xorg + 4 ) => ( $yorg ), # top Right corner ( ( $xorg + $height ) - 4 ) => $yorg, ( ( $xorg + $height ) - 4 ) => ( $yorg + 1 ), ( ( $xorg + $height ) - 2 ) => ( $yorg + 1 ), ( ( $xorg + $height ) - 2 ) => ( $yorg + 2 ), ( ( $xorg + $height ) - 1 ) => ( $yorg + 2 ), ( ( $xorg + $height ) - 1 ) => ( $yorg + 4 ), ( ( $xorg + $height ) ) => ( $yorg + 4 ), # bottom Right corner ( $xorg + $height ) => ( ( $yorg + $width ) - 4 ), ( ( $xorg + $height ) - 1 ) => ( ( $yorg + $width ) - 4 ), ( ( $xorg + $height ) - 1 ) => ( ( $yorg + $width ) - 2 ), ( ( $xorg + $height ) - 2 ) => ( ( $yorg + $width ) - 2 ), ( ( $xorg + $height ) - 2 ) => ( ( $yorg + $width ) - 1 ), ( ( $xorg + $height ) - 4 ) => ( ( $yorg + $width ) - 1 ), ( ( $xorg + $height ) - 4 ) => ( $yorg + $width ), # bottom Left corner ( $xorg + 4 ) => ( $yorg + $width ), ( $xorg + 4 ) => ( ( $yorg + $width ) - 1 ), ( $xorg + 2 ) => ( ( $yorg + $width ) - 1 ), ( $xorg + 2 ) => ( ( $yorg + $width ) - 2 ), ( $xorg + 1 ) => ( ( $yorg + $width ) - 2 ), ( $xorg + 1 ) => ( ( $yorg + $width ) - 4 ), $xorg => ( ( $yorg + $width ) - 4 ), ); return @list; } package main; use Gtk2 -init; use MIME::Base64; # a 32 X 32 png my $icodata = decode_base64( 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBI WXMAAAsTAAALEwEAmpwYAAABwUlEQVRYw+2VvS8EURTFf8/4CKJQTKOgEIlEqFCIRnSUhE6iUOhV Go3Kf0ChkiipNWqFQuErEtFJSEREfO7O0dyNid2Z3X3sbmHfy82bdzPv3TPnnnvHSaKWo4EajzqA OoBGn0MOl+cT+l8pmANugEdb53wBuHIbkdGfAYKYOws0+qTBl4GgyL5ehhUHcFVkX3ERfm9jraAq IrQgLmbewX+TghcgsvKLAFVbA895xFQLgMOFQHstq2ACaMnD5TsklWyIEPGMiFDe3C7nrpyVy8AI 0JaQ81mHO3C4oJIp2E0R3TswBKxWJAWIW6M+W4D+LOLd7BaxWPq9xQP3I64teITIxAIvIc7t+dPW N8QdIvwrAKex4PG5guhAjMd8T8bCh4m13wuAqX0H8ZCg+GlEA6IV0YZYLvBOZKycIcaSADhJuebS B0wCvcAUECbIZgC4AJqAZhNfaNWxA4wWOBMBl8AecAIc2n/lHsQ8Yh9xYvQlzW3EIKIZ0fKDsU7z dyO2TANKYCWyFB0jZkgJmJsZxIZRHqRoBYQzG0AclXD3axqAF8SaT3czED2ILsRmKoQCrjvEQtrX egAKEMOI9R9lHH0BkYPO/OE8jeIAAAAASUVORK5CYII=' ); my $icopixbuf = do { my $loader = Gtk2::Gdk::PixbufLoader->new(); $loader->write( $icodata ); $loader->close(); $loader->get_pixbuf(); }; my ($xscr, $yscr) = (Gtk2::Gdk->screen_width, Gtk2::Gdk->screen_height); #print "$xscr $yscr\n"; my $window = Gtk2::Window->new; $window->set_border_width(10); $window->set_title('Window 0'); my $width = 300; my $height = 100; $window->set_size_request($width,$height); $window->set_gravity('GDK_GRAVITY_SOUTH_EAST'); $window->signal_connect( delete_event => sub { Gtk2->main_quit; 1 } ); $window->move($xscr,$yscr); my $window1 = Gtk2::Window->new; $window1->set_border_width(10); $window1->set_title('Window 1'); $window1->set_size_request($width,$height); $window1->set_gravity('GDK_GRAVITY_SOUTH_WEST'); $window1->move(0,$yscr); my $window2 = Gtk2::Window->new; $window2->set_border_width(10); $window2->set_title('Window 2'); $window2->set_size_request($width,$height); $window2->set_gravity('GDK_GRAVITY_NORTH_WEST'); $window2->move(0,0); my $statusicon = Gtk2::StatusIcon->new_from_pixbuf($icopixbuf); # will make a nice icon automagically from a file if desired #my $statusicon = Gtk2::StatusIcon->new_from_file('12uni2.png'); $statusicon->set_tooltip( "Info v1.0" ); #show in tray $statusicon->set_visible( 1 ); $statusicon->signal_connect( 'activate', sub { print "1\n" } ); $statusicon->signal_connect( 'popup-menu', \&config_it ); my $vbox = Gtk2::VBox->new; my $b = Gtk2::Button->new_from_stock( "gtk-close" ); $b->signal_connect('button_press_event' => sub { exit }); $vbox->pack_start( $b, TRUE, TRUE, 0 ); $window->add( $vbox ); $vbox->show_all; $window->show_all(); $window1->show_all(); $window2->show_all(); # messageTypes: info warning question error my $id = Glib::Timeout->add (3000, sub { my $test = Gtk2::Ex::ZNotify->new( type => 'popup', parent_widget => $window, messageType => "info", timeout => 10000, message => "Info for Window 0", ); $test->show_all(); return 0; # don't run again }); # messageTypes: info warning question error my $id1 = Glib::Timeout->add (4000, sub { my $test = Gtk2::Ex::ZNotify->new( type => 'popup', parent_widget => $window1, messageType => "warning", timeout => 10000, message => "Warning for Window 1", ); $test->show_all(); return 0; # don't run again }); # messageTypes: info warning question error my $id2 = Glib::Timeout->add (5000, sub { my $test = Gtk2::Ex::ZNotify->new( type => 'popup', parent_widget => $window2, messageType => "question", timeout => 10000, message => "Question for Window 2\nWhere is your response?\nWe are waiting", ); $test->show_all(); return 0; # don't run again }); my $id3 = Glib::Timeout->add (6000, sub { # statusicon is not a widget, so need to find it's rectangle # after it has visibility, but it has no expose event, so # check it's location, also location may change my ($screen,$rect)=$statusicon->get_geometry; my ($x,$y,$w, $h) = $rect->values; # print "exposed $x $y $w $h\n"; my $test = Gtk2::Ex::ZNotify->new( type => 'popup', # parent_widget => $window, # undefind, use x y location messageType => "error", timeout => 15000, message => 'Hoo hah Please check now', x => ($x + $w/2), y => ($y + $h ), ); $test->show_all(); return 0; # don't run again }); Gtk2->main; sub config_it{ my $menu = Gtk2::Menu->new(); #Quit my $menu_quit = Gtk2::ImageMenuItem->new_with_label( "Quit" ); $menu_quit->signal_connect( activate => sub{ undef $statusicon } ); $menu_quit->set_image( Gtk2::Image->new_from_stock( 'gtk-quit', 'menu' ) ); $menu->add( $menu_quit ); $menu->show_all(); #to position the menu under the icon, instead of at mouse position my ($x, $y, $push_in) = Gtk2::StatusIcon::position_menu($menu, $statusicon); print "$x, $y, $push_in\n"; $menu->popup( undef, undef, sub{return ($x,$y,0)} , undef, 0, 0 ); return 1; }