RubyQt Crashing on QTableWidget - ruby

I'm getting some weirdness with QtRuby when using a TableWidget. The table widget loads, but when you click on the elements in the row, the app segfaults and crashes.
require 'Qt4'
class SimpleModel < Qt::AbstractTableModel
def rowCount(parent)
return 1
end
def columnCount(parent)
return 1
end
def data(index, role=Qt::DisplayRole)
return Qt::Variant.new("Really Long String") if index.row == 0 and index.column == 0 and role == Qt::DisplayRole
return Qt::Variant.new
end
end
Qt::Application.new(ARGV) do
Qt::TableWidget.new(1, 1) do
set_model SimpleModel.new
show
end
exec
end
The backtrace seems to imply that it is bombing in mousePressEvent
#6 0x01624643 in QAbstractItemView::pressed(QModelIndex const&) () from /usr/lib/libQtGui.so.4
#7 0x016306f5 in QAbstractItemView::mousePressEvent(QMouseEvent*) () from /usr/lib/libQtGui.so.4
If I override mousePressEvent and mouseMoveEvent, these kinds of crashes no longer happen. Am I doing something wrong over here, or can I chalk this up as a bug in QtRuby?
I'm on fedora11, with the following packages installed:
QtRuby-4.4.0-1.fc11.i586
ruby-1.8.6.369-1.fc11.i586
These crashes also happen when running the script on Windows.

You're using a Qt::TableWidget when you should be using a Qt::TableView. The following code fixed the crash for me. In addition to a switch from Qt::TableWidget to Qt::TableView, I also reimplemented the index method, just in case. :)
require 'Qt4'
class SimpleModel < Qt::AbstractTableModel
def rowCount(parent)
return 1
end
def columnCount(parent)
return 1
end
def data(index, role=Qt::DisplayRole)
return Qt::Variant.new("Really Long String") if index.row == 0 and index.column == 0 and role == Qt::DisplayRole
return Qt::Variant.new
end
def index(row, column, parent)
if (row > 0 || column > 0)
return Qt::ModelIndex.new
else
return createIndex(row, column, 128*row*column)
end
end
end
Qt::Application.new(ARGV) do
Qt::TableView.new do
set_model SimpleModel.new
show
end
exec
end

Related

Music and image failed to initialize

I was trying to make a multiple-choice music player using Gosu but the picture and music Iwanted would not initialize despite the program running, it showed a black screen. The single block of codes works:
require 'gosu'
require './input_functions'
class MW < Gosu::Window
def initialize
super 200, 135
#beth = Gosu::Image.new("media/beth.jpg")
#song = Gosu::Song.new("media/3rdmovement.mp3")
#song.play
end
def draw
#beth.draw(0, 0)
end
end
window = MW.new
window.show
But adding the multiple choice elements would not work(note: read_integer_in_range is defined in input function, the name itself is self explanatory). Full code:
require 'gosu'
require './input_functions'
class MW < Gosu::Window
def initialize
super 200, 135
#beth = Gosu::Image.new("media/beth.jpg")
#dimitri = Gosu::Image.new("media/dimitri.png")
#vil = Gosu::Image.new("media/vilva.png")
#song = Gosu::Song.new("media/3rdmovement.mp3")
#song2=Gosu::Song.new("media/2ndwaltz.mp3")
#song3=Gosu::Song.new("media/1stseason.mp3")
read_integer_in_range( "What song you want play
1st Spring
2nd Waltz
3rd movement", 1, 3)
choice = gets.chomp.to_i()
case choice
when 1
#song3.play
#vil.draw(0, 0)
when 2
#song2.play
#dimitri.draw(0, 0)
when 3
#song.play
draw_beth()
end
end
end
def draw_beth
#beth.draw(0, 0)
end
window = MW.new
window.show
All of the Png/Jpg and mp3 file works just fine..
I tried separating the draw_beth to call it in case but it did not work. I hope some passing by could help me with this one
As I can see, you are creating a music player with GUI, and if you are doing so, you shouldn't use gets function, instead you should track for the cursor's position and return a test value; for example:
def update
#locs = [mouse_x, mouse_y]
#cursor_choice_album = mouse_over_button(mouse_x, mouse_y)
end
def needs_cursor?; true; end
def mouse_over_button(mouse_x, mouse_y)
if ((mouse_x > 100 and mouse_x < 500) and (mouse_y < 500 and mouse_y > 100))
return 1
end
then you can use the case condition in the "button down ID" function

When I add a byebug statement to my code the bug I was investigating disappears

I have run across a strange bug I'm not sure how to solve. I am trying to make a program that solves Wordle in the browser using Ruby and Selenium. I noticed that starting with the second guess, the program would just keep guessing the same word instead of incorporating feedback and fetching a new word. So I added a byebug statement to see what was going on, and then it started working. If I remove the byebug statement, the bug appears again. I couldn't believe it so I pushed to Github and cloned on another computer and the same thing happened again. Basically, as long as I put a byebug statement anywhere after line 24, the code works as expected. Here is the code.
require 'webdrivers'
require './words.rb'
require 'byebug'
class WordleSolver
attr_reader :driver
def initialize
options = Selenium::WebDriver::Options.chrome
#driver = Selenium::WebDriver.for :chrome, options: options
#driver.manage.timeouts.implicit_wait = 10
#driver.get 'https://www.nytimes.com/games/wordle/index.html'
#game_div = driver.find_element(css: 'game-app').shadow_root.find_element(css: 'div#game')
#words = PossibleWords::WORDS
#word = ['', '', '', '', '']
#present = {}
#absent = []
#current_row = 1
end
def solve
close_modal
guess('store')
5.times do
get_feedback
break if game_won?
next_guess = formulate_guess
guess(next_guess)
sleep(3)
end
end
def game_won?
#word.each { |letter| return false if letter == '' }
true
end
def close_modal
#game_div.find_element(css: 'game-modal').shadow_root.find_element(css: 'div.close-icon').click
end
def guess(word)
#driver.action.send_keys(word).send_keys(:enter).perform
end
def formulate_guess
#words.each do |word|
return word if valid_guess?(word)
end
'guess'
end
def valid_guess?(word)
#word.each_with_index do |letter, index|
if letter != ''
return false if word[index] != letter
end
end
#absent.each do |letter|
return false if word.include?(letter)
end
#present.each do |letter, incorrect_positions|
return false if !word.include?(letter)
incorrect_positions.each do |position|
return false if word[position] == letter
end
end
end
def get_feedback
row = #game_div.find_element(css: "game-row:nth-of-type(#{#current_row})").shadow_root
1.upto(5) do |n|
tile = row.find_element(css: "game-tile:nth-of-type(#{n})")
letter = tile.attribute("letter")
evaluation = tile.attribute("evaluation")
add_feedback(letter, evaluation, n - 1)
end
#current_row += 1
end
def add_feedback(letter, evaluation, position)
case evaluation
when 'absent'
#absent.push(letter)
when 'present'
if #present[letter]
#present[letter].push(position)
else
#present[letter] = [position]
end
when 'correct'
#present.delete(letter)
#word[position] = letter if #word[position] == ''
end
end
end
begin
solver = WordleSolver.new
solver.solve
sleep(5)
ensure
solver.driver.quit
end
TLDR when I add a byebug statement to my code the bug I was investigating disappears
The comment by BroiSatse solved my issue. By adding a byebug statement I was also causing the program to wait, giving it time to synchronize with the browser, which fixed the bug.

Redis semaphore locks can't be released

I am using the redis-semaphore gem, version 0.3.1.
For some reason, I occasionally can't release a stale Redis lock. From my analysis it seems to happen if my Docker process crashed after the lock was created.
I have described my debugging process below and would like to know if anyone can suggest how to further debug.
Assume that we want to create a redis lock with this name:
name = "test"
We insert this variable in two different terminal windows. In the first, we run:
def lock_for_15_secs(name)
job = Redis::Semaphore.new(name.to_sym, redis: NonBlockingRedis.new(), custom_blpop: true, :stale_client_timeout => 15)
if job.lock(-1) == "0"
puts "Locked and starting"
sleep(15)
puts "Now it's stale, try to release in another process"
sleep(15)
puts "Now trying to unlock"
unlock = job.unlock
puts unlock == false ? "Wuhuu, already unlocked" : "Hm, should have been unlocked by another process, but wasn't"
end
end
lock_for_15_secs(name)
In the second we run:
def release_and_lock(name)
job = Redis::Semaphore.new(name.to_sym, redis: NonBlockingRedis.new(), custom_blpop: true, :stale_client_timeout => 15)
release = job.release_stale_locks!
count = job.available_count
puts "Release reponse is #{release.inspect} and available count is #{count}"
if job.lock(-1) == "0"
puts "Wuhuu, we can lock it"
job.unlock
else
puts "Hmm, we can't lock it"
end
end
release_and_lock(name)
This usually plays out as expected. For 15 seconds, the second terminal can't relase the lock, but when run after 15 seconds, it releases. Below is the output from release_and_lock(name).
Before 15 seconds have passed:
irb(main):1:0> release_and_lock(name)
Release reponse is {"0"=>"1580292557.321834"} and available count is 0
Hmm, we can't lock it
=> nil
After 15 seconds have passed:
irb(main):2:0> release_and_lock(name)
Release reponse is {"0"=>"1580292557.321834"} and available count is 1
Wuhuu, we can lock it
=> 1
irb(main):3:0> release_and_lock(name)
Release reponse is {} and available count is 1
Wuhuu, we can lock it
But whenever I see that a stale lock isn't released, and I run release_and_lock(name) to diagnose, this is returned:
irb(main):4:0> release_and_lock(name)
Release reponse is {} and available count is 0
Hmm, we can't lock it
And at this point my only option is to flush redis:
require 'non_blocking_redis'
non_blocking_redis = NonBlockingRedis.new()
non_blocking_redis.flushall
P.s. My NonBlockingRedis inherits from Redis:
class Redis
class Semaphore
def initialize(name, opts = {})
#custom_opts = opts
#name = name
#resource_count = opts.delete(:resources) || 1
#stale_client_timeout = opts.delete(:stale_client_timeout)
#redis = opts.delete(:redis) || Redis.new(opts)
#use_local_time = opts.delete(:use_local_time)
#custom_blpop = opts.delete(:custom_blpop) # false=queue, true=cancel
#tokens = []
end
def lock(timeout = 0)
exists_or_create!
release_stale_locks! if check_staleness?
token_pair = #redis.blpop(available_key, timeout, #custom_blpop)
return false if token_pair.nil?
current_token = token_pair[1]
#tokens.push(current_token)
#redis.hset(grabbed_key, current_token, current_time.to_f)
if block_given?
begin
yield current_token
ensure
signal(current_token)
end
end
current_token
end
alias_method :wait, :lock
end
end
class NonBlockingRedis < Redis
def initialize(options = {})
if options.empty?
options = {
url: Rails.application.secrets.redis_url,
db: Rails.application.secrets.redis_sidekiq_db,
driver: :hiredis,
network_timeout: 5
}
end
super(options)
end
def blpop(key, timeout, custom_blpop)
if custom_blpop
if timeout == -1
result = lpop(key)
return result if result.nil?
return [key, result]
else
super(key, timeout)
end
else
super
end
end
def lock(timeout = 0)
exists_or_create!
release_stale_locks! if check_staleness?
token_pair = #redis.blpop(available_key, timeout, #custom_blpop)
return false if token_pair.nil?
current_token = token_pair[1]
#tokens.push(current_token)
#redis.hset(grabbed_key, current_token, current_time.to_f)
if block_given?
begin
yield current_token
ensure
signal(current_token)
end
end
current_token
end
alias_method :wait, :lock
end
require 'non_blocking_redis'
😜 An awesome bug 👏
The bug
I think it happens if you kill the process when it does lpop on the SEMAPHORE:test:AVAILABLE
Most probably here https://github.com/dv/redis-semaphore/blob/v0.3.1/lib/redis/semaphore.rb#L67
To replicate it
NonBlockingRedis.new.flushall
release_and_lock('test');
NonBlockingRedis.new.lpop('SEMAPHORE:test:AVAILABLE')
Now initially you have:
SEMAPHORE:test:AVAILABLE 0
SEMAPHORE:test:VERSION 1
SEMAPHORE:test:EXISTS 1
After the above code you get:
SEMAPHORE:test:VERSION 1
SEMAPHORE:test:EXISTS 1
The code checks the SEMAPHORE:test:EXISTS and then expects to have SEMAPHORE:test:AVAILABLE / SEMAPHORE:test:GRABBED
Solution
From my brief check I don't think it is possible to make the gem work without a modification. I tried adding an expiration: but somehow it managed to disable the expiration for SEMAPHORE:test:EXISTS
NonBlockingRedis.new.ttl('SEMAPHORE:test:EXISTS') # => -1 and it should have been e.g. 20 seconds and going down
So.. maybe a fix will be
class Redis
class Semaphore
def exists_or_create!
token = #redis.getset(exists_key, EXISTS_TOKEN)
if token.nil? || all_tokens.empty?
create!
else
# Previous versions of redis-semaphore did not set `version_key`.
# Make sure it's set now, so we can use it in future versions.
if token == API_VERSION && #redis.get(version_key).nil?
#redis.set(version_key, API_VERSION)
end
true
end
end
end
end
the all_tokens is https://github.com/dv/redis-semaphore/blob/v0.3.1/lib/redis/semaphore.rb#L120
I'll open a PR to the gem shortly -> https://github.com/dv/redis-semaphore/pull/66 maybe 🤷‍♂️
Note 1
Not sure how you use the NonBlockingRedis but it is not in use in Redis::Semaphore. You do lock(-1) which does in the code lpop. Also the code never calls your lock.
Random
Here is a helper to dump the keys
class Test
def self.all
r = NonBlockingRedis.new
puts r.keys('*').map { |k|
[
k,
((r.hgetall(k) rescue r.get(k)) rescue r.lrange(k, 0, -1).join(' | '))
].join("\t\t")
}
end
end
> Test.all
SEMAPHORE:test:AVAILABLE 0
SEMAPHORE:test:VERSION 1
SEMAPHORE:test:EXISTS 1
For completeness here is how it looks when you have grabbed the lock
SEMAPHORE:test:VERSION 1
SEMAPHORE:test:EXISTS 1
SEMAPHORE:test:GRABBED {"0"=>"1583672948.7168388"}

Create instance of class within another class

Forgive me if my question isn't completely clear. I have been awake for way too long and I'm feeling a little brain dead.
I'm doing a Ruby exercise and I can't figure out why my rspec test isn't passing for something I thought would work.
require 'date'
class Product
attr_accessor :photo_src, :promotion, :initial_date
attr_reader :default_photo, :default_price, :current_price
def initialize(name, photo, price)
#name = name
#default_photo = photo
#photo_src = photo
#default_price = price
#current_price = price
#initial_date = Date.today.yday
#promotion = false
end
def price_change(sale_price)
calculator = RedPencilCalculator.new(self)
if promotion
if sale_price > #current_price
calculator.end_promotion!
elsif sale_price < (#default_price - (#default_price * 0.3))
calculator.end_promotion!
end
else
calculator.start_promotion!
end
#current_price = sale_price
end
end
class RedPencilCalculator
attr_accessor :promotion_start, :product
def initialize(product)
#product = product
end
def start_promotion!
if start_promotion?
product.promotion = true
product.photo_src = "redX.png"
#promotion_start = Date.today.yday
end
end
#would need to run daily
def end_promotion?
promotion_duration
if #duration == 30 || #duration == 335
end_promotion!
end
end
def end_promotion!
product.promotion = false
product.photo_src = product.default_photo
product.initial_date = Date.today.yday
end
private
def calculate_range
#min_discount = product.default_price - (product.default_price * 0.05)
#max_discount = product.default_price - (product.default_price * 0.3)
end
def start_promotion?
calculate_range
#max_discount <= product.current_price && product.current_price <= #min_discount && Date.today.yday - product.initial_date >= 30
end
def promotion_duration
current_date = Date.today.yday
#duration = current_date - #promotion_start
end
end
Rspec
This doesn't work:
describe Product do
let(:shoes) { Product.new("shoes", "item.png", 100) }
it 'should change the photo_src and promotion attribute if applicable' do
allow(shoes).to receive(:initial_date) { 100 }
shoes.price_change(75)
expect(shoes.promotion).to eq(true)
expect(shoes.photo_src).to eq("redX.png")
end
end
This does:
describe Product do
let(:shoes) { Product.new("shoes", "item.png", 100) }
let(:calculator) { RedPencilCalculator.new(shoes) }
it 'should change the photo_src and promotion attribute if applicable' do
allow(shoes).to receive(:initial_date) { 100 }
shoes.price_change(75)
calculator.start_promotion!
expect(shoes.promotion).to eq(true)
expect(shoes.photo_src).to eq("redX.png")
end
end
So it seems to me that the start_promotion! method call in the price_change method just isn't working.
I don't have a specific answer to your bug but some suggestions on how to pinpoint the problem.
You're testing too much in one unit test. There's so much that can go wrong it's hard (as you've found) to track down where the bug lies. Even if you work it out now, when something changes down the track (as it inevitably will) it will be at least as difficult as it is now to debug.
Simplify the initializer. It should only set #name, #photo, #price. The other instance variables should be methods (write tests unless they're private).
You suspect RedPencilCalculator#start_promotion! has a bug. Write a test to eliminate that possibility.
With more tests in place, the bug will eventually be cornered and crushed!
Lastly - this is easier said than done - but try writing tests first. It is hard but gets easier and even enjoyable!
ok, I put a puts inside of start_promotion? like this:
p "got past calc range: #{#max_discount.inspect} and #{#min_discount.inspect} and #{product.current_price}"
and got:
"got past calc range: 70.0 and 95.0 and 100"
given that the following line checks that current-price is less than the min-discount...
that's the line you've gotta check/fix to make things work

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