Source for file Gtk2.php
Documentation is available at Gtk2.php
require_once 'PEAR/Frontend/Gtk2/Checks.php';
require_once 'PEAR/Config.php';
require_once 'PEAR/Frontend.php';
require_once 'PEAR/Frontend/Gtk2/About.php';
require_once 'PEAR/Frontend/Gtk2/ChannelDialog.php';
require_once 'PEAR/Frontend/Gtk2/Channels.php';
require_once 'PEAR/Frontend/Gtk2/Config.php';
require_once 'PEAR/Frontend/Gtk2/Packages.php';
require_once 'PEAR/Frontend/Gtk2/Installation.php';
require_once 'Gtk2/FileDrop.php';
* Graphical frontend for PEAR, based on PHP-Gtk2
* - Package categories aren't updated/extendet if there is an error at startup loading/packages are refreshed and a new category is required
* - Warn if the package list is older than 5 days
* - upgrade-all menu option
* - Filter by upgradeable/installed/installable/all packages
* - Channel server icons in channel.xml -> use them and cache locally
* - Config settings window
* - installation dialog showing has to be updated correctly
* - Installation: shrink window if expander is collapsed
* - Drop package file onto install button -> install it
* - Install pecl packages - error messages aren't shown
* - better installation ok icon
* - Window icon - shows the php icon on windows currently
* - When no internet connection on startup, no categories are loaded and no local packages are shown
* - When no pear cache directory exists, the check warning is shown - pear cache directory is created now
* - uninstall: pear/liveuser is required by installed package "pear/Event_Dispatcher"
* - use menu options (deps, nodeps)
* - refresh online info (clear cache or so)
* - save and load settings
* - scroll text in installation
* @author Christian Weiske <cweiske@php.net>
class PEAR_Frontend_Gtk2 extends PEAR_Frontend
* The widgets which shall be loaded from the glade
* file into the $arWidgets array
protected static $arRequestedWidgets = array (
'dlgInstaller', 'lstCategories', 'lstPackages', 'txtPackageInfo',
'cmbChannel', 'imgChannelLogo', 'lblSelectedCategory', 'hboxCategoryInfo',
'btnInstall','btnUninstall','lblBtnInstall','evboxSelectedCategory','expPackageInfo',
'mnuOptDepsNo','mnuOptDepsReq','mnuOptDepsAll','mnuOptDepNothing',
'mnuOffline','mnuQuit','mnuAbout','mnuUpdateOnline','mnuUpdateLocal',
'mnuChannels','mnuConfig','mnuInstallLocal',
'dlgProgress', 'lblDescription', 'imgProgress', 'lblProgress', 'progBar'
protected $nProgressImage = 1;
* Array with images used for the progress animation
protected $arAnimationImages = array ();
* Requested widgets are loaded from glade into this array.
* So this is an associative array with all required widgets
* from the glade file: name => widget object
* The PEAR_Frontend_Gtk2_Installation object to use
* @var PEAR_Frontend_Gtk2_Installation
protected $installer = null;
* @var PEAR_Frontend_Gtk2_ChannelDialog
protected $channelDialog = null;
protected $selectedPackage = null;
protected $strSelectedCategoryName = null;
protected $strSelectedCategoryKey = null;
* The package information class instance
* @var PEAR_Frontend_Gtk2_Packages
protected $packages = null;
* Last package file that has been installed
protected $strLastFile = null;
const CATEGORY_ALL = 12345678;
const CATEGORY_SELECTED = 12345679;
* Special categories which are added to the
protected static $arSpecialCategories = array (
PEAR_Frontend_Gtk2 ::CATEGORY_ALL => '*All packages',
// PEAR_Frontend_Gtk2::CATEGORY_SELECTED => '*Selected for install'
public function __construct ()
PEAR_Frontend_Gtk2_Config ::loadConfig ();
PEAR_Frontend_Gtk2_Config ::loadCurrentConfigIntoGui ($this);
$this->loadChannels (PEAR_Frontend_Gtk2_Config ::$strDefaultChannel);
$this->selectPackageByName (PEAR_Frontend_Gtk2_Config ::$strDefaultPackage);
}//public function __construct()
$this->config = PEAR_Config ::singleton ();
$this->packages = new PEAR_Frontend_Gtk2_Packages ($this->config);
* Fill the channel dropdown with channel names
* @param string $strDefaultChannel The channel to set active
public function loadChannels ($strDefaultChannel = null )
if ($strDefaultChannel == null ) {
$strDefaultChannel = $this->arWidgets['cmbChannel']->get_active_text ();
//After all has been initiated, load the data
PEAR_Frontend_Gtk2_Channels ::loadChannels (
$this->arWidgets['cmbChannel'],
}//public function loadChannels($strDefault = null)
public function quitApp ()
PEAR_Frontend_Gtk2_Config ::loadConfigurationFromGui ($this);
PEAR_Frontend_Gtk2_Config ::saveConfig ();
}//public function quitApp()
* load the glade file, load the widgets, connect the signals
protected function buildDialog ()
$this->glade = new GladeXML (dirname(__FILE__ ) . '/Gtk2/installer.glade');
foreach (self ::$arRequestedWidgets as $strWidgetName) {
$this->arWidgets[$strWidgetName] = $this->glade->get_widget ($strWidgetName);
$this->arWidgets['dlgInstaller']->connect_simple ('destroy', array ($this, 'quitApp'));
$strIcon = dirname (__FILE__ ) . '/Gtk2/runicon.png';
$this->arWidgets['dlgInstaller']->set_icon_from_file ($strIcon);
$this->arWidgets['lblSelectedCategory']->set_use_markup (true );
$this->arWidgets['cmbChannel']->connect ('changed', array ($this, 'selectChannel'));
$this->arWidgets['lstCategories']->set_model (
new GtkListStore (Gobject ::TYPE_STRING , Gobject ::TYPE_STRING )
$cell_renderer = new GtkCellRendererText ();
$column = new GtkTreeViewColumn ('test', $cell_renderer, "text", 0 );
$this->arWidgets['lstCategories']->append_column ($column);
$this->arWidgets['btnInstall'] ->connect_simple ('clicked', array ($this, 'installPackage'), true );
$this->arWidgets['btnUninstall']->connect_simple ('clicked', array ($this, 'installPackage'), false );
//drop files onto install button
$this->arWidgets['btnInstall'],
array ('application/x-tgz','.tgz'),
array ($this, 'onFilesDropped'),
$this->arWidgets['lstPackages'],
array ('application/x-tgz','.tgz'),
array ($this, 'onFilesDropped'),
$this->arWidgets['dlgProgress']->connect ('delete-event', array ($this, 'deleteProgressWindow'));
$this->arWidgets['mnuQuit'] ->connect_simple ('activate', array ($this, 'quitApp'));
$this->arWidgets['mnuAbout'] ->connect_simple ('activate', array ('PEAR_Frontend_Gtk2_About', 'showMe'));
$this->arWidgets['mnuInstallLocal'] ->connect_simple ('activate', array ($this, 'onInstallLocalPackage'));
$this->arWidgets['mnuUpdateLocal'] ->connect_simple ('activate', array ($this, 'refreshLocalPackages'));
$this->arWidgets['mnuUpdateOnline'] ->connect_simple ('activate', array ($this, 'refreshOnlinePackages'));
$this->arWidgets['mnuChannels'] ->connect_simple ('activate', array ($this, 'showChannelDialog'));
//that's channel name and array key of the packages
$this->arWidgets['lstPackages']->set_model (
Gobject ::TYPE_STRING , Gobject ::TYPE_STRING ,
Gobject ::TYPE_STRING , Gobject ::TYPE_STRING ,
$cell_renderer = new GtkCellRendererText ();
$colName = new GtkTreeViewColumn ('Package', $cell_renderer, "text", 0 );
$colName->set_resizable (true );
$colName->set_sort_column_id (0 );
$this->arWidgets['lstPackages']->append_column ($colName);
$colInstalled = new GtkTreeViewColumn ('Installed', $cell_renderer, "text", 1 );
$colInstalled->set_resizable (true );
$colInstalled->set_sort_column_id (1 );
$this->arWidgets['lstPackages']->append_column ($colInstalled);
$colNew = new GtkTreeViewColumn ('New version', $cell_renderer, "text", 2 );
$colNew->set_resizable (true );
$colNew->set_sort_column_id (2 );
$this->arWidgets['lstPackages']->append_column ($colNew);
$colSummary = new GtkTreeViewColumn ('Summary', $cell_renderer, "text", 3 );
$colSummary->set_resizable (true );
$colSummary->set_sort_column_id (3 );
$this->arWidgets['lstPackages']->append_column ($colSummary);
$selCategories = $this->arWidgets['lstCategories']->get_selection ();
$selCategories->set_mode (Gtk ::SELECTION_SINGLE );
$selCategories->connect ('changed', array ($this, 'selectCategory'));
$selPackages = $this->arWidgets['lstPackages']->get_selection ();
$selPackages->set_mode (Gtk ::SELECTION_SINGLE );
$selPackages->connect ('changed', array ($this, 'selectPackage'));
for ($nA = 1; $nA <= 3; $nA++ ) {
$this->arAnimationImages[] = GdkPixbuf ::new_from_file (dirname(__FILE__ ) . '/Gtk2/pixmaps/progress/load-anim-' . $nA . '.png');
$this->nProgressImage = 0;
}//protected function buildDialog()
protected function loadInstaller ()
$this->installer = new PEAR_Frontend_Gtk2_Installation ($this->arWidgets['dlgInstaller'], $this->glade);
PEAR_Frontend ::setFrontendObject ($this->installer);
}//protected function loadInstaller()
public function getInstaller ()
}//public function getInstaller()
public function showChannelDialog ()
if ($this->channelDialog === null ) {
$this->channelDialog = new PEAR_Frontend_Gtk2_ChannelDialog ($this->glade, $this);
$this->channelDialog->show ();
}//public function showChannelDialog()
* A channel has been selected from the channel combo box
* Has to be public as it is a callback function
* @param GtkComboBox $cmbChannel The channel selection combo box
* @param boolean $bSecondTime If the function is being run a second time (because the first run had a problem) - used to detect infinite loops.
public function selectChannel ($cmbChannel, $bSecondTime = false )
$strChannel = $cmbChannel->get_active_text ();
if ($strChannel === null ) {
$this->setChannelStyles ($strChannel);
$model = $this->arWidgets['lstCategories']->get_model ();
$this->arWidgets['lstPackages']->get_model ()->clear ();
$this->packages->setActiveChannel ($strChannel);
if (!$this->packages->packagesLoaded ()) {
//show the progress dialog before it gets loaded with the first callback
$this->showProgressDialog (true );
$arCategories = $this->packages->getCategories (
array ($this, 'packagesCallback'),
$this->hideProgressDialog ();
$dialog = new GtkMessageDialog (
$this->arWidgets['dlgInstaller'],
'Can\'t list the categories:' . "\r\n"
. $e->getCode () . ': ' . $e->getMessage ()
. "\r\n\r\nMake sure you have an internet connection."
$dialog->set_transient_for ($this->arWidgets['dlgInstaller']);
$dialog->set_position (Gtk ::WIN_POS_CENTER_ON_PARENT );
//The exception code seem to vary on every run - so I can't check for a certain code.
//But as we still can load the local packages, we can work offline.
$this->setWorkOffline (true );
$this->selectChannel ($cmbChannel, true );
//no chance to do a fix - do nothing
$arCategories = $this->appendSpecialCategories (
foreach ($arCategories as $key => $strCategory) {
$model->set ($model->append (), 0 , $strCategory, 1 , $key);
$this->arWidgets['lstPackages']->get_model ()->clear ();
$this->arWidgets['lstCategories']->get_selection ()->select_path ('0');
$this->hideProgressDialog ();
}//public function selectChannel($cmbChannel, $bSecondTime = false)
protected function setChannelStyles ($strChannel)
$strIcon = dirname(__FILE__ ) . '/Gtk2/pixmaps/' . $strChannel . '.png';
$strIcon = dirname(__FILE__ ) . '/Gtk2/pixmaps/default.png';
$this->arWidgets['imgChannelLogo']->set_from_file ($strIcon);
$strColorChannel = $strChannel;
if (!isset (PEAR_Frontend_Gtk2_Config ::$arChannels[$strColorChannel])) {
$strColorChannel = 'default';
$colBg = GdkColor ::parse (PEAR_Frontend_Gtk2_Config ::$arChannels[$strColorChannel]['background-color']);
$colFg = GdkColor ::parse (PEAR_Frontend_Gtk2_Config ::$arChannels[$strColorChannel]['color']);
$this->arWidgets['evboxSelectedCategory']->modify_bg (Gtk ::STATE_NORMAL , $colBg);
$this->arWidgets['lblSelectedCategory']->modify_fg (Gtk ::STATE_NORMAL , $colFg);
}//protected function setChannelStyles($strChannel)
* Appends defined special categories to the given
* @param array Array of categories
* @return array Array of categories with special ones
function appendSpecialCategories ($arCategories)
foreach (PEAR_Frontend_Gtk2 ::$arSpecialCategories as $key => $value) {
$arCategories[$key] = $value;
}//function appendSpecialCategories($arCategories)
* A category has been selected
function selectCategory ($selection)
list ($model, $iter) = $selection->get_selected ();
$this->arWidgets['lblSelectedCategory']->set_text ('No category selected');
$this->strSelectedCategoryName = $model->get_value ($iter, 0 );
$this->strSelectedCategoryKey = $model->get_value ($iter, 1 );
$this->arWidgets['lblSelectedCategory']->set_markup ('<b>' . $this->strSelectedCategoryName . '</b>');
if ($this->strSelectedCategoryKey == self ::CATEGORY_ALL ) {
$this->strSelectedCategoryName = null;
$this->showPackageList ($this->strSelectedCategoryName);
}//function selectCategory($selection)
* Fill the package list for the given category
protected function showPackageList ($strCategory)
$model = $this->arWidgets['lstPackages']->get_model ();
$arPackages = $this->packages->getPackages ($strCategory);
if (count($arPackages) > 0 ) {
foreach ($arPackages as $key => $package) {
1 , $package->getInstalledVersion (),
2 , $package->getLatestVersion (),
3 , $package->getSummary (),
}//protected function showPackageList($strCategory)
* Selects the given package
* @param string $strPackage The package name (without channel!)
public function selectPackageByName ($strPackage)
if ($strPackage === null ) {
//select the "*all*" category to make sure the package
$selection = $this->arWidgets['lstCategories']->get_selection ();
$model = $this->arWidgets['lstCategories']->get_model ();
$model->get_iter_from_string (
//gets number of children - 1 = from 0
$model->iter_n_children (null ) - 1
//now, select the package
$model = $this->arWidgets['lstPackages']->get_model ();
$iter = $model->get_iter_first ();
if ($model->get_value ($iter, 0 ) == $strPackage) {
$this->arWidgets['lstPackages']->get_selection ()->select_iter (
$iter = $model->iter_next ($iter);
}//public function selectPackageByName($strPackage)
* Update the package list model entries for the given packages.
* Useful after installing/uninstalling a package
* @param array $arPackages Array of packages which model entry shall be updated
protected function updatePackageList ($arPackages)
$model = $this->arWidgets['lstPackages']->get_model ();
$iter = $model->get_iter_first ();
$package = $model->get_value ($iter, 4 );
1 , $package->getInstalledVersion (),
2 , $package->getLatestVersion (),
3 , $package->getSummary (),
$iter = $model->iter_next ($iter);
}//protected function updatePackageList($arPackages)
* Callback for the package loading functions
function packagesCallback ($nPackageCount, $nCurrentPackage)
$dlgProgress = $this->arWidgets['dlgProgress'];
if ($nPackageCount === true ) {
$this->hideProgressDialog ();
$this->showProgressDialog ();
$this->arWidgets['lblProgress']->set_text ($nCurrentPackage . ' / ' . $nPackageCount);
$this->arWidgets['progBar']->set_fraction (1/ $nPackageCount * $nCurrentPackage);
if (!isset ($this->arAnimationImages[$this->nProgressImage])) {
$this->nProgressImage = 0;
//TODO: hold the images in memory
$this->arWidgets['imgProgress']->set_from_pixbuf ($this->arAnimationImages[$this->nProgressImage]);
while (Gtk ::events_pending ()) { Gtk ::main_iteration (); }
}//function packagesCallback($nPackageCount, $nCurrentPackage)
public function selectPackage ($selection)
list ($model, $iter) = $selection->get_selected ();
$this->setSelectedPackage (null );
$this->arWidgets['btnUninstall']->hide ();
$this->arWidgets['btnInstall'] ->set_sensitive (false );
$package = $model->get_value ($iter, 4 );
$this->setSelectedPackage ($package);
$strDescription = $package->getDescription ();
$this->ableInstallButtons ($package);
if ($package->getInstalledVersion () === null || $package->getInstalledVersion () == '') {
$this->arWidgets['lblBtnInstall']->set_label ('Inst_all package');
$this->arWidgets['lblBtnInstall']->set_label ('Upgr_ade package');
$this->arWidgets['txtPackageInfo']->get_buffer ()->set_text ($strDescription);
}//public function selectPackage($selection)
* Enables or disables the install buttons depending
* on the installation status of $package
* @param PEAR_Frontend_Gtk2_Package $package The package to check
protected function ableInstallButtons ($package)
$this->arWidgets['btnInstall']->set_sensitive (
$package->getLatestVersion () !== null
&& $package->getLatestVersion () !== ''
if ($package->getInstalledVersion () == '') {
$this->arWidgets['btnUninstall']->hide ();
$this->arWidgets['btnUninstall']->set_sensitive (false );
$this->arWidgets['btnUninstall']->show ();
$this->arWidgets['btnUninstall']->set_sensitive (true );
}//protected function ableInstallButtons($package)
protected function updateInstallButtons ()
$selection = $this->arWidgets['lstPackages']->get_selection ();
list ($model, $iter) = $selection->get_selected ();
$package = $model->get_value ($iter, 4 );
$this->ableInstallButtons ($package);
}//protected function updateInstallButtons()
protected function getSelectedPackage ()
return $this->selectedPackage;
}//protected function getSelectedPackage()
protected function setSelectedPackage ($package)
$this->selectedPackage = $package;
}//protected function setSelectedPackage($package)
* Installs (or uninstalls) the selected package
* @param boolean $bInstall True if the package shall be installed, false if it shall be uninstalled
function installPackage ($bInstall = true )
$strChannel = $this->packages->getActiveChannel ();
$package = $this->getSelectedPackage ();
$dialog = new GtkMessageDialog (
$this->arWidgets['dlgInstaller'], 0 , Gtk ::MESSAGE_ERROR , Gtk ::BUTTONS_OK ,
'You have to select a package before you can install it.'
//get changes done in gui into config
PEAR_Frontend_Gtk2_Config ::loadConfigurationFromGui ($this);
$this->installer->installPackage (
$package->getLatestVersion (),
PEAR_Frontend_Gtk2_Config ::$strDepOptions,
PEAR_Frontend_Gtk2_Config ::$bForceInstall
$package->refreshLocalInfo ();
$this->updatePackageList (array ($package));
$this->updateInstallButtons ();
}//function installPackage($bInstall = true)
* Shows the progress dialog if that hasn't already been done
* @param boolean $bInitWithZero If the dialog's labels should be initialized (if the dialog has been hidden)
protected function showProgressDialog ($bInitWithZero = false )
$dlgProgress = $this->arWidgets['dlgProgress'];
if ($dlgProgress->window === null || !$dlgProgress->window ->is_visible ()) {
$dlgProgress->modify_bg (Gtk ::STATE_NORMAL , GdkColor ::parse ('#FFFFFF'));
$dlgProgress->set_transient_for ($this->arWidgets['dlgInstaller']);
$dlgProgress->set_position (Gtk ::WIN_POS_CENTER_ON_PARENT );
$this->arWidgets['lblProgress']->set_text ('');
$this->arWidgets['progBar']->set_fraction (0 );
$dlgProgress->show_now ();
while (Gtk ::events_pending ()) { Gtk ::main_iteration (); }
}//protected function showProgressDialog($bInitWithZero = false)
* Hides the progress dialog
protected function hideProgressDialog ()
$this->arWidgets['dlgProgress']->hide ();
while (Gtk ::events_pending ()) { Gtk ::main_iteration (); }
}//protected function hideProgressDialog()
* The user may not close the progress window by hand
public function deleteProgressWindow ()
}//public function deleteProgressWindow()
* Check if the user wants to work offline
* @return boolean True, if the offline setting is active (work offline), false if working online
protected function getWorkOffline ()
return $this->arWidgets['mnuOffline']->get_active ();
}//protected function getWorkOffline()
* Set the offline setting
* @param boolean $bOffline If the user wants to work offline (true) or not (false)
protected function setWorkOffline ($bOffline)
return $this->arWidgets['mnuOffline']->set_active ($bOffline);
}//protected function setWorkOffline($bOffline)
public function getWidget ($strWidget)
return $this->arWidgets[$strWidget];
}//public function getWidget($strWidget)
* Reload local package information and update the list.
public function refreshLocalPackages ()
$this->packages->refreshLocalPackages ();
$this->showPackageList ($this->strSelectedCategoryName);
}//public function refreshLocalPackages()
* Reload the online package list and package information,
* and update the list here.
public function refreshOnlinePackages ()
$this->packages->refreshRemotePackages (null , array ($this, 'packagesCallback'));
$this->showPackageList ($this->strSelectedCategoryName);
}//public function refreshOnlinePackages()
* Let the user select a .tgz package file that will be installed.
public function onInstallLocalPackage ()
$fcd = new GtkFileChooserDialog (
'Select a package file to install',
$this->arWidgets['dlgInstaller'],
Gtk ::FILE_CHOOSER_ACTION_OPEN ,
Gtk ::STOCK_CANCEL , Gtk ::RESPONSE_NO ,
Gtk ::STOCK_OPEN , Gtk ::RESPONSE_YES
$fcd->set_select_multiple (false );
if ($this->strLastFile !== null ) {
$fcd->set_filename ($this->strLastFile);
$ffilter = new GtkFileFilter ();
$ffilter->add_pattern ('*.tgz');
$ffilter->add_pattern ('*');
$fcd->set_filter ($ffilter);
if ($fcd->run () == Gtk ::RESPONSE_NO ) {
$strFile = $fcd->get_filename ();
$this->installFile ($strFile);
}//public function onInstallLocalPackage()
* Some files have been dropped on the install button
public function onFilesDropped ($widget, $arFiles)
$strFile = reset($arFiles);
$this->installFile ($strFile);
}//public function onFilesDropped($widget, $arFiles)
* Install the given package file
* @param string $strFile File to install
protected function installFile ($strFile)
//get changes done in gui into config
PEAR_Frontend_Gtk2_Config ::loadConfigurationFromGui ($this);
$this->installer->installPackage (
PEAR_Frontend_Gtk2_Config ::$strDepOptions,
PEAR_Frontend_Gtk2_Config ::$bForceInstall
$this->refreshLocalPackages ();
$this->strLastFile = $strFile;
}//protected function installFile($strFile)
public function userConfirm ()
//just to make sure that the class is recognized as proper PEAR_Frontend class
}//public function userConfirm()
* @param PEAR_Error $error The error
public function displayFatalError ($error)
$strMessage = $error->getMessage ();
//404 occurs often and isn't that bad, so we can ignore it
//404 occurs, if a package exists but hasn't released a version.
if (strpos (strtolower($strMessage), '404 not found') === false ) {
$dialog = new GtkMessageDialog (
$this->arWidgets['dlgInstaller'],
'A fatal error occured:' . "\r\n" . $strMessage
$answer = $dialog->run ();
}//public function displayFatalError($error)
}//class PEAR_Frontend_Gtk2
Documentation generated on Mon, 11 Mar 2019 15:46:47 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|