Commit:    7f972b820fb9e0f5dd8f37cb2c39d941480e8d29
Author:    v-maf...@microsoft.com <a...@a.com>         Fri, 2 Dec 2011 21:57:09 
-0800
Parents:   0c5cddd28769cf96b3d11073e0d89fecdf56c384
Branches:  master

Link:       
http://git.php.net/?p=pftt2.git;a=commitdiff;h=7f972b820fb9e0f5dd8f37cb2c39d941480e8d29

Log:
improvements to Diff Engine support searching, managing and filtering Diff 
files from PHPT results


Former-commit-id: 01c5eb16c74924b591e2947a456f48c8ac4e6fc5

Changed paths:
  A  PFTT/lib/diff/engine.rb
  A  PFTT/lib/diff/zd.rb
  A  PFTT/lib/diff/zd/all_test_cases.rb
  M  PFTT/lib/host.rb
  M  PFTT/lib/host/local.rb
  M  PFTT/lib/host/remote/psc/client.rb
  M  PFTT/lib/host/remote/psc/recovery_manager.rb
  M  PFTT/lib/host/remote/psc/remote_host.rb
  M  PFTT/lib/host/remote/ssh.rb
  M  PFTT/lib/middleware.rb
  M  PFTT/lib/middleware/cli.rb
  M  PFTT/lib/test/case/phpt.rb
  M  PFTT/lib/test/result/phpt.rb
  M  PFTT/lib/test/runner/stage/phpt/package.rb
  M  PFTT/lib/test/runner/stage/phpt/upload.rb
  M  PFTT/lib/tracing/context.rb
  M  PFTT/lib/tracing/prompt/diff.rb
  M  PFTT/lib/util/package.rb
  A  PFTT/src/se/datadosen/component/RiverLayout.class
  A  PFTT/src/se/datadosen/component/RiverLayout.java
  A  PFTT/src/se/datadosen/component/Ruler.class

diff --git a/PFTT/lib/diff/engine.rb b/PFTT/lib/diff/engine.rb
new file mode 100644
index 0000000..e69de29
diff --git a/PFTT/lib/diff/zd.rb b/PFTT/lib/diff/zd.rb
new file mode 100644
index 0000000..d5b1b5d
--- /dev/null
+++ b/PFTT/lib/diff/zd.rb
@@ -0,0 +1,108 @@
+
+module Diff
+  module ZD
+    
+class BaseZD
+  
+  attr_reader :diff
+  
+  def initialize(diff)
+    unless [:expected, :actual, :added, :removed, :added_and_removed, 
:org_expected, :org_actual].include?(diff)
+      raise ArgumentError, diff
+    end
+    
+    @diff = diff
+  end
+  
+  def zd_label
+    "#{level_label()} #{diff_label()}"
+  end
+  
+  def diff_label
+    # Override
+  end
+  
+  def sym
+    # Override
+  end
+  
+  def has_sym?
+    sym.length > 0
+  end
+  
+  def level_label
+    # Override
+  end
+  
+  def level
+    # Override
+  end
+  
+  def is_diff?(o_diff)
+    diff()==o_diff
+  end
+  
+  def is_level?(o_level)
+    level() == o_level
+  end
+  
+  def save_chunk_replacements
+  end
+            
+  def load_chunk_replacements
+  end
+            
+  def save_diff
+  end
+            
+  def delete(id)
+  end
+            
+  def add(id)
+  end
+            
+  def pass(id)
+  end
+            
+  def change(id, to)
+  end
+            
+  def find(needle)
+    # Override
+  end
+  
+  def iterate
+    # Override
+    # DiffIterator.new
+  end
+            
+end # class ZD
+
+class DiffIterator
+  
+  def zd
+  end
+  
+  def has_next?
+  end
+  
+  def next
+  end
+  
+  def delete
+  end
+              
+  def add
+  end             
+              
+  def pass
+  end
+              
+  def change(to)
+  end
+  
+end # class DiffIterator
+
+    
+  end # module ZD
+end # module Diff
\ No newline at end of file
diff --git a/PFTT/lib/diff/zd/all_test_cases.rb 
b/PFTT/lib/diff/zd/all_test_cases.rb
new file mode 100644
index 0000000..231204b
--- /dev/null
+++ b/PFTT/lib/diff/zd/all_test_cases.rb
@@ -0,0 +1,262 @@
+
+module Diff
+  module ZD
+    module AllTestCases
+      
+class BaseAllTestCases < BaseZD
+  
+  def level
+    :all
+  end
+  
+  def level_label
+    'All'
+  end
+  
+  def find(needle)
+    down_needle = needle.downcase
+    up_needle = needle.upcase
+    
+    dirs().each do |dir|
+      files().each do |file_ext|
+        Dir.glob("#{dir}/**/*#{file_ext}") do |file_name|
+          # TODO document
+          #
+          unless accept_file(dir, Host.sub(dir, file_name))
+            next
+          end
+      
+          # search file for needle
+          IO.readlines(file_name).each do |line|
+            if true#accept_line(line)
+              if check_line(line, down_needle, up_needle)
+              
+                # TODO report match
+                puts line
+          
+              end
+            end
+          end
+        end
+      end
+    end
+  end # def find
+  
+  protected
+  
+  def accept_file(dir, file_name)
+    # can Override
+    true
+  end
+  
+  def dirs
+    # Override
+    []
+  end
+  
+  def check_line(line, down_needle, up_needle)
+    return ( line.downcase.include?(down_needle) or 
line.upcase.include?(up_needle) )
+  end
+  
+  def files
+    if [:added, :removed, :added_and_removed].include?(diff())
+      return ['.diff']
+    elsif [:org_expected, :expected].include?(diff())
+      return ['.expectf'] # TODO complete list?
+    elsif [:actual, :org_actual].include?(diff())
+      return ['.result']
+    else
+      return [] # shouldn't happen
+    end
+  end
+    
+  def accept_line(line)
+    case diff()
+    when :added
+      return line.starts_with?('+')
+    when :removed
+      return line.starts_with?('-')
+    when :added_and_removed
+      return ( line.starts_with?('+') or line.starts_with?('-') )
+    else
+      # search any line of Expected output, actual output, etc... files
+      return true
+    end
+  end
+  
+end # class BaseAllTestCases
+
+class BaseSingleRun < BaseAllTestCases
+  attr_reader :dir, :rev
+        
+  def initialize(diff, dir, rev=nil)
+    super(diff)
+    @dir = dir
+    @rev = rev
+  end
+        
+  def dirs
+    [@dir]
+  end
+  
+  def sym
+    ''
+  end
+          
+end # class SingleRun
+
+class BaseRun < BaseSingleRun
+  def diff_label
+    if @rev
+      @rev # LESSON BaseRun and TestRun DRY not normalized
+    else
+      'Base'
+    end
+  end
+end
+
+class TestRun < BaseSingleRun
+  def diff_label
+    if @rev
+      @rev
+    else
+      'Test'
+    end
+  end
+end
+
+class Base2Runs < BaseAllTestCases
+  attr_reader :base_dir, :test_dir, :base_rev, :test_rev
+  
+  def initialize(diff, base_dir, test_dir, base_rev=nil, test_rev=nil)
+    super(diff)
+    @base_dir = base_dir
+    @test_dir = test_dir
+    @base_rev = base_rev
+    @test_rev = test_rev
+  end
+    
+  protected
+  
+  def dirs
+    [@base_dir, @test_dir]
+  end
+  
+  def get_contents(dir, file_name)
+    file_name = File.join(dir, file_name)
+    unless File.exist?(file_name)
+      return nil
+    end
+    
+    # TODO should only have to read a file in once
+    # comparing arrays of lines instead of the file contents as a string
+    # eliminates comparison problems due to line ending characters
+    IO.readlines(file_name)
+  end
+  
+end # class Base2Runs
+
+class BaseTestRun < Base2Runs
+  def diff_label
+    if @base_rev and @test_rev
+      "#{@base_rev}#{sym()}#{@test_rev}"
+    else
+      "Base#{sym()}Test"
+    end
+  end
+end
+
+class TestMinusBase < Base2Runs
+  # Test-Base => only results from Test not matching those in Base run
+        
+  def diff_label
+    if @base_rev and @test_rev
+      "#{@test_rev}#{sym()}#{@base_rev}"
+    else
+      "Test#{sym()}Base"
+    end
+  end
+  
+  def sym
+    '-'
+  end
+  
+  protected
+          
+  def accept_file(dir, file_name)
+    if dir == @base_dir
+      test_file = get_contents(@test_dir, file_name)
+      base_file = get_contents(@base_dir, file_name)
+      return ( base_file.nil? or test_file != base_file )
+    end
+    return false
+  end
+        
+end # class TestMinusBase
+
+class BaseMinusTest < BaseTestRun
+  # Base-Test => only results from Test not matching those in Base run
+              
+  def sym
+    '-'
+  end
+  
+  protected
+                
+  def accept_file(dir, file_name)
+    if dir == @test_dir
+      base_file = get_contents(@base_dir, file_name)
+      test_file = get_contents(@test_dir, file_name)
+      return ( test_file.nil? or test_file != base_file )
+    end
+    return false
+  end
+              
+end # class BaseMinusTest
+      
+class BasePlusTest < BaseTestRun
+  # Base+Test => all results from Base and Test (duplicates removed)
+              
+  def sym
+    '+'
+  end
+  
+  protected
+                
+  def accept_file(dir, file_name)
+    if dir == @test_dir
+      # TODO == (no duplicate)
+      test_file = get_contents(@test_dir, file_name)
+      base_file = get_contents(@base_dir, file_name)
+      return ( base_file.nil? or test_file == base_file )
+    end
+    return false
+  end
+              
+end # class BasePlusTest
+      
+class BaseEqTest < BaseTestRun
+  # Base=Test => only results from Base and Test that match
+              
+  def sym
+    '='
+  end
+  
+  protected
+                
+  def accept_file(dir, file_name)
+    if dir == @test_dir
+      # TODO ==
+      test_file = get_contents(@test_dir, file_name)
+      base_file = get_contents(@base_dir, file_name)
+      return ( base_file.nil? or test_file != base_file )
+    end
+    return false
+  end
+              
+end # class BaseEqTest
+
+    
+    end # module AllTestCases
+  end
+end
diff --git a/PFTT/lib/host.rb b/PFTT/lib/host.rb
index b66cb09..0ac446b 100644
--- a/PFTT/lib/host.rb
+++ b/PFTT/lib/host.rb
@@ -17,6 +17,17 @@ module Host
       @@hosts||={}
     end
   end
+  
+  def self.sub base, path
+    if path.starts_with?(base)
+      return path[base.length..path.length]
+    end
+    return path
+  end
+  
+  def self.join *path_array
+    path_array.join('/')
+  end
 
   def self.administrator_user (platform)
     if platform == :windows
@@ -44,11 +55,15 @@ module Host
     
   def self.to_windows_path!(path)
     # remove \\ from path too. they may cause problems on some Windows SKUs
-    return path.gsub!('/', '\\').gsub!('\\\\', '\\').gsub!('\\\\', '\\')
+    path.gsub!('/', '\\')
+    path.gsub!('\\\\', '\\')
+    path.gsub!('\\\\', '\\')
   end
             
   def self.to_posix_path!(path)
     path.gsub!('\\', '/')
+    path.gsub!('//', '/')
+    path.gsub!('//', '/')
   end
   
   def self.fs_op_to_cmd(fs_op, src, dst)
@@ -109,27 +124,23 @@ module Host
     end
     
     def no_trailing_slash(path)
-      if path.ends_with?('/') or path.ends_with?('\\')
-        path = path[0..path.length-1]
-      end
-      return path
+      Host.no_trailing_slash(path)
     end
     
     def to_windows_path(path)
-      # remove \\ from path too. they may cause problems on some Windows SKUs
-      return path.gsub('/', '\\').gsub('\\\\', '\\').gsub('\\\\', '\\')
+      Host.to_windows_path(path)
     end
         
     def to_posix_path(path)
-      return path.gsub('\\', '/')
+      Host.to_posix_path(path)
     end
     
     def to_windows_path!(path)
-      path.gsub!('/', '\\')
+      Host.to_windows_path!(path)
     end
             
     def to_posix_path!(path)
-      path.gsub!('\\', '/')
+      Host.to_posix_path!(path)
     end
     
     def number_of_processors(ctx=nil)
@@ -212,14 +223,14 @@ module Host
         end
       end
       
-      cmd!(case
-      when posix? then %Q{cp -R "#{from}" "#{to}"}
-      else
-        to_windows_path!(from)
-        to_windows_path!(to)
-              
-        %Q{xcopy /Y /s /i /q "#{from}" "#{to}"}
-      end, ctx)
+      if !directory?(from)
+        copy_file(from, to, ctx, mk)
+        return
+      elsif mk
+        mkdir(File.dirname(to), ctx)
+      end
+      
+      copy_cmd(from, to, ctx)
     end
     
     def move from, to, ctx
@@ -229,17 +240,12 @@ module Host
         end
       end
       
-      from = no_trailing_slash(from)
-      to = no_trailing_slash(to)
+      if !directory?(from)
+        move_file(from, to, ctx)
+        return
+      end
       
-      cmd!(case
-      when posix? then %Q{mv "#{from}" "#{to}"}
-      else
-        to_windows_path!(from)
-        to_windows_path!(to)
-        
-        %Q{move "#{from}" "#{to}"}        
-      end, ctx)
+      move_cmd(from, to, ctx)
     end
     
     def time=(time)
@@ -290,6 +296,9 @@ module Host
     end
     
     def userprofile(ctx=nil)
+      unless @_userprofile.nil?
+        return @_userprofile
+      end
       p = nil
       if posix?
         p = env_value('HOME', ctx)
@@ -298,44 +307,50 @@ module Host
       end
       
       if exists?(p, ctx)
-        return p
+        return @_userprofile = p
       else
-        return nil
+        return @_userprofile = systemdrive(ctx)
       end
     end
     
     def appdata(ctx=nil)
+      unless @_appdata.nil?
+        return @_appdata
+      end
       if posix?
         p = env_value('HOME', ctx)
         if p and exists?(p, ctx)
-          return p
+          return @_appdata = p
         end  
       else
         p = env_value('USERPROFILE', ctx)
         if p
           q = p + '\\AppData\\'
           if exists?(q, ctx)
-            return q
+            return @_appdata = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_appdata = q
           end
         end
       end
       
-      return systemdrive(ctx)
+      return @_appdata = systemdrive(ctx)
     end # def appdata
     
     def appdata_local(ctx=nil)
+      unless @_appdata_local.nil?
+        return @_appdata_local
+      end
       if posix?
         p = env_value('HOME', ctx)
         if p
           q = p + '/PFTT'
           if exists?(q, ctx)
-            return q
+            return @_appdata_local = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_appdata_local = q
           end
         end
       else
@@ -343,34 +358,37 @@ module Host
         if p
           q = p + '\\AppData\\Local'
           if exists?(q, ctx)
-            return q
+            return @_appdata_local = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_appdata_local = q
           end
         end
       end
             
-      return systemdrive(ctx)
+      return @_appdata_local = systemdrive(ctx)
     end # def appdata_local
     
     def tempdir ctx
+      unless @_tempdir.nil?
+        return @_tempdir
+      end
       if posix?
         p = '/usr/local/tmp'
         q = p + '/PFTT'
         if exists?(q, ctx)
-          return q
+          return @_tempdir = q
         elsif exists?(p, ctx)
           mkdir(q, ctx)
-          return q
+          return @_tempdir = q
         end
         p = '/tmp'
         q = p + '/PFTT'
         if exists?(q, ctx)
-          return q
+          return @_tempdir = q
         elsif exists?(p, ctx)
           mkdir(q, ctx)
-          return q
+          return @_tempdir = q
         end
       else
         # try %TEMP%\\PFTT
@@ -378,10 +396,10 @@ module Host
         if p
           q = p + '\\PFTT'
           if exists?(q, ctx)
-            return q
+            return @_tempdir = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_tempdir = q
           end
         end
         
@@ -390,10 +408,10 @@ module Host
         if p
           q = p + '\\PFTT'
           if exists?(q, ctx)
-            return q
+            return @_tempdir = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_tempdir = q
           end
         end
         
@@ -403,10 +421,10 @@ module Host
           p = '\\AppData\\Local\\Temp\\' + p
           q = p + '\\PFTT'
           if exists?(q, ctx)
-            return q
+            return @_tempdir = q
           elsif exists?(p, ctx)
             mkdir(q, ctx)
-            return q
+            return @_tempdir = q
           end
         end
         
@@ -414,15 +432,15 @@ module Host
         p = systemdrive(ctx)+'\\temp'
         q = p + '\\PFTT'
         if exists?(q, ctx)
-          return q
+          return @_tempdir = q
         elsif exists?(p, ctx)
           mkdir(q, ctx)
-          return q
+          return @_tempdir = q
         end
         
       end
                   
-      return systemdrive(ctx)
+      return @_tempdir = systemdrive(ctx)
     end # def tempdir
     
     alias :tmpdir :tempdir
@@ -592,7 +610,7 @@ module Host
     #     the current directory of the command to run
     #  :debug   true|false
     #     runs the command with the host's debugger. if host has no debugger 
installed, command will be run normally
-    #  :stdin   ''
+    #  :stdin_data   ''
     #     feeds given string to the commands Standard Input
     #  :null_output true|false
     #     if true, returns '' for both STDOUT and STDERR. (if host is remote, 
STDOUT and STDERR are not sent over
@@ -606,6 +624,7 @@ module Host
     #  :success_exit_code int, or [int] array   default=0
     #     what exit code(s) defines success
     #     note: this is ignored if Command::Expected is used (which evaluates 
success internally)
+    # LATER :elevate and :sudo support for windows and posix
     # other options are silently ignored
     #
     #
@@ -676,7 +695,7 @@ module Host
       
       # Windows will always have a C:\ even if the C:\ drive is not the 
systemdrive
       # posix doesn't have C: D: etc... drives
-      @is_windows = exist?('C:\\', ctx)
+      @is_windows = _exist?('C:\\', ctx)
       
       if ctx
         # cool stuff: allow user to override OS detection
@@ -687,7 +706,6 @@ module Host
     end
 
     def posix?(ctx=nil)
-      return false # TODO TUE
       unless @posix.nil?
         return @posix
       end
@@ -696,7 +714,7 @@ module Host
       end
       ctx = ctx==nil ? nil : 
ctx.new(Tracing::Context::Dependency::Detect::OS::Type)
       
-      @posix = exist?('/usr', ctx)
+      @posix = _exist?('/usr', ctx)
         
       if ctx
         @posix = ctx.check_os_type_detect(:posix, @posix)
@@ -707,13 +725,32 @@ module Host
 
     def make_absolute! *paths
       paths.map do |path|
+        # support for Windows drive letters
+        # (if drive letter present, path is absolute)
         return path if !posix? && path =~ /\A[A-Za-z]:\//
-        return path if path =~ /\A[A-Za-z]:\//  
+        return path if path =~ /\A[A-Za-z]:\//
+        #  
         
         path.replace( File.absolute_path( path, cwd() ) )
         path
       end
     end
+    
+    def exist? path, ctx=nil
+      make_absolute! path
+      
+      if ctx
+        ctx.fs_op1(self, :exist, path) do |path|
+          return exist?(path, ctx)
+        end
+      end
+      
+      _exist?(path, ctx)
+    end
+    
+    alias :exists? :exist?
+    alias :exist :exist?
+    alias :exists :exist?
 
     def format_path path, ctx=nil
       case
@@ -753,10 +790,14 @@ module Host
       else
         return '/'
       end
+    end 
+    
+    def sub base, path
+      Host.sub(base, path)
     end
     
     def join *path_array
-      path_array.join(separator)
+      Host.join(path_array)
     end
     
     def upload_if_not(local, remote, ctx)
@@ -832,13 +873,16 @@ module Host
       _mkdir(path, ctx) unless directory? path
     end
 
-    def mktmpdir path, ctx
-      ctx = ctx==nil ? nil : ctx.new(SystemSetup::TempDirectory)
+    def mktmpdir ctx, path=nil, suffix=''
+      ctx = ctx==nil ? nil : 
ctx.new(Tracing::Context::SystemSetup::TempDirectory)
+      unless path
+        path = tempdir(ctx)
+      end
       
       make_absolute! path
       tries = 10
       begin
-        dir = File.join( path, String.random(4) )
+        dir = File.join( path, String.random(6)+suffix )
         raise 'exists' if directory? dir
         mkdir(dir, ctx)
       rescue
@@ -865,6 +909,9 @@ module Host
         raise $!
       end
     end
+    
+    alias :mktempfile :mktmpfile
+    alias :mktempdir :mktmpdir
 
     def sane? path
       make_absolute! path
@@ -894,6 +941,7 @@ module Host
     end
     
     def name ctx=nil
+      return 'OI1-PHP-FUNC-15' # TODO TUE
       unless @_name
         # find a name that other hosts on the network will use to reference 
localhost
         if windows?(ctx)
@@ -907,6 +955,31 @@ module Host
     
     protected
     
+    def move_cmd(from, to, ctx)
+      from = no_trailing_slash(from)
+      to = no_trailing_slash(to)
+      
+      cmd!(case
+      when posix? then %Q{mv "#{from}" "#{to}"}
+      else
+        from = to_windows_path(from)
+        to = to_windows_path(to)
+        
+        %Q{move "#{from}" "#{to}"}        
+      end, ctx)
+    end
+    
+    def copy_cmd(from, to, ctx)
+      cmd!(case
+      when posix? then %Q{cp -R "#{from}" "#{to}"}
+      else
+        from = to_windows_path(from)
+        to = to_windows_path(to)
+                      
+        %Q{xcopy /Y /s /i /q "#{from}" "#{to}"}
+      end, ctx)
+    end
+    
     def _exec in_thread, command, opts, ctx, block
       @cwd = nil # clear cwd cache
       
@@ -940,7 +1013,7 @@ module Host
       end
       #
       
-      if !opts.has_key?(:max_len) or opts[:max_len].is_a?(Integer) or 
opts[:max_len] < 0
+      if !opts.has_key?(:max_len) or !opts[:max_len].is_a?(Integer) or 
opts[:max_len] < 0
         opts[:max_len] = 128*1024 
       end
       
@@ -949,8 +1022,6 @@ module Host
         command = debug_wrap(command)
       end
       
-      stdin_data = (opts.has_key?(:stdin))? opts[:stdin] : nil 
-        
       if in_thread
         Thread.start do
           ret = _exec_thread(command, opts, ctx, block)
@@ -1042,7 +1113,7 @@ module Host
       return [stdout, stderr, exit_code]
     end # def _exec_thread
     
-    attr_accessor :_systeminfo, :_name, :_osname, :_systeminfo, :_systemdrive, 
:_systemroot, :posix, :is_windows
+    attr_accessor :_systeminfo, :_name, :_osname, :_systeminfo, :_systemdrive, 
:_systemroot, :posix, :is_windows, :_appdata, :_appdata_local, :_tempdir, 
:userprofile
     
     def clone(clone)
       clone._systeminfo = @systeminfo
@@ -1054,6 +1125,10 @@ module Host
       clone.posix = @posix
       clone.is_windows = @is_windows
       clone._name = @_name
+      clone._appdata = @_appdata
+      clone._appdata_local = @_appdata_local 
+      clone._tempdir = @_tempdir
+      clone._userprofile = @_userprofile
       clone
     end
     
diff --git a/PFTT/lib/host/local.rb b/PFTT/lib/host/local.rb
index 5f9fe67..200c5ad 100644
--- a/PFTT/lib/host/local.rb
+++ b/PFTT/lib/host/local.rb
@@ -2,6 +2,7 @@
 require 'java'
 include_class 'java.util.Timer'
 include_class 'java.util.TimerTask'
+require 'FileUtils'
 
 module Host
   class Local < HostBase
@@ -102,20 +103,6 @@ module Host
     alias :upload :copy
     alias :download :copy
     
-    def exist? file, ctx=nil
-      if ctx
-        ctx.fs_op1(self, :exist, file) do |file|
-          return exist?(file, ctx)
-        end
-      end
-      make_absolute! file
-      File.exist? file
-    end
-    
-    alias :exists? :exist?
-    alias :exist :exist?
-    alias :exists :exist?
-
     def directory? path, ctx=nil
       if ctx
         ctx.fs_op1(self, :is_dir, path) do |path|
@@ -154,8 +141,29 @@ module Host
       false
     end
     
+    def mtime(file)
+      # TODO implement for ssh
+      File.mtime(file).to_i
+    end
+    
     protected
     
+    def _exist?(file, ctx)
+      File.exist? file
+    end
+    
+    def move_file(from, to, ctx)
+      FileUtils.move_file(from, to)
+    end
+    
+    def copy_file(from, to, ctx, mk)
+      if mk
+        mkdir(File.dirname(to), ctx)
+      end
+                  
+      FileUtils.copy_file(from, to)
+    end
+    
     class LocalExecHandle < ExecHandle
       def initialize(stdout, stderr, process=nil)
         @stdout = stdout
diff --git a/PFTT/lib/host/remote/psc/client.rb 
b/PFTT/lib/host/remote/psc/client.rb
index efba21c..fb7fbd5 100644
--- a/PFTT/lib/host/remote/psc/client.rb
+++ b/PFTT/lib/host/remote/psc/client.rb
@@ -25,8 +25,8 @@ class Client < BaseRemoteHostAndClient
             
       #
       # TODO temp do we really need to do this anymore?
-      @host.exec!('taskkill /im:jruby.exe /f', ctx)
-      @host.exec!('taskkill /im:jruby.exe /f', ctx)
+      @host.exec!('taskkill /im:jruby.exe /f', ctx, {:success_exit_code=>128})
+      @host.exec!('taskkill /im:jruby.exe /f', ctx, {:success_exit_code=>128})
     rescue 
       puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect
     end
@@ -97,30 +97,21 @@ class Client < BaseRemoteHostAndClient
       #      end
             
         #
+        # TODO
+        send_php(PhpBuild.new('g:/php-sdk/PFTT-PHPS/'+$php_build_path))
         @running = true
         @wait_lock.synchronize do
               begin
+                
+                
                 # set JAVA_HOME=\\jruby-1.6.5\\jre
-                  @host.exec!('_pftt_hc.cmd ""', 
Tracing::Context::Phpt::RunHost.new(), 
{:chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle|
-                  if handle.has_stderr?
-                    recv_ssh_block(handle.read_stderr)
-                  end
-                end
+                do_it()
+                
               rescue 
                 puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect
                 
-                # TODO be able to resume: rerun _pftt_hc, but limit the list 
of tests to those that results weren't received for
+                do_it() #again
                 
-                # retry
-                begin
-                  @host.exec!('_pftt_hc.cmd ""', 
Tracing::Context::Phpt::RunHost.new(), 
{:chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle|
-                                    if handle.has_stderr?
-                                      recv_ssh_block(handle.read_stderr)
-                                    end
-                                  end
-                        rescue
-                  puts @host.name+" "+$!.inspect+" "+$!.backtrace.inspect
-                  end
               ensure
                 @running = false
                 @started = false
@@ -155,6 +146,13 @@ class Client < BaseRemoteHostAndClient
             end
           end # thread
         end
+        def do_it
+          @host.exec!(@host.systemdrive+'/php-sdk/1/pftt/_pftt_hc.cmd', 
Tracing::Context::Phpt::RunHost.new(), {:stdin_data=>@stdin, :max_len=>0, 
:chdir=>@host.systemdrive+'\\php-sdk\\1\\PFTT', :stdin=>@stdin}) do |handle|
+            if handle.has_stderr?
+              recv_ssh_block(handle.read_stderr)
+            end
+          end
+        end
         def hosted_client_failed
           # terminate _pftt_hc.rb if its still running
           @host.close
@@ -166,6 +164,7 @@ class Client < BaseRemoteHostAndClient
         def send_full_block(block)
           block += "<Boundary>\n"
           
+          puts block
           @stdin += block
         end
         def dispatch_recvd_xml(xml)
@@ -194,7 +193,7 @@ class Client < BaseRemoteHostAndClient
           end
         end
         def send_start
-          send_xml({}, 'start')
+          # TODO TUE send_xml({}, 'start')
         end
         def send_stop
           send_xml({}, 'stop')
diff --git a/PFTT/lib/host/remote/psc/recovery_manager.rb 
b/PFTT/lib/host/remote/psc/recovery_manager.rb
index eeeba62..68bedee 100644
--- a/PFTT/lib/host/remote/psc/recovery_manager.rb
+++ b/PFTT/lib/host/remote/psc/recovery_manager.rb
@@ -8,12 +8,12 @@ module Host
 class HostRecoveryManager
     def initialize(hosts, php, middleware, scn_set)
   #hosts = [hosts.first]
-      php = 
PhpBuild.new('C:/php-sdk/builds/php-5.4-nts-windows-vc9-x86-r319120')
+      php = PhpBuild.new('C:\\php-sdk\\builds\\php-5.4.0rc2-nts-Win32-VC9-x86')
 
       threads = []
       hosts.each do |host|
 
-        file_name = 'C:/php-sdk/PFTT-PSCC/r319120/'+host.name
+        file_name = 'C:/php-sdk/PFTT-PSCC/540rc2'# r319120/'+host.name
         if File.exists?(file_name)
           #t = Thread.start do
             recover(file_name, host, php, middleware, scn_set)
@@ -136,13 +136,17 @@ def to_simple(raw_xml)
                             
           when 4#Xml.TEXT
             text = parser.getText();
-      
+#      if text.include?('+Warning: strtotime()')
+#        puts text
+#      end
             if t['text']
               t['text'] += text
             else
               t['text'] = text
             end
-            
+  if text.include?('+Warning: strtotime()')
+    puts t.inspect
+  end
           when 3#Xml.END_TAG
             if s.length > 1
               s.pop # double pop
diff --git a/PFTT/lib/host/remote/psc/remote_host.rb 
b/PFTT/lib/host/remote/psc/remote_host.rb
index ce6a4b2..4c9cd4f 100644
--- a/PFTT/lib/host/remote/psc/remote_host.rb
+++ b/PFTT/lib/host/remote/psc/remote_host.rb
@@ -39,7 +39,10 @@ class RemoteHost < BaseRemoteHostAndClient
           
     while @run
       line = STDIN.gets()
-      recv_ssh_block(line)
+      # TODO fucked up recv_ssh_block(line)
+      xml = to_simple(line)
+      dispatch_recvd_xml(xml)
+      dispatch_recvd_xml({'@msg_type'=>'start'}) # TODO
     end
   end
   def send_result(result)
diff --git a/PFTT/lib/host/remote/ssh.rb b/PFTT/lib/host/remote/ssh.rb
index 7b16a89..942104a 100644
--- a/PFTT/lib/host/remote/ssh.rb
+++ b/PFTT/lib/host/remote/ssh.rb
@@ -152,35 +152,6 @@ module Host
       end
     end
     
-    def exist? path, ctx=nil
-      if ctx
-        ctx.fs_op1(self, :exist, path) do |path|
-          return exist?(path, ctx)
-        end
-      end
-      
-      # see T_* constants in Net::SFTP::Protocol::V01::Attributes
-      # v04 and v06 attributes don't have a directory? or file? method (which 
v01 does)
-      # doing it this way will work for all 3 (v01, v04, v06 attributes)
-      begin
-        a = wait_for(sftp(ctx).stat(path), :attrs)
-        # types: regular(1), directory(2), symlink, special, unknown, socket, 
char_device, block_device, fifo
-        #        # if type is any of those, then path exists
-        if a.nil?
-          return false
-        else
-          return ( a.type > 0 and a.type < 10 )
-        end
-      rescue
-        if_closed
-        return false
-      end
-    end
-    
-    alias :exists? :exist?
-    alias :exist :exist?
-    alias :exists :exist?
-
     def list(path, ctx)
       if ctx
         ctx.fs_op1(self, :list, path) do |path|
@@ -267,11 +238,11 @@ module Host
       # LATER remove this gotcha/rule/limitation (implement using 
File.basename)
       #
       if windows?
-        to_windows_path!(local_file)
-        to_windows_path!(remote_path)
+        local_file = to_windows_path(local_file)
+        remote_path = to_windows_path(remote_path)
       else
-        to_posix_path!(local_file)
-        to_posix_path!(remote_path)
+        local_file = to_posix_path(local_file)
+        remote_path = to_posix_path(remote_path)
       end
       #
       
@@ -318,6 +289,38 @@ module Host
 
     protected
     
+    def _exist?(path, ctx)
+      # see T_* constants in Net::SFTP::Protocol::V01::Attributes
+      # v04 and v06 attributes don't have a directory? or file? method (which 
v01 does)
+      # doing it this way will work for all 3 (v01, v04, v06 attributes)
+      begin
+        a = wait_for(sftp(ctx).stat(path), :attrs)
+        # types: regular(1), directory(2), symlink, special, unknown, socket, 
char_device, block_device, fifo
+        #        # if type is any of those, then path exists
+        if a.nil?
+          return false
+        else
+          return ( a.type > 0 and a.type < 10 )
+        end
+      rescue
+        if_closed
+        return false
+      end
+    end
+    
+    def move_file(from, to, ctx)
+      move_cmd(from, to, ctx)
+    end
+    
+    def copy_file(from, to, ctx, mk)
+      to = File.dirname(to)
+      if mk
+        mkdir(to, ctx)
+      end
+      
+      copy_cmd(from, to, ctx)
+    end
+    
     # for #clone()
     attr_accessor :rebooting, :rebooting_reconnect_tries
     
@@ -355,11 +358,21 @@ module Host
       def write_stdin(stdin_data)
         @channel.send_data(stdin_data)
       end
+      def has_stderr?
+        @stderr.length > 0
+      end
+      def has_stdout?
+        @stdout.length > 0
+      end
       def read_stderr
-        @stderr
+        x = @stderr
+        @stderr = ''
+        return x
       end
       def read_stdout
-        @stdout
+        x = @stdout
+        @stdout = ''
+        return x
       end
       def post_stdout(data)
         @stdout = data
@@ -388,7 +401,7 @@ module Host
       stdout, stderr = '',''
       stdin_data = opts[:stdin_data]
       exit_code = -254 # assume error unless success
-      
+            
       ssh(ctx).open_channel do |channel|
         channel.exec(command) do |channel, success|
           unless success
@@ -404,7 +417,7 @@ module Host
             if stdin_data
               ch.send_data(stdin_data)
               stdin_data = nil
-            end
+            end            
             if block
               sh.post_stdout(data)
               block.call(sh)
@@ -470,11 +483,11 @@ module Host
 
     def _delete path, ctx
       if windows?
-        to_windows_path!(path)
+        path = to_windows_path(path)
         
         cmd!("DEL /Q /F \"#{path}\"", ctx)
       else
-        to_posix_path!(path)
+        path = to_posix_path(path)
         
         exec!("rm -rf \"#{path}\"", ctx)
       end
diff --git a/PFTT/lib/middleware.rb b/PFTT/lib/middleware.rb
index 734ae25..06c2331 100644
--- a/PFTT/lib/middleware.rb
+++ b/PFTT/lib/middleware.rb
@@ -98,14 +98,19 @@ module Middleware
       
       # if $force_deploy, make a new directory! otherwise, reuse existing 
directory (for quick manual testing can't take the time
       #          to copy everything again)
-      @deployed_php ||= @host.join(deploy_to, ( @php_build[:version] + 
((@php_build[:threadsafe])?'-TS':'-NTS') + ( $force_deploy ? 
'_'+String.random(4) : '' ) ) )
+      @deployed_php ||= @php_build.path # TODO @host.join(deploy_to, ( 
@php_build[:version] + ((@php_build[:threadsafe])?'-TS':'-NTS') + ( 
$force_deploy ? '_'+String.random(4) : '' ) ) )
       
       #    
 #  TODO TUE    if $force_deploy or not File.exists?(php_binary()) or 
File.mtime(@php_build.path) >= File.mtime(php_binary())
         unless $hosted_int
         puts "PFTT:deploy: uploading... "+@deployed_php
-      host.upload_force("c:/php-sdk/5.4.0beta2-NTS.7z", 
host.systemdrive(ctx)+'/5.4.0beta2-NTS.7z', false, ctx) # critical: false
+        
+        zip_name = package_php_build(Host::Local.new(), 
'c:/php-sdk/builds/'+$php_build_path)
+        
+          # TODO package_php_build
+      host.upload_force(zip_name, host.systemdrive(ctx)+'/5.4.0beta2-NTS.7z', 
false, ctx) # critical: false
                 
+      # TODO check if build already on remote host (so compression and upload 
can be skipped)
       sd = host.systemdrive
           ctx = Tracing::Context::Dependency::Check.new # TODO ctx.new
       host.delete_if("#{sd}\\php-sdk\\PFTT-PHPs\\5.4.0beta2-NTS", ctx)
diff --git a/PFTT/lib/middleware/cli.rb b/PFTT/lib/middleware/cli.rb
index 622283b..8189aae 100644
--- a/PFTT/lib/middleware/cli.rb
+++ b/PFTT/lib/middleware/cli.rb
@@ -20,10 +20,10 @@ module Middleware
         # phpt thread) 
         config_ctx = Tracing::Context::Middleware::Config.new()
         
-        @host.exec!('REG DELETE "HKLM\\Software\\Microsoft\\Windows 
NT\\CurrentVersion\\AeDebug" /v Debugger /f', config_ctx)
+        # TODO @host.exec!('REG DELETE "HKLM\\Software\\Microsoft\\Windows 
NT\\CurrentVersion\\AeDebug" /v Debugger /f', config_ctx)
         # disable Hard Error Popup Dialog boxes (will still get this even 
without a debugger)
         # see http://support.microsoft.com/kb/128642
-        @host.exec!('REG ADD 
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows" /v ErrorMode /d 2 /t 
REG_DWORD /f', config_ctx)
+        # TODO @host.exec!('REG ADD 
"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Windows" /v ErrorMode /d 2 /t 
REG_DWORD /f', config_ctx)
           
         # disable windows firewall
         # LATER edit firewall rules instead (what if on public network, ex: 
Azure)
diff --git a/PFTT/lib/test/case/phpt.rb b/PFTT/lib/test/case/phpt.rb
index 8bc78c7..cdc3ad7 100644
--- a/PFTT/lib/test/case/phpt.rb
+++ b/PFTT/lib/test/case/phpt.rb
@@ -497,7 +497,9 @@ class Phpt
       @parts[:file]=@parts.delete(:fileeof).gsub(/\r?\n\Z/,'')
     elsif @parts.has_key? :file_external
       context = File.dirname( @phpt_path )
-      external_file = File.absolute_path( 
@parts.delete(:file_external).gsub(/\r?\n\Z/,''), context ) 
+      external_file = File.absolute_path( 
@parts.delete(:file_external).gsub(/\r?\n\Z/,''), context )
+      # TODO TUE c:/abc/ 
+      # ('c:/abc/'+external_file).gsub('C:/php-sdk/0/PFTT2/PFTT', '')
       @parts[:file]= IO.read( external_file ).lines do |line|
         parse_line line, context
       end
diff --git a/PFTT/lib/test/result/phpt.rb b/PFTT/lib/test/result/phpt.rb
index 014a09e..42db006 100644
--- a/PFTT/lib/test/result/phpt.rb
+++ b/PFTT/lib/test/result/phpt.rb
@@ -17,20 +17,33 @@ module Test
     end
     
     def self.from_xml(xml, test_bench, deploydir, php)
-      rclass = case xml['@result_type']
-      when 'XSkip'
+      rclass = case xml['@status']
+      when 'xskip'
         Test::Result::Phpt::XSkip
-      when 'Skip'
+      when 'skip'
         Test::Result::Phpt::Skip
-      when 'Bork'
+      when 'bork'
         Test::Result::Phpt::Bork
-      when 'Unsupported'
+      when 'unsupported'
         Test::Result::Phpt::Unsupported
-      when 'Mean'
+      when 'xfail'
+        Test::Result::Phpt::Meaningful
+      when 'fail'
+        Test::Result::Phpt::Meaningful
+      when 'works'
+        Test::Result::Phpt::Meaningful
+      when 'pass'
         Test::Result::Phpt::Meaningful
       end
-      if xml['@status'] == 'unsupported'
+      if rclass.nil?
+#      puts rclass
+#      puts xml['@result_type']
+#        puts xml.inspect
+      end
+      
+      if xml['@status'] == 'unsupported' or xml['@status'] == 'bork'
         r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), 
test_bench, deploydir, php)
+      # TODO included borked reasons array (field)
       elsif xml.has_key?('@reason')
         r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), 
test_bench, deploydir, php, xml['@reason'])
       else
@@ -51,6 +64,7 @@ module Test
           result_str = result_str.to_s
         end
         r = rclass.new(Test::Case::Phpt.from_xml(xml['test_case'][0]), 
test_bench, deploydir, php, result_str)
+          
       end
       case xml['@status']
       when 'pass'
@@ -70,11 +84,14 @@ module Test
       when 'xfail'
         r.status = :xfail
       end
+      #puts xml.inspect
       if xml.has_key?('diff')
+        #r.diff = xml['diff'][0]['result_str'][0]['text']
         r.diff = xml['diff'][0]
         unless r.diff.is_a?(String)
           r.diff = ''
         end
+        #puts xml.inspect
       end
       r.set_files
       return r
@@ -82,24 +99,24 @@ module Test
     
     def to_xml
       #
-      result_type = case self.class
-      when Test::Result::Phpt::XSkip
-        'XSkip'
-      when Test::Result::Phpt::Skip
-        'Skip'
-      when Test::Result::Phpt::Bork
-        'Bork'
-      when Test::Result::Phpt::Unsupported
-        'Unsupported'
-      when Test::Result::Phpt::Meaningful
-        'Meaningful'
-      else
-        ''
-      end
+#      result_type = case self.class
+#      when Test::Result::Phpt::XSkip
+#        'XSkip'
+#      when Test::Result::Phpt::Skip
+#        'Skip'
+#      when Test::Result::Phpt::Bork
+#        'Bork'
+#      when Test::Result::Phpt::Unsupported
+#        'Unsupported'
+#      when Test::Result::Phpt::Meaningful
+#        'Meaningful'
+#      else
+#        ''
+#      end
       #
       
       xml = {
-        '@result_type' => result_type,
+        #'@result_type' => result_type,
         'test_case' => @test_case.to_xml,
         '@status' => @status
       }
@@ -277,8 +294,10 @@ module Test
         unless @diff.is_a?(String)
           @diff = @diff.to_s
         end
+        #puts @diff.length.to_s
       if @diff.length > 0
         files['diff']= @diff
+          
       end
     end
     attr_accessor :diff
diff --git a/PFTT/lib/test/runner/stage/phpt/package.rb 
b/PFTT/lib/test/runner/stage/phpt/package.rb
index 36813f1..440f58c 100644
--- a/PFTT/lib/test/runner/stage/phpt/package.rb
+++ b/PFTT/lib/test/runner/stage/phpt/package.rb
@@ -7,10 +7,10 @@ module Test
 class Package < Tracing::Stage
   
   def run()
-    notify_start
+    notify_start 
     
     puts 'PFTT:compress: compressing PHPTs...'
-    local_phpt_zip = package_svn(Host::Local.new(), 
'c:/php-sdk/svn/branches/PHP_5_4')
+    local_phpt_zip = package_svn(Host::Local.new(), $phpt_path)
     puts 'PFTT:compress: compressed PHPTs...'
     
     notify_end(true)
@@ -18,7 +18,7 @@ class Package < Tracing::Stage
     return local_phpt_zip
   end
   
-end      
+end # class Package
 
       end # module PHPT
     end # module Stage
diff --git a/PFTT/lib/test/runner/stage/phpt/upload.rb 
b/PFTT/lib/test/runner/stage/phpt/upload.rb
index 33d36c9..be758be 100644
--- a/PFTT/lib/test/runner/stage/phpt/upload.rb
+++ b/PFTT/lib/test/runner/stage/phpt/upload.rb
@@ -12,20 +12,20 @@ class Upload < Tracing::Stage::ByHostMiddleware
   
     local_host = Host::Local.new()
             
-            upload_7zip(local_host, host)
-            remote_phpt_zip = host.systemdrive+'/PHP_5_4.7z'
-            host.upload_force(local_phpt_zip, remote_phpt_zip, 
Tracing::Context::Phpt::Upload.new)
-            host.delete_if(host.systemdrive+'/abc', 
Tracing::Context::Phpt::Decompress.new)
-            host.delete_if(host.systemdrive+'/PHP_5_4', 
Tracing::Context::Phpt::Decompress.new)
-            # TODO unpackage(host, host.systemdrive, remote_phpt_zip)
-            sd = host.systemdrive
-            #sleep(20)
-            # critical: must chdir to output directory or directory where 7zip 
file is stored!!!
-            host.exec!("#{sd}\\php-sdk\\bin\\7za.exe x -o#{sd}\\ 
#{sd}\\PHP_5_4.7z ", Tracing::Context::Phpt::Decompress.new, 
{:chdir=>"#{sd}\\", :null_output=>true})
-            # TODO host.move(host.systemdrive+'/PHP_5_4', 
host.systemdrive+'/abc')
-            #sleep(20)
-              # TODO use test_ctx.new
-            host.cmd!("move #{sd}\\php_5_4 #{sd}\\abc", 
Tracing::Context::PhpBuild::Compress.new)
+    upload_7zip(local_host, host)
+    remote_phpt_zip = host.systemdrive+'/PHP_5_4.7z'
+    host.upload_force(local_phpt_zip, remote_phpt_zip, 
Tracing::Context::Phpt::Upload.new)
+    host.delete_if(host.systemdrive+'/abc', 
Tracing::Context::Phpt::Decompress.new)
+    host.delete_if(host.systemdrive+'/PHP_5_4', 
Tracing::Context::Phpt::Decompress.new)
+    # TODO unpackage(host, host.systemdrive, remote_phpt_zip)
+    sd = host.systemdrive
+    #sleep(20)
+    # critical: must chdir to output directory or directory where 7zip file is 
stored!!!
+    host.exec!("#{sd}\\php-sdk\\bin\\7za.exe x -o#{sd}\\ #{sd}\\PHP_5_4.7z ", 
Tracing::Context::Phpt::Decompress.new, {:chdir=>"#{sd}\\", :null_output=>true})
+    # TODO host.move(host.systemdrive+'/PHP_5_4', host.systemdrive+'/abc')
+    #sleep(20)
+    # TODO use test_ctx.new
+    host.cmd!("move #{sd}\\php_5_4 #{sd}\\abc", 
Tracing::Context::PhpBuild::Compress.new)
             
     notify_end(true)
   end # def run
diff --git a/PFTT/lib/tracing/context.rb b/PFTT/lib/tracing/context.rb
index fd1a514..9d7fd8a 100644
--- a/PFTT/lib/tracing/context.rb
+++ b/PFTT/lib/tracing/context.rb
@@ -112,7 +112,7 @@ module Tracing
         
         while true do
           # TODO empty all STDIN chars now (before user is prompted to enter a 
char we shouldn't ignore)
-          
+           
           # show the prompt line to the user
           STDOUT.write(host.name+'('+host.osname_short+')'+prompt.prompt_str)
                   
diff --git a/PFTT/lib/tracing/prompt/diff.rb b/PFTT/lib/tracing/prompt/diff.rb
index 5c7e5b3..57a7651 100644
--- a/PFTT/lib/tracing/prompt/diff.rb
+++ b/PFTT/lib/tracing/prompt/diff.rb
@@ -3,37 +3,144 @@
 # TODO be able to print a list of all insertions or all insertions contianing 
'Warning', etc...
 module Tracing
   module Prompt
-
-class Diff < TestCaseRunPrompt
+    module Diff
+    
+      
+      
+#class BaseDiff < TestCaseRunPrompt
+#  
+#  def axis_label
+#      
+#    end
+#    
+#    def prompt_str
+#      axis_label+'::Diff> '
+#    end
+#    
+#        
+#    def help
+#      super
+#      puts
+#      puts 'Axes: [Run] [Host] [[Zoom] [Diff]]'
+#      puts ' [Run]  - Base - only Base run            Test - only Test run'
+#      puts '          Base+Test - all results from Base and Test (duplicates 
removed)'
+#      puts '          Base=Test - only results from Base and Test that match'
+#      puts '          Test-Base - only results from Base not matching those 
in Test run'
+#      puts '          Base-Test - only results from Test not matching those 
in Base run'
+#      puts ' [Host] - All - all selected hosts        {Hostname} - named host'
+#      puts ' [Zoom] - A - All tests                   E - One extension'
+#      puts '          T - One test case               L - One line from a 
test case'
+#      puts '          C - One chunk from one line'
+#      puts ' [Diff] - E - original Expected output    A - original Actual 
output'
+#      puts '          + - only added output           - - only removed output'
+#      puts '          d - + and -'
+#      puts '          e - Expected output (changed?)  a - Actual (changed?)'
+#      puts
+#      help_diff_cmds
+#      puts ' c  - change'
+#      puts ' C  - change (&next)'
+#      puts ' t  - Triage'
+#      puts ' h  - help'
+#      puts ' v  - view diff'
+#      puts ' V  - view PHPT documentation'
+#      puts ' y  - save chunk replacements'
+#      puts ' Y  - load chunk replacements'
+#      puts ' k  - save diff to file'
+#      puts ' l  - locate - os middleware build and other info'
+#      help_zoom_out
+#      puts ' F  - find'    
+#      puts ' R  - run'
+#      puts ' N  - network/host'
+#      puts ' Z  - zoom'
+#      puts ' D  - diff'
+#      
+#    end # def help
+#    
+#  def help_zoom_out
+#    # top level can't zoom out
+#  end
+#    
+#    def confirm_find
+#      true
+#    end
+#    
+#  def help_diff_cmds
+#  end
+#  
+#end # class BaseDiff
+#    
+#class All < BaseDiff
+#  
+#  
+#  def confirm_find
+#    # LATER ask user to confirm searching everything (b/c it can be slow)
+#  end
+#end
+#
+#class BaseNotAll < BaseDiff
+#  def help_zoom_out
+#        puts ' o  - zoom out'#
+#      end
+#      
+#      def execute(ans)
+#        if ans == 'o'
+#        end
+#      end
+#end    
+#
+#class Ext < BaseNotAll
+#end
+#
+#class TestCaseLineChunk < BaseNotAll
+#  def help_diff_cmds
+#    puts ' d  - delete (&next)'
+#    puts ' a  - add (&next)'
+#    puts ' s  - skip (&next)'
+#    puts ' p  - pass (&next)'      
+#  end
+#  
+#  def execute(ans)
+#    if ans == 'd' or ans == '-'
+#    elsif ans == 'a' or ans == '+'
+#    elsif ans == 's'
+#    elsif ans == 'p'
+#    end
+#  end
+#end
+#
+#class TestCase < TestCaseLineChunk
+#end
+#
+#class Line < TestCaseLineChunk
+#end
+#
+#class Chunk < TestCaseLineChunk
+#end
+#    
+class Diff 
       
   def initialize(dlm)
     @dlm = dlm
     # TODO
   end
-      
-  def help
-    super
-    puts ' d  - (or -) delete: modify expect'
-    puts ' a  - (or +) add: modify expect'
-    puts ' i  - ignore: remove from diffs' # TODO
-    puts ' s  - skip line, count'
-    puts ' m  - display more commands (this whole list)'
-    puts ' t  - Triage diffs in this test'
-    puts ' T  - Triage all diffs from all tests'
-    puts ' r  - replace expect with regex to match actual'
-    puts ' R  - replace all in file'
-    puts ' A  - replace all in test case set'
-    puts ' l  - show modified expect line (or original if not modified)'
-    puts ' L  - show original expect line'
-    puts ' P  - show original expect section'
-    puts ' p  - show modified expect section (or original if not modified)'
-    puts ' v  - show PHPT file format documentation (HTML)'
-    puts ' H  - highlight diff (Swing UI)'
-    puts ' y  - save chunk replacements to file'
-    puts ' Y  - load chunk replacements from a file'
-    puts ' k  - save diff to file (insertions and deletions)'
-    puts ' K  - save all inserted chunks to file'
-  end # def help
+  
+  
+  
+  def show_diff
+    if host.windows?
+      if host.exist?('winmerge')
+        host.exec!("winmerge #{expected} #{result}")
+        return
+      end
+    else
+      if host.exec('winmeld')
+        host.exec("winmeld #{expected} #{result}")
+        return
+      end
+    end
+    
+    # LATER fallback swing display
+  end
       
   def execute(ans)
     if ans=='-' or ans=='d'
diff --git a/PFTT/lib/util/package.rb b/PFTT/lib/util/package.rb
index 414347f..b41b302 100644
--- a/PFTT/lib/util/package.rb
+++ b/PFTT/lib/util/package.rb
@@ -17,12 +17,21 @@ end
 def package_php_build(local_host, build_path)
   ctx = Tracing::Context::PhpBuild::Compress.new
   
-  zip_name = File.basename(build_path)+'.7z'
+  cached_zip_name = 'c:/php-sdk/0/PFTT2/cache/PHP_5_4.7z'
+  if local_host.exist?(cached_zip_name)
+    puts "PFTT: compress: reusing #{cached_zip_name}"
+    return cached_zip_name
+  end
+  
+  zip_name = local_host.mktempdir(ctx) + '/' + File.basename(build_path)+'.7z'
   
   local_host.format_path!(zip_name, ctx)
   local_host.format_path!(build_path, ctx) 
   
-  local_host.exec!(host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za #{zip_name} 
#{build_path}", ctx)
+  local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a 
#{zip_name} #{build_path}", ctx)
+  
+  # cache archive for next time
+  local_host.copy(zip_name, cached_zip_name, ctx)
   
   return zip_name
 end
@@ -40,33 +49,73 @@ end
 def package_svn(local_host, path)
   ctx = Tracing::Context::PhpBuild::Compress.new
   
-  tmp_dir = File.join(local_host.tmpdir(ctx), File.basename(path))
+  tmp_dir = File.join(local_host.mktmpdir(ctx), File.basename(path))
+    
+  # put the PHPTs in a sub-folder of tmp_dir (tmp_dir will also store the 
archive)
+  package_tmp_dir = tmp_dir+'/PHP_5_4' # TODO
   
-  local_host.format_path!(tmp_dir, ctx)
+  test_dirs = {}
+  greatest_mtime = 0
   
-  # there's a bunch of files we should remove/not include in zip, copy folder 
to a temporary folder
-  # and then remove the files from that and then compress it
-  local_host.copy(path, tmp_dir, ctx)
+  # copy all files, sub folders and files from any directory named 'test' (in 
ext, sapi, tests, or Zend, or others that get added)
+  # (don't need the complete source code, just the PHPTs and files they might 
need)
+  # (the fewer the files, the smaller the archive, the faster the test cycle 
=> greater SDE/SDET productivity)
+  # TODO local_host.glob(path, '**/*', ctx) do |file|
+  Dir.glob(path+'/**/*') do |file|
+    # for each file
+    s_file = Host.sub(path, file)
+    
+    if s_file.include?('/tests/') or s_file.include?('/test/')
+      
+      mtime = local_host.mtime(file)
+      if mtime > greatest_mtime
+        greatest_mtime = mtime
+      end
+      
+      # TODO cache test_files copy and only add to test_dirs if the cached 
file is older or missing
+      
+      # save the list of dirs... we could copy each test file, buts its fewer 
copy operations
+      # to copy entire directories => so the copy process is faster
+      test_dirs[File.dirname(file)] = File.dirname(Host.join(package_tmp_dir, 
s_file))
+    end
+  end 
+  #
   
-  # remove any .svn directories
-  if local_host.windows?(ctx)
-    # FOR /F "tokens=*" %G IN ('DIR /B /AD /S *.svn*') DO RMDIR /S /Q "%G"
-    local_host.cmd!("FOR /F \"tokens=*\" %%G IN ('DIR /B /AD /S *.svn*') DO 
RMDIR /S /Q \"%%G\"'", ctx)
-  else
-    local_host.exec!("rm -rf `find . -type d -name .svn`", ctx)
+  # TODO (also have separate cache folders for test files, builds, etc...)
+  cached_zip_name = 'c:/php-sdk/0/PFTT2/cache/PHPT_5_4.7z'
+  
+  if local_host.exists?(cached_zip_name)
+    # if none of the test files has been modified since creation of cached 
copy of archive, then
+    # there is no need to copy and archive the test files a second time (just 
use the cached archive)
+    if local_host.mtime(cached_zip_name) > greatest_mtime
+      # don't need to copy+compress again
+      puts "PFTT: reusing cached tests #{cached_zip_name}"
+      return cached_zip_name
+      # TODO check copy on remote_host
+    end
+    puts "PFTT: tests updated, re-creating #{cached_zip_name}"
   end
+  # go ahead and create a new archive of test_files
+  #
   
-  # remove Release and Release_TS (maybe this svn copy was compiled?)
-  local_host.delete_if(File.join(tmp_dir, 'Release'), ctx)
-  local_host.delete_if(File.join(tmp_dir, 'Release_TS'), ctx)
-  # LATER local_host.delete_if(File.join(tmp_dir, 'php_test_results_*'))
+  # copy test dirs
+  test_dirs.each do |entry|
+    local_host.copy(entry[0], entry[1], ctx)
+  end
+  
+  # LATER check path for any PHPTs that didn't get included (PHPTs in wrong 
place)
   
   zip_name = tmp_dir+'.7z'
   
-  # we've removed as much as we really can, compress it
-  local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a 
#{zip_name} #{tmp_dir}", ctx)
+  # make it even smaller, compress it! (copying lots of little files takes a 
lot of overhead bandwidth)
+  local_host.exec!(local_host.systemdrive+"/php-sdk/0/PFTT2/PFTT/7za a 
#{zip_name} #{package_tmp_dir}", ctx)
+  
+  # cleanup temp dir
+  local_host.delete(package_tmp_dir, ctx)
   
-  local_host.delete(tmp_dir, ctx)
+  # cache archive for next time
+  local_host.copy(zip_name, cached_zip_name, ctx)
   
+  # return archive name (LATER delete tmp_dir and zip_name when no longer 
needed)
   return zip_name
 end
diff --git a/PFTT/src/se/datadosen/component/RiverLayout.class 
b/PFTT/src/se/datadosen/component/RiverLayout.class
new file mode 100644
index 0000000..44c7c16
Binary files /dev/null and b/PFTT/src/se/datadosen/component/RiverLayout.class 
differ
diff --git a/PFTT/src/se/datadosen/component/RiverLayout.java 
b/PFTT/src/se/datadosen/component/RiverLayout.java
new file mode 100644
index 0000000..2c24489
--- /dev/null
+++ b/PFTT/src/se/datadosen/component/RiverLayout.java
@@ -0,0 +1,492 @@
+package se.datadosen.component;
+
+import java.io.ObjectInputStream;
+import java.io.IOException;
+import java.awt.*;
+import java.util.*;
+
+/**
+ * <p>RiverLayout makes it very simple to construct user interfaces as 
components
+ * are laid out similar to how text is added to a word processor (Components 
flow
+ * like a "river". RiverLayout is however much more powerful than FlowLayout:
+ * Components added with the add() method generally gets laid out horizontally,
+ * but one may add a string before the component being added to specify 
"constraints"
+ * like this:
+ * add("br hfill", new JTextField("Your name here");
+ * The code above forces a "line break" and extends the added component 
horizontally.
+ * Without the "hfill" constraint, the component would take on its preferred 
size.
+ *</p>
+ * <p>
+ * List of constraints:<ul>
+ * <li>br - Add a line break
+ * <li>p - Add a paragraph break
+ * <li>tab - Add a tab stop (handy for constructing forms with labels followed 
by fields)
+ * <li>hfill - Extend component horizontally
+ * <li>vfill - Extent component vertically (currently only one allowed)
+ * <li>left - Align following components to the left (default)
+ * <li>center - Align following components horizontally centered
+ * <li>right - Align following components to the right
+ * <li>vtop - Align following components vertically top aligned
+ * <li>vcenter - Align following components vertically centered (default)
+ * </ul>
+ * </p>
+ * RiverLayout is LGPL licenced - use it freely in free and commercial programs
+ *
+ * @author David Ekholm
+ * @version 1.1 (2005-05-23) -Bugfix: JScrollPanes were oversized (sized to 
their containing component)
+ *  if the container containing the JScrollPane was resized.
+ */
+public class RiverLayout
+    extends FlowLayout
+    implements LayoutManager, java.io.Serializable {
+
+   public static final String LINE_BREAK = "br";
+   public static final String PARAGRAPH_BREAK = "p";
+   public static final String TAB_STOP = "tab";
+   public static final String HFILL = "hfill";
+   public static final String VFILL = "vfill";
+   public static final String LEFT = "left";
+   public static final String RIGHT = "right";
+   public static final String CENTER = "center";
+   public static final String VTOP = "vtop";
+   public static final String VCENTER = "vcenter";
+
+   Map constraints = new HashMap();
+   String valign = VCENTER;
+   int hgap;
+   int vgap;
+   Insets extraInsets;
+   Insets totalInsets = new Insets(0, 0, 0, 0);// Dummy values. Set by 
getInsets()
+
+
+   public RiverLayout() {
+      this(10, 5);
+   }
+
+   public RiverLayout(int hgap, int vgap) {
+      this.hgap = hgap;
+      this.vgap = vgap;
+      setExtraInsets(new Insets(0, hgap, hgap, hgap));
+   }
+
+   /**
+    * Gets the horizontal gap between components.
+    */
+   public int getHgap() {
+       return hgap;
+   }
+
+   /**
+    * Sets the horizontal gap between components.
+    */
+   public void setHgap(int hgap) {
+       this.hgap = hgap;
+   }
+
+   /**
+    * Gets the vertical gap between components.
+    */
+   public int getVgap() {
+       return vgap;
+   }
+
+   public Insets getExtraInsets() {
+      return extraInsets;
+   }
+
+   public void setExtraInsets(Insets newExtraInsets) {
+     extraInsets = newExtraInsets;
+   }
+
+   protected Insets getInsets(Container target) {
+      Insets insets = target.getInsets();
+      totalInsets.top = insets.top + extraInsets.top;
+      totalInsets.left = insets.left + extraInsets.left;
+      totalInsets.bottom = insets.bottom + extraInsets.bottom;
+      totalInsets.right = insets.right + extraInsets.right;
+      return totalInsets;
+   }
+
+   /**
+    * Sets the vertical gap between components.
+    */
+   public void setVgap(int vgap) {
+       this.vgap = vgap;
+   }
+
+
+   /**
+    * @param name the name of the component
+    * @param comp the component to be added
+    */
+   public void addLayoutComponent(String name, Component comp) {
+      constraints.put(comp, name);
+   }
+
+   /**
+    * Removes the specified component from the layout. Not used by
+    * this class.
+    * @param comp the component to remove
+    * @see       java.awt.Container#removeAll
+    */
+   public void removeLayoutComponent(Component comp) {
+      constraints.remove(comp);
+   }
+
+   boolean isFirstInRow(Component comp) {
+      String cons = (String) constraints.get(comp);
+      return cons != null && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 ||
+                              cons.indexOf(RiverLayout.PARAGRAPH_BREAK) != -1);
+   }
+
+   boolean hasHfill(Component comp) {
+      return hasConstraint(comp, RiverLayout.HFILL);
+   }
+
+   boolean hasVfill(Component comp) {
+      return hasConstraint(comp, RiverLayout.VFILL);
+   }
+
+   boolean hasConstraint(Component comp, String test) {
+      String cons = (String) constraints.get(comp);
+      if (cons == null) return false;
+      StringTokenizer tokens = new StringTokenizer(cons);
+      while (tokens.hasMoreTokens())
+         if (tokens.nextToken().equals(test)) return true;
+      return false;
+   }
+
+   /**
+    * Figure out tab stop x-positions
+    */
+   protected Ruler calcTabs(Container target) {
+      Ruler ruler = new Ruler();
+      int nmembers = target.getComponentCount();
+
+      int x = 0;
+      int tabIndex = 0; // First tab stop
+      for (int i = 0; i < nmembers; i++) {
+         Component m = target.getComponent(i);
+//         if (m.isVisible()) {
+            if (isFirstInRow(m) || i == 0) {
+               x = 0;
+               tabIndex = 0;
+            }
+            else x+= hgap;
+            if (hasConstraint(m, TAB_STOP)) {
+               ruler.setTab(tabIndex, x); // Will only increase
+               x = ruler.getTab(tabIndex++); // Jump forward if neccesary
+            }
+            Dimension d = m.getPreferredSize();
+            x += d.width;
+         }
+//      }
+      return ruler;
+   }
+
+   /**
+    * Returns the preferred dimensions for this layout given the
+    * <i>visible</i> components in the specified target container.
+    * @param target the component which needs to be laid out
+    * @return    the preferred dimensions to lay out the
+    *            subcomponents of the specified container
+    * @see Container
+    * @see #minimumLayoutSize
+    * @see       java.awt.Container#getPreferredSize
+    */
+   public Dimension preferredLayoutSize(Container target) {
+      synchronized (target.getTreeLock()) {
+         Dimension dim = new Dimension(0, 0);
+         Dimension rowDim = new Dimension(0, 0);
+         int nmembers = target.getComponentCount();
+         boolean firstVisibleComponent = true;
+         int tabIndex = 0;
+         Ruler ruler = calcTabs(target);
+
+         for (int i = 0; i < nmembers; i++) {
+            Component m = target.getComponent(i);
+//            if (m.isVisible()) {
+               if (isFirstInRow(m)) {
+                  tabIndex = 0;
+                  dim.width = Math.max(dim.width, rowDim.width);
+                  dim.height += rowDim.height + vgap;
+                  if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
+                  rowDim = new Dimension(0, 0);
+               }
+               if (hasConstraint(m, TAB_STOP)) rowDim.width = 
ruler.getTab(tabIndex++);
+               Dimension d = m.getPreferredSize();
+               rowDim.height = Math.max(rowDim.height, d.height);
+               if (firstVisibleComponent) {
+                  firstVisibleComponent = false;
+               }
+               else {
+                  rowDim.width += hgap;
+               }
+               rowDim.width += d.width;
+  //          }
+         }
+         dim.width = Math.max(dim.width, rowDim.width);
+         dim.height += rowDim.height;
+
+         Insets insets = getInsets(target);
+         dim.width += insets.left + insets.right;// + hgap * 2;
+         dim.height += insets.top + insets.bottom;// + vgap * 2;
+         return dim;
+      }
+   }
+
+   /**
+    * Returns the minimum dimensions needed to layout the <i>visible</i>
+    * components contained in the specified target container.
+    * @param target the component which needs to be laid out
+    * @return    the minimum dimensions to lay out the
+    *            subcomponents of the specified container
+    * @see #preferredLayoutSize
+    * @see       java.awt.Container
+    * @see       java.awt.Container#doLayout
+    */
+   public Dimension minimumLayoutSize(Container target) {
+      synchronized (target.getTreeLock()) {
+         Dimension dim = new Dimension(0, 0);
+         Dimension rowDim = new Dimension(0, 0);
+         int nmembers = target.getComponentCount();
+         boolean firstVisibleComponent = true;
+         int tabIndex = 0;
+         Ruler ruler = calcTabs(target);
+
+         for (int i = 0; i < nmembers; i++) {
+            Component m = target.getComponent(i);
+  //          if (m.isVisible()) {
+               if (isFirstInRow(m)) {
+                  tabIndex = 0;
+                  dim.width = Math.max(dim.width, rowDim.width);
+                  dim.height += rowDim.height + vgap;
+                  if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
+                  rowDim = new Dimension(0, 0);
+               }
+               if (hasConstraint(m, TAB_STOP)) rowDim.width = 
ruler.getTab(tabIndex++);
+               Dimension d = m.getMinimumSize();
+               rowDim.height = Math.max(rowDim.height, d.height);
+               if (firstVisibleComponent) {
+                  firstVisibleComponent = false;
+               }
+               else {
+                  rowDim.width += hgap;
+               }
+               rowDim.width += d.width;
+//            }
+         }
+         dim.width = Math.max(dim.width, rowDim.width);
+         dim.height += rowDim.height;
+
+         Insets insets = getInsets(target);
+         dim.width += insets.left + insets.right;// + hgap * 2;
+         dim.height += insets.top + insets.bottom;// + vgap * 2;
+         return dim;
+      }
+   }
+
+   /**
+    * Centers the elements in the specified row, if there is any slack.
+    * @param target the component which needs to be moved
+    * @param x the x coordinate
+    * @param y the y coordinate
+    * @param width the width dimensions
+    * @param height the height dimensions
+    * @param rowStart the beginning of the row
+    * @param rowEnd the the ending of the row
+    */
+   protected void moveComponents(Container target, int x, int y, int width,
+                               int height,
+                               int rowStart, int rowEnd, boolean ltr, Ruler 
ruler) {
+      synchronized (target.getTreeLock()) {
+         switch (getAlignment()) {
+            case FlowLayout.LEFT:
+               x += ltr ? 0 : width;
+               break;
+            case FlowLayout.CENTER:
+               x += width / 2;
+               break;
+            case FlowLayout.RIGHT:
+               x += ltr ? width : 0;
+               break;
+            case LEADING:
+               break;
+            case TRAILING:
+               x += width;
+               break;
+         }
+         int tabIndex = 0;
+         for (int i = rowStart; i < rowEnd; i++) {
+            Component m = target.getComponent(i);
+//          if (m.isVisible()) {
+               if (hasConstraint(m, TAB_STOP)) x = getInsets(target).left + 
ruler.getTab(tabIndex++);
+               int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2;
+               if (ltr) {
+                  m.setLocation(x, y + dy);
+               }
+               else {
+                  m.setLocation(target.getWidth() - x - m.getWidth(),
+                                y + dy);
+               }
+               x += m.getWidth() + hgap;
+//            }
+         }
+      }
+   }
+
+
+   protected void relMove(Container target, int dx, int dy, int rowStart,
+                        int rowEnd) {
+      synchronized (target.getTreeLock()) {
+         for (int i = rowStart; i < rowEnd; i++) {
+            Component m = target.getComponent(i);
+//            if (m.isVisible()) {
+               m.setLocation(m.getX() + dx, m.getY() + dy);
+//            }
+         }
+
+      }
+   }
+
+   protected void adjustAlignment(Component m) {
+      if (hasConstraint(m, RiverLayout.LEFT)) setAlignment(FlowLayout.LEFT);
+      else if (hasConstraint(m, RiverLayout.RIGHT)) 
setAlignment(FlowLayout.RIGHT);
+      else if (hasConstraint(m, RiverLayout.CENTER)) 
setAlignment(FlowLayout.CENTER);
+      if (hasConstraint(m, RiverLayout.VTOP)) valign = VTOP;
+      else if (hasConstraint(m, RiverLayout.VCENTER)) valign = VCENTER;
+
+   }
+   /**
+    * Lays out the container. This method lets each component take
+    * its preferred size by reshaping the components in the
+    * target container in order to satisfy the constraints of
+    * this <code>FlowLayout</code> object.
+    * @param target the specified component being laid out
+    * @see Container
+    * @see       java.awt.Container#doLayout
+    */
+   public void layoutContainer(Container target) {
+      setAlignment(FlowLayout.LEFT);
+      synchronized (target.getTreeLock()) {
+         Insets insets = getInsets(target);
+         int maxwidth = target.getWidth() -
+             (insets.left + insets.right);
+         int maxheight = target.getHeight() -
+             (insets.top + insets.bottom);
+
+         int nmembers = target.getComponentCount();
+         int x = 0, y = insets.top + vgap;
+         int rowh = 0, start = 0, moveDownStart = 0;
+
+         boolean ltr = target.getComponentOrientation().isLeftToRight();
+         Component toHfill = null;
+         Component toVfill = null;
+         Ruler ruler = calcTabs(target);
+         int tabIndex = 0;
+
+         for (int i = 0; i < nmembers; i++) {
+            Component m = target.getComponent(i);
+            //if (m.isVisible()) {
+               Dimension d = m.getPreferredSize();
+               m.setSize(d.width, d.height);
+
+               if (isFirstInRow(m)) tabIndex = 0;
+               if (hasConstraint(m, TAB_STOP)) x = ruler.getTab(tabIndex++);
+               if (!isFirstInRow(m)) {
+                  if (i > 0 && !hasConstraint(m, TAB_STOP)) {
+                     x += hgap;
+                  }
+                  x += d.width;
+                  rowh = Math.max(rowh, d.height);
+               }
+               else {
+                  if (toVfill != null && moveDownStart == 0) {
+                     moveDownStart = i;
+                  }
+                  if (toHfill != null) {
+                     toHfill.setSize(toHfill.getWidth() + maxwidth - x,
+                                     toHfill.getHeight());
+                     x = maxwidth;
+                  }
+                  moveComponents(target, insets.left, y,
+                                 maxwidth - x,
+                                 rowh, start, i, ltr, ruler);
+                  x = d.width;
+                  y += vgap + rowh;
+                  if (hasConstraint(m, PARAGRAPH_BREAK)) y += 2*vgap;
+                  rowh = d.height;
+                  start = i;
+                  toHfill = null;
+               }
+            //}
+            if (hasHfill(m)) {
+               toHfill = m;
+            }
+            if (hasVfill(m)) {
+               toVfill = m;
+            }
+            adjustAlignment(m);
+         }
+
+         if (toVfill != null && moveDownStart == 0) {  // Don't move anything 
if hfill component is last component
+            moveDownStart = nmembers;
+         }
+         if (toHfill != null) { // last component
+            toHfill.setSize(toHfill.getWidth() + maxwidth - x,
+                            toHfill.getHeight());
+            x = maxwidth;
+         }
+         moveComponents(target, insets.left, y, maxwidth - x, rowh,
+                        start, nmembers, ltr, ruler);
+         int yslack = maxheight - (y+rowh);
+         if (yslack != 0 && toVfill != null) {
+            toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight());
+            relMove(target, 0, yslack, moveDownStart, nmembers);
+         }
+      }
+   }
+
+}
+
+class Ruler {
+   private Vector tabs = new Vector();
+
+   public void setTab(int num, int xpos) {
+      if (num >= tabs.size()) tabs.add(num, new Integer(xpos));
+      else {
+         // Transpose all tabs from this tab stop and onwards
+         int delta = xpos - getTab(num);
+         if (delta > 0) {
+            for (int i = num; i < tabs.size(); i++) {
+               tabs.set(i, new Integer(getTab(i) + delta));
+            }
+         }
+      }
+   }
+
+   public int getTab(int num) {
+      return ((Integer)tabs.get(num)).intValue();
+   }
+
+   public String toString() {
+      StringBuffer ret = new StringBuffer(getClass().getName() + " {");
+      for (int i=0; i<tabs.size(); i++) {
+         ret.append(tabs.get(i));
+         if (i < tabs.size()-1) ret.append(',');
+      }
+      ret.append('}');
+      return ret.toString();
+   }
+
+   public static void main(String[] args) {
+      Ruler r = new Ruler();
+      r.setTab(0,10);
+      r.setTab(1,20);
+      r.setTab(2,30);
+      System.out.println(r);
+      r.setTab(1,25);
+      System.out.println(r);
+      System.out.println(r.getTab(0));
+   }
+}
diff --git a/PFTT/src/se/datadosen/component/Ruler.class 
b/PFTT/src/se/datadosen/component/Ruler.class
new file mode 100644
index 0000000..98aaf53
Binary files /dev/null and b/PFTT/src/se/datadosen/component/Ruler.class differ

Reply via email to