I am building a Ruby application which consists of the code responsible for the logic of the program and the one for GUI. Both parts of the code are split in classes and run in separate threads.
Ruby Gtk library is very poorly documented. I want to know how to update specific Gtk elements in real time (for instance, text in a label, which is in a window). I want to update a specific element every second.
I also want to find out how can threads exchange data. I have tried using Queue library. When I use it, I run into an error in the console:
undefined local variable or method `queue' for #<TimerWindow:0xa36d634 ptr=0xb4201178>
Program code:
require_relative 'notifications'
require_relative 'settings_reader'
require 'gtk3'
require "thread"
q = Queue.new
class TimerWindow < Gtk::Window
def initialize
@label = ""
super
init_ui
end
def init_ui
fixed = Gtk::Fixed.new
add fixed
button = Gtk::Button.new :label => "Quit"
button.set_size_request 80, 35
button.signal_connect "clicked" do
@label.set_text q.pop
end
fixed.put button, 50, 50
set_title "Tomatono"
signal_connect "destroy" do
Gtk.main_quit
end
set_border_width 10
@label = Gtk::Label.new "HEY"
fixed.put @label, 20, 20
set_default_size 250, 200
set_window_position :center
show_all
end
end
class Timer
def initialize
# Current time in seconds
@time = 0
settings = Settings_Reader.new
@work_time = Integer(settings.work_time) * 60
@break_time = Integer(settings.break_time) * 60
@work_text = settings.work_text
@return_text = settings.return_text
@break_text = settings.break_text
@work_notif_header = settings.work_notif_header
@break_notif_header = settings.break_notif_header
@status_notif_header = settings.status_notif_header
@work_status = settings.work_status
@break_status = settings.break_status
end
def launch
while true
work_time()
break_time()
end
end
def work_time()
puts @work_text
notification = Notif.new(@work_notif_header, @work_text)
notification.post
@time = 0
sleep(1)
while @time < @work_time
@time += 1
puts "#{min_remaining()} minutes remaining" if (@time % 60) == 0
if (@time % 60) == 0
notification = Notif.new(@work_notif_header, "#{@work_status} #{@time / 60} minutes.")
notification.post
end
q << @time
sleep(1)
end
end
def break_time
puts @break_text
@time = 0
sleep(1)
while @time < @break_time
@time += 1
puts "#{min_remaining()} minutes remaining" if (@time % 60) == 0
notification = Notif.new(@break_notif_header, "#{@break_status} #{@time / 60} minutes.")
notification.post
q << @time
sleep(1)
end
end
def reset
end
def stop_time
end
def min_remaining()
(1500 - @time) / 60
end
end
app = Thread.new {
timer = Timer.new
timer.launch
}
gui = Thread.new {
Gtk.init
window = TimerWindow.new
#window.update
Gtk.main
}
app.join
gui.join
Whenever I press the "Quit" button, I want the label text to change to the value set in the q variable, set in the Timer class (in the while loop). But it throws out an error that variable does not exist. Should it not be global?
No It is a local variable:
myglobal = "toto"
class Myclass
def initialize
@myvar = myglobal
end
def print
puts @myvar
end
end
an_instance = Myclass.new
an_instance.print
throw this error:
global_and_class.rb:5:in `initialize': undefined local variable or method `myglobal' for #<Myclass:0x00000000a74ce8> (NameError)
But it works if you specify myglobal as a global variable:
$myglobal = "toto"
class Myclass
def initialize
@myvar = $myglobal
end
def print
puts @myvar
end
end
an_instance = Myclass.new
an_instance.print
But you should be carefull with the use of global variable. Why not use the Queue instance as an argument for the initialize method ?
** Edit **
First of all here is a simple example that works with just a local variable:
#!/usr/bin/env ruby
require "gtk3"
label = Gtk::Label.new("test")
othert = Thread.new {
loop {
puts 'thread running';
label.text = Time.now.to_s; sleep 1 }
}
maint = Thread.new {
win = Gtk::Window.new
win.set_default_size 100, 30
win.add(label)
win.show_all
win.signal_connect("destroy") {othert.kill;Gtk.main_quit}
Gtk.main
}
maint.join
othert.join
Maybe you should start from this and see how to create you classes.
Edit 2
class TimerWindow < Gtk::Window
def initialize(label)
super()
add(label)
end
end
alabel = Gtk::Label.enw("test")
othert = Thread.new {
loop {
puts 'thread running';
label.text = Time.now.to_s; sleep 1 }
}
maint = Thread.new {
win = TimerWindow.new(alabel)
win.set_default_size 100, 30
win.show_all
win.signal_connect("destroy") {othert.kill;Gtk.main_quit}
Gtk.main
}
maint.join
othert.join