File Coverage

blib/lib/Tk/DBI/Tree.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Tk::DBI::Tree;
2             #------------------------------------------------
3             # automagically updated versioning variables -- CVS modifies these!
4             #------------------------------------------------
5             our $Revision = '$Revision: 1.11 $';
6             our $CheckinDate = '$Date: 2003/08/13 12:58:13 $';
7             our $CheckinUser = '$Author: xpix $';
8             # we need to clean these up right here
9             $Revision =~ s/^\$\S+:\s*(.*?)\s*\$$/$1/sx;
10             $CheckinDate =~ s/^\$\S+:\s*(.*?)\s*\$$/$1/sx;
11             $CheckinUser =~ s/^\$\S+:\s*(.*?)\s*\$$/$1/sx;
12             #-------------------------------------------------
13             #-- package Tk::DBI::Tree -----------------------
14             #-------------------------------------------------
15            
16            
17 1     1   4426 use DBIx::Tree;
  1         45319  
  1         44  
18 1     1   1582 use Tk::Tree;
  0            
  0            
19             use Tk::Compound;
20             use Tk::ItemStyle;
21             use Tk::ResizeButton;
22             use Term::ANSIColor;
23             use base qw/Tk::Derived Tk::Frame/;
24            
25             use strict;
26            
27             Construct Tk::Widget 'DBITree';
28            
29             # ------------------------------------------
30             sub ClassInit
31             # ------------------------------------------
32             {
33             my($class,$mw) = @_;
34            
35             }
36            
37             # ------------------------------------------
38             sub Populate {
39             # ------------------------------------------
40             my ($obj, $args) = @_;
41             my $style;
42            
43             $obj->{dbh} = delete $args->{'-dbh'} || return error("No DB-Handle!");
44             $obj->{table} = delete $args->{'-table'} || return error("No Table!");
45             $obj->{debug} = delete $args->{'-debug'} || 0;
46             $obj->{idx} = delete $args->{'-idx'} || return error("No IndexColumn!");
47             $obj->{fields} = delete $args->{'-fields'} || return error("No Fields!");
48             $obj->{textcolumn} = delete $args->{'-textcolumn'} || return error("No Textcolumn!");
49             $obj->{joiner} = delete $args->{'-joiner'};
50             $obj->{start_id} = delete $args->{'-start_id'} || 1;
51             $obj->{command} = delete $args->{'-command'};
52             $obj->{parent_id} = delete $args->{'-parent_id'} || return error("No Parent_id!");
53             $obj->{columnWidths} = delete $args->{'-columnWidths'};
54             $obj->{maxchars} = delete $args->{'-maxchars'};
55             $obj->{colNames} = delete $args->{'-colNames'};
56             $obj->{entry_create_cb} = delete $args->{'-entry_create_cb'};
57             $obj->{time_column} = delete $args->{'-time_column'};
58             $obj->{opencmd} = delete $args->{'-opencmd'};
59             $obj->{closecmd} = delete $args->{'-closecmd'};
60            
61             my $h_style = delete $args->{'-highlight'} || [-foreground => 'blue'];
62             my $n_style = delete $args->{'-normal'} || [-foreground => 'black'];
63             $obj->{highlight} = $obj->ItemStyle('imagetext', @{$h_style});
64             $obj->{normal} = $obj->ItemStyle('imagetext', @{$n_style});
65            
66             $obj->SUPER::Populate($args);
67            
68            
69             my %specs;
70             $specs{refresh} = [qw/METHOD refresh Refresh/, undef];
71             $specs{close_all} = [qw/METHOD close_all Close_all/, undef];
72             $specs{listEntrys} = [qw/METHOD listEntrys ListEntrys/, undef];
73             $specs{remember} = [qw/METHOD remember Remember/, undef];
74             $specs{select_entrys} = [qw/METHOD select_entrys Select_entrys/, undef];
75             $specs{info} = [qw/METHOD info Info/, undef];
76             $specs{infozoom} = [qw/METHOD infozoom InfoZoom/, undef];
77             $specs{color_all} = [qw/METHOD color_all Color_All/, undef];
78             $specs{get_id} = [qw/METHOD get_id Get_Id/, undef];
79             $specs{parent_id} = [qw/METHOD parent_id Parent_Id/, undef];
80             $specs{see} = [qw/METHOD see See/, undef];
81             $specs{childs} = [qw/METHOD childs Childs/, undef];
82            
83             $specs{neu} = [qw/METHOD neu Neu/, undef];
84             $specs{move} = [qw/METHOD move Move/, undef];
85             $specs{copy} = [qw/METHOD copy Copy/, undef];
86             $specs{dele} = [qw/METHOD dele Dele/, undef];
87             $specs{refresh_id} = [qw/METHOD refresh_id Refresh_Id/, undef];
88            
89             $obj->ConfigSpecs(%specs);
90            
91             $obj->{last_refresh_time} = 1;
92            
93            
94             # Bildet den Tree in einem Array ab
95             if($obj->{joiner}) {
96             $obj->{dbtree} = DBIx::Tree->new(
97             connection => $obj->{dbh},
98             sql => $obj->makeSql,
99             method => sub { $obj->make_tree_list(@_) },
100             columns => [$obj->{idx}, $obj->{textcolumn}, $obj->{parent_id}],
101             start_id => $obj->{start_id},
102             recursive => 0,
103             );
104             } else {
105             $obj->{dbtree} = DBIx::Tree->new(
106             connection => $obj->{dbh},
107             table => $obj->{table},
108             method => sub { $obj->make_tree_list(@_) },
109             columns => [$obj->{idx}.'+0', $obj->{textcolumn}, $obj->{parent_id}.'+0'],
110             start_id => $obj->{start_id},
111             recursive => 0,
112             );
113             }
114             $obj->{tree} = $obj->Scrolled('Tree',
115             -scrollbars => 'ose',
116             -columns => scalar @{$obj->{fields}} + 1,
117             -header => 1,
118             -separator => ':',
119             )->pack(-expand => 1,
120             -fill => 'both');
121            
122             $obj->{tree}->configure(-opencmd => sub{ &{$obj->{opencmd}}(@_); $obj->{tree}->OpenCmd(@_)})
123             if(defined $obj->{opencmd} and ref $obj->{opencmd} eq 'CODE');
124             $obj->{tree}->configure(-closecmd => sub{ &{$obj->{closecmd}}(@_); $obj->{tree}->CloseCmd(@_)})
125             if(defined $obj->{closecmd} and ref $obj->{closecmd} eq 'CODE');
126            
127             $obj->Advertise("tree" => $obj->{tree});
128            
129             } # end Populate
130            
131            
132             # Class private methods;
133             # ------------------------------------------
134             sub refresh_id {
135             # ------------------------------------------
136             my $obj = shift || return error('No Object');
137             my $path = shift || return error('No Id');
138             my $data = shift || $obj->info('data', $path);
139            
140             my ($parent_path, $id) = ($1, $2)
141             if($path =~/(.+)\:(\d+)/);
142             $obj->dele($path);
143             $obj->neu($id, $parent_path, $data);
144             }
145            
146             # ------------------------------------------
147             sub neu {
148             # ------------------------------------------
149             my $obj = shift || return error('No Object');
150             my $id = shift || return error('No Id');
151             my $to_parent = shift || return error('No To Id');
152             my $data = shift || return error('No Data');
153             $data->{$obj->{idx}} = $id
154             unless $data->{$obj->{idx}};
155            
156             my $new_path = sprintf('%s:%d', $to_parent, $id);
157            
158             $obj->{tree}->add($new_path,
159             -itemtype => 'imagetext',
160             -data => $data,
161             -text => $obj->parse_text($data->{$obj->{textcolumn}}, $obj->{textcolumn}),
162             -style => $obj->{normal},
163             );
164            
165             &{$obj->{entry_create_cb}}($obj->{tree}, $new_path, $data)
166             if(defined $obj->{entry_create_cb} and ref $obj->{entry_create_cb} eq 'CODE');
167            
168             my $c = 1;
169             foreach my $field (@{$obj->{fields}}) {
170             $obj->{tree}->itemCreate( $new_path, $c++,
171             -text => $obj->parse_text($data->{$field}, $field),
172             -style => $obj->{normal},
173             );
174             }
175             push(@{$obj->{ListOfAllEntries}}, $new_path);
176             $obj->{tree}->setmode($to_parent, 'close')
177             if($obj->{tree}->getmode($to_parent) eq 'none');
178             $obj->{Paths}->{$id} = $new_path;
179             return $new_path;
180             }
181            
182             # ------------------------------------------
183             sub move {
184             # ------------------------------------------
185             my $obj = shift || return error('No Object');
186             my $from_entry = shift || return error('No From Id');
187             my $to_parent = shift || return error('No To Id');
188             my $data = shift;
189            
190             $obj->debug('Move - From Entry: %s to Parent: %s', $from_entry, $to_parent);
191            
192             my $to_path = $obj->{Paths}->{int $to_parent} || $obj->{Paths}->{$to_parent};
193             my $id = $obj->id($from_entry);
194            
195             my $did = $obj->dele($from_entry);
196             my $nid = $obj->neu($id, $to_path, $data);
197            
198             $obj->{ListOfAllEntries} = $obj->rm_from_array($obj->{ListOfAllEntries}, $from_entry);
199             push(@{$obj->{ListOfAllEntries}}, $nid);
200            
201             return $nid;
202             }
203            
204             # ------------------------------------------
205             sub copy {
206             # ------------------------------------------
207             my $obj = shift || return error('No Object');
208             my $from_entry = shift || return error('No From Id');
209             my $to_parent = shift || return error('No To Id');
210             my $data = shift;
211            
212             my $id = $obj->id($from_entry);
213             my $to_entry = sprintf('%s:%d', $obj->{Paths}->{$to_parent}, $id);
214            
215             my $hl = $obj->{tree};
216            
217             my @entry_args;
218             foreach ($hl->entryconfigure($from_entry)) {
219             push @entry_args, $_->[0] => $_->[4] if defined $_->[4];
220             }
221            
222             $hl->add($to_entry, @entry_args);
223             $hl->entryconfigure($to_entry, -data => $data)
224             if defined $data;
225            
226             foreach my $col (1 .. $hl->cget(-columns)-1) {
227             my @item_args;
228             foreach ($hl->itemConfigure($from_entry, $col)) {
229             push @item_args, $_->[0] => $_->[4] if defined $_->[4];
230             }
231             $hl->itemCreate($to_entry, $col, @item_args);
232             }
233             $obj->refresh_id($to_entry, $data);
234             push(@{$obj->{ListOfAllEntries}}, $to_entry);
235             $obj->{Paths}->{$id} = $to_entry;
236             return $to_entry;
237             }
238            
239             # ------------------------------------------
240             sub dele {
241             # ------------------------------------------
242             my $obj = shift || return error('No Object');
243             my $id = shift || return error('No Id');
244            
245             my $parent = $obj->info('parent', $id)
246             || return;
247             $obj->{tree}->deleteEntry($id);
248             $obj->{ListOfAllEntries} = $obj->rm_from_array($obj->{ListOfAllEntries}, $id);
249             $obj->{tree}->setmode($parent => 'none')
250             unless( $obj->info('children', $parent) );
251            
252             delete $obj->{Paths}->{ $obj->id($id) };
253            
254             return $id;
255             }
256            
257             # ------------------------------------------
258             sub refresh {
259             # ------------------------------------------
260             my $obj = shift || return error('No Object');
261             my $redraw = shift || $obj->Table_is_Change($obj->{last_refresh_time}, $obj->{table}) || return;
262            
263             unless(defined $obj->{tree_buttons}) {
264             my $c = -1;
265             foreach my $name ($obj->{textcolumn}, @{$obj->{fields}}) {
266             $c++;
267             $obj->{tree_buttons}->{$name} = $obj->{tree}->ResizeButton(
268             -text => $obj->{colNames}->[$c] || $name,
269             -relief => 'flat',
270             -border => -2,
271             -pady => -10,
272             -padx => 10,
273             -widget => \$obj->{tree},
274             -column => $c,
275             );
276            
277             $obj->Advertise(sprintf("HB_%s",$name) => $obj->{tree_buttons}->{$name});
278            
279             $obj->{tree}->headerCreate($c,
280             -itemtype => 'window',
281             -widget => $obj->{tree_buttons}->{$name},
282             );
283            
284             $obj->{tree}->columnWidth($c, $obj->{columnWidths}->[$c])
285             if(defined $obj->{columnWidths}->[$c]);
286            
287             }
288             }
289            
290             $obj->{fieldtypes} = $obj->getFieldTypes
291             unless(defined $obj->{fieldtypes});
292            
293             $obj->{tree}->configure(-command => $obj->{command})
294             if(defined $obj->{command} and ref $obj->{command} eq 'CODE');
295            
296             $obj->remember();
297             @{$obj->{ListOfAllEntries}} = ();
298             $obj->{Paths} = {};
299             $obj->{tree}->delete('all');
300             $obj->list();
301             $obj->{tree}->focus;
302             $obj->select_entrys($obj->{FoundEntrys});
303            
304             if($obj->{zoom} and scalar @{$obj->{FoundEntrys}}) {
305             $obj->{zoom} = 0;
306             $obj->zoom();
307             }
308            
309             $obj->{last_refresh_time} = time;
310             }
311            
312             # ------------------------------------------
313             sub select_entrys {
314             # ------------------------------------------
315             my $obj = shift || return error('No Object');
316             $obj->{FoundEntrys} = shift || return $obj->{FoundEntrys};
317             $obj->color_all();
318             $obj->zoom if($obj->infozoom);
319            
320             unless(grep(/\:/, @{$obj->{FoundEntrys}})){
321             my @found;
322             foreach my $id (@{$obj->{FoundEntrys}} ) {
323             my $entry = ($obj->{Paths}->{$id} || $obj->{Paths}->{ int $id });
324             push(@found, $entry)
325             if(defined $entry);
326             }
327             $obj->{FoundEntrys} = \@found;
328             }
329            
330             foreach (@{$obj->{FoundEntrys}}) {
331             next unless($obj->{tree}->infoExists($_));
332             $obj->to_parent_open($_);
333             $obj->color_row($_, $obj->{highlight});
334             }
335             my $entry = $obj->{FoundEntrys}->[0] || return;
336            
337             $obj->{tree}->open(1);
338             $obj->{tree}->anchorSet($entry);
339             $obj->{tree}->selectionSet($entry);
340             $obj->{tree}->see($entry);
341             }
342            
343             # ------------------------------------------
344             sub childs {
345             # ------------------------------------------
346             my $obj = shift || return error('No Object');
347             my $id = shift || return error('No Id');
348            
349             return grep(/^$id/, @{$obj->listEntrys()});
350             }
351            
352             # ------------------------------------------
353             sub see {
354             # ------------------------------------------
355             my $obj = shift || return error('No Object');
356             my $ids = shift || return error('No Id in see()!');
357             my @ret;
358            
359             $obj->{tree}->selectionClear();
360             foreach my $id ( split(/[^0-9A-Za-z]/, $ids) ) {
361             my $entry = $obj->{Paths}->{$id} || $obj->{Paths}->{int($id)} || next;
362             $obj->debug('%s: = %s', $id, $entry);
363             $obj->to_parent_open($entry);
364             $obj->{tree}->selectionSet($entry);
365             push(@ret, $entry);
366             }
367            
368             my $last = $ret[0] or return error('No Id to see!');
369             $obj->{tree}->open(1);
370             $obj->{tree}->see($last);
371             $obj->{tree}->anchorSet($last);
372             $obj->select_entrys( \@ret )
373             if(scalar @ret > 1);
374             return @ret;
375             }
376            
377            
378             # ------------------------------------------
379             sub color_row {
380             # ------------------------------------------
381             my $obj = shift || return error('No Object');
382             my $id = shift || return error('No Id');
383             my $color = shift || $obj->{normal};
384            
385             return unless($obj->info('exists', $id));
386            
387             my $i = 0;
388             foreach ($obj->{textcolumn}, @{$obj->{fields}}) {
389             $obj->{tree}->itemConfigure($id, $i, -style => $color);
390             $i++;
391             }
392             }
393            
394            
395             # ------------------------------------------
396             sub color_all {
397             # ------------------------------------------
398             my $obj = shift || return error('No Object');
399             my $color = shift || $obj->{normal};
400            
401             foreach my $entry (sort @{$obj->{ListOfAllEntries}}) {
402             $obj->color_row($entry, $color);
403             }
404             }
405            
406             # ------------------------------------------
407             sub remember {
408             # ------------------------------------------
409             my $obj = shift || return error('No Object');
410             my $rem = shift;
411             my $ret;
412             unless( $rem ) {
413             foreach my $entry (@{$obj->{ListOfAllEntries}}) {
414             my $mode = 'none';
415             $mode = $obj->{tree}->getmode($entry)
416             if($obj->{tree}->infoExists($entry));
417             $ret->{status}->{$entry} = $obj->{tree}->{status}->{$entry} = $mode
418             unless($mode eq 'none');
419             }
420             my $i = 0;
421             my $conf;
422             foreach my $spalte ($obj->{textcolumn}, @{$obj->{fields}}) {
423             push(@{$ret->{widths}}, $obj->{tree}->columnWidth($i++));
424             }
425             } else {
426             $obj->{tree}->{status} = $rem->{status}
427             if(defined $rem->{status});
428             $obj->{widths} = $rem->{widths}
429             if(defined $rem->{widths});
430             $obj->refresh('redraw');
431             }
432             return $ret;
433             }
434            
435             # ------------------------------------------
436             sub make_tree_list {
437             # ------------------------------------------
438             my $obj = shift || return error('No Object');
439             my %parms = @_;
440            
441             my @parent_ids = @{ $parms{parent_id} };
442            
443             my $treeval = '';
444             foreach (@parent_ids) {
445             $treeval .= "$_:";
446             }
447             $treeval .= $parms{id};
448             push @{$obj->{ListOfAllEntries}}, $treeval;
449             }
450            
451             # ------------------------------------------
452             sub get_id {
453             # ------------------------------------------
454             my $obj = shift || return error('No Object');
455             my $w = shift || return error('No Widget!');
456             my $ev = $w->XEvent;
457             my $id = $w->nearest($ev->y);
458             $obj->{tree}->anchorSet($id);
459             $obj->{tree}->selectionClear();
460             $obj->{tree}->selectionSet($id);
461             my ($col, $col_nr) = $obj->x2col( $ev->x + $w->xview() );
462             my $wert = $w->itemCget($id, $col_nr, -text);
463             return ($id, $col, $col_nr, $wert);
464             }
465            
466             # ------------------------------------------
467             sub x2col {
468             # ------------------------------------------
469             my $obj = shift || return error('No Object');
470             my $x = shift;
471             my $c = 0;
472             my $von = 0;
473             foreach my $name ($obj->{textcolumn}, @{$obj->{fields}}) {
474             my $breite = $obj->{tree}->columnWidth( $c);
475             my $bis = $von + $breite;
476             return (($obj->{colNames}->[$c] || $name), $c)
477             if($x >= $von && $x <= $bis);
478             $von += $breite;
479             $c++;
480             }
481             }
482            
483             # ------------------------------------------
484             sub infozoom {
485             # ------------------------------------------
486             my $obj = shift || return error('No Object');
487             $obj->debug('Zoom is %s', ( $obj->{zoom} ? 'on' : 'off' ));
488             return $obj->{zoom};
489             }
490            
491             # ------------------------------------------
492             sub zoom {
493             # ------------------------------------------
494             my $obj = shift || return error('No Object');
495             return unless($obj->{FoundEntrys});
496             $obj->{zoom} = ($obj->{zoom} ? undef : 1);
497             if($obj->{zoom}) {
498             foreach my $entry (sort @{$obj->{ListOfAllEntries}}) {
499             next unless($entry);
500             my $search = $entry;
501             $search =~ s/\:/\\:/sig;
502             unless(grep(/^$search/, @{$obj->{FoundEntrys}})) {
503             unless($obj->{tree}->infoHidden($entry)) {
504             $obj->{tree}->hide('entry', $entry);
505             push(@{$obj->{HiddenEntrys}}, $entry);
506             }
507             }
508             }
509             } else {
510             foreach my $entry (@{$obj->{HiddenEntrys}}) {
511             $obj->{tree}->show('entry', $entry)
512             if($obj->{tree}->infoHidden($entry));
513             }
514             @{$obj->{HiddenEntrys}} = qw//;
515             }
516             }
517            
518            
519             # ------------------------------------------
520             sub makeSql {
521             # ------------------------------------------
522             my $obj = shift || return error('No Object');
523             my $sql;
524            
525             if($obj->{joiner}) {
526             $sql = sprintf('SELECT %s, %s, %s, %s FROM %s LEFT JOIN %s ON %s ORDER BY %s',
527             $obj->{idx}, $obj->{textcolumn},join(',', @{$obj->{fields}}), $obj->{parent_id},
528             $obj->{table}->[0], $obj->{table}->[1],
529             $obj->{joiner},
530             $obj->{textcolumn}
531             );
532             } else {
533             $sql = sprintf('select %s, %s, %s, %s from %s %s ORDER BY %s, %s',
534             $obj->{idx}, $obj->{textcolumn},join(',', @{$obj->{fields}}), $obj->{parent_id},
535             $obj->{table},
536             (defined $obj->{where} ? $obj->{where} : ''),
537             $obj->{parent_id}, $obj->{idx}
538             );
539             }
540             $obj->debug($sql)
541             if($obj->{debug});
542             return $sql;
543             }
544            
545             # ------------------------------------------
546             sub getFieldTypes {
547             # ------------------------------------------
548             my $obj = shift or return warn("No object");
549             my $dbh = $obj->{dbh};
550             my $table = ref $obj->{table} ? $obj->{table}->[0] : $obj->{table};
551            
552             return $obj->{$table}->{fieldtypes}
553             if(defined $obj->{$table}->{fieldtypes});
554            
555             $obj->{$table}->{fieldtypes} = $dbh->selectall_hashref("show fields from $table", 'Field')
556             or return $obj->debug($dbh->errstr);
557            
558             return $obj->{$table}->{fieldtypes};
559             }
560            
561            
562             # ------------------------------------------
563             sub list {
564             # ------------------------------------------
565             my $obj = shift || return error('No Object');
566             my $idx = ( index($obj->{idx}, '.') ? ( split( '\.', $obj->{idx} ) )[-1] : $obj->{idx});
567            
568             my $len = $1 if($obj->getFieldTypes->{$idx}->{Type} =~ /(\d+)/);
569            
570             if($DBIx::Tree::VERSION < 1) {
571             $obj->{dbtree}->do_query;
572             $obj->{dbtree}->tree;
573             } else {
574             $obj->{dbtree}->traverse;
575             }
576            
577             my $sql = $obj->makeSql;
578            
579             my $DATA = $obj->{dbh}->selectall_hashref( $sql, $idx)
580             or return error($obj->{dbh}->errstr);
581            
582             foreach my $id (sort @{$obj->{ListOfAllEntries}}) {
583             my $item_id = (split( /:/, $id ))[-1];
584             $obj->{Paths}->{$item_id} = $id;
585             my $row = $DATA->{$item_id} || $DATA->{sprintf("%0${len}d", $item_id)};
586             $obj->{tree}->add($id,
587             -itemtype => 'imagetext',
588             -data => $row,
589             -text => $obj->parse_text($row->{$obj->{textcolumn}}, $obj->{textcolumn}),
590             -style => $obj->{normal},
591             );
592            
593             &{$obj->{entry_create_cb}}($obj->{tree}, $id, $row)
594             if(defined $obj->{entry_create_cb} and ref $obj->{entry_create_cb} eq 'CODE');
595            
596             my $c = 1;
597             foreach my $field (@{$obj->{fields}}) {
598             $obj->{tree}->itemCreate( $id, $c++,
599             -text => $obj->parse_text($row->{$field}, $field),
600             -style => $obj->{normal},
601             );
602             }
603             }
604            
605             # Draw Indicators
606             $obj->{tree}->autosetmode;
607            
608             foreach my $entry (@{$obj->{ListOfAllEntries}}) {
609             if(defined $obj->{tree}->{status}->{$entry} and $obj->{tree}->{status}->{$entry} eq 'open') {
610             $obj->{tree}->close($entry);
611             }
612             }
613            
614             }
615            
616             # ------------------------------------------
617             sub close_all {
618             # ------------------------------------------
619             my $obj = shift || return error('No Object');
620             return unless(ref $obj->{ListOfAllEntries} eq 'ARRAY');
621             foreach my $entry (sort @{$obj->{ListOfAllEntries}}) {
622             $obj->{tree}->close($entry);
623             }
624             }
625            
626             # ------------------------------------------
627             sub to_parent_open{
628             # ------------------------------------------
629             my $obj = shift || return error('No Object');
630             my $entry = shift;
631             my $path = $obj->{start_id};
632             my $id = $obj->id($entry);
633             foreach my $e (split(/\:/, $entry)) {
634             next if($e eq $obj->{start_id});
635             next if($e eq $id);
636             $path .= sprintf(':%d', $e);
637             $obj->{tree}->open($path);
638             }
639             }
640            
641             # ------------------------------------------
642             sub parse_text {
643             # ------------------------------------------
644             my $obj = shift;
645             my $text = shift || return ' ';
646             my $field = shift || return error('No FieldName!');
647             my $maxchars =
648             (ref $obj->{maxchars} eq 'HASH'
649             ? $obj->{maxchars}->{$field}
650             : $obj->{maxchars}
651             ) || 0;
652             $text = substr($text, 0, $maxchars).'...'
653             if($maxchars and length($text)>$maxchars);
654             $text =~ s/(\r|\n)//sig;
655             return $text;
656             }
657            
658             # ------------------------------------------
659             sub parent_id {
660             # ------------------------------------------
661             my $obj = shift || return error('No Object');
662             my $path = shift || return error('No Path');
663             my $parent = $obj->info('parent', $path)
664             or return error('Parent not found!');
665             return (split( /:/, $parent ))[-1];
666             }
667            
668            
669             # ------------------------------------------
670             sub id {
671             # ------------------------------------------
672             my $obj = shift || return error('No Object');
673             my $path = shift || return error('No Path');
674            
675             return (split( /:/, $path ))[-1];
676             }
677            
678             # ------------------------------------------
679             sub listEntrys {
680             # ------------------------------------------
681             my $obj = shift;
682             return $obj->{ListOfAllEntries};
683             }
684            
685             # ------------------------------------------
686             sub info {
687             # ------------------------------------------
688             my $obj = shift or return error("No object");
689             my $typ = shift or return error("No Type");
690             my $entry = shift;
691            
692             if($typ =~ /^(selection|anchor|dragsite|dropsite)$/si) {
693             my @ids = $obj->{tree}->info($typ);
694             return \@ids;
695             }
696            
697             if($entry !~ /\:/) {
698             $entry = $obj->{Paths}->{$entry}
699             or return error('Can\'t find <%s> in Paths!', $entry);
700             }
701            
702             return error('Can\'t find Id: %s', $entry)
703             if($typ ne 'exists' and ! $obj->{tree}->info('exists', $entry));
704             return $obj->{tree}->info(${typ}, $entry);
705             }
706            
707            
708             # ------------------------------------------
709             sub getSqlArray {
710             # ------------------------------------------
711             my $obj = shift or return error("No object");
712             my $sql = shift or return error('No Sql');
713             my $dbh = $obj->{dbh};
714            
715             my $sth = $dbh->prepare($sql) or warn("$DBI::errstr - $sql");
716             $sth->execute or warn("$DBI::errstr - $sql");
717             return $sth->fetchall_arrayref;
718             }
719            
720             # ------------------------------------------
721             sub Table_is_Change {
722             # ------------------------------------------
723             my $obj = shift or return error("No object");
724             my $lasttime = shift || $obj->{last_refresh_time}; # No last time, first request!
725             my $table = shift || $obj->{table} || $obj->{table}->[0];
726            
727             my $dbh = $obj->{dbh};
728             my $ret = 0;
729            
730             my $data = $dbh->selectall_hashref(sprintf("SHOW TABLE STATUS LIKE '%s'", $table),'Name')
731             or return $obj->debug($dbh->errstr);
732            
733             my $unixtime = $obj->getSqlArray(sprintf("select UNIX_TIMESTAMP('%s')", $data->{$table}->{Update_time}))
734             if(defined $data->{$table}->{Update_time});
735            
736             $obj->{last_refresh_time} = time;
737            
738             if(defined $unixtime->[0][0] and $unixtime->[0][0] > $lasttime) {
739             return 1;
740             }
741             }
742            
743             # ------------------------------------------
744             sub rm_from_array {
745             # ------------------------------------------
746             my $obj = shift || return error('No Object');
747             my $arr = shift || return error('No Array');
748             my $id = shift || return error('No Id');
749            
750             my @new_array = grep(!/$id/, @$arr);
751            
752             return \@new_array;
753             }
754            
755            
756            
757             # ------------------------------------------
758             sub debug {
759             # ------------------------------------------
760             my $obj = shift;
761             my $msg = shift || return;
762             print color 'green';
763             print 'Tk::Tree: ', sprintf($msg, @_), "\n"
764             if($obj->{debug});
765             print color 'reset';
766             }
767            
768             # ------------------------------------------
769             sub error {
770             # ------------------------------------------
771             my $msg = shift;
772             my ($package, $filename, $line, $subroutine, $hasargs,
773             $wantarray, $evaltext, $is_require, $hints, $bitmask) = caller(1);
774             my $error = sprintf("ERROR in %s:%s #%d: <%s>\n",
775             $package, $subroutine, $line, sprintf($msg, @_));
776             print color 'bold red';
777             print $error;
778             print color 'reset';
779             return undef;
780             }
781            
782            
783             1;
784            
785            
786             =head1 NAME
787            
788             Tk::DBI::Tree - Megawidget to display a table column in a tree.
789            
790             =head1 SYNOPSIS
791            
792             use Tk;
793             use Tk::DBI::Tree;
794            
795             my $top = MainWindow->new;
796             my $tkdbi = $top->DBITree(
797             -dbh => $dbh,
798             -table => 'Inventory',
799             -textcolumn => 'name',
800             -idx => 'id',
801             -columnWidths => [undef, undef, undef, 150],
802             -fields => [qw(changed_by changed_at descr)],
803             -parent_id => 'parent_id',
804             -start_id => 1,
805             -maxchars => { descr => 25 },
806             )->pack(-expand => 1,
807             -fill => 'both');
808            
809             MainLoop;
810            
811             =head1 DESCRIPTION
812            
813             This is a megawidget to display a sql statement from your database in a tree view
814             widget. When you've got one of those nasty self-referential tables that you
815             want to bust out into a tree, this is the module to check out.
816            
817             =head1 WIDGET-SPECIFIC OPTIONS
818            
819             =head2 -dbh => $ref_on_database_handle
820            
821             A database handle, this will return an error if it is'nt defined.
822            
823             =head2 -debug => [I<0>|1]
824            
825             This is a switch to turn on debug output to the standard console (STDOUT)
826            
827             =head2 -table => 'tablename'
828            
829             The table to display.
830            
831             =head2 -idx => 'index_column'
832            
833             The index column from the table.
834            
835             =head2 -fields => [col0, col1, col2, ...]
836            
837             List of additional fields to display.
838            
839             =head2 -colNames => [col0, col1, col2, ...]
840            
841             List of alternative names for every column. This will display on header.
842            
843             =head2 -where => 'WHERE foo == 1, ...'
844            
845             Additional where statement for choice rows in table.
846            
847             =head2 -textcolumn => colname
848            
849             The name of the column to be displayed in the tree..
850            
851             =head2 -start_id => integer
852            
853             The id, where the widget will start to create the tree. Default is 1.
854            
855             =head2 -columnWidths => [colWidth_0, colWidth_1, colWidth_2, ...]
856            
857             Default field column width.
858            
859             =head2 -highlight => I<[-foreground => 'blue']>
860            
861             Style for founded Entries.
862            
863             =head2 -normal => I<[-foreground => 'black']>
864            
865             Default style for Entries.
866            
867             =head2 -maxchars => number or {col1 =number}
868            
869             Maximum number of characters to be displayed within the cells. Global
870             validity or set only for named columns.
871             I.E.:
872            
873             -maxchars => {
874             descr => 25,
875             name => 10,
876             },
877             # or ....
878             -maxchars => 25, # global for all fields
879            
880            
881             =head2 -time_column => $name_from_time_column
882            
883             Maximum number of characters to be displayed within the cells. Global
884             validity or set only for named columns.
885             I.E.:
886            
887             -maxchars => {
888             descr => 25,
889             name => 10,
890             },
891             # or ....
892             -maxchars => 25, # global for all fields
893            
894             =head1 METHODS
895            
896             These are the methods you can use with this Widget.
897            
898             =head2 $DBITree->refresh('reload');
899            
900             Refresh the tree. if you call this method with the parameter reload
901             then this will reload the table from database. If you call this without parameter, then
902             look this widget is the table changed (update date) at the last refresh. If this true, then
903             load this the complete table and redraw the tree.
904            
905             =head2 $DBITree->refresh_id( I, I );
906            
907             This will refresh (delete -> new) a Tree item.
908            
909             =head2 $DBITree->copy( I, I, I );
910            
911             Copy an entry (entry) to a parent branch (to_parent_entry) with data (data);
912            
913             =head2 $DBITree->move( I, I, I );
914            
915             Move an entry (from_entry) to a parent branch (to_parent_entry) with data (data);
916            
917             =head2 $DBITree->dele( I );
918            
919             Delete a entry.
920            
921             =head2 $DBITree->neu( I, I, I );
922            
923             Create a entry.
924            
925            
926             =head2 $DBITree->close_all;
927            
928             close all tree branches.
929            
930             =head2 $DBITree->info('anchor, bbox, children, B, dragsite, dropsite ...', $id);
931            
932             This is a wrapper to the HList Method ->info. The default method is info('data', ...).
933             Please read the manual from Tk::HList.
934            
935             =head2 $DBITree->ListEntrys;
936            
937             This returnd a sorted ref array with all entrys in the tree.
938            
939             =head2 $DBITree->select_entrys([en1, en2, en3, ...]);
940            
941             This returns a sorted ref array with all selected entries
942             in the tree or you can set an array of selected entries.
943             Also you can use only the id's, i.e.:
944            
945             $dbitree->select_entrys(qw/1:2 1:3 1:4/);
946            
947             # or ...
948            
949             $dbitree->select_entrys(qw/2 3 4/);
950            
951             These is friendly if you use i.e. a statement 'select id from table where foo == bla'
952             and you have only the id's without the pathinformation. Tk::DBI::Tree know, select only
953             the entries have at last position this id in path.
954            
955             =head2 $DBITree->zoom;
956            
957             Shrink or unshrink tree to display only founded entries.
958            
959             =head2 $DBITree->infozoom;
960            
961             Returnd true if zoom active.
962            
963             =head2 $DBITree->color_all([style]);
964            
965             Set all entries to normal style without parameters.
966             You can put a new Style to all entries.
967            
968             i.e:
969            
970             $DBITree->color_all([-background => 'gray50']);
971            
972            
973            
974             =head2 $DBITree->get_id;
975            
976             select the row under mouseposition and returnd following parameters.
977            
978             =over 4
979            
980             =item path - The path from the entry under mouseposition.
981            
982             =item col - Column name under mouseposition.
983            
984             =item path - Column number under mouseposition.
985            
986             =item value - Cell value under mouseposition.
987            
988             =back
989            
990             =head2 $DBITree->childs($item);
991            
992             Return an array with paths from childs for $item, include $item.
993            
994             =head2 $DBITree->parent_id($path);
995            
996             Return the actually id from the parentree (only the integer id, not the hole path)
997            
998             =head2 $DBITree->see($id);
999            
1000             Jump to id (only database id)
1001            
1002             =head2 $DBITree->remember( $hash );
1003            
1004             This method is very useful, when you want to remember the last tree status
1005             and column widths for the resize button. This returns a ref hash with following
1006             keys, if this call is done without parameters.
1007            
1008             =over 4
1009            
1010             =item widths - a ref array including the width of each column.
1011            
1012             =item stats - a ref hash with status information(open close none) for each entry.
1013            
1014             =back
1015            
1016             You can give an old Hash (may eval-load at program start) and the tree
1017             remembers this status.
1018            
1019             I.E.:
1020            
1021             $tree->rembember( $tree->rembember );
1022            
1023             # or ...
1024            
1025             $tree->remember( {
1026             status => {
1027             '0:1' ='open',
1028             '0:1:2' ='close',
1029             ...
1030             },
1031             widths =[165, 24, 546],
1032             } );
1033            
1034             =head1 CALLBACKS
1035            
1036             =head2 -command => sub{ ... }
1037            
1038             Callback on TreeWidget at browsing.
1039            
1040             =head2 -entry_create_cb => sub{ ... }
1041            
1042             Callback if an entry created. The routine have 2 parameters:
1043            
1044             =over 4
1045            
1046             =item entry - a ref to created entry
1047            
1048             =item data - a ref hash with row information.
1049            
1050             =back
1051            
1052             i.e;
1053            
1054             -entry_create_cb => sub{
1055             my($w, $path, $row) = @_;
1056             if(exists $DOC->{ $row->{id} } and exists $EVENT->{ $row->{id} } ) {
1057             $w->entryconfigure($path, -image => $pics{'icon_document_event'});
1058             }
1059             },
1060            
1061             =head2 -opencmd => sub{ ... }
1062            
1063             Callback on TreeWidget if this entry activate(from User or Automatic) to open.
1064             i.E.
1065             -opencmd => sub{ &display_prj_items( @_ ) },
1066            
1067            
1068             =head2 -closecmd => sub{ ... }
1069            
1070             Callback on TreeWidget if this entry activate(from User or Automatic) to close.
1071            
1072            
1073             =head1 ADVERTISED WIDGETS
1074            
1075             =head2 'tree' => Tree-Widget
1076            
1077             This is a normal Tree widget. I.e.:
1078            
1079             $DBITree->Subwidget('tree')->configure(
1080             -background => 'gray50',
1081             };
1082            
1083             =head2 'HB_' => ResizeButton-Widget
1084            
1085             This is a (Resize)Button widget.
1086            
1087             =head1 CHANGES
1088            
1089             $Log: Tree.pm,v $
1090             Revision 1.11 2003/08/13 12:58:13 xpix
1091             * colored debug output
1092             * new method childs, output an array with complete paths from childs
1093             * new methods neu, move, dele to transfering from entrys
1094             * new option maxchars
1095             ! many, many bug fixes
1096              
1097             Revision 1.8 2003/07/18 16:14:15 xpix
1098             ! Fehler im Table_is_Change algo, fix
1099             ! Komplett refresh bei TreeStatusaenderung
1100             ! to_parent_open macht jetzt wirklich nur die Vaeter und nicht noch den Sohn auf ;-)
1101             ! unit. value in Form.pm
1102              
1103             Revision 1.10 2003/07/17 14:59:54 xpix
1104             ! many little bugfixes
1105              
1106             Revision 1.3 2003/06/24 16:38:44 xpix
1107             * add symbolic to cvs
1108             * new loking mechanism in Form.pm
1109              
1110             Revision 1.9 2003/06/23 16:15:22 xpix
1111             ! cvs error
1112              
1113             Revision 1.8 2003/06/18 15:31:47 xpix
1114             * new methods: copy, move, neu ... dele. This will work only on the Tree Widget (not in table)
1115             * change a little bit the docu
1116              
1117             Revision 1.7 2003/06/16 12:58:01 xpix
1118             ! No Error, if the id ot exists in selct_entrys
1119              
1120             Revision 1.6 2003/05/23 13:47:46 xpix
1121             ! No debug if debug = 0
1122              
1123             Revision 1.5 2003/05/20 13:51:50 xpix
1124             * add field parent_id to data entry
1125              
1126             Revision 1.4 2003/05/11 16:33:47 xpix
1127             * new option -colNames
1128             * new option -entry_create_cb
1129             * new option -higlight
1130             * new option -normal
1131             * new method info
1132             * new method infozoom
1133             * new method color_all
1134             * new method get_id
1135             ! much bugfixes
1136             * better select_entrys (without pathinformation)
1137              
1138             Revision 1.3 2003/05/05 16:02:06 xpix
1139             * correct the documentation and write a little more ;-)
1140            
1141             Revision 1.2 2003/05/04 23:38:25 xpix
1142             ! bug in make_tree_list
1143            
1144             Revision 1.1 2003/05/04 20:52:13 xpix
1145             * New Widget for display a table in a tree
1146            
1147             =head1 AUTHOR
1148            
1149             Copyright (C) 2003 , Frank (xpix) Herrmann. All rights reserved.
1150            
1151             http://www.xpix.de
1152            
1153             This program is free software; you can redistribute it and/or
1154             modify it under the same terms as Perl itself.
1155            
1156             =head1 KEYWORDS
1157            
1158             Tk::DBI::*, Tk::ResizeButton, Tk::Tree, DBIx::Tree
1159            
1160             __END__