raubarede/Ruiby

View on GitHub
lib/ruiby_gtk/ruiby_dsl.rb

Summary

Maintainability
A
0 mins
Test Coverage
# Creative Commons BY-SA :  Regis d'Aubarede <regis.aubarede@gmail.com>
# LGPL

=begin
general definition of Ruiby DSL, for GTK2. 

Most of this ressource are not thread-safe : use ruiby in the context
of main thread (thread which invoke Ruiby.start).

Exception : in thread, theses methods are frequently used. so they are thread-protected,
if they detect a invocation out of main thread, they auto-recall in a gui_invoke block :
    append_to(cont,&blk)       clear_append_to(cont,&blk)
    slot_append_before(w,wref) slot_append_after(w,wref)
    delete(w) 
    log(txt)

=end
require_relative 'ruiby_default_dialog'


module Ruiby_dsl
  include ::Gtk
  include ::Ruiby_default_dialog

  ############################ Slot : H/V Box or Frame

  def _nocodeeeeeeeeeee() end

  # container : vertical box, take all space available, sloted in parent ny default
  def stack(add1=true,&b)            _cbox(true,VBox.new(false, 2),add1,&b) end
  # container : horizontal box, take all space available, sloted in parent
  def flow(add1=true,&b)               _cbox(true,HBox.new(false, 2),add1,&b) end
  # container : vertical box, take only necessary space , sloted in parent
  def stacki(add1=true,&b)        _cbox(false,VBox.new(false, 2),add1,&b) end
  # container : horizontal box, take only necessary space , sloted in parent
  def flowi(add1=true,&b)               _cbox(false,HBox.new(false, 2),add1,&b) end

  # box { } used for container which manage the widget (as stack(false) {} ) 
  # use it for cell in table : table { row { cell(box { });... };... }
  def box() 
    box=VBox.new(false, 2)
    @lcur << box
    yield
    autoslot()
    @lcur.pop
  end
  # center { }  container which center his content (auto-sloted)
  def center() 
    autoslot()
    valign = Gtk::Alignment.new(0,0,0,0)
    @lcur.last.pack_start(valign,false,false,0)
    vbox=VBox.new(true, 0)
    valign.add(vbox)
    @lcur << vbox
    yield
    autoslot()
    @lcur.pop
  end

  # a box with border and texte title, take all space
  def frame(t="",add1=true,&b)      
    _cbox(true,Frame.new(t),add1) { stack { b.call } } 
  end
  # a box with border and texte title, take only necessary space
  def framei(t="",add1=true,&b)
    _cbox(false,Frame.new(t),add1) { stack { b.call } }
  end

  # private: generic packer
  def _cbox(expand,box,add1)
    autoslot()
    if add1
      expand ? @lcur.last.add(box) : @lcur.last.pack_start(box,false,false,3)
    end
    @lcur << box
    yield
    autoslot()
    @lcur.pop
  end

  # pack widget in parameter, share space with prother widget
  # this is the default: all widget will be sloted if they are not slotied
  # this is done by attribs(w) which is call after construction of almost all widget
  def slot(w)  @current_widget=nil; @lcur.last.add(w) ; w end
  
  # pack widet in parameter, take only necessary space
  def sloti(w) @current_widget=nil; @lcur.last.pack_start(w,false,false,3) ; w end

  # slot() precedently created widget if not sloted.
  # this is done by attribs(w) which is call after construction of almost all widget
  def autoslot(w=nil)
    (slot(@current_widget)) if @current_widget!=nil
    @current_widget=w 
  end
  # forget precedent widget constructed, same as autoslot()
  def razslot() @current_widget=nil; end

  # append the result of bloc parameter to a contener (stack or flow)
  # thread protected
  # Usage : 
  #     @stack= stack {}
  #    . . . . 
  #    append_to(@stack) { button("Hello") }
  def append_to(cont,&blk)
    if $__mainthread__ != Thread.current
      gui_invoke { append_to(cont,&blk) }
      return
    end
    @lcur << cont
    yield 
    autoslot()
    @lcur.pop
    show_all_children(cont)
  end

  # clear a containet (stack or flow)
  # thread protected
  def clear(cont) 
    if $__mainthread__ != Thread.current
      p "not in main thread"
      gui_invoke { clear(cont) }
      return
    end
    cont.children.each { |w| cont.remove(w) } 
  end
  
  # clear a container (stack or flow) and append the result of bloc parameter to this
  # container
  # thread protected
  def clear_append_to(cont,&blk) 
    if $__mainthread__ != Thread.current
      p "not in main thread"
      gui_invoke { clear_append_to(cont,&blk) }
      return
    end
    cont.children.each { |w| cont.remove(w) } 
    @lcur << cont
    yield 
    autoslot()
    @lcur.pop
    show_all_children(cont)
  end
  def show_all_children(c)
    return unless c
    c.each { |f|   show_all_children(f) if  f.respond_to?(:children) ; f.show() } ; c.show
  end
  # append the widget w before another one wref
  # thread protected
  def slot_append_before(w,wref)
    if $__mainthread__ != Thread.current
      gui_invoke { slot_append_before(w,wref) }
      return
    end
    parent=_check_append("slot_append_before",w,wref)
    parent.add(w)
    parent.children.each_with_index { |child,i| 
      next if child!=wref
      parent.reorder_child(w,i)
      break
    }
    w
  end
  # append the widget w after anotherone wref)
  # thread protected
  def slot_append_after(w,wref)
    if $__mainthread__ != Thread.current
      gui_invoke { slot_append_after(w,wref) }
      return
    end
    parent=_check_append("slot_append_after",w,wref)
    parent.add(w)
    parent.children.each_with_index { |child,i| 
      next if child!=wref
      parent.reorder_child(w,i+1)
      break
    }
    w
  end
  # delete a widget or a timer
  # thread protected
  def delete(w)
    if $__mainthread__ != Thread.current
      gui_invoke { delete(w) }
      return
    end
    if  Numeric === w && @hTimer[w]
      @hTimer[w]=false
    elsif  GLib::Timeout === w
      w.destroy
    else
      w.parent.remove(w) rescue error($!)
    end
  end
  def _check_append(name,w,wref)
    raise("#{name}(w,r) : Widget ref not created!") unless wref
    raise("#{name}(w,r) : new Widget not created!") unless w
    parent=wref.parent
    raise("#{name}(w,r): r=#{parent.inspect} is not a XBox or Frame !") unless !parent || parent.kind_of?(Container)
    raise("#{name}(w,r): r=#{parent.inspect} is not a XBox or Frame !") unless parent.respond_to?(:reorder_child)
    parent
  end


  def get_current_container() @lcur.last end

  # get a Hash aff all properties of a gtk widget
  def get_config(w)
    return({"nil"=>""}) unless w && w.class.respond_to?("properties")
    w.class.properties().inject({"class"=>w.class.to_s}) { |h,meth| 
      data=(w.send(meth) rescue nil)
      h[meth]=data.inspect.gsub(/^#/,'')[0..32]  if data 
      h
    }
  end

  ########################### raster images access #############################

  def get_icon(name)
    return name if name.index('.') && File.exists?(name)
    eval("Gtk::Stock::"+name.upcase) rescue nil
  end
  def get_stockicon_pixbuf(name)
    Image.new(eval("Gtk::Stock::"+name.upcase),IconSize::BUTTON).pixbuf
  end

  # get a Image widget from a file or from a Gtk::Stock
  # image can be a filename or a predefined icon in GTK::Stock
  # for file image, whe can specify a sub image (sqared) :
  #     filename.png[NoCol , NoRow]xSize
  #     filename.png[3,2]x32 : extract a icon of 32x32 pixel size from third column/second line
  #    see samples/draw.rb
  def get_image_from(name)
    if name.index('.') 
      return Image.new(name) if File.exists?(name)
      return _sub_image(name) if name.index("[")
      alert("unknown icone #{name}")
    end
    iname=get_icon(name)
    if iname
      Image.new(iname,IconSize::BUTTON)
    else
      nil
    end
  end
  def _sub_image(name)
    Image.new(get_pixbuf(name))
  end
  def get_pixbuf(name)
    @cach_pix={} unless defined?(@cach_pix)
    filename,px,py,bidon,dim=name.split(/\[|,|(\]x)/)
    if filename && px && py && bidon && dim && File.exist?(filename)
      dim=dim.to_i
      @cach_pix[filename]=Gdk::Pixbuf.new(filename) unless @cach_pix[filename]
      x0= dim*px.to_i
      y0= dim*py.to_i
      #p [x0,y0,"/",@cach_pix[filename].width,@cach_pix[filename].height]
      Gdk::Pixbuf.new(@cach_pix[filename],x0,y0,dim,dim)
    elsif File.exists?(name)
      @cach_pix[name]=Gdk::Pixbuf.new(name) unless @cach_pix[name]
      @cach_pix[name]
    elsif ! name.index(".")
      get_stockicon_pixbuf(name)
    else
      raise("file #{name} not exist");
    end
  end

  ############### Commands

  # general property automaticly applied for (almost) all widget (eval last argument a creation)
  def attribs(w,options)
      w.set_size_request(*options[:size]) if options[:size]    
      w.width_request=(options[:width]) if options[:width]
      w.height_request=(options[:height]) if options[:height]
      w.modify_bg(Gtk::STATE_NORMAL,color_conversion(options[:bg])) if options[:bg] # not work on window
      w.modify_fg(Gtk::STATE_NORMAL,color_conversion(options[:fg])) if options[:fg] # not work on window
      w.modify_font(Pango::FontDescription.new(options[:font])) if options[:font]
      autoslot(w)  # slot() precedent widget if existe and not already sloted, and declare this one as the precedent
      w
  end
  def color_conversion(color)
    case color 
      when String then ::Gdk::Color.parse(color).last
      when ::Gdk::Color then color
      else
        raise "unknown color : #{color}"
    end
  end
  def widget_properties(title=nil,w=nil) 
    widg=w||@current_widget||@lcur.last
    properties(title||widg.to_s,{},get_config(widg)) 
  end

  # create a bar (vertical or horizontal according to stack/flow current container) 
  def separator(width=1.0)  
    autoslot()
    sloti(HBox === @lcur.last ? VSeparator.new : HSeparator.new)  
  end

  # create  label, with text (or image if txt start with a '#')
  def label(text,options={})
    l=_label(text,options)
    attribs(l,options)
  end
  def labeli(text,options={}) sloti(label(text,options)) end 
  def _label(text,options={})
    if text && text[0,1]=="#"
      get_image_from(text[1..-1]);
    else
      Label.new(text);
    end
  end

  # create a icon with a raster file 
  # option can specify a new size : :width and :height, or :size  (square image)
  def image(file,options={})
    im=if File.exists?(file)
      pix=Gdk::Pixbuf.new(file) 
      pix=pix.scale(options[:width],options[:height],Gdk::Pixbuf::INTERP_BILINEAR) if options[:width] && options[:height]
      pix=pix.scale(options[:size],options[:size],Gdk::Pixbuf::INTERP_BILINEAR)  if options[:size] 
      Image.new(pix)
    else
      label("? "+file)
    end
    options.delete(:size)
    attribs(im,options)
  end
  # create a one-character size space, (or n character x n line space)
  def space(n=1) label(([" "*n]*n).join("\n"))  end

  # create  button, with text (or image if txt start with a '#')
  # block argument is evaluate at button click
  def button(text,option={},&blk)
    if text && text[0,1]=="#"
      b=Button.new()
      b.set_image(get_image_from(text[1..-1]))
    else
      b=Button.new(text);
    end
    b.signal_connect("clicked") { |e| blk.call(e) rescue error($!) } if blk
    attribs(b,option)
  end 
  
  # create  button, with text (or image if txt start with a '#')
  # block argument is evaluate at button click, slotied :
  #  packed without expand for share free place
  def buttoni(text,option={},&blk) sloti(button(text,option,&blk)) end 

  # horizontal toolbar of icon button and/or separator
  # if icon name contain a '/', second last is  tooltip text
  # Usage: 
  #   htoolbar(["text/tooltip", proc { },"separator" => "", ....]
  def htoolbar(items,options={})
    b=Toolbar.new
    b.set_toolbar_style(Toolbar::Style::ICONS)
    i=0
    items.each {|name_tooltip,v| 
      name,tooltip=name_tooltip,nil
      if ((ar=name_tooltip.split('/')).size>1)
        name,tooltip=*ar
        tooltip="  #{tooltip.capitalize}  "
      end
      iname=get_icon(name)
      w=if iname
        Gtk::ToolButton.new(iname).tap { |but|
          but.signal_connect("clicked") { v.call rescue error($!) } if v
          but.set_tooltip_text(tooltip) if tooltip
        }
      elsif name=~/^sep/i
        Gtk::SeparatorToolItem.new                
      elsif name=~/^right-(.*)/i
        Gtk::ToolButton.new(get_icon($1)).tap { |but|
          but.signal_connect("clicked") { v.call rescue error($!) } if v
          but.set_tooltip_text(tooltip) if tooltip
        }
      else
        puts "=======================\nUnknown icone : #{name}\n====================="
          puts "Icones dispo: #{Stock.constants.map { |ii| ii.downcase }.join(", ")}"
        Gtk::ToolButton.new(Stock::MISSING_IMAGE)
      end
      if w
        Ruiby.gtk_version(2) ? b.insert(i,w) : b.insert(w,i)
      end
      i+=1
     }
    attribs(b,options)
  end 

  ############### Inputs widgets

  #combo box, decribe  with a Hash choice-text => value-of-choice
  def combo(choices,default=-1,option={})
    if   Ruiby.gtk_version(2)
      w=ComboBox.new()
      choices.each do |text,indice|  
        w.append_text(text) 
      end
      w.set_active(default) if default>=0
      attribs(w,option)        
      w
    else
      w=ComboBoxText.new()
      choices.each do |text,indice|  
        w.append_text(text) 
      end
      w.set_active(default) if default>=0
      attribs(w,option)        
      w
    end
  end

  # to state button, with text for each state and a initiale value
  # value can be read by w.active?
  # callback on state change with new value as argument
  def toggle_button(text1,text2=nil,value=false,option={},&blk)
    text2 = "- "+text1 unless text2
    b=ToggleButton.new(text1);
    b.signal_connect("toggled") do |w,e| 
      w.label= w.active?() ? text2.to_s : text1.to_s 
      ( blk.call(w.active?()) rescue error($!) ) if blk
    end
    b.set_active(value)
    b.label= value ? text2.to_s : text1.to_s 
    attribs(b,option)        
    b
  end
  # create a checked button
  # no callback
  # state can be read by cb.active?
  def check_button(text="",value=false,option={})
    b=CheckButton.new(text)
          .set_active(value)
    attribs(b,option)
    b
  end
  # create a liste of radio button, horiznataly disposed
  def hradio_buttons(ltext=["empty!"],value=-1)
    flow(false) {
      b0=nil
      ltext.each_with_index {|t,i|
        b=if i==0
            b0=slot(RadioButton.new(t))
        else
            slot(RadioButton.new(b0,t))
        end
        if i==value
        b.toggled 
        b.set_active(true) 
        end
      }
    }
  end

  # create a liste of radio button, vrtically disposed
  def vradio_buttons(ltext=["empty!"],value=-1)
    stack(false) {
      b0=nil
      ltext.each_with_index {|t,i|
        b=if i==0
            b0=slot(RadioButton.new(t))
        else
            slot(RadioButton.new(b0,t))
        end
        if i==value
        b.toggled 
        b.set_active(true) 
        end
      }
    }
  end
  # create a text entry for keyboed input
  # if block defined, it while be trigger on ech of (character) change of the entry
  def entry(value,size=10,option={},&blk)
    w=Entry.new().tap {|e| e.set_text(value ? value.to_s : "") }
    after(1) do
      w.signal_connect("key-press-event") do |en,e|
        after(1) { blk.call(w.text) rescue error($!) }
        false
      end 
    end if block_given?
    attribs(w,option)
  end
  # create a integer text entry for keyboed input
  # option must define :min :max :by for spin button
  def ientry(value,option={},&blk)
    w=SpinButton.new(option[:min].to_i,option[:max].to_i,option[:by])
    w.set_numeric(true)
    w.set_value(value ? value.to_i : 0)
    
    w.signal_connect("value-changed") do |en|
      after(1) { blk.call(w.value) }
      false
    end if block_given?
    attribs(w,option)        
    w
  end

  # create a integer text entry for keyboed input
  # option must define :min :max :by for spin button
  def fentry(value,option={},&blk)
    w=SpinButton.new(option[:min].to_f,option[:max].to_f,option[:by].to_f)
    w.set_numeric(true)
    w.set_value(value ? value.to_f : 0.0)
    w.signal_connect("value-changed") do |en|
      after(1) { blk.call(w.value) rescue error($!)  }
      false
    end if block_given?
    attribs(w,option)        
    w
  end
  def field(tlabel,width,value,option={},&blk)
    e=nil
    flow {
      l=label(tlabel+ " : ")
      l.width_chars=width+3
      e=entry(value,option,&blk)
    }
    e
  end
  def fields(alabel,option={},&blk)   
    size=alabel.map {|t| t[0].size}.max
    stack {
      le=alabel.map { |(label,value)| field(label,size,value) }
      if block_given?
          button("Validation") { blk.call(*le.map {|t| t.text}) }
          button("Annulation") { blk.call(*le.map {|t| nil}) }
      end
    }
  end
  # create a slider
  # option must define :min :max :by for spin button
  # current value can be read by w.value
  # if bloc is given, it with be call on each change, with new value as parameter
  def islider(value,option={},&b)
    w=HScale.new(option[:min].to_i,option[:max].to_i,option[:by])
      .set_value(value ? value.to_i : 0)
    attribs(w,option)        
    w.signal_connect(:value_changed) { || b.call(w.value)  rescue error($!) } if block_given?
    w
  end

  # create a button wich will show a dialog for color choice
  # if bloc is given, it with be call on each change, with new color value as parameter
  def color_choice(text=nil,options={},&cb)
    b,d=nil,nil
    hb=flow(false) { b = slot(button(text.to_s||"Color?...")) ; d=slot(DrawingArea.new) }                    
    b.signal_connect("clicked") {
      c=ask_color
      if c
      d.modify_bg(Gtk::STATE_NORMAL,c)
      b.modify_bg(Gtk::STATE_NORMAL,c)
      cb.call(c) if block_given?
      end
    }
    attribs(hb,options)        
    hb
  end
  # create a drawing area, for pixel draw
  # option can define closure :mouse_down :mouse_up :mouse_move
  # for interactive actions
  # see tst.rb fo little example
  # see samples/draw.rb for a little vector editor...
  def canvas(width,height,option={})
    autoslot()
    w=DrawingArea.new()
    w.set_size_request(width,height)
    w.events |= ( ::Gdk::Event::BUTTON_PRESS_MASK | ::Gdk::Event::POINTER_MOTION_MASK | ::Gdk::Event::BUTTON_RELEASE_MASK)
    w.signal_connect( 'expose-event'  ) { |w1,e| 
      cr = w1.window.create_cairo_context
      cr.save {
        cr.set_line_join(Cairo::LINE_JOIN_ROUND)
        cr.set_line_cap(Cairo::LINE_CAP_ROUND)
            cr.set_line_width(2)
            cr.set_source_rgba(1,1,1,1)
            cr.paint
        if option[:expose]
          begin
            option[:expose].call(w1,cr) 
          rescue Exception => e
           bloc=option[:expose]
           option.delete(:expose)
           after(1) { error(e) }
           after(3000) {  puts "reset expose bloc" ;option[:expose] = bloc }
          end  
        end
      }
    }
    @do=nil
    w.signal_connect('button_press_event')   { |wi,e| @do = option[:mouse_down].call(wi,e)  rescue error($!)              ; force_update(wi) }  if option[:mouse_down]
    w.signal_connect('button_release_event') { |wi,e| (option[:mouse_up].call(wi,e,@do)  rescue error($!)) if @do ; @do=nil ; force_update(wi) if @do }  if option[:mouse_up]
    w.signal_connect('motion_notify_event')  { |wi,e| (@do = option[:mouse_move].call(wi,e,@do) rescue error($!)) if @do     ; force_update(wi) if @do }  if option[:mouse_move]
    w.signal_connect('key_press_event')  { |wi,e| (option[:key_press].call(wi,e) rescue error($!)) ; force_update(wi) }  if option[:key_press]
    attribs(w,option)    
    def    w.redraw() 
      self.queue_draw_area(0,0,1000,1000)
    end
    w
  end
  # update a canvas
  def force_update(canvas) canvas.queue_draw unless  canvas.destroyed?  end

  ############################ table
  # create a container for table-disposed widgets. this is not a grid!
  # table(r,c) { row { cell(w) ; .. } ; ... }
  def table(nb_col,nb_row,config={})
    table = Gtk::Table.new(nb_row,nb_col,false)
    table.set_column_spacings(config[:set_column_spacings]) if config[:set_column_spacings]
    #sloti(table)
    @lcur << table
    @ltable << { :row => 0, :col => 0}
    yield
    @ltable.pop
    @lcur.pop
    attribs(table,config)
  end
    # create a row. must be defined in a table closure    
  # can only contain cell(s) call
  def row()
    autoslot()
    @ltable.last[:col]=0 # will be increment by cell..()
    yield
    @ltable.last[:row]+=1
  end    
  # a cell in a row/table. take all space, centered
  def  cell(w)     cell_hspan(1,w)      end
  # a cell in a row/table. take space of n cells, horizontaly
  def  cell_hspan(n,w) razslot();@lcur.last.attach(w,@ltable.last[:col],@ltable.last[:col]+n,@ltable.last[:row],@ltable.last[:row]+1) ; @ltable.last[:col]+=n end 
  # a cell in a row/table. take space of n cells, vericaly
  def  cell_vspan(n,w) razslot();@lcur.last.attach(w,@ltable.last[:col],@ltable.last[:col]+1,@ltable.last[:row],@ltable.last[:row]+n) ; @ltable.last[:col]+=1 end 
  # keep empty n cell consecutive on current row
  def  cell_pass(n=1)  @ltable.last[:col]+=n end
  # a cell in a row/table. take space of n cells, horizontaly
  def  cell_span(n=2,w) cell_hspan(n,w) end

  # create a cell in a row/table, left justified
  def cell_left(w)     razslot();w.set_alignment(0.0, 0.5) rescue nil; cell(w) end
  # create a cell in a row/table, right justified
  def cell_right(w)    razslot();w.set_alignment(1.0, 0.5)rescue nil ; cell(w) end

  # create a hspan_cell in a row/table, left justified
  def cell_hspan_left(n,w)   razslot();w.set_alignment(0.0, 0.5)rescue nil ; cell_hspan(n,w) end
  # create a hspan_cell in a row/table, right justified
  def cell_hspan_right(n,w)  razslot();w.set_alignment(1.0, 0.5)rescue nil ; cell_hspan(n,w) end

  # create a cell in a row/table, top aligned
  def cell_top(w)      razslot();w.set_alignment(0.5, 0.0)rescue nil ; cell(w) end
  # create a cell in a row/table, bottom aligned
  def cell_bottom(w)   razslot();w.set_alignment(0.5, 1.0)rescue nil ; cell(w) end

  def cell_vspan_top(n,w)    razslot();w.set_alignment(0.5, 0.0)rescue nil ; cell_vspan(n,w) end
  def cell_vspan_bottom(n,w) razslot();w.set_alignment(0.5, 1.0)rescue nil ; cell_vspan(n,w) end

  # deprecated: see properties
  def propertys(title,hash,options={:edit=>false, :scroll=>[0,0]},&b)
    properties(title,hash,options,&b)
  end
  def _make_prop_line(prop_current,options,k,v)
    if k.to_s =~/^sep\d+$/
        cell_span(2,HSeparator.new)
    else
      cell_right(label(" "+k.to_s+" : "))
      cell_left(options[:edit] ? 
        (prop_current[k]=entry(v.to_s)) : 
        label(v.to_s))
    end
  end
  # show methods of a object/class in log window
  def show_methods(obj=nil,filter=nil)
    obj=self unless obj
    title="\n============ #{Class===obj.class ? obj : obj.class} ===========\n"
    data=(obj.methods-Object.methods).grep(filter || /.*/).sort.each_slice(3).map { |a,b,c| "%-30s| %-30s| %-30s" % [a,b,c]}.join("\n")
    footer="\n==================================================\n"
    log( title+data+footer)
  end
  
  # create a property shower/editor : vertical liste of label/entry representing the ruby Hash content
  # Edition: Option: use :edit => true for show value in text entry, and a validate button, 
  # on button action, yield of bloc parameter is done with modified Hash as argument
  # widget define set_data()methods for changing current value
  def properties(title,hash,options={:edit=>false, :scroll=>[0,0]})
    if ! defined?(@prop_index)
      @prop_index=0
      @prop_hash={}
    else
      @prop_index+=1
    end
    prop_current=(@prop_hash[@prop_index]={})
    widget=stacki {
    framei(title.to_s) {
       stack {
        if options[:scroll] &&  options[:scroll][1]>0
         vbox_scrolled(options[:scroll][0],options[:scroll][1]) {
           table(2,hash.size) {
            hash.each { |k,v| row {
              _make_prop_line(prop_current,options,k,v)
            }}
            }
          }
        else
         table(2,hash.size) {
          hash.each { |k,v| row {
              _make_prop_line(prop_current,options,k,v)
          }}
          }
        end
          if options[:edit]
            sloti(button("Validation") { 
              nhash=widget.get_data()
              if block_given? 
                yield(nhash)
              else
                hash.clear
                nhash.each { |k,v| hash[k]=v }
              end
            }) 
          end
        }
     }
    }    
    widget.instance_variable_set(:@prop_current,prop_current)      
    widget.instance_variable_set(:@hash_initial,hash)      
    def widget.set_data(newh)
      newh.each { |k,v| @prop_current[k].text=v.to_s }
    end
    def widget.get_data()
    @prop_current.inject({}) {|nhash,(k,w)| 
      v_old=@hash_initial[k]
      v_new=w.text
      vbin=case v_old 
        when String then v_new
        when Fixnum then v_new.to_i
        when Float  then v_new.to_f
        when /^(\[.*\])|(\{.*\})$/ then eval( v_new ) rescue error($!)
        else v_new.to_s
      end
      nhash[k]=vbin
      nhash
    }
    end
    widget
  end

  ###################################### notebooks

  # create a notebook widget. it must contain page() wigget
  # notebook { page("first") { ... } ; ... }
  def notebook() 
    nb = Notebook.new()
    slot(nb)
    @lcur << nb
    yield
    @lcur.pop
    nb
  end
  # a page widget. only for notebook container.
  # button can be text or icone (if startin by '#', as label)
  def page(title,icon=nil)
  if icon && icon[0,1]=="#" 
    l = Image.new(get_icon(icon[1..-1]),IconSize::BUTTON); #flow(false) { label(icon) ; label(title) }
  else
    l=Label.new(title)
  end 
  @lcur.last.append_page( stack(false)  { yield }, l )
  end

  ############################## Popup
  # popup { pp_item("text") { } ; ... }
  def popup(w=nil)
    w ||= @lcur.last() 
    ppmenu = Gtk::Menu.new
    @lcur << ppmenu 
    yield
    @lcur.pop
    ppmenu.show_all        
    w.add_events(Gdk::Event::BUTTON_PRESS_MASK)
    w.signal_connect("button_press_event") do |widget, event|
      ppmenu.popup(nil, nil, event.button, event.time) if (event.button == 3)
    end
    ppmenu
  end
  def pp_item(text,&blk)
    item = Gtk::MenuItem.new(text)
    item.signal_connect('activate') { |w| blk.call() }
    @lcur.last.append(item)
  end
  def pp_separator()
    item = Gtk::SeparatorMenuItem.new()
    @lcur.last.append(item)
  end
  ############################## Menu
  # create a application menu. must contain menu() {} :
  # menu_bar {menu("F") {menu_button("a") { } ; menu_separator; menu_checkbutton("b") { |w|} ...}}
  def menu_bar()
    @menuBar= MenuBar.new
    ret=@menuBar
    yield
    sloti(@menuBar)
    @menuBar=nil
    ret
  end

  # a vertial drop-down menu, only for menu_bar container
  def menu(text)
    raise("menu(#{text}) without menu_bar {}") unless @menuBar
    @filem = MenuItem.new(text.to_s)
    @menuBar.append(@filem)
    @mmenu = Menu.new()
    yield
    @filem.submenu=@mmenu
    show_all_children(@mmenu)
    @filem=nil
    @mmenu=nil
  end

  # create an text entry in a menu
  def menu_button(text="?",&blk)
  raise("menu_button(#{text}) without menu('ee') {}") unless @mmenu
  item = MenuItem.new(text.to_s)
  @mmenu.append(item)
  item.signal_connect("activate") { blk.call(text)  rescue error($!) }
  end

  # create an checkbox  entry in a menu
  def menu_checkbutton(text="?",state=false,&blk)
  raise("menu_button(#{text}) without menu('ee') {}") unless @mmenu
  item = CheckMenuItem.new(text,false)
  item.active=state
  @mmenu.append(item)
  item.signal_connect("activate") {
    blk.call(item,text) rescue error($!.to_s)
  } 
  end
  def menu_separator() @mmenu.append( SeparatorMenuItem.new ) end

  ############################## Accordion

  # create a accordion menu.     
  # must contain aitem() which must containe alabel() :
  # accordion { aitem(txt) { alabel(lib) { code }; ...} ... }
  def accordion() 
    @slot_accordion_active=nil #only one accordion active by window!
    w=stack { stacki {
      yield
    }}
    separator
    w
  end
  # create a hoizontral accordion menu.     
  def haccordion() 
    @slot_accordion_active=nil #only one accordion active by window!
    w=flow { flowi {
      yield
    }}
    separator
    w
  end
  #  a button menu in accordion
  def aitem(txt,&blk) 
    b2=nil
    button(txt) {
          clear_append_to(@slot_accordion_active) {} if @slot_accordion_active
          @slot_accordion_active=b2
          clear_append_to(b2) { 
            blk.call()
          }
    }
    b2=stacki { }
  end

  # create e entry in button associate vue af a accordion menu
  def alabel(txt,&blk)
    l=nil
    pclickable(proc { blk.call(l) if blk} ) { l=label(txt) }
  end

  ############################## Panned : 
  # split current frame in 2 panes
  # create a container which can cntaine 2 widget, separated by movable bar
  # block invoked must return a array of 2 box wich will put in the 2 panes
  # vertivaly disposed
  def stack_paned(size,fragment,&blk) _paned(false,size,fragment,&blk) end

  # split current frame in 2 panes
  # create a container which can cntaine 2 widget, separated by movable bar
  # block invoked must return a array of 2 box wich will put in the 2 panes
  # horizonaly disposed
  def flow_paned(size,fragment,&blk) _paned(true,size,fragment,&blk) end

  def _paned(vertical,size,fragment)
    paned = vertical ? HPaned.new : VPaned.new
    slot(paned)
    @lcur << paned
    frame1,frame2=*yield()
    @lcur.pop
    (frame1.shadow_type = Gtk::SHADOW_IN) rescue nil
    (frame2.shadow_type = Gtk::SHADOW_IN) rescue nil
    paned.position=size*fragment
    vertical ? paned.set_size_request(size, -1) : paned.set_size_request(-1,size)
    paned.pack1(frame1, true, false)
    paned.pack2(frame2, false, false)
    show_all_children(paned)
  end

  ##################### source editor

  # a source_editor widget : text as showed in fixed font, colorized (default: ruby syntaxe)
  # from: green shoes plugin
  # options= :width  :height :on_change :lang :font
  # @edit=source_editor().editor
  # @edit.buffer.text=File.read(@filename)
  def source_editor(args={}) # from green_shoes plugin
    begin
      require 'gtksourceview2'
    rescue Exception
      log('gtksourceview2 not installed!, please use text_area')
      return
    end
    args[:width]  = 400 unless args[:width]
    args[:height] = 300 unless args[:height]
    change_proc = proc { }
    (change_proc = args[:on_change]; args.delete :on_change) if args[:on_change]
    sv = ::Gtk::SourceView.new
    sv.show_line_numbers = true
    sv.insert_spaces_instead_of_tabs = false
    sv.smart_home_end = Gtk::SourceView::SMART_HOME_END_ALWAYS
    sv.tab_width = 4
    sv.buffer.text = (args[:text]||"").to_s
    sv.buffer.language = Gtk::SourceLanguageManager.new.get_language(args[:lang]||'ruby')
    sv.buffer.highlight_syntax = true
    sv.modify_font(  Pango::FontDescription.new(args[:font] || "Courier new 10")) 

    cb = ScrolledWindow.new
    cb.define_singleton_method(:editor) { sv }
    cb.define_singleton_method(:text=) { |t| sv.buffer.text=t }
    cb.define_singleton_method(:text) {  sv.buffer.text }

    cb.set_size_request(args[:width], args[:height])
    cb.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
    cb.set_shadow_type(SHADOW_IN)
    cb.add(sv)
    cb.show_all
    attribs(cb,{})    
  end

  # multiline entry
  # @edit=text_area(300,100).text_area
  # @edit.buffer.text="Hello!"
  def text_area(w=200,h=100,args={}) # from green_shoes app
      tv = Gtk::TextView.new
      tv.wrap_mode = TextTag::WRAP_WORD
      tv.buffer.text = args[:text].to_s if args[:text]
      tv.modify_font(Pango::FontDescription.new(args[:font])) if args[:font]
      tv.accepts_tab = true

      eb = Gtk::ScrolledWindow.new
      eb.set_size_request(w,h) 
      eb.add(tv)
      eb.define_singleton_method(:text_area) { tv }
      class << eb
      def text=(a)  self.children[0].buffer.text=a.to_s end
      def text()    self.children[0].buffer.text end
      def append(a) self.children[0].buffer.text+=a.to_s.encode("UTF-8") end
      end
      eb.children[0].buffer.text=(args[:text]||"")
      eb.show_all
      eb    
  end    

  ############################# calendar

    # Month Calendar with callback on month/year move and day selection :
  # calendar(Time.now-24*3600, :selection => proc {|day| } , :changed => proc {|widget| }
  # calendar respond to
  # *    set_time(time)  : toto and select the day of tume object
  # *    get_time()        : return time of selected day
  def calendar(time=Time.now,options={})
    c = Calendar.new
    #c.display_options(Calendar::SHOW_HEADING | Calendar::SHOW_DAY_NAMES |  
    #        Calendar::SHOW_WEEK_NUMBERS )
    after(1) { c.signal_connect("day-selected") { |w,e| options[:selection].call(w.day)  rescue error($!) } } if options[:selection]
    after(1) { c.signal_connect("month-changed") { |w,e| options[:changed].call(w)  rescue error($!) } }if options[:changed]
    calendar_set_time(c,time)
    class << c
      def set_time(time)
        select_month(time.month,time.year)
        select_day(time.day)
      end
      def get_time()
        year, month, day= *date() 
        Time.local(year, month, day) 
      end
    end
    attribs(c,options)

  end

  # deprecated : change the current selection of a calendar, by Time object
  def calendar_set_time(cal,time=Time.now)
    cal.select_month(time.month,time.year)
    cal.select_day(time.day)
  end

  ############################# Video
  # need gems gst, clutter-gtk, clutter-gstreamer
  # sho a video viewer, with size w andd h.
  # defined url= , play(), stop()

  
  def video(url=nil,w=300,h=200)
    require "gst"
    require "clutter-gtk"  # gem install clutter-gtk
    require "clutter-gst"  # gem install clutter-gstreamer
  
    clutter = ClutterGtk::Embed.new
    video=ClutterGst::VideoTexture.new
    clutter.stage.add_child(video)
    video.width=w
    video.height=h
    video.uri = url if url
    video.playing = false
    isNotify=false
    clutter.define_singleton_method(:url=) { |u| video.url = url }
    clutter.define_singleton_method(:play) { video.playing = true }
    clutter.define_singleton_method(:stop) { video.playing = false }
    clutter.define_singleton_method(:progress=) { |pp|
            video.progress=(pp) unless isNotify
    }
    if block_given?
      video.signal_connect("notify") { |o,v,param|
            isNotify=true ;
            yield(video.progress()) rescue p $! ;
            isNotify=false
      }
    end
    attribs(clutter,{})
  end
end

  ######### Scrollable stack container

  # create a Scrolled widget with a autobuild stack in it
  # stack can be populated 
  # respond to : scrooo_to_top; scroll_to_bottom,
  def scrolled(width,height,&b)  vbox_scrolled(width,height,&b) end
  def vbox_scrolled(width,height,&b)
    sw=slot(ScrolledWindow.new())
    sw.set_width_request(width)        if width>0 
    sw.set_height_request(height)    if height>0
    sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
    ret= stack(false,&b) if  block_given? 
    sw.add_with_viewport(ret)
    class << sw
      def scroll_to_top()    vadjustment.set_value( 0 )                     ; vadjustment.value_changed ; end
      def scroll_to_bottom() vadjustment.set_value( vadjustment.upper - 100); vadjustment.value_changed ; end
      #def scroll_to_left()   hadjustment.set_value( 0 ) end
      #def scroll_to_right()  hadjustment.set_value( hadjustment.upper-1 ) end
    end
    attribs(sw,{})
  end    

  # specific to gtk : some widget like label can't support click event, so they must
  # be contained in a clockable parent (EventBox)
  #  clickable(:callback_click_name) { label(" click me! ") }
  #  def callback_click_name(widget) ... end
  # clickable with methone callback by name
  def clickable(methode_name,&b) 
    eventbox = Gtk::EventBox.new
    eventbox.events = Gdk::Event::BUTTON_PRESS_MASK
    ret=_cbox(true,eventbox,true,&b) 
    eventbox.realize
    eventbox.signal_connect('button_press_event') { |w, e| self.send(methode_name,ret)  rescue error($!) }
    ret
  end

  # clickable with callback by closure :
  # pclicakble(proc { alert("e") }) { alabel("click me!") }
  def pclickable(aproc,&b) 
    eventbox = Gtk::EventBox.new
    eventbox.events = Gdk::Event::BUTTON_PRESS_MASK
    ret=_cbox(true,eventbox,true,&b) 
    eventbox.realize
    eventbox.signal_connect('button_press_event') { |w, e| aproc.call(w,e)  rescue error($!)  }
    ret
  end

  ##################################### List

  # create a verticale liste of data, with scrollbar if necessary
  # define methods: 
  #   list() : get (gtk)list widget embeded
  #   model() : get (gtk) model of the list widget
  #   clear()  clear content of the list
  #   set_data(array) : clear and put new data in the list
  #   selected() : get the selected item (or nil)
  #   index() : get the index  of selected item (or nil)
  def list(title,w=0,h=0)
    scrolled_win = Gtk::ScrolledWindow.new
    scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC)
    scrolled_win.set_width_request(w)    if w>0
    scrolled_win.set_height_request(h)    if h>0
    model = Gtk::ListStore.new(String)
    column = Gtk::TreeViewColumn.new(title.to_s,Gtk::CellRendererText.new, {:text => 0})
    treeview = Gtk::TreeView.new(model)
    if block_given?
      treeview.signal_connect("row-activated") do |view, path, _|
        iter = view.model.get_iter(path)
        yield iter[0]
      end        
    end
    treeview.append_column(column)
    treeview.selection.set_mode(Gtk::SELECTION_SINGLE)
    scrolled_win.add_with_viewport(treeview)
    def scrolled_win.list() children[0].children[0] end
    def scrolled_win.model() list().model end
    def scrolled_win.clear() list().model.clear end
    def scrolled_win.add_item(word)
      raise("list.add_item() out of main thread!") if $__mainthread__ != Thread.current
      list().model.append[0]=word  
    end
    def scrolled_win.set_data(words)
      raise("list.set_data() out of main thread!") if $__mainthread__ != Thread.current
      list().model.clear
      words.each { |w| list().model.append[0]=w }
    end
    def scrolled_win.selection() a=list().selection.selected ; a ? a[0] : nil ; end
    def scrolled_win.index() list().selection.selected end
    autoslot(scrolled_win)
    scrolled_win
  end

  # create a grid of data (as list, but multicolumn)
  # use set_data() to put a 2 dimensions array of text
  # same methods as list widget
  # all columnes are String type
  def grid(names,w=0,h=0)
    scrolled_win = Gtk::ScrolledWindow.new
    scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC)
    scrolled_win.set_width_request(w)    if w>0
    scrolled_win.set_height_request(h)    if h>0
    
    model = Gtk::ListStore.new(*([String]*names.size))
    treeview = Gtk::TreeView.new(model)
    treeview.selection.set_mode(Gtk::SELECTION_SINGLE)
    names.each_with_index do  |name,i|
      treeview.append_column(
        Gtk::TreeViewColumn.new( name,Gtk::CellRendererText.new,{:text => i} )
      )
    end
    if block_given?
      treeview.signal_connect("row-activated") do |view, path, column|
        iter = view.model.get_iter(path)
        yield(names.size.times.map { |i| iter[i] })
      end        
    end
    
    def scrolled_win.grid() children[0].children[0] end
    def scrolled_win.model() grid().model end
    def scrolled_win.add_row(words)
      l=grid().model.append()
      words.each_with_index { |w,i| l[i] = w.to_s }
    end
    $ici=self
    def scrolled_win.get_data()    
      raise("grid.get_data() out of main thread!")if $__mainthread__ != Thread.current
      @ruiby_data
    end
    def scrolled_win.set_data(data)    
      @ruiby_data=data
      raise("grid.set_data() out of main thread!")if $__mainthread__ != Thread.current
      grid().model.clear() ; data.each { |words| add_row(words) }
    end
    def scrolled_win.selection() a=grid().selection.selected ; a ? a[0] : nil ; end
    def scrolled_win.index() grid().selection.selected end
    
    scrolled_win.add_with_viewport(treeview)
    autoslot(nil)
    slot(scrolled_win)
  end

  # create a tree view of data (as grid, but first column is a tree)
  # use set_data() to put a  Hash of data
  # same methods as grid widget
  # a columns Class are distinges by column name :
  # <li>  raster image if name start with  a '#'
  # <li>  checkbutton  if name start with  a '?'
  # <li>  Integer      if name start with  a '0'
  # <li>  String         else
  def tree_grid(names,w=0,h=0,options={})
    scrolled_win = Gtk::ScrolledWindow.new
    scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_AUTOMATIC)
    scrolled_win.set_width_request(w)    if w>0
    scrolled_win.set_height_request(h)    if h>0
    scrolled_win.shadow_type = Gtk::SHADOW_ETCHED_IN
    
    types=names.map do |name|
     case name[0,1]
      when "#" then Gdk::Pixbuf
      when "?" then TrueClass
      when "0".."9" then Integer
      else String
     end
    end
    model = Gtk::TreeStore.new(*types)
    
    treeview = Gtk::TreeView.new(model)
    treeview.selection.set_mode(Gtk::SELECTION_SINGLE)
    names.each_with_index do  |name,i|
      renderer,symb= *(
        if    types[i]==TrueClass then     [Gtk::CellRendererToggle.new().tap { |r| r.signal_connect('toggled') { } },:win]
        elsif types[i]==Gdk::Pixbuf then [Gtk::CellRendererPixbuf.new,:active]
        elsif types[i]==Numeric then     [Gtk::CellRendererText.new,:text]
        else                              [Gtk::CellRendererText.new,:text]
        end
      )
      treeview.append_column(
        Gtk::TreeViewColumn.new( name.gsub(/^[#?0-9]/,""),renderer,{symb => i} )
      )
    end
    
    #------------- Build singleton
    
    def scrolled_win.init(types) @types=types end
    scrolled_win.init(types)
    def scrolled_win.tree() children[0].children[0] end
    def scrolled_win.model() tree().model end
    $ici=self
    def scrolled_win.get_data()    
      raise("tree.get_data() out of main thread!")if $__mainthread__ != Thread.current
      @ruiby_data
    end
    def scrolled_win.set_data(hdata,parent=nil,first=true)    
      raise("tree.set_data() out of main thread!")if $__mainthread__ != Thread.current
      if parent==nil && first
        @ruiby_data=hdata
        model.clear()
      end
      hdata.each do |k,v|
        case v
          when Array 
            set_row([k.to_s]+v,parent)
          when Hash 
            p=model.append(parent)
            p[0] =k.to_s
            set_data(v,p,false)
        end
      end
    end
    def scrolled_win.set_row(data,parent=nil)
      puts "treeview: raw data size nok : #{data.size}/#{data.inspect}" if data.size!=@types.size
      i=0
      c=self.model.append(parent)
      data.zip(@types) do |item,clazz|
        c[i]=if clazz==TrueClass then (item ? true : false)
          elsif clazz==Gdk::Pixbuf then $ici.get_pixbuf(item.to_s).tap {|a| p [item,clazz,a]}
          elsif clazz==Integer then item.to_i
          else item.to_s
        end
        i+=1
      end
    end
    def scrolled_win.selection() a=tree().selection.selected ; a ? a[0] : nil ; end
    def scrolled_win.index() tree().selection.selected end
    
    scrolled_win.add_with_viewport(treeview)
    autoslot(nil)
    slot(scrolled_win)
  end

  # TODO: test!
  def button_expand(text,initiale_state=false,options={},&b) 
    expander = Gtk::Expander.new(text)
    expander.expanded = initiale_state
    frame=box(&b)
    expander.add(frame)
    attribs(expander,options)
  end
  ######################## Dialog ##################

  # dialog_async("title",:response=> bloc {|dia,e| }) {
  #   flow { button("dd") ... }
  # }
  # Dialog content is build with bloc parameter.
  # Action on Ok/Nok/delete button make a call to :response bloc.
  # dialog is destoy if return value of :response is true
  #
  def dialog_async(title,config,&b) 
    dialog = Dialog.new("Message",
      self,
      Dialog::DESTROY_WITH_PARENT,
      [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
            [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])

    dialog.set_window_position(Window::POS_CENTER)
    
    @lcur << dialog.vbox
    stack { yield }
    @lcur.pop

    dialog.signal_connect('response') do |w,e|
      rep=config[:response].call(dialog,e) if 
      dialog.destroy if rep
    end
    dialog.show_all    
  end
  # dialog_sync("title") {
  #   flow { button("dd") ... }
  # }
  # Dialog contents is build with bloc parameter.
  # call is bloced until action on Ok/Nok/delete button 
  # return true if dialog quit is done by action on OK button

  def dialog(title="") 
    dialog = Dialog.new(title,
      self,
      Dialog::DESTROY_WITH_PARENT,
      [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_ACCEPT],
            [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_REJECT])
      
    @lcur << dialog.vbox
    stack { yield }
    @lcur.pop
    
    dialog.set_window_position(Window::POS_CENTER)
    dialog.show_all    
    rep=dialog.run
    dialog.destroy
    rep
  end

  ###################################### Logs

  # put a line of message text in log dialog (create and show the log dialog if not exist)
  def log(*txt)
    if $__mainthread__ && $__mainthread__ != Thread.current
      gui_invoke { log(*txt) }
      return
    end
    loglabel=_create_log_window()
    loglabel.buffer.text +=  Time.now.to_s+" | " + (txt.join(" ").encode("UTF-8"))+"\n" 
    if ( loglabel.buffer.text.size>1000*1000)
      loglabel.buffer.text=loglabel.buffer.text[-7000..-1].gsub(/^.*\n/m,"......\n\n")
    end
  end
  def _create_log_window() 
    return(@loglabel) if defined?(@loglabel) && @loglabel && ! @loglabel.destroyed?
    wdlog = Dialog.new("Logs : #{$0}",
      nil,
      0,
      [ Stock::OK, :none ])
    Ruiby.set_last_log_window(wdlog)
    logBuffer = TextBuffer.new
    @loglabel=TextView.new(logBuffer)
    sw=ScrolledWindow.new()
    sw.set_width_request(800)    
    sw.set_height_request(200)    
    sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)
    
    sw.add_with_viewport(@loglabel)
    
    wdlog.vbox.add(sw)
    wdlog.signal_connect('response') { wdlog.destroy }
    wdlog.show_all    
    @loglabel
  end

  ############################ define style !! Warning: specific to gtk
  # see http://ruby-gnome2.sourceforge.jp/hiki.cgi?Gtk%3A%3ARC
  # %GTK_BASEPATH%/share/themes/Metal/gtk-2.0/gtkrc
  #
  # style "mstyle"
  # {
  #     GtkWidget::interior_focus = 1
  #     GtkButton::default_spacing = { 1, 1, 1, 1 }
  #     GtkButton::default_outside_spacing = { 0, 0, 0, 0 }
  #     font_name = "lucida"
  #   bg_pixmap[NORMAL] = 'pixmap.png'
  #     bg[NORMAL]      = { 0.80, 0.80, 0.80 }
  #     bg[PRELIGHT]    = { 0.80, 0.80, 1.00 }
  #     bg[ACTIVE]      = { 0.80, 0.80, 0.80 }
  #     bg[SELECTED]    = { 0.60, 0.60, 0.80 }
  #     text[SELECTED]  = { 0.00, 0.00, 0.00 }
  #     text[ACTIVE]    = { 0.00, 0.00, 0.00 }
  # }    
  # class "GtkLabel" style "mstyle"
  #
  def def_style(string_style=nil)
    unless string_style
       fn=caller[0].gsub(/.rb$/,".rc")
       raise "Style: no ressource (#{fn} not-exist)" if !File.exists?(fn)
       string_style=File.read(fn)
    end
    begin
      Gtk::RC.parse_string(string_style)
      @style_loaded=true
    rescue Exception => e
      error "Error loading style : #{e}\n#{string_style}"
    end
  end

end