package Tk::GridColumns; # PRAGMAS # ------- use strict; use warnings 'all'; # MODULES # ------- use Tk; use Tk::Button; use Tk::Frame; use Tk::Pane; use Tk::ROText; # GLOBALS # ------- # version and debuglevel our $VERSION = '0.05'; our $DEBUG = 100 ; # # debuglevel get-/setter # sub set_debuglevel ($$) { $DEBUG = pop } sub get_debuglevel ($ ) { $DEBUG } # # get_version() - Get the module's version # sub get_version ($ ) { $VERSION } # package local my $ID = 0; # METHODS # ------- # # constructor # sub new ($$;) { my( $class, $top, %opt ) = @_; # pick parameters $class = ref($class) || $class; # dual-constructor print __PACKAGE__ . " :: Create new object of class '$class'\n\n" if $DEBUG; # create obejct my $obj = { frame => 0, # predefine, store it later head => [], # header section data => [], # data section grid => {}, # gridded widgets weight => {}, # weigth columns opt => { # widget option defaults -scrollbars => 'ose', -relief => 'sunken', -bd => 2, -background => 'white', -header_font => '{Arial} 10 {bold}', -item_font => '{Arial} 10 {normal}', -item_scrollbars => 'osoe', -item_relief => 'sunken', -item_bd => 2, -item_padx => 1, -item_pady => 1, -item_background => Tk::NORMAL_BG, }, }; bless $obj, $class; # bless the reference $obj->set_opt( %opt ); # set user options # create and pack Pane my $pane = $top -> Scrolled( Pane => Name => $ID++, # increment ID -scrollbars => $obj->get_opt('-scrollbars'), # scrollbar locations -relief => $obj->get_opt('-relief'), # border-type -bd => $obj->get_opt('-bd'), # border-width -gridded => 'xy', # fill the whole $top -sticky => 'nsew', # fill the whole $top -background => $obj->get_opt('-background'), # background-color ) -> pack( -fill => 'both', # fill the whole $top -expand => 1, # fill the whole $top ) -> Subwidget( 'scrolled' ); # get the pane from the Scrolled $obj->{frame} = $pane -> Subwidget( 'frame' ); # get the frame from the pane return $obj; # return object } # new # # set_opt() - Set widget options # sub set_opt ($;) { my( $self, %opt ) = @_; # pick parameters print __PACKAGE__ . " :: Set options\n" if $DEBUG; if ( $DEBUG > 25 ) { foreach my $key ( keys %opt ) { print __PACKAGE__ . " :: -opt: '$key' => '$opt{$key}'\n"; } # foreach } %{$self->{opt}} = ( %{$self->{opt}}, %opt ); # write options print "\n" if $DEBUG; return $self; # return object } # set_opt # # get_opt() - Get widget options # sub get_opt ($;) { my( $self, @opt ) = @_; # pick parameters print __PACKAGE__ . " :: Get options\n" if $DEBUG; print __PACKAGE__.qq( :: -opt: '@{[join"', '",@opt]}'\n) if $DEBUG > 25; print "\n" if $DEBUG; return @{$self->{opt}}{@opt}; # return option values } # get_opt # # set_header() - Set the header section ( buttons and sort algorithms ) # sub set_header ($$) { my( $self, $head ) = @_; # pick parameters print __PACKAGE__ . " :: Set header\n" if $DEBUG; if ( $DEBUG > 25 ) { if ( require Data::Dumper ) { print __PACKAGE__ . " :: -header dump:\n"; print Data::Dumper::Dumper( $head ), "\n"; } else { print __PACKAGE__ . " :: !can't dump without Data::Dumper installed!\n"; } } $self->{head} = [ ( # assign the new headers map { [ @$_, 0 ] # button-text and sort algorithm come from # $head, the 0 stands for don't sort reversed } ( @$head ) # have to group this ) ]; print "\n" if $DEBUG; return $self; # return object } # set_header # # set_data() - Set the data section ( row's elements ) # sub set_data ($$) { my( $self, $data ) = @_; # pick parameters print __PACKAGE__ . " :: Set data\n" if $DEBUG; if ( $DEBUG > 25 ) { if ( require Data::Dumper ) { print __PACKAGE__ . " :: -data dump:\n"; print Data::Dumper::Dumper( $data ), "\n"; } else { print __PACKAGE__ . " :: !can't dump without Data::Dumper installed!\n"; } } $self->{data} = $data; # assign data section print "\n" if $DEBUG; return $self; # return object } # set_data # # set_weight() - Set the columns weight # sub set_weight ($$@) { my( $self, $mode, @columns ) = @_; # pick parameters print __PACKAGE__ . " :: Set weight\n" if $DEBUG; print __PACKAGE__ . " :: -mode: '$mode'\n" if $DEBUG > 25; print __PACKAGE__.qq( :: -cols: '@{[join"', '",@columns]}'\n) if $DEBUG > 25; foreach my $c ( @columns ) { # iterate over @columns $mode ? $self->{weight}->{$c} = 1 # set column's weight to 1 : delete $self->{weight}->{$c} ; # delete this column from the weight list } # foreach print "\n" if $DEBUG; return $self; # return object } # set_weight # # getter # sub get_header ($) { $_[0]->{ head } } sub get_data ($) { $_[0]->{ data } } sub get_weight ($) { $_[0]->{weight} } sub get_grid ($) { $_[0]->{ grid } } sub get_frame ($) { $_[0]->{frame } } # # del_header() - Delete the header # sub del_header ($) { my( $self ) = @_; # pick object print __PACKAGE__ . " :: Delete header\n" if $DEBUG; my $grid = $self->get_grid; # get grid foreach my $n ( keys %$grid ) { # iterate over the grid if ( $n =~ /^HEAD/ ) { # search leading 'HEAD' print __PACKAGE__ . " :: -item: '$n'\n" if $DEBUG > 75; $grid->{$n}->destroy; # destroy the widget delete $grid->{$n}; # delete it from the widget-hash } # if } # foreach print "\n" if $DEBUG; return $self; # return object } # del_header # # del_items() - Delete the items # sub del_items ($) { my( $self ) = @_; # pick object print __PACKAGE__ . " :: Delete items\n" if $DEBUG; my $grid = $self->get_grid; # get grid foreach my $n ( keys %$grid ) { # iterate over the grid if ( $n =~ /^ITEM/ ) { # search leading 'ITEM' print __PACKAGE__ . " :: -item: '$n'\n" if $DEBUG > 75; $grid->{$n}->destroy; # destroy the widget delete $grid->{$n}; # delete it from the widget-hash } # if } # foreach print "\n" if $DEBUG; return $self; # return object } # del_items # # draw_header() - Draw the header # sub draw_header ($) { my( $self ) = @_; # pick object print __PACKAGE__ . " :: Draw header\n" if $DEBUG; my $head = $self->get_header; # get header my $frame = $self->get_frame; # get frame my $data = $self->get_data; # get data my $grid = $self->get_grid; # get grid for my $x ( 0 .. $#{$head} ) { print __PACKAGE__ . " :: -name: '$head->[$x]->[0]'\n" if $DEBUG > 25; print __PACKAGE__ . " :: -command: '$head->[$x]->[1]'\n" if $DEBUG > 50; print __PACKAGE__ . " :: -sorted: '$head->[$x]->[2]'\n" if $DEBUG > 75; $grid->{'HEAD'.$x} = $frame -> Button( # create header -text => $head->[$x]->[0], -font => $self->get_opt('-header_font'), -command => ( ref( $head->[$x]->[1] ) eq 'CODE' # check for code-reference ) ? $head->[$x]->[1] # use user command : sub { # create sort sub print __PACKAGE__ . " :: Sort after column '$x'\n" if $DEBUG; return if @$data < 2; # dont sort less than 2 elements print __PACKAGE__ . " :: -algorithm: '$head->[$x]->[1]'\n" if $DEBUG > 25; # sort after column $x my $start = time(); my @tmp = sort { my @t = ( $a, $b ); ( $a, $b ) = ( $a->[$x], $b->[$x] ); my $t = eval $head->[$x]->[1]; # EVIL - FIND ANOTHER SOLUTION ( $a, $b ) = @t; $t } (@$data); print __PACKAGE__ . " :: -time: '". ( time() - $start ) ."' sec\n" if $DEBUG > 25; # reverse order? @tmp = $head->[$x]->[2] ? reverse @tmp : @tmp; # reverse reverse-flag $head->[$x]->[2] = !$head->[$x]->[2]; # assign data @$data = @tmp; print "\n" if $DEBUG; # refresh display $self->refresh_items; }, ) -> grid( # position the widget -row => 0, # 1st row because it's an header -column => $x, -sticky => 'ew', # stretch horizontally ); } # for $x print "\n" if $DEBUG; return $self; # return object } # draw_header # # draw_items() - Draw the items # sub draw_items ($) { my( $self ) = @_; # pick object print __PACKAGE__ . " :: Draw items\n" if $DEBUG; my $frame = $self->get_frame; # get frame my $data = $self->get_data; # get data my $grid = $self->get_grid; # get grid my $head = $self->get_header; # get header for my $y ( 0 .. $#{$data} ) { # iterate over rows for my $x ( 0 .. $#{$data->[$y]} ) { # iterate over columns # MAKE THE ITEM EDITABLE IN A LATER VERSION # get item my $item = "" . $data->[$y]->[$x]; my $width = length( $item ); print __PACKAGE__ . " :: -item: '$item'\n" if $DEBUG > 25; print __PACKAGE__ . " :: -coords: '$x' : '$y' ( row : col )\n" if $DEBUG > 50; print __PACKAGE__ . " :: -width: '$width'\n" if $DEBUG > 75; # create item $grid->{'ITEM'.$x.'.'.$y} = $frame -> Scrolled( ROText => -scrollbars => $self->get_opt('-item_scrollbars'), -font => $self->get_opt('-item_font'), -background => $self->get_opt('-item_background'), -bd => $self->get_opt('-item_bd'), -relief => $self->get_opt('-item_relief'), -width => $width < 30 ? $width : 30, -height => 2, ) -> grid( -row => $y+1, -column => $x, -sticky => 'nsew', -padx => $self->get_opt('-item_padx'), -pady => $self->get_opt('-item_pady'), ); # insert text $grid->{'ITEM'.$x.'.'.$y} -> insert( 'end', $item, ); } # for $x } # for $y print __PACKAGE__ . " :: create extra frame to fill the whole window\n" if $DEBUG; # create extra cell at the bottom to fill the whole window $grid->{'ITEM.EX'} = $frame -> Frame( -relief => 'flat', -bd => 0, -background => 'white', ) -> grid( -row => 1+@$data, -column => 0, -columnspan => 0+@$head, -sticky => 'nsew', ); # MAKE THIS MORE EDITABLE!!! # stretch columns print __PACKAGE__ . " :: stretch columns and extra frame\n" if $DEBUG; $frame -> gridColumnconfigure( $_, -weight => $self->{weight}->{$_+1} || 0, ) for 0 .. $#{$head}; $frame -> gridRowconfigure( 1+@$data, -weight => 1 ); print "\n" if $DEBUG; return $self; # return object } # draw_items # # refresh routines # sub refresh_items ($) { $_[0]->del_items->draw_items } sub refresh_header ($) { $_[0]->del_header->draw_header } sub refresh ($) { $_[0]->refresh_header->refresh_items } # TO GAIN MORE PERFORMANCE: REFERSH THE CHANGED PART OF THE GRID HERE!!! # # add_row() - Add a new data row # sub add_row ($@) { my( $self, @row ) = @_; # pick parameters print __PACKAGE__ . " :: Add data row\n" if $DEBUG; print __PACKAGE__.qq( :: -items: '@{[join"', '",@row]}'\n) if $DEBUG > 25; push @{ $self->{data} }, \@row; # add row print "\n" if $DEBUG; return $self; # return object } # add_row # # show debug information if the object gets destroyed # sub DESTROY { print __PACKAGE__ . " :: Destroy object\n\n" if $DEBUG; } # DESTROY 1; __END__ =pod =head1 NAME Tk::GridColumns - Columns widget for Tk =head1 SYNOPSIS use Tk::GridColumns; # load the module # to create a new Tk::GridColumns widget, use the following syntax, # all options are optional, each default is shown here: my $gc = new Tk::GridColumns ( # create a Tk::GridColumns widget $top, # the frame/window to put it in -scrollbars => 'ose', # change the scrollbars for the Pane -relief => 'sunken', # change the relief for the Pane -bd => 2, # change the borderwidth for the Pane -background => 'white', # change the background for the Pane -header_font => '{Arial} 10 {bold}', # change the header font -item_scrollbars => 'osoe', # change the scrollbars for each item -item_relief => 'sunken', # change the relief for each item -item_bd => 2, # change the borderwidth for each item -item_background => Tk::NORMAL_BG, # change the background for each item -item_font => '{Arial} 10 {normal}', # change the font for each item -item_padx => 1, # change the x-padding for each item -item_pady => 1, # change the y-padding for each item ); $gc->set_opt( %opt ); # change options $gc->get_opt( @keys ); # get option values $gc->set_data( $data ); # set grid data $gc->get_data; # get grid data $gc->set_header( $head ); # set header data $gc->get_header; # get header data $gc->set_weight( $m, @cols ); # set column's ( @cols ) weight to $m $gc->get_weight; # get the actual weight for some columns, unspecified columns have the weight 0 $gc->get_frame; # get the frame the items and the header is gridded on $gc->get_grid; # get the widget hash, all displayed widgets can get found in this hash $gc->add_row( @cols ); # add a data row to the GC ( after this operation you should refresh the items ) $gc->refresh_header; # refresh the header $gc->refresh_items; # refresh the items $gc->refresh; # refresh header and items $gc->del_header; # delete the actual header ( after this operation you should refresh the header ) $gc->del_items; # delete the actual items ( after this operation you should refresh the items ) $gc->draw_header; # draw the actual header ( after this operation you should refresh the header ) $gc->draw_items; # draw the actual items ( after this operation you should refresh the items ) # the following is NOT supported #my $gc = $top -> GridColumns( %opt ); # this is NOT supported =head1 AUTHOR Matthias Wienand =cut