set_from_pixbuf(GdkPixbuf::new_from_xpm_data($icon_data)); } } class Button extends GtkEventBox{ protected $label; protected $callbacks; public function __construct($label){ parent::__construct(); $this->modify_bg(Gtk::STATE_NORMAL, GdkColor::parse('#EEE')); $this->label = new GtkLabel($label); $this->add($this->label); $this->label->modify_font(new PangoFontDescription('Sans Bold 6')); $this->add_events(Gdk::BUTTON_PRESS_MASK); $this->connect('button-press-event', array($this, 'button_press_event')); $this->callbacks = array(); } public function connect($signal, $callback){ if($signal == 'clicked'){ $this->callbacks[] = $callback; } else parent::connect($signal, $callback); } public function button_press_event($selft, $event){ foreach($this->callbacks as $callback){ call_user_func($callback, $event); } return true; } } class MovableWidget extends GtkEventBox{ protected $clic_point; public function __construct(){ parent::__construct(); $this->add_events(Gdk::POINTER_MOTION_MASK|Gdk::BUTTON_PRESS_MASK); $this->connect('motion-notify-event', array($this, 'motion_notify_event')); $this->connect('button-press-event', array($this, 'button_press_event')); $this->click_point = null; } public function button_press_event($w, $event){ $this->get_parent()->move_top($this); $this->click_point = array( 'x'=> $event->x, 'y'=> $event->y); } public function motion_notify_event($w, $event){ # no button press if($event->state == 0) return; # track new position and move object $x = $this->allocation->x + $event->x - $this->click_point['x']; $y = $this->allocation->y + $event->y - $this->click_point['y']; $this->get_parent()->move($this, $x, $y); } } class ArchitectClassWidget extends MovableWidget{ protected $_name; protected $properties; protected $vbox; protected $frame; protected $properties_box; protected $expended = true; protected $id; static $id_counter = 0; public function __construct($name, $properties){ parent::__construct(); $this->_name = $name; $this->properties = $properties; $this->frame = new GtkFrame(); $this->add($this->frame); $this->vbox = new GtkVbox(); $this->frame->add($this->vbox); $prop_event_box = new GtkEventbox(); $this->vbox->pack_end($prop_event_box); $prop_event_box->modify_bg(Gtk::STATE_NORMAL, GdkColor::parse('#DDD')); $this->properties_box = new GtkVbox(); $prop_event_box->add($this->properties_box); $this->buildTitle($this->vbox, $name); $this->setProperties($this->properties_box, $properties); $this->set_size_request(150, -1); $this->modify_bg(Gtk::STATE_NORMAL, GdkColor::parse('#aaa')); $this->id = ArchitectClassWidget::$id_counter++; } protected function buildTitle($parent, $title){ $hbox = new GtkHbox(); # title bar box $parent->pack_start($hbox); $hbox->pack_end($button = new Button('X'), false, false); $button->connect('clicked', array($this, 'on_button_close')); $hbox->pack_end($button = new Button('^'), false, false); $button->connect('clicked', array($this, 'on_button_expand')); global $icon_handle_xpm; $handle = new HandleIcon(); $hbox->pack_start($handle, false, false); $handle = new HandleIcon(); $hbox->pack_end($handle, false, false); $hbox->pack_start($label = new GtkLabel($title), false, true); $label->modify_font(new PangoFontDescription('Sans Bold 8')); } public function on_button_close(){ echo "ArchitectClassWidget::on_button_close()\n"; $this->get_parent()->link_remove($this); $this->destroy(); } public function on_button_expand(){ if($this->expended) $this->properties_box->hide(); else $this->show_all(); $this->expended = !$this->expended; } protected function setProperties($parent, $properties){ # remove existing labels. foreach($parent->get_children() as $child) $parent->remove($child); foreach($properties as $key=>$prop){ $label = new GtkLabel(); $desc = sprintf('%s:%s', $key, $prop); $label->set_markup($desc); $label->set_justify(Gtk::JUSTIFY_LEFT); $parent->pack_start($label, false, false); } } public function center(){ $center = new stdClass(); $center->x = $this->allocation->x + intval($this->allocation->width/2); $center->y = $this->allocation->y + intval($this->allocation->height/2); return ($center); } function id(){ return $this->id; } } class ArchitectWindow extends GtkFixed{ protected $links; protected $widget; public function __construct(){ parent::__construct(); $this->add_events(Gdk::EXPOSURE_MASK|Gdk::BUTTON_PRESS_MASK); $this->connect('expose-event', array($this, 'expose_event')); $this->connect('button-press-event', array($this, 'button_press_event')); $this->links = array(); $this->modify_bg(Gtk::STATE_NORMAL, GdkColor::parse("#000")); } public function move_top($w){ $pos = $w->allocation; $this->remove($w); $this->put($w,$pos->x,$pos->y); } public function add_link($w1, $w2){ $this->links[] = array($w1, $w2); if(!isset($this->widgets[$w1->id()])){ $this->widgets[$w1->id()] = $w1; } if(!isset($this->widgets[$w2->id()])){ $this->widgets[$w2->id()] = $w2; } } public function link_remove($widget){ foreach($this->widgets as $w){ if($w->id() == $widget->id()) unset($this->widgets[$widget->id()]); } foreach($this->links as $key=>$link){ if($link[0]->id() == $widget->id() || $link[1]->id() == $widget->id()) unset($this->links[$key]); } } public function expose_event($self, $event){ if($event->count != 0) return; foreach($this->links as $link){ $p1 = $link[0]->center(); $p2 = $link[1]->center(); $this->window->draw_line($this->style->black_gc, $p1->x, $p1->y, $p2->x, $p2->y); } } public function button_press_event($self, $event){ echo "ArchitectWindow::button_press_event()\n"; list($w1, $w2) = $this->links[0]; print_r($w1->window); } } // Create a new window. $window = new GtkWindow(); // Properly handle closing of the window. $window->connect_simple('destroy', array('Gtk', 'main_quit')); $architect = new ArchitectWindow(); $window->add($architect); $core = new ArchitectClassWidget('Core', $properties = array('id' => 'integer', 'name' => 'char')); $architect->put($core, 150, 10); $customer = new ArchitectClassWidget('Customer', $properties = array('name' => 'char')); $architect->put($customer, 80, 70); $article = new ArchitectClassWidget('Article', $properties = array('desc' => 'char')); $architect->put($article, 235, 70); $properties = array( 'x' => 'integer', 'y' => 'integer', 'w' => 'integer', 'h' => 'integer', 'background' => 'char', 'foreground' => 'char', 'font' => 'char', 'h' => 'integer', 'desc' => 'char' ); $big = new ArchitectClassWidget('BigObject', $properties); $architect->put($big, 10, 130); $architect->add_link($core, $customer); $architect->add_link($core, $article); $architect->add_link($customer, $big); $window->set_size_request(400, 300); $window->set_position(Gtk::WIN_POS_CENTER); $window->show_all(); Gtk::main(); ?>