<?php
    
/**
     * IPv6 Entry widget
     *
     * IPv6Entry performs some checks to see if userinput is valid and implements the
     * get_text() and set_text() methods found in regular GtkEntry widgets for
     * compatibility and ease of use.
     *
     * Small warning note: Needs to be fitted within a GtkAlignment because it will
     * take up all available space otherwise.
     *
     * @author Robert van der Linde <robert@linde002.nl>
     * @version 1.0
     */
    
class IPv6Entry extends GtkFrame
    
{
        
/**
         * Contains the four GtkEntry fields.
         *
         * @var array
         */
        
protected $aEntries = array();
        
        
/**
         * The GtkEntry currently selected
         *
         * @var int
         */
        
protected $iCurrentEntry = 0;
        
        
/**
         * Creates a IPEntry widget
         *
         */
        
public function __construct()
        {
            
parent::__construct();
            
            
/**
             * The container for all the GtkEntries
             */
            
$oHBox = new GtkHBox();
            
            
/**
             * Create four GtkEntry fields
             */
            
for ($x=0; $x<8; $x++)
            {
                
$this->aEntries[$x] = new GtkEntry();
                
                
/**
                 * Align the text in the middle of the GtkEntry. Looks pretty
                 */
                
$this->aEntries[$x]->set_property('xalign', 0.5);
                
$this->aEntries[$x]->set_property('has-frame', 0);
                
$this->aEntries[$x]->set_size_request(25, -1);
                
                
/**
                 * I know we skip fields after max 4 characters but if a user keeps
                 * the button pressed there is a possibility that he can continue inputting
                 * numbers. Let's not allow this.
                 */
                
$this->aEntries[$x]->set_max_length(4);
                
                
$this->aEntries[$x]->set_text('0');
                
                
/**
                 * Connect the callback signals used to perform input checking
                 */
                
$this->aEntries[$x]->connect('key-press-event', array($this, 'onButtonDown'), $x);
                
$this->aEntries[$x]->connect('key-release-event', array($this, 'onButtonUp'), $x);
                
                
$oHBox->pack_start($this->aEntries[$x], 0);
                
                
/**
                 * If this is the last entry field don't add the dot.
                 */
                
if($x != 7)
                    
$oHBox->pack_start(new GtkLabel(':'), 0);
            }
            
            
/**
             * Pack it all in a pretty eventbox so we can set the general background color.
             */
            
$oEventBox = new GtkEventBox();
            
$oEventBox->add($oHBox);
            
$oEventBox->modify_bg(Gtk::STATE_NORMAL, GdkColor::parse("#ffffff"));
            
            
$this->add($oEventBox);
            
$this->set_shadow_type(Gtk::SHADOW_ETCHED_OUT);
        }
        
        
/**
         * Called when a user starts inputting a character. If that character is a dot
         * we jump to the next entry field and suppress the dot.
         *
         * @param GtkEntry $oEntry
         * @param GdkEvent $oEvent
         * @param int $iEntryid
         * @return boolean
         */
        
public function onButtonDown(GtkEntry $oEntry, GdkEvent $oEvent, $iEntryid)
        {
            if(
chr($oEvent->keyval) == ':')
            {
                if(
$iEntryid != 7)
                    
$this->aEntries[$iEntryid+1]->grab_focus();    
                return
true;
            }
            
            
/**
             * Check if the character is a valid hex value
             */
            
elseif(!ereg ('^[0-9A-Fa-f]+$', chr($oEvent->keyval)))
                return
true;

            else
                return
false;
        }
        
        
/**
         * Called when a user finishes inputting a character. If the character is
         * the third digit or by inputting the character makes the value higher then 25
         * jump to the next inputfield.
         *
         * If this inputfield is selected by way of using TAB or dot we do not check the
         * value of the input fields. This is a workaround to stop the widget from entering
         * a possible infiniteloop when all input fields are > 0xFFFF
         *
         * @param GtkEntry $oEntry
         * @param GdkEvent $oEvent
         * @param int $iEntryid
         */
        
public function onButtonUp(GtkEntry $oEntry, GdkEvent $oEvent, $iEntryid)
        {
            
            if(
$oEvent->keyval != Gdk::KEY_Tab && chr($oEvent->keyval) != ':')
            {
                
/**
                 * If the entry field is full skip to the next one
                 * unless it's the last field
                 */
                
if(strlen(trim($oEntry->get_text())) == 4 && $iEntryid != 7)
                {
                        
$this->aEntries[$iEntryid+1]->grab_focus();
                }
            }
        }
        
        
/**
         * Implementation of the GtkEntry::set_text() function for usability.
         * Checks if the string is a valid IPv6 address
         *
         * When it fails it defaults
         *
         * @param string $sIP
         */
        
public function set_text($sIP)
        {
            
$sIPv6Pattern = '/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/';
            if(
preg_match($sIPv6Pattern, $sIP))
            {
                
$aIPSections = explode(':', $sIP);
                for(
$x=0; $x<8; $x++)
                    
$this->aEntries[$x]->set_text($aIPSections[$x]);
            }
        }
        
        
/**
         * Implementation of the GtkEntry::get_text() function for usability.
         * Returns a concatenation of the GtkEntry fields and :.
         *
         * @return string
         */
        
public function get_text()
        {
            
$sIP = '';
            for(
$x=0; $x<8; $x++)
            {
                
$sIP .= trim($this->aEntries[$x]->get_text());
                if(
$x != 7)
                    
$sIP .= ":";
            }
            
            return
$sIP;
        }
    }
?>