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