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();
?>
Komentáře
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 ?