###################################
#
# vrdde.rb
# Programmed by  nyasu <nyasu@osk.3web.ne.jp>
# Copyright 1999-2001 Nishikawa,Yasuhiro
#
# More information at http://www.threeweb.ad.jp/~nyasu/software/vrproject.html
# (in Japanese)
#
###################################

=begin
= VisualuRuby(tmp) modules for DDE
This file provides modules for DDE conversation.
<<< handlers.rd
=end


# DDE_ADVISE not tested.

vr_DIR="vr/" unless vr_DIR
require vr_DIR+'vruby'
require vr_DIR+'sysmod'


module DDElParam
  PackDDElParam=Win32API.new("user32","PackDDElParam",["I","I","I"],"L")
  UnpackDDElParam=Win32API.new("user32","UnpackDDElParam",["I","L","P","P"],"I")
  FreeDDElParam=Win32API.new("user32","FreeDDElParam",["I","L"],"I")

  def packDDElParam(msg,low,high)
    PackDDElParam.call(msg,low,high)
  end
  def unpackDDElParam(msg,lparam)
    a="        "; b="        "
    UnpackDDElParam.call(msg,lparam,a,b)
    [a.unpack("I")[0],b.unpack("I")[0]]
  end

  def freeDDElParam(msg,lparam)
    FreeDDElParam.call(msg,lparam)
  end
end

module VRDdeConversation
=begin
== VRDdeConversation
Utilities for DDE conversation.

=== class DDEAckFlags
This is a capsule of return code,ack flag, busy flag for DDE_ACK message
+++ Attributes
--- retcode
    8bit value.
--- ack
    DDE request is accepted or not.
--- busy
    busy or not.
=end

  WM_DDE_INITIATE = 0x3e0
  WM_DDE_TERMINATE= 0x3e1
  WM_DDE_ADVISE   = 0x3e2
  WM_DDE_UNADVISE = 0x3e3
  WM_DDE_ACK      = 0x3e4
  WM_DDE_DATA     = 0x3e5
  WM_DDE_REQUEST  = 0x3e6
  WM_DDE_POKE     = 0x3e7
  WM_DDE_EXECUTE  = 0x3e8

  class DDEAckFlags
#    attr_accessor :ack
#    attr_accessor :busy
#    attr_accessor :retcode
    def ack() @_vr_ack; end
    def busy() @_vr_busy; end
    def retcode() @_vr_retcode; end

    def initialize(ack=true,busy=false,retcode=0)
      @_vr_ack,@_vr_busy,@_vr_retcode = ack,busy,retcode
    end
  end

  def sendDDEAck(shwnd,aItem,retcode=0,ack=true,busy=false)
    r = retcode & 0xff
    r |= 0x8000 if ack 
    r |= 0x4000 if busy 
    lparam = packDDElParam(WM_DDE_ACK,r,aItem)
    SMSG::PostMessage.call shwnd,WM_DDE_ACK, self.hWnd,lparam
  end
end

module VRDdeServer
=begin
== VRDdeServer
This module prepares fundamental functions for DDE server.

=== Methods
--- addDDEAppTopic(appname,topic)
    Adds acceptable pair of application name and topic for DDE.
--- delDDEAppTopic(appname,topic)
    Deletes acceptable pair of application name and topic.

=== Event handlers
--- ????_ddeinitiate(shwnd,appname,topic)
    Fired when DDE client whose hwnd is ((|shwnd|)) connects the 
    DDE server.
--- ???_ddeterminate(shwnd)
    Fred when DDE client whose hwnd is ((|shwnd|)) disconnects.
=end

  include VRMessageHandler
  include VRDdeConversation
  include DDElParam

  def ddeserverinit
    acceptEvents([WM_DDE_INITIATE,WM_DDE_TERMINATE])
    addHandler WM_DDE_INITIATE,"_ddeInitiate",MSGTYPE::ARGINTINTINT,nil
    addHandler WM_DDE_TERMINATE,"_ddeTerminate",MSGTYPE::ARGWINT,nil
    @_vr_clients={} #{client's hwnd=>[client's hwnd,appname,topic]
    @_vr_ddeacceptable=[] # [appname,topic]
  end
  def vrinit
    super
    ddeserverinit
  end

  def self__ddeInitiate(shwnd,aApp,aTopic)
    p=[GAtom::GetName(aApp),GAtom::GetName(aTopic)]
    return unless @_vr_ddeacceptable.index(p)

    @_vr_clients[shwnd] = [shwnd] + p
    r=nil
    r=send("self_ddeinitiate",shwnd,p[0],p[1])
    SMSG::SendMessage.call shwnd,WM_DDE_ACK,self.hWnd,MAKELPARAM(aApp,aTopic)
  end

  def self__ddeTerminate(shwnd)
    send("self_ddeterminate",shwnd)
    SMSG::PostMessage.call shwnd,WM_DDE_TERMINATE,self.hWnd,0
    @_vr_clients.delete(shwnd)
  end

  def addDDEAppTopic(appname,topic)
    @_vr_ddeacceptable.push([appname,topic])
  end
  def delDDEAppTopic(appname,topic)
    @_vr_ddeacceptable.delete([appname,topic])
  end

  def self_ddeinitiate(*args) end
  def self_ddeterminate(*arg) end
end

module VRDdeExecuteServer
=begin
== VRDdeExecuteServer
This module provides a feature of DDE_Execute server.
((<VRDdeServer>)) is included.

=== Event handler
--- self_ddeexecute(command,shwnd,appname,topic)
    Fired when the client whose name is ((|shwnd|)) sends DDE_EXECUTE 
    to the server.The command string is ((|command|)).
    If the return value is a kind of VRDdeConversation::((<DDEAckFlags>)),
    DDE_ACK message that will be sent is according to this return value.
=end

  include VRDdeServer
  
  EXECUTEMETHOD="self_ddeexecute"

  def ddeexecuteserverinit
    addEvent WM_DDE_EXECUTE
    addHandler WM_DDE_EXECUTE,"_ddeexecuteinternal",MSGTYPE::ARGINTINT,nil
  end
  def vrinit
    super
    ddeexecuteserverinit
  end
  
  def self__ddeexecuteinternal(shwnd,hcmd)
    cl=@_vr_clients[shwnd]
    raise "unknown dde client (not initiated)" unless cl
    cmd=GMEM::Get(hcmd).unpack("A*")[0]
    ret=nil
    ret=send(EXECUTEMETHOD,cmd,*cl) if respond_to?(EXECUTEMETHOD)
    if ret.is_a?(DDEAckFlags) then
      sendDDEAck shwnd,hcmd,(ret.retcode || 0),ret.ack,ret.busy
    else
      sendDDEAck shwnd,hcmd
    end
  end
end



module VRDdeClient
=begin
== VRDdeClient
This module provides features of DDE clients.

=== Methods
--- ddeconnected?
--- ddeready?
    Returns whether it is able to request dde conversation.
--- ddebusy?
    Returns true when it waits DDE_ACK/DDE_DATA message.

--- ddeexecute(appname,topic.command)
    Sends DDE_EXECUTE request to the server specified by the pair of (appname,topic).
--- ddepoke(appname,topic,item,data,fmt)
    Sends DDE_POKE request to the server specified by the pair of (appname,topic).
    The ((|data|)) in the format of ((|fmt|)) (1 as CF_TEXT and so on) is transferred 
    tothe ((|item|)) in the server
--- dderequest(appname,topic,item,fmt)
    Sends DDE_REQUEST request to the server specified by the pair of (appname,topic).
    The server calls back ((<self_dderequestdata>)) method of client window to
    transfer the data of ((|item|)).
--- ddeadvise(appname,topic,item,fmt)
--- ddeunadvise(appname,topic,item,fmt)
    Not tested.

=== Event handlers
???? is the downcase-ed appname of the DDE conversation. It is because the ???? name
is named by WM_DDE_ACK message's App-name which is created by the server and that 
may be different from the requested appname from client.

--- ????_ddeterminate(shwnd)
    Fired when the server disconnects the conversation.
--- ????_dderefused(retcode,busy)
    Fired when the server refused the connection. The return code is ((|retcode|)) and
    busy flag is ((|busy|)).
--- ????_ddeexecdone(retcode)
    Fired when the server accepts the DDE_EXECUTE request.
--- ????_ddepokedone(retcode)
    Fired when the server accepts the DDE_POKE request.
--- ????_ddedata(data,fmt,flag)
    Fired when the server returns the data by the DDE_REQUEST that client sended.
    If the return value is a kind of VRDdeConversation::((<DDEAckFlags>)),
    DDE_ACK message that will be sent is according to this return value.
--- ????_ddeadvisedata(data,fmt,flag)
    Not tested.
=end

  include VRMessageHandler
  include VRDdeConversation
  include DDElParam

 private
  STATUSCONNECT= 1
  STATUSEXECUTE= 2
  STATUSPOKE   = 4
  STATUSREQUEST= 8
  STATUSADVISE = 16
  STATUSTERMINATE=128
  GMEMSTYLE = 0x2042


  def ddeconnect(appname,topic)
    raise "DDE_INITIATE busy!" if @_vr_ddesearching
    raise "Application name must be specified" unless appname
    aApp   = GAtom::Add(appname)
    aTopic = if topic then   GAtom::Add(topic) else 0 end

    srv =  @_vr_servers.find do |key,item| item[1]==appname end

    shwnd = if srv then srv[0] else 0xffffffff end
    @_vr_ddesearching=nil
    SMSG::SendMessage.call shwnd,WM_DDE_INITIATE,self.hWnd,MAKELPARAM(aApp,aTopic)
    shwnd = @_vr_ddesearching unless srv
    @_vr_ddesearching=nil
    
    GAtom::Delete(aApp)   
    GAtom::Delete(aTopic) if topic!=0
    raise "DDE Server (#{appname}:#{topic}) not found" unless shwnd
    shwnd
  end

  def ddeterminate(shwnd)
    SMSG::PostMessage.call shwnd,WM_DDE_TERMINATE,self.hWnd,0
    @_vr_servers[shwnd][2]|=STATUSTERMINATE
#    @_vr_servers.delete(shwnd)
  end

 public
  def vrinit
    super
    ddeclientinit
  end

  def ddeclientinit
    acceptEvents([WM_DDE_ACK,WM_DDE_DATA,WM_DDE_TERMINATE])
    addHandler WM_DDE_ACK, "ddeAck", MSGTYPE::ARGINTINT,nil
    addHandler WM_DDE_DATA,"ddeData",MSGTYPE::ARGINTINT,nil
    addHandler WM_DDE_TERMINATE,"ddeTerminate",MSGTYPE::ARGWINT,nil
    @_vr_servers={} # {server's hwnd => [server's hwnd,appname,status,params]}
  end

  def ddeconnected?(appname)
    srv =  @_vr_servers.find do |key,item| item[1].downcase==appname.downcase end
    if srv then srv=srv[1];((srv[2]&STATUSCONNECT)>0) else false end
  end
  def ddebusy?(appname)
    srv =  @_vr_servers.find do |key,item| item[1].downcase==appname.downcase end
    if srv then srv=srv[1]; ((srv[2] & ~STATUSCONNECT)>0) else nil end
  end
  def ddeidle?(appname)
    srv =  @_vr_servers.find do |key,item| item[1].downcase==appname.downcase end
    if srv then
      srv=srv[1]
      ((srv[2]&STATUSCONNECT)>0) and ((srv[2] & ~STATUSCONNECT)==0)
    else
      true
    end
  end
  def ddeready?(appname)
    srv =  @_vr_servers.find do |key,item| item[1].downcase==appname.downcase end
    if srv then
      srv=srv[1]
      ((srv[2]&STATUSCONNECT)>0) and 
      ((srv[2] & ~(STATUSCONNECT | STATUSADVISE))==0)
    else
      true
    end
  end

  def ddeexecute(appname,topic,cmdstr)
    raise "dde(#{appname}:#{topic}) not ready" unless ddeready?(appname)
    shwnd=ddeconnect(appname,topic)
    executemem = GMEM::AllocStr(GMEMSTYLE,cmdstr)
    @_vr_servers[shwnd][2] |= STATUSEXECUTE
    SMSG::PostMessage.call shwnd,WM_DDE_EXECUTE,self.hWnd,executemem
  end

  def ddepoke(appname,topic,item,data,fmt=1)  #1=CF_TEXT
    raise "dde(#{appname}:#{topic}) not ready" unless ddeready?(appname)
    shwnd=ddeconnect(appname,topic)

    aItem   = GAtom::Add(item.to_s)

    pokedata=[0,fmt].pack("Ss")+data.to_s
    pokemem = GMEM::AllocStr(GMEMSTYLE,pokedata)

    @_vr_servers[shwnd][2] |= STATUSPOKE
    @_vr_servers[shwnd][3] = pokemem

    lparam = packDDElParam(WM_DDE_POKE,pokemem,aItem)
    SMSG::PostMessage.call shwnd,WM_DDE_POKE,self.hWnd,lparam
  end

  def dderequest(appname,topic,item,fmt=1) #1=CF_TEXT
    raise "dde(#{appname}:#{topic}) not ready" unless ddeready?(appname)
    shwnd=ddeconnect(appname,topic)

    aItem   = GAtom::Add(item.to_s)

    @_vr_servers[shwnd][2] |= STATUSREQUEST
    SMSG::PostMessage.call shwnd,WM_DDE_REQUEST,self.hWnd,
                             MAKELPARAM(fmt.to_i,aItem)
  end

  def ddeadvise(appname,topic,item,fmt=1,defup=false,ackreq=false)
    raise "dde(#{appname}:#{topic}) not ready" unless ddeready?(appname)
    shwnd=ddeconnect(appname,topic)

    aItem   = GAtom::Add(item.to_s)

    flag = 0
    if defup then flag |= 0x4000 end
    if ackreq then flag |= 0x8000 end
    
    advisedata=[flag,fmt].pack("Ss")
    advisemem = GMEM::AllocStr(GMEMSTYLE,advisedata)
    
    @_vr_servers[shwnd][2] |= STATUSADVISE
    lparam = packDDElParam(WM_DDE_POKE,advisemem,aItem)
    SMSG::PostMessage.call shwnd,WM_DDE_ADVISE,self.hWnd,lparam
  end

  def ddeunadvise(appname,topic,item,fmt=0)
    raise "dde(#{appname}:#{topic}) not ready" unless ddeready?(appname)
    shwnd=ddeconnect(appname,topic)

    aItem   = GAtom::Add(item.to_s)

    @_vr_servers[n][2] |= STATUSREQUEST
    SMSG::PostMessage.call @_vr_servers[n][0],WM_DDE_UNADVISE,self.hWnd,
                             MAKELPARAM(fmt.to_i,aItem)
  end


  def self_ddeTerminate(shwnd)
    fname=@_vr_servers[shwnd][1] + "_ddeterminate"
    send(fname,shwnd) if respond_to?(fname)
    @_vr_servers.delete(shwnd)
  end

  def self_ddeAck(shwnd,lparam)
#p "ACK"
    sv = @_vr_servers[shwnd]
    fname=""
    if(!sv) then
      aApp,aTopic = LOWORD(lparam),HIWORD(lparam)
      appname=GAtom::GetName(aApp).downcase
      tpcname=GAtom::GetName(aTopic)

      @_vr_servers[shwnd]=[shwnd,appname,STATUSCONNECT,nil]

      @_vr_ddesearching=shwnd

      GAtom::Delete(aApp)
      GAtom::Delete(aTopic)

    elsif (sv[2] & ~STATUSCONNECT)>0 then
      wstatus,param = unpackDDElParam(WM_DDE_ACK,lparam)
      freeDDElParam(WM_DDE_ACK,lparam)

      retcode = wstatus&0xf
      busy= 0<(wstatus&0x4000)
      ack = 0<(wstatus&0x8000)
      sname = sv[1]

      unless ack then
        sv[2] &= STATUSCONNECT
        fname=sname+"_dderefused"
        send(fname,retcode,busy) if respond_to?(fname)
      end
      if (sv[2] & STATUSEXECUTE)>0 then
        GMEM::Free(param) unless ack
        sv[2] &= ~STATUSEXECUTE
        fname=sname+"_ddeexecdone"
        ddeterminate(shwnd)
      elsif(sv[2] & STATUSPOKE)>0 then
        GAtom::Delete(param)
        GMEM::Free(sv[3])
        sv[3]=nil
        sv[2] &= ~STATUSPOKE
        fname=sname+"_ddepokedone"
        ddeterminate(shwnd)
      elsif(sv[2] & STATUSREQUEST)>0 then
        GAtom::Delete(param)
        sv[2] &= ~STATUSPOKE
        fname=nil
        ddeterminate(shwnd)
      elsif(sv[2] & STATUSADVISE)>0 then
        GMEM::Free(param) unless ack
        sv[2] &= ~STATUSADVISE
        fname=nil
      else
        ddeterminate(shwnd)
      end
      send(fname,retcode) if ack and fname and respond_to?(fname)
    else
      raise "DDE MultiSession Error"
    end
  end

  def self_ddeData(shwnd,lparam)
#p "DATA"
    datamem,aItem = unpackDDElParam(WM_DDE_DATA,lparam)
    freeDDElParam(WM_DDE_DATA,lparam)

    sv = @_vr_servers[shwnd]
    fname=sname=nil

    datastr = GMEM::Get(datamem)
    flag,fmt,data = datastr.unpack("Ssa*")

    if (flag&0x2000) > 0 then  # fRelease is asserted
      GMEM::Free(datamem)
    end

    ret = nil
    if(!sv) then
      # Ignored
    else
      sname=sv[1]
      if (sv[2] & STATUSREQUEST)>0 then
        sv[2]&=1
        fname="_ddedata"
        ddeterminate(sv[0])
      elsif (sv[2] & STATUSADVISE)>0 
        sv[2]&=1
        fname="_ddeadvisedata"
      else
      end

      fn=sname+fname
      if(fn) then
        ret=send(fn,data,fmt,flag) if respond_to?(fn)
      end
    end

    if (flag&0x8000) > 0 then  #fAckReq is asserted
      if ret.is_a?(DDEAckFlags) then
        sendDDEAck shwnd,aItem,(ret.retcode || 0),ret.ack,ret.busy
      else
        sendDDEAck shwnd,aItem
      end
    else
      GAtom::Delete(aItem)
    end

  end
end
