• 26 May 2013 - Since the new version attracted too many spammy registrations (around 250 fake accounts/day), user registrations are now protected by Mollom's spam protection service. Contact us if this causes some trouble.
  • 01 May 2013 - After the site upgrade, all passwords were reset and you will need to ask the site for a login reset on your first connection.

Extending GtkDrawingArea with OO methods

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

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

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

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

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

error_reporting(E_ALL);

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

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

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

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

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

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

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

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

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

    return
true;
  }
}



class
Matrix{

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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

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

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

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

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

   
#
   
function composite(&$m){

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    function
draw($da){

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

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

class
GxPolyLine extends GxObject{
    protected
$points;

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

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

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

class
GxDrawingArea extends GtkDrawingArea{

    protected
$pixmap = null;

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

    }

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

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

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

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

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

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

class
GxDrawingAreaTest extends GxDrawingArea{

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

    function
__construct(){

       
parent::__construct();

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

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

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

           
# GDK_ALL_EVENTS_MASK

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


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

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

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

    function
timeout(){

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


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

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

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


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

   
}

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

        return
false;
    }



    function
button_press_event($widget, $event)
    {

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

        return
true;
    }

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

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

        return
true;
    }
}


class
App extends GtkWindow
{

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

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

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

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



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

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


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


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

commenti

Drag and Drop

Hi,
I like to extend this example with the following functionalities but I don't know how? Any clue?
1) Drag and drop an icon from a GTKIconView to the DrawingArea
2) Drag and drop the icons inside the drawingarea
3) Connect the icons to each other by selecting a connection line
4) By moving the icons the connection line moves too and is connect all the time by dragging
5) Delete the connection line between the icons
6) Copy, paste and delete functionality for all components inside the DrawingArea

It is something like Visio, AutoCAD, etc application that has a graphical display.
Any hint, thought highly appreciated.

Thanks
Mohsen

Opzioni visualizzazione commenti

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