Playing with GtkStyle

buttons widgets from scratch using GtkStyle methods Here is an example using GtkEventBox and GtkStyle to create standard Buttons. See the complete story and source code on my own site.

2011-12-10, by fgm: This code was hosted on the currently unavailable phpclasses.free.fr. Since it was declared as open source on that site (page bottom: "Content is available under GNU Free Documentation License 1.2"), I went ahead and restored the article from Google cache. Note that the original license still applies.

GtkStyle methods

Goal

Create standard buttons from scratch using GtkStyle methods.

Description

Here you will find how GtkWidget standards buttons are built and can be drawn. Here we use ready to use methods from GtkStyle class to draw buttons, checkboxes and handles. You can use theses classes to override standard buttons features. It's also useful to understand how GtkWidget works. This class study started when I was trying to display a handle and did not found any one.

Source code

<?php
error_reporting
(E_ALL);

/**
* author : Marc Quinton / march 2007.
*
* simulate button drawing with GtkStyle::paint_*() method.
* - take care of events (button, enter, leave)
*
* The main purpose of this script is to demonstrate use of GtkStyle pain method with state_type, shadow_type params
* and to have the ability to override buttons features.
*  class Tree:
*
*    GtkEventBox
*        GadgetBox
*            ButtonGadget (contains GtkLabel)
*                ToggleButtonGadget
*                    CheckButtonGadget
*            HandleGadget
*
*        Button         (contains ButtonGadget)
*        ToggleButton (contains ToggleButtonGadget)
*        CheckButton  (contains GtkHbox(CheckButtonGadget,GtkLabel))
*        HandleBox    (contains HandleGadget)
*
*  limitations :
*  - need to implement signals ; not very difficult
*  - there is a probleme displaying Gadget widget in standard Gtk composite widget, so we have created composite classes
*
*/
abstract class GadgetBox extends GtkEventBox{
     protected
$_state;
     protected
$_style;

     public function
__construct(){
        
parent::__construct();

        
$this->add_events(Gdk::EXPOSURE_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::BUTTON_PRESS_MASK);
        
$this->connect('expose-event', array($this, 'expose_event'));
        
$this->connect('leave-notify-event', array($this, 'leave_notify_event'));
        
$this->connect('enter-notify-event', array($this, 'enter_notify_event'));
        
$this->connect('button-press-event', array($this, 'button_press_event'));
        
$this->connect('button-release-event', array($this, 'button_release_event'));

        
$this->_state = Gtk::STATE_NORMAL;
        
$this->_style = Gtk::SHADOW_OUT;
     }

     public function
expose_event($self, $event){
        
$this->draw();

         if(
$child = $this->get_child() != null)
            
$this->propagate_expose($this->get_child(), $event);

         return
true# returning true to tell we have done expose ourself ;
    
}

     public function
enter_notify_event($self, $event){
        
$this->_state = Gtk::STATE_PRELIGHT;
        
$this->redraw();
     }

     public function
leave_notify_event($self, $event){
        
$this->_state = Gtk::STATE_NORMAL;
        
$this->redraw();
     }

     public function
button_press_event($self, $event){
        
$this->_state = Gtk::STATE_ACTIVE;
        
$this->redraw();
     }

     public function
button_release_event($self, $event){
        
$this->_state = Gtk::STATE_PRELIGHT;
        
$this->redraw();
     }

     protected function
redraw(){
        
$alloc = $this->allocation;
        
$this->queue_draw();
     }

     public function
draw(){
     }

     public function
draw_border($x, $y, $w, $h){
        
$style = $this->get_style();
        
$style->paint_box($this->window, $this->_state, $this->_style, null, $this, "test", $x, $y, $w, $h);
     }

     public function
draw_string($x, $y, $string){
        
$style = $this->get_style();
        
# void paint_string(GdkWindow window, state_type, GdkRectangle area, GtkWidget widget, detail, x, y, string);
        
$style->paint_string($this->window, Gtk::STATE_NORMAL, null, $this, "button", $x, $y, $string);
     }
}

class
ButtonGadget extends GadgetBox{
     protected
$_name;
     protected
$label;

     public function
__construct($name = , $font = null){
        
parent::__construct();
        
$this->_name = $name;

        
# $this->set_border_width(6);
         # $this->set_size_request(20,20);

        
if($name != null)
            
$this->add($this->label = new GtkLabel($name));

         if(
$font != null)
            
$this->label->modify_font(new PangoFontDescription($font));

     }

     public function
button_press_event($self, $event){
        
$this->_state = Gtk::STATE_ACTIVE;
        
$this->_style = Gtk::SHADOW_IN;
        
$this->redraw();
     }

     public function
button_release_event($self, $event){
        
$this->_state = Gtk::STATE_PRELIGHT;
        
$this->_style = Gtk::SHADOW_OUT;
        
$this->redraw();
     }

     public function
draw(){
        
$alloc = $this->allocation;

        
$border = $this->get_border_width();

        
$x = $alloc->x;
        
$y = $alloc->y;

        
$w = $alloc->width - 2*$border;
        
$h = $alloc->height - 2* $border;

        
$style = $this->get_style();
        
$style->paint_box($this->window, $this->_state, $this->_style, null, $this, "test", $x, $y, $w, $h);
         return
true;    # returning true is : yes I have done all expose event for childs
    
}
}

class
ToggleButtonGadget extends ButtonGadget{
     protected
$toggled;
     protected
$count;

     public function
__construct($name = , $font = null){
        
parent::__construct($name, $font);
        
$this->toggled = false; # off
        
$this->count = 1;
     }

     public function
button_press_event($self, $event){
        
$this->count++;

         if(
$this->count%2 == 0){
            
$this->_state = Gtk::STATE_ACTIVE;
            
$this->_style = Gtk::SHADOW_IN;
            
$this->toggled = true;
         }

        
$this->redraw();
     }

     public function
button_release_event($self, $event){
         if(
$this->count%2 == 1){
            
$this->_style = Gtk::SHADOW_OUT;
            
$this->_state = Gtk::STATE_PRELIGHT;
            
$this->toggled = false;
         }

        
$this->redraw();
     }

     public function
enter_notify_event($self, $event){
         if(
$this->toggled)
            
$this->_state = Gtk::STATE_ACTIVE;
         else
            
$this->_state = Gtk::STATE_PRELIGHT;
        
$this->redraw();
     }

     public function
leave_notify_event($self, $event){
         if(
$this->toggled)
            
$this->_state = Gtk::STATE_ACTIVE;
         else
            
$this->_state = Gtk::STATE_NORMAL;
        
$this->redraw();
     }
}

class
CheckButtonGadget extends ToggleButtonGadget{
     protected
$size;
     protected
$border;

     public function
__construct(){
        
parent::__construct(null);
        
$this->size = 15;
        
$this->border = 3;
     }

     public function
draw(){
        
$alloc = $this->allocation;

        
$border = $this->get_border_width();

        
$x = 0;
        
$y = intval(($alloc->height - $this->size) / 2);

        
$w = $this->size;
        
$h = $this->size;

        
$style = $this->get_style();

        
# void paint_check(GdkWindow window, state_type, shadow_type, GdkRectangle area, GtkWidget widget, detail, x, y, width, height);
        
$style->paint_check($this->window, $this->_state, $this->_style, null, $this, "test", $x, $y, $w, $h);

         return
true;
     }
}


class
HandleGadget extends GadgetBox{
     protected
$size;
     protected
$orientation;

     public function
__construct($orientation = Gtk::ORIENTATION_HORIZONTAL){
        
parent::__construct();
        
$this->orientation = $orientation;
        
$this->setup();
     }

     protected function
setup(){
        
$this->size = array(
            
'w' => 40,
            
'h' => 8
        
);

         if(
$this->orientation == Gtk::ORIENTATION_HORIZONTAL)
            
$this->set_size_request($this->size['w'], $this->size['h']);
         else
            
$this->set_size_request($this->size['h'], $this->size['w']);

     }

     public function
draw(){
        
$alloc = $this->allocation;

        
$border = $this->get_border_width();

         if(
$this->orientation == Gtk::ORIENTATION_HORIZONTAL){
            
$x = $alloc->x + intval(($alloc->width-$this->size['w']) / 2);
            
$y = $alloc->y;
            
$w = $this->size['w'];
            
$h = $this->size['h'];
         } else{
            
$y = $alloc->y + intval(($alloc->height-$this->size['w']) / 2);
            
$x = $alloc->x;
            
$w = $this->size['h'];
            
$h = $this->size['w'];
         }
        
$style = $this->get_style();

        
# void paint_check(GdkWindow window, state_type, shadow_type, GdkRectangle area, GtkWidget widget, detail, x, y, width, height);
        
$style->paint_handle($this->window, $this->_state, $this->_style, null, $this, "handle",
            
$x, $y, $w, $h, $this->orientation);

         return
true;
     }
}

# ------------------------------------------------------------------------------------------------------------
# bellow, it's composition of GtkEventBox with Gadget to insure correct
# management in composite widget (GtkVbox for example)
# without this composition, gadget are not drawn properly ; probably a clip problem with GtkStyle::paint*() methods.
#
#
class Button extends GtkEventBox{
     protected
$button;

     public function
__construct($name = , $font = null){
        
parent::__construct();
        
$this->button = new ButtonGadget($name, $font);
        
$this->add($this->button);
     }   
}

class
ToggleButton extends GtkEventBox{
     protected
$button;

     public function
__construct($name = , $font = null){
        
parent::__construct();
        
$this->button = new ToggleButtonGadget($name, $font);
        
$this->add($this->button);
     }   
}

class
CheckButton extends GtkEventBox{
     protected
$hbox;
     protected
$check;
     protected
$label;

     public function
__construct($name = , $font = null){
        
parent::__construct();

        
$this->add($this->hbox = new GtkHbox());

            
$this->hbox->pack_start($this->check = new CheckButtonGadget(), false, false);
            
$this->hbox->pack_end($this->label = new GtkLabel($name), true, true);

            
$this->check->set_size_request(15,15);
            
$this->label->set_alignment(0.02, 0.5);

        
$this->show_all();
     }   
}

class
HandleBox extends GtkEventBox{
     protected
$button;

     public function
__construct($orientation = Gtk::ORIENTATION_HORIZONTAL){
        
parent::__construct();
        
$this->button = new HandleGadget($orientation);
        
$this->add($this->button);
     }   
}

$window = new GtkWindow();
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->set_size_request( 200, 200 );      # set window size
$window->set_position(Gtk::WIN_POS_CENTER); # place window to screen center
$window->set_title("GtkStyle::paint_box test");


    
$window->add($hbox1 = new GtkHbox());

        
$hbox1->pack_start($vbox = new GtkVbox());
            
$vbox->pack_start(new HandleBox(Gtk::ORIENTATION_HORIZONTAL), false, false);
            
$vbox->pack_start($l = new GtkLabel('Horizontal HandleBox')); $l->set_angle(90);


        
$hbox1->pack_end($vbox = new GtkVbox());

            
$vbox->pack_start($hbox2 = new GtkHbox());
                
$hbox2->pack_start(new HandleBox(Gtk::ORIENTATION_VERTICAL), false, false);
                
$hbox2->pack_start(new GtkLabel('Vertical HandleBox'));

            
$vbox->pack_start(new Button('button'));
            
$vbox->pack_start(new CheckButton('check button'));
            
$vbox->pack_start(new ToggleButton('toggle button'));


$window->show_all();
Gtk::main();
?>