File Coverage

blib/lib/Gtk2/Ex/RecordsFilter.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Gtk2::Ex::RecordsFilter;
2             our $VERSION = '0.03';
3 1     1   22105 use strict;
  1         2  
  1         33  
4 1     1   5 use warnings;
  1         1  
  1         23  
5 1     1   357 use Gtk2;
  0            
  0            
6             use constant TRUE => 1;
7             use constant FALSE => !TRUE;
8              
9             =head1 NAME
10              
11             Gtk2::Ex::RecordsFilter - A high level widget to browse reasonably large amounts of relational data and select a subset of records. This widget is inspired by the song browser of iTunes.
12              
13             =head1 SYNOPSIS
14              
15             use Gtk2 -init;
16             use Gtk2::Ex::RecordsFilter;
17              
18             # Create a recordset
19             my $recordset = [
20             [Automobiles,Cars,Toyota,Camry],
21             [Automobiles,SUV,BMW,Xi],
22             [Automobiles,SUV,Toyota,Highlander],
23             [Automobiles,Cars,Mitsubishi,Lancer]
24             ];
25              
26             # Create the recordsfilter object
27             my $recordsfilter = Gtk2::Ex::RecordsFilter->new;
28            
29             # Specify the headers for the columns
30             my $headers = ['Category', 'Sub-Category', 'Brand', 'Model'];
31             Gtk2::Ex::RecordsFilter->set_headers($headers);
32              
33             # Inject data into the widget
34             Gtk2::Ex::RecordsFilter->set_data($recordset);
35              
36             # Get a ref to its widget
37             my $recordsfilter_widget = $recordsfilter->get_widget();
38              
39             # Create the root window
40             my $window = Gtk2::Window->new;
41             $window->signal_connect(destroy => sub { Gtk2->main_quit; });
42             $window->set_default_size(500, 300);
43              
44             # Add the widget to the root window
45             $window->add($recordsfilter_widget);
46             $window->show_all;
47              
48             Gtk2->main;
49              
50             =head1 DESCRIPTION
51              
52             When working with large amounts of relational records (csv files, database records, music files index), a common task is to filter out a subset or records from a given set. For example, in a I database design, the I recordset, which is typically smaller than the I recordset, can be filtered out and the filtered subset of I records can then be used to perform additional tasks on the I records that they point to.
53              
54             A common example of this usage is the song browser in your own mp3 player application (for example, the iTunes application). This application will allow you to choose an mp3 file (the I record) based on criteria such as Artist, Album, Song (the I record). Once the I record is choosen (i.e., the Artist, Album and Song) it then performs a task on the I record (i.e., play the mp3 file).
55              
56             This B widget is inspired by the iTunes song browser widget. But this widget carries certain functionality which is not present in the iTunes song browser. The iTunes song browser allows the user to choose one song (one I record) at a time and play it. However, a more general usage should allow the user to choose multiple I records at a time. One approach for such multiple selections is to enable the user to click on different records with the CTRL key pressed and then choose all the highlighted records in one shot. This widget takes a different approach, which I call the I<'shopping cart'> approach. This is explained in the next section.
57              
58             =head1 USER INTERACTION
59              
60             The top half the widget shows all the master records. The records are shown in a hierarchical fashion similar to the iTunes song browser. Clicking on a parent node on the left-most box will cause all the boxes on its right to show B the child nodes.
61              
62             The user can click on any entry in the top half and then click on the I button and the record will show up in the bottom half. If the entry clicked is in one of the left boxes, then the widget automatically discovers all the child nodes and adds them to the selection. Upon adding to selection, the record is removed from the top half and shows up in the bottom half. The I button works in the reverse way.
63              
64             The user can click on multiple records in the top half using the CTRL key.
65              
66             =head1 METHODS
67              
68             =head2 Gtk2::Ex::RecordsFilter->new
69              
70             my $recordsfilter = Gtk2::Ex::RecordsFilter->new;
71              
72             The widget can be used to select a subset of these records (into a selection list). The methods C and C can be used to view the selection ( or unselection) at any point.
73              
74             'Selected' portion is referred to as the 'RIGHT' side (bottom). 'Unselected' portion is referred to as the 'LEFT' side (top)
75              
76             =cut
77              
78             sub new {
79             my ($class) = @_;
80             my $self = {};
81             $self->{CURRENT_SELECTED_COLUMN} = -1;
82             $self->{CURRENT_SELECTION}->{LEFT} = undef; # Which treeview is in focus ?
83             $self->{CURRENT_SELECTION}->{RIGHT} = undef;
84             $self->{ALL_ROWS_HASH} = undef;
85             $self->{COLUMN_FIELD_INDEX} = undef;
86             $self->{ROWS_INDEX}->{LEFT} = undef;
87             $self->{ROWS_INDEX}->{RIGHT} = undef;
88             $self->{DATA_COLUMN_COUNT} = undef; # Total number of columns
89             $self->{DATA_ROW_COUNT} = undef;
90             $self->{DATA_HASH}->{LEFT} = undef; # Tree data structure
91             $self->{DATA_HASH}->{RIGHT} = undef;
92             $self->{TREEVIEWS}->{LEFT} = [];
93             $self->{TREEVIEWS}->{RIGHT} = [];
94             $self->{TREEVIEW_LISTS}->{RIGHT} = [];
95             $self->{TREEVIEW_LISTS}->{LEFT} = [];
96             $self->{TREEVIEW_PANEL}->{LEFT} = undef;
97             $self->{TREEVIEW_PANEL}->{RIGHT} = undef;
98             $self->{DOWN_BUTTON} = undef;
99             $self->{UP_BUTTON} = undef;
100             $self->{HPANED} = undef;
101             $self->{HEADERS} = undef;
102             bless ($self, $class);
103             return $self;
104             }
105              
106             sub set_headers {
107             my ($self, $headers) = @_;
108             $self->{HEADERS} = $headers;
109             }
110              
111              
112             =head2 Gtk2::Ex::RecordsFilter->set_data
113              
114             $recordsfilter->set_data($recordset);
115              
116             The recordset is injected into the widget using this method. The widget will automatically create the HPaned children and the Selection buttons.
117              
118             =cut
119              
120             sub set_data {
121             my ($self, $recordset) = @_;
122             $self->{ALL_ROWS_ARRAY} = $recordset;
123             $self->_set_data($recordset);
124             }
125              
126             # This is a private method
127             # This method gets called everytime a treeview is clicked ('changed' signal)
128             # All the 'child' treeviews are reset by this method
129             sub _reset_lists {
130             my ($self, $left_or_right, $selectedcolumn, $selectedindices) = @_;
131              
132             # Sanity check! If nothing is selected, then return
133             return unless ($#{@$selectedindices} >= 0);
134              
135             my @selectedentries = map { $self->{TREEVIEW_LISTS}->{$left_or_right}->[$selectedcolumn]->[$_] } @$selectedindices;
136             $self->{CURRENT_SELECTED_COLUMN} = $selectedcolumn;
137             $self->{CURRENT_SELECTION}->{$left_or_right}->[$selectedcolumn] = \@selectedentries;
138            
139             # Reset the lists for all 'child' treeviews. The parents (including myself) can stay as it is.
140              
141             $self->_hash_to_lists_using_selection($left_or_right, $selectedcolumn);
142            
143             # Reset the model of all 'child' treeviews
144             for (my $i=$selectedcolumn+1; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
145             $self->_populate_model($left_or_right, $i);
146             }
147              
148             # Remove from the CURRENT_SELECTION if unfocused
149             for (my $i=0; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
150             my ($focusrow, $focuscol) = $self->{TREEVIEWS}->{$left_or_right}->[$i]->get_cursor();
151             $self->{CURRENT_SELECTION}->{$left_or_right}->[$i] = undef unless ($focusrow);
152             }
153             }
154              
155             # Each panel is just an hbox with required number of treeviews.
156             # Each treeview uses a ListStore (not a TreeStore)
157             sub _create_panel {
158             my ($self, $left_or_right) = @_;
159             my $hbox = Gtk2::HBox->new (FALSE, 1);
160             my $scrolledwindow_list;
161             for (my $i=0; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
162             my $header = 'None';
163             $header = $self->{HEADERS}->[$i] if $self->{HEADERS};
164              
165             my $treeview = _create_treeview($header);
166            
167             $treeview->set_headers_visible(TRUE) if ($left_or_right eq 'LEFT' && $self->{HEADERS});
168            
169             # Create new variables. Won't work inside the signal otherwise
170             my $columnid = $i;
171             my $side = $left_or_right;
172            
173             $treeview->get_selection->signal_connect('changed' =>
174             sub {
175             my ($selection) = @_;
176             my @selected_paths = $selection->get_selected_rows;
177             my @selected_indices = map { ($_->get_indices)[0] } @selected_paths;
178             $self->_reset_lists($side, $columnid, \@selected_indices);
179             }
180             );
181             $self->{TREEVIEWS}->{$left_or_right}->[$i] = $treeview;
182             my $scrolledwindow = Gtk2::ScrolledWindow->new;
183             $scrolledwindow->set_shadow_type ('etched-in');
184             $scrolledwindow->set_policy ('never', 'automatic');
185             $scrolledwindow->add ($treeview);
186             push @$scrolledwindow_list, $scrolledwindow;
187             #$hbox->pack_start($scrolledwindow, TRUE, TRUE, 0);
188             }
189             $hbox->pack_start($self->_pack_to_paned(@$scrolledwindow_list, $left_or_right), TRUE, TRUE, 0);
190             return $hbox;
191             }
192              
193             # Given a list of scrolledwindows, this one will pack them all into a set of HPaned
194             # This way it looks much better than choosing $scrolledwindow->set_policy ('automatic', 'automatic');
195             # Recursion once again !
196             sub _pack_to_paned {
197             my ($self, @list) = @_;
198             my $left_or_right = pop @list;
199             return $list[0] if ($#list == 0);
200             my $hpaned = Gtk2::HPaned->new();
201             $hpaned->add1($list[0]);
202             shift @list;
203             $self->{HPANED}->{$#list}->{$left_or_right} = $hpaned;
204             $hpaned->signal_connect('notify::position' =>
205             sub {
206             my ($hpaned) = shift;
207             $self->_adjust_left_and_right_hpanes($#list, $left_or_right, $hpaned->get_position);
208             }
209             );
210             $hpaned->add2($self->_pack_to_paned(@list, $left_or_right));
211             return $hpaned;
212             }
213              
214             sub _adjust_left_and_right_hpanes {
215             my ($self, $number, $left_or_right, $position) = @_;
216             my $other = $left_or_right eq 'LEFT' ? 'RIGHT' : 'LEFT';
217             my $this_position = $self->{HPANED}->{$number}->{$left_or_right}->get_position();
218             my $other_position= $self->{HPANED}->{$number}->{$other}->get_position();
219             $self->{HPANED}->{$number}->{$other}->set_position($this_position) if $this_position != $other_position;
220             }
221              
222             sub _create_treeview {
223             my ($header) = @_;
224             my $model = Gtk2::ListStore->new (qw/Glib::String/);
225             my $treeview = Gtk2::TreeView->new_with_model($model);
226             my $renderer = Gtk2::CellRendererText->new;
227             my $COLUMN_NUMBER = 0;
228             $renderer->set_data (column => $COLUMN_NUMBER);
229             $treeview->insert_column_with_attributes (-1, $header, $renderer, text => $COLUMN_NUMBER);
230             $treeview->set_headers_visible(FALSE);
231             $treeview->get_selection->set_mode ('multiple');
232             return $treeview;
233             }
234              
235             # Populate entries into a specified treeview
236             # This gets called whenever a treeview has to be reset
237             # Typically called from inside the _reset_lists method
238             sub _populate_model {
239             my ($self, $left_or_right, $treeview_number) = @_;
240             my $model = $self->{TREEVIEWS}->{$left_or_right}->[$treeview_number]->get_model();
241             my $list = $self->{TREEVIEW_LISTS}->{$left_or_right}->[$treeview_number];
242             $model->clear;
243             my $COLUMN_NUMBER = 0;
244             foreach my $entry (@$list) {
245             my $iter = $model->append;
246             $model->set ($iter, $COLUMN_NUMBER, $entry);
247             }
248             }
249              
250              
251             # This method will convert the hash datastructure into lists for
252             # displaying inside the treeview. Current selection must be known inorder
253             # to display all the treeviews on the list correctly.
254             sub _hash_to_lists_using_selection {
255             my ($self, $left_or_right, $selectedcolumn) = @_;
256              
257             my $currentlist = $self->{TREEVIEW_LISTS}->{$left_or_right}->[$selectedcolumn];
258              
259             # All the 'child' columns are nullified
260             for (my $i=$selectedcolumn; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
261             $self->{TREEVIEW_LISTS}->{$left_or_right}->[$i] = undef;
262             }
263            
264             # Call the recursive procedure to populate the 'child' columns
265             $self->_extract_keys ($left_or_right, $self->{DATA_HASH}->{$left_or_right}, 0, $selectedcolumn);
266            
267             # Remove duplicate entries from each list
268             foreach my $treeview_list(@{$self->{TREEVIEW_LISTS}->{$left_or_right}}) {
269             $treeview_list = _purify_array($treeview_list);
270             }
271              
272             my $newlist = $self->{TREEVIEW_LISTS}->{$left_or_right}->[$selectedcolumn];
273              
274             # This portion is required for the following reason
275             # If all the children of a parent get moved, the parent 'may' not realise this
276             # The parent will still stay around because we call _populate_model on only the 'child' treeviews
277             # The parent has to be explicitly repopulated to account for this
278             # Whenever the parent treeview is focussed, the $currentlist and $newlist will be different
279             # if all the child rows are gone
280             if ($currentlist and $newlist) {
281              
282             unless (_compare_arrays($currentlist, $newlist)) {
283             $self->_populate_model($left_or_right, $selectedcolumn);
284             }
285             }
286              
287             }
288              
289             # Utility method. Just compares two arrays serially
290             sub _compare_arrays {
291             my ($a, $b) = @_;
292             return FALSE if ($#{@$a} != $#{@$b});
293             for (my $i=0; $i<=$#{@$a}; $i++) {
294             return FALSE if ($a->[$i] ne $b->[$i]);
295             }
296             return TRUE;
297             }
298              
299             # Utility method. Make the array a unique list. Remove all duplicate entries
300             sub _purify_array {
301             my ($array) = @_;
302             my %hash = map {$_, 1} @$array;
303             my @a = keys %hash;
304             @a = sort @a;
305             return \@a;
306             }
307              
308             # This is the core recursive method that populates the 'child' columns
309             sub _extract_keys {
310             my ($self, $left_or_right, $hash, $thiscolumn, $selectedcolumn) = @_;
311            
312             # Here is the termination criteria for the recursion
313             return unless ($thiscolumn < $self->{DATA_COLUMN_COUNT});
314              
315             my $lists = $self->{TREEVIEW_LISTS}->{$left_or_right};
316              
317             my $selecteditems = $self->{CURRENT_SELECTION}->{$left_or_right}->[$thiscolumn];
318              
319             # Convert to a hash for easier search
320             my %selecteditemshash;
321             foreach my $selecteditem (@$selecteditems) {
322             $selecteditemshash{$selecteditem} = 1 unless (!$selecteditem);
323             }
324              
325             # Continue for each key in the hash
326             foreach my $key (keys %$hash) {
327             push @{$lists->[$thiscolumn]}, $key;
328             if ($thiscolumn <= $selectedcolumn) {
329             if (%selecteditemshash) {
330             next unless $selecteditemshash{$key};
331             }
332             }
333            
334             # Now continue to the next level of recursion
335             my $nextcolumn = $thiscolumn + 1;
336             $self->_extract_keys ($left_or_right, $hash->{$key}, $nextcolumn, $selectedcolumn);
337             }
338             }
339              
340             # Utility method
341             # Given two lists, returns a list with the common elements.
342             sub _array_intersection {
343             my ($a, $b) = @_;
344             my %ahash = map {$_, 1} @$a;
345             my %bhash = map {$_, 1} @$b;
346             my %chash;
347             foreach my $key (keys %ahash) {
348             $chash{$key} = 1 if ($bhash{$key});
349             }
350             foreach my $key (keys %bhash) {
351             $chash{$key} = 1 if ($ahash{$key});
352             }
353             my @c = keys %chash;
354             return \@c;
355             }
356              
357             # For a given column, return all the linenumbers that contain
358             # the specified set ot fields.
359             # This is required for partitioning the lists into 'selected' and 'unselected'
360             sub _locate {
361             my ($self, $left_or_right, $column) = @_;
362            
363             # The $linenumbers will get reduced using the _array_intersection()
364             # Initialize with all line numnbers
365             my $linenumbers;
366             for (my $i=0; $i<$self->{DATA_ROW_COUNT}; $i++) {
367             push @$linenumbers, $i;
368             }
369              
370             for (my $i=0; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
371             my $selection = $self->{CURRENT_SELECTION}->{$left_or_right}->[$i];
372            
373             # Get the linenumbers that contain this particular field-column
374             my $theselines;
375             foreach my $field (@$selection) {
376             push @$theselines, @{$self->{COLUMN_FIELD_INDEX}->[$i]->{$field}};
377             }
378            
379             # Now intersect it with the previous set of linenumbers
380             # And so on keep reducing this set for evey treeview (column)
381             if ($#{@$theselines} >= 0) {
382             $linenumbers = _array_intersection($linenumbers, $theselines);
383             }
384             }
385             return $linenumbers;
386             }
387              
388             # Rebuild the ROW_INDEX for both sides. Move the $linenumbers from one INDEX to the other
389             sub _move_from_to {
390             my ($self, $linenumbers, $from_left_or_right, $to_left_or_right) = @_;
391            
392             # Convert to hashes for easier search
393             my %fromhash = map {$_, 1} @{$self->{ROWS_INDEX}->{$from_left_or_right}};
394             my %tohash = map {$_, 1} @{$self->{ROWS_INDEX}->{$to_left_or_right}};
395            
396             foreach my $linenumber (@$linenumbers) {
397             delete $fromhash{$linenumber};
398             $tohash{$linenumber} = 1;
399             }
400             my @from = keys %fromhash;
401             my @to = keys %tohash;
402             $self->{ROWS_INDEX}->{$from_left_or_right} = \@from;
403             $self->{ROWS_INDEX}->{$to_left_or_right} = \@to;
404             }
405              
406             # Flat dataset (array of arrays) has to be converted into a hierarchical tree
407             # Recursion once again !!
408             sub _flat_to_hash {
409             my ($self, $rownumbers) = @_;
410             my $hash = {};
411             foreach my $rownumber(@$rownumbers) {
412             my $sub_hash = $hash;
413             my $row = $self->{ALL_ROWS_HASH}->{$rownumber};
414             for (my $i=0; $i<=$#{@$row}; $i++) {
415             if (!exists $sub_hash->{$row->[$i]}) {
416             if ($i<$self->{DATA_COLUMN_COUNT}-1) {
417             $sub_hash->{$row->[$i]} = {};
418             } else {
419             $sub_hash->{$row->[$i]} = 1;
420             }
421             }
422             $sub_hash = $sub_hash->{$row->[$i]};
423             }
424             }
425             return $hash;
426             }
427              
428             # One time affair. Prepare the INDEX for later use
429             sub _process_recordset {
430             my ($self, $recordset) = @_;
431             my $columncount = $#{@{$recordset->[0]}} + 1;
432             $self->{DATA_COLUMN_COUNT} = $columncount;
433             my $linecount = 0;
434             foreach my $record (@$recordset) {
435             $self->{ALL_ROWS_HASH}->{$linecount} = $record;
436             for (my $i=0; $i<=$#{@$record}; $i++) {
437             push @{$self->{COLUMN_FIELD_INDEX}->[$i]->{$record->[$i]}}, $linecount;
438             }
439             $linecount++;
440             }
441             $self->{DATA_ROW_COUNT} = $linecount;
442             }
443              
444             # Utility method. Removes a given set of entries from an array
445             sub _remove_from_array {
446             my ($array, $entries) = @_;
447             my %hash = map {$_, 1} @$array;
448             foreach my $entry (@$entries) {
449             delete $hash{$entry};
450             }
451             @$array = keys %hash;
452             return $array;
453             }
454              
455             # Once the INDEXes are rebuilt, then rebuild the HASHes and the LISTs
456             sub _recreate_hashes {
457             my ($self) = @_;
458             $self->{DATA_HASH}->{LEFT} = $self->_flat_to_hash($self->{ROWS_INDEX}->{LEFT});
459             $self->{DATA_HASH}->{RIGHT} = $self->_flat_to_hash($self->{ROWS_INDEX}->{RIGHT});
460             $self->_hash_to_lists_using_selection('LEFT', $self->{CURRENT_SELECTED_COLUMN});
461             $self->_hash_to_lists_using_selection('RIGHT', $self->{CURRENT_SELECTED_COLUMN});
462             }
463              
464             # You know what this is for !
465             sub _create_buttons {
466             my ($self) = @_;
467              
468             my $buttonlabel;
469             $self->{DOWN_BUTTON} = Gtk2::Button->new;
470             $buttonlabel = Gtk2::HBox->new (FALSE, 0);
471             $buttonlabel->pack_start (Gtk2::Label->new(' Add to Selection '), TRUE, TRUE, 0);
472             $buttonlabel->pack_start (Gtk2::Image->new_from_stock ('gtk-go-down', 'GTK_ICON_SIZE_BUTTON'), FALSE, FALSE, 0);
473             $self->{DOWN_BUTTON}->add($buttonlabel);
474            
475             $self->{UP_BUTTON} = Gtk2::Button->new;
476             $buttonlabel = Gtk2::HBox->new (FALSE, 0);
477             $buttonlabel->pack_start (Gtk2::Image->new_from_stock ('gtk-go-up', 'GTK_ICON_SIZE_BUTTON'), FALSE, FALSE, 0);
478             $buttonlabel->pack_start (Gtk2::Label->new(' Remove from Selection '), TRUE, TRUE, 0);
479             $self->{UP_BUTTON}->add($buttonlabel);
480              
481             $self->{DOWN_BUTTON}->signal_connect (clicked =>
482             sub {
483             $self->_move_and_rebuild_from_to('LEFT', 'RIGHT');
484             }
485             );
486            
487             $self->{UP_BUTTON}->signal_connect (clicked =>
488             sub {
489             $self->_move_and_rebuild_from_to('RIGHT', 'LEFT');
490             }
491             );
492             }
493              
494             # This method is responsible for doing the actual partition into 'selected' and 'unselected'
495             sub _move_and_rebuild_from_to {
496             my ($self, $from_left_or_right, $to_left_or_right) = @_;
497            
498             # Sanity check! Return if no treeview is selected (focused)
499             return unless ($self->{CURRENT_SELECTED_COLUMN} >=0);
500            
501             # First _locate the linenumbers to be moved based on the current selection
502             my $linenumbers_to_move = $self->_locate($from_left_or_right,$self->{CURRENT_SELECTED_COLUMN});
503            
504             # Rebuild the ROW_INDEXes for the movement
505             $self->_move_from_to($linenumbers_to_move, $from_left_or_right, $to_left_or_right);
506              
507             # Recreate the DATA_HASHes for the movement
508             $self->_recreate_hashes();
509              
510             $self->{TREEVIEW_LISTS}->{$from_left_or_right}->[$self->{CURRENT_SELECTED_COLUMN}] =
511             _remove_from_array($self->{TREEVIEW_LISTS}->{$from_left_or_right}->[$self->{CURRENT_SELECTED_COLUMN}], $self->{CURRENT_SELECTION}->{$from_left_or_right}->[$self->{CURRENT_SELECTED_COLUMN}]);
512             $self->{TREEVIEW_LISTS}->{$from_left_or_right}->[$self->{CURRENT_SELECTED_COLUMN}] =
513             _purify_array($self->{TREEVIEW_LISTS}->{$from_left_or_right}->[$self->{CURRENT_SELECTED_COLUMN}]);
514              
515             # On the FROM side, re-populate only the child treeviews
516             for (my $i=$self->{CURRENT_SELECTED_COLUMN}; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
517             $self->_populate_model($from_left_or_right, $i);
518             }
519            
520             # On the TO side, re-populate all the treeviews
521             for (my $i=0; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
522             $self->_populate_model($to_left_or_right, $i);
523             }
524             }
525              
526             # Show everything to start with
527             sub _initialize {
528             my ($self) = @_;
529            
530             # Display everything on the LEFT side to start with
531             for (my $i=0; $i<$self->{DATA_ROW_COUNT}; $i++) {
532             push @{$self->{ROWS_INDEX}->{LEFT}}, $i;
533             }
534             $self->{DATA_HASH}->{LEFT} = $self->_flat_to_hash($self->{ROWS_INDEX}->{LEFT});
535            
536             # Prepare LEFT and RIGHT sides for display
537             $self->_hash_to_lists_using_selection('LEFT', 0);
538             $self->_hash_to_lists_using_selection('RIGHT', 0);
539              
540             # Create the LEFT and RIGHT panels
541             $self->{TREEVIEW_PANEL}->{LEFT} = $self->_create_panel('LEFT');
542             $self->{TREEVIEW_PANEL}->{RIGHT} = $self->_create_panel('RIGHT');
543              
544             # Now populate all the treeviews
545             for (my $i=0; $i<$self->{DATA_COLUMN_COUNT}; $i++) {
546             $self->_populate_model('LEFT', $i);
547             $self->_populate_model('RIGHT', $i);
548             }
549             }
550              
551             # This is a private method
552             sub _set_data {
553             my ($self, $recordset) = @_;
554             $self->_process_recordset($recordset);
555             $self->_initialize();
556             $self->_create_buttons();
557             }
558              
559             =head2 Gtk2::Ex::RecordsFilter->get_widget
560              
561             my $recordsfilter_widget = $recordsfilter->get_widget();
562              
563             This method returns the widget of the filter object.
564              
565             =cut
566              
567             sub get_widget {
568             my ($self) = @_;
569            
570             my $buttonbox = Gtk2::HBox->new(TRUE, 0);
571             $buttonbox->pack_start (Gtk2::Label->new, TRUE, TRUE, 0);
572             $buttonbox->pack_start($self->{UP_BUTTON}, FALSE, TRUE, 0);
573             $buttonbox->pack_start($self->{DOWN_BUTTON}, FALSE, TRUE, 0);
574             $buttonbox->pack_start (Gtk2::Label->new, TRUE, TRUE, 0);
575            
576             my $vbox = Gtk2::VBox->new (FALSE, 1);
577             $vbox->pack_start($self->{TREEVIEW_PANEL}->{LEFT}, TRUE, TRUE, 0);
578             $vbox->pack_start($buttonbox, FALSE, TRUE, 0);
579             $vbox->pack_start($self->{TREEVIEW_PANEL}->{RIGHT}, TRUE, TRUE, 0);
580             return $vbox;
581             }
582              
583              
584             # Public method to get the 'selected' portion
585             sub get_selected_rowids {
586             my ($self) = @_;
587             return $self->{ROWS_INDEX}->{RIGHT};
588             }
589              
590             # Public method to get the 'unselected' portion
591             sub get_unselected_rowids {
592             my ($self) = @_;
593             return $self->{ROWS_INDEX}->{LEFT};
594             }
595              
596             =head2 Gtk2::Ex::RecordsFilter->get_selected_rows
597              
598             my $selected_rows = $recordsfilter->get_selected_rows();
599             print Dumper $selected_rows;
600              
601             This method returns the I contained in the bottom half of filter widget. A common usage is to have an I button which, when clicked, will invoke this method and use the returned results. Check out the examples directory to see this in action.
602              
603             $apply_button->signal_connect (clicked =>
604             sub {
605             my $selected_rows = $recordsfilter->get_selected_rows();
606             print Dumper $selected_rows;
607             }
608             );
609              
610             =cut
611              
612             sub get_selected_rows {
613             my ($self) = @_;
614             my @rows = map { $self->{ALL_ROWS_ARRAY}->[$_] } @{$self->{ROWS_INDEX}->{RIGHT}};
615             return \@rows;
616             }
617              
618             =head2 Gtk2::Ex::RecordsFilter->get_unselected_rows
619              
620             my $selected_rows = $recordsfilter->get_unselected_rows();
621             print Dumper $selected_rows;
622              
623              
624             This method returns the I contained in the top half of filter widget. This method too can be used with an I button like the one shown above.
625              
626             =cut
627              
628             sub get_unselected_rows {
629             my ($self) = @_;
630             my @rows = map { $self->{ALL_ROWS_ARRAY}->[$_] } @{$self->{ROWS_INDEX}->{LEFT}};
631             return \@rows;
632             }
633              
634             1;
635              
636             __END__