Page 1 of 1

PHP-GTK & PECL/Cairo - An Example Rate Topic: -----

#1 mahcuz  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 143
  • View blog
  • Posts: 213
  • Joined: 03-June 10

Posted 13 June 2010 - 11:23 AM

Attention Nerds
Once upon a time there was a man with a need. That need led to PHP/FI, a simple set of tools for your homepage. A very simple set of tools. Of course, over time, due to interest in said tools, it had to adapt and grow and become things that it never intended to be in the first place. We could draw an analogy to this from almost any thing, be it living or inanimate, as everything has to grow and adapt to survive. Technology is no exception. With that said, if you believe PHP is not fit for a desktop GUI environment, simply because it was not initially designed for that, then you, my friend, should take your narrow-minded, stone-age views back to church. Suckerz.

What diz?
This article will be a run-through of a simple little PHP-GTK application coupled with some Cairo art.

Btw...
To distinguish between Cairo-the-graphics-library and Cairo-the-PHP-binding-for-Cairo-the-graphics-library, I will be calling the latter PECL/Cairo. Starting... now.

Prerequisites
There are a few things you'll need for PHP-GTK to work with Cairo. Of course, the first things you need are going to be the GTK and Cairo libraries. Then you'll need to grab the latest copy of PECL/Cairo - latest release is 0.2 at the time of writing, however, I suggest getting the source from SVN, so you have the latest and greatest features. Then you'll be wanting to compile PHP-GTK - be sure to have PECL/Cairo loaded by your PHP configuration file! However, only the sourcecode in PHP-GTK's SVN bears the stuff that lets it work with Cairo, so you'll be doing an SVN checkout. You'll find documentation on how to compile both PECL/Cairo and PHP-GTK in their respective manuals. If you have problems compiling either, head on over to #php-gtk on irc.freenode.net and bug auroraeosrose and mgdm with your questions - they just love to help newbies. They l-o-v-e it! Once you've done the aforementioned and loaded both extensions in your PHP config file, let's do a quick check to make sure they are available: run php -m to see what extensions PHP has loaded; if you see 'Cairo' and 'PHP-GTK2', then you're laughing! If not, scroll up and repeat.

What happened next, pa?

Fred Morcos wrote a simple program that demonstrated http://fredmorcos.bl...ro-example.html, so I'll be using that, just tweaked and transposed into PHP. The example creates a window (GtkWindow) and paints onto it a circle using Cairo. That circle's transparency is modified by adjusting the horizontal scale (GtkHScale).

Posted Image

We'll start off with our class
PHP-GTK exposes an OO API, allowing us to use all the juicy goodness that Object Oriented Programming allows for, inheritance, polymorphism, etc. Using a class for this quite simple demonstration doesn't make a whole lotta sense, it may even seem like overkill, and it probably is. However, there are aspects I want to demonstrate (inheritance) that make programming with PHP-GTK enjoyable. PECL/Cairo also exposes an OO API, and a procedural API for that matter, but we'll get to that later. Because our application will be centered around a window, so let's go ahead and have our class, GtkCairoExample, extend Gtkwindow.


class GtkCairoExample extends GtkWindow {

}



Our class needs some state-properties, specifically we need to know whether it has support for alpha blending, and what the current alpha value is - this ranges between 0 and 1, with 1 being no transparency and 0 being full transparency.

class GtkCairoExample extends GtkWindow {

    private $supports_alpha = FALSE;
    private $alpha = 1;
}


When this object is initialized, we're going to want to have some code executed so we can set-up the object. If you know anything about OOP, you'll know that we generally will do that in the class's __construct()method. This method is called whenever an object of this class is instantiated, that is, whenever you see new className();, an object is said to have been instantiated - it's locatable in some area of memory. The first thing we need to do in the constructor, because PHP does not implicitly do this, is call our parent's constructor.

class GtkCairoExample extends GtkWindow {

    private $supports_alpha = FALSE;
    private $alpha = 1;

    public function __construct() {

        parent::__construct();
    }
}


Next we'll create the GtkHScale object and set some properties for it.

parent::__construct();

$scale = GtkHScale::new_with_range(10, 100, 10);
$scale->set_update_policy(Gtk::UPDATE_CONTINUOUS);
$scale->set_value($this->alpha * 100);


Instead of using GtkHScale's constructor, we use a special, static one which creates a GtkAdjustment automatically. GtkHScale::new_with_range() takes 3 parameters: the minimum value for the slider; the maximum value for the slider; and lastly the step value, which is used whenever one uses the arrow keys to adjust the slider. And now we have a GtkHScale object which we can play with and modify.

Let us take a brief minute to talk about signals
Signals are a huge part of working with GTK+. With all these widgets being displayed, they are useless if you do not have a way to connect some part of your code with a specific event, for example, you need to know when a button has been clicked so you can take the appropriate action, and we need to know when our slider has been adjusted so we can update our object's $alpha property and also redraw our circle. Connecting to signals is as easy as:

$scale->connect('changed-value', array($this, 'alphaChanged'));


This registers a callback to a signal. Whenever that signal is emitted, PHP-GTK calls every registered callback. Of course, we'll have to write the class method to handle that, but we'll do that after we've finished our constructor. Now that we have a slider we need to add it to our window. We could use the GtkWindow::add() method. However, a GtkWindow is what is called a Bin, that is, it has only a single child, and that child will be positioned in the center of the window. To have more power over the positioning of widgets, we'll instead use a GtkContainer that can handle multiple children - a GtkBox. We'll use a GtkVBox so that children are stacked vertically.

Organising widget layouts is a whole 'nother article, so I'll only be brief here - this article is getting long-winded already. So, for more information, check out PHP-GTK's tutorial on packing.


$vbox = new GtkVBox;
$vbox->pack_start($scale, FALSE, TRUE);


This code is pretty self-explanatory: we create a new GtkVBox container and pack into it our GtkHScale widget. The second parameter determines whether the widget we're adding should expand into all available space - but we don't want it to. The second parameter determines whether the widget should fill out into available space - we want it to. Now we have our container with our slider packed into it. The next thing to do is add this to our main Gtkwindow. Because our class extends GtkWindow, our class is a GtkWindow, so we can simply use $this to refer to the window. All Bin containers, which a GtkWindow is, have an add() method that take a widget and add it as their child.

$this->add($vbox);


Next we'll modify the display of the Gtkwindow.

$this->set_title('Alpha Cairo Demo');
$this->resize(400, 400);
$this->set_position(Gtk::WIN_POS_CENTER);
$this->set_app_paintable(TRUE);
$this->set_border_width(0);
$this->set_decorated(FALSE);


Most of this should make sense. GtkWidget::set_app_paintable() tells GTK+ whether to overwrite anything you paint on the widget, and GtkWindow::set_decorated() tells GTK+ whether to draw borders and title bar, etc.

Just like with the slider, we'll need to connect our class to some signals: expose-event, button-press-event, and delete-event. The expose event is emitted when a GdkWindow becomes visible, or part of it becomes visible after being covered by something, for example another window. This signal is what we'll use to trigger the drawing of our circle. The delete signal is triggered when a user requests that a window be closed, for example, by ALT+F4. The button-press signal is a little more interesting: while you may guess what triggers the emission of this signal, you may not know that simply connecting to this signal will not produce the desired results. This is because a GtkWindow, by default, does not emit a button-press signal. Instead, you have to tell your GtkWindow that it should have this behaviour.

$this->connect('expose-event', array($this, 'expose'));
$this->connect('button-press-event', array($this, 'clicked'));
$this->connect_simple('delete-event', array('Gtk', 'main_quit'));
$this->add_events(Gdk::BUTTON_PRESS_MASK);


Now when we click on our GtkWindow, we'll receive the signal.
Has alpha?
We need to load the appropriate color map based on whether the system has support for alpha. This is done by getting the GdkScreen associated with our GtkWindow and then seeing if GdkScreen::get_rgba_colormap() returns a usable object.

$screen = $this->get_screen();
$colormap = $screen->get_rgba_colormap();
if (!$colormap) {
    print 'No support for alpha color maps.';
    $colormap = $screen->get_rgb_colormap();
    $this->supports_alpha = FALSE;
}
else {
    print 'You has support for alpha color maps.';
    $this->supports_alpha = TRUE;
}
$this->set_colormap($colormap);


Now that we have all of our widgets created and we have determined whether we have support alpha, we're done with our constructor. Just one final thing to do - tell our GtkWindow to display all of its children. You should have something that look like this now:

set_update_policy(Gtk::UPDATE_DELAYED);
        $scale->set_value($this->alpha * 100);
        $scale->connect('change-value', array($this, 'alphaChanged'));

        $hbox = new GtkVBox;
        $hbox->pack_start($scale, FALSE, TRUE);

        $this->add($hbox);
        $this->set_title('Alpha Cairo Demo');
        $this->resize(400, 400);
        $this->set_position(Gtk::WIN_POS_CENTER);
        $this->set_app_paintable(TRUE);
        $this->set_border_width(0);
        $this->set_decorated(FALSE);

        $this->connect('expose-event', array($this, 'expose'));
        $this->connect('button-press-event', array($this, 'clicked'));
        $this->connect_simple('destroy', array('Gtk', 'main_quit'));
        $this->add_events(Gdk::BUTTON_PRESS_MASK);

        $screen = $this->get_screen();
        $colormap = $screen->get_rgba_colormap();
        if (!$colormap) {
            print 'No support for alpha color maps.';
            $colormap = $screen->get_rgb_colormap();
            $this->supports_alpha = FALSE;
        }
        else {
            print 'You has support for alpha color maps.';
            $this->supports_alpha = TRUE;
        }
        $this->set_colormap($colormap);

        $this->show_all();
    }
}


All that's left to do is write the signal handlers and do the drawing!

We need our clicked signal handler to toggle our window's decoration on and off. This is as simple as:

public function clicked($widget, $event) {

    $this->setDecorated(!$this->decorated);
}


The $widget and $event parameters are sent by PHP-GTK. The $widget variable is the widget that emitted the signal. The $event variable contains an array of information regarding the event, for example, the X and Y coordinates of the mouse.

The alphaChanged signal handler needs to update out object's $alpha property to reflect that of the slider. It will also have to let GTK know that the widget needs redrawing.

public function alphaChanged($range, $scroll_type, $value) {

    $this->alpha = $value / 100;
    $this->queue_draw();
}


Time for some drawing
GTK is now dependant on Cairo, and, as such, PHP-GTK is now dependant on PECL/Cairo. Cairo is a 2D vector graphics library, it is powerful and it is fast.

Cairo's painting canvases are called surfaces. Surfaces can range from PDFs, PNG images, windows, etc., which makes it ideal for drawing GTK widgets - indeed, GTK uses Cairo to draw its sliders, buttons, etc. You target this surface with a context. You use this context to draw shapes to the surface.

Once again, to explain Cairo in more depth would be to extend this article greatly. The documentation for Cairo can be found on its website.

We want to paint our circle onto our window so we need to use the window as a surface for cairo. GTK+ provides a way to do this using the GdkWindow::cairo_create() method. We can obtain the GdkWindow object of our widget by referencing its window property.

So, the first thing we must do is get a CairoContext object which targets our GdkWindow as the surface.

public function expose($widget, $event) {

    $context = $this->window->cairo_create();
}


Now that we have a CairoContext, we can do some drawing on the surface. Firstly we'll tell Cairo to replace the destination source:

$context->setOperator(CairoOperator::SOURCE);


The background color of a surface is determined by the [highlight]first[/highlight] call to CairoContext::setSourceRgb or CairoContext::setSourceRgba(). The latter takes an alpha value. To get just the circle and slider visible, we'll see if we can use CairoContext::setSourceRgba() (our object's supports_alpha property will come in handy here). The first 3 parameters to the aforementioned methods take values in the range of 0.0 - 1.0 for red, green, and blue, respectively.

if ($this->supports_alpha)
$context->setSourceRgba(1, 1, 1, 0); // Full transparency
else
$context->setSourceRgba(1, 1, 1);


Now we want to paint that source everywhere:

$context->paint();


Now that we're ready to draw our circle, we should set the source's color.

$context->setSourceRgba(1, 0, 0, $this->alpha);


To determine where exactly we should draw the circle, we need to know the dimensions of the window, which are available via the widget's get_size() method.

/** Index 0 - width; Index 1 - height */
$sizes = $this->get_size();


Drawing the circle now is as simple as calling CairoContext::arc() with the relevant information - center X coordinate, center Y coordinate, the radius of the circle, beginning angle (radians), ending angle (radians).

$context->arc(
    $sizes[0] / 2, // Center X
    $sizes[1] / 2, // Center Y
    ($sizes[0] fill();


... and that is our class done. All we have left to do is instantiate it and begin GTK's main loop. You should be left with something like:
set_update_policy(Gtk::UPDATE_DELAYED);
        $scale->set_value($this->alpha * 100);
        $scale->connect('change-value', array($this, 'alphaChanged'));
        
        $hbox = new GtkVBox;
        $hbox->pack_start($scale, FALSE, TRUE);
        
        $this->add($hbox);        
        $this->connect('expose-event', array($this, 'expose'));
        $this->connect('button-press-event', array($this, 'clicked'));
        $this->connect_simple('delete-event', array('Gtk', 'main_quit'));
        $this->add_events(Gdk::BUTTON_PRESS_MASK);
        $this->set_title('Alpha Cairo Demo');
        $this->resize(400, 400);
        $this->set_position(Gtk::WIN_POS_CENTER);
        $this->set_app_paintable(TRUE);
        $this->set_border_width(0);
        $this->set_decorated(FALSE);
                
        $screen = $this->get_screen();
        $colormap = $screen->get_rgba_colormap();
        if (!$colormap) {
            print 'No support for alpha color maps.';
            $colormap = $screen->get_rgb_colormap();
            $this->supports_alpha = FALSE;
        }
        else {
            print 'You has support for alpha color maps.';
            $this->supports_alpha = TRUE;
        }
        $this->set_colormap($colormap);
                
        $this->show_all();
    }
    
    public function expose($widget, $event) {
        
        $context = $this->window->cairo_create();
        $context->setOperator(CairoOperator::SOURCE);
    
        if ($this->supports_alpha) 
            $context->setSourceRgba(1, 1, 1, 0);
        else
            $context->setSourceRgb(1, 1, 1);
        
        $context->paint();
        
        $context->setSourceRgba(1, 0, 0, $this->alpha);
        $sizes = $this->get_size();
        $context->arc(
            $sizes[0] / 2, $sizes[1] / 2,
            ($sizes[0] fill();
    }
    
    public function clicked($widget, $event) {
        
        $this->set_decorated(!$this->decorated);
    }
    
    public function alphaChanged($range, $scroll_type, $value) {
        
        $this->alpha = $value / 100;
        $this->queue_draw();
    }
}

new GtkCairoExample;

Gtk::main();


And that's it. I think you'll agree PHP-GTK, couple with PECL/Cairo is something quite wonderful and powerful.


[i]Cross-posted from Mark's blog (http://mahcuz.com/wp/2010/06/php-gtk-peclcairo-an-example/)


Is This A Good Question/Topic? 0
  • +

Page 1 of 1