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__ |