#
#         Simple OLE Browser (Tk Edition)
#
#         18 Mar 2005 Dave Burt (dave@burt.id.au)
#         Aug. 17, 2002 M.Suketa (masaki.suketa@nifty.ne.jp)
#
# Simple OLE Browser is an OLE browser like Microsoft's OLE viewer or
# the Object Browser in their Visual Basic Editor.
# It shows available Type Libraries, and drills down to details of
# the classes and members that the libraries provide.
#
# [Requirements]
#   + Ruby
#   + Ruby/Tk  OR  swin and vruby
#   + Win32OLE
#
#   All of the above are included in the Ruby One-Click Installer:
#   http://rubyforge.org/frs/?group_id=167
#
# [History]
#   The original version of this software is available from
#   http://homepage1.nifty.com/markey/ruby/win32ole/index_e.html
#   Dave Burt added the Ruby/Tk interface and made it the default.
#
# [Copying]
#   Simple OLE Browser is copyrighted free software by Masaki
#   Suketa <masaki.suketa@nifty.ne.jp> and Dave Burt <dave@burt.id.au>. 
#   You can redistribute it under the license terms of Ruby itself.
#   (See http://www.ruby-lang.org/en/LICENSE.txt)
#
# [Warranty]
#   THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
#   IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
#   WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
#   PURPOSE.
#


require 'win32ole'



#
# Provides basic reflection on COM libraries
#
class OLEINFO
  
  VERSION = '0.0.2a'
  
  @members = []
  
  def typelibs
    WIN32OLE_TYPE.typelibs.select{|t|
      t.size > 0
    }.sort
  end

  def ole_classes(tlib)
    WIN32OLE_TYPE.ole_classes(tlib).collect {|t|
      t.name
    }.sort
  end

  def ole_members(tlib, klass)
    @members = []
    k = WIN32OLE_TYPE.ole_classes(tlib).find {|t|
      t.name == klass
    }
    return [] if !k
    @members = (k.ole_methods + k.variables).sort{|m1, m2|
      m1.name <=> m2.name
    }
    (@members).collect {|m|
      m.name
    }
  end

  def ole_class_info(tlib, klass)
    k = WIN32OLE_TYPE.ole_classes(tlib).find {|t|
      t.name == klass
    }
    return "" if !k
    "#{k.ole_type}  #{k.name}\n" +
    "  GUID : #{k.guid}\n" +
    "  PROGID : #{k.progid}\n" +
    "  DESCRIPTION : #{k.helpstring}\n\n"
  end

  def method_info(m)
    info = ""
    info += "Event " if m.event?
    info += m.invoke_kind 
    info += " "
    info += m.return_type
    info += " "
    info += m.name
    info += "\n"
    info += "  Dispatch ID : #{m.dispid}\n"
    info += "  DESCRIPTION : #{m.helpstring}\n" 
    m.params.each_with_index do |param, i|
      pinfos = []
      pinfos.push "IN" if param.input?
      pinfos.push "OUT" if param.output?
      pinfos.push "OPTION" if param.optional?
      pinfo = "arg#{i+1} - #{param.ole_type} #{param.name} [#{pinfos.join(',')}]"
      pinfo += " = #{param.default}" if param.default
      info += "  #{pinfo}\n"
    end
    info += "\n"
    info += "  Event Interface : #{m.event_interface}\n" if m.event?
    info
  end

  def variable_info(m)
    info = "\n  #{m.variable_kind} #{m.ole_type} #{m.name}"
    info += " = #{m.value}" if m.value
    info
  end

  def ole_member_info(tlib, klass, member, i)  
    k = WIN32OLE_TYPE.ole_classes(tlib).find {|t|
      t.name == klass
    }
    return "" if !k
    m = (k.ole_methods + k.variables).find {|mm|
       mm.name == member
    }
    m = @members[i]
    return "" if !m
    if m.kind_of?(WIN32OLE_METHOD)
      return method_info(m)
    else
      return variable_info(m)
    end
  end
end



#
# Run the vruby OLE Browser interface
#
def run_vr_ui
  require 'vr/vrcontrol'
  require 'vr/vrlayout2'

  eval(<<-END)
    # Define the vruby OLE Browser interface
    class MyForm2 < VRForm
      include VRResizeable
      def construct
        self.caption = "Simple OLE Browser #{OLEINFO::VERSION}"
        @oleinfo = OLEINFO.new

        addControl VRListbox, "lst_class","",0,0,10,10
        addControl VRListbox, "lst_member","",10,0,10,10
        @k1=VRHorizTwoPaneFrame.new(@lst_class,@lst_member).setup(self)

        addControl VRListbox, "lst_tlib","",0,0,10,10
        @lst_tlib.setListStrings @oleinfo.typelibs
        @k2=VRVertTwoPaneFrame.new(@lst_tlib,@k1).setup(self)

        # addControl VRStatic, "info", "", 0,0,10,10
        addControl VRText, "info", "", WStyle::WS_HSCROLL
        @k3 = VRVertTwoPaneFrame.new(@k2,@info).setup(self)
      end
      def self_resize(w,h)
        @k3.move 5,5,w-10,h-10
      end

      def lst_tlib_selchanged
        @lst_class.setListStrings @oleinfo.ole_classes(@lst_tlib.getTextOf(@lst_tlib.selectedIndex))
      end

      def lst_class_selchanged
        @lst_member.setListStrings @oleinfo.ole_members(
          @lst_tlib.getTextOf(@lst_tlib.selectedIndex),
          @lst_class.getTextOf(@lst_class.selectedIndex))
        @info.caption = @oleinfo.ole_class_info(
          @lst_tlib.getTextOf(@lst_tlib.selectedIndex),
          @lst_class.getTextOf(@lst_class.selectedIndex)).gsub("\n", "\r\n")
      end

      def lst_member_selchanged
        @info.caption = 
          (@oleinfo.ole_class_info(
             @lst_tlib.getTextOf(@lst_tlib.selectedIndex),
             @lst_class.getTextOf(@lst_class.selectedIndex)) + 
           @oleinfo.ole_member_info( 
             @lst_tlib.getTextOf(@lst_tlib.selectedIndex),
             @lst_class.getTextOf(@lst_class.selectedIndex),
             @lst_member.getTextOf(@lst_member.selectedIndex),
             @lst_member.selectedIndex)).gsub("\n", "\r\n")
      end
    end
  END

  VRLocalScreen.start MyForm2
end



#
# Run the Ruby/Tk OLE Browser interface
#
def run_tk_ui
  require "tk"

  eval(<<-END)
    # Improve the Tk listbox
    class Listbox < TkListbox
      # return the text of the selected item
      def to_s
        @selected_index = curselection.first if curselection.first
        get(@selected_index) if @selected_index
      end
      def clear
        @selected_index = nil
        super
      end
      def value=(*args)
        @selected_index = nil
        super
      end
      def add_scrollbar
        yscrollbar(TkScrollbar.new(self).pack(:side => :right, :fill => :y))
        self
      end
    end
  END

  oleinfo = OLEINFO.new
  
  # create UI

  root = TkRoot.new do
    title "Simple OLE Browser #{OLEINFO::VERSION}"
    configure(:height => 400)
  end

  lst_tlib = Listbox.new(root, :listvariable => TkVariable.new(oleinfo.typelibs)).add_scrollbar.pack(:side => :top, :fill => :both, :expand => :yes)

  middle_pane = TkFrame.new(root).pack(:fill => :both, :expand => :yes)
  lst_class = Listbox.new(middle_pane, :listvariable => TkVariable.new).add_scrollbar.pack(:side => :left, :fill => :both, :expand => :yes)
  lst_member = Listbox.new(middle_pane, :listvariable => TkVariable.new).add_scrollbar.pack(:side => :right, :fill => :both, :expand => :yes)

  txt_info = TkText.new(root, :height => 11, :wrap => :word).pack(:side => :top, :fill => :both)

  # bind procedures to events

  lst_tlib.bind('ButtonRelease') do |event|
    lst_class.value = oleinfo.ole_classes(lst_tlib.to_s)
    lst_member.clear
    txt_info.value = ""
  end

  lst_class.bind('ButtonRelease') do |event|
    lst_member.value = oleinfo.ole_members(lst_tlib.to_s, lst_class.to_s)
    txt_info.value = oleinfo.ole_class_info(lst_tlib.to_s, lst_class.to_s)
  end

  lst_member.bind('ButtonRelease') do |event|
    txt_info.value = 
      oleinfo.ole_class_info(lst_tlib.to_s, lst_class.to_s) + 
      oleinfo.ole_member_info(lst_tlib.to_s, lst_class.to_s,
        lst_member.to_s, lst_member.curselection.first.to_i)
  end

  # start app
  Tk.mainloop
end




if __FILE__ == $0
  interfaces = [:run_tk_ui, :run_vr_ui]
  interfaces.reverse! unless ARGV.empty?
  begin
    send(interfaces.first)
  rescue LoadError
    send(interfaces.last)
  end
end
