Custom widget

Toolkits usually provide only the most common widgets like buttons, text widgets, sliders etc. No toolkit can provide all possible widgets. If there is a need for a more specialised widget, we must create it ourselves.

Custom widgets are created 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.

Burning widget

This is an example of a widget that we create from scratch. It is based on a minimal GtkWidget widget. This custom widget can be found in various media burning applications, like Nero Burning ROM.

<?php
 
/* 
ZetCode PHP GTK tutorial

This example creates a burning
custom widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: August 2011
*/

class Burning extends GtkDrawingArea { 
     

    public function __construct($par) { 

        parent::__construct(); 
 
        $this->par = $par;          

        $this->init_ui();

    } 

    public function init_ui() {

        $this->num = array("75", "150", "225", "300", 
            "375", "450", "525", "600", "675");
 
        $this->set_size_request(1, 30);
        $this->connect('expose_event', array($this, 'on_expose')); 
 
    }

    public function on_expose() {
        $cr = $this->window->cairo_create();
        $this->draw_widget($cr);
    }

    public function draw_widget($cr) {
 
        $cr->SetLineWidth(0.8);
        $cr->SelectFontFace("Courier", CairoFontSlant::NORMAL, 
            CairoFontWeight::NORMAL);
        $cr->SetFontSize(11);

        $width = $this->get_allocation()->width;
     
        $this->cur_width = $this->par->get_cur_value();

        $step = round($width / 10.0);

        $till = ($width / 750.0) * $this->cur_width;
        $full = ($width / 750.0) * 700;

        if ($this->cur_width >= 700) {
            
            $cr->SetSourceRgb(1.0, 1.0, 0.72);
            $cr->Rectangle(0, 0, $full, 30);
            $cr->Clip();
            $cr->Paint();
            $cr->ResetClip();
            
            $cr->SetSourceRgb(1.0, 0.68, 0.68).
            $cr->Rectangle($full, 0, $till-$full, 30);
            $cr->Clip();
            $cr->Paint();
            $cr->ResetClip();

        } else {
            $cr->SetSourceRgb(1.0, 1.0, 0.72);
            $cr->Rectangle(0, 0, $till, 30);
            $cr->Clip();
            $cr->Paint();
            $cr->ResetClip();
        }
       

        $cr->SetSourceRgb(0.35, 0.31, 0.24);
        $len = count($this->num);

        for ($i=1; $i <= $len; $i++) {
            $cr->MoveTo($i*$step, 0);
            $cr->LineTo($i*$step, 5);
            $cr->Stroke();
            
            $te = $cr->TextExtents($this->num[$i-1]);
            $cr->MoveTo($i*$step-$te['width']/2, 15);
            $cr->TextPath($this->num[$i-1]);
            $cr->Stroke();
        }        
    }
}
            

class Example extends GtkWindow { 
     

    public function __construct() { 

        parent::__construct(); 
         
        $this->init_ui();

    } 

    private function init_ui() {

        $this->set_title('Burning widget');         
        $this->connect_simple('destroy', array('gtk', 'main_quit')); 

        $this->cur_value = 0;
       
        $vbox = new GtkVBox(false, 2);
        
        $scale = new GtkHScale();
        $scale->set_range(0, 750);
        $scale->set_digits(0);
        $scale->set_size_request(160, 35);
        $scale->set_value($this->cur_value);
        
        $scale->connect('value-changed', array($this, 'on_changed'));
        
        $fixed = new GtkFixed();
        $fixed->put($scale, 50, 50);
        
        $vbox->pack_start($fixed);
        
        $this->burning = new Burning($this);
        $vbox->pack_start($this->burning, false, false, 0);

        $this->add($vbox);

        $this->set_default_size(350, 200); 
        $this->set_position(GTK::WIN_POS_CENTER);
        $this->show_all();         
    }

    public function on_changed($sender) {
    
        $this->cur_value = $sender->get_value();
        $this->burning->queue_draw();
    }
    
    public function get_cur_value() {
        return $this->cur_value;
    }
} 
     
new Example(); 
Gtk::main();
 
?>

We put a GtkDrawingArea on the bottom of the window and draw the entire widget manually. All the important code resides in the draw_widget() which is called from the on_expose() method of the Burning class. This widget shows graphically the total capacity of a medium and the free space available to us. The widget is controlled by a scale widget. The minimum value of our custom widget is 0, the maximum is 750. If we reach value 700, we began drawing in red colour. This normally indicates overburning.

$this->num = array("75", "150", "225", "300", 
    "375", "450", "525", "600", "675");

These numbers are shown on the burning widget. They show the capacity of the medium.

$this->cur_width = $this->par->get_cur_value();

From the parent widget, we get the current value of the scale widget.

$till = ($width / 750.0) * $this->cur_width;
$full = ($width / 750.0) * 700;

We use the $width 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. The $till parameter determines the total size to be drawn. This value comes from the slider widget. It is a proportion of the whole area. The $full parameter determines the point, where we begin to draw in red colour.

$cr->SetSourceRgb(1.0, 1.0, 0.72);
$cr->Rectangle(0, 0, $full, 30);
$cr->Clip();
$cr->Paint();
$cr->ResetClip();

We draw a yellow rectangle up to point, where the medium is full.

$te = $cr->TextExtents($this->num[$i-1]);
$cr->MoveTo($i*$step-$te['width']/2, 15);
$cr->TextPath($this->num[$i-1]);
$cr->Stroke();

This code here draws the numbers on the burning widget. We calculate the text extents to position the text correctly.

public function on_changed($sender) {

    $this->cur_value = $sender->get_value();
    $this->burning->queue_draw();
}

We get the value from the scale widget, store it in the $this->cur_value variable for later use. We redraw the burning widget.

Burning widget
Figure: Burning widget

In this chapter, we created a custom widget in GTK and PHP programming language.