Custom widget in Ruby Qt

In this part of the Ruby Qt programming tutorial, we will create a custom widget.

Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets. Programmers must create such widgets by themselves. They do it by using the drawing tools provided by the toolkit. There are two possibilities. A programmer can modify or enhance an existing widget. Or he can create a custom widget from scratch.

The Burning widget

In the next example, we will create a custom burning widget. This widget can be seen in applications like Nero or K3B. The widget will be created from scratch.

#!/usr/bin/ruby


# ZetCode Ruby Qt tutorial

# In this program, we create
# a custom widget
#
# @author jan bodnar
# website zetcode.com
# last modified July 2009


require 'Qt'

PANEL_HEIGHT = 30
DISTANCE = 19
LINE_WIDTH = 5
DIVISIONS = 10
FULL_CAPACITY = 700
MAX_CAPACITY = 750


class Burning < Qt::Widget 


    def initialize(parent) 
        super(parent)
        
        @num = [ "75", "150", "225", "300", 
            "375", "450", "525", "600", "675" ]
            
        @redColor = Qt::Color.new 255, 175, 175
        @yellowColor = Qt::Color.new 255, 255, 184

        @parent = parent
        setMinimumHeight PANEL_HEIGHT
    end

    
    def paintEvent event

        painter = Qt::Painter.new self
        
        drawWidget painter
        painter.end
    end

    def drawWidget painter

        w = width.to_f
        

        slid_width = @parent.getCurrentWidth
        
        step = (w / DIVISIONS).round.to_f

        till = ((w / MAX_CAPACITY) * slid_width).to_f
        full = ((w / MAX_CAPACITY) * FULL_CAPACITY).to_f

        if slid_width > FULL_CAPACITY
            painter.setPen @yellowColor
            painter.setBrush Qt::Brush.new @yellowColor
            painter.drawRect Qt::RectF.new 0, 0, full, PANEL_HEIGHT
            painter.setPen @redColor
            painter.setBrush Qt::Brush.new @redColor
            painter.drawRect Qt::RectF.new full+1, 0, till-full, PANEL_HEIGHT
        else 
            if slid_width > 0
               painter.setPen @yellowColor
               painter.setBrush Qt::Brush.new @yellowColor
               painter.drawRect Qt::RectF.new 0, 0, till, PANEL_HEIGHT
            end
        end

        painter.setPen Qt::Color.new 90, 90, 90
        painter.setBrush Qt::NoBrush
        painter.drawRect 0, 0, w-1, PANEL_HEIGHT-1

        newFont = font
        newFont.setPointSize 7
        painter.setFont newFont
        
        for i in (1..@num.length)
            painter.drawLine Qt::LineF.new i*step, 1, i*step, LINE_WIDTH

            metrics = Qt::FontMetrics.new newFont

            w = metrics.width @num[i-1]
            painter.drawText(Qt::PointF.new(i*step-w/2, DISTANCE), @num[i-1])
            
        end
    end
end

class QtApp < Qt::Widget 

    slots 'onChanged(int)'

    def initialize
        super
        setWindowTitle "The Burning Widget"

        initUI

        resize 370, 200
        move 300, 300
        show
    end

    def initUI
    
       @cur_width = 0
       
       @slider = Qt::Slider.new Qt::Horizontal , self
       @slider.setMaximum MAX_CAPACITY
       @slider.setGeometry 50, 50, 130, 30 

       connect(@slider, SIGNAL("valueChanged(int)"), self, SLOT("onChanged(int)"))
       
       vbox = Qt::VBoxLayout.new self
       hbox = Qt::HBoxLayout.new

       vbox.addStretch 1

       @widget = Burning.new self
       hbox.addWidget @widget, 0

       vbox.addLayout hbox

       setLayout vbox
    end

    def onChanged val
        @cur_width = val
        @widget.repaint
    end

    def getCurrentWidth
      return @cur_width
    end
end


app = Qt::Application.new ARGV
QtApp.new
app.exec

In this file, we create the Burning widget.

class Burning < Qt::Widget 

The custom widget is based on the Widget widget.

PANEL_HEIGHT = 30
DISTANCE = 19
LINE_WIDTH = 5
DIVISIONS = 10
FULL_CAPACITY = 700
MAX_CAPACITY = 750

These are important constants. The PANEL_HEIGHT defines the height for the custom widget. The DISTANCE is the distance of the numbers on the scale from the top of their parent border. The LINE_WIDTH is the vertical line width. The DIVISIONS is the number of parts of the scale. The FULL_CAPACITY is the maximum capacity of the media. After it is reached, overburning happens. This is visualised by a red colour. The MAX_CAPACITY is the maximum capacity of a medium.

@num = [ "75", "150", "225", "300", 
    "375", "450", "525", "600", "675" ]

We use these numbers to build the scale of the Burning widget.

def paintEvent event

    painter = Qt::Painter.new self

    drawWidget painter
    painter.end
end

The drawing of the custom widget is delegated to the drawWidget method.

slid_width = @parent.getCurrentWidth

We use it to get the currently selected slider value.

w = width.to_f

We get the width of the widget. The width of the custom widget is dynamic. It can be resized by a user.

till = ((w / MAX_CAPACITY) * slid_width).to_f
full = ((w / MAX_CAPACITY) * FULL_CAPACITY).to_f

We use the w variable to do the transformations. Between the values of the scale and the custom widget's measures. Note that we use floating point values. We get greater precision in drawing.

painter.setPen @redColor
painter.setBrush Qt::Brush.new @redColor
painter.drawRect Qt::RectF.new full+1, 0, till-full, PANEL_HEIGHT

These three lines draw the red rectangle, indicating the overburning.

painter.drawRect 0, 0, w-1, PANEL_HEIGHT-1

This is the perimeter of the widget, the outside rectangle.

painter.drawLine Qt::LineF.new i*step, 1, i*step, LINE_WIDTH

Here we draw the small vertical lines.

w = metrics.width @num[i-1]
painter.drawText(Qt::PointF.new(i*step-w/2, DISTANCE), @num[i-1])

Here we draw the numbers of the scale. To precisely position the numbers, we must get the width of the string.

@widget = Burning.new self
hbox.addWidget @widget, 0

We create the instance of the Burning widget and add it to the horizontal box.

def onChanged val
    @cur_width = val
    @widget.repaint
end

When the value of the slider changes, we store it inside the @cur_width variable and repaint the custom widget.

def getCurrentWidth
return @cur_width
end

This method is called by the custom widget to get the actual slider value.

The Burning widget
Figure: The Burning widget

In this part of the Ruby Qt tutorial, we have demonstrated how to create a custom widget.