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!)