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();
?>

commenti

Decision needed

I'm a bit annoyed by this post : one the one hand, the example is interesting when one goes on the remote site to see it. On the other, since it is hosted outside and has no local content, I feel it might not warrant being included as a node on the site, but might more logically fit in the "Community" section of the aggregator, if Marc provides a RSS feed on his site.

Yet another solution could be to create a specific category for externally hosted articles, like so many "press-review"-type PHP sites do. This would obviously increase the content volume immediately, but on the other hand make content less dense and useful, less of a "community" site and more of yet another pumped up aggregator.

What do Marc and other community members think of such a situation ?

I do not personally care, he

I do not personally care, he supplied an index which will archive the article for local database searches, I probably would have done something similar. The only bad thing is if the link 404's eventually - in that case the full article with a link to the original would be preferable.

I agree with Bob

I agree with Bob on this, It does not really bother me and that the only problem I see is the 404 that may happen in the future it might have been nice for the full article or even a trimed down version with a link back would be a good idea.

404 reached :(

A few years later, it appears that you, Frédéric, were right.
Of course, I disagree with Bob and Leon about that: in my humble opinion, an acceptable choice would have been a hosting at freshmeat's or at sourceforge's, or even google.

Just my two cents,
Pierre.

Article restored: who's willing to do the others ?

Since the article was licensed under the GFDL 1.2, I went ahead and restored it from Google's cache.

However, Marc wrote many articles linking to the code on his now defunct site, and all would benefit from the same treatment, and also redoing the screenshots to upload them here to avoid the 404 on them too. Anyone willing ?

Opzioni visualizzazione commenti

Seleziona il tuo modo preferito per visualizzare i commenti e premi "Salva impostazioni" per attivare i cambiamenti.