#!/usr/bin/env python22

"""
This example program shows a way to connect to a gimp script-fu server.
Now I've got to learn scheme... :-)
LICENSE: GPL
DATE   : 2002 06 22
AUTHOR : tk@ taponet.de

When sending commands to gimp be sure to have an enclosing "(begin ...)"! 

BUGS: no socket timeout detection
"""

import struct
import socket
import types

class scriptFuClient:
  """
  The scriptFuClient class can be used to talk to a gimp scriptfu server.

  First, start gimp, and in the menu, start Xtns/Script-Fu/Server

  Next, use the class:
       from scriptFuClient import *
       scriptfu = scriptFuClient()
       print scriptfu.fu_add( 12, 13 )
       print scriptfu.fu_add( 12.3, 13.0 )
       
  Or simply start this script (it has a 'if __name__ == "__main__"' part).
     
  To see how it works either read the source or type:
       print scriptfu.fu_add.__doc__
       print scriptfu.sendRequest.__doc__

  Have fun! 
  Send feedback to tk@ taponet.de !
  """
  def __init__( self, server="localhost", port=10008 ):
    """
    Open a socket connection to the Script-Fu server.
    Optional parameters: server (string) and port (int).
    """
    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.socket.connect(  (server, port)  )

  def close( self ):
    "Close the socket."
    self.socket.close()

  def readBytes( self, length ):
    "Read 'length' bytes from our socket. I think there's a better way than this..."
    res = ""
    while (len(res) < length):
      res += self.socket.recv( length - len(res) )
    return res
  
  def sendRequest( self, scheme_program ):
    """
    Send a scheme program to gimp.
    The send protocol is: 'G' + chr(len_hi) + chr(len_lo) + data
    The receive protocol is: 'G' + err + len_hi + len_lo  (=4 bytes)
    If err is 0, we can read ((256*len_hi) + len_lo) bytes = the result.
    """
    l  = len( scheme_program )
    hi = l >> 8
    lo = l - (256*hi)
    req = 'G' + chr(hi) + chr(lo) + scheme_program
    self.socket.send( req )
    
    res_data = self.readBytes( 4 )
    G,err,hi,lo = struct.unpack("BBBB", res_data)

    l = (256 * hi) + lo
    if (l > 0):
      res = self.readBytes( l )
      if err == 0:
        return res
      raise Exception('GIMP returned error code %d: %s' % (err, res))
    else:
      return ""

  def fu_add( self, a, b ):
    """
    An example of a function: add two integer or float values.
    The result is a sequence like ('add', 12, 13, 25).
    It is just an example, using simple scheme code:
      (begin (set! a 12) (set! b 13) (number->string (+ a b)))
    
    When sending commands to gimp be shure to have an enclosing "(begin ...)!"
    """
    request = "(begin (set! a " + str(a) + ") (set! b " + str(b) + ") (number->string (+ a b)))"
    # request = "(begin (set! a %d) (set! b %d) (number->string (+ a b)))" % (a,b)
    result  = self.sendRequest( request )

    # Test: if a or b is a float, the result should be, too:
    if (type(a) is types.FloatType) or (type(b) is types.FloatType):
      result = float(result)
    else:
      result = int(result)
    return ( 'fu_add', a, b, result )

if __name__ == "__main__":
   print "\n Using gimp as a calculator... :-)\n"
   scriptfu = scriptFuClient()
   print scriptfu.__doc__
   
   yesno = raw_input( "Did you start the Script-Fu server (Y/n)?" )
   if not yesno == 'n':
     print scriptfu.fu_add( 12, 13 )
     print scriptfu.fu_add( 12.3, 13 )
   else:
     print "Aborting..."

   scriptfu.close()
        
