Utilizando popup de data nas celulas de um GtkTreeview

Deixar o campo aberto para digitação de datas é muito ágil. Porem algumas vezes é mais viável a utilização de calendário, para a visualização de dias da semana por exemplo. Essa técnica pode ser facilmente adicionada à um GtkTreeview se usado em conjunto de alguns sinais.

Imagem

Exemplo

/**
 * Seta a codificação do programa
 */
ini_set("php-gtk.codepage", "UTF-8");
	
/**
 * Classe de exemplo
 * 
 * @name Demo
 * @author Bruno P. Gonçalves <http: //bruno.pitteli.com.br></http:>
 * @download <http: //code.google.com/p/fabulafw></http:>
 */
class Demo {
	/**
	 * Armazena os widgets necessarios
	 * 
	 * @access private
	 * @property array $widgets
	 */
	public $widgets = array();
	
	/**
	 * @name __construct()
	 * @return Demo
	 */
	public function __construct() {
		// Cria a janela
		$this->widgets['frmDemo'] = new GtkWindow();
		$this->widgets['frmDemo']->set_size_request(250, 250);
		$this->widgets['frmDemo']->set_position(Gtk::WIN_POS_CENTER_ALWAYS);
		$this->widgets['frmDemo']->set_title("Demo");
		$this->widgets['frmDemo']->connect("destroy", array($this, "frmDemo_unload"));
		$vbox = new GtkVBox();

		// Cria o modelo
		$model = new GtkListStore(GObject::TYPE_STRING, GObject::TYPE_STRING);

		// Creia o treeview
		$this->widgets['trvTeste'] = new GtkTreeView($model);

		// Cria a colunas
		$render = new GtkCellRendererText();
		$render->set_property("editable", TRUE);
		$render->connect("edited",  array($this, "trvTeste_onChange"), 0);
		
		$column = new GtkTreeViewColumn("Couna 1", $render, "text", 0);
		$this->widgets['trvTeste']->append_column($column);

		$render = new GtkCellRendererText();
		$render->set_property("editable", TRUE);
		$render->connect("editing-started", array($this, "trvTeste_onEdit"));
		
		$this->widgets['colData'] = new GtkTreeViewColumn("Coluna 2", $render, "text", 1);
		$this->widgets['trvTeste']->append_column($this->widgets['colData']);

		// Popula o model
		$model->append(array("Valor 1", "03/01/2012"));
		$model->append(array("Valor 2", "02/01/2012"));
		$model->append(array("Valor 3", "01/01/2012"));
		
		// Inicia a aplicação
		$vbox->pack_start($this->widgets['trvTeste']);
		$this->widgets['frmDemo']->add($vbox);
		$this->frmDemo_onload();
	}
	
	/**
	 * Método do carregamento do formulario
	 * 
	 * @name frmDemo_onload()
	 */
	public function frmDemo_onload() {
		// Inicia a aplicação
		$this->widgets['frmDemo']->show_all();
		Gtk::main();
	}
	
	/**
	 * Método do descarregamento do formulario
	 * 
	 * @name frmDemo_unload()
	 */
	public function frmDemo_unload() {
		// Encerra a aplicação
		Gtk::main_quit();
	}
	
	/**
	 * Método que inicia a edição da celula
	 * 
	 * @name trvTeste_onEdit
	 * @param GtkCellRender $cellRenderer Renderizador da coluna
	 * @param GtkEditable $editable Objeto sendo editado
	 * @param integer $path Linha que foi editada
	 */
	function trvTeste_onEdit($cellrenderer, $editable, $path) {
		$this->__openCalendar($path);
		$cellrenderer->editing_canceled();
	}
	
	/**
	 * Método que edita as colunas do trvTeste
	 * 
	 * @name trvTeste_onChange
	 * @param GtkCellRender $cellRenderer Renderizador da coluna
	 * @param integer $path Linha que foi editada
	 * @param mixed $value Valor que está sendo adicionado
	 * @param integer $column Numero da coluna editada
	 */
	public function trvTeste_onChange($cellRenderer, $path, $value, $column) {
		// Verifica se foi digitado alguma coisa
		if(trim($value) == "") {
			return FALSE;
		}
		
		// Recupera o model do treeview
		$model = $this->widgets['trvTeste']->get_model();
		$iter = $model->get_iter($path);
		
		// Seta o valor no treeview
		$model->set($iter, $column, $value);
	}
	
	/**
	 * Método de abertura do calendario
	 * 
	 * @name __openCalendar
	 * @access private
	 */
	private function __openCalendar($path) {
		// Busca as informações da celula
		$info = $this->widgets['trvTeste']->get_cell_area($path, $this->widgets['colData']);
		
		$entryX = $info->x;
		$entryY = $info->y;
		
		// Busca a posição da janela parente
		$window  = $this->widgets['frmDemo'];
		$win_pos = $window->get_position();
		$windowX = $win_pos[0];
		$windowY = $win_pos[1];
		
		// Faz o calculo da posicao final
		$finalX = $entryX + $windowX + 5;
		
		$trv_pos = $this->widgets['trvTeste']->window->get_position();
		$finalY = $trv_pos[1] + ($info->height * 3) + $entryY + $windowY;
		
		// Cria o dialogo para o calendario
		$dialog = new GtkDialog(NULL, NULL, Gtk::DIALOG_MODAL|Gtk::DIALOG_NO_SEPARATOR);
		$dialog->set_decorated(FALSE);
		$dialog->set_uposition($finalX, $finalY);
		
		// Cria o calendario e o coloca dentro do dialogo
		$calendar = new GtkCalendar();
		$dialog->vbox->pack_start($calendar, 0, 0);
		$dialog->action_area->set_size_request(-1, 0);
		
		// Busca o dia do entry
		$model = $this->widgets['trvTeste']->get_model();
		$iter = $model[$path];
		
		$text = $model->get_value($iter->iter, 1);
		if(strlen($text) > 0) {
			// Separa as datas
			$date = explode("/", $text);
			
			// Marca o dia do entry no calendario
			$calendar->select_day($date[0]);
			$calendar->select_month($date[1]-1, $date[2]);
		}
		
		// Faz as conexões do calendario
		$calendar->connect("day-selected", array($this, "__changedayCalendar"), $dialog, $path);
		$calendar->connect("focus-out-event", array($this, "__onlostfocusCalendar"), $dialog);
		$dialog->connect("key-press-event", array($this, "__keypressDialog"));

		// Mostra o calendario
		$dialog->show_all();
		$dialog->run();
	}
	
	/**
	 * Método que verifica as teclas pressionadas no dialog
	 * 
	 * @access private
	 * @name __keypressDialog
	 * @param GtkDialog $widget Dialogo do calendario
	 * @param GtkEvent $event Informações sobre o evento ocorrido
	 */
	function __keypressDialog($widget, $event) {
		// Verifica se é um ESC
		if($event->keyval == 65307) {
			$widget->destroy();
		}
	}
	
	/**
	 * Método ao tirar o foco do calendario
	 * 
	 * @name __onlostfocusCalendar($widget, $event, $dialog)
	 * @access private
	 * @param GtkCalendar $widget Calendario passado pelo sinal
	 * @param GtkEvents $event Eventos disparados externamente
	 * @param GtkDialog $dialog Container onde está o calendario
	 * @return bool
	 */
	public function __onlostfocusCalendar($widget, $event, $dialog) {
		// Fecha a janela do calendario
		$dialog->destroy();
		return FALSE;
	}
	
	/**
	 * Método disparado ao mudar a data do calendario
	 * 
	 * @name __changedayCalendar($widget, $dialog)
	 * @access private
	 * @param GtkCalendar $widget Calendario passado pelo sinal
	 * @param GtkDialog $dialog Container onde está o calendario
	 * @param integer $path Linha editada
	 * @return bool
	 */
	public function __changedayCalendar($widget, $dialog, $path) {
		// Busca o calendario
		$date = $widget->get_date();
		
		// Formata a data
		$date[2] = sprintf("%02d", $date[2]);
		$date[1] = sprintf("%02d", $date[1]+1);
		$formatedDate = implode("/", array_reverse($date));
		
		// Busca o model
		$model = $this->widgets['trvTeste']->get_model();
		
		// Coloca a data na celula
		$this->trvTeste_onChange($this->widgets['colData'], $path, $formatedDate, 1);
		
		// Remove o dialog
		return FALSE;
	}
}
	
/**
 * Inicia o demo
 */
new Demo();

Referências

http://gtk.php.net/manual/en/gtk.gtktreeview.php
http://gtk.php.net/manual/en/gtk.gtkcellrenderer.php
http://gtk.php.net/manual/en/gtk.gtkcellrenderertext.php
http://gtk.php.net/manual/en/gtk.gtkdialog.php
http://gtk.php.net/manual/en/gtk.gtkcalendar.php