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.
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.
Here is a complete widget tree with some useful information
Gtk class related
# 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
menubar:
File
New
Quit
Edit
Cut
Copy
Paste
---
- Choice 1
* Choice 2 (activated)
- Choice 3
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 :
null to first widget in a group,
and the first widget in successive widget creations.append() or
add() methods without significant
changes.set_active() method,"activate" signal,"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";
}
?>Examples
API
| Fichier attaché | Taille |
|---|---|
| gtk-menubar-overview.php_.txt | 2.51 Ko |
A short tutorial showing how to build a submenu in GtkMenubar. You can refer to the complete GtkMenubar tutorial for full explanations.
To build a submenu, first create (see note numbers on source code):
GtkMenu and object tree,
see tutorial about this),GtkMenuItem supporting the submenu
($view_zoom_item),GtkMenu with associated menu items,set_submenu() method.Notes :
menubar:
...
View
Line
Border
Toolbar
---
Zoom
50
100
150
---
Scrollbar
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)
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'));
?>| Fichier attaché | Taille |
|---|---|
| gtk-menubar-overview-submenu.php_.txt | 3.61 Ko |
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.
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).
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 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();
?>See the attached sources file below
See on source code header
| Fichier attaché | Taille |
|---|---|
| gtk-command-widget.php_.txt | 10.29 Ko |
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.
DesktopGadget is :
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();
?>You should have write acces to directory where this script is running to save gadget position.
source code is provided as a link below for free as LGPL licence ; also available here
| Fichier attaché | Taille |
|---|---|
| desktop-gadget.php_.txt | 10.05 Ko |
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
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.
<?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;
}
?>$dial->set_value($val)
See the attached sources file below
| Fichier attaché | Taille |
|---|---|
| gtk-drawing-area-dial.php_.txt | 6.31 Ko |
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.
<?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.
<?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;
}
}
?>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.
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.
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.
<?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 |
This example shows a nice drawing area class, derived
from GtkDrawingArea:
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();
?>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 :
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";
?><?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();
?>
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.
<?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);
}
?><?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);
}
}
?>| Fichier attaché | Taille |
|---|---|
| popup-menu-oo.php_.txt | 3.68 Ko |
A very easy way to build toolbars with icons, tooltips
and an easy signal connect feature.
Building GtkToolbar with buttons icons and tooltips require :
stock icons (from systeme library),GtkTooltips to each buttons ; there must be only one instance of GtkTooltips object.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.
Toolbar was a Gtktoolbar
clicked signal,
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();
}
?>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 ...
}
?>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']);
}
}
?>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.
| Fichier attaché | Taille |
|---|---|
| Toolbar.php_.txt | 6.77 Ko |
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:
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>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_.txt | 2.43 Ko |
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();
?>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();
?>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
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.
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).
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;
}
}
?>This method is used to add instances of GtkWindow
or derived from GtkWindow to the Application.
This method will fail in two cases:
Application::$GtkWindows array.This method is used to retrieve instances of
GtkWindow or derived from it.
GtkWindow instancefalseThis method will fail in one case:
Application::$GtkWindows array.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]
)
)
<?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();
}
}
}
?><?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;
}
}
?><?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();
?>
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 :
($x, $y) and size ($w, $h) as width and heightabstract ; if you try $obj=new Object, php will complaint about this. (Fatal error: Cannot instantiate abstract class Object)
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);
}
?>when you create some classes :
protected if you have no good reasons to make them private or public,public attribute visibilityprotected methods, avaid private methodspublic visibilitypublic visibility to.Now create a basic star with this piece of code :
Object classBasicStar constructorGtkDrawable 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);
}
}
?>Let's create a graphical widget than can handle :
BasicStar classhere are some details :
ChristmasWidget inherits from a basic graphical widget containg some drawing facilities not shown here.$objects is an array where we will store our objects ; those objects must be drawables (should inherit from Object)($w, $h) to display in full window,draw() method ; objects can only draw on a DrawingArea ; $this is a DrawingiArea, so give it to graphical objects to render graphics.
<?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
}
}
?>Here every things is object, so let's create a new Window dedicated for our Christmas application demonstration. Here are all details :
GtkWindowChrismasDemo window class<?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
?>To make our stars blink, we need :
step) ; each time we call draw() method, this counter will grow up,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 :
BlinkingStar,$cycle, $step have been explained below,$r is the last radius used when drawing at last call ; it will be used to clear star<?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++;
}
}
?>You can get complete source code in attachment at the bottom of this page.
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,
| Fichier attaché | Taille |
|---|---|
| Star tutorial source code | 5.35 Ko |
| Star tutorial with moon class | 5.58 Ko |
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.
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
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();
?><?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";
}
}
?>trace() method. Just subclass PhpGtkDirectoryTree widget and display a feedback in a label widget, or perhaps in
title part of PhpGtkDirectoryTree.
Main of this class comes from great site from kksou. Please refer to his site for usage.
Gnope repository.
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.
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.
<?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 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.
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();
?>See the attached sources.
GtkFixed /
GtkEventBox / GtkLabel,GtkEventBox is used to catch button click,GtkFixed is a special composite widget that
allows widget placement and moves,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_.txt | 3.03 Ko |
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 :
callback
type code design,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 frozenOur 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 :
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.Gtk::main_quit()
is invokedTake 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.
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 :
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__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_.txt | 16.05 Ko |
| CHANGELOG.txt | 41.12 Ko |
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.
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;
}
}
?>You can build more complex models using PHP syntax.
array to create sub-arrays
key and
a litteral value that could be translated :
use syntaxe : "key" => "value"$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"
)
);
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 code | 10.89 Ko |
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.
To use a GtkSourceView widget, you need to manage :
GtkSourceView widgets are extensions of
a GtkTextWidget model. So you can use
regular methods from this framework (see
GtkTextView, GtkTextBuffer
classes).
GtksourceView has builtin features :
here is some details :
GtkSourceLanguages* classesGtkSourceBuffer 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.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 onlywindow 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();
?>| Fichier attaché | Taille |
|---|---|
| phpEditWidget.php source example | 1.92 Ko |
Drawing graphs with GtkDrawingArea
is very easy. This is even easier using 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 :
Graph_Core::set_color()Graph_Core::gc()Graph_Core::colormap()To create some drawings, you need a graphic context
and colors setting. All other drawing primitives are available
as GdkDrawable methods.
Making some rotating graphs like vu-meters is very easy with
the Grapher class.
<?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;
}
?>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.<?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
?><?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);
}
}
?>GtkDrawingArea| Fichier attaché | Taille |
|---|---|
| Grapher.php_.txt | 4.72 Ko |