package Tk::ListMgr;
use Tk qw(Ev);
use Tk::Cloth;
use Carp;
use strict;
use vars qw(@ISA $VERSION);
@ISA = qw(Tk::Derived Tk::Frame);
$VERSION = "0.02";
Construct Tk::Widget 'ListMgr';
*Tk::Widget::privateData = sub {
my $w = shift;
my $p = shift || caller;
$w->{$p} ||= {};
} unless defined &Tk::Widget::privateData;
sub ClassInit {
my($class,$mw) = @_;
$class->SUPER::ClassInit($mw);
$mw->bind($class,"<1>", ['BeginSelect', Ev('index',Ev('@'))]);
$mw->bind($class,"<Shift-1>", ['BeginExtend',Ev('index',Ev('@'))]);
$mw->bind($class,"<Configure>" , ['LayoutRequest', 1 ]);
$mw->bind($class,"<FocusIn>" , ['swapHighlight' ]);
$mw->bind($class,"<FocusOut>" , ['swapHighlight' ]);
}
sub swapHighlight {
my $self = shift->Subwidget('cloth');
$self->configure(
-highlightbackground => $self->cget('-highlightcolor'),
-highlightcolor => $self->cget('-highlightbackground'),
);
}
sub Populate {
my $lmgr = shift;
$lmgr->configure(
-borderwidth => 0,
-highlightthickness => 0
);
my $cloth = $lmgr->Cloth()->pack(
-fill => 'both',
-expand => 1
);
$lmgr->Advertise(cloth => $cloth);
my $cb = [ 'ForwardEvent', Ev(['parent'])];
foreach my $tag (qw(ButtonRelease ButtonPress KeyPress KeyRelease Motion)) {
$cloth->bind(ref($cloth), "<Any-$tag>" , $cb);
}
$cloth->bindtags([ ref($cloth),$cloth->toplevel, 'all']);
$lmgr->ConfigSpecs(
DEFAULT => [$cloth],
-layout => [METHOD => undef, undef, 'horizontal'],
-takefocus => ["SELF", "takeFocus", "TakeFocus", 1],
-background => [['SELF',$cloth],qw(background Background green)],
-borderwidth => [$cloth, 'borderwidth','Borderwidth',2],
-relief => [$cloth, 'relief','Relief','raised'],
-highlightthickness => [$cloth, 'highlightThickness','HighlightThickness',0],
-selectmode => [PASSIVE => undef, undef, 'single'],
);
my $data = $lmgr->privateData;
%$data = (
items => [],
columns => [],
headerConfigure => [],
subitemConfigure => [],
why => 0,
);
$lmgr;
}
sub col {
my $lmgr = shift;
my $index = shift;
my $data = $lmgr->privateData;
my $cols = $data->{'columns'} ||= [];
return $cols->[$index]
if defined $cols->[$index];
my $col = $lmgr->Tag;
my $l = $col->Component(Line => 'anchor',
-coords => [$index*100,0,$index*100,-20]
);
$l->bind('<B1-Motion>', [
sub {
my($line,$col,$x) = @_;
$x = $line->cloth->canvasx($x);
my $tx = ($col->SubItem('anchor')->coords)[0];
$x = 0 if $x < 0;
$col->move($x - $tx,0);
}, $col, Ev('x') ]
);
$l->bind('<Any-Enter>', [
sub { shift->cloth->configure(-cursor => 'sb_h_double_arrow') }]
);
$l->bind('<Any-Leave>', [
sub { shift->cloth->configure(-cursor => undef) }]
);
$cols->[$index] = $col;
}
sub BeginSelect
{
my $w = shift;
my $el = shift;
if ($w->cget("-selectmode") eq "multiple")
{
if ($w->selectionIncludes($el))
{
$w->selectionClear($el)
}
else
{
$w->selectionSet($el)
}
}
else
{
$w->selectionClear(0,"end");
$w->selectionSet($el);
$w->selectionAnchor($el);
# @Selection = ();
# $Prev = $el
}
}
sub Motion
{
}
sub BeginExtend
{
my $w = shift;
my $el = shift;
if ($w->cget("-selectmode") eq "extended" && $w->selectionIncludes("anchor"))
{
$w->Motion($el)
}
}
sub Button1 {
my $lmgr = shift;
my $cloth = $lmgr->Subwidget('cloth');
my $x = $cloth->canvasx(shift);
my $y = $cloth->canvasy(shift);
return
if $y <= 0 || $x <= 0;
my @i = $cloth->find('overlapping',$x,$y,$x,$y);
my $item = undef;
my $i;
foreach $i (@i) {
next if $i->tag =~ /^seln/;
$item = $i;
last;
}
if($item) {
$item = $item->parent
while $item->parent != $lmgr;
}
$lmgr->selectSet($item)
unless $item && $item->{'selected'};
}
sub ShiftButton1 {
my $lmgr = shift;
my $x = $lmgr->canvasx(shift);
my $y = $lmgr->canvasy(shift);
$lmgr->selectAdd($lmgr->itemAt($x,$y));
}
sub LayoutRequest {
my $lmgr = shift;
my $why = shift;
my $data = $lmgr->privateData;
my $wref = \$data->{why};
$lmgr->DoWhenIdle( [ 'arrange', $lmgr ])
unless $$wref;
$$wref ||= $why;
}
sub arrange {
my $lmgr = shift;
my $layout = $lmgr->layout;
my $data = $lmgr->privateData;
my $why = delete $data->{why};
$lmgr->update
if($why & 1);
my $meth = "arrange_" . $layout;
$lmgr->$meth(0);
}
sub layout {
my $lmgr = shift;
my $data = $lmgr->privateData;
my $o = $lmgr->{Configure}{'-layout'} ||= 'vertical';
if(@_) {
my $new = shift;
croak "Bad value for -layout, shoutl be one of horizontal, vertical, list"
unless $new =~ /^(horizontal|vertical|list)$/;
$lmgr->{Configure}{'layout'} = $new;
$data->{H} = $data->{W} = 1;
$lmgr->LayoutRequest(2);
}
$o;
}
sub subitemConfigure {
my $lmgr = shift;
my $index = shift;
my $data = $lmgr->privateData;
my $fmt = $data->{'subitemConfigure'};
my $item = $fmt->[$index] ||= {
-type => 'Text',
-display => 1,
-imageon => undef,
-imageoff => undef,
-width => undef,
};
return %$item
unless @_;
my %args = @_;
%$item = (%$item, %args);
}
sub headerConfigure {
my $lmgr = shift;
my $index = shift;
my $data = $lmgr->privateData;
my $hdr = $data->{'headerConfigure'};
my $item = $hdr->[$index] ||= {
-type => 'Text',
-text => '',
-image => '',
-columnspan => 1,
-display => 1,
-width => undef,
};
return %$item
unless @_;
my %args = @_;
%$item = (%$item, %args);
}
my $pad = 2;
sub arrange_list {
my $lmgr = shift;
my $start = shift;
my $data = $lmgr->privateData;
my $items = $data->{'items'};
my $cols = $data->{'columns'};
my $H = $data->{H} ||= 0;
my $redo = 0;
my $where = $start;
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @rb = $item->SubItem(0)->bbox;
my $ry = $rb[1] + int(($rb[3] - $rb[1]) / 2);
my $i;
my $subitem;
for($i = 0 ; $subitem = $item->SubItem($i) ; $i++) {
my $column = $cols->[$i] ||= $lmgr->Tag;
my @b = $subitem->bbox;
my $dx = $pad - int(($subitem->coords)[0]);
my $dy = 0;
my $h = $b[3] - $b[1] + $pad;
$redo = $data->{H} = $H = $h
if $h > $H;
my $anchor = $column->SubItem('anchor');
unless(defined $anchor) {
$anchor = $column->Component(
Line => 'anchor',
-coords => [ $i * 100,0, $i*100,-20]
);
if($i) {
$anchor->bind('<B1-Motion>', [
sub {
my($line,$col,$x) = @_;
$x = $line->cloth->canvasx($x);
my $tx = ($col->SubItem('anchor')->coords)[0];
$x = 0 if $x < 0;
$col->move($x - $tx,0);
}, $column, Ev('x') ]
);
$anchor->bind('<Any-Enter>', [
sub { shift->cloth->configure(-cursor => 'sb_h_double_arrow') }]
);
$anchor->bind('<Any-Leave>', [
sub { shift->cloth->configure(-cursor => undef) }]
);
}
}
$dx += int (($anchor->coords)[0]);
if($i) {
my $y = $b[1] + (($b[3] - $b[1]) / 2);
$dy = $ry - $y;
}
$subitem->move(int $dx,int $dy);
$column->addtagWithtag($subitem);
}
}
$start = $redo ? 0 : $where;
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @b = $item->SubItem(0)->bbox;
my $y = $b[1] + int(($b[3] - $b[1]) / 2) - int($H / 2);
my $seln = $item->SubItem('seln') ||
$item->Component(Tag => 'seln');
my $i;
my $subitem;
for($i = 0 ; $subitem = $item->SubItem($i) ; $i++) {
my $column = $cols->[$i];
my $x = int(($column->SubItem('anchor')->coords)[0]);
my $bg = $item->selected
? $lmgr->cget(-selectbackground)
: $lmgr->cget(-background);
my $r = $seln->SubItem($i) ||
$seln->Component(Rectangle => $i,
-coords => [0,0,0,0],
-fill => $bg,
-outline => $bg,
);
$r->coords($x-$pad,$y,10000,$y+$H);
$r->raise($subitem);
$r->lower($subitem);
$column->addtagWithtag($r);
}
$y = ($item->SubItem('seln')->SubItem(0)->coords)[1];
$item->move(0,$start * $H - $y);
}
$lmgr->configure(-scrollregion => [0,-20,300,100]);
}
sub arrange_vertical {
my $lmgr = shift;
my $start = shift;
my $data = $lmgr->privateData;
my $items = $data->{'items'};
my $cols = $data->{'columns'};
my $cloth = $lmgr->Subwidget('cloth');
my $H = $data->{H} ||= 1;
my $W = $data->{W} ||= 1;
my $hlbw = $cloth->cget('-highlightthickness') +
$cloth->cget('-borderwidth');
my $width = $cloth->Width - $hlbw*2 - 2;
my $across = int($width / $W) || 1;
my $redo = 0;
my $where = $start;
my $mW = 1;
my $bg = $lmgr->cget('-background');
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @rb = $item->SubItem(0)->bbox;
my $ry = $rb[3];
my $rx = $rb[0] + int(($rb[2] - $rb[0]) / 2);
my $seln = $item->SubItem('seln') ||
$item->Component(Tag => 'seln');
my $i;
my $subitem;
for($i = 0 ; $subitem = $item->SubItem($i) ; $i++) {
my @b = $subitem->bbox;
my $dx = $rx - ($b[0] + int(($b[2] - $b[0]) / 2));
my $dy = $i ? $ry - $b[1] : 0;
$ry = $b[3] + $dy;
$subitem->move(int $dx,int $dy);
my $r = $seln->SubItem($i) ||
$seln->Component(Rectangle => $i,
-coords => [0,0,0,0],
-fill => $bg,
-outline => $bg,
);
$r->coords($subitem->bbox);
$r->raise($subitem);
$r->lower($subitem);
$r->delete unless $subitem->Tk_type eq 'text';
}
my @b = $item->bbox;
my $h = $b[3] - $b[1];
my $w = $b[2] - $b[0];
$redo = $H = $data->{H} = $h
if $h > $H;
$mW = $w
if($w > $mW);
}
$across = int($width / $mW) || 1;
$mW = int($width / $across)
if $across < @$items;
$redo = $W = $data->{W} = $mW
if($mW != $W);
$start = $redo ? 0 : $where;
my $hW = int($W/2);
my $hH = int($H/2);
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @b = $item->bbox;
my $cx = $b[0] + int(($b[2] - $b[0]) / 2);
my $cy = $b[1] + int(($b[3] - $b[1]) / 2);
my $x = ($start % $across) * $W + $hW;
my $y = int($start / $across) * $H + $hH;
$item->move($x - $cx,$y - $cy);
}
$cloth->configure(-scrollregion => [0,0,300,100]);
}
sub arrange_horizontal {
my $lmgr = shift;
my $start = shift;
my $data = $lmgr->privateData;
my $items = $data->{'items'};
my $cols = $data->{'columns'};
my $cloth = $lmgr->Subwidget('cloth');
my $H = $data->{H} ||= 1;
my $W = $data->{W} ||= 1;
my $hlbw = $cloth->cget('-highlightthickness') +
$cloth->cget('-borderwidth');
my $width = $cloth->Width - $hlbw*2 - 2;
my $across = int($width / $W) || 1;
my $redo = 0;
my $where = $start;
my $mW = 1;
my $bg = $lmgr->cget('-background');
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @rb = $item->SubItem(0)->bbox;
my $rx = $rb[2];
my $ry = $rb[1] + int(($rb[3] - $rb[1]) / 2);
my $seln = $item->SubItem('seln') ||
$item->Component(Tag => 'seln');
my $i;
my $subitem;
for($i = 0 ; $subitem = $item->SubItem($i) ; $i++) {
my @b = $subitem->bbox;
my $dy = $ry - ($b[1] + int(($b[3] - $b[1]) / 2));
my $dx = $i ? $rx - $b[0] : 0;
$rx = $b[2] + $dx;
$subitem->move(int $dx,int $dy);
my $r = $seln->SubItem($i) ||
$seln->Component(Rectangle => $i,
-coords => [0,0,0,0],
-fill => $bg,
-outline => $bg,
);
$r->coords($subitem->bbox);
$r->raise($subitem);
$r->lower($subitem);
$r->delete unless $subitem->Tk_type eq 'text';
}
my @b = $item->bbox;
my $h = $b[3] - $b[1];
my $w = $b[2] - $b[0];
$redo = $H = $data->{H} = $h
if $h > $H;
$mW = $w
if($w > $mW);
}
$across = int($width / $mW) || 1;
$mW = int($width / $across)
if $across < @$items;
$redo = $W = $data->{W} = $mW
if($mW != $W);
$start = $redo ? 0 : $where;
my $hW = int($W/2);
my $hH = int($H/2);
for( ; $start < @$items ; $start++) {
my $item = $items->[$start];
my @b = $item->bbox;
my $cx = $b[0]; # + int(($b[2] - $b[0]) / 2);
my $cy = $b[1] + int(($b[3] - $b[1]) / 2);
my $x = ($start % $across) * $W; # + $hW;
my $y = int($start / $across) * $H + $hH;
$item->move($x - $cx,$y - $cy);
}
$cloth->configure(-scrollregion => [0,0,300,100]);
}
sub itemAt {
my $lmgr = shift;
my $cloth = $lmgr->Subwidget('cloth');
my($x,$y) = @_;
my $item = ($cloth->find('overlapping',$x,$y,$x,$y))[0] or
return undef;
$item = $item->parent
while($item->parent != $cloth);
$item;
}
sub selectClear {
my $cloth = shift;
$cloth->selectSet(undef);
}
sub selectSet {
my $lmgr = shift;
my $item = shift;
my $i;
foreach $i (@{$lmgr->privateData->{'items'}}) {
defined $item && $i == $item
? $i->selectSet
: $i->selectClear;
}
}
sub selectAdd {
my $lmgr = shift;
my $item = shift;
$item->selectSet
if $item;
}
sub activate {
}
sub bbox {
}
sub curselection {
}
sub delete {
my($lmgr,$start,$end) = @_;
}
sub get {
}
sub index {
my $lmgr = shift;
my $where = shift;
my $idx = undef;
my $data = $lmgr->privateData;
my $items = $data->{'items'};
if($where =~ /^\d+$/o) {
return $where < @$items ? $where : undef;
}
elsif($where =~ /^@(\d+),(\d+)/o) {
my $item = $lmgr->itemAt($1,$2);
my $idx = 0;
my $i;
foreach $i (@$items) {
last if $i == $item;
$idx++;
}
return $idx < @$items ? $idx : undef;
}
elsif($where eq 'end') {
my $n = @{$data->{'items'}} - 1;
return $n >= 0 ? $n : undef;
}
elsif($where eq 'active') {
return undef;
}
elsif($where eq 'anchor') {
my $a = $data->{selectionAnchor};
return defined $a ? $a : undef;
}
return undef;
}
sub insert {
my $lmgr = shift;
my $where = shift;
my $data = $lmgr->privateData;
my $fmt = $data->{'subitemConfigure'};
my $bg = $lmgr->cget('-background');
my $cloth = $lmgr->Subwidget('cloth');
my @items = ();
foreach my $item (@_) {
my $tag = $cloth->ListItem;
push(@items, $tag);
my $seln = $tag->Component(Tag => 'seln');
for(my $idx = 0 ; $idx < @$fmt ; $idx++) {
my $ifmt = $fmt->[$idx];
if(defined $item->[$idx] && defined $fmt->[$idx]) {
my $type = $ifmt->{-type};
if($type eq 'Text') {
$tag->Component(Text => $idx,
-coords => [-100,-100],
-text => $item->[$idx],
-justify => 'left',
-anchor => 'nw'
);
}
elsif($type eq 'Image') {
$tag->Component(Image => $idx,
-coords => [-100,-100],
-image => $item->[$idx],
-anchor => 'nw'
);
}
}
else {
$tag->Component(Rectangle => $idx,
-coords => [0,0,0,0],
-fill => undef,
-outline => undef
);
}
$seln->Component(Rectangle => $idx,
-coords => [0,0,0,0],
-fill => $bg,
-outline => $bg
);
}
}
if(@items) {
my $items = $data->{'items'};
$where = $lmgr->index($where) || 0;
splice(@{$items},$where,0,@items);
$lmgr->LayoutRequest(4);
}
}
sub nearest {
}
sub scan {
my $lmgr = shift;
my $opt = lc shift;
my $meth = "scan\u$opt";
$lmgr->$meth(@_);
}
sub scanMark {
}
sub scanDragto {
}
sub see {
}
sub selection {
my $lmgr = shift;
my $opt = lc shift;
my $meth = "selection\u$opt";
$lmgr->$meth(@_);
}
sub selectionAnchor {
my $lmgr = shift;
my $data = $lmgr->privateData;
$data->{selectionAnchor} = shift;
}
sub selectionClear {
my($lmgr,$start,$end) = @_;
my $items = $lmgr->privateData->{'items'};
return unless defined $start;
$start = $lmgr->index($start);
$end = defined $end ? $lmgr->index($end) : $start;
for( ; $start <= $end ; $start++) {
$items->[$start]->selectClear
if(defined($items->[$start]));
}
}
sub selectionIncludes {
my $lmgr = shift;
my $elem = shift;
my $items = $lmgr->privateData->{'items'};
defined($elem) &&
defined($items->[$elem]) &&
$items->[$elem]->selected;
}
sub selectionSet {
my($lmgr,$start,$end) = @_;
my $items = $lmgr->privateData->{'items'};
return unless defined $start;
$start = $lmgr->index($start);
$end = defined $end ? $lmgr->index($end) : $start;
for( ; $start <= $end ; $start++) {
$items->[$start]->selectSet
if(defined($items->[$start]));
}
}
sub size {
}
sub xview {
}
sub yview {
}
package Tk::ListMgr::Item;
use vars qw(@ISA);
@ISA = qw(Tk::Cloth::Tag);
Construct Tk::Cloth 'ListItem';
sub selected { shift->{'selected'} }
sub selectToggle {
my $item = shift;
$item->{'selected'}
? $item->selectClear
: $item->selectSet;
}
sub selectSet {
my $item = shift;
my $c = $item->cloth;
my $bg = $c->cget('-selectbackground');
$item->{'selected'} = 1;
$item->SubItem('seln')->configure(-fill => $bg,-outline => $bg);
}
sub selectClear {
my $item = shift;
my $c = $item->cloth;
my $bg = $c->cget('-background');
$item->{'selected'} = 0;
$item->SubItem('seln')->configure(-fill => $bg,-outline => $bg);
}
1;