I have Gtk2::TreeView, some of columns are using CellRendererCombo. I have everything work ok, except of I can't get integer index of selected item in CellRendererCombo. Not a text, but the integer value.
As a workaround, it could be useful to associate somehow a hash with combo_model, but I don't know how.
Could somebody help me? Trying and googling for a couple of days already.
Here's the code:
#!/usr/bin/perl
package uform;
use utf8;
use warnings;
use strict;
use Gtk2 -init;
use Glib qw/TRUE FALSE/;
use Glib ':constants';
use Data::Dumper;
use constant col0 => 0;
use constant col1 => 1;
use constant col2 => 2;
use constant colC => 3;
binmode(STDOUT,':utf8');
my $model; my $treeview; my #attr_models;
sub create_window ($$) {
my ($width,$height)=(shift,shift);
my $window = Gtk2::Window->new( "toplevel" );
$window->signal_connect( "destroy", sub { Gtk2->main_quit(); } );
$window->set_default_size($width,$height);
my $vbox = Gtk2::VBox->new( 0, 5 );
$model = Gtk2::ListStore->new(
"Gtk2::Gdk::Pixbuf", #0 pic
"Glib::String", #1 product
"Glib::String", #2 attr
"Gtk2::ListStore" #combo values list
);
#sample_data
#some combo lists
foreach my $d (
[qw(fast medium slow)],
[qw(greay orange black white rainbow)],
[qw(fat with_wholes molded)],
[qw(long short jeans)]
)
{
my $cmodel = Gtk2::ListStore->new('Glib::String');
foreach my $str (#$d) {$cmodel->set($cmodel->append,0,$str);}
push #attr_models,$cmodel;
}
#some pixbufs to play with
my $pixbuf2 = Gtk2::Button->new->render_icon ('gtk-info', 'large-toolbar');
#add some rows
my #data = (
[$pixbuf2,'Shirt',1,$attr_models[0]],
[$pixbuf2,'Pants',0,$attr_models[0]],
[$pixbuf2,'Cheese',2,$attr_models[1]],
[$pixbuf2,'Cola',1,$attr_models[2]],
[$pixbuf2,'Laptop',0,$attr_models[3]]
);
foreach my $st(#data) {
$st->[2]=$st->[3]->get($st->[3]->iter_nth_child(undef,$st->[2]), 0);
$model->set( $model->append,
0, $st->[0],
1, $st->[1],
2, $st->[2],
3, $st->[3],
);
}
#Column0 setup
my $combo_model0 = Gtk2::ListStore->new( 'Gtk2::Gdk::Pixbuf' );
my $renderer_0 = Gtk2::CellRendererPixbuf->new;
my $column_0 = Gtk2::TreeViewColumn->new_with_attributes(
"Pic",
$renderer_0,
pixbuf => col0
);
#Column1 setup
my $renderer_1 = Gtk2::CellRendererText->new;
$renderer_1->set( editable => FALSE );
my $column_1 = Gtk2::TreeViewColumn->new_with_attributes(
"Product",
$renderer_1,
text => col1
);
#Column2 setup
my $renderer_2 = Gtk2::CellRendererCombo->new;
$renderer_2->set(
editable => TRUE,
text_column => 0,
has_entry => FALSE
);
$renderer_2->signal_connect (changed => sub {
my ($renderer, $str, $iter)=#_;
print Dumper (#_) . "\n";
}
);
$renderer_2->signal_connect (edited =>
sub {
my ($renderer, $text_path, $new_text) = #_;
my $combo_model = $renderer->get("model");
$model->set ($model->get_iter_from_string ($text_path), col2, $new_text);
}
);
my $column_2 = Gtk2::TreeViewColumn->new_with_attributes(
"Attr",
$renderer_2,
text => col2,
model => colC
);
# main program starts
$treeview = Gtk2::TreeView->new( $model );
$treeview->get_selection->set_mode ('single');
$treeview->set_rules_hint( TRUE );
$treeview->append_column( $column_0 );
$treeview->append_column( $column_1 );
$treeview->append_column( $column_2 );
my $sw = Gtk2::ScrolledWindow->new( undef, undef );
$sw->set_shadow_type( "etched-in" );
$sw->set_policy( "never", "always" );
$sw->add( $treeview );
$vbox->pack_start( $sw, 1, 1, 0 );
$window->add( $vbox );
$window->show_all;
}
So, looks like there isn't any direct answer for this question.
As a workaround, you may create array of hashes. Each element corresponds to one TreeView's row and have e.g. fields like 'combo_hash' and 'current_index'.
'current_index' is self-explained, 'combo_hash' is array which consists of hashes with some fields like 'text' and e.g. 'index' (or other id).
On CellRendererCombo's 'edited' signal you get current TreeView's index by $treeview->get_selection()->get_selected_rows()->get_indices() (it's simple int), and search for selected in CellRendererCombo's 'text' field through 'combo_hash' array. Don't forget to store finded 'index' to 'current_index'.
This workaround allows non-unique text in different TreeView's rows, but can't handle non-unique text in one CellRendererCombo's ListStore.
Another workaround is to inherite subclass from CellRenderer and embed ComboBox, which provides simple integer index. Looks better and have no limitations by non-unique data, but if array of hashes is unavoidable by design (you need to store lots of other info not visible in TreeView), first workaround should be more relevant.
Related
What should I do if I have to pass two values for same variable? Does following syntax work ?
sub get_db { return "database_name", "new_database"};
It does not pass both the value
The simplest way to return several values from a sub is a list: (NOTE - none of the following code has been tested)
return ($db_name, $new_db, $table, $rows)
...
my ($database_name, $database, $table_name, $entry_count) = get_db();
but that's easy to mess up - if you have a missmatch between the number of things returned and received something silently becomes undef. Likewise if the order of returned values is wrong, you are going to introduce a subtle bug.
Slightly better is to return a hashref;
....
my $return_values = { NAME => $db_name , DB => $new_db , TABLE => $table } ;
# add the number of rows and return it
$return_values->{ COUNT } = $rows ;
return $return_values ;
...
...
my $db_stuff = get_db();
for my $i (1 .. $db_stuff->{COUNT}) {
...
Better again is to learn a little OO and return an object. Moo is one of many options - it would look something like this:
Package DBstuff;
has name, is => ro ;
has db, is => ro ;
has table, is => ro ;
has count, is => rw ;
1;
... in another file ...
my $db_stuff = DBstuff->new(
name => $name ,
db => $db ,
table => $table,
);
# Add rows and return
$db_stuff->count( $rows );
return $db_stuff ;
...
...
my $db_data = get_db();
for my $i (1 .. $db_data->count) {
...
There is also a module called Object::Result which would almost certainly be overkill but whether you use it or not, I'd like to recommend the RATIONAL section of that module's documentation which covers the issue of returning several things from a sub in more depth.
Perl allows to return an array with multiple values, e.g.
sub get_db { return ["database_name", "new_database"] };
I am using Hypertable::ThriftClient, and using mutator for mass insertion. Here is the code sample: $master, $port and $namespace are all defined.
Table:
show create table users; # Table schema is below
CREATE TABLE GROUP_COMMIT_INTERVAL "100" users (
'column_name_fake' MAX_VERSIONS 1,
ACCESS GROUP audience ('column_name_fake'),
)
:hypertable$ SELECT * FROM users limit 1; # displays
2342345 sf_following:1000234
2342346 sf_following:1234234
Code:
my $ht = new Hypertable::ThriftClient($master, $port);
my $ns = $ht->namespace_open($namespace);
my $users_mutator = $ht->mutator_open($ns, 'table_name', 2);
Subroutine:
sub batch_insert {
my ($ht, $mutator, $column_family, $row, $val) = #_;
my $keys;
my $cell;
try {
$keys = new Hypertable::ThriftGen::Key({
row => $row,
column_family => $column_family });
$cell = new Hypertable::ThriftGen::Cell({key => $keys, value => $val});
}
catch {
warn Dumper({ 'Error' => $_ });
};
$ht->mutator_set_cell($mutator, $cell);
$ht->mutator_flush($mutator);
return 1;
}
Function called:
for(.....) { # Just for example
batch_insert($ht, $users_mutator, '', $fs, "node:$node_u");
}
Here I get an exception,
Hypertable::ThriftGen::ClientException=HASH(0x54d7380)
Can anyone clarify, why?
Edit: I added table structure for more clarity?
The ClientException class is defined as follows:
exception ClientException {
1: i32 code
2: string message
}
Try catching the exception and printing out the code (in hex) and message. That should help pinpoint what's going on.
FIX: So, this exception is raised is a required parameter 'column_qualifier' was not
passed as per the table design. See below:
sub batch_insert {
my ($ht, $mutator, $cf, $cq, $row, $val) = #_;
my $keys = new Hypertable::ThriftGen::Key({
row => $row,
column_family => $cf,
column_qualifier => $cq });
my $cell = new Hypertable::ThriftGen::Cell({key => $keys, value => $val});
$ht->mutator_set_cell($mutator, $cell);
#$ht->mutator_flush($mutator);
}
Calling the above function:
--------------------------
batch_insert($ht, $users_mutator, 'column_family' , 'column_qualifier, 'row_key', '');
and it worked like a charm. If you are in similar types of issues, Let me know, I can help out. I spent quite a bit of time, reading about the Thrift api.
I am using a DB::CDBI class for accessing the database in our application. Our project is in object-oriented Perl.
package LT::LanguageImport;
use strict;
use warnings;
use base 'Misk5::CDBI';
__PACKAGE__->table( 'english_text_translation' );
__PACKAGE__->columns( Primary => qw/english language translation/ );
__PACKAGE__->columns( Essential => qw/english language translation/ );
__PACKAGE__->has_a( english => 'LT::EnglishLanguage' );
In one such scenario, I am supposed to check if a row in a table exists. I am using the built-in search API in a CDBI call.
sub find_translation {
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
);
return;
}
$translation_exists_r_not is getting the expected values depending on the inputs given in the search. If the row exists, then the _data is updated with the row details.
$translation_exists_r_not = bless({
'_data' => [
{
'language' => 'polish',
'translation' => 'Admin',
'english' => 'admin'
}
],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
If the row desn't exist, then I get a return value like this.
$translation_exists_r_not = bless({
'_data' => [],
'_place' => 0,
'_mapper' => [],
'_class' => 'LT::LanguageImport'
},
'Class::DBI::Iterator'
);
I want to return the value of translation from this sub find_translation depending on the search result. I am not able to get a best condition for this.
I tried copying the _data into an array, but I'm not sure how to proceed further. As _data will be an empty arrayref and another condition it will have a hashref inside the arrayref.
my #Arr = $translation_exists_r_not->{'_data'};
CDBI's search method will return an iterator, because there may be multiple rows returned depending on your criteria.
If you know there can be only one row that matches your criteria, you want to use the retrieve method, i.e.:
if (my $translation_exists_r_not = $class->retrieve(
english => $english,
language => $language,
translation => $translation
)){
return [$translation_exists_r_not->translation,
'Misk5::TranslationAlreadyExists']
}
else {
return [ '', undef ]
}
And if multiple rows can be returned from your search, and you're only interested in the truthiness, then again, don't be rummaging around inside the CDBI::Iterator, but use its methods:
my $translation_exists_r_not = $class->search(
english => $english,
language => $language,
translation => $translation
); # returns an iterator
if ($translation_exists_r_not){
my $first = $translation_exists_r_not->first;
return [ $first->translation, 'Misk5::TranslationAlreadyExists' ]
}
Have a look at perldoc Class::DBI and perldoc Class::DBI::Iterator. CDBI has excellent documentation.
I think I got the solution. Thanks to whoever has tried to solve it.
my #req_array = %$translation_exists_r_not->{_data};
my $length_of_data = '9';
foreach my $elem (#req_array) {
$length_of_data = #{$elem};
}
Now check the length of the array.
if ($length_of_data == 0) {
$error = '';
$result = [undef, $error];
}
Now check if it is one.
if ($length_of_data == 1) {
my #result_array = #{%$translation_exists_r_not->{_data}};
my $translation = $result_array[0]{'translation'};
$error = 'Misk5::TranslationAlreadyExists';
$result = [$translation, $error];
}
return #$result;
Has anyone successfully used something like DBIx::Class::WebForm or CatalystX-CRUD to automagically build a self-validating webform from a database table?
I'm imagining a module that reads a database table schema, reads the constraints for each column, and generates some abstract representation of a webform, with fields for error messages, etc. I'm using Catalyst and Plack with a big existing codebase.
I don't want to code up an HTML webform, nor any validation logic. I'm aiming to write as little code as possible, in the style of Ruby on Rails. Which Perl module is best for this?
UPDATE: I've solved the webform side with HTML::FormFu, but it's still clunky mapping the form inputs onto the database, e.g. date_start and date_end both relate to the 'created' column, and comment should match using 'LIKE %foo%', etc. Where's the 'DBICFu'?
UPDATE: This is for a web application, the webform should not look like a database table. I'm not looking for a database management tool.
You can use use HTML::FormHandler::Moose and HTML::FormHandler::Model::DBIC and get some nice forms.
As a simple example:
The form definition:
package MyStats::Form::Datetime ;
use HTML::FormHandler::Moose ;
extends 'HTML::FormHandler::Model::DBIC' ;
use Date::Calc qw(Today_and_Now) ;
has_field 'datetimeid' => ( label => 'ID' ) ;
has_field 'datetime' => ( type => 'Text',
apply => [ { transform => \&transform_dt } ] ,
deflation => \&deflation_dt ,
required => 1 ) ;
has_field 'submit' => ( type => 'Submit' ,
value => 'Speichern' ) ;
# These are the fields of the table datetime
sub transform_dt {
my ( $dt ) = #_ ;
my #d = ( $dt =~ m/(\d{1,2})\.(\d{1,2})\.(\d{4})\s+(\d{1,2}):(\d{1,2})/ ) ;
return sprintf( '%04d-%02d-%02d %02d:%02d:00' , #d[2,1,0,3,4] ) ;
}
sub deflation_dt {
my ( $dt ) = #_ ;
my #d = ( $dt =~ m/(\d{4})-(\d{2})-(\d{2})\s+(\d{1,2}):(\d{1,2})/ ) ;
if( ! #d ) {
#d = Today_and_Now() ;
}
return sprintf( '%02d.%02d.%04d %02d:%02d:00' , #d[2,1,0,3,4] ) ;
}
1 ;
And the usage in a controller:
package MyStats::Controller::Datetime ;
use Moose ;
use namespace::autoclean ;
BEGIN { extends 'Catalyst::Controller' ; }
use MyStats::Form::Datetime ;
has 'form' => ( isa => 'MyStats::Form::Datetime' ,
is => 'rw' ,
lazy => 1 ,
default => \&new_datetime_form ) ;
sub new_datetime_form {
MyStats::Form::Datetime->new( css_class => 'datetimeform' ,
name => 'datetimeform' ) ;
}
...
sub add :Local :Args(0) {
my ( $self , $ctx ) = #_ ;
my $data = $ctx->model( 'MyStatsDB::Datetime' )->new_result( {} ) ;
$ctx->stash( template => 'datetime/add.tt2' ,
form => $self->form ) ;
$ctx->bread_crumb( { name => 'Datum/Zeit eingeben' ,
location => '/datetime/add' } ) ;
$ctx->req->param( 'datetimeid' , undef ) if $ctx->req->param( 'datetimeid' ) ;
return unless $self->form->process( item => $data ,
params => $ctx->req->params ) ;
$ctx->flash( message => 'Neuer Datensatz ' . $data->datetimeid .
' angelegt.' ,
id_add => $data->datetimeid ) ;
$ctx->res->redirect( $ctx->uri_for( '/datetime' ) ) ;
}
...
__PACKAGE__->meta->make_immutable ;
1 ;
Works good.
I've used HTML::FormHandler to generate forms for me in this fashion. It needs some tweaking, but it does 90% of the work for you. Separately DBIx::Class offers a similar tool.
There are a number of crud options on the Catalyst wiki.
It sounds like AutoCrud would fit your needs.
There is something like status changes check logic in our app.
Currently checking is being handled by ugly if statement
I want to replace it by transition matrix:
my %allowed_status_changes = (
1 => (2,5),
2 => (1,2,3,4,5),
3 => (4,2),
4 => (3,2),
5 => (),
);
my $is_allowed_transition =
$submitted_status ~~ $allowed_status_changes {$original_status};
if ($prerequestsites && !$is_allowed_transition) {
return;
}
certain transitions can be allowed only on additional condition, therefore I will need something like
2 => (
(target => 1)
(target => 2, condition => $some_condition)
(target => (3,4), condition => $other_condition),
(target => 5)
),
(in my opinion it is too long)
What structure would you use in this situation if you should focus on readability and maintainability?
How you will parse it to check if transition is allowed?
If the conditions are very prevalent (e.g. almost every allowed transition has them) then your latter structure is perfectly fine, other than your syntax error of representing a hashref with "()" instead of "{}".
If the conditions are rare, I'd suggest going with #1, augmented by optional constructs similar to your #2.
Please note that readability of checking code is IMHO very clear though voluminous and not very idiomatic.
OTOH, maintainability of the matrix is high - you have terse yet readable syntax from #1 where no conditions are needed and a clear though longer syntax for conditions which is flexible enough for many conditions per many settings like your #2.
my %allowed_status_changes = (
1 => [2,5],
2 => [1,5,{targets=>[2], conditions=>[$some_condition]}
,{targets=>[3,4], conditions=>[$other_condition, $more_cond]}]
3 => [4,2],
4 => [3,2],
5 => [],
);
sub is_allowed_transition {
my ($submitted_status, $original_status ) = #_;
foreach my $alowed_status (#$allowed_status_changes{$original_status}) {
return 1 if !ref $alowed_status && $alowed_status == $submitted_status;
if (ref $alowed_status) {
foreach my $target (#$alowed_status{targets}) {
foreach my $condition (#$alowed_status{conditions}) {
return 1 if check_condition($submitted_status
, $original_status, $condition);
}
}
}
}
return 0;
}
if ($prerequestsites
&& !$is_allowed_transition($submitted_status, $original_status )) {
return;
}
Although I agree with DVK for the most part, I have to say, once you start delving into arrays of arrays of hashes, you're reaching a code complexity level that is hard to maintain without much spinning of heads and bugs.
At this point, I'd probably reach for an object and a class, for a bit of syntactic sugar.
my $transitions = TransitionGraph->new();
$transition->add( 1, { targets => [ 2, 5 ] });
$transition->add( 2, { targets => [ 1, 5 ] });
$transition->add( 2, { targets => [ 2 ], conditions => [ $some_condition ] });
$transition->add( 2, { targets => [ 3, 4 ], conditions => [ $other_condition, $more_cond ]});
$transition->add( 3, { targets => [4,2] } );
$transition->add( 4, { targets => [3,2] } );
$transition->add( 5, { targets => [] } );
if( $transition->allowed( 1 , 3 )){
}
Class implementation is up to the user, but I'd use Moose.
The primary benefits of this is you're encapsulating how the state graph works so you can Just Use it and worry about how the graph works seperate from where its used.
nb. in the above proposed API, add() creates a new record if one does not exist, and updates that record if it does exist. This turned out to be simpler than having "upgrade" methods or "get this item and then modify it" techniques.
Internally, it could do this, or something like it:
sub add {
my ( $self , $input_state, $rules ) = #_;
my $state;
if ( $self->has_state( $input_state ) ) {
$state = $self->get_state( $input_state );
} else {
$state = TransitionGraphState->new( source_id => $input_state );
$self->add_state( $input_state, $state );
}
my $targets = delete $rules{targets};
for my $target ( #$targets ) {
$state->add_target( $target, $rules );
}
return $self;
}
sub allowed {
my ( $self, $from, $to ) = #_;
if ( not $self->has_state( $from ) ){
croak "NO source state $from in transition graph";
}
my $state = $self->get_state( $from );
return $state->allowed_to( $to );
}
This also has the cool perk of not requiring one particular code set to work on the sub-nodes, you can create seperate instances with their own behaviour just in case you want one source state to be treated differently.
$transition->add_state( 2, $some_other_class_wich_works_like_transitiongraphstate );
Hope this is helpful =).
There is nothing wrong with the second form. You are not going to be able to get around the fact that you must encode the state machine somewhere. In fact, I think having the entire machine encoded in one place like that is far easier to understand that something with too many layers of abstraction where you need to look in n different places to understand the flow of the machine.