raubarede/Ruiby

View on GitHub
lib/Ruiby.rb

Summary

Maintainability
C
1 day
Test Coverage
###############################################################################################
#            ruiby.rb :   A DSL for Ruby/Gui
#                         Gtk based. Should will support SWT , Qt, WinForms ?...
###############################################################################################
# Creative Commons BY-SA :  Regis d'Aubarede <regis.aubarede@gmail.com>
# LGPL
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
# 
if RUBY_VERSION < "2.1.0"
  puts("Ruby version must be 2.1 or more (for gtk3 usage)") 
  exit(-1)
end
require 'tmpdir'
require 'timeout'
require 'thread'
require 'pathname'
if ! defined?(Gtk) # caller can preload  gtk3/gtk2, at his own risk...
  require 'gtk3' 
end
#require "gst"          # gem install gstreamer
#require "clutter-gtk"  # gem install clutter-gtk
#require "clutter-gst"  # gem install clutter-gstreamer

#require 'gtksourceviewX' # done by source_editor() tag, so only if source edit is needed


############# Patch Cairo::contet for buf in gtk3 2.2.3
#module Cairo
#  class Context
#    if method_defined?(:set_source_not_gdk_rgba)
#      def set_source_rgba(*rgba)
#         set_source_not_gdk_rgba(*rgba)
#      end
#    end
#  end
#end

module Ruiby
  DIR = Pathname.new(__FILE__).realpath.dirname.to_s
  MEDIA = Pathname.new(__FILE__).realpath.dirname.dirname.to_s+"/media"
  VERSION = IO.read(File.join(DIR, '../VERSION')).chomp
  GUI='gtk'
  
  # Update gui while event is pending
  # to be use if current algorithme is long and user want see gui update
  # log long list of message to screen...
  #
  def self.update() 
    Gtk.main_iteration while Gtk.events_pending?  
  end
  def self.gtk_version(major)
    p "executing Gtk::Version.or_later? ..." 
    Gtk::Version.or_later?(major, 0, 0)
    p ".or_later done."
  end
  def self.make_doc_api()
      lfile=Dir.glob(DIR+"/**/*dsl*.rb")
      ret=lfile.map do |src|
        File.read(src).split(/\r?\n/).grep(/^\s*def[\s\t]+[^_]/).map {|a|
          a.strip.gsub(/def\s+/,"").split(')')[0]+")" unless a.index(".")
        }
      end
      ret.flatten.compact.sort
  end
  class << self
      def set_style_provider(provider)
        @@provider=provider
      end
      def apply_provider(widget)
          return unless defined?(@@provider)
          widget.style_context.add_provider(@@provider, GLib::MAXUINT)
          if widget.is_a?(Gtk::Container)
            widget.each_all { |child| Ruiby.apply_provider(child) }
          elsif widget.respond_to?(:child)
            Ruiby.apply_provider(widget.child) 
          end
      end
  end
  ###########################################################
  #                S t o r a g e 
  ###########################################################
  
  # persistent stock a value associated to a name
  # dictionary is serialised (Marshalling) to a default file
  # file is /tmp/$0.storage
  def self.stock_put(name,value)
    db="#{Dir.tmpdir}/#{File.basename($0)}.storage"
    data={}
    (File.open(db,"r") { |f| data=Marshal.load(f) } if File.exists?(db)) rescue nil
    data[name]=value
      File.open(db,"w") { |f| Marshal.dump(data,f) }
  end
  # read a value associated to a name from persistant storage
  def self.stock_get(name,default="")
    db="#{Dir.tmpdir}/#{File.basename($0)}.storage"
    data={}
    (File.open(db,"r") { |f| data=Marshal.load(f) } if File.exists?(db) )rescue nil
    data[name] || default
  end
  # clear persistant strorage
  def self.stock_reset()
    db="#{Dir.tmpdir}/#{File.basename($0)}.storage"
    File.delete(db) if File.exists?(db)
  end
  ###########################################################
  #                start Ruiby application
  ###########################################################
  
  # start ruiby.
  # Usage: Ruiby.start { Win.new() }
  # One shot :reloading of source can be done, block wjile not be evaluated
  #
  # Thread.abort_on_exception, BasicSocket.do_not_reverse_lookup, 
  # trap('INT') are settings
  def self.start(&bloc)
    return if defined?($__MARKER_IS_RUIBY_INITIALIZED)
    $__MARKER_IS_RUIBY_INITIALIZED = true
    $stdout.sync=true 
    $stderr.sync=true 
    Thread.abort_on_exception = true  
    BasicSocket.do_not_reverse_lookup = true if defined?(BasicSocket)
    trap("INT") { exit(0) }
    yield
    secure_main()    
  end
  
  # Start ruiby with a main loop which trap all error :
  # A Exception in a callback will not kill the process.
  # Gtk Exception are trapped, so process should not exited by ruiby fault!
  def self.start_secure(&bloc)
    return if defined?($__MARKER_IS_RUIBY_INITIALIZED)
    $__MARKER_IS_RUIBY_INITIALIZED = true
    $stdout.sync=true 
    $stderr.sync=true 
    Thread.abort_on_exception = true  
    BasicSocket.do_not_reverse_lookup = true if defined?(BasicSocket)
    trap("INT") { exit(0) }
    yield
    secure_main()    
  end
  
  # Direct acces to Ruiby DSL
  # config can contain :title, :width, :height
  # Warning ! bolc is invoked vy instance_eval on window
  # def action() puts "Hello..." end
  # Ruib.app {
  #    stack do button("test") { action() } end
  # }
  def self.app(config={},&blk)
    $blk=blk
    klass = Class.new Ruiby_gtk do
      def initialize(title,w,h)
        super
        threader(10)
      end
    end
    klass.send(:define_method,:component,&blk)
    klass.send(:chrome,config[:chrome]) if config.has_key?(:chrome)
    start_secure { 
      w=klass.new(config[:title] || "",config[:width] ||600,config[:height] ||600) 
      w.send(:chrome,config[:chrome]) if config[:chrome]
      $app=w
    }
  end
  def self.set_last_log_window(win)
    @last_log=win
  end
  def self.destroy_log()
    return unless @last_log  && ! @last_log.destroyed?
    @last_log.destroy() rescue nil
    @last_log=nil
  end
end


require_relative 'ruiby_gtk/ruiby_default_dialog.rb'
require_relative 'ruiby_gtk/ruiby_dsl3.rb'
require_relative 'ruiby_gtk/ruiby_threader.rb'
require_relative 'ruiby_gtk/windows.rb'
require_relative 'ruiby_gtk/component.rb'
require_relative 'ruiby_gtk/editor.rb'
require_relative 'ruiby_gtk/systray.rb'
require_relative 'ruiby_gtk/dyn_var.rb'
require_relative 'ruiby_gtk/ruiby_terminal.rb'

Dir.glob("#{Ruiby::DIR}/plugins/*.rb").each do |filename| 
  autoload(File.basename(filename).split(".")[0].capitalize.to_sym, filename) 
end

module Kernel
  # do a gem require, and if fail, try to load the gem from internet.
  # asking  permission is done for each gem. the output of 'gem install'
  # id show in ruiby log window
  def ruiby_require(*gems)
    w=nil
    gems=gems.map {|g| g.split(/\s+/)}.flatten
    gems.each do|gem| 
      begin
        require gem
      rescue LoadError 
        w=Ruiby_dialog.new unless w
        rep=Message.ask("Loading #{gems.join(', ')}\n\n'#{gem}' package is missing. Can I load it from internet ?")
        exit(0) unless rep
        Ruiby.update
        require 'open3'
        w.log("gem install  #{gem} --no-ri --no-rdoc")
        Ruiby.update
        Open3.popen3("gem install  #{gem} --no-ri --no-rdoc") { |si,so,se| 
          q=Queue.new
          Thread.new { loop {q.push(so.gets) } rescue p $!; q.push(nil)}
          Thread.new { loop {q.push(se.gets) } rescue p $!; q.push(nil)}
          str=""
          while str
            (timeout(90) { str=q.pop } ) rescue p $!
            (w.log(str);str="") if str && str.size>0
            Ruiby.update                        
          end
        }
        w.log "done!"
        Ruiby.update
        Gem.clear_paths() 
        begin
          require(gem) 
          w.log("loading '#{gem}' ok!")
          Ruiby.update
        rescue Exception => e
          Message.alert("Gem #{gem} unknown ! #{e}")
          exit(-1)
        end
      end        
    end
    w.destroy() if w
    Ruiby.update
  end
  # run gtk mainloop with trapping gtk/callback error
  # used by sketchi.rb, not good
  # see Ruiby.secure_main { }
  # if EventMachine is present, do loop with wixed EM/main_iteration
  def secure_main()
    if defined?(EM)
        puts "Ruiby mainloop: EM is detected, mainloop while be mixed EM/Gtk"
        EM::run do
          give_tick = proc do
            begin
              Gtk::main_iteration
              EM.next_tick(give_tick); 
            rescue Exception => e
              exit(0) if e.to_s=="exit"
              $__mainwindow__.error("Error GTK : "+e.to_s + " :\n     " +  e.backtrace[0..10].join("\n     "))
            end
          end
          give_tick.call
        end
    else
      eend=false
      begin 
        Gtk.main 
        exit(0)
      rescue Exception => e
        exit(0) if e.to_s=="exit"
        puts("Error GTK : "+e.to_s + " :\n     " +  e.backtrace.join("\n     "))
      end while ! eend
    end
  end
end