Gtk2-Ex-GroupBy

 view release on metacpan or  search on metacpan

lib/Gtk2/Ex/GroupBy.pm  view on Meta::CPAN

package Gtk2::Ex::GroupBy;

our $VERSION = '0.02';

use warnings;
use strict;
use Gtk2 -init;
use Glib ':constants';
use Gtk2::Ex::Simple::List;
use Data::Dumper;

use constant ORDER_COLUMN => 0;
use constant MODEL_COLUMN => 1;
use constant NAME__COLUMN => 2;

sub new {
	my ($class, $slist) = @_;
	my $self  = {};
	bless ($self, $class);
	$self->{widget} = $self->_create_widget();
	return $self;
}

sub signal_connect {
	my ($self, $signal, $callback) = @_;
	$self->{signals}->{$signal} = $callback;
}

sub set_model {
	my ($self, $data) = @_;
	# Add explicitly or else TiedRows are gonna bite you
	foreach my $x (@{$data->{'groupby'}->[0]}) {
		push @{$self->{groupby_list1}->{data}}, [$x];
	}
	foreach my $x (@{$data->{'groupby'}->[1]}) {
		push @{$self->{groupby_list2}->{data}}, [$x];
	}
	_populate($self->{formula_treeview1}, $self->{formula_model1}, $data->{'formula'}->[0]);
	_populate($self->{formula_treeview2}, $self->{formula_model2}, $data->{'formula'}->[1]);
	$self->_check_visibility;
}

sub get_model {
	my ($self) = @_;
	$self->_update_model;
	return $self->{model};
}

sub get_widget {
	my ($self) = @_;
	return $self->{widget};
}

sub _create_widget {
	my ($self) = @_;
	my $groupby_list1 = Gtk2::Ex::Simple::List->new (
		'Group By Fields'    => 'text',
	);
	my $groupby_list2 = Gtk2::Ex::Simple::List->new (
		'Choose Group By Fields from'    => 'text',
	);	

	my @column_types;
	$column_types[0] = 'Glib::String';
	$column_types[NAME__COLUMN] = 'Glib::String';
	$column_types[ORDER_COLUMN] = 'Glib::String';
	$column_types[MODEL_COLUMN] = 'Gtk2::ListStore';

	my $formula_model1 = Gtk2::ListStore->new (@column_types);   
	my $formula_treeview1= Gtk2::TreeView->new($formula_model1);
	$self->_add_combo($formula_treeview1, $formula_model1, 'Fields', 'Aggregation Formula');

	my $formula_model2 = Gtk2::ListStore->new (@column_types);   
	my $formula_treeview2= Gtk2::TreeView->new($formula_model2);
	$self->_add_combo($formula_treeview2, $formula_model2, 'Choose Fields from', 'Choose Formula from');

	$self->{groupby_list1} = $groupby_list1;
	$self->{groupby_list2} = $groupby_list2;
	$self->{formula_treeview1} = $formula_treeview1;
	$self->{formula_treeview2} = $formula_treeview2;	
	$self->{formula_model1} = $formula_model1;
	$self->{formula_model2} = $formula_model2;	
	
	($groupby_list1->get_column(0)->get_cell_renderers)[0]->set(xalign => 0.5);
	($groupby_list2->get_column(0)->get_cell_renderers)[0]->set(xalign => 0.5);

	$groupby_list1->get_column(0)->set_expand(TRUE);
	$groupby_list2->get_column(0)->set_expand(TRUE);

	$groupby_list1->get_column(0)->set_alignment(0.5);
	$groupby_list2->get_column(0)->set_alignment(0.5);

	($formula_treeview1->get_column(0)->get_cell_renderers)[0]->set(xalign => 0.5);
	($formula_treeview2->get_column(0)->get_cell_renderers)[0]->set(xalign => 0.5);
	($formula_treeview1->get_column(1)->get_cell_renderers)[0]->set(xalign => 0.5);
	($formula_treeview2->get_column(1)->get_cell_renderers)[0]->set(xalign => 0.5);

	$formula_treeview1->get_column(0)->set_expand(TRUE);
	$formula_treeview2->get_column(0)->set_expand(TRUE);
	$formula_treeview1->get_column(1)->set_expand(TRUE);
	$formula_treeview2->get_column(1)->set_expand(TRUE);

	$formula_treeview1->get_column(0)->set_alignment(0.5);
	$formula_treeview2->get_column(0)->set_alignment(0.5);
	$formula_treeview1->get_column(1)->set_alignment(0.5);
	$formula_treeview2->get_column(1)->set_alignment(0.5);

	# $formula_treeview2->get_column(0)->set_visible(FALSE);

	$groupby_list1->set_reorderable(TRUE);
	$formula_treeview1->set_reorderable(TRUE);
		
	$groupby_list1->get_selection->set_mode('multiple');
	$formula_treeview1->get_selection->set_mode('multiple');
	$groupby_list2->get_selection->set_mode('multiple');
	$formula_treeview2->get_selection->set_mode('multiple');

	_populate($formula_treeview1, $formula_model1, $self->{model}->{'formula'}->[0]);
	_populate($formula_treeview2, $formula_model2, $self->{model}->{'formula'}->[1]);
		
	my $buttonbox = $self->_pack_buttons;

	my $groupby_list1_none_label = Gtk2::Label->new;
	$groupby_list1_none_label->set_line_wrap(TRUE);

	$groupby_list1_none_label->set_markup('<span foreground="red">(Please add from the list Below)</span>');
	$self->{groupby_list1_none_label} = $groupby_list1_none_label;

	my $vbox11 = Gtk2::VBox->new(FALSE);
	$vbox11->pack_start ($groupby_list1, TRUE, TRUE, 0);
	$vbox11->pack_start ($groupby_list1_none_label, FALSE, FALSE, 0);
		
	my $groupby_list2_none_label = Gtk2::Label->new;
	$groupby_list2_none_label->set_markup('<span foreground="red">(Please add from the list Below)</span>');
	$self->{groupby_list2_none_label} = $groupby_list2_none_label;

	my $vbox12 = Gtk2::VBox->new(FALSE);
	$vbox12->pack_start ($formula_treeview1, TRUE, TRUE, 0);
	$vbox12->pack_start ($groupby_list2_none_label, FALSE, FALSE, 0);

	my $vbox21 = Gtk2::VBox->new(FALSE);
	$vbox21->pack_start ($groupby_list2, TRUE, TRUE, 0);
	
	my $vbox22 = Gtk2::VBox->new(FALSE);
	$vbox22->pack_start ($formula_treeview2, TRUE, TRUE, 0);	
	
	my $hbox1 = Gtk2::HBox->new(TRUE);
	$hbox1->pack_start ($vbox11, TRUE, TRUE, 0);	
	$hbox1->pack_start ($vbox12, TRUE, TRUE, 0);	

	my $hbox2 = Gtk2::HBox->new(TRUE);
	$hbox2->pack_start ($vbox21, TRUE, TRUE, 0);	
	$hbox2->pack_start ($vbox22, TRUE, TRUE, 0);	

	my $vbox = Gtk2::VBox->new(FALSE);
	$vbox->pack_start ($hbox1, TRUE, TRUE, 0);	
	$vbox->pack_start ($buttonbox, FALSE, TRUE, 0);	
	$vbox->pack_start (_frame_it($hbox2), TRUE, TRUE, 0);	
	
	return $vbox;
}

sub _frame_it {
	my ($widget) = @_;
	my $frame = Gtk2::Frame->new;
	$frame->add($widget);
	return $frame;
}

sub _pack_buttons {
	my ($self) = @_;
	
	my $removebutton = Gtk2::Button->new;
	my $removebuttonlabel = Gtk2::HBox->new (FALSE, 0);
	$removebuttonlabel->pack_start (Gtk2::Label->new(' Remove '), TRUE, TRUE, 0);
	$removebuttonlabel->pack_start (Gtk2::Image->new_from_stock('gtk-go-down', 'GTK_ICON_SIZE_BUTTON'), FALSE, FALSE, 0);
	$removebutton->add($removebuttonlabel);

	my $okbutton = Gtk2::Button->new_from_stock('gtk-ok');

	my $addbutton = Gtk2::Button->new;
	my $addbuttonlabel = Gtk2::HBox->new (FALSE, 0);
	$addbuttonlabel->pack_start (Gtk2::Label->new(' Add '), TRUE, TRUE, 0);
	$addbuttonlabel->pack_start (Gtk2::Image->new_from_stock('gtk-go-up', 'GTK_ICON_SIZE_BUTTON'), FALSE, FALSE, 0);
	$addbutton->add($addbuttonlabel);

	my $clearbutton = Gtk2::Button->new_from_stock('gtk-clear');

	$removebuttonlabel->set_sensitive(FALSE);
	$addbuttonlabel->set_sensitive(FALSE);
	$removebutton->set_sensitive(FALSE);
	$addbutton->set_sensitive(FALSE);
	
	my $triggered = FALSE;
	
	$self->{groupby_list1}->get_selection->signal_connect ('changed' =>
		sub {
			return if $triggered;
			$triggered = TRUE;
			$removebuttonlabel->set_sensitive(TRUE);
			$addbuttonlabel->set_sensitive(FALSE);
			$removebutton->set_sensitive(TRUE);
			$addbutton->set_sensitive(FALSE);
			$self->{groupby_list2}->get_selection->unselect_all;
			$self->{formula_treeview1}->get_selection->unselect_all;
			$self->{formula_treeview2}->get_selection->unselect_all;
			$triggered = FALSE;
		}
	);

	$self->{groupby_list2}->get_selection->signal_connect ('changed' =>
		sub {
			return if $triggered;
			$triggered = TRUE;
			$removebuttonlabel->set_sensitive(FALSE);
			$addbuttonlabel->set_sensitive(TRUE);
			$removebutton->set_sensitive(FALSE);
			$addbutton->set_sensitive(TRUE);
			$self->{groupby_list1}->get_selection->unselect_all;
			$self->{formula_treeview1}->get_selection->unselect_all;
			$self->{formula_treeview2}->get_selection->unselect_all;
			$triggered = FALSE;
		}
	);

	$self->{formula_treeview1}->get_selection->signal_connect ('changed' =>
		sub {
			return if $triggered;
			$triggered = TRUE;
			$removebuttonlabel->set_sensitive(TRUE);
			$addbuttonlabel->set_sensitive(FALSE);
			$removebutton->set_sensitive(TRUE);
			$addbutton->set_sensitive(FALSE);
			$self->{groupby_list1}->get_selection->unselect_all;
			$self->{groupby_list2}->get_selection->unselect_all;
			$self->{formula_treeview2}->get_selection->unselect_all;
			$triggered = FALSE;
		}
	);

	$self->{formula_treeview2}->get_selection->signal_connect ('changed' =>
		sub {
			return if $triggered;
			$triggered = TRUE;
			$removebuttonlabel->set_sensitive(FALSE);			
			$removebutton->set_sensitive(FALSE);
			if ($#{@{$self->{groupby_list1}->{data}}} >= 0) {
				$addbuttonlabel->set_sensitive(TRUE);
				$addbutton->set_sensitive(TRUE);
			} else {
				$addbuttonlabel->set_sensitive(FALSE);
				$addbutton->set_sensitive(FALSE);			
			}
			$self->{groupby_list1}->get_selection->unselect_all;
			$self->{groupby_list2}->get_selection->unselect_all;
			$self->{formula_treeview1}->get_selection->unselect_all;
			$triggered = FALSE;
		}
	);	

	$addbutton->signal_connect ('button-release-event' => 
		sub {
			my $indices = _get_selected_indices($self->{formula_treeview2});
			if ($indices) {
				my @removeorder = reverse sort @$indices;
				my $data1 = _get_data($self->{formula_treeview1});
				my $data2 = _get_data($self->{formula_treeview2});
				foreach my $x (@removeorder) {
					push @$data1, splice (@$data2, $x, 1);
				}
				$self->{formula_treeview1}->set_no_show_all(FALSE);
				$self->{formula_treeview1}->show_all;
				_populate($self->{formula_treeview1}, $self->{formula_model1}, $data1);
				_populate($self->{formula_treeview2}, $self->{formula_model2}, $data2);
			} else {
				my @selected = $self->{groupby_list2}->get_selected_indices;
				my @removeorder = reverse sort @selected;
				foreach my $x (@removeorder) {
					push @{$self->{groupby_list1}->{data}}, 
						splice (@{$self->{groupby_list2}->{data}}, $x, 1);
				}
			}
			$self->_update_model;
			&{ $self->{signals}->{'changed'} } if $self->{signals}->{'changed'};
			$removebuttonlabel->set_sensitive(FALSE);
			$addbuttonlabel->set_sensitive(FALSE);
			$removebutton->set_sensitive(FALSE);
			$addbutton->set_sensitive(FALSE);
			return FALSE;
		}
	);  

	$clearbutton->signal_connect ('clicked' => 
		sub {
			my $removeorder = [0..$#{@{$self->{groupby_list1}->{data}}}];
			foreach my $x (reverse sort @$removeorder) {
				push @{$self->{groupby_list2}->{data}}, 
					splice (@{$self->{groupby_list1}->{data}}, $x, 1);
			}
			if ($#{@{$self->{groupby_list1}->{data}}} < 0) {
				my $data1 = _get_data($self->{formula_treeview1});
				my $data2 = _get_data($self->{formula_treeview2});
				push @$data2, @$data1 if $data1;
				$data1 = undef;
				_populate($self->{formula_treeview1}, $self->{formula_model1}, $data1);
				_populate($self->{formula_treeview2}, $self->{formula_model2}, $data2);
			}
			$self->_update_model;
			&{ $self->{signals}->{'changed'} } if $self->{signals}->{'changed'};
			return FALSE;
		}
	);
	
	$removebutton->signal_connect ('clicked' => 
		sub {
			my $indices = _get_selected_indices($self->{formula_treeview1});
			if ($indices) {
				my @removeorder = reverse sort @$indices;
				my $data1 = _get_data($self->{formula_treeview1});
				my $data2 = _get_data($self->{formula_treeview2});
				foreach my $x (@removeorder) {
					push @$data2, splice (@$data1, $x, 1);
				}
				_populate($self->{formula_treeview1}, $self->{formula_model1}, $data1);
				_populate($self->{formula_treeview2}, $self->{formula_model2}, $data2);
			} else {
				my @selected = $self->{groupby_list1}->get_selected_indices;
				my @removeorder = reverse sort @selected;
				foreach my $x (@removeorder) {
					push @{$self->{groupby_list2}->{data}}, 
						splice (@{$self->{groupby_list1}->{data}}, $x, 1);
				}
				if ($#{@{$self->{groupby_list1}->{data}}} < 0) {
					my $data1 = _get_data($self->{formula_treeview1});
					my $data2 = _get_data($self->{formula_treeview2});
					push @$data2, @$data1 if $data1;
					$data1 = undef;
					_populate($self->{formula_treeview1}, $self->{formula_model1}, $data1);
					_populate($self->{formula_treeview2}, $self->{formula_model2}, $data2);
				}
			}
			$self->_update_model;
			&{ $self->{signals}->{'changed'} } if $self->{signals}->{'changed'};
			$removebuttonlabel->set_sensitive(FALSE);
			$addbuttonlabel->set_sensitive(FALSE);
			$removebutton->set_sensitive(FALSE);
			$addbutton->set_sensitive(FALSE);
			return FALSE;
		}
	);

	$okbutton->signal_connect ('clicked' => 
		sub {
			&{ $self->{signals}->{'closed'} } if $self->{signals}->{'closed'};
		}
	);

	$self->{addbutton}    = $addbutton;
	$self->{removebutton} = $removebutton;
	$self->{okbutton}  = $okbutton;
	$self->{addbuttonlabel}    = $addbuttonlabel;
	$self->{removebuttonlabel} = $removebuttonlabel;
	
	my $hbox = Gtk2::HBox->new (TRUE, 0);
	$hbox->pack_start ($addbutton, TRUE, TRUE, 0);   
	$hbox->pack_start ($okbutton, TRUE, TRUE, 0);	
	$hbox->pack_start ($clearbutton, TRUE, TRUE, 0);	
	$hbox->pack_start ($removebutton, TRUE, TRUE, 0);	
	
	return $hbox;
}

sub _update_model {
	my ($self) = @_;
	my @data1;
	my @data2;
	foreach my $x (@{$self->{groupby_list1}->{data}}) {
		push @data1, $x->[0];
	}
	foreach my $x (@{$self->{groupby_list2}->{data}}) {
		push @data2, $x->[0];
	}
	$self->{model}->{'groupby'}->[0] = $#data1 >= 0 ? \@data1 : undef;
	$self->{model}->{'groupby'}->[1] = $#data2 >= 0 ? \@data2 : undef;
	$self->{model}->{'formula'}->[0] = _get_data($self->{formula_treeview1});
	$self->{model}->{'formula'}->[1] = _get_data($self->{formula_treeview2});
	$self->_check_visibility;
}

sub _get_selected_indices {
	my ($treeview) = @_;
	my @indices;
	my (@paths) = $treeview->get_selection->get_selected_rows;
	foreach my $path (@paths) {
		push @indices, $path->to_string;
	}
	return undef if $#indices < 0;
	return \@indices;
}

sub _get_data {
	my ($treeview) = @_;
	my $model = $treeview->get_model;
	my @data;
	for my $i(0..$model->iter_n_children-1) {
		my $col0 = $model->get($model->get_iter_from_string($i), 0);
		my $col1 = $model->get($model->get_iter_from_string($i), 2);
		push @data, { formula => $col0, field => $col1 };
	}
	return undef if $#data < 0;
	return \@data;
}

sub _add_combo {
	my ($self, $treeview, $model, $groupby_header, $formula_header) = @_;
	
	my $combo_renderer = Gtk2::CellRendererCombo->new;
	$combo_renderer->set (
		text_column => 0, # col in combo model with text to display
		editable => TRUE, # without this, it's just a text renderer
	); 
	$combo_renderer->signal_connect (edited => 
		sub {
			my ($cell, $text_path, $new_text) = @_;
			$model->set (
				$model->get_iter_from_string($text_path),
				ORDER_COLUMN, 
				$new_text
			);
			&{ $self->{signals}->{'changed'} } if $self->{signals}->{'changed'};
		}
	);
	$treeview->insert_column_with_attributes(
		-1, $formula_header, 
		$combo_renderer,
		text  => ORDER_COLUMN, 
		model => MODEL_COLUMN
	);
	$treeview->insert_column_with_attributes(
		-1, $groupby_header, 
		Gtk2::CellRendererText->new, 
		text => NAME__COLUMN
	);
}

sub _check_visibility {
	my ($self) = @_;
	if ($#{@{$self->{groupby_list1}->{data}}} >= 0) {
		$self->{groupby_list1_none_label}->hide;
		$self->{groupby_list1_none_label}->set_no_show_all(TRUE);
	} else {
		$self->{groupby_list1_none_label}->set_no_show_all(FALSE);
		$self->{groupby_list1_none_label}->show;	
	}
	if ($self->{formula_treeview1}->get_model->iter_n_children > 0) {
		$self->{groupby_list2_none_label}->hide;
		$self->{groupby_list2_none_label}->set_no_show_all(TRUE);
	} else {
		$self->{groupby_list2_none_label}->set_no_show_all(FALSE);
		$self->{groupby_list2_none_label}->show;	
	}
}

sub _populate{
	my ($treeview, $model, $temp) = @_;
	my $lookup_hash = { 
		'COUNT of' 	=> 0,
		'MAX of' 	=> 1,
		'MIN of' 	=> 2,
		'SUM of' 	=> 3, 
		'AVG of' 	=> 4,
		'STDDEV of' => 5,		
	};
	my $combomodel = Gtk2::ListStore->new('Glib::String');
	$combomodel->set($combomodel->append, 0, 'COUNT of');
	$combomodel->set($combomodel->append, 0, 'MAX of');
	$combomodel->set($combomodel->append, 0, 'MIN of');
	$combomodel->set($combomodel->append, 0, 'SUM of');
	$combomodel->set($combomodel->append, 0, 'AVG of');
	$combomodel->set($combomodel->append, 0, 'STDDEV of');
	$model->clear();
	my $i = 0;
	foreach my $data (@$temp) {
		$data->{formula} = $lookup_hash->{$data->{formula}};
		$data->{formula} = $combomodel->get(
			$combomodel->iter_nth_child (undef, $data->{formula})
			, 0
		);
		$model->set (
			$model->append,
			NAME__COLUMN, $data->{field},
			ORDER_COLUMN, $data->{formula},
			MODEL_COLUMN, $combomodel 
		);
	}
}

1;

__END__

=head1 NAME

Gtk2::Ex::GroupBy - A simple widget for specifying a I<Group By> and I<Aggregation>
on a relational table.

=head1 DESCRIPTION

A simple widget for specifying a I<Group By> and I<Aggregation>
on a relational table. Mainly for using with a SQL data source.

=head1 METHODS

=head2 new;

=head2 get_widget;

=head2 get_model;

=head2 set_model;

=head2 signal_connect;

=head1 COPYRIGHT & LICENSE

Copyright 2005 Ofey Aikon, All Rights Reserved.

This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Library General Public License as published by the Free
Software Foundation; either version 2.1 of the License, or (at your option) any
later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU Library General Public License for more
details.

You should have received a copy of the GNU Library General Public License along
with this library; if not, write to the Free Software Foundation, Inc., 59
Temple Place - Suite 330, Boston, MA  02111-1307  USA.

=cut



( run in 1.686 second using v1.01-cache-2.11-cpan-5837b0d9d2c )