home *** CD-ROM | disk | FTP | other *** search
/ PC World 1997 November / PCWorld_1997-11_cd.bin / software / programy / komix / DATA.Z / rwmethods.tcl < prev    next >
Encoding:
Text File  |  1996-10-23  |  14.2 KB  |  570 lines

  1. #---------------------------------------------------------------------------
  2. #
  3. #    (c) Cadre Technologies Inc. 1995
  4. #
  5. #    File:        @(#)rwmethods.tcl    /main/hindenburg/1
  6. #    Author:        Harm Leijendeckers
  7. #    Description:    Implementation of methods of class Report
  8. #
  9. #---------------------------------------------------------------------------
  10. # SccsId = @(#)rwmethods.tcl    /main/hindenburg/1    23 Oct 1996    Copyright 1995 Cadre Technologies Inc.
  11.  
  12.  
  13. ###########################################################################
  14. #
  15. #    Public methods.
  16. #
  17. ###########################################################################
  18.  
  19.  
  20. constructor Report {class this} {
  21.     set this [GCObject::constructor $class $this]
  22.     $this init
  23.     return $this
  24. }
  25.  
  26.  
  27. # Initialize attributes. It's also called in the constructor.
  28. method Report::init {this args} {
  29.     set errormsg "wrong # args: should be \"Report::init ?-linelen num?\
  30.         ?-pagelen num?\""
  31.  
  32.     # check arguments
  33.     set ret [scan $args "%s%d%s%d" what1 val1 what2 val2]
  34.     if { $ret != -1 && $ret != 2 && $ret != 4 } { error $errormsg }
  35.  
  36.     set oldLinelen [$this linelen]
  37.     set oldPagelen [$this pagelen]
  38.     $this linelen 0
  39.     $this pagelen 0
  40.  
  41.     for { set i 0 } { $i < [expr $ret/2] } { incr i } {
  42.     if { [Report::lchkfirst args -linelen] } {
  43.         $this linelen [lvarpop args]
  44.     }
  45.     if { [Report::lchkfirst args -pagelen] } {
  46.         $this pagelen [lvarpop args]
  47.     }
  48.     }
  49.  
  50.     if ![lempty $args] {
  51.     $this linelen oldLinelen
  52.     $this pagelen oldPagelen
  53.     error $errormsg
  54.     }
  55.  
  56.     # set linelen to parameter if used else use the m4 variable
  57.     # and if that is not set use the default value
  58.     if { [$this linelen] <= 0 } { $this linelen [m4_var get M4_a_printer_llen] }
  59.     if { [$this linelen] <= 0 } { $this linelen 130 }
  60.  
  61.     # set pagelen to parameter if used else use the m4 variable
  62.     # and if that is not set use the default value
  63.     if { [$this pagelen] <= 0 } {
  64.     $this pagelen [m4_var get M4_a_printer_plen]
  65.     }
  66.     if { [$this pagelen] <= 0 } { $this pagelen 61 }
  67.  
  68.     $this toPrint ""
  69.     $this lineno 0
  70.     $this pageno 0
  71.     $this date [fmtclock [getclock] "%d %h %Y %X"]
  72.     $this headerList ""
  73.     $this footerList ""
  74.  
  75.     return
  76. }
  77.  
  78.  
  79. # Main print procedure.
  80. method Report::print {this str args} {
  81.     # substitute all tabs in str for one space
  82.     regsub -all ((\r?\n)|\t) $str " " str
  83.  
  84.     # number
  85.     set len [lindex $args 0]
  86.     expr { [Report::isInt $len] ? [lvarpop args]
  87.                 : [set len [string length $str]] }
  88.  
  89.     # 'left', 'center', 'right' or 'fill'
  90.     switch -exact [lindex $args 0] {
  91.     left    { set align left; lvarpop args }
  92.     center  { set align center; lvarpop args }
  93.     right   { set align right; lvarpop args }
  94.     fill    { set align fill; lvarpop args }
  95.     default { set align left }
  96.     }
  97.  
  98.     # 'break'
  99.     set break [Report::lchkfirst args break]
  100.  
  101.     # format str according to len, align and break and add str to toPrint
  102.     if { [string length $str] <= $len || $len == 0 } {
  103.     # strlen(str) < len
  104.     lappend new_s [Report::align $str $len $align]
  105.     } else {
  106.     # strlen(str) > len
  107.     if { $break } {
  108.         # truncate str
  109.         lappend new_s [list [string range $str 0 [expr $len-1]]]
  110.     } else {
  111.         # break == off so divide str over more lines
  112.         while { [string length $str] > $len } {
  113.         set sub [string range $str 0 [expr $len-1]]
  114.         set str [string range $str $len [string length $str]]
  115.         lappend new_s [list $sub]
  116.         }
  117.         # strlen(str) <= len so add aligned str
  118.         lappend new_s [Report::align $str $len $align]
  119.     }
  120.     }
  121.  
  122.     # add new_s to global toPrint
  123.     set tmp [$this toPrint]
  124.     lappend tmp [join $new_s]
  125.     $this toPrint $tmp
  126.  
  127.     # look if the next argument is 'line'
  128.     if { [Report::lchkfirst args line] } { $this line }
  129.  
  130.     # look if the next argument is 'page'
  131.     if { [Report::lchkfirst args page] } { $this page }
  132.  
  133.     if ![lempty $args] {
  134.     error [format "print command: unknown argument(s): %s" $args]
  135.     }
  136.  
  137.     return
  138. }
  139.  
  140.  
  141. # Print all outstanding output and a newline.
  142. method Report::line {this} {
  143.     set tp [$this toPrint]
  144.     $this toPrint ""
  145.  
  146.     # look if we need to print a header first
  147.     $this doHeader
  148.     $this doFooter
  149.  
  150.     if [lempty $tp] {
  151.     puts ""
  152.     $this lineno [expr [$this lineno]+1]
  153.     return
  154.     }
  155.  
  156.     for { set i 0 } { $i < [Report::maxelem $tp] } { incr i } {
  157.     set s ""
  158.     foreach j $tp {
  159.         if { [Report::isSeparator $j] } {
  160.             set elem [lindex $j 1]
  161.         } else {
  162.         if [lempty [lindex $j $i]] {
  163.             set elem [join [Report::align " " \
  164.             [string length [lindex $j 0]] fill]]
  165.         } else { set elem [lindex $j $i] }
  166.         }
  167.         append s $elem
  168.     }
  169.  
  170.     # delete trailing spaces
  171.     set s [string trimright $s]
  172.  
  173.     $this doHeader
  174.  
  175.     # if s is longer than linelen, we only have to print
  176.     # s again with length linelen
  177.     if { [string length $s] > [$this linelen] } {
  178.         $this print "$s" [$this linelen] line
  179.     } else {
  180.         puts $s
  181.         $this lineno [expr [$this lineno]+1]
  182.         $this doFooter
  183.     }
  184.     }
  185.  
  186.     return
  187. }
  188.  
  189.  
  190. # Print all outstanding lines and fill the rest of the page with empty lines.
  191. method Report::page {this} {
  192.     # print outstanding lines
  193.     if ![lempty [$this toPrint]] { $this line }
  194.  
  195.     # and fill the rest of the page with empty lines
  196.     while { ![$this doFooter] } { $this line }
  197.  
  198.     return
  199. }
  200.  
  201.  
  202. # Print a separator. This can be any kind of string.
  203. method Report::separator {this s args} {
  204.     set tmp [$this toPrint]
  205.     lappend tmp [list {} $s]
  206.     $this toPrint $tmp
  207.  
  208.     # look if the next argument is 'line'
  209.     if { [Report::lchkfirst args line] } { $this line }
  210.  
  211.     # look if the next argument is 'page'
  212.     if { [Report::lchkfirst args page] } { $this page }
  213.  
  214.     if ![lempty $args] {
  215.        error [format "separator command: unknown argument(s): %s" $args]
  216.     }
  217.  
  218.     return
  219. }
  220.  
  221.  
  222. # Print a number of spaces, default is one.
  223. method Report::space {this args} {
  224.     if [lempty $args] { set args 1 }
  225.     if { [llength $args] > 1 ||
  226.     ( [llength $args] == 1 && ![Report::isInt $args] ) } {
  227.     error [format "space command: unknown argument(s): %s" $args]
  228.     return
  229.     }
  230.  
  231.     $this print " " $args
  232. }
  233.  
  234.  
  235. method Report::file {this fileName {indent 0}} {
  236.     set fp [open $fileName]
  237.     set fstatus [gets $fp s]
  238.  
  239.     while { $fstatus != -1 } {
  240.     $this print " " $indent fill
  241.     set s [Report::expandTabs $s $indent 8]
  242.     $this print $s [expr [$this linelen]-$indent] line
  243.     set fstatus [gets $fp s]
  244.     }
  245.     close $fp
  246.  
  247.     return
  248.  
  249.  
  250. # Add a header to the headerlist. A header is a set of Tcl commands.
  251. method Report::header {this cmdstr} {
  252.     lappend hl [expr [info level]-1]
  253.     lappend hl $cmdstr
  254.  
  255.     # append to the end of headerList
  256.     set tmp [$this headerList]
  257.     lappend tmp $hl
  258.     $this headerList $tmp
  259.  
  260.     return
  261. }
  262.  
  263.  
  264. # Add a footer to the footerlist. A footer is a set of Tcl commands.
  265. method Report::footer {this cmdstr {len -1}} {
  266.     if { $len < 1 } { set len [Report::countLines $cmdstr] }
  267.  
  268.     lappend fl [expr [info level]-1]
  269.     lappend fl $len
  270.     lappend fl $cmdstr
  271.  
  272.     # insert at the beginning of footerList
  273.     $this footerList [linsert [$this footerList] 0 $fl]
  274.  
  275.     return
  276. }
  277.  
  278.  
  279. # Remove a header, footer or both on this level or on all levels.
  280. method Report::remove {this what args} {
  281.     set argerror "wrong # args: should be \"remove header|footer|hf <all>\""
  282.     set all [lvarpop args]
  283.  
  284.     if { ![lempty $args] || ($all != "" && $all != "all") } {
  285.     error $argerror
  286.     }
  287.  
  288.     if { $what == "hf" } {
  289.     $this remove header $all
  290.     $this remove footer $all
  291.     return
  292.     }
  293.  
  294.     if { $all != "all" } {
  295.     if { $what == "footer" } {
  296.         $this footerList [lrange [$this footerList] 1 end]
  297.         return
  298.     }
  299.     if { $what == "header" } {
  300.         $this headerList [lrange [$this headerList] \
  301.             0 [expr [llength [$this headerList]]-2]]
  302.         return
  303.     }
  304.     error $argerror
  305.     }
  306.  
  307.     # do a smart deletion of all the headers or footers on this
  308.     # level
  309.     if { $what == "header" || $what == "footer" } {
  310.     # walk through list and delete headers on
  311.     # current level and headers which should have
  312.     # been deleted by the user after leaving a
  313.     # level.
  314.  
  315.     if { $what == "header" } {
  316.         set origList [$this headerList]
  317.     } else {
  318.         set origList [Report::lreverse [$this footerList]]
  319.     }
  320.  
  321.     set callLevel [expr [info level]-2]
  322.     set prevLevel -1
  323.     set newL {}
  324.     foreach i $origList {
  325.         if { [lindex $i 0] >= $callLevel || [lindex $i 0] < $prevLevel } {
  326.             break
  327.         }
  328.         lappend newL $i
  329.         set prevLevel [lindex $i 0]
  330.     }
  331.  
  332.     if { $what == "header" } {
  333.         $this headerList $newL
  334.     } else {
  335.         $this footerList [Report::lreverse $newL]
  336.     }
  337.  
  338.     return
  339.     }
  340.  
  341.     error $argerror
  342. }
  343.  
  344.  
  345. # Return 1 if there is output to be printed, otherwise 0
  346. method Report::queued {this} {
  347.     if [lempty [$this toPrint]] {
  348.     return 0
  349.     }
  350.  
  351.     return 1
  352. }
  353.  
  354.  
  355. ###########################################################################
  356. #
  357. #    Private methods. Don't call them outside a method.
  358. #
  359. ###########################################################################
  360.  
  361.  
  362. # Execute headerList if lineno == 0.
  363. method Report::doHeader {this} {
  364.     if { ([$this lineno] != 0 && [$this lineno] <= [$this pagelen])
  365.         || [$this pagelen] == -1 } {
  366.     return
  367.     }
  368.  
  369.     $this pageno [expr [$this pageno]+1]
  370.  
  371.     # set the pagelen to unlimited so the header is always printed
  372.     # as if it fits on one page
  373.     set oldpagelen [$this pagelen]
  374.     $this pagelen -1
  375.     $this lineno 1
  376.  
  377.     foreach i [$this headerList] {
  378.     if { [info level] < [lindex $i 0] } {
  379.         # skip headers which were defined on a lower level
  380.         # than the current level
  381.         continue
  382.     }
  383.     # execute the header on the level it was defined
  384.     uplevel #[lindex $i 0] [lindex $i 1]
  385.     }
  386.  
  387.     $this pagelen $oldpagelen
  388.  
  389.     # if the header needed to be printed on more than one page
  390.     # the current lineno is bigger then pagelen, so adjust it
  391.     $this lineno [expr [$this lineno]%[$this pagelen]]
  392.     if { ![$this lineno] } { $this lineno [$this pagelen] }
  393.  
  394.     return
  395. }
  396.  
  397.  
  398. # Execute footerList if lineno+footerlength == pagelen.
  399. method Report::doFooter {this} {
  400.     if { [$this pagelen] == -1 } { return 0 }
  401.  
  402.     # calculate current footerlength
  403.     set len 0
  404.     foreach i [$this footerList] { incr len [lindex $i 1] }
  405.  
  406.     # if we have got to print the footer
  407.     if { [expr $len+[$this lineno]] > [$this pagelen] } {
  408.     set oldpagelen [$this pagelen]
  409.     $this pagelen -1
  410.  
  411.     foreach i [$this footerList] {
  412.         # execute the footer on the level it was defined
  413.         uplevel #[lindex $i 0] [lindex $i 2]
  414.     }
  415.  
  416.     # if the user forgot a "print line" we do it
  417.     if ![lempty [$this toPrint]] { $this line }
  418.  
  419.     $this pagelen $oldpagelen
  420.  
  421.     # if lineno < pagelen the footer was shorter than expected
  422.     # so print some empty lines to cover it
  423.     for { } \
  424.         { [$this lineno] <= [$this pagelen] } \
  425.         { $this lineno [expr [$this pageno]+1] } \
  426.         { puts "" }
  427.  
  428.     $this lineno 0
  429.  
  430.     return 1
  431.     }
  432.  
  433.     return 0
  434. }
  435.  
  436.  
  437. ###########################################################################
  438. #
  439. #    Public methods, implemented as procedures.
  440. #
  441. ###########################################################################
  442.  
  443.  
  444. # Correct version of lrange, used as long as the bug in lrange is still there.
  445. proc Report::lrange {list start end} {
  446.     set result ""
  447.  
  448.     if { $end == "end" } { set end [expr [llength $list]-1] }
  449.  
  450.     for { set i $start } { $i <= $end } { incr i } {
  451.     lappend result [lindex $list $i]
  452.     }
  453.  
  454.     return $result
  455. }
  456.  
  457.  
  458. # Align string 's' according to 'len' and 'align' (== left|right|center|fill).
  459. proc Report::align {s len align} {
  460.     set retval ""
  461.  
  462.     if { $len <= [string length $s] } {
  463.     set retval [string range $s 0 [expr $len-1] ]
  464.     } else {
  465.     switch -exact $align {
  466.     right   { set retval [format [format "%%%ds" $len] $s] }
  467.     center  { set tmp [format [format "%%%ds" [expr ($len+\
  468.             [string length $s])/2] ] $s]
  469.           set retval [format [format "%%-%ds" $len] $tmp] }
  470.     fill    { set tmp $s
  471.           while { [string length $tmp] < $len } {
  472.             append tmp $s
  473.           }
  474.           set retval [string range $tmp 0 [expr $len-1] ] }
  475.     default { set retval [format [format "%%-%ds" $len] $s] }
  476.     }
  477.     }
  478.  
  479.     return [list $retval]
  480. }
  481.  
  482.  
  483. # Return 1 if parameter is an integer, else 0.
  484. proc Report::isInt {int} {
  485.     return [expr { [scan $int "%d%s" int rest] == 1 }]
  486. }
  487.  
  488.  
  489. # Return 1 if the first element of 'list' == 'first' and delete that first
  490. # element, else return 0.
  491. proc Report::lchkfirst {list first} {
  492.     upvar 1 $list l
  493.  
  494.     if { [lindex $l 0] == $first } {
  495.     lvarpop l
  496.     return 1
  497.     }
  498.  
  499.     return 0
  500. }
  501.  
  502.  
  503. # Return 1 if parameter is a separater, else 0.
  504. proc Report::isSeparator {args} {
  505.     set args [join $args]
  506.     return [expr {[llength $args] == 2 &&
  507.           [string length [lindex $args 0]] == 0 &&
  508.           [string length [lindex $args 1]] != 0}]
  509. }
  510.  
  511.  
  512. # Return the number of elements of the biggest subtree in 'args'.
  513. proc Report::maxelem {args} {
  514.     set args [join $args]
  515.     set retval 0
  516.     foreach i $args {
  517.     if { [Report::isSeparator $i] } { continue }
  518.     if { [llength $i] > $retval } { set retval [llength $i] }
  519.     }
  520.     return $retval
  521. }
  522.  
  523.  
  524. # Count the number of times that word "line" occurs in string 's'.
  525. proc Report::countLines {s} {
  526.     set num 0
  527.     while { [regsub "line" $s "" s] } { incr num }
  528.     return $num
  529. }
  530.  
  531.  
  532. # Expand tabs to spaces in string 's', tablength is variable, default = 8.
  533. proc Report::expandTabs {s indent {tablen 8}} {
  534.     if { $tablen < 0 } { set tablen 0 }
  535.  
  536.     # add indent spaces
  537.     set s [format [format "%%%ds%%s" $indent] "" $s]
  538.  
  539.     set pos [string first \t $s]
  540.     while { $pos != -1 } {
  541.     if { $tablen != 0 } {
  542.         set numspaces [expr $tablen-$pos%$tablen]
  543.     } else {
  544.         set numspaces 0
  545.     }
  546.  
  547.     set spaces [Report::align " " $numspaces fill]
  548.  
  549.     # join is needed otherwise spaces are enclosed in braces
  550.     regsub \t $s [join $spaces] s
  551.  
  552.     set pos [string first \t $s]
  553.     }
  554.  
  555.     # remove the indented spaces
  556.     set s [string range $s $indent end]
  557.  
  558.     return $s
  559. }
  560.  
  561.  
  562. # Return the reverse of list 'l'.
  563. proc Report::lreverse {l} {
  564.     if [lempty $l] { return {} }
  565.     set len [llength $l]
  566.     return [concat [list [lindex $l [incr len -1]] \
  567.            [Report::lreverse [Report::lrange $l 0 [incr len -1]]]]]
  568. }
  569.