###################################
#
# vrcontrol.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)
#
###################################

VR_DIR="vr/" unless defined?(::VR_DIR)
require VR_DIR+'vruby'
require VR_DIR+'sysmod'
=begin
= VisualuRuby(tmp) Standard Controls
This file prepares classes of standard controls, panels and menu modules.

<<< handlers.rd

=== Control Container
* ((<VRStdControlContainer>))

=== Standard Controls
* ((<VRButton>))
* ((<VRGroupbox>))
* ((<VRCheckbox>))
* ((<VRRadiobutton>))
* ((<VRStatic>))
* ((<VREdit>))
* ((<VRText>))
* ((<VRListbox>))
* ((<VRCombobox>))

=== Menu
* ((<VRMenu>))
* ((<VRMenuItem>))
* ((<VRMenuUseable>))

=== Panels
* ((<VRPanel>))
* ((<VRBitmapPanel>))
* ((<VRCanvasPanel>))
=end

###########################################
#  Messages and Styles
#

module WMsg
  BN_CLICKED  =       0
  BN_DBLCLICKED  =    5
  BM_GETCHECK =    0xf0
  BM_SETCHECK =    0xf1

  STN_CLICKED =       0
  STN_DBLCLICKED =    1

  LB_ADDSTRING =    0x0180
  LB_INSERTSTRING = 0x0181
  LB_DELETESTRING = 0x0182
  LB_SETSEL =       0x0185
  LB_SETCURSEL =    0x0186
  LB_GETSEL =       0x0187
  LB_GETCURSEL =    0x188
  LB_GETTEXT =      0x189
  LB_GETTEXTLEN =   0x18a
  LB_GETCOUNT   =   0x18b
  LB_SELECTSTRING = 0x18c
  LB_DIR        =   0x18d
  LB_FINDSTRING =   0x18f
  LB_GETSELCOUNT =  0x190
  LB_GETSELITEMS =  0x191
  LB_GETITEMDATA =  0x199
  LB_SETITEMDATA =  0x19a
  LBN_SELCHANGE=1
  LBN_DBLCLK=2
  
  CB_ADDSTRING    = 0x143
  CB_DELETESTRING = 0x144
  CB_DIR          = 0x145
  CB_GETCOUNT     = 0x146
  CB_GETCURSEL =    0x147
  CB_GETLBTEXT =    0x148
  CB_GETLBTEXTLEN = 0x149
  CB_INSERTSTRING = 0x14a
  CB_FINDSTRING   = 0x14c
  CB_SELECTSTRING = 0x14d
  CB_SETCURSEL    = 0x14e
  CB_GETITEMDATA  = 0x150
  CB_SETITEMDATA  = 0x151
  CB_SETEXTENDEDUI =0x155
  CBN_SELCHANGE    = 1
end

module WStyle
  BS_PUSHBUTTON =     0
  BS_CHECKBOX =       2
  BS_AUTOCHECKBOX =   3
  BS_RADIOBUTTON =    4
  BS_3STATE =         5
  BS_GROUPBOX =       7
  BS_USERBUTTON =     8
  BS_AUTORADIOBUTTON= 9

  ES_MULTILINE   = 4
  ES_PASSWORD    = 0x20
  ES_AUTOVSCROLL = 0x40
  ES_AUTOHSCROLL = 0x80
  ES_READONLY    = 0x800
  ES_WANTRETURN  = 0x1000
  LBS_STANDARD =    0x00a00000 | 1
  LBS_MULTIPLESEL = 8

  CBS_STANDARD    = 0x00a00200 | 3
  CBS_AUTOSCROLL = 0x40
end

###############################################
# Standard Control
#

class VRStdControl < VRControl
  def _vr_cmdhandlers
    unless defined?(@_vr_cmdhandlers)
      @_vr_cmdhandlers={} 
    else
      @_vr_cmdhandlers
    end
  end

  def addCommandHandler(msg,handlername,handlertype,argparsestr)
    @_vr_cmdhandlers={} unless defined?(@_vr_cmdhandlers)
    @_vr_cmdhandlers[msg]=[] unless _vr_cmdhandlers[msg]
    @_vr_cmdhandlers[msg].push [handlername,handlertype,argparsestr]
  end

  def deleteCommandHandler(msg,handlername)
    return false unless @_vr_cmdhandlers[msg]
    @_vr_cmdhandlers.delete_if do |shandler|
      shandler[0] != (PREHANDLERSTR+handlername).intern
    end
  end
end


################################################
# Standard Control Container 
#
module VRStdControlContainer
=begin
== VRStdControlContainer
This module provides a message handler for WM_COMMAND, which calls
the defined methods in a parent window.
VRForm includes this module automatically loading "vrcontrol.rb".
=end

  include VRMessageHandler

  def stdcontrolcontainerinit
    addHandler(WMsg::WM_COMMAND,"wmcommand",MSGTYPE::ARGPASS,nil)
    addEvent WMsg::WM_COMMAND
  end
  
  def vrinit
    super
    stdcontrolcontainerinit
    addNoRelayMessages [WMsg::WM_COMMAND]
  end

  def self_wmcommand(msg)
    id=LOWORD(msg.wParam)
    ct=@controls[id]       # Activated Control
    if !ct then
      if !@_vr_menu then return end
      ct=@_vr_menus[id]
      if !ct then
        return # no handler
      end
    end
    
    messageid=HIWORD(msg.wParam)
    return unless ct._vr_cmdhandlers and ct._vr_cmdhandlers[messageid]

    ct._vr_cmdhandlers[messageid].each do |shandler|
      args=msgarg2handlerarg(shandler[1],msg,shandler[2])
      ct.__send__(shandler[0],*args) if ct.respond_to?(shandler[0])
      msg.retval = controlmsg_dispatching(ct,shandler[0],*args)
    end
  end

end

# add
module VRContainersSet
  include VRStdControlContainer
  INITIALIZERS.push :stdcontrolcontainerinit
end



##########################################
#  Standard Controls
#
#
#  Minimum definition for a Standard Control is
#
#> class VRSampleControl <VRStdControl
#>   WINCLASSINFO = ["ControlClass",additionalStyle,exStyle]
#>   def vrinit()
#>     super 
#>     addCommandHandler(message,eventname,type,argparsestr])
#>   end
#> end
#
# @_vr_cmdhandlers such as {
## message -> [eventname, argtype, unpackstr]
#   WMsg::BN_CLICKED=>["clicked",MSGTYPE::ARGNONE,nil],
#   WMsg::LBN_SELCHANGE=>["selchange",MSGTYPE::ARGNONE,nil]
#}
# ############################################################

class VRButton < VRStdControl
=begin
== VRButton
Button.
Button's caption can be set/got by caption/caption= method of SWin::Window.

=== Event handlers
Buttons invoke the following method of parent window.
--- ????_clicked
    This parent's method is fired when button is pushed.
--- ????_dblclicked
    This parent's method is fired when button is double clicked.
=end

  WINCLASSINFO =  ["BUTTON",WStyle::BS_PUSHBUTTON] 

  def vrinit
    super
    addCommandHandler(WMsg::BN_CLICKED,    "clicked",   MSGTYPE::ARGNONE,nil)
    addCommandHandler(WMsg::BN_DBLCLICKED, "dblclicked",MSGTYPE::ARGNONE,nil)
  end
end

class VRGroupbox < VRStdControl
=begin
== VRGroupbox
Groupbox.

This control can be a container of other window.
You need to extend this class by Container modules, VRStdControlContainer for Standard Controls and VRCommonControlContainer for Common Controls.
=end

  include VRParent
  WINCLASSINFO = ["BUTTON",WStyle::BS_GROUPBOX]
end

class VRCheckbox < VRButton
=begin
== VRCheckbox
Check box.
Parent class is ((<VRButton>))

=== Methods
Checkbox has the following methods.
--- checked?
    Return boolean value whether checkbox is checked or not.
--- check(v)
    Check checkbox as v(=true/false).
=end
  WINCLASSINFO = ["BUTTON",WStyle::BS_AUTOCHECKBOX]

  def checked?
    c=sendMessage(WMsg::BM_GETCHECK,0,0)               #getcheck
    return c!=0
  end
  def check(v)
    sendMessage WMsg::BM_SETCHECK, ( (v)? 1 : 0 ),0
  end
end

class VRRadiobutton < VRCheckbox
=begin
== VRRadiobutton
Radio Button.

Parent class is ((<VRCheckbox>))
=end
  WINCLASSINFO = ["BUTTON",WStyle::BS_AUTORADIOBUTTON]
end


class VRStatic < VRStdControl
=begin
== VRStatic
Static Control(text).

Text can be access as caption.
=end
  WINCLASSINFO = ["STATIC",0]
  def vrinit
    addCommandHandler(WMsg::STN_CLICKED,  "clicked",0,nil)
  end
end

class VREdit < VRStdControl
=begin
== VREdit
Edit Control.

=== Methods
--- text
    Gets Text in the control. Line break(\r\n) is translated by this method.
--- text=(str)
    Sets Text in the control. Line break is translated by this method.
--- getSel
    Returns [start_pos,end_pos] of the selected area.
--- setSel(st,en,noscroll=0)
    Sets the selected area specified by ((|st|)) as start point and ((|en|))
    as end point and Scrolls to show the area. When ((|noscroll|)) is 1, 
    it doesn't scroll.
--- setCaret(r)
    Sets cursor point at ((|r|))
--- replaceSel(newstr)
    Replaces text in the selected area with ((|newstr|)).
--- readonly=(b)
    Sets readonly flag as boolean ((|b|))
--- limit
    Gets the length limit of this Editbox.
--- limit=(lmt)
    Sets the length limit of this Editbox.
--- modified
    Returns true if the editbox text is modified by user.
--- modified=(f)
    Sets modified flag of the editbox.
--- cut
    Cuts the selected area into clipboard.
--- copy
    Copies the selected area into clipboard.
--- paste
    Paste the clipboard text into the editbox.
--- clear
    Clears selected area text.
    Use ((<cut>)) method to set the text into clipboard.
--- undo
    Undo the previous cut/copy/paste/clear action.

=== Event handlers
Editboxes invoke the following method of parent window.
--- ????_changed
    This parent's method is fired when editbox text is modified.
=end

  WINCLASSINFO = ["EDIT",WStyle::ES_AUTOHSCROLL,WExStyle::WS_EX_CLIENTEDGE]

  def vrinit
    super
    addCommandHandler(0x300,"changed",MSGTYPE::ARGNONE,nil)
  end

  def getSel
    r=sendMessage(0xb0,0,0)
    return LOWORD(r),HIWORD(r)
  end

  def setCaret(r)
    setSel(r,r)
  end

  def setSel(st,en,noscroll=0)
    sendMessage(0xb1,st,en)
  end

  def replaceSel(newstr)
    sendMessage(0xc2,0,newstr.to_s)
  end

  def readonly=(b)
    f= (b)? 1 : 0
    sendMessage 0xcf,f,0
  end

  def limit=(lmt)
    sendMessage 0xc5,lmt.to_i,0
  end
  def limit
    sendMessage 0xd5,0,0
  end

  def modified
    r=sendMessage 0xb8,0,0              #GETMODIFY
    if r==0 then false else true end
  end
  alias modified? :modified
  def modified=(f)
    r= if f then 1 else 0 end
    sendMessage 0xb9,r,0
  end

# change linebreak code
# caption,caption= methods is raw methods

  VR_REGEXPNEWLINE="\r\n"
  def text() self.caption.gsub(/#{VR_REGEXPNEWLINE}/,$/); end
  def text=(str) 
    self.caption = str.gsub(/([^\r])\n/,'\1'+VR_REGEXPNEWLINE)
  end


 # for clipboard and so on..
  def cut
    sendMessage 0x300,0,0  #CUT
  end
  def copy
    sendMessage 0x301,0,0  #COPY
  end
  def paste
    sendMessage 0x302,0,0  #PASTE
  end
  def clear
    sendMessage 0x303,0,0  #CLEAR
  end
  def undo
    sendMessage 0x304,0,0  #UNDO
  end

end

class VRText < VREdit
=begin
== VRText
Mutiline Editbox.
Parent class is ((<VREdit>))

=== Methods
--- scrollTo(line.col)
    Scrolls ((|line|)) lines vertically and ((|col|)) columns horizontally.
--- visibleStartLine
    Returns line number of the first visible line in the editbox.
--- countLines
    Returns max line number.
--- getLineLengthOf(ln)
    Returns length of the ((|ln|))-th line.
--- getLineTextOf(ln,maxlen)
    Returns text of ((|ln|))-th line. If maxlen is specified, returned text is
    limited in its length.
--- scrollup
    Scrolls line up in the editbox.
--- scrolldown
    Scrolls line down in the editbox.
--- scrollupPage
    Scrolls page up in the editbox.
--- scrolldownPage
    Scrolls page down in the editbox.
--- scrolltocaret
    Scrolls the caret into view in the editbox.
--- char2line(ptr)
    Returns the index of the line that contains the specified character index.
--- line2char(ln)
    Returns the character index of a line.
--- getCurrentLine
    Returns the current line index.
=end

  WINCLASSINFO = ["EDIT",
     WStyle::ES_MULTILINE|WStyle::ES_AUTOVSCROLL,WExStyle::WS_EX_CLIENTEDGE
    ]

private
  def c_scroll(r)
    sendMessage 0xb5,r.to_i,0
  end

public

  def countLines
    sendMessage 0x00BA,0,0
  end

  def scrollTo(line,col)
    sendMessage 0x00B6,0,MAKELPARAM(line,col)    #LINESCROLL
  end
  def visibleStartLine()
    sendMessage 0x00CE,0,0             #GETFIRSTVISIBLELINE
  end
  
  def getLineLengthOf(l)
    sendMessage 0xc1,l.to_i,0          #GETLINELENGTH
  end
  def getLineTextOf(l,textsize=getLineLengthOf(l))
    r=pack("Ia",textsize," "*textsize)
    len=sendMessage 0xc4,l.to_i,r
    r[0,len]
  end

  def scrollup() c_scroll 0; end
  def scrolldown() c_scroll 1; end
  def scrollupPage() c_scroll 2; end
  def scrolldownPage() c_scroll 3; end
  
  def scrolltocaret
    sendMessage 0xb7,0,0               #SCROLLCARET
  end
  
  def char2line(ptr)
    sendMessage 0xc9,ptr.to_i,0        #LINEFROMCHAR
  end
  def line2char(l)
    sendMessage 0xbb,l.to_i,0          #LINEINDEX
  end
  def getCurrentLine
    char2line(-1)
  end
end


class VRListbox < VRStdControl
=begin
== VRListbox
Listbox.
Listbox has multi strings to select.

=== Methods
--- addString(idx,str)
    Adds ((|str|)) String at the ((|idx|)).
    When ((|idx|)) is omitted, ((|str|)) is added at the bottom.
--- deleteString(idx)
    Deletes string at ((|idx|))
--- countStrings
    Returns the count of its strings.
--- clearStrings
    Deletes all strings.
--- eachString
    Yields each string.
--- setListStrings(strarray)
    Sets array of string ( ((|strarray|)) ) as the listbox strings.
--- selectedString
    Returns the selected index.
--- select(idx)
    Selects ((|idx|))-th string.
--- getTextOf(idx)
    Retrieves the text of ((|idx|))-th string.
--- setDir(fname,opt=0)
    Sets the filenames specified by ((|fname|)) to the listbox.
    ((|opt|)) specifies the attributes of the files to be added.
--- findString(findstr,start=0)
    Finds ((|findstr|)) in the listbox strings and returns the its index.
    ((|start|)) specifies the index to start finding.
--- getDataOf(idx)
    Retrieves the 32bit value associated with the ((|idx|))-th string.
--- setDataOf(idx,data)
    Associates the 32bit value with the ((|idx|))-th string.
=== Event handlers
--- ????_selchanged
    Fired when the selected string is changed.
=end

  WINCLASSINFO = ["LISTBOX",WStyle::LBS_STANDARD,WExStyle::WS_EX_CLIENTEDGE]

  def vrinit
    super
    addCommandHandler(WMsg::LBN_SELCHANGE, "selchange",MSGTYPE::ARGNONE,nil)
    set_liststrings
  end
  
 private
  def set_liststrings
    clearStrings
    
    return unless defined? @_vr_init_items
    0.upto(@_vr_init_items.size-1) do |idx|
      addString idx,@_vr_init_items[idx]
    end if @_vr_init_items
  end

 public
  def addString(*arg)
    if arg[0].is_a?(Integer) then
      sendMessage WMsg::LB_INSERTSTRING,arg[0],arg[1].to_s
    else
      sendMessage WMsg::LB_ADDSTRING,0,arg[0].to_s
    end
  end

  def deleteString(idx)
    sendMessage WMsg::LB_DELETESTRING,idx.to_i,0
  end
  def countStrings
    sendMessage WMsg::LB_GETCOUNT,0,0
  end

  def clearStrings
    c=getCount
    (1..c).each do |i|
      deleteString 0           #delete #0 c-times
    end
  end
  
  def eachString
    c=getCount
    0.upto(c-1) do |i|
      yield getString(i)
    end
  end
  
  def setListStrings(sarray)
    @_vr_init_items=sarray
    if hWnd>0 then set_liststrings end
  end

  def selectedString
    sendMessage(WMsg::LB_GETCURSEL,0,0)
  end

  def getTextOf(idx)
    len = sendMessage(WMsg::LB_GETTEXTLEN,idx.to_i,0) #LB_gettextlen
    raise "No such index(#{idx}) in the Listbox"if len<0
    str=" " * len
    sendMessage(WMsg::LB_GETTEXT,idx.to_i,str)          #LB_getText
    str
  end

  def select(idx)
    sendMessage(WMsg::LB_SETCURSEL,idx.to_i,0)
  end

  def setDir(fname,opt=0)
    sendMessage(WMsg::LB_DIR,opt.to_i,fname.to_s)
  end

  def findString(findstr,start=0)
    sendMessage(WMsg::LB_FINDSTRING,start.to_i,findstr.to_s)
  end

  def getDataOf(idx)
    sendMessage(WMsg::LB_GETITEMDATA,idx.to_i,0)
  end
  def setDataOf(idx,data)
    sendMessage(WMsg::LB_SETITEMDATA,idx.to_i,data.to_i)
  end

  def eachSelected
    count = sendMessage(WMsg::LB_GETSELCOUNT,0,0)
  
    if count<1 then
      yield selectedString
      return
    end

    buffer = '\0\0\0\0' * count
    sendMessage(WMsg::LB_GETSELITEMS,count,buffer)

    buffer.unpack("I*")[0,count].each do |i|
      yield i
    end
  end

end

class VRCombobox < VRStdControl
=begin
== VRCombobox
Combobox is like Listbox, but Only one string is selected.

=== Methods
--- addString(idx,str)
    Adds ((|str|)) String at the ((|idx|)). 
    When ((|idx|)) is omitted, ((|str|)) is added at the bottom.
--- deleteString(idx)
    Deletes string at ((|idx|))
--- countStrings
    Returns the count of its strings.
--- clearStrings
    Deletes all strings.
--- eachString
    Yields each string.
--- setListStrings(strarray)
    Sets array of string ( ((|strarray|)) ) as the combobox strings.
--- selectedString
    Returns the selected index.
--- select(idx)
    Selects ((|idx|))-th string.
--- getTextOf(idx)
    Retrieves the text of ((|idx|))-th string.
--- setDir(fname,opt=0)
    Sets the filenames specified by ((|fname|)) to the combobox.
    ((|opt|)) specifies the attributes of the files to be added.
--- findString(findstr,start=0)
    Finds ((|findstr|)) in the combobox strings and returns the its index.
    ((|start|)) specifies the index to start finding.
--- getDataOf(idx)
    Retrieves the 32bit value associated with the ((|idx|))-th string.
--- setDataOf(idx,data)
    Associates the 32bit value with the ((|idx|))-th string.
=== Event handlers
--- ????_selchanged
    Fired when the selected string is changed.
=end

  WINCLASSINFO = ["COMBOBOX",WStyle::CBS_STANDARD] 

  def vrinit
    super
    set_liststrings
    addCommandHandler(WMsg::CBN_SELCHANGE, "selchange",MSGTYPE::ARGNONE,nil)
  end

 private
  def set_liststrings
    clearStrings
    return unless defined? @_vr_init_items
    0.upto(@_vr_init_items.size-1) do |idx|
      addString idx,@_vr_init_items[idx]
    end if @_vr_init_items
  end

 public
  def addString(*arg)
    if arg[0].is_a?(Integer) then
      sendMessage WMsg::CB_INSERTSTRING,arg[0],arg[1].to_s
    else
      sendMessage WMsg::CB_ADDSTRING,0,arg[0].to_s
    end
  end


  def deleteString(idx)
      sendMessage WMsg::CB_DELETESTRING,idx,0
  end
  def countStrings
      sendMessage WMsg::CB_GETCOUNT,0,0
  end

  def clearStrings
    c=getCount
    (1..c).each do |i|
      deleteString 0           #delete #0 c-times
    end
  end
  
  def eachString
    c=getCount
    0.upto(c-1) do |i|
      yield getString(i)
    end
  end

  def setListStrings(sarray)
    @_vr_init_items=sarray
    if hWnd>0 then set_liststrings end
  end

  def selectedString
    sendMessage(WMsg::CB_GETCURSEL,0,0)
  end

  def getTextOf(idx)
    len = sendMessage(WMsg::CB_GETLBTEXTLEN,idx,0)   #CB_gettextlen
    raise "No such index(#{idx}) in the Combobox"if len<0
    str=" " * len
    sendMessage(WMsg::CB_GETLBTEXT,idx,str)          #CB_getText
    str
  end

  def select(idx)
    sendMessage(WMsg::CB_SETCURSEL,idx,0)
  end

  def setDir(fname,opt=0)
    sendMessage(WMsg::CB_DIR,opt.to_i,fname.to_s)
  end

  def findString(findstr,start=0)
    sendMessage(WMsg::CB_FINDSTRING,start.to_i,findstr.to_s)
  end

  def getDataOf(idx)
    sendMessage(WMsg::CB_GETITEMDATA,idx.to_i,0)
  end
  def setDataOf(idx,data)
    sendMessage(WMsg::CB_SETITEMDATA,idx.to_i,data.to_i)
  end
end

#####################################
#  Editable Combobox  
#  programmed by USHIWATARI-san
module WMsg
  CBN_EDITCHANGE = 5
end
module WStyle
  CBS_DROPDOWN    = 0x00a00200 | 2
end

class VREditCombobox < VRCombobox
=begin
== VREditCombobox
Editable Combobox.
This is a kind of Combobox where you can edit the text.

=== Methods
This has also VRCombobox's methods.
--- text
    Returns the current text.

=== Event handlers
--- changed
    Fired when the text is changed.
=end


  WINCLASSINFO = ["COMBOBOX",WStyle::CBS_DROPDOWN | WStyle::CBS_AUTOSCROLL ] 
  def vrinit
    super
    addCommandHandler(WMsg::CBN_EDITCHANGE, "changed",MSGTYPE::ARGNONE,nil)
  end

  def text
    self.caption
  end
  def text=(str) 
    self.caption = str.gsub(/([^\r])\n/,'\1'+VREdit::VR_REGEXPNEWLINE)
  end

end



#####################################
#  Menu
#
class VRMenu
=begin
== VRMenu
Menu.

=== Methods
--- append(caption,state)
    Adds a new menu item at the last with the caption of ((|caption|)) and 
    the state of ((|state|)). See ((<state=>)) about ((|state|)).
--- insert(ptr,caption,name,state)
    Inserts new item at ((|ptr|)).
--- delete(id)
    Deletes menu. ((|id|)) can be a instance of VRMenuItem or a menu-id integer.
--- count
    Counts the menu items.
--- set(sarr)
    Sets the menu according to the ((|sarr|)) which is a structured array 
    of strings such like
    (({  [ ["&File", [["&Open","open"],["E&xit","exit"]],  }))
    (({       ["&Edit",["&Copy","copy]]    }))
    (({    ] ]  }))
      Caption : TEXT
      Name : TEXT
      Menu : [Caption, Name|Menu]

    When a menu is clicked, ((|Name|))_clicked method is fired.

=end

  attr :menu
  attr :parent

  SEPARATOR=["sep","_vrmenusep",0x800]

  def initialize(menu,parent)
    @menu=menu
    @parent=parent
  end

  def append (*arg)
    m=VRMenuItem.new(@menu)
    id=@parent.newControlID
    if arg[2] then
      @menu.append arg[0],id,arg[2]
    else
      @menu.append arg[0],id
    end
    @parent.registerMenu(m,arg[1],id,arg[0])
    m
  end
  def insert (ptr,*arg)
    m=VRMenuItem.new(@menu)
    id=@parent.newControlID
    if arg.size>2 then
      @menu.insert arg[0],id,ptr,arg[2]
    else
      @menu.insert arg[0],id,ptr
    end
    @parent.registerMenu(m,arg[1],id,arg[0])
    m
  end
  def delete (id)
    if id.is_a?(VRMenuItem) then
      @menu.delete id.etc
    else
      @menu.delete id
    end
  end
  def count (*arg)
    @menu.count(*arg)
  end

  def set(sarr)
    sarr.each do |item|
      if item[1].is_a?(Array) then
        m=@parent.newMenu(true)
        m.set(item[1])
        @menu.append item[0],m.menu,item[2]
      elsif item[1].is_a?(VRMenu) then
        @menu.append item[0],item[1].menu,item[2]
      else
        append(*item)
      end
    end
    self
  end
end

VRMenuTemplate = Struct.new("VRMenuTemplate",:caption,:item,:state)
=begin
== VRMenuTemplate
If you don't like using Array for VRMenu#set, this will help you.

=== Methods
--- caption
--- caption=
    Represents the caption of the menu.
--- item
--- item=
    When ((|item|)) is String, ((|item|)) means the name of menu item.
    This name is used for the event handler name like ((|item|))_clicked.
    ((|item|)) can be also an instance of VRMenuTemplate. In this case ((|item|))
    represents its submenu.
=== example
  a=VRMenuTemplate.new
  a.caption,a.item = "Test1","test1"
  
  b=VRMenuTemplate.new("Test2","test2")
  c=VRMenuTemplate.new("Test3","test3",VRMenuItem::GRAYED)

  d1=VRMenuTemplate.new("Test4-1","test41")
  d2=VRMenuTemplate.new("Test4-2","test42")
  d3=VRMenuTemplate.new("Test4-3","test43")

  d = VRMenuTemplate.new
  d.caption , d.item = "Test4" , [d1,d2,d3]
=end

class VRMenuItem
=begin
== VRMenuItem
This is a wrapper of SWin::Menu
=== Methods
--- new(menu)
    ((|menu|)) must be the instance of SWin::Menu.
--- state
    Returns the state of the menu item.
--- state=
    Sets the state of the menu item.
    state means
    * 0 MF_ENABLED
    * 1 MF_GRAYED
    * 2 MF_DISABLED
    * 8 MF_CHECKED
--- checked?
    Returns true if the menu item is checked. 
--- checked=
    Sets true/false whether the menu item is checked or not.
--- modify(text)
    Modifies the text of the menu item.
=end
  attr :name,1
  attr :etc,1

  GRAYED=1
  DISABLED=2
  CHECKED=8

  def initialize(menu)
    @menu=menu
  end
  def _vr_cmdhandlers
    {0=>[["clicked",MSGTYPE::ARGNONE,nil]],    # for menu
     1=>[["clicked",MSGTYPE::ARGNONE,nil]]}    # for key accelerator
  end

  def state= (st)
    @menu.setState(@etc,st)
  end
  def state
    @menu.getState(@etc)
  end
  def checked=(f)
    @menu.setChecked(@etc,f)
  end
  def checked?
    (self.state&8)==8
  end
  def modify(text)
    @menu.modify @etc,text
  end
end

module VRKeyAcceleratorUseable   # Thanks to Yukimisake-san.
  def menuTransTable(hsh) 
    tVirt = {"Ctrl" => 0x08,"Shift" => 0x04,"Alt" => 0x10}
    tVkey = {"F1"=>0x70,"F2"=>0x71,"F3"=>0x72,"F4"=>0x73,
             "F5"=>0x74,"F6"=>0x75,"F7"=>0x76,"F8"=>0x77,
             "F9"=>0x78,"F10"=>0x79,"F11"=>0x7a,"F12"=>0x7b,
             "Insert"=>0x2d,"Delete"=>0x2e,"PageUp"=>0x21,"PageDown"=>0x22,
             "End"=>0x23,"Home"=>0x24
    }
    r = []
    hsh.each{|k,v|
      fVirt = 1
      key = nil
      if txt = v[/\t.*/] then
        a = txt.strip!.split(/\+/)
        a[0,a.size-1].each{|ii|
            raise "Iregal Key" unless n = tVirt[ii]
            fVirt += n } if a.size > 1
        if (s = a[a.size-1]).size > 1 then
          unless key = tVkey[s] then raise "Iregal Key" end
        else
          key = (s.upcase)[0]
        end
      end
      if key then
        r << fVirt
        r << key
        r << k
      end
    }
    r
  end
end

module VRMenuUseable
=begin
== VRMenuUseable
Include this module to use menus.

=== Methods
--- newMenu(popup=false)
    Creates a new ((<VRMenu>)). If popup is true, the created menu is popup menu.
--- newPopupMenu
    Same as newMenu(true)
--- setMenu(menu)
    Sets the menu ((<VRMenu>)) to the window.
--- showPopup(popupmenu)
    Shows ((|popupmenu|)) at the cursor position
=end

  include VRKeyAcceleratorUseable

  SetForegroundWindow = Win32API.new("user32","SetForegroundWindow","I","I")

  def registerMenu(m,name,id,mcaption="")
    m.etc= id
    instance_eval("@"+name+"=m")
    @_vr_menus= {} unless defined?(@_vr_menus)
    @_vr_menucaptions= {} unless defined?(@_vr_menucaptions)
    @_vr_menus[id]= m
    @_vr_menucaptions[id] = mcaption
    m.name = name
    m
  end

  def newMenu(popup=false)
    if popup then
      menu=@screen.factory.newpopup
    else
      menu=@screen.factory.newmenu
    end
    VRMenu.new(menu,self)
  end

  def newPopupMenu    # contributed by Yuya-san
    return self.newMenu(true)
  end

  def setMenu(menu,keyacc=false)
    SetForegroundWindow.call(self.hWnd)
    if menu.is_a?(SWin::Menu) then  
      super menu
      return
    end

    @_vr_menu=menu
    @menu=menu
    super menu.menu
    if keyacc then
      tb = menuTransTable(@_vr_menucaptions)
      SWin::Application.setAccel(self,tb) 
    end
  end

  def showPopup(menu)
    SetForegroundWindow.call(self.hWnd)
    if menu.is_a?(SWin::Menu) then  
      m=menu
    else     # asumes it is VRMenu
      m=menu.menu
      @_vr_menu = menu
    end
    popupMenu m,*Cursor.get_screenposition
  end

  def setMenuByArray(arr)
    self.setMenu newMenu(arr)
  end

end


##############################
# Other Controls
#

module WStruct
  SCROLLINFO="llllll"
end

class VRScrollbar < VRControl 
  WINCLASSINFO = ["SCROLLBAR",0]
  attr :smallstep,1
  attr :longstep,1

  def vrinit
    setRange(0,100)
    self.pagesize=10
    self.position=50
    @smallstep, @longstep = 1,10
  end
  
  def setRange(min,max)
    sendMessage 0xe6,min.to_i,max.to_i
  end

  def position=(val)
    sendMessage 0xe0,val.to_i,1
  end
  def position
    sendMessage 0xe1,0,0
  end

  def pagesize=(p)
    si=[4*7,2,0,0,p.to_i,0,0].pack WStruct::SCROLLINFO
    sendMessage 0xe9,1,si  # SBM_SETSCROLLINFO
  end
  def pagesize
    si=[4*7,2,0,0,0,0,0].pack WStruct::SCROLLINFO
    sendMessage 0xea,1,si  # SBM_GETSCROLLINFO
    si.unpack(WStruct::SCROLLINFO)[4]
  end
end

class VRHScrollbar < VRScrollbar
  WINCLASSINFO = ["SCROLLBAR",0]
end
class VRVScrollbar < VRScrollbar
  WINCLASSINFO = ["SCROLLBAR",1]
end

module WMsg
  WM_HSCROLL=276
  WM_VSCROLL=277
end

module VRScrollbarContainer
  def scrollbarcontainerinit
    addHandler WMsg::WM_HSCROLL,"vrscroll",MSGTYPE::ARGINTINT,nil
    addHandler WMsg::WM_VSCROLL,"vrscroll",MSGTYPE::ARGINTINT,nil
    acceptEvents [ WMsg::WM_HSCROLL,WMsg::WM_VSCROLL]
    @_vr_scrollbars={}
  end
  def vrinit
    super
    scrollbarcontainerinit
  end

  def searchscrollbar(hwnd)
    @_vr_scrollbars=Hash.new unless defined? @_vr_scrollbars
    srl = @_vr_scrollbars[hwnd]   # false : to be ignored window, 0: form
    if srl==nil then
      @controls.each_value do |v| 
        if v.is_a?(SWin::Window) and v.hWnd==hwnd then
          if v.is_a?(VRScrollbar) then
            @_vr_scrollbars[hwnd] = srl = v
          elsif defined?(VRTrackbar) and v.is_a?(VRTrackbar) then
            @_vr_scrollbars[hwnd] = srl = false
          elsif defined?(VRUpdown) and v.is_a?(VRUpdown) then
            @_vr_scrollbars[hwnd] = srl = false
          else 
            @_vr_scrollbars[hwnd] = srl = 0
          end
          break
        end
      end # @controls.each_value
    end

    case srl
    when 0
      self
    when false
      nil
    when nil
      nil
    else
      srl
    end
  end

  def self_vrscroll(wparam,hwnd)
    srl = searchscrollbar(hwnd)
    return unless srl

    code=LOWORD(wparam)
    if code==4 then
      pos=HIWORD(wparam)
    else
      pos  = srl.position
    end
    case code
    when 0 
      srl.sendMessage 224,pos-srl.smallstep,1
    when 1
      srl.sendMessage 224,pos+srl.smallstep,1
    when 2
      srl.sendMessage 224,pos-srl.longstep,1
    when 3
      srl.sendMessage 224,pos+srl.longstep,1
    when 4
      srl.sendMessage 224,pos,1
    end
    controlmsg_dispatching(srl,"changed")
  end
end

# add
module VRContainersSet
  include VRScrollbarContainer
  INITIALIZERS.push :scrollbarcontainerinit
end


########################
#
# Control using VRPanel
#
class VRBitmapPanel < VRPanel
=begin
== VRBitmapPanel
Bitmap panel that can display static bitmap.

=== Methods
--- loadFile(filename)
    Loads ((|filename|)) as bitmap file.
--- createBitmap(info,bmp)
    Creates an bitmap from ((|info|)) and ((|bmp|)).
--- bmp
    The instance of displaying SWin::Bitmap.
=end
  attr :bmp,1

  include VRDrawable
  def vrinit
    super
    @bmp=nil
  end

  def loadFile(fname)
    @bmp=SWin::Bitmap.loadFile fname
    self.refresh
  end

  def createBitmap(info,bmp)
    @bmp=SWin::Bitmap.createBitmap info,bmp
    self.refresh
  end

  def self_paint
    drawBitmap @bmp if @bmp.is_a?(SWin::Bitmap)
  end
end


class VRCanvasPanel < VRPanel
=begin
== VRCanvasPanel
Bitmap Canvas panel that can display drawable bitmap.
=== Methods
--- createCanvas(w,h,color=0xffffff)
    Creates a new canvas dimensioned ( ((|w|)),((|h||)) ) and
    colored ((|color|)) on the background.
--- canvas
    Returns the instance of SWin::Canvas to draw it.
=end

  attr :canvas 

  include VRDrawable
  
  def createCanvas(w,h,color=0xffffff)
    @canvas=@screen.factory.newcanvas(w,h)
    @canvas.setBrush(color)
    @canvas.setPen(color)
    @canvas.fillRect(0,0,w,h)
    @canvas.setPen(0x0)
  end

  def vrinit
    super
    @canvas=nil
  end

  def self_paint
    bitblt @canvas if @canvas
  end
end

if VR_COMPATIBILITY_LEVEL then
  require VR_DIR + 'compat/vrcontrol.rb'
end
