How to create Ruby Tk radiobuttons in nested loops - ruby

Using Ruby Tk, I'm wanting to create sets of radio buttons in nested loops: an outer loop for devices (Foo, Bar, Baz), and inner loop for actions on each device (Start, Stop, Pause, Nuke).
Current code:
require 'tk'
device_names = %w/Foo Bar Baz/
action_names = %w/Start Pause Stop Nuke/
button_variables = Array.new(device_names.size)
root = TkRoot.new
foo = bar = nil
device_names.each_with_index do |device_name, i|
TkLabel.new(root) do
text device_name
pack {}
end
action_names.each_with_index do |action_name, j|
TkRadiobutton.new(root) do
text action_name
value j
variable button_variables[i]
pack {}
end
end
end
Tk.mainloop
This produces the 12 radiobuttons, but when I click on any of the 4 Pause buttons, for example, all 4 Pause buttons are set.
Other variations of the code produce the 12 radiobuttons, but all as one set; i.e., only one of the 12 is set at any given time.
What's needed to group these into 3 sets of 4 radiobuttons?
(This code is pared down from a much larger application, which is why it does not make much sense symantically.)

The value passed as variable is the name of a global variable.
The outer loop needs to dynamically define an instance variable, via instance_variable_set:
This code works:
require 'tk'
device_names = %w/Foo Bar Baz/
action_names = %w/Start Pause Stop Nuke/
root = TkRoot.new
device_names.each do |device_name|
TkLabel.new(root) do
text device_name
pack {}
end
instance_variable_set("##{device_name}", nil)
action_names.each do |action_name|
TkRadiobutton.new(root) do
text action_name
value action_name
variable device_name
pack {}
end
end
end
Tk.mainloop

Related

How to skip an element when iterating through a list Ruby

I have the following script which loops through an element list:
$browser.divs(class:'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class:'menu-index-page__items')
cc.lis(class:'menu-index-page__item').each do |lit|
lit.fire_event :click
sleep(5)
Once in a while the name of the class list changes to:
menu-index-page__item menu-index-page__item--unavailable
This breaks the script, and I want to be able to skip this whenever it comes up and continue with the original script.
Assuming that you are using Watir v6.5+, the :class locator supports excluding classes. You can find all elements that include class "a", but not "b" by doing:
browser.elements(class: ['a', '!b'])
For your specific example, you can do:
cc.lis(class: ['menu-index-page__item', '!menu-index-page__item--unavailable']).each do |lit|
# your actions on lit
end
When you include the lis using class, you have to be careful because when you locate using menu-index-page__item, it locates everything which includes menu-index-page__item so it locates menu-index-page__item menu-index-page__item--unavailable as well. So try forming xpath in this place, it would work
$browser.divs(class: 'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class: 'menu-index-page__items')
cc.lis(xpath: "//li[#class='menu-index-page__item']").each do |lit|
lit.fire_event :click
end
end
Or you can exclude the specific list this way
$browser.divs(class:'menu-index-page__menu-category').map do |cat|
cate = cat.h3.text
puts cate
cc = cat.ul(class:'menu-index-page__items')
cc.lis(xpath: "//li[not(#class=menu-index-page__item menu-index-page__item--unavailable')]").each do |lit|
lit.fire_event :click
end
end
And also never do click via fireevent because that's the javascript click, it's not a selenium click, you would be missing many events which would be triggered after the click.
So perform
lit.click

Sharing list between functions

I recently asked another question asking how to pass a list from one function to another, which was kindly answered by #Modred. Essentially what I've got now is this:
def run_command():
machines_off = []
# Some stuff .....
machines_off.append(machineName)
# Some stuff ....
wol_machines(machines_off)
def wol_machines(machines_off):
# Some stuff ....
(I've cleared off all the non-essential code for this example as it's 300+ lines).
Now, each function is called by clicking on a tkinter button; the run_command is always run and sometimes will add items to the list 'machines_off'. I only want it to action machines_off if the second function button is clicked. At the moment after clicking on the run_command button, it runs through the whole script, including the second function when I don't want it to. I assume as I'm forwarding the list onto the second function (with the 5th line), it's bypassing the need to click the second functions button.
What do I need to change/add to allow the list from the first function to be available to the second, but not action it until needed??
Many thanks,
Chris.
I'm guessing your code looks something like:
from Tkinter import Tk, Button
def run_command():
machines_off = []
# Some stuff .....
machineName = "foo"
machines_off.append(machineName)
# Some stuff ....
wol_machines(machines_off)
def wol_machines(machines_off):
print "wol_machines was called"
print "contents of machines_off: ", machines_off
# Some stuff ....
root = Tk()
a = Button(text="do the first thing", command=run_command)
b = Button(text="do the second thing", command=wol_machines)
a.pack()
b.pack()
root.mainloop()
If you want the functions to execute independently of one another, you shouldn't call wol_machines from within run_command. You'll have to find some other way for both functions to see the list. One way of doing this is to use a global value.
from Tkinter import Tk, Button
machines_off = []
def run_command():
#reset machines_off to the empty list.
#delete these next two lines if you want to retain old values.
global machines_off
machines_off = []
# Some stuff .....
machineName = "foo"
machines_off.append(machineName)
# Some stuff ....
def wol_machines():
print "wol_machines was called"
print "contents of machines_off: ", machines_off
# Some stuff ....
root = Tk()
a = Button(text="do the first thing", command=run_command)
b = Button(text="do the second thing", command=wol_machines)
a.pack()
b.pack()
root.mainloop()
This is the simplest change to your original code that can give you your desired behavior, but global values are generally considered a sign of bad design. A more object oriented approach could localize the global into a class attribute.
from Tkinter import Tk, Button
class App(Tk):
def __init__(self):
Tk.__init__(self)
self.machines_off = []
a = Button(self, text="do the first thing", command=self.run_command)
b = Button(self, text="do the second thing", command=self.wol_machines)
a.pack()
b.pack()
def run_command(self):
#reset machines_off to the empty list.
#delete this next line if you want to retain old values.
self.machines_off = []
# Some stuff .....
machineName = "foo"
self.machines_off.append(machineName)
# Some stuff ....
def wol_machines(self):
print "wol_machines was called"
print "contents of machines_off: ", self.machines_off
# Some stuff ....
root = App()
root.mainloop()

How can I tab dynamically with capybara?

I am testing a web app with multiple dynamic rows. With nothing to scope and grab in the vicinity. I get to the particular field by grabbing something I can id, and tabbing to the text box or selector I wish to manipulate.
It looks like this...
editor = page.find_by_id('grabbable')
editor.native.send_keys(:tab, :tab, "Hello World")
What I'd like to do is something like...
tab_amount = tabs(2)
editor = page.find_by_id('grabbable')
editor.native.send_keys(tab_amount, "Hello World")
...
def tabs(amount)
tab_object = :tab
while amount > 1
tab_object = tab_object + :tab
amount = amount - 1
end
return tab_amount
end
Is such a dynamic tab possible?
what about something like
def tabs(amount)
tab_object = Array.new(amount, :tab)
end
editor.native.send_keys(*tabs(3), "Hello World")
some info on splat here
http://www.ruby-doc.org/core-2.0/doc/syntax/calling_methods_rdoc.html#label-Array+to+Arguments+Conversion
Here is what I ended up doing...
def autotab(amount)
tab = Array.new
amount.times do
tab << :tab
end
return tab
end

How to override or edit the last printed lines in a ruby CLI script?

I am trying to build a script that gives me feedback about progress on the command-line. Actually it is just putting a newline for every n-th progress step made. Console looks like
10:30:00 Parsed 0 of 1'000'000 data entries (0 %)
10:30:10 Parsed 1'000 of 1'000'000 data entries (1 %)
10:30:20 Parsed 2'000 of 1'000'000 data entries (2 %)
[...] etc [...]
11:00:00 Parsed 1'000'000 of 1'000'000 data entries (100 %)
Even if timestamp and progressnumbers are fictional, you should see the problem.
What I want is to do it "wget-style" with a progressbar updated on the command line, with linewidth in mind.
First I thought about the use of curses because I had hands on as I tried to learn C, but I never could get warm with it, also I think it is bloated for the purpose of manipulating just a few lines. Also I dont need any coloring. Also most other libraries I found seemed to be specialized for coloring.
Can someone help me with this problem?
A while ago I created a class to be a status text on which you can change part of the content of the text within the line. It might be useful to you.
The class with an example use are:
class StatusText
def initialize(parms={})
#previous_size = 0
#stream = parms[:stream]==nil ? $stdout : parms[:stream]
#parms = parms
#parms[:verbose] = true if parms[:verbose] == nil
#header = []
#onChange = nil
pushHeader(#parms[:base]) if #parms[:base]
end
def setText(complement)
text = "#{#header.join(" ")}#{#parms[:before]}#{complement}#{#parms[:after]}"
printText(text)
end
def cleanAll
printText("")
end
def cleanContent
printText "#{#parms[:base]}"
end
def nextLine(text=nil)
if #parms[:verbose]
#previous_size = 0
#stream.print "\n"
end
if text!=nil
line(text)
end
end
def line(text)
printText(text)
nextLine
end
#Callback in the case the status text changes
#might be useful to log the status changes
#The callback function receives the new text
def onChange(&block)
#on_change = block
end
def pushHeader(head)
#header.push(head)
end
def popHeader
#header.pop
end
def setParm(parm, value)
#parms[parm] = value
if parm == :base
#header.last = value
end
end
private
def printText(text)
#If not verbose leave without printing
if #parms[:verbose]
if #previous_size > 0
#go back
#stream.print "\033[#{#previous_size}D"
#clean
#stream.print(" " * #previous_size)
#go back again
#stream.print "\033[#{#previous_size}D"
end
#print
#stream.print text
#stream.flush
#store size
#previous_size = text.gsub(/\e\[\d+m/,"").size
end
#Call callback if existent
#on_change.call(text) if #on_change
end
end
a = StatusText.new(:before => "Evolution (", :after => ")")
(1..100).each {|i| a.setText(i.to_s); sleep(1)}
a.nextLine
Just copy, paste in a ruby file and try it out. I use escape sequences to reposition the cursor.
The class has lots of features I needed at the time (like piling up elements in the status bar) that you can use to complement your solution, or you can just clean it up to its core.
I hope it helps.
In the meanwhile I found some gems that give me a progressbar, I will list them up here:
ProgressBar from paul at github
a more recent version from pgericson at github
ruby-progressbar from jfelchner at github
simple_progressbar from bitboxer at github
I tried the one from pgericson and that from jfelchner, they both have pros and cons but also both fits my needs. Probably I will fork and extend one of them in the future.
I hope this one helps others to find faster, what I searched for months.
Perhaps replace your outputting to this:
print "Progress #{progress_var}%\r"

Strange treeview behaviour: strikeout text only if bold

I need to show some treeview item text striked out text into a QT treeview from Ruby.
After some reading on QT documentation and much coding, I found that only when rendering font in bold, also the strikeout was rendered.
So I wonder, where I'm doing wrong?
This is the code to achive the result shown above. Note as I set strikeout for every even row item.
I'm using Ruby 1.8.7 and Qt 4.6.2 and qt4ruby 4.4.3-6 on Mandriva Linux.
require 'Qt4'
require 'date'
class MyStandardItem < Qt::StandardItem
def initialize(str = nil)
super str
end
def data(role = Qt::UserRole + 1)
return super(role) unless role == Qt::FontRole
ret_val = Qt::Font.new()
#parameters for "fromString":font family, pointSizeF, pixelSize, QFont::StyleHint, QFont::Weight, QFont::Style, underline, strikeOut, fixedPitch, rawMode
ret_val.fromString "sans serif,-1,-1,0,0,0,0,0,0,0"
case role
when Qt::FontRole
ret_val.setStrikeOut(true) if (index.row % 2) == 0
if index.column == 1
ret_val.weight = Qt::Font.Bold
else
ret_val.weight = Qt::Font.Normal
end
return Qt::Variant.fromValue(ret_val)
end
return ret_val
end
end
Qt::Application.new(ARGV) do
treeview = Qt::TreeView.new do
model = Qt::StandardItemModel.new self
head = [MyStandardItem.new "Qt v. #{Qt.version}"]
head << MyStandardItem.new("Ruby v. #{VERSION}")
head << MyStandardItem.new("Qt4Ruby v. 4.4.3-6 (Mandriva)")
model.append_row head
(1..10).each do |i|
col0 = MyStandardItem.new 'some text'
col0.check_state = ((i % 3) == 0)? Qt.Checked : Qt.Unchecked
col0.checkable = true
col0.editable= false
col1 = MyStandardItem.new "line ##{i}"
col2 = MyStandardItem.new((Date.today + i).strftime '%d/%m/%y')
model.append_row [col0, col1, col2]
end
self.model = model
show
end
exec
end
Eventually I find an hackish trick to overcome this problem. Playing around after reading again the enum QFont::Weight description I tried to set
ret_val.weight = 51 # Qt::Font.Normal value is 50
instead of
ret_val.weight = Qt::Font.Normal
and magically the normal text appears striked out!
Maybe this strange behaviour is due to a bug on QT?

Resources