SlideShare una empresa de Scribd logo
1 de 33
PHP ON THE DESKTOP AND PHP-GTK2
Elizabeth Marie Smith – Zend Uncon 2008
ABOUT ME
Elizabeth Marie Smith

• AKA auroraeosrose
• Have a very very common name
• Was an English Major (minor in Sociology)
• Started using PHP for a Manga/Anime fan site (still alive)
• Have been using PHP since 4 beta 1 (23-Jul-1999)
• Have been using C since Nov of 2006
• Love the color Pink
• I hate computers
• I love programming
• Thinks open source on Windows is a “gateway drug”
• Works at OmniTI (http://omniti.com)
• Am a contributor on various open source projects (including
PHP, PECL and PHP-GTK)
IS THE DESKTOP DEAD?
THE DESKTOP STILL LIVES
Reasons For Desktop Applications


• People aren’t always online
• Some applications would be bandwidth
prohibitive
• Some data is too sensitive to be passed
on the wire
• Embedded OSs don’t always work well
with websites
• Browsers are just Quirky (I’m looking at you,
IE6, IE7, Opera…)
• Current Desktop RIA tools (AIR, Xulrunner, Mozilla
Prism) are still young (and buggy)
WHY USE PHP FOR DESKTOP APPLICATIONS
PHP Advantages




• No Compiling
• Instant Changes
• No learning curve for a new language
• Plug into PHP extensions
• Easy to Use
• Fast to Write
• Use already familiar libraries and tools
WHY USE PHP FOR DESKTOP APPLICATIONS
PHP Disadvantages


• Speed
• Additional Extensions/Libraries needed
• Speed
• Distribution (phar is helpful there)
• Speed
• Security (of code – source code encoders
  might help here)
• No threading

                    See the theme?
HOW DO I WRITE DESKTOP APPS?
PHP CLI


• PHP does not care about HTML (really)
• PHP works through SAPIS (Server
  Application Programming Interface) – for
  the desktop the CLI (Command Line
  Interface) SAPI is our friend
• CLI has no headers, CLI responds to no
  requests
• You need a way to make GUI’s if you don’t
  want a console app – Just like we wrap
  other C/C++ libraries we can wrap GUI
  libraries
GUI OPTIONS WITH PHP
So What are your Options?


•        Well Established
          • PHP-GTK2
          • Winbinder

•        Works, but not Complete
          • PHP-QT (no windows support)
          • WxWidgets (only windows support)
          • Win::Gui (part of Win::API)


    Bottom line? For Cross-Platform PHP-GTK works
WTH IS GTK?
Quick Intro


• GTK was Gimp Tool Kit (long long ago)
• GTK is GUI Toolkit for Gnome (if you use
  gnome, you have it already)
• GTK has multiple backends (for native
  Windows and MacOSX apps – you don’t
  need X on Mac anymore)
• To use PHP-GTK you need PHP CLI, GTK, and
  the PHP-GTK extension
•        http://oops.opsat.net/doc/install.html - how to install
         (windows is unzip and run, mac has an installer, ubuntu has
         .debs – yes this should be in the php-gtk docs, we need
         volunteers who aren’t afraid of xml)
LET’S DO PHP-GTK
THE BASICS
What you need to know to use PHP-GTK




• Main Loop
• Signals & Callbacks
• Widgets
• OOP to the Max
• Visibility
STARTING OUR PROJECT
Create Our initial Window                                                Resulting window
<?php
class Php_Gtk_Twitter_Client extends GtkWindow {

      public function __construct() {
          parent::__construct();
          $this->set_icon($this->render_icon(
             Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG));
          $this->set_size_request(300, 500);
          $this->set_title('PHP-GTK Twitter Client');
          $this->connect_simple('destroy', array('Gtk', 'main_quit'));
      }
}
$window = new Php_Gtk_Twitter_Client;
$window->show_all();
Gtk::main();
OVERRIDING BASIC FUNCTIONALITY
Minimize to the Taskbar




• Specialty Widgets are Cool
• GtkStatusIcon – 2.10+
• Know your Hierarchy
• Stock Icons
• Callback “bubbling”
• timeouts (timers)
STOCK ITEMS
Demo from PHP-GTK
Minimize to Status Icon




                                                                                                                                    MINIMIZE TO TRAY
 <?php                                                                                                           Resulting Window
 class Php_Gtk_Twitter_Icon extends GtkStatusIcon {
      protected $alive;
      protected $lockout;
      public function __construct() {
          parent::__construct();
            $this->set_from_stock(Gtk::STOCK_ABOUT);
            $this->set_tooltip('PHP-GTK Twitter Client');
            while(Gtk::events_pending() || Gdk::events_pending()) {
                  Gtk::main_iteration_do(true);
            }
            $this->is_ready();
            return;
      }
      public function is_ready() {
          $this->alive = true;
          if($this->lockout < 5 && !$this->is_embedded()) {
                Gtk::timeout_add(750,array($this,'is_ready'));
                ++$this->lockout;
                $this->alive = false;
          } else if(!$this->is_embedded()) {
                die("Error: Unable to create Tray Icon. Please insure that your system's tray is enabled.n");
                $this->alive = false;
          }
            return;
      }
      public function activate_window($icon, $window) {
          if ($window->is_visible()) {
              $window->hide();
          } else {
              $window->deiconify();
              $window->show();
          }
      }
 }
 class Php_Gtk_Twitter_Client extends GtkWindow {
      protected $statusicon;
       public function __construct() {
            parent::__construct();
            $this->set_icon($this->render_icon(Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG));
            $this->set_size_request(300, 500);
            $this->set_title('PHP-GTK Twitter Client');
            $this->connect_simple('destroy', array('Gtk', 'main_quit'));
            $this->statusicon = new Php_Gtk_Twitter_Icon;
            $this->statusicon->connect('activate', array($this->statusicon,
                  'activate_window'), $this);
            $this->set_skip_taskbar_hint(true);
            $this->connect('window-state-event', array($this, 'minimize_to_tray'));
      }
      public function minimize_to_tray($window, $event) {
          if ($event->changed_mask == Gdk::WINDOW_STATE_ICONIFIED &&
              $event->new_window_state & Gdk::WINDOW_STATE_ICONIFIED) {
              $window->hide();
          }
          return true; //stop bubbling
      }
 }
 $window = new Php_Gtk_Twitter_Client;
 $window->show_all();
 Gtk::main();
VIEWING DATA
Data from PHP + a Treeview from GTK




• Remember this is PHP
• Treeview has a model and view
• ListStore is a single level model
• Treeviews need Columns
• Columns need Renderers
• Different types of Renderers
MY TWITTER API
HTTP Streams are GOOD

I really don’t believe in wheel inventing , especially when I have little experience with the subject. However there is an issue – every
existing twitter API uses curl – and I don’t want to depend on an extension that isn’t always installed.


        protected function process($url, $date = 0, $type = 'GET', $data = null) {
                  // add caching header
                  $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date);

                   $options = array(
                          'http' => array(
                                 'method' => $type,
                                 'header' => $this->headers)
                          );
                   if (!is_null($data)) {
                          $options['http']['content'] = http_build_query($data);
                   }
                   $context = stream_context_create($options);
                   if ($this->username && $this->password) {
                          $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password)
                          . '@twitter.com/';
                   } else {
                          $base = 'http://twitter.com/';
                   }
                   set_error_handler(array($this,'swallow_error'));
                   $string = file_get_contents($base . $url, false, $context);
                   restore_error_handler();
                   return json_decode($string);
             }
MINIMIZE TO TRAY
   Put a Treeview in the Window – this goes in __construct                                Callbacks for showing Images and Messages
$this->temp = sys_get_temp_dir() . 'php-gtk-twitter-api-cache';                     public function show_user($column, $cell, $store, $position) {
if (!file_exists($this->temp)) {                                                                  $pic = $store->get_value($position, 1);
                                                                                                  $name = $this->temp . md5($pic);
    mkdir($this->temp, null, true);                                                               if (isset($this->pic_queue[$name])) {
}                                                                                                       return;
                                                                                                  } elseif (isset($this->pic_cached[$name])) {
                                                                                                        $store = $this->treeview->get_model();
$this->twitter = new Php_Gtk_Twitter_Api;                                                               if (is_null($store->get_value($position, 0))) {
                                                                                                               $pixbuf = GdkPixbuf::new_from_file($name . '.jpg');
// User image pixbuf, user image string, user name,                                                            $store->set($position, 0, $pixbuf);
                                                                                                               $cell->set_property('pixbuf', $pixbuf);
// user id, text, favorited, created_at, id                                                             }
  $store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING,                                     return;
                                                                                                  }
   Gobject::TYPE_STRING, Gobject::TYPE_LONG, GObject::TYPE_STRING,                                $this->pic_queue[$name] = array('name' => $name, 'url' => $pic,
   GObject::TYPE_BOOLEAN, GObject::TYPE_STRING, Gobject::TYPE_LONG);                                    'pos' => $position, 'cell' => $cell);
                                                                                                  if (empty($this->load_images_timeout)) {
$store->set_sort_column_id(7, Gtk::SORT_DESCENDING);                                                    $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue'));
                                                                                                  }
$list = $this->twitter->get_public_timeline();                                              }
                                                                                            public function pic_queue() {
// stuff the store                                                                              $pic = array_shift($this->pic_queue);
foreach($list as $object) {                                                                     if (empty($pic)) {
      $store->append(array(null, $object->user->profile_image_url, $object->user->name,               $this->load_images_timeout = null;
                                                                                                      return true;
             $object->user->id, $object->text, $object->favorited, $object->created_at,         }
             $object->id));                                                                     if (!file_exists($pic['name'])) {
}                                                                                                     file_put_contents($pic['name'] . '.jpg', file_get_contents($pic['url']));
                                                                                                      $this->pic_cached[$pic['name']] = $pic['url'];
                                                                                                }
$this->public_timeline_timeout = Gtk::timeout_add(61000,                                        return true; // keep the timeout going
  array($this, 'update_public_timeline')); // every 60 seconds                              }
                                                                                            public function message_markup($column, $cell, $store, $position) {
$scrolled = new GtkScrolledWindow();                                                            $user = utf8_decode($store->get_value($position, 2));
                                                                                                $message = utf8_decode($store->get_value($position, 4));
$scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);                                   $time = $this->distance($store->get_value($position, 6));
$this->add($scrolled);
$this->treeview = new GtkTreeView($store);                                                        $message = htmlspecialchars_decode($message, ENT_QUOTES);
$scrolled->add($this->treeview);                                                                  $message = str_replace(array('@' . $user, '&nbsp;', '&'), array('<span foreground="#FF6633">@' .
$this->treeview->set_property('headers-visible', false);                                            $user . '</span>', ' ', '&amp;'), $message);
                                                                                                  $cell->set_property('markup', "<b>$user</b>:n$messagen<small>$time</small>");
$this->treeview->set_rules_hint(true);                                                      }

$picture_renderer = new GtkCellRendererPixbuf();                                            protected function distance($from) {
                                                                                                $minutes = round(abs(time() - strtotime($from)) / 60);
$picture_column = new GtkTreeViewColumn('Picture', $picture_renderer, 'pixbuf', 0);
$picture_column->set_cell_data_func($picture_renderer, array($this, 'show_user'));                switch(true) {
$this->treeview->append_column($picture_column);                                                      case ($minutes == 0):
                                                                                                           return 'less than 1 minute ago';
                                                                                                      case ($minutes < 1):
$message_renderer = new GtkCellRendererText();                                                             return '1 minute ago';
$message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD);                                         case ($minutes <= 55):
                                                                                                           return $minutes . ' minutes ago';
$message_renderer->set_property('wrap-width', 200);                                                   case ($minutes <= 65):
$message_renderer->set_property('width', 10);                                                              return 'about 1 hour ago';
                                                                                                      case ($minutes <= 1439):
$message_column = new GtkTreeViewColumn('Message', $message_renderer);                                     return 'about ' . round((float) $minutes / 60.0) . ' hours';
                                                                                                      case ($minutes <= 2879):
$message_column->set_cell_data_func($message_renderer,                                                     return '1 day ago';
  array($this, 'message_markup'));                                                                    default:
                                                                                                           return 'about ' . round((float) $minutes / 1440) . ' days ago';
$this->treeview->append_column($message_column);                                                  }
                                                                                            }
$this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE);
A WORKING VIEW OF TWITTER MESSAGES
It works! Public Timeline in Action
PACKING
Putting other things in a Window




• GTK is not “pixel perfect”
• Packing is not as hard as it looks
• Containers can hold one or many
• Remember that a container
expands to the size of it’s contents
TOOLBARS AND STATUSBARS
Pack the Statusbar, Treeview, and Toolbar in a VBox                         Create Statusbar and Toolbar

$this->statusbar = new GtkStatusBar();

// Create a toolbar with login button
$tb = new GtkToolbar();
$tb->set_show_arrow(false);
$this->loginbutton = GtkToolButton::new_from_stock(Gtk::STOCK_JUMP_TO);
$this->loginbutton->set_label('Login');
$this->loginbutton->connect_simple('clicked', array($this, 'login'));
$tb->insert($this->loginbutton, -1);

// logout button
$this->logoutbutton = GtkToolButton::new_from_stock(Gtk::STOCK_CLOSE);
$this->logoutbutton->set_label('Logout');
$this->logoutbutton->connect_simple('clicked', array($this, 'logout'));
$tb->insert($this->logoutbutton, -1);
$this->logoutbutton->set_sensitive(false);


With Toolbar, Treeview, and Statusbar
$vbox = new GtkVBox();
$this->add($vbox);
$vbox->pack_start($tb, false, false);
$vbox->pack_start($scrolled);
$vbox->pack_start($this->statusbar, false, false);

Update the Header and Public Timeline using a Gtk::timeout
public function update_public_timeline() {
      $this->pic_queue = array();
      $list = $this->twitter->get_public_timeline();
      $this->statusbar->pop(1);
      $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') .
         ' ' . count($list) . ' new tweets');
      $store = $this->treeview->get_model();
      $store->clear();
      foreach($list as $object) {
              $store->append(array(null,
                 $object->user->profile_image_url, $object->user->name,
                    $object->user->id, $object->text, $object->favorited,
                    $object->created_at, $object->id));
      }
      return true;
}
DIALOGS
Additional Windows




• Dialogs are cool
• Dialogs are usually modal, but
not always
• Dialogs can be very general or
very specific
• You can put anything inside one
LOGIN DIALOG BOX
Extends GtkDialog                                                           Login Dialog
class Php_Gtk_Twitter_Login_Dialog extends GtkDialog {
       protected $emailentry;
       protected $passwordentry;
       public function __construct($parent) {
           parent::__construct('Login to Twitter', $parent, Gtk::DIALOG_MODAL,
                 array(
                             Gtk::STOCK_OK, Gtk::RESPONSE_OK,
                             Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL));
           $table = new GtkTable();
           $email = new GtkLabel('Email:');
           $table->attach($email, 0, 1, 0, 1);
           $password = new GtkLabel('Password:');
           $table->attach($password, 0, 1, 1, 2);
           $this->emailentry = new GtkEntry();
           $table->attach($this->emailentry, 1, 2, 0, 1);
           $this->passwordentry = new GtkEntry();
           $table->attach($this->passwordentry, 1, 2, 1, 2);
           $this->passwordentry->set_visibility(false);
           $this->vbox->add($table);
           $this->errorlabel = new GtkLabel();
           $this->vbox->add($this->errorlabel);
           $this->show_all();
       }
        public function check_login($twitter) {
              $this->errorlabel->set_text('');
              $email = $this->emailentry->get_text();
              $password = $this->passwordentry->get_text();
              if (empty($password) || empty($password)) {
                    $this->errorlabel->set_markup(
    '<span color="red">Name and Password must be entered</span>');
                    return false;
              }
              if ($twitter->login($email, $password)) {
                    return true;
              } else {
                    $this->errorlabel->set_markup(
                '<span color="red">Authentication Error</span>');
                    return false;
              }
        }
}
LOGIN DIALOG BOX
Callbacks                                                                             After Login
public function login() {
            if (!empty($this->load_images_timeout)) {
                  Gtk::timeout_remove($this->load_images_timeout);
                  $readd = true;
            }
            Gtk::timeout_remove($this->public_timeline_timeout);
            $login = new Php_Gtk_Twitter_Login_Dialog($this);
            while($response = $login->run()) {
                  if ($response == GTK::RESPONSE_CANCEL ||
                        $response == GTK::RESPONSE_DELETE_EVENT) {
                          if (isset($readd)) {
                                $this->load_images_timeout =
                                  Gtk::timeout_add(500, array($this, 'pic_queue'));
                          }
                          $this->public_timeline_timeout =
                                 Gtk::timeout_add(61000,
                             array($this, 'update_public_timeline'));
                          $login->destroy();
                          break;
                  } elseif ($response == GTK::RESPONSE_OK) {
                          if($login->check_login($this->twitter)) {
                                $this->logoutbutton->set_sensitive(true);
                                $this->loginbutton->set_sensitive(false);
                                $login->destroy();
                                $this->public_timeline_timeout = Gtk::timeout_add(
                                         61000, array($this, 'update_timeline'));
                                $this->load_images_timeout = Gtk::timeout_add(500,
                                        array($this, 'pic_queue'));
                                $this->treeview->get_model()->clear();
                                $this->pic_queue = array();
                                $this->pic_cached = array();
                                $this->update_timeline();
                                break;
                          }
                  }
            }
      }

public function logout() {
            $this->twitter->logout();
            $this->logoutbutton->set_sensitive(false);
            $this->loginbutton->set_sensitive(true);
            $this->public_timeline_timeout = Gtk::timeout_add(61000,
                  array($this, 'update_public_timeline')); // every 60 seconds
            $this->pic_queue = array();
            $this->pic_cached = array();
            $this->update_public_timeline();
      }
DATA ENTRY
Putting in Data




• GTKEntry
• Basic Data Entry – activates on
return, can set maximum length
allowed
• Simple label for messages –
could use a dialog or other
method of informing the user
LOGIN DIALOG BOX
Packing it in                                                           Creating the Entries
$vbox = new GtkVBox();                                                              // Create an update area
$this->add($vbox);                                                                  $this->updateentry = new GtkEntry();
$vbox->pack_start($tb, false, false);                                               $this->updateentry->set_max_length(140);
$scrolled = new GtkScrolledWindow();                                                $this->updateentry->set_sensitive(false);
$scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);                       $this->updateentry->connect('activate',
$vbox->pack_start($scrolled);                                                          array($this, 'send_update'));
$vbox->pack_start(new GtkLabel('What are you doing?'), false, false);               $this->entrystatus = new GtkLabel();
$vbox->pack_start($this->updateentry, false, false);
$vbox->pack_start($this->entrystatus, false, false);
$vbox->pack_start($this->statusbar, false, false);




The Callback on Activate


public function send_update($entry) {
            if ($this->twitter->send($entry->get_text())) {
                  $this->entrystatus->set_text('Message Sent');
                  $this->update_timeline();
                  $this->updateentry->set_text('');
            } else {
                  $this->entrystatus->set_markup(‘
   <span color="red">Error Sending Message - Try Again</span>');
            }
      }
PHP-GTK TWITTER CLIENT
Completed App
COMPLETED CODE
Final Code, all 492 lines
class Php_Gtk_Twitter_Api {
                                                                             public function get_timeline() {
       protected $login = false;                                                   if ($this->login && $this->can_call()) {
       protected $username;                                                            if (empty($this->lastid)) {
       protected $password;                                                                  $data = $this->process('statuses/friends_timeline.json');
                                                                                       } else {
                                                                                             $data = $this->process('statuses/friends_timeline.json', $this->lasttime,
      protected $cached_public;                                                               'GET', array('since_id' => $this->lastid));
                                                                                       }
      protected $cached_public_timestamp = 0;                                          if ($data) {
                                                                                             $this->lastid = $data[0]->id;
                                                                                       }
      protected $lastid = 0;                                                           $this->lasttime = time();
                                                                                       return $data;
      protected $headers = array                                                   }
                                                                               }
         ('', 'X-Twitter-Client: PHP-GTK Twitter Client',
                                                                               public function send($message) {
           'X-Twitter-Client-Version: 0.1.0-dev',                                  if ($this->login && $this->can_call()) {
           'X-Twitter-Client-URL: http://elizabethmariesmith.com');                    $data = $this->process('statuses/update.json', 0, 'POST', array('status' => $message));
                                                                                       if ($data) {
                                                                                             return true;
      public function login($username, $password) {                                    }
          $this->username = $username;                                                 return false;
                                                                                   }
          $this->password = $password;                                         }
          $worked = $this->process('account/verify_credentials.json');
                                                                               protected function can_call() {
          if ($worked && $worked->authorized == true) {                            if (!$this->login) {
                $this->login = true;                                                    return false;
                                                                                   }
                return true;                                                       $worked = $this->process('account/rate_limit_status.json');
          }                                                                        return ($worked->remaining_hits > 1);
          return false;                                                        }
      }                                                                        protected function process($url, $date = 0, $type = 'GET', $data = null) {
                                                                                   // add caching header
                                                                                   $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date);
      public function logout() {
          $this->username = null;                                                    $options = array(
                                                                                           'http' => array(
          $this->password = null;                                                                  'method' => $type,
          $this->login = false;                                                                    'header' => $this->headers)
          $this->process('account/end_session', 0, 'POST');                                );
                                                                                     if (!is_null($data)) {
      }                                                                                    $options['http']['content'] = http_build_query($data);
                                                                                     }
                                                                                     $context = stream_context_create($options);
      public function get_public_timeline() {                                        if ($this->username && $this->password) {
          if ($this->cached_public_timestamp < time()) {                                   $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password)
                                                                                           . '@twitter.com/';
               $this->cached_public =                                                } else {
                  json_decode(file_get_contents(                                           $base = 'http://twitter.com/';
                                                                                     }
                 'http://twitter.com/statuses/public_timeline.json'));               set_error_handler(array($this,'swallow_error'));
               $this->cached_public_timestamp = time() + 60;                         $string = file_get_contents($base . $url, false, $context);
                                                                                     restore_error_handler();
              // caches every 60 seconds                                             return json_decode($string);
                                                                               }
          }
          return $this->cached_public;                                         public function swallow_error($errno, $errstr) {} // this should be treated as private
      }                                                                  }
COMPLETED CODE
Final Code, all 492 lines
class Php_Gtk_Twitter_Login_Dialog extends GtkDialog {                class Php_Gtk_Twitter_Icon extends GtkStatusIcon {
       protected $emailentry;
       protected $passwordentry;                                           protected $alive;
                                                                           protected $lockout;
      public function __construct($parent) {
          parent::__construct('Login to Twitter',                          public function __construct() {
            $parent, Gtk::DIALOG_MODAL,                                        parent::__construct();
                array(Gtk::STOCK_OK, Gtk::RESPONSE_OK,
                         Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL));              $this->set_from_stock(Gtk::STOCK_ABOUT);
          $table = new GtkTable();
                                                                                 $this->set_tooltip('PHP-GTK Twitter Client');
          $email = new GtkLabel('Email:');
          $table->attach($email, 0, 1, 0, 1);                                    while(Gtk::events_pending() || Gdk::events_pending()) {
          $password = new GtkLabel('Password:');                                       Gtk::main_iteration_do(true);
          $table->attach($password, 0, 1, 1, 2);                                 }
          $this->emailentry = new GtkEntry();                                    $this->is_ready();
          $table->attach($this->emailentry, 1, 2, 0, 1);                         return;
          $this->passwordentry = new GtkEntry();                           }
          $table->attach($this->passwordentry, 1, 2, 1, 2);
          $this->passwordentry->set_visibility(false);                     public function is_ready() {
          $this->vbox->add($table);
                                                                               $this->alive = true;
          $this->errorlabel = new GtkLabel();
          $this->vbox->add($this->errorlabel);                                 if($this->lockout < 5 && !$this->is_embedded()) {
          $this->show_all();                                                         Gtk::timeout_add(750,array($this,'is_ready'));
      }                                                                              ++$this->lockout;
                                                                                     $this->alive = false;
      public function check_login($twitter) {                                  } else if(!$this->is_embedded()) {
           $this->errorlabel->set_text('');                                          die("Error: Unable to create Tray Icon.
           $email = $this->emailentry->get_text();                                      Please insure that your system's tray is enabled.n");
           $password = $this->passwordentry->get_text();                             $this->alive = false;
           if (empty($password) || empty($password)) {
                                                                               }
                 $this->errorlabel->set_markup(
         '<span color="red">Name and Password must be entered</span>');
                                                                                 return;
                 return false;
           }                                                               }
           if ($twitter->login($email, $password)) {
                 return true;                                              public function activate_window($icon, $window) {
           } else {                                                            if ($window->is_visible()) {
                 $this->errorlabel->set_markup(                                    $window->hide();
                    '<span color="red">Authentication Error</span>');          } else {
                 return false;                                                     $window->deiconify();
           }                                                                       $window->show();
      }                                                                        }
}                                                                          }
COMPLETED CODE
Final Code, all 492 lines
class Php_Gtk_Twitter_Client extends GtkWindow {                                                                        foreach($list as $object) {
                                                                                                                               $store->append(array(null, $object->user->profile_image_url,
     protected      $statusicon;
     protected      $twitter;                                                                                                      $object->user->name, $object->user->id,
     protected      $treeview;                                                                                                     $object->text, $object->favorited, $object->created_at,
     protected      $statusbar;
                                                                                                                                      $object->id));
     protected $temp;                                                                                                     }
     protected $pic_queue = array();
     protected $pic_cached = array();
                                                                                                                         $this->public_timeline_timeout = Gtk::timeout_add(61000,,
     protected $public_timeline_timeout;                                                                                   array($this, 'update_public_timeline')); // every 60 seconds
     protected $load_images_timeout;
     public function __construct() {
         parent::__construct();
         $this->set_icon($this->render_icon(Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG));                                   $vbox = new GtkVBox();
         $this->set_size_request(300, 500);                                                                              $this->add($vbox);
         $this->set_title('PHP-GTK Twitter Client');
         $this->connect_simple('destroy', array('Gtk', 'main_quit'));                                                    $vbox->pack_start($tb, false, false);
                                                                                                                         $scrolled = new GtkScrolledWindow();
           $this->statusicon = new Php_Gtk_Twitter_Icon;                                                                 $scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
           $this->statusicon->connect('activate', array($this->statusicon,
                 'activate_window'), $this);                                                                             $vbox->pack_start($scrolled);
           $this->set_skip_taskbar_hint(true);                                                                           $this->treeview = new GtkTreeView($store);
           $this->connect('window-state-event', array($this, 'minimize_to_tray'));
                                                                                                                         $scrolled->add($this->treeview);
           $this->temp = sys_get_temp_dir() . 'php-gtk-twitter-api-cache';                                             $this->treeview->set_property('headers-visible', false);
           if (!file_exists($this->temp)) {
                 mkdir($this->temp, null, true);                                                                         $this->treeview->set_rules_hint(true);
           }                                                                                                             $vbox->pack_start(new GtkLabel('What are you doing?'), false, false);
           $this->twitter = new Php_Gtk_Twitter_Api;                                                                     $vbox->pack_start($this->updateentry, false, false);
           $this->statusbar = new GtkStatusBar();                                                                        $vbox->pack_start($this->entrystatus, false, false);
           // Create a toolbar with login button                                                                         $vbox->pack_start($this->statusbar, false, false);
           $tb = new GtkToolbar();
           $tb->set_show_arrow(false);                                                                                   $picture_renderer = new GtkCellRendererPixbuf();
           $this->loginbutton = GtkToolButton::new_from_stock(Gtk::STOCK_JUMP_TO);
           $this->loginbutton->set_label('Login');                                                                       $picture_column = new GtkTreeViewColumn('Picture',
           $this->loginbutton->connect_simple('clicked', array($this, 'login'));                                              $picture_renderer, 'pixbuf', 0);
           $tb->insert($this->loginbutton, -1);
                                                                                                                         $picture_column->set_cell_data_func($picture_renderer,
           // logout button, hide it                                                                                        array($this, 'show_user'));
           $this->logoutbutton = GtkToolButton::new_from_stock(Gtk::STOCK_CLOSE);
           $this->logoutbutton->set_label('Logout');                                                                     $this->treeview->append_column($picture_column);
           $this->logoutbutton->connect_simple('clicked', array($this, 'logout'));
           $tb->insert($this->logoutbutton, -1);
           $this->logoutbutton->set_sensitive(false);                                                                    $message_renderer = new GtkCellRendererText();
                                                                                                                         $message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD);
           // Create an update area                                                                                      $message_renderer->set_property('wrap-width', 200);
           $this->updateentry = new GtkEntry();
           $this->updateentry->set_max_length(140);                                                                      $message_renderer->set_property('width', 10);
           $this->updateentry->set_sensitive(false);
           $this->updateentry->connect('activate', array($this, 'send_update'));
           $this->entrystatus = new GtkLabel();                                                                          $message_column = new GtkTreeViewColumn('Message',
                                                                                                                           $message_renderer);
           // User image pixbuf, user image string, user name, user id, text, favorited, created_at, id
           $store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING, Gobject::TYPE_STRING,                       $message_column->set_cell_data_func($message_renderer,
                 Gobject::TYPE_LONG, GObject::TYPE_STRING, GObject::TYPE_BOOLEAN                                            array($this, 'message_markup'));
              , GObject::TYPE_STRING,                                                                                    $this->treeview->append_column($message_column);
                 Gobject::TYPE_LONG);
           $store->set_sort_column_id(7, Gtk::SORT_DESCENDING);
                                                                                                                         $this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE);
           $list = $this->twitter->get_public_timeline();
           $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') . ' - ' . count($list) . ' new tweets');   }
COMPLETED CODE
Final Code, all 492 lines
public function show_user($column, $cell, $store, $position) {                protected function distance($from) {
            $pic = $store->get_value($position, 1);                                     $minutes = round(abs(time() - strtotime($from)) / 60);
            $name = $this->temp . md5($pic);
            if (isset($this->pic_queue[$name])) {                                       switch(true) {
                   return;                                                                     case ($minutes == 0):
            } elseif (isset($this->pic_cached[$name])) {                                              return 'less than 1 minute ago';
                   $store = $this->treeview->get_model();                                      case ($minutes < 1):
                   if (is_null($store->get_value($position, 0))) {
                          $pixbuf = GdkPixbuf::new_from_file($name . '.jpg');                         return '1 minute ago';
                          $store->set($position, 0, $pixbuf);                                  case ($minutes <= 55):
                          $cell->set_property('pixbuf', $pixbuf);                                     return $minutes . ' minutes ago';
                   }                                                                           case ($minutes <= 65):
                   return;                                                                            return 'about 1 hour ago';
            }                                                                                  case ($minutes <= 1439):
            $this->pic_queue[$name] = array('name' => $name, 'url' => $pic,                           return 'about ' . round((float) $minutes / 60.0) . ' hours';
                   'pos' => $position, 'cell' => $cell);                                       case ($minutes <= 2879):
            if (empty($this->load_images_timeout)) {
                   $this->load_images_timeout = Gtk::timeout_add(500,                                 return '1 day ago';
                                                                                               default:
                    array($this, 'pic_queue'));
            }                                                                                         return 'about ' . round((float) $minutes / 1440) . ' days ago';
      }                                                                                 }
                                                                                  }
      public function pic_queue() {
            $pic = array_shift($this->pic_queue);                                 public function minimize_to_tray($window, $event) {
            if (empty($pic)) {                                                          if ($event->changed_mask == Gdk::WINDOW_STATE_ICONIFIED &&
                   $this->load_images_timeout = null;                                          $event->new_window_state & Gdk::WINDOW_STATE_ICONIFIED) {
                   return true;                                                                $window->hide();
            }
            if (!file_exists($pic['name'])) {                                           }
                   file_put_contents($pic['name'] . '.jpg',                             return true; //stop bubbling
                    file_get_contents($pic['url']));                              }
                   $this->pic_cached[$pic['name']] = $pic['url'];
            }                                                                     public function update_public_timeline() {
            return true; // keep the timeout going                                      $this->pic_queue = array();
      }                                                                                 $list = $this->twitter->get_public_timeline();
                                                                                        $this->statusbar->pop(1);
      public function message_markup($column, $cell, $store,                            $this->statusbar->push(1, 'last updated ' .
          $position) {                                                                     date('Y-m-d H:i') . ' - ' . count($list) . ' new tweets');
            $user = utf8_decode($store->get_value($position, 2));                       $store = $this->treeview->get_model();
            $message = utf8_decode($store->get_value($position, 4));                    $store->clear();
            $time = $this->distance($store->get_value($position, 6));
                                                                                        foreach($list as $object) {
            $message = htmlspecialchars_decode($message, ENT_QUOTES);                          $store->append(array(null, $object->user->profile_image_url,
            $message = str_replace(array('@' . $user, '&nbsp;', '&'), array(                        $object->user->name, $object->user->id, $object->text,
              '<span foreground="#FF6633">@'                                                       $object->favorited, $object->created_at,
                . $user . '</span>', ' ', '&amp;'), $message);                                        $object->id));
            $cell->set_property('markup',                                               }
                "<b>$user</b>:n$messagen<small>$time</small>");                       return true;
      }                                                                           }
COMPLETED CODE
 Final Code, all 492 lines
public function update_timeline() {                                                 public function logout() {
            $list = $this->twitter->get_timeline();
            $this->statusbar->pop(1);                                                      $this->twitter->logout();
            $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') . ' - '          $this->logoutbutton->set_sensitive(false);
                . count($list) . ' new tweets');                                           $this->loginbutton->set_sensitive(true);
            $store = $this->treeview->get_model();
            foreach($list as $object) {                                                    $this->public_timeline_timeout = Gtk::timeout_add(61000, array($this,
                    $store->append(array(null, $object->user->profile_image_url,              'update_public_timeline')); // every 60 seconds
                         $object->user->name, $object->user->id, $object->text,            $this->pic_queue = array();
                         $object->favorited, $object->created_at,                          $this->pic_cached = array();
                          $object->id));
            }                                                                              $this->update_public_timeline();
            return true;                                                                   $this->updateentry->set_sensitive(false);
      }                                                                                }
     public function login() {
         if (!empty($this->load_images_timeout)) {                                     public function send_update($entry) {
               Gtk::timeout_remove($this->load_images_timeout);
               $readd = true;                                                                    if ($this->twitter->send($entry->get_text())) {
         }                                                                                              $this->entrystatus->set_text('Message Sent');
         Gtk::timeout_remove($this->public_timeline_timeout);                                           $this->update_timeline();
         $login = new Php_Gtk_Twitter_Login_Dialog($this);
         while($response = $login->run()) {                                                             $this->updateentry->set_text('');
               if ($response == GTK::RESPONSE_CANCEL ||                                          } else {
                     $response == GTK::RESPONSE_DELETE_EVENT) {                                         $this->entrystatus->set_markup('<span color="red">
                      if (isset($readd)) {
                            $this->load_images_timeout = Gtk::timeout_add(500,                           Error Sending Message - Try Again</span>');
                                 array($this, 'pic_queue'));                                     }
                      }                                                                    }
                      $this->public_timeline_timeout = Gtk::timeout_add(61000,
                         array($this, 'update_public_timeline')); // every 60 seconds
                      $login->destroy();                                                   public function __destruct() {
                      break;                                                                     foreach(scandir($this->temp) as $filename) {
               } elseif ($response == GTK::RESPONSE_OK) {                                               if ($filename[0] == '.')
                      if($login->check_login($this->twitter)) {
                            $this->logoutbutton->set_sensitive(true);                                   continue;
                            $this->loginbutton->set_sensitive(false);                                   if (file_exists($this->temp . $filename)) {
                            $login->destroy();
                            $this->public_timeline_timeout = Gtk::timeout_add(61000,                          unlink($this->temp . $filename);
                              array($this, 'update_timeline')); // every 60 seconds                     }
                            $this->load_images_timeout = Gtk::timeout_add(500,                   }
                            array($this, 'pic_queue'));                                    }
                            $this->treeview->get_model()->clear();
                            $this->pic_queue = array();                               }
                            $this->pic_cached = array();                              $window = new Php_Gtk_Twitter_Client;
                            $this->update_timeline();                                 $window->show_all();
                            $this->updateentry->set_sensitive(true);
                            break;                                                    Gtk::main();
                      }
               }
         }
     }
RESOURCES
Wrapping it up

• Slides
      • http://callicore.net/php-gtk/php-on-the-desktop.pdf
• Code
      • http://callicore.net/php-gtk/php-gtk-twitter.zip
• GTK docs
      • http://gtk.org
      • http://pygtk.org
      • http://gtk.php.net/docs.php
      • http://leonpegg.com/php-gtk-doc/phpweb/
      • http://kksou.com
      • http://oops.opsat.net
      • http://php-gtk.eu
• Me
      • http://elizabethmariesmith.com
      • http://callicore.net
      • http://callicore.net/php-gtk
      • auroraeosrose@php.net
                                       THANKS

Más contenido relacionado

La actualidad más candente

Understanding PHP objects
Understanding PHP objectsUnderstanding PHP objects
Understanding PHP objectsjulien pauli
 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128PrinceGuru MS
 
Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Adam Tomat
 
Auto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK NodesAuto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK Nodesnihiliad
 
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015Fernando Hamasaki de Amorim
 
Zephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensionsZephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensionsMark Baker
 
Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)julien pauli
 
MIND sweeping introduction to PHP
MIND sweeping introduction to PHPMIND sweeping introduction to PHP
MIND sweeping introduction to PHPBUDNET
 
Puppet for Sys Admins
Puppet for Sys AdminsPuppet for Sys Admins
Puppet for Sys AdminsPuppet
 
Aura Project for PHP
Aura Project for PHPAura Project for PHP
Aura Project for PHPHari K T
 
Doctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPDoctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPGuilherme Blanco
 
Puppet Camp Berlin 2014: Manageable puppet infrastructure
Puppet Camp Berlin 2014: Manageable puppet infrastructurePuppet Camp Berlin 2014: Manageable puppet infrastructure
Puppet Camp Berlin 2014: Manageable puppet infrastructurePuppet
 

La actualidad más candente (20)

Php on Windows
Php on WindowsPhp on Windows
Php on Windows
 
Understanding PHP objects
Understanding PHP objectsUnderstanding PHP objects
Understanding PHP objects
 
Ant
Ant Ant
Ant
 
Php tips-and-tricks4128
Php tips-and-tricks4128Php tips-and-tricks4128
Php tips-and-tricks4128
 
Anatomy of a reusable module
Anatomy of a reusable moduleAnatomy of a reusable module
Anatomy of a reusable module
 
Php security3895
Php security3895Php security3895
Php security3895
 
Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018Supercharging WordPress Development in 2018
Supercharging WordPress Development in 2018
 
Auto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK NodesAuto-loading of Drupal CCK Nodes
Auto-loading of Drupal CCK Nodes
 
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
The worst Ruby codes I’ve seen in my life - RubyKaigi 2015
 
Puppet @ Seat
Puppet @ SeatPuppet @ Seat
Puppet @ Seat
 
Apache Velocity 1.6
Apache Velocity 1.6Apache Velocity 1.6
Apache Velocity 1.6
 
Zephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensionsZephir - A Wind of Change for writing PHP extensions
Zephir - A Wind of Change for writing PHP extensions
 
Power of Puppet 4
Power of Puppet 4Power of Puppet 4
Power of Puppet 4
 
Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)Php in 2013 (Web-5 2013 conference)
Php in 2013 (Web-5 2013 conference)
 
MIND sweeping introduction to PHP
MIND sweeping introduction to PHPMIND sweeping introduction to PHP
MIND sweeping introduction to PHP
 
Puppet for Sys Admins
Puppet for Sys AdminsPuppet for Sys Admins
Puppet for Sys Admins
 
Aura Project for PHP
Aura Project for PHPAura Project for PHP
Aura Project for PHP
 
Doctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHPDoctrine 2.0 Enterprise Persistence Layer for PHP
Doctrine 2.0 Enterprise Persistence Layer for PHP
 
Puppet Camp Berlin 2014: Manageable puppet infrastructure
Puppet Camp Berlin 2014: Manageable puppet infrastructurePuppet Camp Berlin 2014: Manageable puppet infrastructure
Puppet Camp Berlin 2014: Manageable puppet infrastructure
 
Introduction to Perl
Introduction to PerlIntroduction to Perl
Introduction to Perl
 

Similar a Php on the desktop and php gtk2

関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐいHisateru Tanaka
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applicationschartjes
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For BeginnersJonathan Wage
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)arcware
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy CodeRowan Merewood
 
Charla EHU Noviembre 2014 - Desarrollo Web
Charla EHU Noviembre 2014 - Desarrollo WebCharla EHU Noviembre 2014 - Desarrollo Web
Charla EHU Noviembre 2014 - Desarrollo WebMikel Torres Ugarte
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormMichelangelo van Dam
 
Drupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionDrupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionPhilip Norton
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8Alexei Gorobets
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmersAlexander Varwijk
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ EtsyNishan Subedi
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Michelangelo van Dam
 

Similar a Php on the desktop and php gtk2 (20)

関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい関西PHP勉強会 php5.4つまみぐい
関西PHP勉強会 php5.4つまみぐい
 
Drupal 8 migrate!
Drupal 8 migrate!Drupal 8 migrate!
Drupal 8 migrate!
 
Building Testable PHP Applications
Building Testable PHP ApplicationsBuilding Testable PHP Applications
Building Testable PHP Applications
 
Unittests für Dummies
Unittests für DummiesUnittests für Dummies
Unittests für Dummies
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Doctrine and NoSQL
Doctrine and NoSQLDoctrine and NoSQL
Doctrine and NoSQL
 
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
10 Things Every Plugin Developer Should Know (WordCamp Atlanta 2013)
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
 
Charla EHU Noviembre 2014 - Desarrollo Web
Charla EHU Noviembre 2014 - Desarrollo WebCharla EHU Noviembre 2014 - Desarrollo Web
Charla EHU Noviembre 2014 - Desarrollo Web
 
Quality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStormQuality assurance for php projects with PHPStorm
Quality assurance for php projects with PHPStorm
 
Fatc
FatcFatc
Fatc
 
Hardcore PHP
Hardcore PHPHardcore PHP
Hardcore PHP
 
Drupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency InjectionDrupal 8 Services And Dependency Injection
Drupal 8 Services And Dependency Injection
 
Dependency injection in Drupal 8
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
 
Doctrine for NoSQL
Doctrine for NoSQLDoctrine for NoSQL
Doctrine for NoSQL
 
Multilingualism makes better programmers
Multilingualism makes better programmersMultilingualism makes better programmers
Multilingualism makes better programmers
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
Pecl Picks
Pecl PicksPecl Picks
Pecl Picks
 
Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010Advanced Php - Macq Electronique 2010
Advanced Php - Macq Electronique 2010
 
Separation of concerns - DPC12
Separation of concerns - DPC12Separation of concerns - DPC12
Separation of concerns - DPC12
 

Más de Elizabeth Smith

Más de Elizabeth Smith (20)

Welcome to the internet
Welcome to the internetWelcome to the internet
Welcome to the internet
 
Database theory and modeling
Database theory and modelingDatabase theory and modeling
Database theory and modeling
 
Taming the resource tiger
Taming the resource tigerTaming the resource tiger
Taming the resource tiger
 
Modern sql
Modern sqlModern sql
Modern sql
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Taming the resource tiger
Taming the resource tigerTaming the resource tiger
Taming the resource tiger
 
Php internal architecture
Php internal architecturePhp internal architecture
Php internal architecture
 
Taming the tiger - pnwphp
Taming the tiger - pnwphpTaming the tiger - pnwphp
Taming the tiger - pnwphp
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Php extensions
Php extensionsPhp extensions
Php extensions
 
Php’s guts
Php’s gutsPhp’s guts
Php’s guts
 
Lexing and parsing
Lexing and parsingLexing and parsing
Lexing and parsing
 
Hacking with hhvm
Hacking with hhvmHacking with hhvm
Hacking with hhvm
 
Security is not a feature
Security is not a featureSecurity is not a feature
Security is not a feature
 
Using unicode with php
Using unicode with phpUsing unicode with php
Using unicode with php
 
Mentoring developers-php benelux-2014
Mentoring developers-php benelux-2014Mentoring developers-php benelux-2014
Mentoring developers-php benelux-2014
 
Using unicode with php
Using unicode with phpUsing unicode with php
Using unicode with php
 
Socket programming with php
Socket programming with phpSocket programming with php
Socket programming with php
 
Mentoring developers
Mentoring developersMentoring developers
Mentoring developers
 
Do the mentor thing
Do the mentor thingDo the mentor thing
Do the mentor thing
 

Último

Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Drew Madelung
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processorsdebabhi2
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Servicegiselly40
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking MenDelhi Call girls
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024The Digital Insurer
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘RTylerCroy
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?Igalia
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdfhans926745
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024Rafal Los
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxMalak Abu Hammad
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsMaria Levchenko
 

Último (20)

Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
CNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of ServiceCNv6 Instructor Chapter 6 Quality of Service
CNv6 Instructor Chapter 6 Quality of Service
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
08448380779 Call Girls In Diplomatic Enclave Women Seeking Men
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
🐬 The future of MySQL is Postgres 🐘
🐬  The future of MySQL is Postgres   🐘🐬  The future of MySQL is Postgres   🐘
🐬 The future of MySQL is Postgres 🐘
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
The Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptxThe Codex of Business Writing Software for Real-World Solutions 2.pptx
The Codex of Business Writing Software for Real-World Solutions 2.pptx
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 

Php on the desktop and php gtk2

  • 1. PHP ON THE DESKTOP AND PHP-GTK2 Elizabeth Marie Smith – Zend Uncon 2008
  • 2. ABOUT ME Elizabeth Marie Smith • AKA auroraeosrose • Have a very very common name • Was an English Major (minor in Sociology) • Started using PHP for a Manga/Anime fan site (still alive) • Have been using PHP since 4 beta 1 (23-Jul-1999) • Have been using C since Nov of 2006 • Love the color Pink • I hate computers • I love programming • Thinks open source on Windows is a “gateway drug” • Works at OmniTI (http://omniti.com) • Am a contributor on various open source projects (including PHP, PECL and PHP-GTK)
  • 4. THE DESKTOP STILL LIVES Reasons For Desktop Applications • People aren’t always online • Some applications would be bandwidth prohibitive • Some data is too sensitive to be passed on the wire • Embedded OSs don’t always work well with websites • Browsers are just Quirky (I’m looking at you, IE6, IE7, Opera…) • Current Desktop RIA tools (AIR, Xulrunner, Mozilla Prism) are still young (and buggy)
  • 5. WHY USE PHP FOR DESKTOP APPLICATIONS PHP Advantages • No Compiling • Instant Changes • No learning curve for a new language • Plug into PHP extensions • Easy to Use • Fast to Write • Use already familiar libraries and tools
  • 6. WHY USE PHP FOR DESKTOP APPLICATIONS PHP Disadvantages • Speed • Additional Extensions/Libraries needed • Speed • Distribution (phar is helpful there) • Speed • Security (of code – source code encoders might help here) • No threading See the theme?
  • 7. HOW DO I WRITE DESKTOP APPS? PHP CLI • PHP does not care about HTML (really) • PHP works through SAPIS (Server Application Programming Interface) – for the desktop the CLI (Command Line Interface) SAPI is our friend • CLI has no headers, CLI responds to no requests • You need a way to make GUI’s if you don’t want a console app – Just like we wrap other C/C++ libraries we can wrap GUI libraries
  • 8. GUI OPTIONS WITH PHP So What are your Options? • Well Established • PHP-GTK2 • Winbinder • Works, but not Complete • PHP-QT (no windows support) • WxWidgets (only windows support) • Win::Gui (part of Win::API) Bottom line? For Cross-Platform PHP-GTK works
  • 9. WTH IS GTK? Quick Intro • GTK was Gimp Tool Kit (long long ago) • GTK is GUI Toolkit for Gnome (if you use gnome, you have it already) • GTK has multiple backends (for native Windows and MacOSX apps – you don’t need X on Mac anymore) • To use PHP-GTK you need PHP CLI, GTK, and the PHP-GTK extension • http://oops.opsat.net/doc/install.html - how to install (windows is unzip and run, mac has an installer, ubuntu has .debs – yes this should be in the php-gtk docs, we need volunteers who aren’t afraid of xml)
  • 11. THE BASICS What you need to know to use PHP-GTK • Main Loop • Signals & Callbacks • Widgets • OOP to the Max • Visibility
  • 12. STARTING OUR PROJECT Create Our initial Window Resulting window <?php class Php_Gtk_Twitter_Client extends GtkWindow { public function __construct() { parent::__construct(); $this->set_icon($this->render_icon( Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG)); $this->set_size_request(300, 500); $this->set_title('PHP-GTK Twitter Client'); $this->connect_simple('destroy', array('Gtk', 'main_quit')); } } $window = new Php_Gtk_Twitter_Client; $window->show_all(); Gtk::main();
  • 13. OVERRIDING BASIC FUNCTIONALITY Minimize to the Taskbar • Specialty Widgets are Cool • GtkStatusIcon – 2.10+ • Know your Hierarchy • Stock Icons • Callback “bubbling” • timeouts (timers)
  • 15. Minimize to Status Icon MINIMIZE TO TRAY <?php Resulting Window class Php_Gtk_Twitter_Icon extends GtkStatusIcon { protected $alive; protected $lockout; public function __construct() { parent::__construct(); $this->set_from_stock(Gtk::STOCK_ABOUT); $this->set_tooltip('PHP-GTK Twitter Client'); while(Gtk::events_pending() || Gdk::events_pending()) { Gtk::main_iteration_do(true); } $this->is_ready(); return; } public function is_ready() { $this->alive = true; if($this->lockout < 5 && !$this->is_embedded()) { Gtk::timeout_add(750,array($this,'is_ready')); ++$this->lockout; $this->alive = false; } else if(!$this->is_embedded()) { die("Error: Unable to create Tray Icon. Please insure that your system's tray is enabled.n"); $this->alive = false; } return; } public function activate_window($icon, $window) { if ($window->is_visible()) { $window->hide(); } else { $window->deiconify(); $window->show(); } } } class Php_Gtk_Twitter_Client extends GtkWindow { protected $statusicon; public function __construct() { parent::__construct(); $this->set_icon($this->render_icon(Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG)); $this->set_size_request(300, 500); $this->set_title('PHP-GTK Twitter Client'); $this->connect_simple('destroy', array('Gtk', 'main_quit')); $this->statusicon = new Php_Gtk_Twitter_Icon; $this->statusicon->connect('activate', array($this->statusicon, 'activate_window'), $this); $this->set_skip_taskbar_hint(true); $this->connect('window-state-event', array($this, 'minimize_to_tray')); } public function minimize_to_tray($window, $event) { if ($event->changed_mask == Gdk::WINDOW_STATE_ICONIFIED && $event->new_window_state & Gdk::WINDOW_STATE_ICONIFIED) { $window->hide(); } return true; //stop bubbling } } $window = new Php_Gtk_Twitter_Client; $window->show_all(); Gtk::main();
  • 16. VIEWING DATA Data from PHP + a Treeview from GTK • Remember this is PHP • Treeview has a model and view • ListStore is a single level model • Treeviews need Columns • Columns need Renderers • Different types of Renderers
  • 17. MY TWITTER API HTTP Streams are GOOD I really don’t believe in wheel inventing , especially when I have little experience with the subject. However there is an issue – every existing twitter API uses curl – and I don’t want to depend on an extension that isn’t always installed. protected function process($url, $date = 0, $type = 'GET', $data = null) { // add caching header $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date); $options = array( 'http' => array( 'method' => $type, 'header' => $this->headers) ); if (!is_null($data)) { $options['http']['content'] = http_build_query($data); } $context = stream_context_create($options); if ($this->username && $this->password) { $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password) . '@twitter.com/'; } else { $base = 'http://twitter.com/'; } set_error_handler(array($this,'swallow_error')); $string = file_get_contents($base . $url, false, $context); restore_error_handler(); return json_decode($string); }
  • 18. MINIMIZE TO TRAY Put a Treeview in the Window – this goes in __construct Callbacks for showing Images and Messages $this->temp = sys_get_temp_dir() . 'php-gtk-twitter-api-cache'; public function show_user($column, $cell, $store, $position) { if (!file_exists($this->temp)) { $pic = $store->get_value($position, 1); $name = $this->temp . md5($pic); mkdir($this->temp, null, true); if (isset($this->pic_queue[$name])) { } return; } elseif (isset($this->pic_cached[$name])) { $store = $this->treeview->get_model(); $this->twitter = new Php_Gtk_Twitter_Api; if (is_null($store->get_value($position, 0))) { $pixbuf = GdkPixbuf::new_from_file($name . '.jpg'); // User image pixbuf, user image string, user name, $store->set($position, 0, $pixbuf); $cell->set_property('pixbuf', $pixbuf); // user id, text, favorited, created_at, id } $store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING, return; } Gobject::TYPE_STRING, Gobject::TYPE_LONG, GObject::TYPE_STRING, $this->pic_queue[$name] = array('name' => $name, 'url' => $pic, GObject::TYPE_BOOLEAN, GObject::TYPE_STRING, Gobject::TYPE_LONG); 'pos' => $position, 'cell' => $cell); if (empty($this->load_images_timeout)) { $store->set_sort_column_id(7, Gtk::SORT_DESCENDING); $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue')); } $list = $this->twitter->get_public_timeline(); } public function pic_queue() { // stuff the store $pic = array_shift($this->pic_queue); foreach($list as $object) { if (empty($pic)) { $store->append(array(null, $object->user->profile_image_url, $object->user->name, $this->load_images_timeout = null; return true; $object->user->id, $object->text, $object->favorited, $object->created_at, } $object->id)); if (!file_exists($pic['name'])) { } file_put_contents($pic['name'] . '.jpg', file_get_contents($pic['url'])); $this->pic_cached[$pic['name']] = $pic['url']; } $this->public_timeline_timeout = Gtk::timeout_add(61000, return true; // keep the timeout going array($this, 'update_public_timeline')); // every 60 seconds } public function message_markup($column, $cell, $store, $position) { $scrolled = new GtkScrolledWindow(); $user = utf8_decode($store->get_value($position, 2)); $message = utf8_decode($store->get_value($position, 4)); $scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS); $time = $this->distance($store->get_value($position, 6)); $this->add($scrolled); $this->treeview = new GtkTreeView($store); $message = htmlspecialchars_decode($message, ENT_QUOTES); $scrolled->add($this->treeview); $message = str_replace(array('@' . $user, '&nbsp;', '&'), array('<span foreground="#FF6633">@' . $this->treeview->set_property('headers-visible', false); $user . '</span>', ' ', '&amp;'), $message); $cell->set_property('markup', "<b>$user</b>:n$messagen<small>$time</small>"); $this->treeview->set_rules_hint(true); } $picture_renderer = new GtkCellRendererPixbuf(); protected function distance($from) { $minutes = round(abs(time() - strtotime($from)) / 60); $picture_column = new GtkTreeViewColumn('Picture', $picture_renderer, 'pixbuf', 0); $picture_column->set_cell_data_func($picture_renderer, array($this, 'show_user')); switch(true) { $this->treeview->append_column($picture_column); case ($minutes == 0): return 'less than 1 minute ago'; case ($minutes < 1): $message_renderer = new GtkCellRendererText(); return '1 minute ago'; $message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD); case ($minutes <= 55): return $minutes . ' minutes ago'; $message_renderer->set_property('wrap-width', 200); case ($minutes <= 65): $message_renderer->set_property('width', 10); return 'about 1 hour ago'; case ($minutes <= 1439): $message_column = new GtkTreeViewColumn('Message', $message_renderer); return 'about ' . round((float) $minutes / 60.0) . ' hours'; case ($minutes <= 2879): $message_column->set_cell_data_func($message_renderer, return '1 day ago'; array($this, 'message_markup')); default: return 'about ' . round((float) $minutes / 1440) . ' days ago'; $this->treeview->append_column($message_column); } } $this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE);
  • 19. A WORKING VIEW OF TWITTER MESSAGES It works! Public Timeline in Action
  • 20. PACKING Putting other things in a Window • GTK is not “pixel perfect” • Packing is not as hard as it looks • Containers can hold one or many • Remember that a container expands to the size of it’s contents
  • 21. TOOLBARS AND STATUSBARS Pack the Statusbar, Treeview, and Toolbar in a VBox Create Statusbar and Toolbar $this->statusbar = new GtkStatusBar(); // Create a toolbar with login button $tb = new GtkToolbar(); $tb->set_show_arrow(false); $this->loginbutton = GtkToolButton::new_from_stock(Gtk::STOCK_JUMP_TO); $this->loginbutton->set_label('Login'); $this->loginbutton->connect_simple('clicked', array($this, 'login')); $tb->insert($this->loginbutton, -1); // logout button $this->logoutbutton = GtkToolButton::new_from_stock(Gtk::STOCK_CLOSE); $this->logoutbutton->set_label('Logout'); $this->logoutbutton->connect_simple('clicked', array($this, 'logout')); $tb->insert($this->logoutbutton, -1); $this->logoutbutton->set_sensitive(false); With Toolbar, Treeview, and Statusbar $vbox = new GtkVBox(); $this->add($vbox); $vbox->pack_start($tb, false, false); $vbox->pack_start($scrolled); $vbox->pack_start($this->statusbar, false, false); Update the Header and Public Timeline using a Gtk::timeout public function update_public_timeline() { $this->pic_queue = array(); $list = $this->twitter->get_public_timeline(); $this->statusbar->pop(1); $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') . ' ' . count($list) . ' new tweets'); $store = $this->treeview->get_model(); $store->clear(); foreach($list as $object) { $store->append(array(null, $object->user->profile_image_url, $object->user->name, $object->user->id, $object->text, $object->favorited, $object->created_at, $object->id)); } return true; }
  • 22. DIALOGS Additional Windows • Dialogs are cool • Dialogs are usually modal, but not always • Dialogs can be very general or very specific • You can put anything inside one
  • 23. LOGIN DIALOG BOX Extends GtkDialog Login Dialog class Php_Gtk_Twitter_Login_Dialog extends GtkDialog { protected $emailentry; protected $passwordentry; public function __construct($parent) { parent::__construct('Login to Twitter', $parent, Gtk::DIALOG_MODAL, array( Gtk::STOCK_OK, Gtk::RESPONSE_OK, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL)); $table = new GtkTable(); $email = new GtkLabel('Email:'); $table->attach($email, 0, 1, 0, 1); $password = new GtkLabel('Password:'); $table->attach($password, 0, 1, 1, 2); $this->emailentry = new GtkEntry(); $table->attach($this->emailentry, 1, 2, 0, 1); $this->passwordentry = new GtkEntry(); $table->attach($this->passwordentry, 1, 2, 1, 2); $this->passwordentry->set_visibility(false); $this->vbox->add($table); $this->errorlabel = new GtkLabel(); $this->vbox->add($this->errorlabel); $this->show_all(); } public function check_login($twitter) { $this->errorlabel->set_text(''); $email = $this->emailentry->get_text(); $password = $this->passwordentry->get_text(); if (empty($password) || empty($password)) { $this->errorlabel->set_markup( '<span color="red">Name and Password must be entered</span>'); return false; } if ($twitter->login($email, $password)) { return true; } else { $this->errorlabel->set_markup( '<span color="red">Authentication Error</span>'); return false; } } }
  • 24. LOGIN DIALOG BOX Callbacks After Login public function login() { if (!empty($this->load_images_timeout)) { Gtk::timeout_remove($this->load_images_timeout); $readd = true; } Gtk::timeout_remove($this->public_timeline_timeout); $login = new Php_Gtk_Twitter_Login_Dialog($this); while($response = $login->run()) { if ($response == GTK::RESPONSE_CANCEL || $response == GTK::RESPONSE_DELETE_EVENT) { if (isset($readd)) { $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue')); } $this->public_timeline_timeout = Gtk::timeout_add(61000, array($this, 'update_public_timeline')); $login->destroy(); break; } elseif ($response == GTK::RESPONSE_OK) { if($login->check_login($this->twitter)) { $this->logoutbutton->set_sensitive(true); $this->loginbutton->set_sensitive(false); $login->destroy(); $this->public_timeline_timeout = Gtk::timeout_add( 61000, array($this, 'update_timeline')); $this->load_images_timeout = Gtk::timeout_add(500, array($this, 'pic_queue')); $this->treeview->get_model()->clear(); $this->pic_queue = array(); $this->pic_cached = array(); $this->update_timeline(); break; } } } } public function logout() { $this->twitter->logout(); $this->logoutbutton->set_sensitive(false); $this->loginbutton->set_sensitive(true); $this->public_timeline_timeout = Gtk::timeout_add(61000, array($this, 'update_public_timeline')); // every 60 seconds $this->pic_queue = array(); $this->pic_cached = array(); $this->update_public_timeline(); }
  • 25. DATA ENTRY Putting in Data • GTKEntry • Basic Data Entry – activates on return, can set maximum length allowed • Simple label for messages – could use a dialog or other method of informing the user
  • 26. LOGIN DIALOG BOX Packing it in Creating the Entries $vbox = new GtkVBox(); // Create an update area $this->add($vbox); $this->updateentry = new GtkEntry(); $vbox->pack_start($tb, false, false); $this->updateentry->set_max_length(140); $scrolled = new GtkScrolledWindow(); $this->updateentry->set_sensitive(false); $scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS); $this->updateentry->connect('activate', $vbox->pack_start($scrolled); array($this, 'send_update')); $vbox->pack_start(new GtkLabel('What are you doing?'), false, false); $this->entrystatus = new GtkLabel(); $vbox->pack_start($this->updateentry, false, false); $vbox->pack_start($this->entrystatus, false, false); $vbox->pack_start($this->statusbar, false, false); The Callback on Activate public function send_update($entry) { if ($this->twitter->send($entry->get_text())) { $this->entrystatus->set_text('Message Sent'); $this->update_timeline(); $this->updateentry->set_text(''); } else { $this->entrystatus->set_markup(‘ <span color="red">Error Sending Message - Try Again</span>'); } }
  • 28. COMPLETED CODE Final Code, all 492 lines class Php_Gtk_Twitter_Api { public function get_timeline() { protected $login = false; if ($this->login && $this->can_call()) { protected $username; if (empty($this->lastid)) { protected $password; $data = $this->process('statuses/friends_timeline.json'); } else { $data = $this->process('statuses/friends_timeline.json', $this->lasttime, protected $cached_public; 'GET', array('since_id' => $this->lastid)); } protected $cached_public_timestamp = 0; if ($data) { $this->lastid = $data[0]->id; } protected $lastid = 0; $this->lasttime = time(); return $data; protected $headers = array } } ('', 'X-Twitter-Client: PHP-GTK Twitter Client', public function send($message) { 'X-Twitter-Client-Version: 0.1.0-dev', if ($this->login && $this->can_call()) { 'X-Twitter-Client-URL: http://elizabethmariesmith.com'); $data = $this->process('statuses/update.json', 0, 'POST', array('status' => $message)); if ($data) { return true; public function login($username, $password) { } $this->username = $username; return false; } $this->password = $password; } $worked = $this->process('account/verify_credentials.json'); protected function can_call() { if ($worked && $worked->authorized == true) { if (!$this->login) { $this->login = true; return false; } return true; $worked = $this->process('account/rate_limit_status.json'); } return ($worked->remaining_hits > 1); return false; } } protected function process($url, $date = 0, $type = 'GET', $data = null) { // add caching header $this->headers[0] = 'If-Modified-Since: ' . date(DATE_RFC822, $date); public function logout() { $this->username = null; $options = array( 'http' => array( $this->password = null; 'method' => $type, $this->login = false; 'header' => $this->headers) $this->process('account/end_session', 0, 'POST'); ); if (!is_null($data)) { } $options['http']['content'] = http_build_query($data); } $context = stream_context_create($options); public function get_public_timeline() { if ($this->username && $this->password) { if ($this->cached_public_timestamp < time()) { $base = 'http://' . urlencode($this->username) . ':' . urlencode($this->password) . '@twitter.com/'; $this->cached_public = } else { json_decode(file_get_contents( $base = 'http://twitter.com/'; } 'http://twitter.com/statuses/public_timeline.json')); set_error_handler(array($this,'swallow_error')); $this->cached_public_timestamp = time() + 60; $string = file_get_contents($base . $url, false, $context); restore_error_handler(); // caches every 60 seconds return json_decode($string); } } return $this->cached_public; public function swallow_error($errno, $errstr) {} // this should be treated as private } }
  • 29. COMPLETED CODE Final Code, all 492 lines class Php_Gtk_Twitter_Login_Dialog extends GtkDialog { class Php_Gtk_Twitter_Icon extends GtkStatusIcon { protected $emailentry; protected $passwordentry; protected $alive; protected $lockout; public function __construct($parent) { parent::__construct('Login to Twitter', public function __construct() { $parent, Gtk::DIALOG_MODAL, parent::__construct(); array(Gtk::STOCK_OK, Gtk::RESPONSE_OK, Gtk::STOCK_CANCEL, Gtk::RESPONSE_CANCEL)); $this->set_from_stock(Gtk::STOCK_ABOUT); $table = new GtkTable(); $this->set_tooltip('PHP-GTK Twitter Client'); $email = new GtkLabel('Email:'); $table->attach($email, 0, 1, 0, 1); while(Gtk::events_pending() || Gdk::events_pending()) { $password = new GtkLabel('Password:'); Gtk::main_iteration_do(true); $table->attach($password, 0, 1, 1, 2); } $this->emailentry = new GtkEntry(); $this->is_ready(); $table->attach($this->emailentry, 1, 2, 0, 1); return; $this->passwordentry = new GtkEntry(); } $table->attach($this->passwordentry, 1, 2, 1, 2); $this->passwordentry->set_visibility(false); public function is_ready() { $this->vbox->add($table); $this->alive = true; $this->errorlabel = new GtkLabel(); $this->vbox->add($this->errorlabel); if($this->lockout < 5 && !$this->is_embedded()) { $this->show_all(); Gtk::timeout_add(750,array($this,'is_ready')); } ++$this->lockout; $this->alive = false; public function check_login($twitter) { } else if(!$this->is_embedded()) { $this->errorlabel->set_text(''); die("Error: Unable to create Tray Icon. $email = $this->emailentry->get_text(); Please insure that your system's tray is enabled.n"); $password = $this->passwordentry->get_text(); $this->alive = false; if (empty($password) || empty($password)) { } $this->errorlabel->set_markup( '<span color="red">Name and Password must be entered</span>'); return; return false; } } if ($twitter->login($email, $password)) { return true; public function activate_window($icon, $window) { } else { if ($window->is_visible()) { $this->errorlabel->set_markup( $window->hide(); '<span color="red">Authentication Error</span>'); } else { return false; $window->deiconify(); } $window->show(); } } } }
  • 30. COMPLETED CODE Final Code, all 492 lines class Php_Gtk_Twitter_Client extends GtkWindow { foreach($list as $object) { $store->append(array(null, $object->user->profile_image_url, protected $statusicon; protected $twitter; $object->user->name, $object->user->id, protected $treeview; $object->text, $object->favorited, $object->created_at, protected $statusbar; $object->id)); protected $temp; } protected $pic_queue = array(); protected $pic_cached = array(); $this->public_timeline_timeout = Gtk::timeout_add(61000,, protected $public_timeline_timeout; array($this, 'update_public_timeline')); // every 60 seconds protected $load_images_timeout; public function __construct() { parent::__construct(); $this->set_icon($this->render_icon(Gtk::STOCK_ABOUT, Gtk::ICON_SIZE_DIALOG)); $vbox = new GtkVBox(); $this->set_size_request(300, 500); $this->add($vbox); $this->set_title('PHP-GTK Twitter Client'); $this->connect_simple('destroy', array('Gtk', 'main_quit')); $vbox->pack_start($tb, false, false); $scrolled = new GtkScrolledWindow(); $this->statusicon = new Php_Gtk_Twitter_Icon; $scrolled->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS); $this->statusicon->connect('activate', array($this->statusicon, 'activate_window'), $this); $vbox->pack_start($scrolled); $this->set_skip_taskbar_hint(true); $this->treeview = new GtkTreeView($store); $this->connect('window-state-event', array($this, 'minimize_to_tray')); $scrolled->add($this->treeview); $this->temp = sys_get_temp_dir() . 'php-gtk-twitter-api-cache'; $this->treeview->set_property('headers-visible', false); if (!file_exists($this->temp)) { mkdir($this->temp, null, true); $this->treeview->set_rules_hint(true); } $vbox->pack_start(new GtkLabel('What are you doing?'), false, false); $this->twitter = new Php_Gtk_Twitter_Api; $vbox->pack_start($this->updateentry, false, false); $this->statusbar = new GtkStatusBar(); $vbox->pack_start($this->entrystatus, false, false); // Create a toolbar with login button $vbox->pack_start($this->statusbar, false, false); $tb = new GtkToolbar(); $tb->set_show_arrow(false); $picture_renderer = new GtkCellRendererPixbuf(); $this->loginbutton = GtkToolButton::new_from_stock(Gtk::STOCK_JUMP_TO); $this->loginbutton->set_label('Login'); $picture_column = new GtkTreeViewColumn('Picture', $this->loginbutton->connect_simple('clicked', array($this, 'login')); $picture_renderer, 'pixbuf', 0); $tb->insert($this->loginbutton, -1); $picture_column->set_cell_data_func($picture_renderer, // logout button, hide it array($this, 'show_user')); $this->logoutbutton = GtkToolButton::new_from_stock(Gtk::STOCK_CLOSE); $this->logoutbutton->set_label('Logout'); $this->treeview->append_column($picture_column); $this->logoutbutton->connect_simple('clicked', array($this, 'logout')); $tb->insert($this->logoutbutton, -1); $this->logoutbutton->set_sensitive(false); $message_renderer = new GtkCellRendererText(); $message_renderer->set_property('wrap-mode', Gtk::WRAP_WORD); // Create an update area $message_renderer->set_property('wrap-width', 200); $this->updateentry = new GtkEntry(); $this->updateentry->set_max_length(140); $message_renderer->set_property('width', 10); $this->updateentry->set_sensitive(false); $this->updateentry->connect('activate', array($this, 'send_update')); $this->entrystatus = new GtkLabel(); $message_column = new GtkTreeViewColumn('Message', $message_renderer); // User image pixbuf, user image string, user name, user id, text, favorited, created_at, id $store = new GtkListStore(GdkPixbuf::gtype, Gobject::TYPE_STRING, Gobject::TYPE_STRING, $message_column->set_cell_data_func($message_renderer, Gobject::TYPE_LONG, GObject::TYPE_STRING, GObject::TYPE_BOOLEAN array($this, 'message_markup')); , GObject::TYPE_STRING, $this->treeview->append_column($message_column); Gobject::TYPE_LONG); $store->set_sort_column_id(7, Gtk::SORT_DESCENDING); $this->treeview->set_resize_mode(Gtk::RESIZE_IMMEDIATE); $list = $this->twitter->get_public_timeline(); $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') . ' - ' . count($list) . ' new tweets'); }
  • 31. COMPLETED CODE Final Code, all 492 lines public function show_user($column, $cell, $store, $position) { protected function distance($from) { $pic = $store->get_value($position, 1); $minutes = round(abs(time() - strtotime($from)) / 60); $name = $this->temp . md5($pic); if (isset($this->pic_queue[$name])) { switch(true) { return; case ($minutes == 0): } elseif (isset($this->pic_cached[$name])) { return 'less than 1 minute ago'; $store = $this->treeview->get_model(); case ($minutes < 1): if (is_null($store->get_value($position, 0))) { $pixbuf = GdkPixbuf::new_from_file($name . '.jpg'); return '1 minute ago'; $store->set($position, 0, $pixbuf); case ($minutes <= 55): $cell->set_property('pixbuf', $pixbuf); return $minutes . ' minutes ago'; } case ($minutes <= 65): return; return 'about 1 hour ago'; } case ($minutes <= 1439): $this->pic_queue[$name] = array('name' => $name, 'url' => $pic, return 'about ' . round((float) $minutes / 60.0) . ' hours'; 'pos' => $position, 'cell' => $cell); case ($minutes <= 2879): if (empty($this->load_images_timeout)) { $this->load_images_timeout = Gtk::timeout_add(500, return '1 day ago'; default: array($this, 'pic_queue')); } return 'about ' . round((float) $minutes / 1440) . ' days ago'; } } } public function pic_queue() { $pic = array_shift($this->pic_queue); public function minimize_to_tray($window, $event) { if (empty($pic)) { if ($event->changed_mask == Gdk::WINDOW_STATE_ICONIFIED && $this->load_images_timeout = null; $event->new_window_state & Gdk::WINDOW_STATE_ICONIFIED) { return true; $window->hide(); } if (!file_exists($pic['name'])) { } file_put_contents($pic['name'] . '.jpg', return true; //stop bubbling file_get_contents($pic['url'])); } $this->pic_cached[$pic['name']] = $pic['url']; } public function update_public_timeline() { return true; // keep the timeout going $this->pic_queue = array(); } $list = $this->twitter->get_public_timeline(); $this->statusbar->pop(1); public function message_markup($column, $cell, $store, $this->statusbar->push(1, 'last updated ' . $position) { date('Y-m-d H:i') . ' - ' . count($list) . ' new tweets'); $user = utf8_decode($store->get_value($position, 2)); $store = $this->treeview->get_model(); $message = utf8_decode($store->get_value($position, 4)); $store->clear(); $time = $this->distance($store->get_value($position, 6)); foreach($list as $object) { $message = htmlspecialchars_decode($message, ENT_QUOTES); $store->append(array(null, $object->user->profile_image_url, $message = str_replace(array('@' . $user, '&nbsp;', '&'), array( $object->user->name, $object->user->id, $object->text, '<span foreground="#FF6633">@' $object->favorited, $object->created_at, . $user . '</span>', ' ', '&amp;'), $message); $object->id)); $cell->set_property('markup', } "<b>$user</b>:n$messagen<small>$time</small>"); return true; } }
  • 32. COMPLETED CODE Final Code, all 492 lines public function update_timeline() { public function logout() { $list = $this->twitter->get_timeline(); $this->statusbar->pop(1); $this->twitter->logout(); $this->statusbar->push(1, 'last updated ' . date('Y-m-d H:i') . ' - ' $this->logoutbutton->set_sensitive(false); . count($list) . ' new tweets'); $this->loginbutton->set_sensitive(true); $store = $this->treeview->get_model(); foreach($list as $object) { $this->public_timeline_timeout = Gtk::timeout_add(61000, array($this, $store->append(array(null, $object->user->profile_image_url, 'update_public_timeline')); // every 60 seconds $object->user->name, $object->user->id, $object->text, $this->pic_queue = array(); $object->favorited, $object->created_at, $this->pic_cached = array(); $object->id)); } $this->update_public_timeline(); return true; $this->updateentry->set_sensitive(false); } } public function login() { if (!empty($this->load_images_timeout)) { public function send_update($entry) { Gtk::timeout_remove($this->load_images_timeout); $readd = true; if ($this->twitter->send($entry->get_text())) { } $this->entrystatus->set_text('Message Sent'); Gtk::timeout_remove($this->public_timeline_timeout); $this->update_timeline(); $login = new Php_Gtk_Twitter_Login_Dialog($this); while($response = $login->run()) { $this->updateentry->set_text(''); if ($response == GTK::RESPONSE_CANCEL || } else { $response == GTK::RESPONSE_DELETE_EVENT) { $this->entrystatus->set_markup('<span color="red"> if (isset($readd)) { $this->load_images_timeout = Gtk::timeout_add(500, Error Sending Message - Try Again</span>'); array($this, 'pic_queue')); } } } $this->public_timeline_timeout = Gtk::timeout_add(61000, array($this, 'update_public_timeline')); // every 60 seconds $login->destroy(); public function __destruct() { break; foreach(scandir($this->temp) as $filename) { } elseif ($response == GTK::RESPONSE_OK) { if ($filename[0] == '.') if($login->check_login($this->twitter)) { $this->logoutbutton->set_sensitive(true); continue; $this->loginbutton->set_sensitive(false); if (file_exists($this->temp . $filename)) { $login->destroy(); $this->public_timeline_timeout = Gtk::timeout_add(61000, unlink($this->temp . $filename); array($this, 'update_timeline')); // every 60 seconds } $this->load_images_timeout = Gtk::timeout_add(500, } array($this, 'pic_queue')); } $this->treeview->get_model()->clear(); $this->pic_queue = array(); } $this->pic_cached = array(); $window = new Php_Gtk_Twitter_Client; $this->update_timeline(); $window->show_all(); $this->updateentry->set_sensitive(true); break; Gtk::main(); } } } }
  • 33. RESOURCES Wrapping it up • Slides • http://callicore.net/php-gtk/php-on-the-desktop.pdf • Code • http://callicore.net/php-gtk/php-gtk-twitter.zip • GTK docs • http://gtk.org • http://pygtk.org • http://gtk.php.net/docs.php • http://leonpegg.com/php-gtk-doc/phpweb/ • http://kksou.com • http://oops.opsat.net • http://php-gtk.eu • Me • http://elizabethmariesmith.com • http://callicore.net • http://callicore.net/php-gtk • auroraeosrose@php.net THANKS

Notas del editor

  1. Tell the starting PHP story, tell the starting C story, blame it all on Andrei
  2. Ah, the big argument approaches
  3. People forget reasons why you’d want to have a desktop application
  4. Using PHP has some great advantages
  5. Speed is the big reason to pick C or something similar – stacked against ruby or python though, that’s the only advantage ;)
  6. Your computer does not care about HTML.Your shell does not care about HTML.PHP does not care about HTML.Because of all of this, you should not care about HTML.PHP and GTK does not use HTML.PHP and GTK does not build web pages.PHP and GTK does not build web applications.PHP and GTK builds computer programs.- From bob Majdak
  7. PHP-QT and Win::Gui are in active development if you aren’t worried about cross platformness – wxwidgets appears to be stalled, winbinder is stalled and doesn’t support latest php versions and is functional and is not thread safe (ouch ouch)
  8. PHP-QT and Win::Gui are in active development if you aren’t worried about cross platformness – wxwidgets appears to be stalled, winbinder is stalled and doesn’t support latest php versions and is functional and is not thread safe (ouch ouch)
  9. Tutorial Ahoy
  10. Basic concepts you need to use GTKThe main loop is like locking into a while loop and scripts run continuouslySignals are things that happen, and can have default callbacks or callbacks attached to themGtk is based around the concept of widgets – an item that is used for a specific purposePHP-GTK2 requires PHP OOP knowledgeItems are hidden on creation and can be shown and hidden recursively (using show_all and hide_all) – you can also block a specific widget from being included in an “all” call
  11. Using what we just talked aboutLet’s start with a basicgtk window – notice it’s easier to extend the base classConnect the destruction of our main window with stopping theConcepts – signals and connections, the main loop and main_quit, show allNotice if you put PHP after the main(); call it will not be executed until after the main_quit call
  12. Introduce a rather specialized widget – gtkstatusiconEverything is a gobject, hierarchy goes from there – most things are gtkwidgets for gtk itselfStock icons are cool and easy – callbacks stop bubbling when you return “true”, timeouts can be used to do something on a regular basis
  13. Quick look at stock items on windows – notice the pretty tango default themeThis is a demo from php-gtk2 in the /demos folder
  14. You can use php to do the data manipulationTreeviews show hierarchical data – the store or model decides if there are children or notThere are all kinds of renderers – some day we’ll have custom ones ;)
  15. First version of the app with the public timeline
  16. We begin with packing – do the pixel perfect and resize issue – show how packing is like packing with some hard and some soft containers
  17. Concepts: packing and toolbarsMore gtktimeout
  18. General concept of dialogs – when is a good time for modal dialogs, when is not a good time
  19. Concepts: packing and toolbarsMore gtktimeout
  20. Concepts: packing and toolbarsMore gtktimeout
  21. Add data in with a gtkentry
  22. Concepts: packing and toolbarsMore gtktimeout
  23. Here’s the finished app – then I’ll run it in action
  24. Twitter API code – just love wheel reinventing – don’t you?
  25. Dialog and Statusicon classes
  26. Humongo constructor
  27. Callbacks part 1 – treeview callbacks for formatting, the minimize override, and the public timeline updater
  28. Callbacks part 2, the login and login, update own timeline, and send – also destructor cleanup to not fill up temp and the bootstrap
  29. Places to get more information