#! /usr/bin/perl use Data::Dumper; use strict; use warnings; use Gtk2 '-init'; # Beispiel my @test=( [map{"[0][$_]"}(0..2)], [map{"[1][$_]"}(0..2)], [map{"[2][$_]"}(0..2)], ); my $window = Gtk2::Window->new('toplevel'); $window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); $window->set_border_width(5); my $vbox = Gtk2::VBox->new(0,5); $vbox->set_size_request (500, 500); # Neues Matrix TreeView my $treeview = extern::MatrixTreeView->new(); $vbox->pack_start($treeview,1,1,0); # alternativ kann man auch ein vorhandenes TreeView verwenden. #my $test=Gtk2::TreeView->new(); #$vbox->pack_start($test,1,1,0); #my $treeview=Gtk2::Ex::MatrixTreeView->new_from_treeview($test); # Die Anzeige der Spalten/Zeilenzähler wählen # number # roman # alpha # oder eine Codereferenz die den Anzuzeigenden Wert zurück liefern soll # übergeben wird die Zeilen/Spalten-Nmummen. $treeview->set_row_counter('roman'); $treeview->set_col_counter('alpha'); # eine Matrix setzten $treeview->set_matrix(\@test); # "get_matrix" erleubt es die Matrix wieder aus zu lesen # Signalhandler "cell_clicked" wird jedesmal aufgerufen, # wenn ein Eintrag selektiert wurde. # es lifert die zeile, spalte als zweiten/drittenwert $treeview->signal_connect(cell_clicked => sub{ my $tree=shift; my ($row,$col)=@_; # get_value lifert den wert zu einem "Eintrag" my $val=$treeview->get_value($row,$col) // ''; printf "POS(%u, %u) = %s\n",@_,$val; $treeview->set_value(@_,"TEST$val"); }); my $row_btn=Gtk2::Button->new('APPEND ROW'); $vbox->pack_start($row_btn,0,0,0); $row_btn->signal_connect(clicked => sub{ my @data; # row_count liefert die anzehl der Zeilen my $rows=$treeview->row_count(); # col_count liefert die Anzahl der Spalten my $cols=$treeview->col_count(); push( @data, "[$rows][$_]") for(0..$cols-1); # append_row fügt eine Zeile hinzu $treeview->append_row(@data); }); my $col_btn=Gtk2::Button->new('APPEND COL'); $vbox->pack_start($col_btn,0,0,0); $col_btn->signal_connect(clicked => sub{ my @data; # row_count liefert die anzehl der Zeilen my $rows=$treeview->row_count(); # col_count liefert die Anzahl der Spalten my $cols=$treeview->col_count(); push( @data, "[$_][$cols]") for(0..$rows-1); # append_col fügt eine Spalte hinzu $treeview->append_col(@data); }); my $exit_btn=Gtk2::Button->new('EXIT'); $vbox->pack_start($exit_btn,0,0,0); $exit_btn->signal_connect(clicked => sub{ Gtk2->main_quit; }); $vbox->show_all(); #add and show the vbox $window->add($vbox); $window->show(); #our main event-loop Gtk2->main(); ######################################################################## ######################################################################## ######################################################################## # Custom Gtk2::TreeView # Dient zur Darstellung von matrizen # Man kann # Zeilen und Spalten hinzufügen # und # einzelne Werte lesen/ändern # # der Siganlhandler "cell_clicked" # kann genutzt werden, um Selektionen einzelner Zellen zu reagieren # zurück geliefert wird package extern::MatrixTreeView; use strict; use warnings; use Gtk2; use Carp; use Glib::Object::Subclass Gtk2::TreeView::, signals => { cell_clicked => { flags => [qw/run-last/], param_types => [qw/Glib::Int Glib::Int/], }, }, properties => []; my $roman_loaded=0; my $offset=2; # neues Objekt sub new { my $class=shift; my $self=$class->SUPER::new(); $self->_init(); return $self; } # aus einem vorhanden TreeView umwandeln # ersetzt das aktuelle TreeView sub new_from_treeview { my $class = shift; my $view = shift; unless( $view && $view->isa('Gtk2::TreeView')) { croak("It's not a Gtk2::TreeView"); } #..................................................................... # replace the TreeView properly # # copy propertys my %prop; for my $param ($view->list_properties()) { if(grep{$_ eq 'readable'}@{$param->{flags}}) { $prop{$param->{name}} = $view->get($param->{name}); } } my $self=$class->new(); # # replace if(my $parent=$view->get('parent')) { $parent->remove($view); $parent->add($self); } # #set propertys for my $param ($self->list_properties()) { if(grep{$_ eq 'writable'}@{$param->{flags}} && exists($prop{$param->{name}})) { $self->set($param->{name},$prop{$param->{name}}); } } #..................................................................... map { $self->remove_column ($_) } $self->get_columns; $self->_init(); return $self; } sub _init { my $self=shift; $self->_clean(); $self->set_row_counter('number'); $self->set_col_counter('number'); $self->set_hover_selection(0); $self->{last_select}=undef; $offset=2; } sub new_with_model{ croak("Custom Model not supported!"); } sub set_model{ croak("Custom Model not supported!"); } # möglich # number (1 2 3 4) # roman (I II III) # alpha (A B C) sub set_row_counter { my $self=shift; $self->_set_counter('row_counter',@_); } # möglich # number (1 2 3 4) # roman (I II III) # alpha (A B C) sub set_col_counter { my $self=shift; $self->_set_counter('col_counter',@_); } # matrix setzen sub set_matrix { my $self=shift; my $matrix=shift; return 0 if(ref($matrix) ne 'ARRAY'); for(@$matrix) { return 0 if(ref($_) ne 'ARRAY') } $self->_clean(); my $size_col=@{$matrix->[0]}; my $size_row=@{$matrix}; my $store = __new_store($size_col); for my $row (0..$size_row-1) { my $iter=$store->append(); $store->set($iter,$offset-2,$row); $store->set($iter,$offset-1,$self->{row_counter}->($self->{row_count}++)); for my $col (0..$size_col-1) { $store->set($iter,$col+$offset,$matrix->[$row]->[$col] // ''); } } $self->SUPER::set_model($store); for my $col (0..$size_col-1) { $self->_append_column($col+$offset); } } # Zeile hinzufügen sub append_row { my $self=shift; my $store=$self->get_model(); my $col_size=$self->col_count(); my $row_size=$self->row_count(); my $iter = $store->append(); $store->set($iter,$offset-2,$row_size); $store->set($iter,$offset-1,$self->{row_counter}->($self->{row_count}++)); for my $col (0..$col_size-1) { my $val=shift(@_) // '???'; $store->set($iter, $col+$offset => $val); } } # Spalte Hinzufügen sub append_col { my $self=shift; my $size_col=$self->col_count(); my $size_row=$self->row_count(); my $old = $self->get_model; my $new = __new_store($size_col+1); for my $row (0..$size_row-1) { my $old_iter=$old->iter_nth_child(undef,$row); my $new_iter=$new->append(); $new->set($new_iter,$offset-2,$row); $new->set($new_iter,$offset-1,$old->get($old_iter,1)); for my $col (0..$size_col-1) { my $val=$old->get($old_iter,$col+$offset); $new->set($new_iter,$col+$offset,$val); } my $val=shift(@_) // '???'; $new->set($new_iter,$size_col+$offset,$val); } $self->SUPER::set_model($new); $self->_append_column($size_col+$offset); } # matrix lesen sub get_matrix { my $self=shift; my @matrix=(); my $model=$self->get_model(); for my $row (0..$self->row_count()-1) { my $iter=$model->iter_nth_child(undef, $row); for my $col(0..$self->col_count()-1) { $matrix[$row][$col]=$model->get($iter, $col+$offset); } } return \@matrix; } # wert setzen sub set_value { my $self=shift; my $col=shift // 0; my $row=shift // 0; my $value=shift // ''; my $model=$self->get_model(); my $iter=$model->iter_nth_child (undef, $row); return 0 if(!$iter); return 0 if($self->col_count() <= $col); $model->set($iter, $col+$offset, $value); return 1; } # wert lesen sub get_value { my $self=shift; my $col=shift // 0; my $row=shift // 0; my $model=$self->get_model(); my $iter=$model->iter_nth_child (undef, $row); return undef if(!$iter); return undef if($self->col_count() <= $col); return $model->get($iter, $col+$offset); } # Anzahl Zeilen sub row_count { my $treeview=shift; return $treeview->get_model->iter_n_children(undef); } # Anzahl Spalten sub col_count { my $treeview=shift; my @l=$treeview->get_columns(); return @l+1-$offset; } #----------------------------------------------------------------------- # private sub __new_store { my $size=shift; return Gtk2::ListStore->new('Glib::Uint',('Glib::String')x($size+1)); } sub _set_counter { my $self=shift; my $elem=shift; my $type=shift; return 0 unless($type); if(ref($type) eq 'CODE') { $self->{$elem}=$type; return 1; } if($type eq 'number') { $self->{$elem}=sub{ return shift()+1; }; return 1; } if($type eq 'alpha') { my @alpha=('A'..'Z'); $self->{$elem}=sub{ my $p=shift()+1; my $ret=''; while($p>=@alpha) { my $pp=$p/@alpha; $pp=int($pp); $ret=$alpha[$p-$pp*@alpha].$ret; $p=$pp; } $ret=$alpha[$p-1].$ret; return $ret; }; return 1; } if($type eq 'roman') { $roman_loaded=1 if(!$roman_loaded && eval{ require Text::Roman; }); if($roman_loaded) { $self->{$elem}=sub{ my $count=shift; my $ret=''; $ret=Text::Roman::int2roman($count+1) if($roman_loaded); return $ret; }; return 1; } carp(qq(Modul "Text::Roman" not loadable!)); return 0; } carp(qq(Type "$type" not recognized use "number", "alpha", "roman" or a coderef!)); return 0; } sub _clean { my $self=shift; $self->SUPER::set_model(Gtk2::ListStore->new('Glib::String')); map { $self->remove_column($_) } $self->get_columns(); my $col = Gtk2::TreeViewColumn->new_with_attributes ( ' ', extern::CellRendererFirstCol->new(), text => 1, ); $self->append_column($col); $self->{rows}=0; $self->{cols}=0; $self->{row_count}=0; $self->{col_count}=0; } sub _append_column { my $self=shift; my $pos=shift; my $col = Gtk2::TreeViewColumn->new_with_attributes ( $self->{col_counter}->($self->{col_count}), $self->_new_cellrenderer_col($self->{col_count}), text => $pos, row => 0, ); $self->{col_count}++; $self->append_column($col); } sub _new_cellrenderer_col { my $self=shift; my $pos=shift; my $r=extern::CellRendererCol->new(); $r->set(mode => 'activatable'); $r->set(col => $pos); $r->signal_connect( clicked => sub{$self->_cell_clicked(@_) } ); return $r; } sub _cell_clicked { my $self=shift; my $cell=shift; $self->{last_select}=[@_]; $self->signal_emit('cell_clicked',@_); } 1; ######################################################################## #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ######################################################################## package extern::CellRendererFirstCol; use strict; use warnings; use Gtk2; use constant xpad => 3; use constant ypad => 3; use Glib::Object::Subclass Gtk2::CellRenderer::, signals => { }, properties => [ Glib::ParamSpec->string( 'text', 'TEXT', 'Text of Element', '', [qw/readable writable/], ), ]; sub get_layout { my ($cell, $widget) = @_; return $cell->{layout} if defined $cell->{layout}; return $cell->{layout} = $widget->create_pango_layout(""); } sub _cell_text { my $cell=shift; return $cell->{text} // ""; } sub _calc_size { my ($cell, $layout) = @_; my ($w, $h) = $layout->get_pixel_size; return (0, 0, $w + xpad * 2, $h + ypad * 2); } sub RENDER { my ($cell, $drawable, $widget, $background_area, $cell_area, $expose_area, $flags) = @_; my $style=$widget->get_style(); $style->paint_box( $drawable, $widget->state, 'out', $background_area, undef, "cellrendererbg", $background_area->x, $background_area->y, $background_area->width, $background_area->height ); my $layout = $cell->get_layout ($widget); $layout->set_text($cell->_cell_text()); my ($xoff, $yoff, $width, $height) = $cell->_calc_size($layout); $style->paint_layout( $drawable, $widget->state, 1, $cell_area, $widget, "cellrenderertext", $cell_area->x + $xoff + xpad, $cell_area->y + $yoff + ypad, $layout ); } sub GET_SIZE { my ($cell, $widget, $area) = @_; if ($area) { return (3, 3, $area->width - 2*xpad - 4, $area->height - 6); } my $layout = $cell->get_layout ($widget); $layout->set_text($cell->_cell_text()); return $cell->_calc_size($layout); } 1; ######################################################################## #+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ######################################################################## package extern::CellRendererCol; use POSIX qw(UINT_MAX); use strict; use warnings; use Gtk2; use constant xpad => 3; use constant ypad => 3; use Glib::Object::Subclass Gtk2::CellRenderer::, signals => { clicked => { flags => [qw/run-last/], param_types => [qw/Glib::Int Glib::Int/], }, }, properties => [ Glib::ParamSpec->string( 'text', 'TEXT', 'Text of Element', '', [qw/readable writable/]), Glib::ParamSpec->uint( "col", "Column", "Wich Col am i?", 0, UINT_MAX, 0, [ qw(readable writable) ], ), Glib::ParamSpec->uint( "row", "Row", "Wich Row am i?", 0, UINT_MAX, 0, [ qw(readable writable) ], ), ]; sub get_layout { my ($cell, $widget) = @_; return $cell->{layout} if defined $cell->{layout}; return $cell->{layout} = $widget->create_pango_layout (""); } sub _cell_text { my $cell=shift; return $cell->get('text') // ""; } sub _calc_size { my ($cell, $layout) = @_; my ($w, $h) = $layout->get_pixel_size; return (0, 0, $w + xpad * 2, $h + ypad * 2); } sub RENDER { my ($cell, $drawable, $widget, $background_area, $cell_area, $expose_area, $flags) = @_; my $style = $widget->get_style(); my $simple=1; my $row=$cell->get('row'); my $col=$cell->get('col'); if( $widget->{last_select} && $widget->{last_select}->[0] == $col && $widget->{last_select}->[1] == $row ) { $simple=0; } $drawable->draw_rectangle( $style->bg_gc($simple?$widget->state():'active'), 1, $background_area->x, $background_area->y, $background_area->width(), $background_area->height(), ); $drawable->draw_line ( $style->fg_gc ($widget->state()), $background_area->x, $background_area->y, $background_area->x, $background_area->y + $background_area->height()-1 ); $drawable->draw_line ( $style->fg_gc ($widget->state()), $background_area->x, $background_area->y + $background_area->height()-1, $background_area->x + $background_area->width(), $background_area->y + $background_area->height()-1 ); my $layout = $cell->get_layout ($widget); $layout->set_text($cell->_cell_text()); my ($xoff, $yoff, $width, $height) = $cell->_calc_size($layout); $style->paint_layout( $drawable, $widget->state(), 1, $cell_area, $widget, "cellrenderertext", $cell_area->x + $xoff + xpad, $cell_area->y + $yoff + ypad, $layout ); } sub GET_SIZE { my ($cell, $widget, $area) = @_; if ($area) { return (3, 3, $area->width - 2*xpad - 4, $area->height - 6); } my $layout = $cell->get_layout ($widget); $layout->set_text($cell->_cell_text()); return $cell->_calc_size($layout); } sub ACTIVATE { my ($cell, $event, $treeview, $path, $background_area, $cell_area, $flags) = @_; my ($x,$y)=($cell->get("col"),$cell->get('row')); $cell->signal_emit('clicked',$x,$y); return 0; } 1;