Gtk

Perchè Gtk e non Qt o altro?

Quando ho iniziato a programmare in modalità grafica le Qt non erano ancora proprio libere. Ma non solo. In linux io preferisco Gnome a Kde perciò è venuto del tutto naturale la scelta. A distanza di anni non me ne pento. Anche se una volta compreso il loro funzionamento si scopre che passare da un ambiente ad un altro non è così difficile perchè i motori che girano sotto sono molto simili.

Quando ho iniziato c’ erano solo le gtk poi sono arrivate le gtk3. Il passaggio è quasi immediato ma ci vuole un pò di pazienza su alcune ridefinizioni. Non ho mai capito perchè hanno voluto rinominarle senza fare una retro compatibilità.

Visto che non c’è la facciamo noi.

Esempio

#-----------------------------------------------------------------------------
# Gtk2 = Gtk3
#-----------------------------------------------------------------------------
Gtk.ICON_SIZE_MENU = Gtk.IconSize.MENU
Gtk.ICON_SIZE_BUTTON = Gtk.IconSize.BUTTON

Un altro metodo che scelgo sempre di usare è quello di creare un file my00init.py dove all’ interno inserisco i richiami dei moduli più usati e inoltre aggiungo alcune definizioni che normalmente utilizzo spesso.

Esempio: my00init.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#from __future__ import with_statement
""" lista oggetti definiti:
	
	- insLib, we_are_frozen, module_path
	- myPath, myNone, printD, myPickle
	- chaStockLabel, chaBackColor
	- myDialog
"""

myRev = "(rev.150820)"
#-----------------------------------------------------------------------------
# Modules
#-----------------------------------------------------------------------------
# structures C
from ctypes import *

# system
# import warnings
# warnings.filterwarnings("ignore")
import os, sys
#import threading
#from glob import glob

# importa i moduli in modo dinamico (new style)
from gi.repository import Gtk, Gdk, GLib
from gi.repository import GObject 

# # gtk
# import gtk, gobject, pango
# ## Get Icons shown on buttons 
# settings = gtk.settings_get_default() 
# gtk.Settings.set_long_property(settings, "gtk-button-images", 1, "main") 

# time
from time import time, sleep, clock, localtime
# string
from string import atoi, atof
# from math import pi, sin, cos, tan, log, radians

# # serializzazione
# try:
#     import cPickle as pickle
# except:
#     import pickle, dump, load 

#-----------------------------------------------------------------------------
# Gtk2 = Gtk3 (retrocompatibilità)
#-----------------------------------------------------------------------------
Gtk.ICON_SIZE_MENU = Gtk.IconSize.MENU
Gtk.ICON_SIZE_BUTTON = Gtk.IconSize.BUTTON
Gtk.JUSTIFY_LEFT = Gtk.Justification.LEFT
Gtk.JUSTIFY_RIGHT = Gtk.Justification.RIGHT
Gtk.POS_RIGHT = Gtk.PositionType.RIGHT
Gtk.POLICY_AUTOMATIC = Gtk.PolicyType.AUTOMATIC
Gtk.POLICY_ALWAYS = Gtk.PolicyType.ALWAYS
Gtk.SELECTION_BROWSE = Gtk.SelectionMode.BROWSE
Gtk.SELECTION_NONE = Gtk.SelectionMode.NONE
Gtk.SORT_ASCENDING = Gtk.SortType.ASCENDING
Gtk.SHADOW_NONE = Gtk.ShadowType.NONE
Gtk.SHADOW_IN = Gtk.ShadowType.IN
Gtk.SHADOW_OUT = Gtk.ShadowType.OUT
Gtk.SHADOW_ETCHED_IN = Gtk.ShadowType.ETCHED_IN
Gtk.SHADOW_ETCHED_OUT = Gtk.ShadowType.ETCHED_OUT
Gtk.STATE_NORMAL = Gtk.StateType.NORMAL
Gdk.BUTTON_PRESS = Gdk.EventType.BUTTON_PRESS
Gtk.TEXT_DIR_RTL = Gtk.TextDirection.RTL
Gdk.CONTROL_MASK = Gdk.ModifierType.CONTROL_MASK
Gtk.FILE_CHOOSER_ACTION_OPEN = Gtk.FileChooserAction.OPEN
Gtk.FILE_CHOOSER_ACTION_SAVE = Gtk.FileChooserAction.SAVE
Gtk.RESPONSE_CANCEL = Gtk.ResponseType.CANCEL
Gtk.RESPONSE_OK = Gtk.ResponseType.OK
Gtk.WIN_POS_CENTER = Gtk.WindowPosition.CENTER
Gtk.SELECTION_MULTIPLE = Gtk.SelectionMode.MULTIPLE
Gtk.TEXT_WINDOW_WIDGET = Gtk.TextWindowType.WIDGET

#-----------------------------------------------------------------------------
# Enviroment
#-----------------------------------------------------------------------------
def insLib(lib, deb=True):
	""" Inserisce nel path la libreria passata 
	"""
	# verifica se gia' presente
	if lib not in sys.path:
		# aggiungo in prima posizione
		sys.path.insert(0, lib)
	else:
		if deb:
			print "gia' presente:", lib

#-----------------------------------------------------------------------------
def we_are_frozen():
	"""Returns whether we are frozen via py2exe.
	This will affect how we find out where we are located."""
	return hasattr(sys, "frozen")

#-----------------------------------------------------------------------------
def module_path():
	# verifico il sitema operativo
	if sys.platform != 'win32':
		return os.getcwd()
	else:
		""" This will get us the program's directory,
		even if we are frozen using py2exe"""
		if we_are_frozen():
			return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding( )))
		return os.path.dirname(unicode(__file__, sys.getfilesystemencoding( )))
			
#-----------------------------------------------------------------------------
def myPath(path):
	# elimino la current path per comodita' visiva
	myFath = os.getcwd()
	if path.find(myFath) == 0:
		path = path[len(myFath)+1:]
	return path

#-----------------------------------------------------------------------------
# ricavo l'ambiente
myRoot = module_path()
myFath, myHome = os.path.split(myRoot)
if 0:
	print "myRoot", myRoot
	print "myFath", myFath
	print "myHome", myHome
# aggiungo il path delle mie librerie
insLib(myRoot+'/myLib',True)
#insLib(myRoot+'/myLib/myGtk',True)
insLib(myRoot+'/myLib/myProtocol',True)
#insLib(myRoot+'/myLib/myUtilities',True)
# insLib(myRoot+'/myWidg',True)

#-----------------------------------------------------------------------------
# myDefine
#-----------------------------------------------------------------------------
def myNone(*args):
	pass

#-----------------------------------------------------------------------------
def printD(msg, att='\n'):
	""" forzo scrittura nella standard out
		 server quando lo stdout e' reinderizzato e si vuole
		 inviare comunque vero stdout di default
	"""
	sys.__stdout__.write(str(msg)+att)

def chaStockLabel(self,lab):
	" cambio nome alla label in una stock_id"
	al = self.get_children()[0]
	hb = al.get_children()[0]
	image, text = hb.get_children()
	text.set_text(lab)

#-----------------------------------------------------------------------------
def chaBackColor(obj=None, css="MyWind", col="#666666"):
	" cambio colore al background dell'oggetto"
	obj.set_name(css)
	# colore background
	style_provider = Gtk.CssProvider()
	css = "#%s {background-color: %s; }" %(css, col)
	# css = """
	# #MyWind {
	#     background-color: #F00;
	# }
	# """
	style_provider.load_from_data(css)
	Gtk.StyleContext.add_provider_for_screen(
		Gdk.Screen.get_default(), 
		style_provider,     
		Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
	)

#-----------------------------------------------------------------------------
def myPickle(obj=None, nam="mio.pkl"):
	""" Serializzazione degli oggetti.
		obj==None => read  es: lObj=myPickle(nam="mio.pkl")
		obJ!=None => write es: myPickle(obj=["prova",1,[1,2,3]], nam="new.pkl")
	"""
	# provo l'importazione del modulo piu' veloce
	#  altrimenti ripiego in quello standard
	try:
		import cPickle as pickle
	except:
		import pickle
	
	if obj != None:
		# write
		pickle.dump(obj, open(nam,'w'))
	else:
		# read
		return pickle.load(open(nam,'r'))

#-----------------------------------------------------------------------------
# myClass
#-----------------------------------------------------------------------------
class myDialog(Gtk.Dialog):
	""" Dialog di supporto
		 quando si vuol interagire velocemente con l'utente
	"""
	def __init__(self, parent=None, title="myDialog", mesg="Vuoi Confermare?", 
					width=3, heigth=3, x=None, y=0, icon=True):
		Gtk.Dialog.__init__(self, title, parent, 0,
			(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
			 Gtk.STOCK_OK, Gtk.ResponseType.OK))

		# abilita le icone che per default sono nascoste
		if icon:
			# prendeo la referenza degli oggetti presenti
			for obj in self.action_area.get_children():
				# abilito la vista dell'immagine
				obj.set_always_show_image (True)

		# se x è presente voglio scegliere la posizione del nostro dialog
		if x==None:
			# altrimenti la centro nella dimensione del nostro schermo
			self.set_position(Gtk.WindowPosition.CENTER)
		else:
			self.move(x, y)
		# imposto la dimensione
		self.set_default_size(width,heigth)
		# prendo la referenza del box interno
		box = self.get_content_area()
		# istanzio una label per il message
		label = Gtk.Label(mesg)
		# e la inserisco
		box.add(label)
		# visualizzo il tutto
		self.show_all()
#-----------------------------------------------------------------------------
def testMyDialog():
	# istanzio una dialog
	dial = myDialog(parent=None, title="myDialog", mesg="Vuoi Confermare?", 
			width=3, heigth=3, x=None, y=0, icon=True)
	# avvio l'esecuzione
	resp = dial.run()
	# attendo la risposta
	if resp == Gtk.ResponseType.OK:
		print("OK")
	elif resp == Gtk.ResponseType.CANCEL:
		print("Cancel")
	# distruggo l'istanza
	dial.destroy()

#-----------------------------------------------------------------------------
# Main
#-----------------------------------------------------------------------------
if __name__ == "__main__":

	# test arguments
	if len(sys.argv) == 1:
		# no arguments (scelgo io)
		choi = 1
	else:
		# get first argument (scelta esterna)
		choi = int(sys.argv[1])

	if choi == 1:
		"test myDialog"
		testMyDialog()

link

Dopo di che non faccio altro che inserire la direttiva in tutti i miei files:

#-----------------------------------------------------------------------------
# Modules
#-----------------------------------------------------------------------------
from my00init import *

Qualcuno avrà qualcosa da ridere in questo modo di fare.

Dialogs

Nel file precedente ho introdotto il primo metodo che usa le GTK. In particolare le dialogs servono quando dobbiamo interagire con l’ utente.

Le dialogs sono un particolare oggetto che normalmente blocca l’ esecuzione del programma principale per attendere una risposta dall’ utente.

In gergo chiamato modale o non modale.

Nota

Da un pò di tempo si è deciso di non visualizzare più di default le icone degli oggetti. Per questo bisogna abilitarli tramite l’ apposita funzione quando desiderato.

Windows

Bene ora che abbiamo il nostro init passiamo alla prima applicazione.

Esempio: myWind.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" lista degli oggetti definiti:

	- myBox1
	- myFrame1
	- myWind
"""

myRev = "(rev.150822)"
#-----------------------------------------------------------------------------
# Modules
#-----------------------------------------------------------------------------
from my00init import *

#-----------------------------------------------------------------------------
# myModules
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# myDefines
#-----------------------------------------------------------------------------
def myBox1(tBox='v', homo=False, spac=0):
	""" crea un contenitore del tipo richiesto
	
		-> tBox tipo di contenitore v/h 
		-> homo tipo omogeneita'
		-> spac spazio da mantenere all'esterno dell'oggetto
	"""
	if tBox == 'v':
		# creo un Vertical Box
		xBox = Gtk.VBox(homo,spac)
	elif tBox == 'h':
		# creo un Horizontal Box
		xBox = Gtk.HBox(homo,spac)
	xBox.show()
# <-
	return xBox

#-----------------------------------------------------------------------------
def myFrame1(name='myFrame', obje=None, colo='blue',
			bord=2, shad=Gtk.SHADOW_ETCHED_OUT, 
			tBox='v', aBox=[False, False, 1],
			xtBox='', xaBox=[False, False, 1],
			show=False ):
	""" crea una cornice con un titolo
	
		-> name nome associato alla label della cornice
		-> obje oggetto da inserire
		-> colo colore label
		-> bord bordo riservato all'esterno
		-> shad tipo di cornice
			Gtk.SHADOW_NONE, Gtk.SHADOW_IN, Gtk.SHADOW_OUT, 
			Gtk.SHADOW_ETCHED_IN, Gtk.SHADOW_ETCHED_OUT
		-> tBox tipo di contenitore v/h interno 
		-> aBox attributi del contenitore interno
		-> xtBox tipo di contenitore v/h esterno 
		-> xaBox attributi del contenitore esterno
		-> show abilita la visione della cornice
	"""
#frame
	if name != "":
		name = " "+name+" "

	# nasconde il bordo e il nome del frame
	if show==False:
		bord=0
		aBox=[False, False, 0]
		name = ""
		shad = Gtk.SHADOW_NONE

	fram = Gtk.Frame(label=name)
	# la rendo visibile
	fram.show()
	# imposto il bordo (esterno)
	fram.set_border_width(bord)
	fram.set_shadow_type(shad)
#label
	# referenzio la label della Frame
	labe = fram.get_label_widget()
	# attivo il markup
	labe.set_markup("<b>%s</b>" %name)
	# imposto il colore
	labe.modify_fg(Gtk.STATE_NORMAL, Gdk.color_parse(colo))
#myBox (interno)
	xBox = myBox1(tBox)
	fram.add(xBox)
#object
	if obje != None:
		#(child, expand=False, fill=False, padding=1)
		xBox.pack_start(obje, *aBox)
#myBox (esterno)
	if (xtBox == 'v') or (xtBox == 'h'):  
		yBox = myBox1(xtBox)
		#(child, expand=False, fill=False, padding=1)
		yBox.pack_start(fram, *xaBox)
# <-
		return yBox, [labe, xBox, fram]
	else:    
# <-
		return fram, [labe, xBox]

#-----------------------------------------------------------------------------
# myClass
#-----------------------------------------------------------------------------
class MyWind(Gtk.Window):
	""" + window (self)                         .------------------------.
	"     - vBox                                | vertical               |
	"       - hBox                              |   .--------------------.
	"                                           |   | horizontal         |
	"                                           |   |                    |
	"                                           .---.--------------------.
	"""
	def __init__(self, width=None, height=400, title="myWind", center=True, color=""):
		# inizializzo l'ogetto da cui derivo
		super(MyWind, self).__init__()

		# referenzio gli attributi passati
		self.title = title
		self.width = width
		self.height = height

		# imposto il nome dell'oggetto uguale al titolo
		self.set_name(title)
		# imposta il titolo dell'applicazione
		self.set_title(self.title)

		# ridimensiono se viene passato almeno l'ampiezza
		if width != None:
			self.resize(width, height)
		# se richiesto centro la posizione
		if center:
			self.set_position(Gtk.WindowPosition.CENTER)

		# callbacks di uscita da eventi
		self.connect("delete-event", Gtk.main_quit)
		# intercettiamo la tastiera
		self.connect("key_press_event", self.doKeyPress)

		# se passato cambio colore
		if color:
			# change background color to Class
			chaBackColor(obj=self, css=title, col=color)

		# abilita la propria visualizzazione
		self.show()

#vBox (istanza un contenitore verticale)
		# self.vBox = Gtk.VBox(homogeneous=False, spacing=0)
		# self.vBox.show()
		self.vBox = myBox1('v')
		self.add(self.vBox)

#hBox (istanza un contenitore orizzontale)
		# self.hBox = Gtk.HBox(homogeneous=False, spacing=0)
		# self.vBox.show()
		self.hBox = myBox1('h')
		self.vBox.pack_start(child=self.hBox, expand=False, fill=False, padding=0)

	def doKeyPress(self, widget, event):
		# intercetto tasto speciale ctrl
		if (event.state == Gdk.ModifierType.CONTROL_MASK):
			#print "Ctrl", Gdk.keyval_name(event.keyval)
			pass
		else:
			# intercetto tasto normale
			keyname = Gdk.keyval_name(event.keyval)
			#print "the button %s was pressed" % keyname
			if keyname == "Escape":
				# richiesta di uscita dal programma
				Gtk.main_quit()
#-----------------------------------------------------------------------------
def testMyWind():
	# istanza l'applicazione 
	#  (width=None, height=400, title="myWind", center=True, color="")
	self = MyWind(title="myWind", color="#aaa")

	# attributes
	colo="#333333"
	bord=2 			# external
	expa=False
	fill=False
	padd=1 			# internal
	show=True

#aBox (application)
	# fram,[labe,xBox]
	aObj, oth1 = myFrame1(name='application', obje=None, colo=colo,
						bord=bord, shad=Gtk.SHADOW_IN,
						tBox='h', aBox=[expa, fill, padd],
						#xtBox='h', xaBox=[expa, fill, padd],
						show=show )
	self.aBox = oth1[1]

	# inserimento nel contenitore
	self.vBox.pack_start(child=aObj, expand=False, fill=False, padding=0)

#gBox (aBox/global)
	# fram,[labe,xBox]
	gObj, oth1 = myFrame1(name='global', obje=None, colo=colo,
						bord=bord, shad=Gtk.SHADOW_IN,
						tBox='h', aBox=[expa, fill, padd],
						#xtBox='h', xaBox=[expa, fill, padd],
						show=show )
	self.gBox = oth1[1]

	# inserimento nel contenitore
	self.vBox.pack_start(child=gObj, expand=False, fill=False, padding=0)

#-----------------------------------------------------------------------------
# myTry
#-----------------------------------------------------------------------------
def myTry01():
	testMyWind()

#-----------------------------------------------------------------------------
# Main
#-----------------------------------------------------------------------------
if __name__ == "__main__":

	# test arguments
	if len(sys.argv) == 1:
		# no arguments (scelgo io)
		choi = 1
	else:
		# get first argument (scelta esterna)
		choi = int(sys.argv[1])

	if choi == 1:   # (MyWind)
		# draw window
		myTry01()

	# avvia applicazione
	Gtk.main()

link

Per creare un’ applicazione grafica di solito si parte ereditando dalla classe madre:

Gtk.Window

Subito dopo si definisce il metodo __init__() che serve ad inizializzare l’ oggetto che stiamo definendo. La prima cosa da fare è richiamare l’ inizializzazione del genitore col metodo super ed infine si inizia a definire gli attributi e le altre definizioni che caratterizzeranno la nostra classe.

Descrizione

Iniziamo col descrivere le cose più salienti.

Callback

Le callbacks in python non sono altro che dei metodi richiamati da un evento ad essi associati.

Esempio

# callback di uscita da envento
self.connect("delete-event", Gtk.main_quit)

Come si può leggere dal codice qui sopra la chiave connect lega l’ evento definito come delete-event al metodo Gtk.main_quit che serve per uscire dall’ esecuzione della nostra applicazione.

Start

Si perchè in python l’ applicazione principale va in esecuzione alla chiamata

Gtk.main

che altro non è che il loop principale della nostra applicazione.

Stop

Invece viene interrotta dalla chiamata di

Gtk.main_quit

Event/Signal

delete-event invece è un evento che viene inviato quando l’ utente richiede di chiudere la finestra cliccando col mouse sul pulsante [x] che di solito si trova in alto a destra della finestra stessa.

Nota

A volte si vuol differenziare eventi da segnali come messaggi emessi da soggetti diversi.

Event => Widgets oggetto visibile (es: pulsante).

Signal => Objects oggetto non visibile (es: tastiera).

Keyboard

Noi da bravi programmatori diamo la possibilità di chiudere la finestra anche usando solo la tastiera. Il tasto principe per la fuga è nominato Escape. Questo perchè ritengo, e non solo io, che l’ uso della tastiera in molti casi sia più veloce che l’ uso del buon mouse.

In questo caso ho dovuto definire il metodo per intercettare l’ avvenuta pressione del tasto interessato. E per altre eventuali intercettazioni ho previsto anche la verifica del tasto Ctrl.

Esempio

def doKeyPress(self, widget, event):
        # intercetto tasto speciale ctrl
        if (event.state == Gdk.ModifierType.CONTROL_MASK):
                #print "Ctrl", Gdk.keyval_name(event.keyval)
                pass
        else:
                # intercetto tasto normale
                keyname = Gdk.keyval_name(event.keyval)
                #print "the button %s was pressed" % keyname
                if keyname == "Escape":
                        # richiesta di uscita dal programma
                        Gtk.main_quit()

L’associazione della callback è la seguente.

# intercettiamo la tastiera
self.connect("key_press_event", self.doKeyPress)

Saluti

Ok per oggi finiamo quì.

I commenti di Box e Frame li vediamo alla prossima**

Ciao alla prossima. (stay tune!)