PHP-GTK 2 code snippets

PHP-GTK2

Building a GtkMenubar

Here is a quick overview of how to build a menu bar using php-gtk2 widgets. Think of it as a cheat sheet on menubar and friends.

GtkMenubar example overview The goal of this page is to list the most important objects and methods needed to build a basic menubar. You can use this document to write your own function or menubar class builder. We try to keep this document as simple as possible, not to show all advanced mechanisms.

Widget tree

Here is a complete widget tree with some useful information

  • tree level ; you can find this level on source code comments,
  • php variable name (sometimes)
  • Gtk class related
  • variable or strings instance in source code below
  • methods available to build object tree
# widget tree (or relations) :
  0 - $window  (GtkWindow) -> add()
    1 - $menubar  (GtkMenubar) -> append() or add()
      2 - $item  (GtkMenuItem) (File, Edit) -> set_submenu()
        3 - $menu  (GtkMenu) ($quit_menu, $file_menu) -> append() or add()
          4 - $menu_item (GtkMenuItem|GtkSeparatorMenuItem|GtkRadioMenuItem)

and here is the expected widget representation on your screen GtkMenubar overview components

  menubar:
    File
      New
      Quit
    Edit
      Cut
      Copy
      Paste
      ---
      - Choice 1
      * Choice 2 (activated)
      - Choice 3

Details

Here is a complete source code example. For this example, code indentation is relative to the object tree hierarchy, not to the code structure as usual. A few points :

  1. to build a group a toggle buttons, give null to first widget in a group, and the first widget in successive widget creations.
  2. you can use either append() or add() methods without significant changes.
  3. default value is "choice 2" for radio buttons ; see the set_active() method,
  4. for button, use "activate" signal,
  5. for toggle buttons, the signal to catch is "toggled",
<?php
$menubar
= new GtkMenuBar(); // 1
$menubar->append($file_item = new GtkMenuItem('_File')); // 2
$menubar->append($edit_item = new GtkMenuItem('_Edit')); // 2

 
$file_item->set_submenu($quit_menu = new  GtkMenu()); // 3

   
$quit_menu->append($file_new_item = new GtkMenuItem('_New'));   // 4
   
$quit_menu->append($file_quit_item = new GtkMenuItem('_Quit')); // 4

 
$edit_item->set_submenu($edit_menu = new  GtkMenu()); // 3

    # create 3 buttons : signal : 'activate'
   
$edit_menu->append($edit_cut_item   = new GtkMenuItem('_Cut'));   // 4
   
$edit_menu->append($edit_copy_item  = new GtkMenuItem('Co_py'));  // 4
   
$edit_menu->append($edit_paste_item = new GtkMenuItem('_Paste')); // 4

   
$edit_menu->append(new GtkSeparatorMenuItem());  // 4

    # create a group of radio buttons : signal : 'toggled' // note 1
   
$edit_menu->append($radio1 = new GtkRadioMenuItem(null,    'Choice _1')); // 4
   
$edit_menu->append($radio2 = new GtkRadioMenuItem($radio1, 'Choice _2')); // 4
   
$edit_menu->append($radio3 = new GtkRadioMenuItem($radio1, 'Choice _3')); // 4
   
$radio2->set_active(true); // note 3

# setup some signal callbacks
$file_quit_item->connect_simple('activate', array('Gtk','main_quit'));  //note 4
$file_new_item->connect  ('activate', 'menu_activate', 'file_new');
$edit_copy_item->connect ('activate', 'menu_activate', 'edit_copy');
$edit_paste_item->connect('activate', 'menu_activate', 'edit_paste');
$edit_cut_item->connect  ('activate', 'menu_activate', 'edit_cut');

$radio1->connect('toggled', 'menu_toggle', 'edit_choice1');  // note 5
$radio2->connect('toggled', 'menu_toggle', 'edit_choice2');
$radio3->connect('toggled', 'menu_toggle', 'edit_choice3');

# php-gtk usual application initialization
$window = new GtkWindow();  // 0
$window->set_size_request(150, -1);
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->set_position(Gtk::WIN_POS_CENTER);

$window->add($menubar);   // 0

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

function
menu_activate($button, $userdata) {
  echo
"button : $userdata\n";
}

function
menu_toggle($button, $userdata) {
 
# if button is active (selected)
 
if ($button->get_active())
    echo
"button toggle : $userdata\n";
}
?>

Links

Examples

API

Fichier attachéTaille
gtk-menubar-overview.php_.txt2.51 Ko

Building a submenu in a GtkMenubar.

A short tutorial showing how to build a submenu in GtkMenubar. You can refer to the complete GtkMenubar tutorial for full explanations.

Details

GtkMenubar submenu example To build a submenu, first create (see note numbers on source code):

  1. a standard menu (GtkMenu and object tree, see tutorial about this),
  2. a GtkMenuItem supporting the submenu ($view_zoom_item),
  3. a GtkMenu with associated menu items,
  4. and link the submenu to the menu item using set_submenu() method.

Notes :

  • you can create as many submenu levels as needed. Nested submenus are unlimited,
  • popup submenus are create using the same method.

Visual Tree

  menubar:
    ...
    View
      Line
      Border
      Toolbar
      ---
      Zoom
        50
        100
        150
      ---
      Scrollbar

Logical tree and relationships

  0 - $window  (GtkWindow) -> add()
    1 - $menubar  (GtkMenubar) -> append() or add()
      2 - $view_item  (GtkMenuItem)  -> set_submenu($view_menu)
        3 - $view_menu  (GtkMenu) -> append() or add()
          4 - $view_zoom_item (GtkMenuItem)
            5 - $zoom_menu (GtkMenu) -> append()
              6 - $view_zoom_50_item  (GtkMenuItem)
              6 - $view_zoom_100_item (GtkMenuItem)
              6 - $view_zoom_150_item (GtkMenuItem)

Source code

Here is the relevant part of the source code ; the full source is available as an attachment.

<?php
$menubar
= new GtkMenuBar(); // 1 - note 1
$menubar->append($view_item = new GtkMenuItem('_View')); // 2

 
$view_item->set_submenu($view_menu = new  GtkMenu()); // 3

   
$view_menu->append($view_line_item    = new GtkMenuItem('_Line'));    // 4
   
$view_menu->append($view_border_item  = new GtkMenuItem('_Border'));  // 4
   
$view_menu->append($view_toolbar_item = new GtkMenuItem('_Toolbar')); // 4

   
$view_menu->append(new GtkSeparatorMenuItem());                       // 4

   
$view_menu->append($view_zoom_item = new GtkMenuItem('_Zoom'));       // 4 - note 2

     
$view_zoom_item->set_submenu($zoom_menu = new  GtkMenu());          // 5 - note 3, 4
     
$zoom_menu->append($view_zoom_50_item  = new GtkMenuItem('_50'));   // 6
     
$zoom_menu->append($view_zoom_100_item = new GtkMenuItem('_100'));  // 6
     
$zoom_menu->append($view_zoom_150_item = new GtkMenuItem('15_0'));  // 6

   
$view_menu->append(new GtkSeparatorMenuItem());

   
$view_menu->append($radio3 = new GtkRadioMenuItem(null, 'Scrollbar'));
?>

Links

Fichier attachéTaille
gtk-menubar-overview-submenu.php_.txt3.61 Ko

CommandWidget - a terminal like widget for php-gtk

Command widget demo The CommandWidget is nearly like a terminal and allows the user to issue commands to a shell and view the result in a GtkTextView widget.

Details

This widget can be used if you want to run some commands in the background and never use any more any printf. Or if you have some text output at console the and would like to see it in a window. CommandWidget can do that for you.

CommandWidget is a external process or a shell connected to a GtkTextview to display results. This connexion is made throught a pipe. You can read and write to this external process. This can be a (nearly) full interactive process, just like terminals (command.com for Windows). There is a special class with output only and really easy to use. It could be used to proide feedback to some management tasks using external commands (say, tar for example).

Example of use

The code below wil create a window just like the one on top of this page. You can send commands using the text entry field at the bottom of the windows, and the results are displayed on the top part (a GtkTextView widget). Text color is higlighted for commands and errors.

<?php
# create the main window
$window = new GtkWindow();

$window->set_size_request(400, 500);
$window->set_position(Gtk::WIN_POS_CENTER);
$window->connect_simple('destroy', array('Gtk', 'main_quit'));

 
$window->set_title('CommandWidget demo');
 
# putenv('TERM=dumb');
 
$window->add(new CommandWidget('/bin/bash'));

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

ShellCommandWidget

ShellCommandWidget is a derived class to simplify usage for text output only for external commands. This widget has no entry widget, so commands can only sent from a script. This widget creates a pipe with a standard shell waiting for commands to be executed. Here is an example of use (this is a bit similar to CommandWidget:

<?php
# create the main window
$window = new GtkWindow();

$window->set_size_request(40, 50);
$window->set_position(Gtk::WIN_POS_CENTER);
$window->connect_simple('destroy', array('Gtk', 'main_quit'));

 
$window->add($shell = new ShellCommandWidget());

$window->show_all();

 
$shell->exec('ls -al ; ps -aef');
 
$shell->exec('ls -al /etc | grep ".conf"');
 
$shell->exec('who am I');
 
$shell->exec('whoami');
 
$shell->exec('make love');  # echo to stderr (not working now)

Gtk::main();
?>

Full source code

See the attached sources file below

Bugs

See on source code header

Fichier attachéTaille
gtk-command-widget.php_.txt10.29 Ko

Desktop Gadget - a small widget moving around your screen

DesktopGadget demo 1 The DesktopGadget is a small window moving around you desktop screen ; it's a container widget and is able to contains usefull object for short information or notification.

Details

DesktopGadget demo2 DesktopGadget demo3 DesktopGadget is :

  • as small as possible and display short messages or notification ; in given code, this is just a literal clock ; I intend to build a Google calendar gadget notifier.
  • this visual gadget is movable all arround the screen ; there is some options in script code to change display and position (on top, on desktop ...)
  • position is saved in a configuration file using Config class
  • given code is fully object oriented and allow some changes
  • if you can write some php-code and have some Gtk skill, you could be able to write some (un)usefull gadgets.

The most interesting part of code is at the end and show you how it's very easy to add some gadgets :

<?php
# include some classes code before ...

# an example gadget (it's just a gtkWidget)
class ClockGadget extends GtkLabel{
    protected
$timeout_id;
    protected
$format;

    public function
__construct($format = 'd/m - H:i:s'){
       
parent::__construct();
       
$this->format=$format;
       
$this->timeout_id = Gtk::timeout_add(1000, array($this, 'on_timeout'));
       
$this->modify_font(new PangoFontDescription('Sans 7'));
       
$this->on_timeout();
    }

    public function
on_timeout(){
       
$this->set_text(date($this->format));
        return
true;
    }
}


$gadget = new DesktopGadget($title='Desktop Gadget');
$gadget->pack_start($clock = new ClockGadget());

Gtk::main();
?>

Restrictions

You should have write acces to directory where this script is running to save gadget position.

Source Code

source code is provided as a link below for free as LGPL licence ; also available here

Links

  • GtkWindow API : set_skip_taskbar_hint, set_skip_pager_hint, set_keep_above, (un)stick, set_decorated and custructor

Todo

  • write some gadgets : google calendar, hosts pinging, email watcher for example
  • write a configuration interface
Fichier attachéTaille
desktop-gadget.php_.txt10.05 Ko

Dial widget - a graphical widget to display an analog value.

Dial widget The Dial widget is a graphical object which displays an analog value. This is a direct port of the Gtk tutorial about creating graphical widgets

Details

The Dial widget can display a value from -120 to 120. This could be changed, just write the right mathematical operations (I'm a bit lazy, guys). This widget can draw directly into a Window (direct drawing) or draw into a pixmap, which we can then refresh as needed ; just look at the class source code to see how it is done.

Direct draw is much faster for large windows ; I can't see differences for small ones. The actual graphical part (drawing the dial) is a php-gtk port from the Gtk tutorial. This widget is a good start when you need to build dynamic graphical widgets like this one.

Example of use

<?php
$dial
= new Dial();  # (1)
$dial->set_size_request(80, 80);
$dial->set_value(0);

$window = new GtkWindow();
$window->connect_simple('destroy', array('gtk', 'main_quit'));
$window->set_title('Dial demo');
$window->set_position(Gtk::WIN_POS_CENTER);
$window->set_border_width(8);
$window->add($dial); # (2)
$window->show_all();

Gtk::timeout_add(100, 'dial_timeout', $dial);  # (3)

Gtk::main();

/**
* a timeout function to change the dial value from -120 to 120
* giving an animation to the widget.
*/
function dial_timeout($dial)
  {
  static
$value = 0;
  static
$increment = 4;

  if (
$value < -120)
   
$increment *= -1 ;

  if (
$value > 120)
   
$increment *= -1;

 
$value += $increment;
 
 
# $dial->set_value($value / 180*PI);
 
$dial->set_value($value);  # (4)
   
 
Gtk::timeout_add(20, 'dial_timeout', $dial);
  return
FALSE;
  }
?>

Highlights

  1. create a dial widget,
  2. add this widget to a composite widget (any composite can be used)
  3. setup a timeout function to update the dial value
  4. set the dial value with $dial->set_value($val)

Full source code

See the attached sources file below

Fichier attachéTaille
gtk-drawing-area-dial.php_.txt6.31 Ko

Easily empty GtkContainer/GtkWindow

This function provides an easy way to empty a GtkWindow/GtkContainer. I use it when i need to refresh a part of my app by replacing some widgets with others. It can destroy or preserve the child widgets depending on your needs.

Sample usage

<?php

# empty my container and destroy its children
empty_widget($myContainer);
# empty widget and preserve children
empty_widget($myContainer,false);
# use it as a callback on a button click
$myButton->connect_simple('clicked','empty_widget',$myContainer);
?>

Note: This is a rewrite for php5/php-gtk2 of the same function i've already poster for php4/php-gtk1. Hope this will be helpful to you.

function empty_widget

<?php
/**
* remove and optionally destroy all child from a widget
* @param GtkWidget    &$call_widget is the calling widget or if
*                     is the only Gtkcontainer is the one to empty
* @param GtkContainer [&$target_widget] is the gtkcontainer to empty
*                     if only one Gtkwidget is passed and is a
*                     Gtkcontainer it would be this one by default
* @param bool         $destroy_childs default is TRUE
*                     so the childs widget will be destroy by default,
*                     pass FALSE as last argument to preserve childs widgets
* @licence LGPL
**/
function empty_widget($call_widget) {
 
# If we receive only one arguments it must be the target widget
 
$n = func_num_args();
  if(
$n>1){
    if(!
is_bool($destroy_childs = func_get_arg($n-1)))
      unset(
$destroy_childs);
    }
  switch(
$n){
    case
1:
      if(! (
$call_widget instanceof GtkContainer || $call_widget instanceof GtkWindow ) )
        return
FALSE;
     
$widget = $call_widget;
     
$destroy_childs = TRUE;
      break;
    case
2:
      if(
is_bool($destroy_childs = func_get_arg(1)) ){
       
$widget = func_get_arg(0);
                if(! (
$widget instanceof GtkContainer || $widget instanceof GtkWindow ) )
          return
FALSE;
      }elseif(
$destroy_childs instanceof GtkContainer || $destroy_childs instanceof GtkWindow ){
       
$widget = $destroy_childs;
       
$destroy_childs = TRUE;
      }else{
        return
FALSE;
      }
      break;
    case
3:
      if(!
is_bool($destroy_childs = func_get_arg(2)) )
                return
false;
           
$widget = func_get_arg(1);
            if(! (
$widget instanceof GtkContainer || $widget instanceof GtkWindow ) )
                return
false;
            break;
    default:
      return
FALSE;
  }
   
   
$childs = $widget->get_children();
    if(
count($childs) ){
        foreach(
$childs as $c ){
           
$widget->remove($c);
            if(
$destroy_childs )
               
$c->destroy();
        }
    return
TRUE;
  }else{
    return
FALSE;
  }
}
?>

Editable cells within GtkTreeView

Intro

this script is based on example in the PHP-GTK 2 manual, which was left unfinished. So here is a working example with editable columns.

Editable Cell renderer example With editable cells, you can display cells, and edit cell content in the same place. This is a very convenient way to manage list of items without having to popup some boring editor window. To edit text cells, you just need to click a second time on an item when it has been selected.

Details

To make cells editable, you just need to set GtkCellRendererText object as editable :

<?php
$model
= new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_BOOLEAN);
...
$cell_renderer = new GtkCellRendererText();
$cell_renderer->set_property('editable', true);
?>

then connect a signal callback function to the "edited" signal; this callback will update the data model

<?php
$cell_renderer
->connect('edited''callback_text_cell_edited');

function
callback_text_cell_edited($cellrenderertext, $path, $new_text){
  global
$model;
 
$iter = $model->get_iter($path);
 
$model->set($iter, 0, $new_text);
}
?>

For toggle renderers, connect "toggled" signal and swap the toggle state.

Source code

<?php
/**
* This sample shows how to use the
* GtkCellRenderer along with GtkTreeView
*/

// Creates the main window
$window = new GtkWindow;
$window->set_title('Cell Renderers');
$window->connect_simple('destroy', array('Gtk', 'main_quit'));
$window->set_position(GTK::WIN_POS_CENTER);
$window->set_default_size(280,140);

// Creates the data model
$model = new GtkListStore(Gtk::TYPE_STRING, Gtk::TYPE_BOOLEAN);

// Creates the view to display the content
$view = new GtkTreeView($model);

// Creates two columns
$column1 = new GtkTreeViewColumn('Language');
$column2 = new GtkTreeViewColumn('Open Source?');

// Add the columns to the view
$view->append_column($column1);
$view->append_column($column2);

// Creates two cell-renderers
$cell_renderer1 = new GtkCellRendererText();
$cell_renderer2 = new GtkCellRendererToggle();

// change the property 'width'
$cell_renderer1->set_property('width', 180);
$cell_renderer1->set_property('editable', true);
$cell_renderer2->set_property('width', -1);
$cell_renderer1->connect('edited''callback_text_cell_edited');
$cell_renderer2->connect('toggled', 'callback_toggle_cell_toggled');

// Pack the cell-renderers
$column1->pack_start($cell_renderer1, true);
$column2->pack_start($cell_renderer2, true);

// link the renderers to the model
$column1->set_attributes($cell_renderer1, 'text',   0);
$column2->set_attributes($cell_renderer2, 'active', 1);

// Add some data
$model->append(array('PHP',             true));
$model->append(array('Python',          true));
$model->append(array('Delphi',          false));
$model->append(array('Visual Basic',    false));

// pack the view inside the window
$window->add($view);

// show the window
$window->show_all();
Gtk::main();

function
callback_text_cell_edited($cellrenderertext, $path, $new_text) {
  global
$model;
 
$iter = $model->get_iter($path);
 
$model->set($iter, 0, $new_text);
}

function
callback_toggle_cell_toggled($cellrenderer, $path){
  global
$model;
 
$value = $cellrenderer->get_active();
 
$iter = $model->get_iter($path);
 
$model->set($iter, 1, !$value);
}
?>
Fichier attachéTaille
editable cell render source code.2.04 Ko

Extending GtkDrawingArea with OO methods

GxDrawingArea example This example shows a nice drawing area class, derived from GtkDrawingArea:

  • with 2D matrix transformation : rotate, translate, scale
  • with many classes : Point, Matrix, GxObject, GxPoint, GxRectangle, GxDrawingArea

This framework is not complete, but marks the first snapshot for a drawing editor done in php-gtk2. The main part of the code is in the "timeout" function (GxDrawingAreaTest class).

A more complete library can be found at : http://quinton.free.fr/php/gtk/gxlib/. There is now a near working drawing editor and some tutorial-games applications (brick-breaker, snake, tetris ...). Please, note : this code below is a little outdated.

<?php
/**
*   Gx is a php-gtk2 graphic library - Marc Quinton - june 2006.
*/

error_reporting(E_ALL);

/**
* @version $Id$
* @copyright 2004
**/
class Point{
  var
$x;
  var
$y;

  function
Point($x = 0, $y = 0){
   
$this->x = $x;
   
$this->y = $y;
  }

  function
x(){
    return
$this->x;
  }

  function
y(){
    return
$this->y;
  }

    function
vals(){
        return array(
$this->x, $this->y);
    }

  function &
transform(&$view, &$screen){
         
$p2 = new Point();
         
$p2->x = intval(($this->x - $view->x) * $screen->w/$view->w);
         
$p2->y = intval($screen->h - ($this->y - $view->y) * ($screen->h/$view->h));
          return
$p2;
  }

  function
to_text(){
    return
"Point({$this->x}, {$this->y});";
  }

  function
in_window($window){
    if(
$this->x < $window->x)
          return
false;
    if(
$this->y < $window->y)
          return
false;

    if(
$this->x > $window->x+$window->w)
          return
false;
    if(
$this->y > $window->y+$window->h)
          return
false;

    return
true;
  }
}



class
Matrix{

 
# matrix coefs
   
var $m11, $m21, $m12, $m22, $v1, $v2;

    function
Matrix($m11=null, $m21=null, $m12=null, $m22=null, $v1=0, $v2=0){

        if(
$m11 == null && $m12 == null && $m21 == null && $m22 == null)
           
$this->identity();
        else
           
$this->initialize($m11, $m21, $m12, $m22, $v1, $v2);
    }

   
# The identity transformation
   
function identity(){
       
$this->m11 = 1;
       
$this->m12 = 0;
       
$this->m21 = 0;
       
$this->m22 = 1;
       
$this->v1  = 0;
       
$this->v2  = 0;
    }

   
# The identity transformation
   
function is_identity(){
        if(
$this->coefs() == array(1,0,0,1,0,0))
            return
true;
        else
            return
false;
    }

    function
is_near_identity($epsilon=1E-15){
       
$m = $this->_clone();
       
$m->round($epsilon);
        return
$m->is_identity();
    }

    function
initialize($m11, $m21, $m12, $m22, $v1, $v2){
       
$this->m11 = $m11;
       
$this->m12 = $m12;
       
$this->m21 = $m21;
       
$this->m22 = $m22;
       
$this->v1  = $v1;
       
$this->v2  = $v2;
    }

    function
translate($offx, $offy){
       
$this->v1+=$offx;
       
$this->v2+=$offy;
        return
true;
    }

   
# rotate angle in degres
   
function rotate($angle, $cx=0, $cy=0){
       
$this->rotate_radians($angle*M_PI/180, $cx, $cy);
    }

   
# rotate angle in degres
   
function rotate_radians($angle, $cx=0, $cy=0){
       
$s = sin($angle);
       
$c = cos($angle);
   
$offx = $cx - $c*$cx + $s*$cy;
   
$offy = $cy - $s*$cx - $c*$cy;

       
$m = new Matrix($c, $s, -$s, $c, $offx, $offy);
       
$this->composite($m);
    }

   
# scale
   
function scale($x, $y=null){
       
$this->m11*=$x;
        if(
$y==null)
           
$this->m22*=$x;
        else
           
$this->m22*=$y;
    }

    function
transformPoint($x, $y=null)
    {

        if(
get_class($x) == 'Point'){
           
$p = $x;
           
$p2 = new Point(
               
$this->m11*$p->x + $this->m12*$p->y + $this->v1,
               
$this->m21*$p->x + $this->m22*$p->y + $this->v2
           
);
            return(
$p2);
        }

        return array(
            (
$this->m11*$x) + ($this->m12*$y) + $this->v1,
            (
$this->m21*$x) + ($this->m22*$y) + $this->v2
           
);
    }



   
/*
        Return the inverse of trafo. If the matrix is singular, raise a error
  */
   
function inverse(){

       
$det = $this->m11 * $this->m22 - $this->m12 * $this->m21;

        if(
$det == 0.0) {
           
trigger_error("inverting singular matrix");
            return
NULL;
        }

       
$m11 $this->m22 / $det;
       
$m12 = -$this->m12 / $det;
       
$m21 = -$this->m21 / $det;
       
$m22 $this->m11 / $det;

       
$this->initialize(
               
$m11, $m21, $m12, $m22,
                -
$m11 * $this->v1 - $m12 * $this->v2,
                -
$m21 * $this->v1 - $m22 * $this->v2);
    }

   
#
   
function composite(&$m){

       
$this->initialize(
               
$this->m11 * $m->m11 + $this->m12 * $m->m21,
               
$this->m21 * $m->m11 + $this->m22 * $m->m21,
               
$this->m11 * $m->m12 + $this->m12 * $m->m22,
               
$this->m21 * $m->m12 + $this->m22 * $m->m22,
               
$this->m11 * $m->v1  + $this->m12 * $m->v2 + $this->v1,
               
$this->m21 * $m->v1  + $this->m22 * $m->v2 + $this->v2
           
);
    }

    function &
composite_copy(&$m){
            return new
Matrix(
               
$this->m11 * $m->m11 + $this->m12 * $m->m21,
               
$this->m21 * $m->m11 + $this->m22 * $m->m21,
               
$this->m11 * $m->m12 + $this->m12 * $m->m22,
               
$this->m21 * $m->m12 + $this->m22 * $m->m22,
               
$this->m11 * $m->v1  + $this->m12 * $m->v2 + $this->v1,
               
$this->m21 * $m->v1  + $this->m22 * $m->v2 + $this->v2
           
);
    }

    function
show(){
       
printf("|%3.3f, %3.3f, %3.3f|\n", $this->m11, $this->m12, $this->v1);
       
printf("|%3.3f, %3.3f, %3.3f|\n", $this->m21, $this->m22, $this->v2);
        echo
"\n";
    }

    function
_clone(){
       
#$m = &new Matrix($this->m11, $this->m21, $this->m12, $this->m22, $this->v1, $this->v2);
       
return $this;
    }

    function
to_text(){
        return
'Matrix(' .
           
$this->m11 . ', ' .
           
$this->m21 . ', ' .
           
$this->m12 . ', ' .
           
$this->m22 . ', ' .
           
$this->v1  . ', ' .
           
$this->v2  . ');';
    }

    function
coefs(){
        return array(
$this->m11, $this->m21, $this->m12, $this->m22, $this->v1, $this->v2);
    }

    function
equals($m){
        if(
$this->m11 != $m->m11)
            return
false;
        if(
$this->m12 != $m->m12)
            return
false;
        if(
$this->m21 != $m->m21)
            return
false;
        if(
$this->m22 != $m->m22)
            return
false;
        if(
$this->v1 != $m->v1)
            return
false;
        if(
$this->v2 != $m->v2)
            return
false;
        return
true;
    }

    function
near_value($val, $near, $epsilon=1.0E-15){
        if(
$val > $near+$epsilon)
            return
false;
        if(
$val < $near-$epsilon)
            return
false;
        return
true;
    }

   
# round floting point values of matrix coefs near 0 or 1
    # usefull for identity matrix check after multiple operations (rotation, scales ....)
   
function round($epsilon=1E-15){
       
$list = array('m11', 'm21', 'm12', 'm22', 'v1', 'v2');
        foreach(
$list as $coef_name){
           
$val = &$this->$coef_name;
           
$val = $this->near_value($val, 0, $epsilon)?0:$val;
           
$val = $this->near_value($val, 1, $epsilon)?1:$val;
        }
    }
}


class
GxObject{
    protected
$transfo; # matrix transformation
   
function __construct($x=0,$y=0){
       
$this->transfo = new Matrix(1,0,0,1,-$x,-$y);
    }

    function
translate($x, $y){
        return
$this->transfo->translate($x, $y);
    }

   
# rotate angle in degres
   
function rotate($angle, $cx=0, $cy=0){
        return
$this->transfo->rotate($angle, $cx, $cy);
    }

   
# rotate angle in degres
   
function rotate_radians($angle, $cx=0, $cy=0){
        return
$this->transfo->rotate_radians($angle, $cx, $cy);
    }

   
# scale
   
function scale($x, $y=null){
        return
$this->transfo->scale($x, $y);
    }

    function
transformPoint($point)
    {
       
$p1 = new Point($point->x, $point->y);
       
$p2 = $this->transfo->transformPoint($p1);
       
$point->x = $p2->x; $point->y = $p2->y;
        return
$point;
    }

    function
bounding_box(){
        return array(
0,0,0,0);
    }
}

class
GxPoint extends GxObject{
    var
$x, $y;
    function
__construct($x=0, $y=0){
       
parent::__construct();
       
$this->x = $x ; $this->y=$y;
    }

    function
x(){
        return
$this->x;
    }
    function
y(){
        return
$this->y;
    }

    function
to_text($name=''){
    return
"GxPoint-$name({$this->x}, {$this->y});";
  }

}

class
GxLine extends GxObject{
    protected
$p1, $p2;
    function
__construct($p1=null, $p2=null){
       
parent::__construct();
       
$this->p1 = $p1;
       
$this->p2 = $p2;
    }

    function &
p1(){
        return
$this->p1;
    }
    function &
p2(){
        return
$this->p2;
    }
}

class
GxRectangle extends GxObject{
    var
$x, $y, $w, $h;
    function
__construct($x, $y, $w, $h){
       
parent::__construct();
       
$this->x = $x;
       
$this->y = $y;
       
$this->w = $w;
       
$this->h = $h;
    }

    function &
p1(){
        return
$this->p1;
    }
    function &
p2(){
        return
$this->p2;
    }

    function
center(){
       
$p = new Point($this->x+$this->w/2, $this->y+$this->h/2);
        return
$p;
    }

    function
transformPoint($point, $center)
    {
       
$p1 = new Point($point->x - $center->x, $point->y-$center->y);
       
$p2 = $this->transfo->transformPoint($p1);
       
$point->x = $p2->x+$center->x; $point->y = $p2->y+$center->x;
        return
$point;
    }

    function
bounding_box(){
        list(
$p1,$p2,$p3,$p4) = $this->transformedPoints();
       
$x_min = min($p1->x, $p2->x, $p3->x, $p4->x);
       
$y_min = min($p1->y, $p2->y, $p3->y, $p4->y);

       
$x_max = max($p1->x, $p2->x, $p3->x, $p4->x);
       
$y_max = max($p1->y, $p2->y, $p3->y, $p4->y);

       
$x = $x_min; $y=$y_min;
       
$w = $x_max-$x_min; $h=$y_max-$y_min;

        return array(
$x, $y, $w, $h);
    }

    function
transformedPoints(){
           
$center = $this->center();

           
$x = $this->x; $y = $this->y;
           
$w = $this->w; $h = $this->h;

           
$p1 = new GxPoint($x,    $y);
           
$p2 = new GxPoint($x+$w, $y);
           
$p3 = new GxPoint($x+$w, $y+$h);
           
$p4 = new GxPoint($x,    $y+$h);

           
$p1 = $this->transformPoint($p1, $center);
           
$p2 = $this->transformPoint($p2, $center);
           
$p3 = $this->transformPoint($p3, $center);
           
$p4 = $this->transformPoint($p4, $center);

            return array(
$p1, $p2,$p3,$p4);
    }

    function
clear($da){
        list(
$x, $y, $w, $h) = $this->bounding_box();
       
$da->clear($x, $y, $w, $h);
    }

    function
draw($da){

        list(
$p1,$p2,$p3,$p4) = $this->transformedPoints();
       
$da->draw_line($p1,$p2);
       
$da->draw_line($p2,$p3);
       
$da->draw_line($p3,$p4);
       
$da->draw_line($p1,$p4);
       
$da->draw_line($p1,$p3);
       
$da->draw_line($p2,$p4);

       
# draw bounding_box (debug)
       
list($x, $y, $w, $h) = $this->bounding_box();
       
$da->draw_rectangle($x, $y,$w+1, $h+1);
    }
}

class
GxPolyLine extends GxObject{
    protected
$points;

    function
__construct($list= null){
       
parent::__construct();
        if(
$list == null)
           
$this->points = array();
        else
           
$this->points = $list;
    }

    function
add($point){
       
$this->points[] = $point;
    }

    function &
points(){
        return
$this->points;
    }
}

class
GxDrawingArea extends GtkDrawingArea{

    protected
$pixmap = null;

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

    }

   
// void draw_arc(GdkGC gc, bool filled, int x, int y, int width, int height, int angle1, int angle2);
   
function draw_arc($x, $y, $w, $h, $angle1, $angle2){
       
$this->pixmap->draw_arc($this->style->black_gc, true, $x, $y, $w, $h, $angle1, $angle2);
       
$this->redraw($x, $y, $w, $h);
    }

    function
draw_rectangle($x, $y, $w, $h, $filled=false){
       
$this->pixmap->draw_rectangle($this->style->black_gc, $filled, $x, $y, $w, $h);
       
$this->redraw($x, $y, $w, $h);
    }

   
// MQ : devrait utiliser pango, mais n'est pas encore bien implementé dans php-gtk.
   
function draw_text($x, $y, $text, $clear=true){
       
# deprecated method ; should use GdkDrawable::draw_layout (with pango)
       
$w = 7*strlen($text); $h = 15;
       
# if($clear)
           
$this->clear($x,$y-$h,$w,$h);
        @
$this->pixmap->draw_string($this->my_font, $this->style->black_gc, $x, $y, $text);
       
# $this->draw_rectangle($x,$y,$w,$h);
       
$this->redraw($x, $y-$h, $w, $h);
    }

    function
draw_line($x1, $y1, $x2=null, $y2=null){
        if(
is_a($x1, 'GxPoint') && is_a($y1, 'GxPoint')){
           
$p1 = $x1;
           
$p2 = $y1;
           
$x1 = $p1->x();
           
$x2 = $p2->x();
           
$y1 = $p1->y();
           
$y2 = $p2->y();
        }
       
$this->pixmap->draw_line($this->style->black_gc, $x1, $y1, $x2, $y2);
       
$x = min($x1,$x2);
       
$y = min($y1,$y2);
       
$w=abs($x2-$x1);
       
$h=abs($y2-$y1);
       
$this->redraw($x, $y, $w, $h);
    }

    function
redraw($x=null, $y=null, $w=null, $h=null){
        if(
$x == null)
           
$this->queue_draw_area(0, 0, $this->w, $this->h);
        else
           
$this->queue_draw_area($x, $y, $w, $h);
    }

    function
clear($x=null, $y=null, $w=null, $h=null){
        if(
$x==null){
           
$x=0 ; $y=0 ; $w=$this->w ; $h=$this->h;
        }
       
$this->pixmap->draw_rectangle($this->style->white_gc, true, $x, $y,$w, $h);
       
$this->redraw($x, $y, $w, $h);
    }
}

class
GxDrawingAreaTest extends GxDrawingArea{

   
# tests
   
protected $point=null;
    protected
$polyline=null;
    protected
$rectangle1;
    protected
$rectangle2;

    function
__construct(){

       
parent::__construct();

       
$this->connect('expose_event'       , array($this, 'expose_event'));
       
$this->connect('configure_event'    , array($this, 'configure_event'));

       
$this->connect('motion_notify_event', array($this, 'motion_notify_event'));
       
$this->connect('button_press_event' , array($this, 'button_press_event'));

       
$this->set_events(
               
Gdk::EXPOSURE_MASK
           
| Gdk::LEAVE_NOTIFY_MASK
           
| Gdk::BUTTON_PRESS_MASK
           
| Gdk::POINTER_MOTION_MASK
           
| Gdk::POINTER_MOTION_HINT_MASK);

           
# GDK_ALL_EVENTS_MASK

       
Gtk::timeout_add(1000, array($this, 'timeout'), 1234);
       
$this->rectangle1 = new GxRectangle(150,150, 50, 100);
       
$this->rectangle2 = new GxRectangle(300,150, 100, 50);
       
$this->count=0;
       
$this->scale = 1.1;
    }


    function
configure_event($widget, $event)
    {
               
$this->w = $widget->allocation->width;
               
$this->h = $widget->allocation->height;

       
$this->pixmap = new GdkPixmap($widget->window,
                               
$widget->allocation->width,
                               
$widget->allocation->height,
                                -
1);
       
$this->pixmap->draw_rectangle($widget->style->white_gc,
                           
true, 0, 0,
                           
$widget->allocation->width,
                           
$widget->allocation->height);
               
$this->point = null;
               
$this->polyline = new GxPolyLine();
               
# $this->my_font = new GdkFont("-adobe-helvetica-bold-r-normal--12-120-75-75-p-70-iso8859-1");
               
$this->my_font = $this->style->get_font();
               
$x = $widget->allocation->width/2;
               
$y = 15;
               
$this->draw_text($x, $y, 'Php-Gtk2 - Drawing Area sample test');
        return
true;
    }

    function
draw_brush($x, $y)
    {
       
$this->draw_arc($x - 4, $y - 4, 8, 8, 0, 64 * 360);
    }

    function
timeout(){

       
# MQ : should clear the right area, not full window
        # $this->clear();


        # display analog clock.
       
Gtk::timeout_add(500, array($this, 'timeout'), 1234);
       
$date = date('H:i:s');
       
$this->draw_text(15,15, $date);

       
$this->count++;
        if(
$this->count % 20 == 1){
           
$this->scale = 1/$this->scale;
        }

       
$this->rectangle1->clear($this);
       
$this->rectangle1->rotate(3);
       
# $this->rectangle1->scale($this->scale);
        # $this->rectangle1->translate(5,5);
       
$this->rectangle1->draw($this);


       
# $this->rectangle2->rotate(-6);
        # $this->rectangle2->scale(1/$this->scale);
        # $this->rectangle2->draw($this);

   
}

    function
expose_event($widget, $event)
    {
       
$widget->window->draw_drawable($widget->style->fg_gc[$widget->state],
                       
$this->pixmap,
                       
$event->area->x, $event->area->y,
                       
$event->area->x, $event->area->y,
                       
$event->area->width, $event->area->height);

        return
false;
    }



    function
button_press_event($widget, $event)
    {

        if (
$event->button == 1 && $this->pixmap) {
               
# $this->draw_brush($widget, (int)$event->x, (int)$event->y);
               
$this->draw_rectangle((int)$event->x, (int)$event->y, 10, 10);
               
$info = sprintf('(%d:%d)', $event->x, $event->y);
               
$this->draw_text((int)$event->x+15, (int)$event->y-15, $info);
                if(
$this->point != null){
                   
$p = new GxPoint($event->x, $event->y);
                   
# $this->draw_line($event->x, $event->y, $this->point->x(), $this->point->y());
                   
$this->draw_line($p, $this->point);
                }
               
$this->point = new GxPoint($event->x, $event->y);
               
$this->polyline->add($this->point);
        }

        return
true;
    }

    function
motion_notify_event($widget, $event)
    {
       
$window  = $event->window;
       
$pointer = $window->get_pointer();
       
$x = $pointer[0];
       
$y = $pointer[1];
       
$state = $pointer[2];

        if ((
$state & Gdk::BUTTON1_MASK) && $this->pixmap) {
               
$this->draw_brush($x, $y);
        }

        return
true;
    }
}


class
App extends GtkWindow
{

    function
__construct($parent = null)
    {
       
parent::__construct();

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

       
$this->set_title(__CLASS__);
       
$this->set_position(Gtk::WIN_POS_CENTER);
       
$this->set_default_size(-1, -1);
       
$this->set_border_width(8);

       
$this->add($this->__create_box());
       
$this->show_all();
    }
//function __construct($parent = null)



   
function __create_box()
    {
       
$vbox = new GtkVBox();
       
$vbox->show();

       
$drawing_area = new GxDrawingAreaTest();
       
$drawing_area->set_size_request(500, 500);
       
$vbox->pack_start($drawing_area);
//        $drawing_area->realize();


       
return $vbox;
    }
//function __create_box()
}//class Scribble extends GtkWindow


$app = new App();
Gtk::main();
?>

Extending GtkFileSelection : the FileSelectionDialog class

This FileSelectionDialog provides a wrapper around the standard GtkFileSelection class in PHP-GTK2, to make it easier to use.

Note : you can check a simpler introductory example to GtkFileSelection before delving into this class.

Main methods :

  • hide()
  • show()
  • get_selection() : return file section if OK pressed, return false if cancel button is pressed. (see usage below)

Quick overview (usage)

You can see how it easy to get a file ; all you need to do is to create a FileSelectionDialog instance and call the get_selection() method.

<?php
  $fs
= new FileSelectionDialog('test');
 
$fs->run();
 
$file = $fs->get_selection();
  if (
$file === FALSE)
    echo
"canceled\n";
  else
    echo
"selected file $file\n";
?>

Complete class code

complete code
<?php
error_reporting
(E_ALL);

/**
* library class (need to be extended)
* @author Marc Quinton / september 2006.
*/
define('FS_ACTIVATE',       0);
define('FS_CANCEL',         1);

class
FileSelectionDialog {
  private
$fs;         # file selection dialog widget
 
private $title;
  private
$status;

  public function
__construct($title) {
   
$this->title = $title;
   
$this->fs = new GtkFileSelection($title);
   
$this->status = null;
   
$this->fs->ok_button->connect_simple( 'clicked' ,
      array(
$this, 'activate'));
   
$this->fs->cancel_button->connect_simple( 'clicked' ,
      array(
$this, 'cancel'));
   
# need to catch double-click in list widget
 
}

  public function
show() {
   
$this->fs->show();
  }

  public function
hide() {
   
$this->fs->hide();
  }

  function
activate() {
   
$this->status = FS_ACTIVATE;
   
$this->hide();
   
Gtk::main_quit();
  }

  function
cancel() {
   
$this->status = FS_CANCEL;
   
$this->hide();
   
Gtk::main_quit();
  }

  public function
run() {
   
$this->show();
   
gtk::main();
  }

  public function
get_selection() {
  if (
$this->status == FS_ACTIVATE)
    return
$this->fs->get_filename();
  else
    return
false;
  }
}

class
MyFileSelectionDialog extends FileSelectionDialog {
  function
activate() {
   
parent::activate();
   
# do what you need here ...
   
echo "activate\n";
  }

  function
cancel() {
   
parent::cancel();
    echo
"cancel\n";
  }
}

/**
* test script
*/
function activate_fs_ok($fs) {
  echo
"fs_ok\n";
 
$fs->hide();
}

function
activate_select() {
 
# to handle cancel and OK button ; not really needed
  # $fs = new MyFileSelectionDialog('test');

 
$fs = new FileSelectionDialog('test');
 
$fs->run();

 
$file = $fs->get_selection();
  if (
$file === false)
    echo
"canceled\n";
  else
    echo
"selected file $file\n";
}

// standard stuff for window creation
$wnd = new GtkWindow();
$wnd->connect_simple('destroy', array('Gtk', 'main_quit'));
$wnd->set_position(Gtk::WIN_POS_CENTER);
$wnd->set_size_request(100, 100);

$btn = new GtkButton('select');
$btn->connect_simple('clicked', 'activate_select');

$wnd->add($btn);

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

Extending GtkMenu class for Popup menu dialog

GtkWidget popup menu example Here is a quick way to build a popup menu supporting event registration from a GtkWidget. Menu items are passed to the class constructor as a list of labels. This class emits activate signals with full context (x,y coordinates, button ...).

See example below for basic usage.

Details

  1. create Gtk standard stuff,
  2. create a Popup menu in a single a simple line with 3 items (Edit, Cut, Paste),
  3. connect an "activate" callback
  4. some items can be unsensitive depending on application context,
  5. set parent widget ; this widget must contain a Window to register some buttons events. No warning about that in this current code, but could be done,
  6. look at activate callback ; complete event context is available, containing (x,y) coordinates ; may be usefull in some usages.

Example

<?php
# include class code here


// Create a normal window
$wnd = new GtkWindow();  // note 1

$menu = new PopupMenu($menu = array('Edit', 'Cut', 'Paste')); // note 2
$menu->connect('activate', 'activate_callback');              // note 3
$menu->set_sensitive('Cut', false);                           // note 4
$menu->set_parent($wnd);                                      // note 5


// Standard stuff // note 1
$wnd->connect_simple('destroy', array('Gtk', 'main_quit'));
$wnd->show_all();
Gtk::main();

function
activate_callback($signal_name, $context, $user_data) { // note 6
 
$reason = $context['key'];
  echo
"popup activate : $reason\n";
 
# print_r($context);
}
?>

Class code source

<?php
class PopupMenu extends GtkMenu {
  protected
$items;
  protected
$menu_items;
  protected
$signal;
  protected
$parent_widget;

  function
__construct($items, $parent_widget=null) {
   
parent::__construct();
   
$this->items = $items;
   
$this->menu_items = array();
   
$this->parent_widget = $parent_widget;
   
$this->signal = new Gnope_FileExplorer_Signal();
   
$this->build($items);
  }

  protected function
build($items) {
    foreach(
$items as $item){
     
# key and label may be different ...
     
$label = $item;
     
$key = $item;
     
$this->append($key, $label);
    }

    if(
$this->parent_widget != null)
      
$this->set_parent($this->parent_widget);

   
$this->show_all();
  }

 
/**
   *
   * Set parent handler for popup window ;
   * - set event mask to receive button_press_mask,
   * - and register signal "button-press-event".
   *
   */
  
public function set_parent($parent_widget){
    
$this->parent_widget = $parent_widget;
    
$this->parent_widget->set_events(
       
$this->parent_widget->get_events() | Gdk::BUTTON_PRESS_MASK);
    
$this->parent_widget->connect('button-press-event', array($this, 'popup'), $this);
   }

 
/**
    * append a GtkMenuitem to this widget ;
    *
    */
 
public function append($key, $label) {
   
$this->menu_items[$key] = new GtkMenuItem($label);
   
$this->menu_items[$key]->connect_simple('activate',
      array(
$this, 'menu_activate'),
      array(
'key'=>$key, 'label' => $label));
   
parent::append($this->menu_items[$key]);
  }

 
# should be protected
 
function menu_activate($userdata) {
   
$context = array(
     
'event'  => $this->popup_event,
     
'key'    => $userdata['key'],
     
'label'  => $userdata['label']
    );
   
$this->signal->emmit('activate', $context);
  }

 
/**
   *  Signal registration with Gnope_FileExplorer_Signal class
   *
   */
 
public function connect($signal, $method, $user_data = null) {
    return
$this->signal->connect($signal, $method, $user_data);
  }

  public function
connect_simple($signal, $method, $user_data = null) {
    return
$this->connect($signal, $method, $user_data);
  }

 
/**
   * registrer to signal event in a window and then
   * call this popup method :
   *
   * ex :
   *
   *  $window->set_events($window->get_events()
   *    | Gdk::BUTTON_PRESS_MASK);
   *  $window->connect('button-press-event',
   *    array($menu, 'popup'), $user_data);
   *
   * target widget can be :
  * - GtkWindow
  * - GtkDrawingArea
  *  -maybe GtkTreeView
  *
  */
 
public function popup($window, $event, $userdata) {
   
# only popup menu if button 3 is pressed.
   
if ($event->button == 3) {
   
# register event to emit complete event information upon menu
    # selection, including (x,y) coordinates and so on ...
   
$this->popup_event = $event;
   
# show popup menu.
   
parent::popup();
    }
  }

 
/**
   * Buttons can be activated or deactivated on demand.
   *
   */
 
public function set_sensitive($item_key, $bool) {
    if (isset(
$this->menu_items[$item_key]))
     
$this->menu_items[$item_key]->set_sensitive($bool);
  }
}
?>

Updates

  • added Popup::set_parent() method to avoid simplify event and signal registration. This is done by this method.
Fichier attachéTaille
popup-menu-oo.php_.txt3.68 Ko

Extending GtkToolbar to quickly build toolbars

Grapher class exampleA very easy way to build toolbars with icons, tooltips and an easy signal connect feature.

Details

Building GtkToolbar with buttons icons and tooltips require :

  1. creating a GtkToolbar widget,
  2. creating some GtkToolbarButtons from an external icon file,
  3. buttons can use stock icons (from systeme library),
  4. insert button to toolbar,
  5. setting up GtkTooltips to each buttons ; there must be only one instance of GtkTooltips object.
  6. setting up clicked signal callback to each buttons.
<?php
# pieces of code - this code is not working standalone.

$toolbar = new GtkToolbar();  // 1

$img = GtkImage::new_from_file($icon); // 2
$button = new GtkToolButton($img, $label); // 2

$button = GtkToolButton::new_from_stock(constant($stock_image_name)); // 3
$button->set_label($label); // 3

$menubar->insert($button, -1); // 4

$tooltips = new GtkTooltips();
$button->set_tooltip($tooltips, 'tip info'); // 5

$button->connect('clicked', 'button_signal_clicked' , $userdata); // 6
?>

all this tasks can be made in a quick way with Toolbar class. Look at example below; snapshot shows you the execution result.

Example usage

  1. this is a standard menubar in english,
  2. you can control attributes of toolbar button (label, tip, icon)
  3. an other toolbar description in french
  4. note defaults values
  5. french labels (external part visible for user) and english internals (coder)
  6. standard php-gtk windowing construction
  7. create a first toolbar with first description array, and set some toolbar attributes just as if Toolbar was a Gtktoolbar
  8. connect a signal callback for clicked signal,
  9. a second tooolbar localised in French,
  10. the signal callback ; have a look a function signature.

and that's all

<?php
#include class code here

// note 1
$specs1 = array(
'New',
'Open',
'Close',
'Quit',
 
'---',
 
'Group' => array(               // note 2
   
'label' => 'Group object',
   
'tip'   => 'Group object ...',
   
'icon'  => 'Group.png'
 
)
);

# a french Toolbar ;
# note : this is not the rigth way to localise a php-gtk2 application
# // note 3
$specs2 = array(
'Nouveau'  => array(
 
'label'   => 'Nouveau',
 
'icon'  => 'New',
 
'tip'  => 'Nouveau ...'
 
),
'Ouvrir'  => array(
 
'icon'  => 'Open'
 
# 'label' defaults to key (Ouvrir) // note 4
  # 'tips' defaults to key (Ouvrir)
 
),
'Fermer'  => array(
 
'icon'  => 'Close'                // note 5
 
),
'Quit'  => array(
 
'label' => 'Quitter',
 
'tip'   => 'Quitter ...'
 
)
);

// note 6
$window = new GtkWindow();
$window->connect_simple('destroy', array('gtk', 'main_quit'));
$window->set_title('Toolbar demo');
$window->set_position(Gtk::WIN_POS_CENTER);

 
$vbox = new GtkVbox();
 
$window->add($vbox);

   
// note 7
   
$toolbar = new Toolbar($specs1);
   
# # Gtk::TOOLBAR_ICONS, Gtk::TOOLBAR_TEXT,   Gtk::TOOLBAR_BOTH, Gtk::TOOLBAR_BOTH_HORIZ
   
$toolbar->set_toolbar_style(Gtk::TOOLBAR_BOTH);
   
$toolbar->set_size_request(300, -1);

  
// note 8
  
$toolbar->connect('clicked', 'toolbar_button_clicked', 'foobar');

   
$vbox->pack_start(new GtkLabel('toolbar1 - style=Gtk::TOOLBAR_BOTH'));
   
$vbox->pack_start($toolbar);

  
// note 9
   
$toolbar = new Toolbar($specs2);
   
$toolbar->set_toolbar_style(Gtk::TOOLBAR_ICONS);
   
$toolbar->set_size_request(300, -1);
   
$toolbar->connect('clicked', 'toolbar_button_clicked', 'foobar');

   
$vbox->pack_start(new GtkLabel('toolbar2 - a french toolbar with icons only (and tooltips)'));
   
$vbox->pack_start($toolbar);

$window->show_all();

Gtk::main();

// note 10
function toolbar_button_clicked($toolbar, $button_id, $userdata=null){
  echo
"button = $button_id ($userdata)\n";
  if(
$button_id == 'Quit')
   
Gtk::main_quit();
}
?>

Source (Tooltips)

This class is from Callicore project with a nice php-gtk2 framework in construction. You can't use multiple instance of GtkTooltips ; if you do, tooltips seems not working. So the choice here is to build a Singleton class using standard GtkTooltip.

<?php
# import here class Tooltips from Calicore project :
# http://websvn.bluga.net/wsvn/Callicore/desktop/trunk/lib/?rev=0&sc=0
# (file tooltips.class.php)
#

class CC_Tooltips extends GtkTooltips
{
  
# please complete ...
}
?>

Source Toolbar

here is full source code for Toolbar class with instructions in comments.

<?php
/**
* Toolbar class extended from GtkToolbar
*
*  - builds buttons from a description array
*  - description array can take attributes : (label, tip, icon)
*  - array keys are used by programmer for switching in signal callbacks,
*  - tooltips are build
*  - you can registrer to "clicked" signal just as if it was toolbar that was clicked.
*
* public methods :
*  - __construct($specs) : builds a Toolbar with specs array.
*  - connect($signal, $func, $userdata),
*  - connect_simple($signal, $func, $userdata) : overrides GtkToolbar signal registration
*    handles "clicked" signal internaly
*
* protected methods :
* signals :
*
* - "clicked" : this signal is emited (relayed) from toolbar buttons.
*  callback signature : void function function ($toolbar, $button_id, $userdata=null)
*
* specs array format :
*
* $spec = array('File', 'Open', 'Quit'); # simple format
*
* $spec = array(
*  'File' => array(
*    'label'  => 'your label'
*    'tip'    => 'your tip info'
*    'icon'   => Gtk::STOCK_* icon or a image file
*  );
*
* if icon is not a stock icon and not a regular file, it defaults to Gtk::STOCK_MISSING_IMAGE
*
*
*/

class Toolbar extends GtkToolbar{

  protected
$specs;
  protected
$buttons;
  protected
$signal;

  function
__construct($specs){
   
parent::__construct();
   
$this->specs = $specs;
   
$this->buttons = array();

   
$this->buildUI($specs);
  }

 
/**
  * construct toolbar managed objects (GtkToolButton)
  *
  */
 
protected function buildUI($specs){

    foreach(
$specs as $_key=>$button){


      if(
is_array($button)){
       
$record = $button;

       
# setup default values
       
$key = $_key;
       
$label = $_key;
       
$icon = $_key;
       
$tip = $_key;

        if(isset(
$record['label']))   $label = $record['label'];
        if(isset(
$record['tip']))     $tip   = $record['tip'];
        if(isset(
$record['icon']))    $icon  = $record['icon'];

       
$button = $this->buttons[$key] = $this->create_button($label, $icon, $tip);
       
$this->insert($button, -1);

      } else {
       
# separator '---'
       
if(preg_match('/^--.+/', $button)){
         
$button = new GtkSeparatortoolitem();
         
$this->insert($button, -1);
        } else{
         
# setup default values
         
$key = $button;
         
$label = $button;
         
$icon = $button;
         
$tip = $label;

         
$button = $this->buttons[$key] = $this->create_button($label, $icon, $tip);
         
$this->insert($button, -1);
        }
      }
    }
  }

 
/**
  * Create toolbar buttons with rigth parameters (label, icon, tip)
  * and register tooltip feature.
  *
  */
 
protected function create_button($label, $icon, $tip, $use_stock_image=true){
   
$tooltips = CC_Tooltips::instance();
   
# this does not work ; need to be a global variable or a singleton instance.
    # $tooltips = new GtkTooltips();

   
if(file_exists($icon)){
     
$img = GtkImage::new_from_file($icon);
     
$button = new GtkToolButton($img, $label);
     
$button->set_tooltip($tooltips, $tip);
    } else {
     
$stock_image_name = 'Gtk::STOCK_'.strtoupper($icon);
      if(!
defined($stock_image_name))
       
$stock_image_name = 'Gtk::STOCK_MISSING_IMAGE';
 
     
$button = GtkToolButton::new_from_stock(constant($stock_image_name));
     
$button->set_label($label);
     
$button->set_tooltip($tooltips, $tip);
    }
    return
$button;
  }

 
/**
  * public function connect($signal, $func, $userdata=null)
  *
  * if signal is "clicked", need to setup a callback with this signature
  *
  *    void function callback($toolbar, $button_id, $userdata=null)
  *
  *  else give signal registration to parent (GtkToolbar class)
  */
 
public function connect($signal, $func, $userdata=null){

   
# build a "relay" for signal callback ;
    # the goal is to call a callback with user-defined context
   
if($signal == 'clicked'){
     
$_userdata = array(
       
'userdata' => &$userdata,
       
'func'     => &$func
     
);
      foreach(
$this->buttons as $key=>$button){
       
$_userdata['button'] = $key;
       
$button->connect($signal, array($this, 'button_signal_clicked') , $_userdata);
      }
    }
    else
     
parent::connect($signal, $func, $userdata);
  }

 
/**
  * see connect method below.
  *
  */
 
public function connect_simple($signal, $func, $userdata=null){
    if(
$signal == 'clicked'){
     
$_userdata = array(
       
'userdata' => &$userdata,
       
'func'     => &$func
     
);
      foreach(
$this->buttons as $button){
       
$_userdata['button'] = $key;
       
$button->connect_simple($signal, array($this, 'button_signal_clicked'), $_userdata);
      }
    }
    else
     
parent::connect_simple($signal, $func, $userdata);
  }

 
/**
  * call user callback with signature :
  *  void function callback($toolbar, $button_id, $userdata=null)
  *
  * note : this method should be protected but can be called if protected outside of this class.
  *
  */
 
public function button_signal_clicked($toolbar, $userdata){
   
# call user callback with right parametrers
   
$userdata['func']($toolbar, $button_id=$userdata['button'], $userdata['userdata']);
  }
}
?>

Todo

If you need to manage a toolbar and a menu, changing active states for buttons in thoses 2 composants, the best way to acheive that task is in using GtkAction. A tutorial about this will be writen soon.

Links

Fichier attachéTaille
Toolbar.php_.txt6.77 Ko

Fast starting with Glade 2

When you are drawing User interfaces with a nice drawing tool like glade, the main difficulty is to connect your script (callbacks) to Gtk objects with signals. Connection is achieved throught object names:

  • you need to name properly each objects in glade,
  • after that, you can reference that name in your php script.

Here is a simple auto-signal connecting system based on the names of methods. See App class example below.

Note: this article is for PHP-GTK 2, but has an equivalent for PHP-GTK 1. See Fast Starting with Glade 1

You must name methods like this :

<?php
 
# 3 possibilities
 
function on_object_signal(){}
  function
on_object_name_signal(){}

 
# the last but the best one :
 
function on_object_name__complex_signal_name()
 
# complex_signal_name is converted to
  # complex-signal-name for GTK toolkit
?>

Here is the complete code ; you should separate the 2 classes.

<?php
error_reporting
(E_ALL);

# library
class GladeApp {
  function
load_glade($file) {
   
$this->wnd = &new GladeXML($file);
   
$list_of_methods=get_class_methods($this);
    for (
$i=0 ; $i < sizeof($list_of_methods) ; $i++) {
      if (
strstr($list_of_methods[$i], "on_")) {
        if (
preg_match('/on_(.+?)__(.+)$/'
         
$list_of_methods[$i], $values))
          {
         
$widget = $this->get_widget($values[1]);
         
$signal = $values[2];
         
$signal = str_replace('_', '-', $signal);
         
$widget->connect_simple($signal,
            array(
$this, $list_of_methods[$i]));
        } elseif (
preg_match('/on_(.+?_.+)_(.+)$/',
         
$list_of_methods[$i], $values))
          {
         
$widget = $this->get_widget($values[1]);
         
$widget->connect_simple($values[2],
            array(
$this, $list_of_methods[$i]));
        } elseif (
preg_match('/on_(.+?)_(.+?)$/',
         
$list_of_methods[$i], $values))
          {
         
$widget = $this->get_widget($values[1]);
         
$widget->connect_simple($values[2],
           array(
$this, $list_of_methods[$i]));
        }
      }
    }
  }

  function
get_widget($widget) {
    return
$this->wnd->get_widget($widget);
  }
}

# your class application
class App extends GladeApp {
  function
quit() {
   
gtk::main_quit();
  }

  function
on_button_quit_clicked() {
    echo
"application terminating ...\n";
   
$this->quit();
  }

  function
on_button_print_clicked() {
   
$entry = $this->get_widget('entry');
   
$txt = $entry->get_text();
    if (
$txt != '')
      echo
"$txt\n";
    else {
     
$label2 = $this->get_widget('label2');
     
$label2->set_text('please type some text before');
      }
    }

  function
on_button_delete_clicked() {
   
$entry = $this->get_widget('entry');
   
$entry->set_text('');
  }

  function
on_entry__key_press_event(){
    echo
"entry : key pressed\n";
  }
}

$a = new App();
$a->load_glade("test.glade");
gtk::main();
?>

The contents of the test.glade Glade file in XML format working with the App class are as follows.

<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">

<glade-interface>

<widget class="GtkWindow" id="window">
  <property name="visible">True</property>
  <property name="title" translatable="yes">window1</property>
  <property name="type">GTK_WINDOW_TOPLEVEL</property>
  <property name="window_position">GTK_WIN_POS_NONE</property>
  <property name="modal">False</property>
  <property name="resizable">True</property>
  <property name="destroy_with_parent">False</property>
  <property name="decorated">True</property>
  <property name="skip_taskbar_hint">False</property>
  <property name="skip_pager_hint">False</property>
  <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
  <property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
  <property name="focus_on_map">True</property>

  <child>
    <widget class="GtkFixed" id="fixed1">
      <property name="visible">True</property>

      <child>
<widget class="GtkEntry" id="entry">
  <property name="width_request">216</property>
  <property name="height_request">32</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="editable">True</property>
  <property name="visibility">True</property>
  <property name="max_length">0</property>
  <property name="text" translatable="yes"></property>
  <property name="has_frame">True</property>
  <property name="invisible_char">*</property>
  <property name="activates_default">False</property>
</widget>
<packing>
  <property name="x">152</property>
  <property name="y">112</property>
</packing>
      </child>

      <child>
<widget class="GtkButton" id="button_print">
  <property name="width_request">58</property>
  <property name="height_request">27</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="label" translatable="yes">Print</property>
  <property name="use_underline">True</property>
  <property name="relief">GTK_RELIEF_NORMAL</property>
  <property name="focus_on_click">True</property>
</widget>
<packing>
  <property name="x">40</property>
  <property name="y">96</property>
</packing>
      </child>

      <child>
<widget class="GtkLabel" id="label1">
  <property name="width_request">224</property>
  <property name="height_request">40</property>
  <property name="visible">True</property>
  <property name="label" translatable="yes">write some text here
and press a button
</property>
  <property name="use_underline">False</property>
  <property name="use_markup">False</property>
  <property name="justify">GTK_JUSTIFY_LEFT</property>
  <property name="wrap">False</property>
  <property name="selectable">False</property>
  <property name="xalign">0.5</property>
  <property name="yalign">0.5</property>
  <property name="xpad">0</property>
  <property name="ypad">0</property>
  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
  <property name="width_chars">-1</property>
  <property name="single_line_mode">False</property>
  <property name="angle">0</property>
</widget>
<packing>
  <property name="x">152</property>
  <property name="y">64</property>
</packing>
      </child>

      <child>
<widget class="GtkButton" id="button_delete">
  <property name="width_request">58</property>
  <property name="height_request">27</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="label" translatable="yes">Delete</property>
  <property name="use_underline">True</property>
  <property name="relief">GTK_RELIEF_NORMAL</property>
  <property name="focus_on_click">True</property>
</widget>
<packing>
  <property name="x">40</property>
  <property name="y">128</property>
</packing>
      </child>

      <child>
<widget class="GtkButton" id="button_quit">
  <property name="width_request">58</property>
  <property name="height_request">27</property>
  <property name="visible">True</property>
  <property name="can_focus">True</property>
  <property name="label" translatable="yes">Quit</property>
  <property name="use_underline">True</property>
  <property name="relief">GTK_RELIEF_NORMAL</property>
  <property name="focus_on_click">True</property>
</widget>
<packing>
  <property name="x">40</property>
  <property name="y">200</property>
</packing>
      </child>

      <child>
<widget class="GtkLabel" id="label2">
  <property name="width_request">216</property>
  <property name="height_request">136</property>
  <property name="visible">True</property>
  <property name="label" translatable="yes">label2</property>
  <property name="use_underline">False</property>
  <property name="use_markup">False</property>
  <property name="justify">GTK_JUSTIFY_LEFT</property>
  <property name="wrap">False</property>
  <property name="selectable">False</property>
  <property name="xalign">0.5</property>
  <property name="yalign">0.5</property>
  <property name="xpad">0</property>
  <property name="ypad">0</property>
  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
  <property name="width_chars">-1</property>
  <property name="single_line_mode">False</property>
  <property name="angle">0</property>
</widget>
<packing>
  <property name="x">152</property>
  <property name="y">160</property>
</packing>
      </child>
    </widget>
  </child>
</widget>

</glade-interface>

Getting a descendant (child) widget by name

This script gets a descedant (child) widget by name using recursion. A name should be defined for the child previously, if not as a name is considered the class name.

The name is passed as a variable by reference, when the target is found it is stored in this variable.
This will change the variable type from string to object, this very information is used as a condition to stop further recursion.

Fichier attachéTaille
gtk-php-get-child-widget-by-name.php_.txt2.43 Ko

GtkClock : a clockwork GtkLabel

This small example show how to extend a widget to add a dynamic behaviour, in this case an enhanced GtkLabel displaying the current time without program intervention.

<?php
# Marc Quinton - September 2006.
#
# a Gtk2 widget extension example
#

class PhpGtkClockLabel extends GtkLabel {
  protected
$label;
  protected
$count;
  protected
$timeout;
  protected
$time_format;

  function
__construct() {
   
parent::__construct();
   
$this->timeout = 1000;              # 1000ms is 1 second
   
$this->time_format = '%X';          # %X - preferred time format

    # update clock label as soon as possible.
   
$this->timeout();
  }

  function
timeout(){
   
$this->display_time();

   
# update clock label next delay
   
Gtk::timeout_add($this->timeout, array($this, 'timeout'));
  }

  function
display_time(){
   
$now = strftime($this->time_format);
   
$this->set_text($now);
  }
}

$win = new GtkWindow();
$win->set_title('clock widget test');
$win->connect_simple('destroy', array('gtk', 'main_quit'));
$win->set_position(Gtk::WIN_POS_CENTER);
$win->set_border_width(10);

$clock = new PhpGtkClockLabel();

$win->add($clock);

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

Inspecting class methods

This examples shows how to discover on your own what the methods for a given widget are, from the actual current implementation, instead of relying on documentation. You can obviously use it to examine other classes, but this sample code shows the methods of classes Gtk and GtkWindow.

In addition, this example shows how to line up vertically by the top the contents of panes in a GtkHBox, just like vertical-align does in CSS. In this example, we use it to align the two lists because they are of very different lengths.

<?php
/**
* Author: Martin Fasani [ www.movil.be ]
* 2006-11-06 Revisited by FG Marand
*   to demo vertical alignment too
*
* The example demonstrates this type of layout.
* +----------------------+
* | Hello ...            |
* +----------------------+
* | text11        text21 |
* | text12        text22 |
* |               text23 |
* |               text24 |
* +----------------------+
*
*/
if (!class_exists('gtk')) {
  die(
"Please load the php-gtk2 module in your php.ini\r\n");
}

$gtk = new Gtk();
$wnd = new GtkWindow();
$wnd->set_title('Hello world');
$wnd->set_size_request(480, 600 );

$wnd->connect_simple('destroy', array('gtk', 'main_quit'));

// Main container
$vb = new GtkVBox();

// Upper pane
$out = "HELLO PHP-GTK2. This is version ".$gtk->get_version();
$lblHello = new GtkLabel($out);
$vb->add($lblHello);

// Scrollable lower pane
$sw = new GtkScrolledWindow(); // Create a scrolled window
$hb = new GtkHBox();           // Create a HBox
$sw->add_with_viewport($hb);   // Fit the HBox into the SW
$vb->add($sw);                 // And add the SW to the VBox

/**
* Now prepare the two subpanes in the lower pane
*/

// Gtk methods go into the leftmost subpane
$gtkmain_methods = get_class_methods(get_class($gtk));
$out = '';
foreach (
$gtkmain_methods as $name => $value) {
 
$out .= "$name : $value\n";
}
$lblGtk = new GtkLabel($out);
$lblGtk->set_alignment(0,0);
$frameGtk = new GtkFrame('Gtk methods');
$frameGtk->add($lblGtk);
$hb->add($frameGtk);

// GtkWindow methods go into the rightmost subpane
$gtkwin_methods = get_class_methods(get_class($wnd) );
$out = '';
foreach (
$gtkwin_methods as $name => $value) {
 
$out.= "$name : $value\n";
}
$lblGtkWindow = new GtkLabel($out);
$frameGtkWindow = new GtkFrame('GtkWindow methods');
$frameGtkWindow->add($lblGtkWindow);
$hb->add($frameGtkWindow);

// Now pack the widgets to optimize geometry
$vb->set_child_packing($lblHello, FALSE, FALSE, FALSE,
 
Gtk::PACK_START);

// Insert to VBox into the main window
$wnd->add($vb);

// We're good to go
$wnd->show_all();
Gtk::main();
?>

Trick question

When inserting the GtkHBox in the GtkScrolledWindow, we used add_with_viewport() instead of just add.

Now why not just add() ? What happens if we replace add_with_viewport() by it ? And why ? Hint

Going further

For deeper inspection of the PHP-GTK 2 widgets, the Dev_Inspector by Christian Weiske is your next stop. It uses the PHP5 Reflection API.

Managing Multiple Windows

Intro

This code snippet shows one of the many ways to handle multiple windows in PHP-GTK 2. In the following example will build 3 classes (Application, GtkWindow_One and GtkWindow_Two).

Class : Application

This class will implement 3 static methods for the managment of GtkWindow classes. A more complete version of this class is available at php-gtk.pastebin.com

<?php
/**
* Class for handling multiple GtkWindow objects
* @author Leon Pegg <leon.pegg@gmail.com>
*/
class Application {
 
/**
   * GtkWindow object array
   *   Structure:
   *      [int window_id]
   *          array(
   *            GtkWindow_Name - Store GtkWindow object
   *          )
   *
   * @var array GtkWindow
   */
 
protected static $GtkWindows = array();
    private function
__construct() {
    }

 
/**
   * Adds a GtkWindow object to Application::$GtkWindows
   *
   * @param GtkWindow $GtkWindow
   * @return bool
   */
 
public static function add_window(GtkWindow $GtkWindow) {
   
$GtkWindow_Name = $GtkWindow->get_name();
    if (
$GtkWindow_Name !== '' && !array_key_exists($GtkWindow_Name,
     
self::$GtkWindows)) {
     
self::$GtkWindows[$GtkWindow_Name] = $GtkWindow;
      return
true;
    }
    return
false;
  }

 
/**
   * Retrieves GtkWindow object from Application::$GtkWindows
   *
   * @param string $GtkWindow_Name
   * @return GtkWindow
   */
 
public static function window($GtkWindow_Name) {
    if (
$GtkWindow_Name !== '' && array_key_exists($GtkWindow_Name,
     
self::$GtkWindows)) {
      return
self::$GtkWindows[$GtkWindow_Name];
    }
  }

/**
   * Retrieves Application::$GtkWindows infomation array
   *   Structure:
   *      [int server_id]
   *          array(
   *            name   - Name of GtkWindow
   *            class  - Name of GtkWindow class
   *          )
   *
   * @return array
   */
 
public static function list_windows() {
   
$Window_List = array();
    foreach (
self::$GtkWindows as $GtkWindow_Name => $Class) {
     
$Class_Name = get_class($Class);
     
$Window_List[] = array(
       
'name' => $GtkWindow_Name,
       
'class' => $Class_Name);
    }
    return
$Window_List;
  }
}
?>

Method Application::add_window

This method is used to add instances of GtkWindow or derived from GtkWindow to the Application.

  • Sucsesful : returns true
  • Failed : returns false

This method will fail in two cases:

  1. The GtkWindow name has not been set
  2. The GtkWindow name is already defined in the Application::$GtkWindows array.

Method Application::window

This method is used to retrieve instances of GtkWindow or derived from it.

  • Successful : returns GtkWindow instance
  • Failed : returns false

This method will fail in one case:

  1. The GtkWindow name does not exist in the Application::$GtkWindows array.

Method Application::list_windows

This method is used to retrieve an array of infomation about the current GtkWindow or objects derived from GtkWindow objects in the Application::$GtkWindows. Information is returned in the following format:

array(
  array(
    'name' => [GtkWindow name],
    'class' => [Class name]
  )
)

Class : GtkWindow_One

... about the class ...

<?php
class GtkWindow_One extends GtkWindow {
  protected
$widgets = array();

  public function
__construct() {
   
parent::__construct();
   
parent::set_name('wnd_one');
   
$this->build_ui();
   
Application::add_window($this);
  }

  protected function
build_ui() {
   
$btn_show = new GtkButton("Show window two");
   
$btn_show->set_name('btn_show');
   
parent::add($btn_show);
   
$this->widgets['btn_show'] = $btn_show;
       
   
$btn_show->connect_simple('clicked',
      array(
$this, 'clicked_btn_show'));
   
parent::connect_simple('destroy',
      array(
'Gtk', 'main_quit'));
  }
   
  public function
widget($widget_name) {
    if (
$widget_name !== '' && array_key_exists($widget_name,
     
$this->widgets)) {
      return
$this->widgets[$widget_name];
    }
    return
false;
  }
   
  public function
clicked_btn_show() {
    if (
Application::window('wnd_two') !== false) {
     
Application::window('wnd_two')->show_all();
    }
  }
}
?>

Class : GtkWindow_Two

... about the class ...

<?php
class GtkWindow_Two extends GtkWindow {
  protected
$widgets = array();

  public function
__construct() {
   
parent::__construct();
   
parent::set_name('wnd_two');
   
$this->build_ui();
   
Application::add_window($this);
  }

  protected function
build_ui() {
   
$btn_hide = new GtkButton("Hide window");
   
$btn_hide->set_name('btn_hide');
   
parent::add($btn_hide);
   
$this->widgets['btn_hide'] = $btn_hide;
       
   
$btn_hide->connect_simple('clicked',
      array(
$this, 'clicked_btn_hide'));
   
parent::connect_simple('delete-event',
      array(
$this, 'delete_event'));
  }
   
  public function
widget($widget_name) {
    if (
$widget_name !== '' && array_key_exists($widget_name,
     
$this->widgets)) {
      return
$this->widgets[$widget_name];
    }
    return
false;
  }
   
  public function
clicked_btn_hide() {
   
parent::hide_all();
  }
   
  public function
delete_event() {
   
parent::hide_all();
    return
true;
  }
}
?>

Putting it all together

<?php
/**
* A class to handle multiple GtkWindow objects
* @author Leon Pegg <leon.pegg@gmail.com>
*/
class Application {
 
/**
   * GtkWindow object array
   *   Structure:
   *      [int window_id]
   *          array(
   *            GtkWindow_Name - Store GtkWindow object
   *          )
   *
   * @var array GtkWindow
   */
 
protected static $GtkWindows = array();
    private function
__construct() {
  }

 
/**
   * Adds a GtkWindow object to Application::$GtkWindows
   *
   * @param GtkWindow $GtkWindow
   * @return bool
   */
 
public static function add_window(GtkWindow $GtkWindow) {
   
$GtkWindow_Name = $GtkWindow->get_name();
    if (
$GtkWindow_Name !== ''
     
&& !array_key_exists($GtkWindow_Name, self::$GtkWindows)) {
     
self::$GtkWindows[$GtkWindow_Name] = $GtkWindow;
      return
true;
    }
    return
false;
  }

 
/**
   * Retrieves GtkWindow object from Application::$GtkWindows
   *
   * @param string $GtkWindow_Name
   * @return GtkWindow
   */
 
public static function window($GtkWindow_Name) {
    if (
$GtkWindow_Name !== '' &&
     
array_key_exists($GtkWindow_Name, self::$GtkWindows)) {
      return
self::$GtkWindows[$GtkWindow_Name];
    }
    return
false;
  }

 
/**
   * Retrieves Application::$GtkWindows infomation array
   *   Structure:
   *      [int server_id]
   *          array(
   *            name   - Name of GtkWindow
   *            class  - Name of GtkWindow class
   *          )
   *
   * @return array
   */
 
public static function list_windows() {
   
$Window_List = array();
    foreach (
self::$GtkWindows as $GtkWindow_Name => $Class) {
     
$Class_Name = get_class($Class);
     
$Window_List[] = array(
        
'name' => $GtkWindow_Name,
        
'class' => $Class_Name);
    }
  return
$Window_List;
  }
}

class
GtkWindow_One extends GtkWindow {
  protected
$widgets = array();

  public function
__construct() {
   
parent::__construct();
   
parent::set_name('wnd_one');
   
$this->build_ui();
   
Application::add_window($this);
  }

  protected function
build_ui() {
   
$btn_show = new GtkButton("Show window two");
   
$btn_show->set_name('btn_show');
   
parent::add($btn_show);
   
$this->widgets['btn_show'] = $btn_show;
       
   
$btn_show->connect_simple('clicked',
      array(
$this, 'clicked_btn_show'));
   
parent::connect_simple('destroy',
      array(
'Gtk', 'main_quit'));
  }
   
  public function
widget($widget_name) {
    if (
$widget_name !== ''
     
&& array_key_exists($widget_name, $this->widgets)) {
      return
$this->widgets[$widget_name];
    }
    return
false;
  }
   
  public function
clicked_btn_show() {
    if (
Application::window('wnd_two') !== false) {
     
Application::window('wnd_two')->show_all();
    }
  }
}

class
GtkWindow_Two extends GtkWindow {
  protected
$widgets = array();

  public function
__construct() {
   
parent::__construct();
   
parent::set_name('wnd_two');
   
$this->build_ui();
   
Application::add_window($this);
  }

  protected function
build_ui() {
   
$btn_hide = new GtkButton("Hide window");
   
$btn_hide->set_name('btn_hide');
   
parent::add($btn_hide);
   
$this->widgets['btn_hide'] = $btn_hide;
       
   
$btn_hide->connect_simple('clicked',
      array(
$this, 'clicked_btn_hide'));
   
parent::connect_simple('delete-event',
      array(
$this, 'delete_event'));
  }
   
  public function
widget($widget_name) {
    if (
$widget_name !== ''
     
&& array_key_exists($widget_name, $this->widgets)) {
      return
$this->widgets[$widget_name];
    }
    return
false;
  }
   
  public function
clicked_btn_hide() {
   
parent::hide_all();
  }
   
  public function
delete_event() {
   
parent::hide_all();
    return
true;
  }
}

new
GtkWindow_One();
new
GtkWindow_Two();

Application::window('wnd_one')->show_all();
Gtk::main();
?>

Merry Christmas with php-gtk and object oriented software (tutorial).

drawing blinking stars with php-gtk It's time to have some fun for the holiday season. We are going to draw some stars in a Drawing Area. Stars will have random colors, and random blinking cycle. Will will try to write some object-oriented code to show good practices with php-gtk.

For basic introduction to object syntax please refer to official php site.

First, create a basic class with some properties :

  • this class will handle graphical objects with geometrical positions ($x, $y) and size ($w, $h) as width and height
  • we can't instanciate this class because it is useless ; so create it as abstract ; if you try $obj=new Object, php will complaint about this. (Fatal error: Cannot instantiate abstract class Object)
  • this is a graphical object, so we need to draw it, so create a draw() method as abstract because we can't draw anything now.
<?php
abstract class Object {
    protected
$x, $y, $w, $h;
    abstract public function
draw($drawing);
}
?>

Object Oriented design hints

when you create some classes :

  • allways create attributes as protected if you have no good reasons to make them private or public,
  • general usage methods will always be with public attribute visibility
  • some methods will exist for internal usage, there are protected methods, avaid private methods
  • unspecified methods will have public visibility
  • unspecified attributes will have public visibility to.

Let's go on

Now create a basic star with this piece of code : BasicStar class

  1. we want to draw an object, so extends our Object class
  2. for a star, we need a postion (Object properties), a color, and a radius for differents star sizes, so create all this atributes as protected members
  3. give some random position to each stars,
  4. but we need to know "screen" size, so give width and heigth size in BasicStar constructor
  5. each stars will have a random size
  6. a star knows how to draw itself in a drawing area (this is a drawable in php-gtk) ; a drawable can be a Window or a Buffer (pixmap). In this case, GtkDrawable has all needed primitives to make some drawings (lines, rectangles, circles ...). Here, we will use a GtkDrawingArea extention class to help drawing hiding some Gtk stuff (graphic context, colormap, event mask, pixmap, draw queue).
<?php
class BasicStar extends Object{     // note 1
   
protected $color;    // note 2
   
protected $radius// note 2

   
function __construct($w, $h){  // note 4
       
$this->color = 'yellow';
       
$this->x = rand(0, $w);   // note 3
       
$this->y = rand(0, $h);   // note 3
       
$this->radius = rand(1, 3);    // note 5
   
}

    function
draw($da){   // note 6
       
$da->draw_circle($this->color, $filled=true, $this->x-$this->radius, $this->y-$this->radius, $this->radius*2);
    }
}
?>

A Christmas Widget

Let's create a graphical widget than can handle :

  • drawing primitives from a drawing area
  • our BasicStar class

here are some details :

  1. ChristmasWidget inherits from a basic graphical widget containg some drawing facilities not shown here.
  2. $objects is an array where we will store our objects ; those objects must be drawables (should inherit from Object)
  3. in object model, we allway construct ourself with this method
  4. this widget will have some dynamic response, so create a timeout that will be called periodicaly
  5. at window creation or resize, this method is called ; now we are nearly ready for drawing ;
  6. empty current list of object ( or create a new one),
  7. parent widget hase some drawing capabilities, so let it do what is needed,
  8. we need to know our size ($w, $h) to display in full window,
  9. create a bunch of Star objects,
  10. number of star is proportional to window size,
  11. this timeout function is called every 350 ms and will allow some dynamics display (see note 4)
  12. this is a full redraw method
  13. a realized widget is a widget that is constructed and have a (system) window ; without window, we can't draw
  14. take our object list and,
  15. call foreach object draw() method ; objects can only draw on a DrawingArea ; ChristmasWidget inherits from DrawingArea, so $this is a DrawingiArea, so give it to graphical objects to render graphics.
  16. we are drawing in a Pixmap (double buffering cache); so we need to refresh Window content from Pixmap
<?php
class ChristmasWidget extends GraphCore// note 1

   
protected $objects;   // note 2

   
public function __construct(){
       
parent::__construct();   // note 3
       
$this->objects = array();  // note 2

       
Gtk::timeout_add(350, array($this, 'timeout'));  // note 4

   
}override this method and draw what you want.

    function
configure_event($widget, $event){  // note 5

       
$this->objects = array();   // note 6

       
parent::configure_event($widget, $event);  // note 6

       
$w = $this->w();  // note 7
       
$h = $this->h();

       
$count = intval(($w+$h)/10);  // note 9

       
for($i=0 ; $i<$count ; $i++)  // note 8
           
$this->objects[] = new BasicStar($w, $h);
    }

    public function
timeout(){  // note 10
       
$this->do_redraw();
        return
true;
    }

    function
do_redraw(){   // note 11

       
if(!$this->realized())  // note 12
           
return;

        foreach(
$this->objects as $obj){ // note 13
           
$obj->draw($this);
        }

       
$this->refresh();   // note 14
   
}
}
?>

ChrismasDemo window

Here every things is object, so let's create a new Window dedicated for our Christmas application demonstration. Here are all details :

  1. we want to create a Widget from a GtkWindow
  2. let Window creation,
  3. this will quit application when user close window,
  4. set window title,
  5. at window creation, set position a screen center,
  6. set window size to (600, 500) pixels
  7. add to window management an instance of our Christmas widget,
  8. show window, by default windows and their content are not displayed (mapped)
  9. create an instance of our ChrismasDemo window class
  10. and enter in event loop
<?php
class ChrismasDemo extends GtkWindow//note 1

   
function __construct(){
       
parent::__construct();  // note 2

       
$this->connect_simple('destroy', array('gtk', 'main_quit'));  // note 3
       
$this->set_title('Merry Christmas');  // note 4
       
$this->set_position(Gtk::WIN_POS_CENTER);  // note 5
       
$this->set_size_request(600, 500);  // note 6

       
$this->add (new ChristmasWidget()); // note 7

       
$this->show_all(); // note 8
   
}
}

$demo = new ChrismasDemo(); // note 9

Gtk::main(); // note 10
?>

Blinking Star

To make our stars blink, we need :

  • a sort of counter (step) ; each time we call draw() method, this counter will grow up,
  • here we will change star radius, it will be beter to change color luminence ; I don't know how to
  • we will have differents colors near red, blue and yellow,
  • each stars will have their own blinking cycle
  • $color and $radius attributes are inherited from BasicStar, so we don't need to declare them again here ; if you do that that will not make some damages just consume some memory.

Some details :

  1. we know how to draw a basic star, so inherit from this class to build our BlinkingStar,
  2. $cycle, $step have been explained below,
  3. we have a predefined list of color in #rrggbb format, select a random value from this list,
  4. introduce an extra random cycle : will exit loop for 30% calls
  5. $r is the last radius used when drawing at last call ; it will be used to clear star
  6. give a value to radius star that will change with $step value (modulo) ; avaid a 0 radius
<?php
class BlinkingStar extends BasicStar{   // note 1
   
protected $cycle;   // note 2
   
protected $step;
    protected
$colors = array(
          
'#fff589', '#f5ff89', '#fff089', '#b7b7ff',
          
'#fff589', '#f5ff89' ,'#f8debc');  // note 3

   
function __construct($w, $h){
       
parent::__construct($w, $h);
       
$this->color = $this->colors[rand(0, count($this->colors)-1)];  // note 3
       
$this->cycle = rand(20, 50);
       
$this->step = rand(0, $this->cycle);
    }

    function
draw($da){

       
# introduce some random delay
       
if(rand(0, 10) > 3// note 4
           
return;

        if(isset(
$this->r)){   // note 5
           
$r = $this->r;
           
# clear star area
           
$da->draw_circle('black', $filled=true,
                            
$this->x-$r, $this->y-$r, $r*2);  // note 5
       
}

       
# draw a star with a radius changing step by step
       
$r = $this->step % $this->radius+1// note 6
       
$da->draw_circle($this->color, $filled=true, $this->x-$r, $this->y-$r, $r*2);  // note 6
       
$this->r = $r;

       
$this->step++;
    }
}
?>

Source code

You can get complete source code in attachment at the bottom of this page.

Conclusions

With object oriented design you can see how it is very easy to create some basics objects and extends them. With php-gtk you can make some graphical application : games, animation, simulations, map drawing, vector drawing. Object oriented design is really interesting with graphical features.

In this tutorial, we have created some graphicals objects with dedicated classes ; theses objects are managed with specialized widget ChristmasWidget and for completeness we have created our ChristmasWindow widget. Here is some details :

  • ChristmasWidget don't know how to draw a star, this widget task is only to manage a collection of graphical objects and call draw() method,
  • graphical objects don't know any thing about application, they only draw them self to a drawable object,

Todo

  • draw a nice a big moon that will move on the sky (look at php script example below), moon class demo
  • change color luminence and chrominance.

Links

Fichier attachéTaille
Star tutorial source code5.35 Ko
Star tutorial with moon class5.58 Ko

PhpGtkDirectoryTree: displaying a directory tree

Displaying a directory tree with php-gtk is not really simple. But with a little help, you will be able to create a new Widget Tree based on GtkTreeView. You can create new widgets with php-gtk in a few lines of code. This task is far more involved with pure C/Gtk code.

Introduction

PhpGtkDirectoryTree example This class is named PhpGtkDirectoryTree. It is a composite widget inherited from GtkComposite widget tree. Because it is a widget, you can add it directly to a GtkWindow object.

A more complete version is available in Gnope repository ; this class is named : FileExplorer

Basic class usage

Here is a quick example displaying the directory tree for a UNIX directory. Windows users, please adapt $dir value.

<?php
# avoid bare errors - reports warning for stupid errors
error_reporting(E_ALL);

# our nice Tree widget class
require_once('PhpGtkDirectoryTree.class.php');

# main part - create a new Tree object and fill it with a directory tree
$tree = new PhpGtkDirectoryTree();
$dir = '/usr/share/doc';
$tree->open_directory($dir);
$tree->set_title($dir);

$window = new GtkWindow();
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->set_size_request(300, 600 );      # set window size
$window->set_position(Gtk::WIN_POS_CENTER); # place window to screen center
$window->set_title("TreeView directory display example");
$window->add($tree);

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

Todo

  • read directory tree as needed, and not all at once upon creation ; opening a large directory tree when widget is opened (realized) may block Gtk mainloop and make some troubles in display and other features managed by mail loop : socket reading, timeout. So we really need to find a nicer way to open and read directories.
  • add event callbacks for selections and folder open-close : work in progress here and here again :-)
  • a complet class framework for directory browsing is available here : http://www.php-gtk.eu/apps/gnope_file_explorer

Class Code

<?php
error_reporting
(E_ALL);

# authors :
#
# * class : Marc Quinton - november 2006.
# * main php-gtk2 tricks from kksou :  http://www.kksou.com/php-gtk2/
#
# require php-gtk2
#

class PhpGtkDirectoryTree extends GtkVbox {
  protected
$model;
  protected
$scrolled_win;
  protected
$treeview;
  protected
$column;

  protected
$icon_folder_open;
  protected
$icon_folder_closed;

  protected
$title;
  protected
$trace;

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

   
$this->icon_folder_open = 'folder_open.gif';
   
$this->icon_folder_closed = 'folder_closed.gif';

   
$this->trace = false;
   
$this->build();
  }

  function
build() {
   
$this->model = new GtkTreeStore(Gtk::TYPE_STRING);

   
$this->scrolled_win = new GtkScrolledWindow();
   
$this->scrolled_win->set_policy(
     
Gtk::POLICY_AUTOMATIC,
     
Gtk::POLICY_AUTOMATIC);

   
$this->add($this->scrolled_win);

   
// set up treeview
   
$this->treeview = new GtkTreeView($this->model);
   
$this->scrolled_win->add($this->treeview);
   
# $this->treeview->set_size_request(400, 320);

    //set up treeview columns
   
$this->column = new GtkTreeViewColumn();

   
// for image
   
$cell_renderer = new GtkCellRendererPixbuf();
   
$this->column->pack_start($cell_renderer, false);

    if (
file_exists($this->icon_folder_open)
    &&
file_exists($this->icon_folder_open)) {
     
$cell_renderer->set_property('pixbuf-expander-open',
       
GdkPixbuf::new_from_file($this->icon_folder_open));
     
$cell_renderer->set_property('pixbuf-expander-closed',
       
GdkPixbuf::new_from_file($this->icon_folder_closed));
    } else
     
trigger_error("PhpGtkDirectoryTree : "
       
. "icon file for folder not found",
       
E_USER_WARNING);

   
// for filename
   
$cell_renderer = new GtkCellRendererText();
   
$this->column->pack_start($cell_renderer, true);
   
$this->column->set_attributes($cell_renderer, 'text', 0);

   
$this->treeview->append_column($this->column);
  }

  function
open_directory($folder) {
    if (!
is_dir($folder)) {
     
trigger_error("PhpGtkDirectoryTree : "
       
. "directory not found",
       
E_USER_WARNING);
      return
false;
    }
   
$this->model->clear();
   
$root = $folder;
   
$dir_list = array($root);
   
$nodes = array();
   
$nodes[$root] = null;

    while (
count($dir_list)>0) {
     
$dir = array_shift($dir_list);
     
$this->trace("folder = $dir\n");

     
// add the directories first
     
if ($handle = opendir($dir)) {
        while (
false !== ($file = readdir($handle))) {
          if (
$file != "." && $file != "..") {
           
$fullpath = $dir.'/'.$file;
            if (
is_dir($fullpath)) {
             
$nodes[$fullpath] = $this->model->append(
               
$nodes[$dir], array($file));
             
array_push($dir_list, $fullpath);
            }
          }
        }
       
closedir($handle);
      }

     
$num_files = 0;
     
// then add the files
     
if ($handle = opendir($dir)) {
        while (
false !== ($file = readdir($handle))) {
          if (
$file != "." && $file != "..") {
           
$fullpath = $dir.'/'.$file;
          if (!
is_dir($fullpath)) {
           
$nodes[$fullpath] = $this->model->append(
             
$nodes[$dir], array($file));
            ++
$num_files;
            }
          }
        }
       
closedir($handle);
      }

     
# FIXME : bug here when directory is empty (no files or only sub-directories)
     
if ($num_files==0)
       
$nodes[$fullpath] = $this->model->append(
         
$nodes[$dir], array(''));
    }
  }

  function
set_title($title) {
   
$this->title = $title;
   
$this->column->set_title($title);
  }

  function
trace($txt) {
    if (
$this->trace)
      echo
"$txt";
  }
}
?>

Class Internals

  • todo ...

Bugs

  • when reading a directory with only subdirectories (no files) or empty, there is a problem with modele tree ; tree is displayed with a blank entry.

Hints

  • you could get feedback for directory tree reading with trace() method. Just subclass PhpGtkDirectoryTree widget and display a feedback in a label widget, or perhaps in title part of PhpGtkDirectoryTree.

Copyright

Main of this class comes from great site from kksou. Please refer to his site for usage.

links

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

ScrollingLabel - a funny label widget with scrolling capabilities

ScrollingLabel widget demo ScrollingLabel is a dynamic label which scrolls from all sides when the parent widget is not large enough. This widget can be used when you need to build very compact interfaces and display long strings of information.

Example of use :

You just need to create a standard widget just like a GtkLabel

<?php
# ....
$text = 'The quick brown fox jumps over the lazy dog.';
$vbox = new GtkVbox();
$vbox->pack_start(new ScrollingLabel( $text ), false, false); #(1)
$vbox->show_all();
# ....
Gtk::main();
?>

Code source

See the attached sources.

Highlights

  • widget tree: GtkFixed / GtkEventBox / GtkLabel,
  • GtkEventBox is used to catch button click,
  • GtkFixed is a special composite widget that allows widget placement and moves,
  • so we move the event box and inside label just to follow the event box (its parent widget),
  • you can adjust class members as needed ; change source or override class
  • click on the event box resets the position
  • an application can handle a few such scrolling widget when not under heavy load ; take care not to use too many instances of this widget

Enhancement

  • we should track the configure event from GtkEventBox and set timeout as needed. With the code as it stands currently, a timeout function is running even when not needed
Fichier attachéTaille
scrolling-label.php_.txt3.03 Ko

Timer class : simplifying gtk::timeout_add

The Timer class simplifies the use of delays between user actions, without freezing the UI.

The standard way to uses delays with php-gtk is Gtk::timeout_add ; but it is not very compact :

  • you have to define a new function that will be called after X miliseconds,
  • it's not a blocking call ; this is a callback type code design,
  • you could use sleep() or usleep(), but in that case, the code does not reenter the main loop, so Gtk events won't be processed, meaning the user interface will be frozen

Our goal is to be able to write this :

<?php
# label is a GtkLabel
function foobar($label)
  {
 
$timer = new Timer(); #(1)
 
$delay = 2000; # ms
 
$label->set_text('hello');
 
$timer->wait($delay); #(2)
 
$label->set_text('hello World');
  return
true;
  }
?>

So here what we do is :

  1. create a new instance of the Timer class,
  2. call the wait method ;

Isn't it an easy way to processes some delays ? Here is how to achieve that task :

<?php
class Timer
 
{
  protected
$timeout;
  protected
$timeout_id;

  public function
wait($time)
    {
   
$this->timeout_id = Gtk::timeout_add($time, array($this, 'do_wait'));
   
Gtk::main();
    return
true;
    }

  public function
do_wait()
    {
   
Gtk::main_quit();
    return
false;
    }

  public function
release()
    {
   
Gtk::timeout_remove($this->timeout_id);
   
Gtk::main_quit();
    }
  }
?>

Some explanations :

  • wait() method is based on Gtk::main() loop.
  • When you enter in main loop, all Gtk events are processed.
  • This call is blocking until Gtk::main_quit() is invoked

Limitations

Take care : this class design won't work correctly if you make simultaneous calls to the wait() method. There is a demonstration code in the attachment files showing this limitation. So delays should be very short and you should not not use this class in a loop.

Example

Timer class example You can find an attachment file below. It contains a combination of multiple Timer->wait() uses. This file contains an XML description for a Glade interface so you don't need to download any extra file.

The most interesting part of this file are :

  1. TimerDemo::run() : the main loop with blocking design ; in the loop we get an integer value from a label and increment that value and then wait for a few milliseconds
  2. in the __constructor method, we just have to create an instance of the Timer class.
<?php
class TimerDemo
 
{
  protected
$button_start;
  protected
$button_stop;
  protected
$label;
  protected
$entry;

  protected
$timer;
  protected
$running;

  public function
__construct($start, $stop, $label, $entry)
    {
   
$this->button_start = $start;
   
$this->button_stop = $stop;
   
$this->label = $label;
   
$this->entry = $entry;

   
$this->timer = new Timer();  # (2)
   
$this->running = false;

   
$this->button_start->connect_simple('clicked', array($this, 'on_button_start'));
   
$this->button_stop->connect_simple('clicked', array($this, 'on_button_stop'));
    }

  public function
on_button_start()
    {
    if (
$this->running)
      return
false;
   
$this->running = true;
   
$this->run();
    }

  public function
on_button_stop()
    {
   
$this->running = false;
    }

 
# (1)
 
protected function run()
    {
    while(
$this->running)
      {
     
$this->label_increment();
     
$this->timer->wait($this->delay() * 100);
      }
    }

  protected function
label_increment()
    {
   
$val = $this->label->get_text();
   
$this->label->set_text(intval($val+1));
    }

  protected function
delay()
    {
    return
intval($this->entry->get_text());
    }
  }
?>
Fichier attachéTaille
timer.php_.txt16.05 Ko
CHANGELOG.txt41.12 Ko

TreeViewPhpModel class - an easy way to build visual tree with GtkTreeView widget

What ?

Building a list for php-gtk2 using the complete framework is really complex task. When you only need to display a data tree and collect user selection, please, use this class. It very easy to use, no need to take care of complex GtkTreeView classes.

TreeView PHP model example

How

here is a simple example using this class :

<?php
error_reporting
(E_ALL);

include_once(
'TreeViewPhpModel.php');

# describe tree here :
$user_model = array(
 
"languages" => array("php", "c", "java"),
 
"methods" => array(
   
"Object Oriented",
   
"Procedural"
 
),
 
"red",
 
"blue",
 
"green"
);

$treeview = new TreeViewPhpModel();
$treeview->set_model($user_model);
$treeview->connect('changed',
 
'treeview_selection_changed', $treeview);
$treeview->expand_all();

$window = new GtkWindow();
$window->connect_simple('destroy', array('Gtk','main_quit'));
$window->set_size_request( 250, 400 );      # set window size
$window->set_position(Gtk::WIN_POS_CENTER); # place window to screen center
$window->set_title("TreeViewPhpModel - user model in php");
$window->add($treeview);

//display it
$window->show_all();
Gtk::main();

function
treeview_selection_changed($signal, $context, $tv) {
 
# echo "selection : {$context['key']} | {$context['value']}\n";
 
$tv->set_title("your selection : " . $context['value']);
  echo
"key = {$context['key']} ; val = {$context['value']}\n";

  switch(
$context['key']) {
    case
'green' :
     
# make what you need to do here
     
break;
  }
}
?>

PhpGtkDirectoryTree example

You can build more complex models using PHP syntax.

  • use array to create sub-arrays
  • you can provide a key and a litteral value that could be translated : use syntaxe : "key" => "value"
  • you can also use special syntaxe : $model = array("key1|value1", "key2|value2")

here is a more complex model example :

$user_model = array(
  "lang|languages" => array(
    "php"=> array(
      "extensions" => array(
        "snmp",
        "gtk" => array(
          "GtkTreeview",
          "GtkList",
          "GtkEntry",
          "GtkLabel"
        ),
        "mysql",
        "sqlite|SQLiteDatabase" => array(
          "constructor|__construct",
          "fetch",
          "fetchAll",
          "next",
        ),
        "image"
      )
    ),
    "c|c language",
    "c++|C++"
  ),
  "meth|methods" => array(
    "oo|Oject Oriented",
    "procedural" => "Procedural"
  )
);

Class

here is complete class source code :

<?php
error_reporting
(E_ALL);

/**
* TreeViewPhpModel class - the purpose of this class  is
* to populate a GtkTreeModel from an php array,
* to build a flat or tree list.
*
* - this class uses a GtkTreeView as main renderer,
* - try to be GtkTreeView externally but is a GtkVbox,
*    (some methods and signals are similar).
*
* public methods    : __construct(), connect(), set_model(),
*         select_key(), select_iter(), unselect_iter(),
*         expand_all(), collapse_all(), set_title()
* protected methods : build(), add_signals(), selection_changed(),
*        find_model(), iter_childs(), model_populate()
* private methods   : user_model_to_text()
*
*/

# for this class include : http://php.classes.free.fr/php/gtk/gnope/
# and install  Gnope_FileExplorer
# other link : http://www.php-gtk.eu/apps/gnope_file_explorer
require_once('Gnope/FileExplorer/Signal.php');

<?
php
class TreeViewPhpModel extends GtkVbox {
 
# internal Gtk widgets set for GtkTreeView managment.
 
protected $user_model;
  protected
$model;
  protected
$scrolled_win;
  protected
$treeview;
  protected
$column;

 
  protected
$title;     # title in TreeRow
 
protected $signal;    # signal managed by this class

  /**
   *  TreeViewPhpModel::__construct($user_model = null)
   *
   *  - create a TreeViewPhpModel widget
   *  - you can in option pass list|tree model
   *
   */
 
function __construct($user_model = null) {
   
parent::__construct();
   
$this->user_model = $user_model;
   
$this->build();
   
$this->add_signals();  # register to GtkSignal for GtkWidgets

    # signals managment for this class.
   
$this->signal = new Gnope_FileExplorer_Signal();

    if (
$this->user_model != null)
     
$this->render($this->user_model);
  }


 
/**
   * protected function build()
   * build widget hierarchy
   *   widget tree :
   *   $this (Gnope_FileExplorer_DirectoryTree extends from GtkVbox)
   *     $this->scolled_win (GtkScrolledWindow)
   *       $this->treeview (GtkTreeView)
   */
 
protected function build() {
   
# store file, path
   
$this->model = new GtkTreeStore(Gtk::TYPE_STRING, Gtk::TYPE_STRING);

   
$this->scrolled_win = new GtkScrolledWindow();
   
$this->scrolled_win->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);

   
$this->add($this->scrolled_win);

   
// set up treeview
   
$this->treeview = new GtkTreeView($this->model);
   
$this->scrolled_win->add($this->treeview);

   
//set up treeview columns
   
$this->column = new GtkTreeViewColumn();

   
// for filename
   
$cell_renderer = new GtkCellRendererText();
   
$this->column->pack_start($cell_renderer, true);
   
$this->column->set_attributes($cell_renderer, 'text', 0);

   
$this->treeview->append_column($this->column);
  }

 
/**
   * protected function add_signals() - internal : add signals to
   * GtkTreeView when items are selected
   */
 
protected function add_signals() {
   
$this->treeview->get_selection()->connect('changed',
      array(
$this,'selection_changed'));
  }

 
/**
   * public function connect($signal, $method, $user_data=null)
   *
   * register to signal (actualy : changed).
   */
 
public function connect($signal, $method, $user_data=null) {
    return
$this->signal->connect($signal, $method, $user_data);
  }

 
/**
   * public function set_model($php_model)
   *
   * given a structured php-array, populate GtkTreeviewModel according to given data.
   *
   * example :
   *
   * $treeview = new TreeViewUserModel()
   * $model = array( "red", "blue", "yellow");
   * $treeview->set_model($model);
   *
   * -> create a flat list entry without subtree
   *
   * creating recursive tree :
   * - you just have to create sub arrays :
   *
   * $model = array(
   *  "colors" => array( "yellow", "blue"),
   *  "month"  => array("january", "february")
   *  "single-item"
   *  ),
   *
   * you can create (key, values) pairs in model :
   *     * key should be short (for programming),
   *    * value should be the literal form
   *
   *    - using "|" separator :
   *        -> $model = array("1|yellow", "2|blue", "days|The days" => array(...));
   *    - using standard php construct :
   *        -> $model = array( "1" => "yellow", "2" => "blue");
   */
 
public function set_model($user_model) {
   
$this->user_model = $user_model;
   
# $txt = $this->user_model_to_text($user_model);
    # echo "$txt\n";

   
$this->model->clear();
   
$this->model_populate($user_model, $node=null);
  }

 
/**
   * public function selection_changed (should be protected, but Signal class can't call)
   *
   * - called when user select a new item in list
   * - automaticaly called at creation because firt item is selected
   */
 
public function selection_changed() {
    list(
$tree_store, $iter_path_array) =
     
$this->treeview->get_selection()->get_selected_rows();

    if (
count($iter_path_array) == 0)
      return;

   
# single selection mode - remove array form for iter_path
   
$iter_path = $iter_path_array[0];

   
$iter = $this->model->get_iter($iter_path);
   
$key = $this->model->get_value($iter, 1);
   
$value = $this->model->get_value($iter, 0);
   
$context = array(
     
'iter'      => $iter,
     
'iter_path' => $iter_path,
     
'model'     => $this->model,
     
'treeview'  => $this->treeview,
     
'key'       => $key,
     
'value'     => $value
   
);

   
$this->signal->emmit('changed', $context);
  }

 
/**
   * public function select_key($key)
   * select (highligt) item in list containing $key (text value):
   *
   * - return false if key not found
   * - return true if key is found
   */
 
public function select_key($key) {
   
$status = $this->model_find($key, $this->model, null);
    if (
$status !== false) {
     
$this->select_iter($status);
      return
false;
    }
    return
true;
  }

 
/**
   * public function select_iter($iter)
   *
   * - highlight item in the treeview list given by iter
   * - $iter should be a GtkTreeIter
   */
 
public function select_iter($iter) {
   
$this->treeview->get_selection()->select_iter($iter);
  }

 
/**
   * public function unselect_iter($iter)
   *
   * - unselect (unhighlight) item in the treeview list given by iter
   * - $iter should be a GtkTreeIter
   *
   */
 
public function unselect_iter($iter) {
   
$this->treeview->get_selection()->unselect_iter($iter);
  }

 
/**
   *  protected function model_find($key_to_find, $model, $iter=null)
   *
   *  - given a $key, try to find the node containing this key
   *  - return iter for that search
   *  - or false;
   *
   */
 
protected function model_find($key_to_find, $model, $iter=null) {
    if (
$iter != null) {
     
$key = $model->get_value($iter, 1);
      if (
$key_to_find == $key)
        return
$iter;
    }

   
$childs = $this->iter_childs($model, $iter);
    foreach(
$childs as $child) {
     
$res = $this->model_find($key_to_find, $model, $child);
      if (
$res != null)
        return
$res;
      }

    return
false;
  }

  function
iter_childs($model, $iter) {
    if (
$iter != null) {
      if (!
$model->iter_has_child($iter))
        return array();

     
$list = array();
     
$count = $model->iter_n_children($iter);
      for(
$i=0 ; $i<$count; $i++)
       
$list[] = $model->iter_nth_child($iter, $i);

      return
$list;
    } else {
     
$iter = $model->get_iter_first();
     
$list = array();
     
$list[] = $iter;
      while (
null != ($iter = $model->iter_next($iter)))
       
$list[] = $iter;
      return
$list;
    }
  }

 
/**
   * void protected function model_populate($model, $node=null)
   *
   * recursive method for pupulating GtkTreeviewModel,
   * - $model : an array containing list or tree to view,
   * - node : null for first call, contains current inserting node in recursion
   *
   */
 
protected function model_populate($model, $node = null) {
    if (!
is_array($model))
      return
false;

    foreach (
$model as $key=>$entry) {
      if (!
is_array($entry)){
        if (
preg_match('/(.+)\|(.+)/', $entry, $values)) {
         
$key = $values[1];
         
$entry = $values[2];
        }
      }
     
     
# current entry is an array with some sub_nodes :
      # create current node entry, and recurse sub array
     
if (is_array($entry)) {
      if (
preg_match('/(.+)\|(.+)/', $key, $values)) {
       
$key = $values[1];
       
$literal = $values[2];
      } else
       
$literal = $key;

     
$sub_node = $this->model->append($node, array($literal, $key));
     
$this->model_populate($entry, $sub_node);
    } else {
      if(!
is_int($key)){
       
$this->model->append($node, array($entry, $key));
      }
      else {
       
$this->model->append($node, array($entry, $entry));
        }
      }
    }
  }

 
# for debug purpose.
 
private function user_model_to_text($model, $level=0) {
    if (!
is_array($model))
      return
'';

   
$spacing = str_repeat('    ', $level);
   
$txt = '';

    foreach (
$model as $key=>$entry) {
      if (!
is_array($entry)) {
        if (
preg_match('/(.+)\|(.+)/', $entry, $values)) {
         
$key = $values[1];
         
$entry = $values[2];
        }
      }
      if (
is_array($entry)) {
       
$txt .=  "$spacing * $key\n";
       
$txt .= $this->model_to_text($entry, $level+1);
      } else {
        if (!
is_int($key))
         
$txt .= "$spacing - $key | $entry\n";
        else
         
$txt .= "$spacing - $entry\n";
      }
    }
    return
$txt;
  }

 
/**
   * public function expand_all()
   *
   * expand all nodes showing all list.
   *
   */
 
public function expand_all() {
   
$this->treeview->expand_all();
  }

 
/**
   * public function collapse_all()
   *
   * collapse all nodes showing only root-tree
   *
   */
 
public function collapse_all() {
   
$this->treeview->collapse_all();
  }

 
/**
   * public function set_title($title)
   *
   * set title in row head.
   *
   */
 
public function set_title($title){
   
$this->title = $title;
   
$this->column->set_title($title);
  }
}
?>

Fichier attachéTaille
TreeViewPhpModel.php - class source code10.89 Ko

Using GtkSourceview to build a php editor with syntax highlighting

a simple php editor using GtkSourceView Here is a quick way to build a PHP editor with syntax highlighting. This widget is nearly usable as a real php-gtk IDE. It extends GtkSourceView widget and internally manages both a text buffer and language object classes.

Details

To use a GtkSourceView widget, you need to manage :

  • a text buffer containing edited data,
  • a view : a GtkSourceWiew widget or subclass,
  • for syntax highlighting, there is some classes ; in the class source below, you can see how to use them.

GtkSourceView widgets are extensions of a GtkTextWidget model. So you can use regular methods from this framework (see GtkTextView, GtkTextBuffer classes).

Supported Features

a simple php editor using GtkSourceView GtksourceView has builtin features :

  • multiple undo (redo?),
  • syntaxe highlighting (php, C, C++,bash ...),
  • line numbering
  • marker management
  • brackets highlighting

Source code

here is some details :

  1. PhpEditWidget is a widget (extending GtkSourceView) ; so you can use it just like a regular widget,
  2. lets parent widget build what need to be done,
  3. we want to create a php editor with php syntax highlighting ; just feed php mime type to GtkSourceLanguages* classes
  4. text editing with Gtk widget respect MVC partern model (Model, View, Controler); GtkSourceBuffer is the Model part, GtkSourceView is the View part, the code you are writing is the Controler part. Text editing, changes are done with Buffer part (Model), the View part is responsible for display part (line numbering, syntax highlighting, user interactions), Controler part should be a php class overriding PhpEditWidget widget ; this widget is keeped as simple as possible for this tutorial.
  5. This is an all-in-one widget ; the MVC part is directly managed by PhpEditWidget widget ; in simple usage, you can ignore text buffer existance, you will only need to use a ready to use widget. If you need to write more complexe usage, GtkSourceBuffer is still available as a protected attribute for experimentated developpers only
  6. File loading is made by a conveniant public method
  7. php source text is appended to text buffer (should be cleared before)
  8. text code should be protected from "undo" feature
  9. some IDE offer window spliting ; this feature is make quickly with this split method; we need to create a new PhpEditor widget and share text buffer between widgets.
<?php
error_reporting
(E_ALL);

class
PhpEditWidget extends GtkSourceView // Note 1
 
protected $buffer;                        // note 5
 
protected $lang;
  protected
$mime_lang;

  function
__construct() {
   
parent::__construct();  // note 2

    # default lang
   
$this->mime_lang = 'text/x-php-source';    // note 3
   
$this->set_lang($this->mime_lang);        // note 3

   
$this->buffer = new GtkSourceBuffer();     // note 4, note 5
   
$this->set_buffer($this->buffer);              // note 4

   
$this->set_show_line_numbers(true);
   
$this->set_show_line_markers(true);

   
$this->buffer->set_language($this->lang);
   
$this->buffer->set_highlight(true);
  }

  protected function
set_lang($mime_type) {      // note 3
   
$lang_manager = new GtkSourceLanguagesManager();
   
$this->lang = $lang_manager->get_language_from_mime_type($mime_type);
  }

  public function
load_file($file) {    // note 6
   
if (!file_exists($file))
      return
false;

   
$lines = file_get_contents($file);

   
$this->buffer->begin_not_undoable_action();  // note 8
   
$this->buffer->set_text($lines);   // note 7
   
$this->buffer->end_not_undoable_action();   // note 8
   
return true;
  }

  function
split() {                               // note 9
   
$edit = new PhpEditWidget();
   
$edit->set_buffer($this->get_buffer());
    return
$edit;
  }
}
// end of class PhpEditWidget

/**
* In this part of the code, indenting matches the
* widgets containment, not the code structure
*/
$win = new GtkWindow();
 
$vbox = new GtkVPaned();
   
$win->add($vbox);
     
$scrolled_win = new GtkScrolledWindow();
     
$scrolled_win->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
     
$vbox->add1($scrolled_win);
 
       
$php_edit = new PhpEditWidget();
       
$php_edit->load_file(__FILE__);
       
$scrolled_win->add_with_viewport($php_edit);    // note 1

      # test for a split-window like feature       // note 9
     
$scrolled_win = new GtkScrolledWindow();
     
$scrolled_win->set_policy(
       
Gtk::POLICY_AUTOMATIC,
       
Gtk::POLICY_AUTOMATIC);
 
     
$vbox->add2($scrolled_win);
       
$scrolled_win->add_with_viewport($php_edit->split());   // note 9

$win->set_size_request(400, 600);
$win->maximize();
$win->set_position(Gtk::WIN_POS_CENTER);

$win->show_all();
$win->set_title("PhpEditWidget - demo");
$win->connect_simple("destroy", array("Gtk", "main_quit"));

Gtk::main();
?>

To do

  • manage fonts,
  • make same widget demo with Scintilla widget

Links

Fichier attachéTaille
phpEditWidget.php source example1.92 Ko

Extending GtkDrawingArea to draw animated graphs

Grapher class example Drawing graphs with GtkDrawingArea is very easy. This is even easier using Graph_Core.

Some details about Graph_Core

Standard event registration is performed in Graph_Core constructor. You don't need to redo it on your own. Some useful functions are ready for use :

  • color setting : Graph_Core::set_color()
  • graphic context : Graph_Core::gc()
  • colormap : Graph_Core::colormap()

To create some drawings, you need a graphic context and colors setting. All other drawing primitives are available as GdkDrawable methods.

Class usage

Making some rotating graphs like vu-meters is very easy with the Grapher class.

  • note 1 - create a Grapher class,
  • note 2 - data acquisition done in a timeout handler,
  • note 3 - this is a rotating graph, so translate it by one pixel
  • note 4 - generate some pseudo-data
  • note 5 - plot this data (in percentile or absolute units).
  • note 6 - refresh graph every 10 ms.
<?php
$grapher
= new Grapher();  // note 1
$grapher->set_size_request(300, 200);

$window = new GtkWindow();
$window->connect_simple('destroy', array('gtk', 'main_quit'));
$window->set_title('Graph demo');
$window->set_position(Gtk::WIN_POS_CENTER);
$window->set_border_width(8);
$window->add($grapher);
$window->show_all();

Gtk::timeout_add(100, 'graph_timeout', $grapher); // note 2
Gtk::main();

/**
  * an arbitrary function generator.
  *
  */
function  graph_timeout($graph) {
  static 
$value  0;
  static 
$delta  1;
  static 
$count  0;

  if (!
$graph->realized())
    return 
true;

  if (
$count  1)
   
$graph->translate();    //  note  3
 
$count++;

  if (
$value>100  ||  $value  0)
   
$delta  *=  -1;

 
$value  +=  $delta;

 
$sin  intval(100  sin($count  3.14  180)); // note 4
 
$cos  intval(100  cos($count  3.14  180));

 
$graph->plot(0+$value, 'blue',   $mode_percent=true);  // note 5
 
$graph->plot($sin,     'red'  $mode_percent=false);
 
$graph->plot($cos,     'green'$mode_percent=false);

 
Gtk::timeout_add(10, 'graph_timeout'$graph);        // note 6
 
return  false;
}
?>

Class source

This class is provided in two parts :

  • Graph_Core contains basic functions that may be reusable for other projects,
  • Grapher class contains basic functions to generate a rotating graph.

Class Graph_core

<?php
class Graph_Core extends GtkDrawingArea {
 
# prefix class attributes with "_" to avoid collisions
  # with GtkDrawingArea attributes.
 
protected $_pixmap;
  protected
$_gc;
  protected
$_realized;

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

   
$this->connect('expose_event',
      array(
$this, 'expose_event'));
   
$this->connect('configure_event',
      array(
$this, 'configure_event'));
   
$this->connect('realize',
      array(
$this, 'realize'));
   
$this->set_events(Gdk::EXPOSURE_MASK
     
| Gdk::LEAVE_NOTIFY_MASK);

   
$this->_pixmap = null;
   
$this->_gc = null;
   
$this->_realized = false;
  }

  function
realize() {
   
$this->_realized = true;
  }

  function
realized() {
    return
$this->_realized;
  }

  function
configure_event($widget, $event) {
   
$w = $this->width();
   
$h = $this->height();

   
# allocate a new pixmap of the requested size ($this->allocation)
   
$this->_pixmap = new GdkPixmap($this->window, $w, $h, -1);

   
# clear pixmap (white color)
   
$this->_pixmap->draw_rectangle($this->style->white_gc, true,
     
0, 0, $w, $h);

    return
true;
  }

  function
expose_event($widget, $event) {
   
$this->window->draw_drawable($this->style->fg_gc[$this->state],
     
$this->pixmap(),
     
$event->area->x, $event->area->y,
     
$event->area->x, $event->area->y,
     
$event->area->width, $event->area->height
   
);
    return
false;
  }

  function
set_color($color) {
   
$col = $this->colormap()->alloc_color($color);
    if (
$col === false)
      return
$this->foreground('black');
    return
$this->gc()->set_foreground($col);
  }

  function
colormap() {
    return
$this->gc()->get_colormap();
  }

  function
pixmap() {
    if (!
$this->realized()) {
     
trigger_error("Widget not realized ; can't get pixmap");
      return
null;
    }
    return
$this->_pixmap;
  }

  function
gc() {
    if (!
$this->realized()) {
     
trigger_error("Widget not realized ; can't get GC");
      return
null;
    }
    if (
$this->_gc == null)
     
$this->_gc = new GdkGc($this->pixmap());
    return
$this->_gc;
  }

  function
width() {
    return
$this->allocation->width;
  }

  function
height() {
    return
$this->allocation->height;
  }

  function
w() {
    return
$this->width();
  }

  function
h() {
    return
$this->height();
  }
}
// file continues with class grapher
?>

class Grapher

<?php
// continuation of previous code piece

class Grapher extends Graph_Core{

/**
  *  plot a dot to the pixmap
  */
 
function plot ($value, $color = 'black', $mode_percent = false,
   
$x=null, $plot_w=1) {

   
$this->set_color($color);

    if (
$x == null)
     
$x = $this->w()-1;

    if (
$mode_percent) {
     
# $value is in percent
     
$value = $value * $this->h() / 100;
     
$y = intval($this->h() - $value);
    } else {
     
# 0 is in middle of window
     
$y = $this->h() / 2 - $value;
    }

    if (
$plot_w == 1) {
     
$this->pixmap()->draw_point($this->gc(), $x, $y);
     
$this->queue_draw_area($x, $y, 1, 1);
    } else {
     
$this->pixmap()->draw_arc($this->gc(), true,
       
$x - $plot_w, $y - $plot_w, $plot_w*2, $w*2,
       
0, 64 * 360);
     
$this->queue_draw_area($x - $w, $y - $plot_w,
       
$plot_w*2, $plot_w*2);
    }
  }

 
/**
   * scroll pixmap
   */
 
function translate($dx = -1, $dy = 0) {
   
$w = $this->w();
   
$h = $this->h();

   
# get full pixmap content into $img
   
$img = $this->pixmap()->get_image(0, 0, $w, $h);

   
# clear full area
   
$this->pixmap()->draw_rectangle($this->style->white_gc, true,
     
0, 0, $w, $h);

   
# draw $img with delta (translation)
   
$this->pixmap()->draw_image($this->gc(), $img, 0, 0,
     
$dx, $dy, $w, $h);
   
$this->queue_draw_area(0,0, $w, $h);
    }
  }
?>

Links

Fichier attachéTaille
Grapher.php_.txt4.72 Ko