package Weaving::Tablet::Tk; use warnings; use strict; use Carp; use Weaving::Tablet; require Tk::HList; require Tk::FileSelect; require Tk::FontDialog; require Tk::ColorEditor; use Data::Dumper; our @ISA = qw(Weaving::Tablet); our $VERSION; use version; $VERSION = qv('0.8.4'); # Preloaded methods go here. my %_windowlist = (); sub new_pattern { my $this = shift; my %args = @_; my $pattern; if ( exists( $args{file} ) ) { $this->window->Busy if ref($this); $pattern = $this->new_from_file( $args{file} ); $this->window->Unbusy if ref($this); } else { $this->window->Busy if ref($this); $pattern = $this->new_from_scratch( $args{cards}, $args{rows} ); $this->window->Unbusy if ref($this); } return unless defined $pattern; delete $args{mw} unless defined $args{mw}; my $win = new MainWindow; $_windowlist{MW} = $win unless exists $args{mw}; $win->iconname('snartemo'); $win->title("Untitled"); $pattern->{window} = $win; $pattern->file_name; $_windowlist{$win} = $win; $pattern->create_menu; $pattern->set_constants; $pattern->{canvas} = $win->Scrolled( 'Canvas', -background => $pattern->background_color, -height => 250, -width => 200, )->pack(qw/-expand yes -fill both/); $pattern->draw_pattern; return $pattern; } sub initialize { my $self = shift; foreach my $k ( grep /^_/, keys %$self ) { $self->{$k} = undef; } $self->SUPER::initialize(@_); } sub close_pattern { my $self = shift; if ( $self->dirty ) { my $button = $self->window->Dialog( -text => "Save pattern?", -buttons => [ 'Save', "Don't Save", 'Cancel' ], -default_button => 'Save', )->Show; return if $button eq 'Cancel'; if ( $button eq 'Save' ) { defined( $self->file_name ) ? $self->save_pattern : $self->save_pattern_as; } } delete $_windowlist{ $self->window }; $self->window->destroy; } sub create_menu { my $pattern = shift; my $w = $pattern->window; my $menuitems = [ [ Cascade => '~File', -menuitems => [ [ Button => '~New...', -command => sub { $pattern->make_new_from_scratch; } ], [ Button => '~Open...', -command => sub { $pattern->make_new_from_file; } ], [ Button => 'Close', -command => sub { $pattern->close_pattern; }, ], [ Separator => '' ], [ Button => '~Save', -command => sub { defined( $pattern->file_name ) ? $pattern->save_pattern : $pattern->save_pattern_as; } ], [ Button => 'Save as...', -command => sub { $pattern->save_pattern_as; } ], [ Separator => '' ], [ Button => 'Revert', -command => sub { $pattern->revert_pattern; } ], [ Button => 'Quit', -command => sub { exit; } ], ] ], [ Cascade => '~Edit', -menuitems => [ [ Checkbutton => 'Smooth', -variable => \$pattern->{_smooth}, -command => sub { $pattern->canvas->itemconfigure( 'turn', -smooth => $pattern->smooth ); } ], [ Checkbutton => 'Grid', -variable => \$pattern->{_draw_grid}, -command => sub { $pattern->canvas->itemconfigure( 'grid', -outline => $pattern->draw_grid ? 'black' : undef ); } ], [ Checkbutton => 'Overlap', -variable => \$pattern->{_overlap}, -command => sub { $pattern->update_pattern; } ], [ Separator => '' ], [ Button => 'Background Color', -command => sub { $pattern->bg_color; } ], [ Button => 'white background', -command => sub { $pattern->bg_color_white; } ], [ Button => 'grey background', -command => sub { $pattern->bg_color_gray; } ], [ Separator => '' ], [ Button => 'Set float length...', -command => sub { $pattern->set_float_length; } ], [ Checkbutton => 'Show Floats', -variable => \$pattern->{_float_pattern}, -command => sub { $pattern->float_pattern; $pattern->update_pattern; } ], [ Separator => '' ], [ Button => 'Dump Pattern', -command => sub { $pattern->dump; } ], ] ], [ Cascade => '~Pattern', -menuitems => [ [ Button => 'Insert row...', -command => sub { $pattern->insert_row_cmd; } ], [ Button => 'Delete row...', -command => sub { $pattern->delete_row_cmd; } ], [ Button => 'Copy row...', -command => sub { $pattern->duplicate_row_cmd; } ], [ Separator => '' ], [ Button => 'Insert card...', -command => sub { $pattern->insert_card_cmd; } ], [ Button => 'Delete card...', -command => sub { $pattern->delete_card_cmd; } ], [ Button => 'Copy card...', -command => sub { $pattern->duplicate_card_cmd; } ], [ Separator => '' ], [ Button => 'Edit colors', -command => sub { $pattern->edit_palette; } ], [ Button => 'Edit threading', -command => sub { $pattern->edit_threading; } ], [ Button => 'Show Twist', -command => sub { $pattern->show_twist; } ], ] ] ]; if ( $Tk::VERSION >= 800 ) { my $menubar = $w->Menu( -menuitems => $menuitems ); $w->configure( -menu => $menubar ); } else { $w->Menubutton( -text => 'Pseudo menubar', -menuitems => $menuitems, )->pack; } } sub file_name { my $self = shift; # pass this up the line my $fn = $self->SUPER::file_name(@_); $fn ||= "Untitled"; $self->window->title($fn) if defined $self->window; $fn; } sub dump { my $self = shift; print $self->dump_pattern; foreach my $k ( sort keys %$self ) { next unless ref( $self->{$k} ); print "self->$k is $self->{$k}\n"; } } sub set_constants { my $self = shift; $self->{_small_font} = '-misc-fixed-medium-r-normal--10-100-75-75-c-60-iso8859-1'; $self->{_row_spacing} = 12; $self->{_col_spacing} = 9; $self->{_half_col_size} = 4; $self->{_current_row} = 0; $self->{_current_card} = 0; $self->{_smooth} = 0; $self->{_draw_grid} = 0; $self->{_background_color} = 'white'; $self->{_overlap} = 0; $self->{_float_pattern} = 0; $self->{_float_length} = 3; } sub show_twist { my $self = shift; my $twist = $self->print_twist; print $twist; } sub background_color { shift->{_background_color}; } sub overlap { my $self = shift; @_ and $self->{_overlap} = shift; $self->{_overlap}; } sub bg_color { my $self = shift; my $bg_color = $self->color_dlog->Show( -title => 'Background Color', -initialcolor => $self->background_color ); return unless defined $bg_color; $self->{_background_color} = $bg_color; $self->canvas->configure( -background => $self->background_color ); } sub bg_color_white { my $self = shift; $self->{_background_color} = 'white'; $self->canvas->configure( -background => $self->background_color ); } sub bg_color_gray { my $self = shift; $self->{_background_color} = 'gray'; $self->canvas->configure( -background => $self->background_color ); } sub set_float_length { my $self = shift; my $float_length = $self->float_length_dlog( $self->{_float_length} ); return unless defined $float_length; $self->{_float_length} = $float_length; $self->update_pattern if $self->{_float_pattern}; } sub window { return shift->{window}; } sub canvas { return shift->{canvas}; } sub current_row { my $self = shift; @_ and $self->{_current_row} = shift; $self->{_current_row}; } sub current_card { my $self = shift; @_ and $self->{_current_card} = shift; $self->{_current_card}; } sub small_font { return shift->{_small_font}; } sub row_spacing { return shift->{_row_spacing}; } sub col_spacing { return shift->{_col_spacing}; } sub half_col_size { return shift->{_half_col_size}; } sub smooth { my $self = shift; @_ and $self->{_smooth} = shift; $self->{_smooth}; } sub draw_grid { my $self = shift; @_ and $self->{_draw_grid} = shift; $self->{_draw_grid}; } sub set_current_row { my $self = shift; my $canvas = $self->canvas; my $r = shift; do { foreach my $t ( $canvas->gettags('current') ) { $r = unpack( 'x3a*', $t ) if $t =~ /^row\d+$/; } } unless defined $r; return unless defined $r; $r %= $self->number_of_rows; $canvas->move( 'currentrow', 0, ( $self->current_row - $r ) * $self->row_spacing ); $self->current_row($r); } sub set_current_card { my $self = shift; my $canvas = $self->canvas; my $c = shift; do { foreach my $t ( $canvas->gettags('current') ) { $c = unpack( 'x4a*', $t ) if $t =~ /^card\d+$/; } } unless defined $c; return unless defined $c; $c %= $self->number_of_cards; $canvas->move( 'currentcard', ( $c - $self->current_card ) * $self->col_spacing, 0 ); $self->current_card($c); } sub draw_pattern { shift->_draw_pattern( @_, 'draw' ); } sub update_pattern { my $pattern = shift; my @cardlist = @_; @cardlist or @cardlist = ( 0 .. $pattern->number_of_cards - 1 ); $pattern->float_pattern(@cardlist) if $pattern->{_float_pattern}; $pattern->_draw_pattern( 'update', @cardlist ); } sub _draw_pattern { my $pattern = shift; my $canvas = $pattern->canvas; my $action = shift; my @cardlist = @_; @cardlist or @cardlist = ( 0 .. $pattern->number_of_cards - 1 ); $pattern->twist_pattern(@cardlist); $pattern->color_pattern(@cardlist); my ( $c8, $r8, $offset, $rs ); do { $canvas->delete('rownumber'); foreach my $row ( 0 .. $pattern->number_of_rows - 1 ) { $r8 = ( -$row ) * $pattern->row_spacing - 8; $canvas->create( 'text', 38, $r8, -text => $row + 1, -font => $pattern->small_font, -anchor => 'e', -tags => [ 'rownumber', "row$row", "rownumber$row" ], ); } } if $action eq 'draw'; $rs = $pattern->row_spacing; my $poly_ht = $pattern->overlap ? $rs : $rs / 2; my $half_col_size = $pattern->half_col_size; foreach my $card (@cardlist) { my $startrow = 0; ( $card, $startrow ) = @$card if ref($card); $pattern->window->update; $c8 = $card * $pattern->col_spacing + 8 + 48; foreach my $row ( $startrow .. $pattern->number_of_rows - 1 ) { $r8 = ( -$row ) * $rs - 8; $offset = ( index( '\|/', $pattern->turns( $card, $row ) ) - 1 ) * $half_col_size; $offset *= $pattern->SZ($card) eq 'S' ? 1 : -1; $canvas->delete( "turn$card" . "x$row" ); $canvas->create( 'polygon', _poly_coords( $pattern, $r8, $c8, $offset, $poly_ht ), -fill => $pattern->color_table( $pattern->threading( $card, $pattern->color( $card, $row ) ) ), -tags => [ 'turn', "row$row", "card$card", "turn$card" . "x$row" ], ); $canvas->delete( "grid$card" . "x$row" ); $canvas->create( 'rectangle', $c8 - $half_col_size - 1, $r8 + $rs / 2, $c8 + $half_col_size, $r8 - $rs / 2, -fill => undef, -outline => $pattern->draw_grid ? 'black' : undef, -tags => [ 'grid', "row$row", "card$card", "grid$card" . "x$row" ], ); } $canvas->delete("float$card"); if ( $pattern->{_float_pattern} ) { foreach my $float ( @{ $pattern->{floats}[$card] } ) { # add check against float_length here... next unless ( $float->[1] - $float->[0] ) + 1 >= $pattern->{_float_length}; $canvas->create( 'rectangle', $c8 - $half_col_size - 1, ( -$float->[0] ) * $rs - 8 + $rs / 2, $c8 + $half_col_size, ( -$float->[1] ) * $rs - 8 - $rs / 2, -fill => undef, -width => 2, -outline => 'black', -tags => [ 'float', "card$card", "float$card" ], ); } } if ( $action eq 'draw' ) { $canvas->delete("SZ$card"); $canvas->create( 'text', $c8, 24, -anchor => 'center', -text => $pattern->SZ($card), -tags => [ 'SZ', "card$card", "SZ$card" ] ); $canvas->delete("starthole$card"); $canvas->create( 'text', $c8, 36, -anchor => 'center', -text => chr( ord('A') + $pattern->start($card) ), -tags => [ 'starthole', 'start', "card$card", "starthole$card" ], ); foreach my $hole ( 0 .. 3 ) { $canvas->delete( "start$card" . "hole$hole" ); $canvas->create( 'rectangle', $c8 - 3, 42 + 12 * $hole, $c8 + 3, 52 + 12 * $hole, -fill => $pattern->color_table( $pattern->threading( $card, $hole ) ), -tags => [ 'start', "card$card", "start$card" . "hole$hole" ], ); } } elsif ( $action eq 'update' ) { $canvas->itemconfigure( "SZ$card", -text => $pattern->SZ($card) ); $canvas->itemconfigure( "starthole$card", -text => chr( ord('A') + $pattern->start($card) ) ); foreach my $hole ( 0 .. 3 ) { $canvas->itemconfigure( "start$card" . "hole$hole", -fill => $pattern->color_table( $pattern->threading( $card, $hole ) ) ); } } } if ( $action eq 'draw' ) { $canvas->delete('holelabel'); foreach my $hole ( 0 .. 3 ) { $canvas->create( 'text', 46, 47 + 12 * $hole, -text => chr( ord('A') + $hole ), -tags => [ 'holelabel', "holelabel$hole" ], ); } } do { $canvas->delete('currentrow'); $canvas->delete('currentcard'); $canvas->create( 'rectangle', 40, -$pattern->current_row * $pattern->row_spacing - 10, 50, -$pattern->current_row * $pattern->row_spacing - 6, -fill => 'black', -tags => ['currentrow'], ); $canvas->create( 'rectangle', $pattern->current_card * $pattern->col_spacing + 8 + 48 - 2, 4, $pattern->current_card * $pattern->col_spacing + 8 + 48 + 2, 16, -fill => 'black', -tags => ['currentcard'], ); $canvas->bind( 'turn', '<1>', sub { $pattern->flip_turn; } ); $canvas->bind( 'grid', '<1>', sub { $pattern->flip_turn; } ); $canvas->bind( 'turn', '', sub { $pattern->null_turn; } ); $canvas->bind( 'grid', '', sub { $pattern->null_turn; } ); $canvas->bind( 'SZ', '<1>', sub { $pattern->flip_card; } ); $canvas->bind( 'start', '<1>', sub { $pattern->rotate_start(1); } ); $canvas->bind( 'start', '', sub { $pattern->rotate_start(-1); } ); $canvas->bind( 'start', '<2>', sub { $pattern->edit_threading; } ); $canvas->bind( 'start', '<3>', sub { $pattern->set_current_card; } ); $canvas->bind( 'rownumber', '<1>', sub { $pattern->set_current_row; } ); $canvas->CanvasBind( '', sub { $pattern->delete_cmd; } ); $canvas->CanvasBind( '', sub { $pattern->insert_cmd; } ); } if $action eq 'draw'; $canvas->configure( -scrollregion => [ $canvas->bbox("all") ] ); } sub _poly_coords { my $pattern = shift; my ( $r8, $c8, $offset, $poly_ht ) = @_; if ( $offset != 0 ) { return $pattern->overlap ? ( $c8 - $offset, $r8 + $poly_ht - 1, $c8 - $offset, $r8, $c8 + $offset, $r8 - $poly_ht + 1, $c8 + $offset, $r8, $c8 - $offset, $r8 + $poly_ht - 1, -smooth => $pattern->smooth, ) : ( $c8 - $offset, $r8 + $poly_ht - 1, $c8 - $offset, $r8 + $poly_ht / 2, $c8 + $offset / 2, $r8 - $poly_ht + 1, $c8 + $offset, $r8 - $poly_ht + 1, $c8 + $offset, $r8 - $poly_ht / 2, $c8 - $offset / 2, $r8 + $poly_ht - 1, $c8 - $offset, $r8 + $poly_ht - 1, -smooth => $pattern->smooth, ); } else { my $offset = $pattern->half_col_size / 2; return ( $c8 - $offset, $r8 + $poly_ht - 1, $c8 - $offset, $r8 - $poly_ht + 1, $c8 + $offset, $r8 - $poly_ht + 1, $c8 + $offset, $r8 + $poly_ht + 1, $c8 - $offset, $r8 + $poly_ht - 1, -smooth => $pattern->smooth, ); } } sub delete_cmd { my $self = shift; my $action = $self->delete_dlog->Show; $action eq 'Row' and $self->delete_row_cmd; $action eq 'Card' and $self->delete_card_cmd; } sub insert_cmd { my $self = shift; my $action = $self->insert_dlog->Show; $action eq 'Row' and $self->insert_row_cmd; $action eq 'Card' and $self->insert_card_cmd; } sub make_new_from_scratch { my $self = shift; my $d = $self->window->DialogBox( -title => "New pattern...", -buttons => [qw/OK Cancel/], -default_button => 'OK' ); my ( $cards, $rows ) = ( 10, 10 ); my $msg = ''; $d->add( 'LabEntry', -label => 'Number of Cards', -labelPack => [qw/-side left -anchor w/], -textvariable => \$cards, )->pack(qw/-side top -anchor w/); $d->add( 'LabEntry', -label => 'Number of turns', -labelPack => [qw/-side left -anchor w/], -textvariable => \$rows, )->pack(qw/-side top -anchor w/); $d->add( 'Label', -textvariable => \$msg, -width => 50, -height => 2, -justify => 'left', )->pack(qw/-side left -anchor w/); LOOP: { return if $d->Show eq 'Cancel'; $msg = ''; $msg = 'Number of Cards must be > 0' unless $cards > 0; $msg = 'Number of turns must be > 0' unless $rows > 0; redo LOOP unless $msg eq ''; } return if $self->new_pattern( mw => $self->window, cards => $cards, rows => $rows ); $self->window->Dialog( -text => 'Could not create new pattern' )->Show; return; } sub revert_pattern { my $self = shift; return unless defined $self->file_name; return unless -r $self->file_name; return if $self->window->Dialog( -text => "Revert to saved version (discard changes)?", -buttons => [qw/OK Cancel/], -default_button => 'OK' ) eq 'Cancel'; $self->load_pattern and $self->update_pattern; } sub make_new_from_file { my $self = shift; my $fs = $self->fs_dlog( { -filelabel => 'Load pattern from...', -create => 0 } ); my $file = $fs->Show; return if $file eq ''; return if $self->new_pattern( mw => $self->window, file => $file ); $self->window->Dialog( -text => "Could not load file $file" )->Show; return; } sub save_pattern_as { my $self = shift; my $fs = $self->fs_dlog( { -filelabel => 'Save pattern as...', -create => 1 } ); my $file = $fs->Show; return if $file eq ''; $self->file_name($file); return if $self->save_pattern; $self->window->Dialog( -text => 'Could not save file as $file' )->Show; return; } sub insert_row_cmd { my $self = shift; my $dlog = $self->insert_row_dlog; $dlog->{where_row} = $self->current_row + 1; $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub delete_row_cmd { my $self = shift; my $dlog = $self->delete_row_dlog; $dlog->{where_row} = $self->current_row + 1; $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub duplicate_row_cmd { my $self = shift; my $dlog = $self->duplicate_row_dlog; $dlog->{from_row} = $self->current_row + 1; $dlog->{after_row} = $self->current_row + 1; $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub insert_card_cmd { my $self = shift; my $dlog = $self->insert_card_dlog; $dlog->{where_card} = $self->current_card + 1; $self->refresh_color_list( $dlog->{palette} ); $self->refresh_card_threading( $dlog->{card_canvas} ); $dlog->{threading} = [ $self->threading( $self->current_card ) ]; $dlog->{turns}->delete( '1.0', 'end' ); $dlog->{turns}->insert( '1.0', join( "", reverse $self->card_turns( $self->current_card ) ) ); $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub delete_card_cmd { my $self = shift; my $dlog = $self->delete_card_dlog; $dlog->{where_card} = $self->current_card + 1; $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub duplicate_card_cmd { my $self = shift; my $dlog = $self->duplicate_card_dlog; $dlog->{from_card} = $self->current_card + 1; $dlog->{after_card} = $self->current_card + 1; $dlog->{dlog}->deiconify; $dlog->{dlog}->raise; } sub edit_palette { my $pattern = shift; my $canvas = $pattern->canvas; my $palette = $pattern->palette_dlog; $pattern->refresh_color_list( $palette->{h} ); $palette->{dlog}->deiconify; $palette->{dlog}->raise; } sub get_new_color { my $pattern = shift; my $h = shift; my $title = "Select new color to add to palette"; my $color = $pattern->color_dlog->Show( -title => $title, -parent => $h ); return unless defined $color; $pattern->color_table( undef, $color ); my $c = @{ $pattern->{color_table} } - 1; $h->add( $c, -text => $c ); $h->itemCreate( $c, 1, -text => $color ); } sub refresh_color_list { my $self = shift; my $h = shift; $h->delete('all'); foreach my $c ( 0 .. $self->color_table - 1 ) { $h->add( $c, -text => $c ); $h->itemCreate( $c, 1, -text => $self->color_table($c) ); } } sub refresh_card_threading { my $pattern = shift; my $card_canvas = shift; my $threading = shift || [ $pattern->threading( $pattern->current_card ) ]; foreach my $h ( 0 .. 3 ) { $card_canvas->itemconfigure( "hole$h", -fill => $pattern->color_table( $$threading[$h] ), ); } } sub edit_threading { my $pattern = shift; my $canvas = $pattern->canvas; $pattern->set_current_card; $pattern->current_card(0) unless $pattern->current_card; my $list = $pattern->threading_dlog->{h}; my $t = $pattern->threading_dlog->{dlog}; my $card_canvas = $pattern->threading_dlog->{card_canvas}; $pattern->threading_dlog->{start} = $pattern->start( $pattern->current_card ); $pattern->refresh_color_list($list); $pattern->refresh_card_threading($card_canvas); $t->deiconify; $t->raise; } sub flip_turn { my $pattern = shift; my $canvas = $pattern->canvas; my ( $r, $c ); foreach my $t ( $canvas->gettags('current') ) { $r = unpack( 'x3a*', $t ) if $t =~ /^row\d+$/; $c = unpack( 'x4a*', $t ) if $t =~ /^card\d+$/; } return unless defined $r; return unless defined $c; $pattern->turns( $c, $r, $pattern->turns( $c, $r ) eq '/' ? '\\' : '/' ); $pattern->set_current_row($r); $pattern->set_current_card($c); $pattern->update_pattern( [ $c, $r ] ); } sub null_turn { my $pattern = shift; my $canvas = $pattern->canvas; my ( $r, $c ); foreach my $t ( $canvas->gettags('current') ) { $r = unpack( 'x3a*', $t ) if $t =~ /^row\d+$/; $c = unpack( 'x4a*', $t ) if $t =~ /^card\d+$/; } return unless defined $r; return unless defined $c; $pattern->turns( $c, $r, '|' ); $pattern->set_current_row($r); $pattern->set_current_card($c); $pattern->update_pattern( [ $c, $r ] ); } sub flip_card { my $pattern = shift; my $canvas = $pattern->canvas; my $c; foreach my $t ( $canvas->gettags('current') ) { $c = unpack( 'x4a*', $t ) if $t =~ /^card\d+$/; } return unless defined $c; $pattern->SZ( $c, $pattern->SZ($c) eq 'S' ? 'Z' : 'S' ); $pattern->set_current_card($c); $pattern->update_pattern($c); } sub rotate_start { my $pattern = shift; my $dir = shift || 1; my $canvas = $pattern->canvas; my $c; foreach my $t ( $canvas->gettags('current') ) { $c = unpack( 'x4a*', $t ) if $t =~ /^card\d+$/; } return unless defined $c; $pattern->start( $c, ( $pattern->start($c) + $dir ) % 4 ); $pattern->set_current_card($c); $pattern->update_pattern($c); } sub palette_dlog { my $self = shift; unless ( defined $self->{_palette_dlog} ) { my $palette = $self->window->Toplevel; $palette->title('Pattern palette editor'); $palette->Button( -text => 'Close', -command => [ withdraw => $palette ], )->pack(qw/-side bottom/); my $h = $self->_palette( $palette->Frame->pack(qw/-side top -fill both -expand y/) ); $self->{_palette_dlog} = { dlog => $palette, h => $h }; } $self->{_palette_dlog}; } sub _palette # (frame, card_canvas, \current_hole, \threading, update) # frame only required { my $self = shift; my $palette = shift; my $card_canvas = shift; my $current_hole = shift; my $threading = shift; my $update = shift || 0; my $h = $palette->Scrolled( 'HList', -width => 40, -height => 10, -header => 1, -drawbranch => 0, -columns => 2, -scrollbars => 'osoe', )->pack(qw/-side top -fill both -expand y/); $palette->Button( -text => 'Add from picker', -command => sub { my $title = "Select new color to add to palette"; my $color = $self->color_dlog->Show( -title => $title ); return unless defined $color; $self->color_table( undef, $color ); $self->refresh_color_list($h); }, )->pack(qw/-side top/); $palette->Button( -text => 'Add name', -command => sub { my $d = $palette->DialogBox( -title => 'Add Color by Name', -buttons => [qw/OK Cancel/], -default_button => 'OK', ); my $cname; $d->add( 'LabEntry', -label => 'Color', -labelPack => [qw/-side left/], -width => 30, -textvariable => \$cname, )->pack; return if $d->Show eq 'Cancel'; return if $cname eq ''; $self->color_table( undef, $cname ); $self->refresh_color_list($h); }, )->pack(qw/-side top/); $h->header( create => 0, -text => 'Number' ); $h->header( create => 1, -text => 'Color' ); $h->columnWidth( 0, -char => 7 ); $h->columnWidth( 1, -char => 33 ); $h->configure( -command => sub { my $s = shift; my $title = "Old color $s (" . $self->color_table($s) . ")"; my $color = $self->color_dlog->Show( -title => $title, -initialcolor => $self->color_table($s), ); return unless defined $color; $self->color_table( $s, $color ); $h->itemConfigure( $s, 1, -text => $color ); $self->update_pattern; $self->refresh_card_threading($card_canvas) if $update; }, ); $h->configure( -browsecmd => sub { $self->threading( $self->current_card, $$current_hole, shift ); $card_canvas->itemconfigure( "hole$$current_hole", -fill => $self->color_table( $self->threading( $self->current_card, $$current_hole ) ) ); $card_canvas->update; $self->color_pattern( $self->current_card ); $self->update_pattern( $self->current_card ); }, ) if $update eq 'old'; $h->configure( -browsecmd => sub { $$threading[$$current_hole] = shift; $card_canvas->itemconfigure( "hole$$current_hole", -fill => $self->color_table( $$threading[$$current_hole] ) ); }, ) if $update eq 'new'; $h->bind( "<3>", sub { my $w = shift; my $d = $h->DialogBox( -title => "Edit Color Name", -buttons => [qw/OK Cancel/], -default_button => 'OK', ); my $old; my $new; $d->add( 'LabEntry', -textvariable => \$old, -label => 'Old name', -labelPack => [qw/-side left -anchor w/], -state => 'disabled', -width => 30, )->pack(qw/-side top -anchor w/); $d->add( 'LabEntry', -textvariable => \$new, -label => 'New name', -labelPack => [qw/-side left -anchor w/], -width => 30, )->pack(qw/-side top -anchor w/); my $y = $w->XEvent->y; my $s = $h->nearest($y); $old = $new = $self->color_table($s); return if $d->Show eq 'Cancel'; print "change $old ($s at $y) to $new\n"; return if $old eq $new; return if $new eq ''; $self->color_table( $s, $new ); $h->itemConfigure( $s, 1, -text => $new ); $self->update_pattern; $self->refresh_card_threading($card_canvas) if $update; } ); $h; } sub threading_dlog { my $self = shift; unless ( defined $self->{_threading_dlog} ) { my $t_info = { current_hole => 0, start => 0 }; my $t = $self->window->Toplevel; $t->title('Edit card threading'); $t->Button( -text => 'Close', -command => [ withdraw => $t ], )->pack(qw/-side bottom/); my $f1 = $t->Frame->pack(qw/-side left/); $f1->Label( -textvariable => \$self->{_current_card} ) ->pack(qw/-side top/); my $f2 = $t->Frame( -label => "Palette", ) ->pack(qw/-side right -expand y -fill both/); my $card_canvas = $self->_card_canvas( $f1, \$t_info->{current_hole}, \$t_info->{start}, 1, )->pack(qw/-side top/); # put up the palette my $h = $self->_palette( $f2, $card_canvas, \$t_info->{current_hole}, undef, 'old' ); $f1->Button( -text => 'Prev', -command => sub { $self->set_current_card( $self->current_card - 1 ); $t_info->{start} = $self->start( $self->current_card ); $self->refresh_card_threading($card_canvas); }, )->pack(qw/-side left/); $f1->Button( -text => 'Next', -command => sub { $self->set_current_card( $self->current_card + 1 ); $t_info->{start} = $self->start( $self->current_card ); $self->refresh_card_threading($card_canvas); }, )->pack(qw/-side right/); $t_info->{dlog} = $t; $t_info->{h} = $h; $t_info->{card_canvas} = $card_canvas; $self->{_threading_dlog} = $t_info; } $self->{_threading_dlog}; } sub fs_dlog { my $self = shift; my $configs = shift; unless ( defined $self->{_fs_dlog} ) { $self->{_fs_dlog} = $self->window->FileSelect; } $self->{_fs_dlog}->configure(%$configs); $self->{_fs_dlog}; } sub color_dlog { my $self = shift; unless ( defined $self->{_color_dlog} ) { $self->{_color_dlog} = $self->window->ColorDialog; } $self->{_color_dlog}; } sub font_dlog { my $self = shift; unless ( defined $self->{_font_dlog} ) { $self->{_font_dlog} = $self->window->FontDialog( -nicefont => 0, -fixedfont => 1, ); } $self->{_font_dlog}; } sub insert_row_dlog { my $self = shift; unless ( defined $self->{_insert_row_dlog} ) { my $d_info = { dlog => undef, where => 'current', where_row => undef, turns => undef, }; my $d = $self->window->Toplevel; $d->title('Insert Row of Turns'); $d->Radiobutton( -text => 'Beginning (bottom)', -variable => \$d_info->{where}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1 = $d->Frame->pack(qw/-side top -anchor w/); $f1->Radiobutton( -text => 'After row', -variable => \$d_info->{where}, -value => 'current', )->pack(qw/-side left/); $f1->Entry( -textvariable => \$d_info->{where_row}, -width => 10, )->pack(qw/-side left/); $d->Radiobutton( -text => 'End (top)', -variable => \$d_info->{where}, -value => 'end', )->pack(qw/-side top -anchor w/); my $f2 = $d->Frame->pack(qw/-side top -anchor w/); $f2->Label( -text => 'Turns', )->pack(qw/-side left -anchor n/); $f2->Scrolled( 'Entry', -scrollbars => 's', -width => 50, -textvariable => \$d_info->{turns}, )->pack(qw/-side left -anchor n/); $d->Button( -text => 'Done', -command => sub { my $after_row = $d_info->{where} eq 'bottom' ? 0 : $d_info->{where} eq 'current' ? $d_info->{where_row} : $self->number_of_rows; if ( $d_info->{where} eq 'current' and ( $after_row < 0 or $after_row > $self->number_of_rows ) ) { $d->Dialog( -text => 'After must be between 0 and the number of turns' )->Show; return; } my @turns = split( //, $d_info->{turns} ); if ( grep !/^[\/\\|]$/, @turns ) { $d->Dialog( -text => 'Turns may only be /, | or \\' ) ->Show; return; } print "insert_row(", $after_row, ", '", $d_info->{turns} || '', "')\n"; $self->insert_row( $after_row, $d_info->{turns} || '' ); $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $d->Button( -text => 'Stay', -command => sub { my $after_row = $d_info->{where} eq 'bottom' ? 0 : $d_info->{where} eq 'current' ? $d_info->{where_row} : $self->number_of_rows; if ( $d_info->{where} eq 'current' and ( $after_row < 0 or $after_row > $self->number_of_rows ) ) { $d->Dialog( -text => 'After must be between 0 and the number of turns' )->Show; return; } my @turns = split( //, $d_info->{turns} ); if ( grep !/^[\/\\|]$/, @turns ) { $d->Dialog( -text => 'Turns may only be /, | or \\' ) ->Show; return; } print "insert_row(", $after_row, ", '", $d_info->{turns} || '', "')\n"; $self->insert_row( $after_row, $d_info->{turns} || '' ); $self->draw_pattern; }, )->pack(qw/-side left/); $d->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); $d_info->{dlog} = $d; $self->{_insert_row_dlog} = $d_info; } $self->{_insert_row_dlog}; } sub float_length_dlog { my $self = shift; my $old_float_length = shift; unless ( defined $self->{_float_length_dlog} ) { my $d_info = { dlog => undef, float_length => '', }; my $d = $self->window->DialogBox( -title => 'Shortest float to mark', -buttons => [qw/OK Cancel/], ); $d->add( 'Entry', -textvariable => \$d_info->{float_length} )->pack; $d_info->{dlog} = $d; $self->{_float_length_dlog} = $d_info; } $self->{_float_length_dlog}->{float_length} = $old_float_length; my $action = $self->{_float_length_dlog}->{dlog}->Show; return $action eq 'OK' ? $self->{_float_length_dlog}->{float_length} : $old_float_length; } sub delete_row_dlog { my $self = shift; unless ( defined $self->{_delete_row_dlog} ) { my $d_info = { dlog => undef, where => 'current', where_row => undef, }; my $d = $self->window->Toplevel; $d->title('delete Row of Turns'); $d->Radiobutton( -text => 'Beginning (bottom)', -variable => \$d_info->{where}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1 = $d->Frame->pack(qw/-side top -anchor w/); $f1->Radiobutton( -text => 'Row', -variable => \$d_info->{where}, -value => 'current', )->pack(qw/-side left/); $f1->Entry( -textvariable => \$d_info->{where_row}, -width => 10, )->pack(qw/-side left/); $d->Radiobutton( -text => 'End (top)', -variable => \$d_info->{where}, -value => 'end', )->pack(qw/-side top -anchor w/); $d->Button( -text => 'OK', -command => sub { my $after_row = $d_info->{where} eq 'bottom' ? 1 : $d_info->{where} eq 'current' ? $d_info->{where_row} : $self->number_of_rows; if ( $d_info->{where} eq 'current' and ( $after_row < 0 or $after_row > $self->number_of_rows ) ) { $d->Dialog( -text => 'Row must be between 1 and the number of turns' ) ->Show; return; } print "delete_row($after_row-1)\n"; $self->canvas->delete( "row" . ( $self->number_of_rows - 1 ) ); $self->delete_row( $after_row - 1 ); $self->current_row( $self->number_of_rows - 1 ) if $self->current_row >= $self->number_of_rows; $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $d->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); $d_info->{dlog} = $d; $self->{_delete_row_dlog} = $d_info; } $self->{_delete_row_dlog}; } sub duplicate_row_dlog { my $self = shift; unless ( defined $self->{_duplicate_row_dlog} ) { my $d_info = { dlog => undef, from => 'current', from_row => undef, after => 'current', after_row => undef, }; my $d = $self->window->Toplevel; $d->title('duplicate a Row of Turns'); my $f_bottom = $d->Frame->pack(qw/-side bottom -fill x -expand y/); my $f_left = $d->Frame( -label => 'From' )->pack(qw/-side left/); my $f_right = $d->Frame( -label => 'After' )->pack(qw/-side right/); $f_left->Radiobutton( -text => 'Beginning (bottom)', -variable => \$d_info->{from}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1 = $f_left->Frame->pack(qw/-side top -anchor w/); $f1->Radiobutton( -text => 'Row', -variable => \$d_info->{from}, -value => 'current', )->pack(qw/-side left/); $f1->Entry( -textvariable => \$d_info->{from_row}, -width => 10, )->pack(qw/-side left/); $f_left->Radiobutton( -text => 'End (top)', -variable => \$d_info->{from}, -value => 'end', )->pack(qw/-side top -anchor w/); $f_right->Radiobutton( -text => 'Beginning (bottom)', -variable => \$d_info->{from}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f2 = $f_right->Frame->pack(qw/-side top -anchor w/); $f2->Radiobutton( -text => 'Row', -variable => \$d_info->{from}, -value => 'current', )->pack(qw/-side left/); $f2->Entry( -textvariable => \$d_info->{from_row}, -width => 10, )->pack(qw/-side left/); $f_right->Radiobutton( -text => 'End (top)', -variable => \$d_info->{from}, -value => 'end', )->pack(qw/-side top -anchor w/); $f_bottom->Button( -text => 'Done', -command => sub { my $from_row = $d_info->{from} eq 'bottom' ? 1 : $d_info->{from} eq 'current' ? $d_info->{from_row} : $self->number_of_rows; if ( $d_info->{from} eq 'current' and ( $from_row < 0 or $from_row > $self->number_of_rows ) ) { $d->Dialog( -text => '"From" row must be between 1 and the number of turns' )->Show; return; } my $after_row = $d_info->{after} eq 'bottom' ? 0 : $d_info->{after} eq 'current' ? $d_info->{after_row} : $self->number_of_rows; if ( $d_info->{after} eq 'current' and ( $after_row < 0 or $after_row > $self->number_of_rows ) ) { $d->Dialog( -text => '"To" row must be between 0 and the number of turns' )->Show; return; } print "duplicate_row($from_row-1, $after_row)\n"; $self->duplicate_row( $from_row - 1, $after_row ); $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $f_bottom->Button( -text => 'Stay', -command => sub { my $from_row = $d_info->{from} eq 'bottom' ? 1 : $d_info->{from} eq 'current' ? $d_info->{from_row} : $self->number_of_rows; if ( $d_info->{from} eq 'current' and ( $from_row < 0 or $from_row > $self->number_of_rows ) ) { $d->Dialog( -text => '"From" row must be between 1 and the number of turns' )->Show; return; } my $after_row = $d_info->{after} eq 'bottom' ? 0 : $d_info->{after} eq 'current' ? $d_info->{after_row} : $self->number_of_rows; if ( $d_info->{after} eq 'current' and ( $after_row < 0 or $after_row > $self->number_of_rows ) ) { $d->Dialog( -text => '"To" row must be between 0 and the number of turns' )->Show; return; } print "duplicate_row($from_row-1, $after_row)\n"; $self->duplicate_row( $from_row - 1, $after_row ); $self->draw_pattern; }, )->pack(qw/-side left/); $f_bottom->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); $d_info->{dlog} = $d; $self->{_duplicate_row_dlog} = $d_info; } $self->{_duplicate_row_dlog}; } sub insert_dlog { my $self = shift; unless ( defined $self->{_insert_dlog} ) { $self->{_insert_dlog} = $self->window->Dialog( -text => 'Insert new card or row?', -default_button => 'Row', -buttons => [qw/Row Card Cancel/], ); } $self->{_insert_dlog}; } sub delete_dlog { my $self = shift; unless ( defined $self->{_delete_dlog} ) { $self->{_delete_dlog} = $self->window->Dialog( -text => 'Delete card or row?', -default_button => 'Row', -buttons => [qw/Row Card Cancel/], ); } $self->{_delete_dlog}; } sub insert_card_dlog { my $self = shift; unless ( defined $self->{_insert_card_dlog} ) { my $d_info = { dlog => $self->window->Toplevel, palette => undef, card_canvas => undef, where => 'current', where_card => $self->current_card, SZ => 'S', start => 0, turns => undef, threading => [ 0, 1, 2, 3 ], current_hole => 0, }; my $d = $d_info->{dlog}; $d->title('Insert New Card'); my $f3 = $d->Frame->pack(qw/-side bottom -fill x -expand y/); $f3->Button( -text => 'Insert and Close', -command => sub { my $after_card = $d_info->{where} eq 'bottom' ? 0 : $d_info->{where} eq 'current' ? $d_info->{where_card} : $self->number_of_cards; if ($d_info->{where} eq 'current' and ( $after_card < 0 or $after_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"To" card must be between 0 and the number of turns' )->Show; return; } my $turns = $d_info->{turns}->get( '1.0', 'end' ); chomp $turns; print "unreversed turns is :$turns:\n"; # any other data checks, like turns... $self->insert_card( $after_card, join( "", reverse split( //, $turns ) ) ); $self->SZ( $after_card, $d_info->{SZ} ); $self->threading( $after_card, $d_info->{threading} ); $self->start( $after_card, $d_info->{start} ); $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $f3->Button( -text => 'Insert and Stay', -command => sub { my $after_card = $d_info->{where} eq 'bottom' ? 0 : $d_info->{where} eq 'current' ? $d_info->{where_card} : $self->number_of_cards; if ($d_info->{where} eq 'current' and ( $after_card < 0 or $after_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"To" card must be between 0 and the number of turns' )->Show; return; } my $turns = $d_info->{turns}->get( '1.0', 'end' ); chomp $turns; print "unreversed turns is :$turns:\n"; # any other data checks, like turns... $self->insert_card( $after_card, join( "", reverse split( //, $turns ) ) ); $self->SZ( $after_card, $d_info->{SZ} ); $self->threading( $after_card, $d_info->{threading} ); $self->start( $after_card, $d_info->{start} ); $self->draw_pattern; }, )->pack(qw/-side left/); $f3->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); my $f1r = $d->Frame( -label => 'Turns' ) ->pack(qw/-side right -fill y -expand y/); my $turns = $f1r->Scrolled( 'Text', -scrollbars => 'e', -height => 40, -width => 1, -wrap => 'char', )->pack(qw/-side top -expand 0 -fill none/); $turns->delete( '1.0', 'end' ); $turns->insert( '1.0', join( "", reverse $self->card_turns( $self->current_card ) ) ); my $f1l = $d->Frame( -borderwidth => 1 )->pack(qw/-side top -anchor w/); my $f2l = $d->Frame( -borderwidth => 1 )->pack(qw/-side top -anchor w/); my $f3l = $d->Frame( -borderwidth => 1 )->pack(qw/-side top -anchor w/); my $f4l = $d->Frame->pack(qw/-side top -anchor w/); # where to insert - in f1r $f1l->Radiobutton( -text => 'Beginning (left)', -variable => \$d_info->{where}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1l1 = $f1l->Frame->pack(qw/-side top -anchor w/); $f1l1->Radiobutton( -text => 'After card', -variable => \$d_info->{where}, -value => 'current', )->pack(qw/-side left/); $f1l1->Entry( -textvariable => \$d_info->{where_card}, -width => 10, )->pack(qw/-side left/); $f1l->Radiobutton( -text => 'End (right)', -variable => \$d_info->{where}, -value => 'end', )->pack(qw/-side top -anchor w/); # threading direction - f2l $f2l->Radiobutton( -text => 'Threaded S', -variable => \$d_info->{SZ}, -value => 'S', )->pack(qw/-side top -anchor w/); $f2l->Radiobutton( -text => 'Threaded Z', -variable => \$d_info->{SZ}, -value => 'Z', )->pack(qw/-side top -anchor w/); # start hole and threading - f3l my $card_canvas = $self->_card_canvas( $f3l, \$d_info->{current_hole}, \$d_info->{start}, 0 )->pack(qw/-side top/); # put up the palette - f4l my $h = $self->_palette( $f4l, $card_canvas, \$d_info->{current_hole}, $d_info->{threading}, 'new' ); $d_info->{turns} = $turns; $d_info->{card_canvas} = $card_canvas; $d_info->{palette} = $h; $self->{_insert_card_dlog} = $d_info; } $self->{_insert_card_dlog}; } sub _card_canvas # (window, \current_hole, \start_hole, update_pattern?) { my $self = shift; my $win = shift; my $current_hole = shift; my $start = shift; my $update = shift || 0; my $card_canvas = $win->Canvas( -height => 200, -width => 200, -background => 'white', ); # focus areas $card_canvas->createRectangle( 10, 10, 90, 90, -fill => 'white', -outline => 'white', -tags => [ 'frect', 'focusrect0', 'A' ], ); $card_canvas->createRectangle( 110, 10, 190, 90, -fill => 'white', -outline => 'white', -tags => [ 'frect', 'focusrect1', 'B' ], ); $card_canvas->createRectangle( 110, 110, 190, 190, -fill => 'white', -outline => 'white', -tags => [ 'frect', 'focusrect2', 'C' ], ); $card_canvas->createRectangle( 10, 110, 90, 190, -fill => 'white', -outline => 'white', -tags => [ 'frect', 'focusrect3', 'D' ], ); # the hole labels $card_canvas->createText( 45, 30, -text => 'A', -anchor => 's', -tags => ['A'], ); $card_canvas->createText( 155, 30, -text => 'B', -anchor => 's', -tags => ['B'], ); $card_canvas->createText( 155, 140, -text => 'C', -anchor => 's', -tags => ['C'], ); $card_canvas->createText( 45, 140, -text => 'D', -anchor => 's', -tags => ['D'], ); # the holes $card_canvas->createOval( 30, 30, 60, 60, -fill => 'white', -outline => 'black', -tags => [ 'hole', 'A', 'hole0' ], ); $card_canvas->createOval( 140, 30, 170, 60, -fill => 'white', -outline => 'black', -tags => [ 'hole', 'B', 'hole1' ], ); $card_canvas->createOval( 140, 140, 170, 170, -fill => 'white', -outline => 'black', -tags => [ 'hole', 'C', 'hole2' ], ); $card_canvas->createOval( 30, 140, 60, 170, -fill => 'white', -outline => 'black', -tags => [ 'hole', 'D', 'hole3' ], ); # current hole indicator my $change_focus = sub { foreach my $h ( 0 .. 3 ) { $card_canvas->itemconfigure( "focusrect$h", -fill => 'white', -outline => 'white' ); } $card_canvas->itemconfigure( "focusrect$$current_hole", -fill => 'gold', -outline => 'black' ); }; # show the current hole &$change_focus; foreach my $h ( 0 .. 3 ) { $card_canvas->bind( chr( ord('A') + $h ), '<1>', sub { $$current_hole = $h; &$change_focus; } ); } # start hole indicators $card_canvas->createText( 100, 100, -text => 'Start hole', -anchor => 'center', ); my $start_sub = $update ? sub { $self->start( $self->current_card, $$start ); $self->update_pattern( $self->current_card ); } : sub { }; foreach my $coords ( [ 0, 80, 80 ], [ 1, 120, 80 ], [ 2, 120, 120 ], [ 3, 80, 120 ] ) { $card_canvas->createWindow( $$coords[1], $$coords[2], -anchor => 'center', -window => $card_canvas->Radiobutton( -text => '', -value => $$coords[0], -command => $start_sub, -variable => $start, ), ); } $card_canvas; } sub delete_card_dlog { my $self = shift; unless ( defined $self->{_delete_card_dlog} ) { my $d_info = { dlog => undef, where => 'current', where_card => undef, }; my $d = $self->window->Toplevel; $d->title('delete Card'); $d->Radiobutton( -text => 'Beginning (left)', -variable => \$d_info->{where}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1 = $d->Frame->pack(qw/-side top -anchor w/); $f1->Radiobutton( -text => 'card', -variable => \$d_info->{where}, -value => 'current', )->pack(qw/-side left/); $f1->Entry( -textvariable => \$d_info->{where_card}, -width => 10, )->pack(qw/-side left/); $d->Radiobutton( -text => 'End (right)', -variable => \$d_info->{where}, -value => 'end', )->pack(qw/-side top -anchor w/); $d->Button( -text => 'OK', -command => sub { my $after_card = $d_info->{where} eq 'bottom' ? 1 : $d_info->{where} eq 'current' ? $d_info->{where_card} : $self->number_of_cards; if ($d_info->{where} eq 'current' and ( $after_card < 0 or $after_card > $self->number_of_cards ) ) { $d->Dialog( -text => 'card must be between 1 and the number of turns' ) ->Show; return; } print "delete_card($after_card-1)\n"; $self->canvas->delete( "card" . ( $self->number_of_cards - 1 ) ); $self->delete_card( $after_card - 1 ); $self->current_card( $self->number_of_cards - 1 ) if $self->current_card >= $self->number_of_cards; $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $d->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); $d_info->{dlog} = $d; $self->{_delete_card_dlog} = $d_info; } $self->{_delete_card_dlog}; } sub duplicate_card_dlog { my $self = shift; unless ( defined $self->{_duplicate_card_dlog} ) { my $d_info = { dlog => undef, from => 'current', from_card => undef, after => 'current', after_card => undef, }; my $d = $self->window->Toplevel; $d->title('duplicate a card'); my $f_bottom = $d->Frame->pack(qw/-side bottom -fill x -expand y/); my $f_left = $d->Frame( -label => 'From' )->pack(qw/-side left/); my $f_right = $d->Frame( -label => 'After' )->pack(qw/-side right/); $f_left->Radiobutton( -text => 'Beginning (left)', -variable => \$d_info->{from}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f1 = $f_left->Frame->pack(qw/-side top -anchor w/); $f1->Radiobutton( -text => 'card', -variable => \$d_info->{from}, -value => 'current', )->pack(qw/-side left/); $f1->Entry( -textvariable => \$d_info->{from_card}, -width => 10, )->pack(qw/-side left/); $f_left->Radiobutton( -text => 'End (right)', -variable => \$d_info->{from}, -value => 'end', )->pack(qw/-side top -anchor w/); $f_right->Radiobutton( -text => 'Beginning (left)', -variable => \$d_info->{from}, -value => 'bottom', )->pack(qw/-side top -anchor w/); my $f2 = $f_right->Frame->pack(qw/-side top -anchor w/); $f2->Radiobutton( -text => 'card', -variable => \$d_info->{from}, -value => 'current', )->pack(qw/-side left/); $f2->Entry( -textvariable => \$d_info->{from_card}, -width => 10, )->pack(qw/-side left/); $f_right->Radiobutton( -text => 'End (right)', -variable => \$d_info->{from}, -value => 'end', )->pack(qw/-side top -anchor w/); $f_bottom->Button( -text => 'Done', -command => sub { my $from_card = $d_info->{from} eq 'bottom' ? 1 : $d_info->{from} eq 'current' ? $d_info->{from_card} : $self->number_of_cards; if ( $d_info->{from} eq 'current' and ( $from_card < 0 or $from_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"From" card must be between 1 and the number of turns' )->Show; return; } my $after_card = $d_info->{after} eq 'bottom' ? 0 : $d_info->{after} eq 'current' ? $d_info->{after_card} : $self->number_of_cards; if ($d_info->{after} eq 'current' and ( $after_card < 0 or $after_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"To" card must be between 0 and the number of turns' )->Show; return; } print "duplicate_card($from_card-1, $after_card)\n"; $self->duplicate_card( $from_card - 1, $after_card ); $self->draw_pattern; $d->withdraw; }, )->pack(qw/-side left/); $f_bottom->Button( -text => 'Stay', -command => sub { my $from_card = $d_info->{from} eq 'bottom' ? 1 : $d_info->{from} eq 'current' ? $d_info->{from_card} : $self->number_of_cards; if ( $d_info->{from} eq 'current' and ( $from_card < 0 or $from_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"From" card must be between 1 and the number of turns' )->Show; return; } my $after_card = $d_info->{after} eq 'bottom' ? 0 : $d_info->{after} eq 'current' ? $d_info->{after_card} : $self->number_of_cards; if ($d_info->{after} eq 'current' and ( $after_card < 0 or $after_card > $self->number_of_cards ) ) { $d->Dialog( -text => '"To" card must be between 0 and the number of turns' )->Show; return; } print "duplicate_card($from_card-1, $after_card)\n"; $self->duplicate_card( $from_card - 1, $after_card ); $self->draw_pattern; }, )->pack(qw/-side left/); $f_bottom->Button( -text => 'Cancel', -command => sub { $d->withdraw; }, )->pack(qw/-side right/); $d_info->{dlog} = $d; $self->{_duplicate_card_dlog} = $d_info; } $self->{_duplicate_card_dlog}; } #sub AUTOLOAD #{ # print "AUTOLOAD: ", join("\n", $AUTOLOAD, @_, ''); #} # Autoload methods go after =cut, and are processed by the autosplit program. 1; __END__ # Below is the stub of documentation for your module. You better edit it! =head1 NAME Weaving::Tablet::Tk - Tk user interface to Weaving::Tablet designer =head1 SYNOPSIS use Weaving::Tablet::Tk; use Tk; my $pattern = Weaving::Tablet::Tk->new_pattern(mw => $mw, file => $file); my $pattern2 = Weaving::Tablet::Tk->new_pattern(mw => $mw, cards => $cards, rows => $rows); $pattern->canvas # get the drawing area $pattern->window # get the toplevel window MainLoop; =head1 DESCRIPTION Weaving::Tablet::Tk wraps a Tk user interface around the core routines in Weaving::Tablet. Weaving::Tablet::Tk is a subclass of Weaving::Tablet. =head2 new The new function expects arguments as key/value pairs. The key "mw" is always expected. If not present, a new MainWindow is created. If present, it is the parent for a new Toplevel window. The key "file" identifies a pattern file to be loaded. If a pattern cannot be loaded from that file, undef is returned. The keys "cards" and "rows" specify the size of a new, blank pattern. If the "file" key is not provided, a blank pattern is created, with a default size of 1 row by 1 card. The window and the drawing canvas are available via member functions. =head2 Member Functions You can retrieve the Toplevel window via the "window" member. You can retrieve the drawing canvas via the "canvas" member. =head1 AUTHOR Michael Houghton =head1 SEE ALSO perl(1). =cut