home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / Spreadsheet.pm < prev    next >
Encoding:
Perl POD Document  |  2002-12-06  |  15.6 KB  |  642 lines

  1.  
  2. =head1 NAME
  3.  
  4. Tk::TableMatrix::Spreadsheet - Table Display with Spreadsheet-like bindings.
  5.  
  6. =head1 SYNOPSIS
  7.  
  8.   use Tk;
  9.   use Tk::TableMatrix::Spreadsheet
  10.  
  11.  
  12.  
  13.   my $t = $top->Scrolled('Spreadsheet', -rows => 21, -cols => 11, 
  14.                               -width => 6, -height => 6,
  15.                   -titlerows => 1, -titlecols => 1,
  16.                   -variable => $arrayVar,
  17.                   -selectmode => 'extended',
  18.                   -resizeborders => 'both',
  19.                   -titlerows => 1,
  20.                   -titlecols => 1,
  21.                   -bg => 'white',
  22.                     );
  23.  
  24. =head1 DESCRIPTION
  25.  
  26. L<Tk::TableMatrix::Spreadsheet> is a L<Tk::TableMatrix>-derived widget that implements
  27. some bindings so the resulting widget behaves more like a spreadsheet.
  28.  
  29. B<Bindings Added:>
  30.  
  31. =over 1
  32.  
  33. =item *
  34.  
  35. Row/Col resize handles appear when the cursor is placed
  36. over a row/col border line in the rol/col title area.
  37.  
  38. =item *
  39.  
  40. A popup menu for row/col insert/delete appears when the mouse is right-clicked in the
  41. row/col title areas. 
  42.  
  43. =item *
  44.  
  45. Cells activate (i.e. the contents become edit-able) only when the cell is double-clicked
  46. or the F2 button is pressed. The default L<Tk::TableMatrix> behavior is for the
  47. cell to be activated when the cell is single-clicked.
  48.  
  49. =item *
  50.  
  51. The Escape key causes any changes made to a cell to be canceled and the current
  52. selection cleared.
  53.  
  54. =item *
  55.  
  56. The return key causes the the current cell to move down.
  57.  
  58. =item *
  59.  
  60. The tab (or shift tab) key causes the current cell to be moved to the right (left).
  61.  
  62. =item *
  63.  
  64. The delete key will delete the current selection, if no cell is currently active.
  65.  
  66. =item *
  67.  
  68. The Mouse button 2 (middle button) paste from the PRIMARY. (Control-v pastes from the
  69. clipboard).
  70.  
  71. =back
  72.  
  73. =head1 Additional Information
  74.  
  75. Widget methods, options, etc, are inherited from the L<Tk::TableMatrix> widget. See its 
  76. docs for additional information.
  77.  
  78. =cut
  79.  
  80.  
  81.  
  82. package Tk::TableMatrix::Spreadsheet;
  83.  
  84. use Carp;
  85.  
  86.  
  87. use Tk;
  88. use Tk::TableMatrix;
  89. use Tk::Derived;
  90.  
  91. use base qw/ Tk::Derived Tk::TableMatrix/;
  92.  
  93. $VERSION = '1.01';
  94.  
  95.  
  96. Tk::Widget->Construct("Spreadsheet");
  97.  
  98.  
  99. sub ClassInit{
  100.     my ($class,$mw) = @_;
  101.  
  102.     $class->SUPER::ClassInit($mw);
  103.     
  104.     #  Bind our motion routine to change cursors for row/column resize
  105.     $mw->bind($class,'<Motion>',['GeneralMotion',$mw]);
  106.  
  107.     # Over-ride default button release binding
  108.     #  so a cell won't activate by just clicking
  109.     $mw->bind($class,'<ButtonRelease-1>',
  110.         sub
  111.          {
  112.           my $w = shift;
  113.           my $Ev = $w->XEvent;
  114.           
  115.           $w->{rowColResizeDrag} = 0;  # reset row/col resize dragging flag
  116.           if ($w->exists)
  117.            {
  118.             $w->CancelRepeat;
  119.             # $w->activate('@' . $Ev->x.",".$Ev->y);
  120.            }
  121.          }
  122.     );
  123.  
  124.  
  125.     # Edit (activate) a cell if it is double-clicked
  126.     #   Or F2 is pressed
  127.     $mw->bind($class,'<Double-1>',
  128.         sub
  129.          {
  130.           my $w = shift;
  131.           my $Ev = $w->XEvent;
  132.           if ($w->exists)
  133.            {
  134.             $w->CancelRepeat;
  135.             $w->activate('@' . $Ev->x.",".$Ev->y);
  136.            }
  137.          }
  138.     );
  139.     $mw->bind($class,'<F2>',
  140.         sub
  141.          {
  142.           my $w = shift;
  143.           my $Ev = $w->XEvent;
  144.           if ($w->exists)
  145.            {
  146.             $w->CancelRepeat;
  147.             my $location = '@' . $Ev->x.",".$Ev->y;
  148.             print "location = $location\n";
  149.             if( $w->selectionIncludes($location)){
  150.                 $w->activate('@' . $Ev->x.",".$Ev->y);
  151.             }
  152.            }
  153.          }
  154.     );
  155.  
  156.  
  157.  
  158.  
  159.     $mw->bind($class,'<Escape>',
  160.         sub
  161.          {
  162.           my $w = shift;
  163.           $w->reread; # undo any changes if editing a cell
  164.               my $upperLeft = $w->cget(-roworigin).",".$w->cget(-colorigin);
  165.           $w->activate($upperLeft);
  166.           $w->selectionClear('all');
  167.           
  168.          }
  169.     );
  170.  
  171.  
  172.     # Make the return key enter and move down
  173.     $mw->bind($class,'<Return>',['MoveCell',1,0]);
  174.     $mw->bind($class,'<KP_Enter>',['MoveCell',1,0]);
  175.     
  176.     # Make the tab key enter and move right
  177.      $mw->bind($class,'<Tab>',
  178.             sub{ 
  179.                 my $w = shift;
  180.                 $w->MoveCell(0,1);
  181.                 Tk->break;
  182.             }
  183.     );
  184.      $mw->bind($class,'<Shift-KP_Tab>',['MoveCell',0,-1]);
  185.  
  186.         # Make the delete key delete the selection, if no active cell
  187.      $mw->bind($class,'<Delete>',
  188.         sub{
  189.             my $self = shift;
  190.             my $active;
  191.             # Get the current active cell, if one exists
  192.             eval { $active = $self->index('active'); }; 
  193.  
  194.             $active = '' if( $@); # No Active cell found;
  195.  
  196.             # No Active cell if it is set to the upper left column (esc key pressed)
  197.                 my $upperLeft = $self->cget(-roworigin).",".$self->cget(-colorigin);
  198.  
  199.             $active = '' if( $active eq $upperLeft); # No Active cell found;
  200.             
  201.             if( $active eq ''){  # No Active Cell, delete the selection
  202.                    eval
  203.                     {
  204.                      $self->curselection(undef);# Clear whatever is selected
  205.                      $self->selectionClear();
  206.                      }
  207.             }
  208.             else{  # There is a current active cell, perform delete in that
  209.                 $self->deleteActive('insert');
  210.             }
  211.         }
  212.         
  213.     );
  214.     
  215.     # Button2 release pastes from PRIMARY (control v pastes from clipboard
  216.      $mw->bind($class,'<ButtonRelease-2>',
  217.           sub
  218.            {
  219.             my $w = shift;
  220.             my $Ev = $w->XEvent;
  221.             $w->Paste($w->index('@' . $Ev->x.",".$Ev->y),'PRIMARY') unless ($Tk::TableMatrix::tkPriv{'mouseMoved'});
  222.            }
  223.          );
  224.  
  225.  
  226. };
  227.  
  228.  
  229. sub Populate {
  230.     my ($cw, $args) = @_;
  231.     
  232.     # Set Default Args:
  233.     $args->{-bg} = 'white' unless defined( $args->{-bg});
  234.     
  235.     $args->{-colstretchmode} = 'unset' unless defined( $args->{-colstretchmode});
  236.     
  237.     
  238.     $cw->SUPER::Populate($args);
  239.     
  240.     # default Tags
  241.     $cw->tagConfigure('active', -bg => 'gray90', -relief => 'sunken', -fg => 'black');
  242.     $cw->tagConfigure( 'title', -bg => 'gray85', -fg => 'black', -relief => 'sunken');
  243.    
  244.    
  245.     # setup Popup Menu (right mouse-button press) for common operations
  246.     my $popup = $cw->Menu('-tearoff' => 0);
  247.     $popup->command('-label' => 'Insert', -bg => 'gray85', '-command' => ['insertRowCol',$cw] );
  248.     $popup->command('-label' => 'Delete', -bg => 'gray85','-command' => ['deleteRowCol',$cw] );
  249.     $popup->command('-label' => 'Clear Contents', -bg => 'gray85','-command' => ['curselection', $cw,''] );
  250.  
  251.  
  252.  
  253.  
  254.     # Bind a sub for button 3 press
  255.     $cw->bind('<ButtonPress-3>', 
  256.  
  257.     sub {
  258.     
  259.  
  260.             my $Ev = $cw->XEvent;
  261.  
  262.         # Don't Do anything if we are on a cell border
  263.         #  This keeps the right-click menu from pop-ing up
  264.         #  when starting a cell re-size
  265.         my @border = $cw->border('mark',$Ev->x,$Ev->y);
  266.         # print "border = ".join(", ",@border)." size = ".scalar(@stuff)."\n";
  267.         
  268.         # return if on a border or if not in edit mode
  269.             return if( scalar(@border) || ( $cw->cget(-state) =~ /disabled/i ));
  270.         
  271.  
  272.         my $inTitleArea = 0;  # Flag = 1 if we are in a title Area
  273.         my $inSelectedArea = 0; # Flag = 1 if we are in a selected area
  274.  
  275.         my ($x,$y) = ($Ev->x, $Ev->y);    
  276.             
  277.         my $pointerLoc = $cw->index('@'."$x,$y");
  278.         # print "Pointer over = '$pointerLoc'\n";
  279.         
  280.         if( $cw->tagIncludes('title',$pointerLoc) && $pointerLoc ne '0,0' ){
  281.             # print "Pointer over a title area\n";
  282.             $inTitleArea = 1;
  283.             
  284.         }
  285.         if( $cw->selectionIncludes($pointerLoc)){
  286.             $inSelectedArea = 1;
  287.             # print "In Selected Area\n";
  288.         }
  289.  
  290.         if( $inTitleArea && !$inSelectedArea){ # select the row/col if
  291.                                # in title area and not selected
  292.             $cw->BeginSelect($pointerLoc);
  293.         }
  294.             
  295.         if( $inTitleArea ){
  296.             $popup->Popup('-popover' => 'cursor', '-popanchor' => 'nw');
  297.         }
  298.         
  299.     }
  300.      );
  301.  
  302.     
  303. }
  304.  
  305. # Sub to insert row/cols
  306. sub insertRowCol{
  307.  
  308.     my $cw = shift;
  309.     my $Ev = $cw->XEvent;
  310.  
  311.     my ($x,$y) = ($Ev->x, $Ev->y);    
  312.  
  313.     my $pointerLoc = $cw->index('@'."$x,$y");
  314.     my ($r,$c) = split(",",$pointerLoc);
  315.     
  316.     if( $r <= 0){ # Insert Col
  317.         my %cols;
  318.         @cols{map /(\d+)$/, $cw->tagCell('sel')} = 1;
  319.         my @cols = sort {$a <=> $b} keys %cols;
  320.         
  321.         my $minCol = $cols[0];
  322.         my $colCount = $cols[-1] - $minCol + 1;
  323.         $cw->insertCols($minCol,-$colCount);
  324.         
  325.         # Make selection and clear
  326.         my $lastRow = $cw->index('end','row');
  327.         $cw->selectionSet("0,$minCol","$lastRow,".$cols[-1]);
  328.         $cw->curselection('');        
  329.     }
  330.     elsif( $c <= 0 ){
  331.         my %rows;
  332.         @rows{map /^(\d+)/, $cw->tagCell('sel')} = 1;
  333.         my @rows = sort {$a <=> $b} keys %rows;
  334.         
  335.         my $minRow = $rows[0];
  336.         my $rowCount = $rows[-1] - $minRow + 1;
  337.         $cw->insertRows($minRow,-$rowCount);
  338.         
  339.         # Make selection and clear
  340.         my $lastCol = $cw->index('end','col');
  341.         $cw->selectionSet("$minRow,0",$rows[-1].",$lastCol");
  342.         $cw->curselection('');        
  343.         
  344.     }
  345.     
  346. }
  347.  
  348. # Sub to delete row/cols
  349. sub deleteRowCol{
  350.  
  351.     my $cw = shift;
  352.     my $Ev = $cw->XEvent;
  353.  
  354.     my ($x,$y) = ($Ev->x, $Ev->y);    
  355.  
  356.     my $pointerLoc = $cw->index('@'."$x,$y");
  357.     my ($r,$c) = split(",",$pointerLoc);
  358.     
  359.     if( $r <= 0){ # Delete Col
  360.         my %cols;
  361.         @cols{map /(\d+)$/, $cw->tagCell('sel')} = 1;
  362.         my @cols = sort {$a <=> $b} keys %cols;
  363.         
  364.         my $minCol = $cols[0];
  365.         my $colCount = $cols[-1] - $minCol + 1;
  366.         $cw->deleteCols($minCol,$colCount);
  367.         
  368.         # Make selection
  369.         my $lastRow = $cw->index('end','row');
  370.         $cw->selectionSet("0,$minCol","$lastRow,".$cols[-1]);
  371.     }
  372.     elsif( $c <= 0 ){
  373.         my %rows;
  374.         @rows{map /^(\d+)/, $cw->tagCell('sel')} = 1;
  375.         my @rows = sort {$a <=> $b} keys %rows;
  376.         
  377.         my $minRow = $rows[0];
  378.         my $rowCount = $rows[-1] - $minRow + 1;
  379.         $cw->deleteRows($minRow,$rowCount);
  380.         
  381.         # Make selection
  382.         my $lastCol = $cw->index('end','col');
  383.         $cw->selectionSet("$minRow,0",$rows[-1].",$lastCol");
  384.         
  385.     }
  386.     
  387. }
  388.  
  389. # General Motion routine. Sets the border cursor to <-> if on a row border.
  390. #  or vertical resize cursor if on a col border
  391.  
  392. sub GeneralMotion{
  393.  
  394.     my $self  = shift;
  395.     my $Ev = $self->XEvent;
  396.  
  397.     my $rc = $self->index('@' . $Ev->x.",".$Ev->y);
  398.     return unless($rc);
  399.     
  400.     my ($row,$col) = split(',',$rc);
  401.     my $rowColResize = $self->{rowColResize};  # Flag = 1 if cursor has been changed for a row/col resize
  402.     my $rowColResizeOldCursor = $self->{rowColResizeOldCursor};          #  name of old cursor that was changed;
  403.     my $rowColResizeOldBDCursor = $self->{rowColResizeBDOldCursor};          #  name of old BD cursor that was changed;
  404.     
  405.     my @border = $self->border('mark',$Ev->x,$Ev->y);
  406.     if( scalar(@border) ){  # we are on a border
  407.         my ($r,$c) = @border;
  408.         
  409.         # print "In motion $r, $c: $row, $col\n";
  410.         
  411.         # my $currentBDCursor = $self->cget(-bordercursor);
  412.  
  413.         if( ($col <= 0) && ($r =~ /\d/)  ){
  414.             # print "Row Border = $r\n";
  415.             # print "Setting Row Border \n";
  416.             unless($rowColResize){
  417.                 $self->{rowColResizeOldCursor} = $self->cget(-cursor);
  418.                 $self->{rowColResizeBDOldCursor} = $self->cget(-bordercursor);
  419.                 $self->configure(-cursor => 'sb_v_double_arrow',
  420.                     -bordercursor => 'sb_v_double_arrow');
  421.                 $self->{rowColResize} = 1;
  422.             }
  423.             
  424.         }
  425.         elsif( ($row <= 0) && ($c =~ /\d/) ){
  426.             # print "Col Border = $c\n";
  427.             unless($rowColResize){
  428.                 $self->{rowColResizeOldCursor} = $self->cget(-cursor);
  429.                 $self->{rowColResizeBDOldCursor} = $self->cget(-bordercursor);
  430.                 $self->configure(-cursor => 'sb_h_double_arrow',
  431.                     -bordercursor => 'sb_h_double_arrow');
  432.                 $self->{rowColResize} = 1;
  433.             }
  434.  
  435.         }
  436.         
  437.     }
  438.     else{
  439.         if( $rowColResize && !($self->{rowColResizeDrag}) ){  # Change cursor back if it has been changed, and
  440.                                     # we aren't currently doing a row/col resize drag.
  441.             # print "Setting to $oldCursor\n";
  442.             $self->configure(-cursor => $rowColResizeOldCursor,
  443.                 -bordercursor => $rowColResizeOldBDCursor);
  444.             $self->{rowColResize} = 0;
  445.         }
  446.  
  447.     }
  448.             
  449.         
  450. }
  451.  
  452.  
  453. # Over-ridden Motion routine. Does a row/col resize if
  454. #   row/col resize cursors are active
  455.  
  456. sub Motion{
  457.     my $self  = shift;
  458.     my $rc = shift;
  459.  
  460.     if( $self->{rowColResize}){ # Do a row/col resize if cursors active
  461.         my $Ev = $self->XEvent;
  462.         
  463.         $self->{rowColResizeDrag} = 1;  # Flag = 1 if we are currently doing a row/col resize drag
  464.         $self->border('dragto',$Ev->x,$Ev->y);
  465.     }
  466.     else{
  467.         
  468.         $self->SUPER::Motion($rc);
  469.     }
  470. }
  471.         
  472. #############################################################
  473. ## Over-ridden beginselect. Doesn't select if we are doing a row/col resize
  474. sub BeginSelect{
  475.     my $self  = shift;
  476.     my $rc = shift;
  477.     
  478.     return if( $self->{rowColResize}); # Don't Select if currently doing a row/col resize
  479.     
  480.     # print "Calling inherited BeginSelect\n";
  481.     $self->SUPER::BeginSelect($rc);
  482.     
  483. }
  484.  
  485.  
  486. #############################################################
  487. ## Over-ridden TableInsert. 
  488. ##  If a  key is pressed and a cell is not activated. Activate the
  489. ##    current cell and insert the key pressed
  490. sub TableInsert{
  491.     my $self  = shift;
  492.     my $key = shift;
  493.  
  494.     # my $Ev = $self->XEvent;
  495.  
  496.     # Activate the current anchor position, if 
  497.     #  key pressed, and no cell currently active
  498.     
  499.     # Get the current active cell, if one exists
  500.     eval { $active = $self->index('active'); }; 
  501.         
  502.     $active = '' if( $@); # No Active cell found;
  503.  
  504.     # No Active cell if it is set to the upper left column (esc key pressed)
  505.         my $upperLeft = $self->cget(-roworigin).",".$self->cget(-colorigin);
  506.  
  507.     $active = '' if( $active eq $upperLeft); # No Active cell found;
  508.  
  509.     if( $key ne '' && $active eq '' ){
  510.             my $anchor = $self->index('anchor');
  511.         $self->activate($anchor);
  512.         $self->deleteActive(0,'end'); # delete text from the cell
  513.     }
  514.         
  515.     $self->SUPER::TableInsert($key);
  516.     
  517. }
  518.  
  519.  
  520. #############################################################
  521. ## Over-ridden MoveCell. 
  522. ##  This method performs moving cells in a more Excel-like way:
  523. ##   1) Moving cell when one is active unactivates the cell and then selects (not activates)
  524. ##      the new cell
  525. ##   2) Moving cell when none is active moves the anchor point cell, if one exits.
  526. ##   3)  Does nothing otherwise
  527.  
  528. sub MoveCell{
  529.  
  530.     my $w = shift;
  531.     my $x = shift; # Delta X for moving
  532.     my $y = shift; # Delta y for moving
  533.     my $c;
  534.     my $cell;      # new cell index
  535.     my $true;
  536.     my $r;
  537.     
  538.     my $fromCell; # Cell to move from (Could be an active cell, if present, or selection anchor point
  539.               #  if present.
  540.               
  541.     my $active;    # Current active cell
  542.  
  543.     # Get the current active cell, if one exists
  544.     eval { $active = $w->index('active'); }; 
  545.  
  546.     $active = '' if( $@); # No Active cell found;
  547.  
  548.     # No Active cell if it is set to the upper left column (i.e. esc key pressed)
  549.         my $upperLeft = $w->cget(-roworigin).",".$w->cget(-colorigin);
  550.  
  551.     $active = '' if( $active eq $upperLeft); # No Active cell found;
  552.  
  553.     if( $active eq ''){  # no active cell found, see if there is a selection
  554.         my $anchor = $w->index('anchor');
  555.  
  556.                 
  557.         unless( defined($anchor) ){
  558.             # print "Anchor not defined\n";
  559.             return;
  560.         }
  561.         
  562.         $fromCell = $anchor;
  563.     }
  564.     else{
  565.         $fromCell = $active;
  566.     }
  567.             
  568.  
  569.     ($r,$c) = split(',',$fromCell);
  570.     # my $currentCell = "$r,$c";
  571.  
  572.     $cell = $w->index(($r += $x).",".($c += $y));
  573.  
  574.  
  575.     $w->activate($upperLeft) if( $active ne '');
  576.     $w->see($cell);
  577.     if ($w->cget('-selectmode') eq 'browse')
  578.      {
  579.       $w->selection('clear','all');
  580.       $w->selection('set',$cell);
  581.      }
  582.     elsif ($w->cget('-selectmode') eq 'extended')
  583.      {
  584.       $w->selection('clear','all');
  585.       $w->selection('set',$cell);
  586.       $w->selection('anchor',$cell);
  587.       $Tk::TableMatrix::tkPriv{'tablePrev'} = $cell;
  588.      }
  589. }    
  590.     
  591.  
  592. #############################################################
  593. ## Over-ridden Paste. 
  594. ##  This method performs pasting cells in a more Excel-like way:
  595. ##   Paste Data will be pasted into the current selection anchor point
  596. ##     if no current cell is active, otherwise it pastes starting at the active
  597. ##       cell.
  598. ##
  599. ##   If no current active cell, and no anchor point, does nothing.
  600. sub Paste{
  601.      my $w = shift;
  602.      my $cell = shift || ''; 
  603.      my $source = shift || 'CLIPBOARD';  # Default is to paste from the clipboard
  604.      my $data;
  605.      
  606.      # Check for active cell or anchor cell:
  607.      unless($cell){
  608.  
  609.  
  610.         my $active;    # Current active cell
  611.  
  612.         # Get the current active cell, if one exists
  613.         eval { $active = $w->index('active'); }; 
  614.  
  615.         $active = '' if( $@); # No Active cell found;
  616.  
  617.         # No Active cell if it is set to the upper left column (i.e. esc key pressed)
  618.             my $upperLeft = $w->cget(-roworigin).",".$w->cget(-colorigin);
  619.  
  620.         $active = '' if( $active eq $upperLeft); # No Active cell found;
  621.  
  622.         if( $active eq ''){  # no active cell found, see if there is a selection
  623.             $cell = $w->index('anchor');
  624.  
  625.             return unless( $cell); # don't paste if no anchor point and no active
  626.  
  627.         }
  628.         else{
  629.             $cell = $active;
  630.         }
  631.  
  632.      }
  633.      
  634.      eval{ $data = $w->SelectionGet(-selection => $source); }; return if($@);
  635.       $w->PasteHandler($cell,$data);
  636.       $w->focus if ($w->cget('-state') eq 'normal');
  637. }
  638.  
  639.  
  640. 1;
  641.  
  642.