2009-10-25 22:29:24 UTC David Baird <dhbaird@gmail.com>
Prerađuje kod za autodetekciju za WebKit ili GtkMozEmbed i koristi koji god pronađe. Takođe, sada koristi standardne funkcije bibliteke os.path.abspath i urllib.pathname2url umesto da ručno pokušava da generiše ispravne putanje i URL adrese.
2009-06-21 13:30:57 UTC David Baird <dhbaird@gmail.com>
Ispravljeno "kill_gtk_thread()" i "my_quit_wrapper()" prebacivanjem sa synchronous_gtk_message na asynchronous_gtk_message. (Hvala Timu Kerstenu što nam je na to ukazao).
2009-05-16 07:36:37 UTC David Baird <dhbaird@gmail.com>
Ažurirao sam ovaj dokument tako da opiše GtkMozEmbed. Ponovo sam napisao primer koda za podršku GtkMozEmbed i PyWebKitGtk.
2009-05-16 06:27:53 UTC David Baird <dhbaird@gmail.com>
Ispravljene neke greške u kucanju (hvala John Tantalo iz Emend (Twitter) - odlična alatka za gramatičke greške). Takođe realizujem predlog (sa Y Combinator News) za korišćenje uslovnih varijabli. Takođe sam preimenovao funkcije “execution (izvršenje)” ua naprednije ime “worker (radnik)”.
2009-05-16 02:22:09 UTC David Baird <dhbaird@gmail.com>
Ažuriram ovaj dokument prenošenjem JavaScript "send()" funkcije u odvojeni fajl pod imenom "ipc.js"; Pre toga, "send()" se injektovao pomoću WebKit ".execute_script()" metoda. Prerađivanjem koda "send()", biće olakšano korišćenje gtkmozembed (Mozilla), čija je JavaScript injekcija malo drugačija, kao alternativa WebKit-u.
2009-04-22 David Baird <dhbaird@gmail.com>
Prvo izdanje, objašnjava kako ugraditi WebKit i razmeniti poruke sa Python-om.
U ovom priručniku objašnjavam sledeće koncepte:
Ovaj priručnik govori o korišćenju HTML-a i pridruženih web tehnologija (JavaScript, CSS, JSON...) u cilju kreiranja GUI-a za samostalnu aplikaciju u Python-u.
O čemu ovaj priručnik ne govori:
Tokom perioda od nekoliko godina pokušavao sam da kreiram GUI. Svaki put bih biavo razočaran. Postoje dva glavna obeshrabrujuća faktora na koja sam nailazio:
Zatvorena palata altki. Različite palate alatki imaju sopstvene osnovne petlje i čak i procesualne okvire. Na primer, ako želite da napišete GUI koji se integriše sa priključcima, teško je koristiti “select (odabir)” funkciju. Upotreba odabira zahteva da programer napiše sopstvenu osnovnu petlju, a to je u konfliktu sa paletama alatki koje daje pre-programirana osnovna petlja.
Rešenje: napraviti dve niti. Jedna nit je samo za GUI paletu alatki. Druga nit pokreće vašu sopstvenu personalizovanu osnovnu petlju. Dve niti komuniciraju tako što šalju poruke jedna drugoj (npr. korišćenjem sigurnosnih FIFO-a ili redova).
Napomena o nitima i GUI-u: važno je strogo pratiti model prenošenja poruka: vi u principu NE MOŽETE učiniti da višestruke niti pristupe GUI funkcijama da X11 biblioteke ne bi došlo do greške segmentacije; tako bi tačno jedna nit trebalo da bude u stanju da direktno upravlja GUI-em. Sve ostale niti mogu da upravljaju GUI-em indirektno, slanjem poruka toj jednoj direktno upravljačkoj niti. Objasniću kako se to radi u nastavku teksta.
Izražavanje. Mislim da je teško vizualizovati i razumeti strukture koje realizuju GUI palete alatki. Doduše, neke alatke imaju integrisanu XML podršku za konstruisanje njihovih GUI-a, ali mislim da to još uvek zahteva mnogo dodatnog učenja i da mu nedostaje fleksibilnosti koju HTML posebno nudi. Nakon rada i uživanja sa HTML-om neko vreme, počeo sam da mislim: šta ako bih mogao da koristim HTML za kreiranje GUI-a?
Rešenje: Ugradite web pretraživač u vaš GUI. Time ćete imati fleksibilnost korišćenja palete alatki za kreiranje nekoliko osnovnih stvari (kao što je meni i stavke u meniju), ali možete koristiti web pretraživač da radi sva teška podizanja. To stvara novi problem o kojem će se govoriti kasnije: kako prenositi poruke između pretraživača i vaše personalizovane osnovne petlje.
Na kraju sam došao do tih zaključaka nakon rada sa raznim petljama koje procesiraju događaje i koje uključuju priključke, serijske portove, audio podatke, MIDI podatke i GUI-e. Petljao sam nešto i sa MVC (Model View Controller) pristupom GUI dizajnu i mislim da je to pomoglo, ali još uvek nisam bio zadovoljan. Napokon sam pronašao nešto što je izgledalo da ima smisla kada sam počeo da radim na jednostavnim web aplikacijama. Web aplikacije imaju veoma strogu separaciju od GUI-a (pokreću se sa udaljenog pretraživača) od vašeg koda (pokreće se na serveru). Time vaš glavni kod ne mora biti zatvoren u GUI paletu alatki, sam za sebe. Ili bar ne mora tako da se oseća.
HTML je dobra tehnologija. On integriše nekoliko drugih tehnologija uključujući:
HTML se lako komponuje, što znači da je lako preslikati HTML dizajn tehnikom copy-paste. Uveren sam da je teže komponovati dizajn GUI alatki. Takođe postoji mnogo onlajn šablona i primera za izgradnju visoko kvalitetnih HTML dokumenata.
Moguće je petljati sa HTML korisničkim interfejsom, čak i kada nedostaje interna logika aplikacije. To znači da proces dizajniranja GUI-a ne mora biti strogo povezan sa pisanjem glavne logike aplikacije.
Druga dobra strana HTML-a je što je otvoren za ne-programere Postoji mnogo web programera koji mogu da doprinesu dizajniranju korisničkog interfejsa aplikacije, ako su ti interfejsi zasnovani na HTML-u.
Ovo je verovatno najjednostavniji deo ovog članka: pokretanje GTK-a u sopstvenoj niti ostavljajući tako osnovnu nit dostupnom za realizovanje naše personalizovane osnovne petlje:
import gtk import thread gtk.gdk.threads_init() thread.start_new_thread(gtk.main, ()) # Yes, it really is that easy. GTK is now running in its own thread, # waiting for us to send messages to it.
Nastavite sa čitanjem “Message Passing (Prenošenje poruka)” da biste videli šta dalje raditi.
Prenošenje poruka je sredstvo se kojim proces komunicira. I ljudi komuniciraju tako što pričaju jedni s drugima ili pišu poruke, a tako i kompjuterski programi jedni s drugima. Priključci i kanali su osnovni primeri tehnologije prenošenja poruka. Podeljena memorija se takođe može upotrebiti za prenošenje poruka.
Programiranje sa funkcijama ili objektima je takođe oblik prenošenja poruka. U tom slučaju, pozivanje funkcije ili metoda ekvivalentno je slanju poruke vašem programu ili drugom objektu u vašem programu. Suštinski, vaš program komunicira sa samim sobom. Ali to je i dalje prenošenje poruka. Isto kao što ljudi ponekad govore sami sa sobom (barem ja).
Neke biblioteke programski hezika imaju specijalne redove koji se mogu upotrebiti za prenošenje poruka između niti u aplikaciji sa višestrukim nitima. Neki jezici, Erlang ili Termite Scheme, imaju prenošenje poruka tako ukorenjeno da biste bili ludi ako ne biste koristili te funkcije.
Ovde ću objasniti prenošenje poruka između GTK-a i vaše prilagođene osnovne petlje. Ne objašnjavam prenošenje poruka unutar GTK-a (npr. između GTK grafičkih objekata - za to morate pročitati GTK dokument).
GTK ima tzv. petlju u stanju mirovanja. Ta petlja u mirovanju je način na koji postoji interfejst sa GTK osnovnom petljom. GTK ima specijalnu funkciju koja se zove “idle:add” koja nam omogućava da dodajemo poruke koje će se obrađivati u petlji koja miruje. Opisaću dva tipa poruka ovde:/p>
Asinhrone poruke
Ovo su poruke koje ubacujete u GTK petlju u mirovanju, ali ne brinete o tome da li ćete dobiti odgovor.
Sinhrone poruke
Ovo su poruke koje ubacujete u GTK petlju u mirovanju, ali brinete o tome da li ćete dobiti odgovor.
U Python programskom jeziku funkcije su građani prvog reda koji se mogu prenositi okolo kao normalni podaci. Time poruka zapravo postaje samo referenca funkcije (ili functor) koju injektujemo u GTK petlju u mirovanju. GTK petlja će jedva pozvati/izvršiti tu funkciju. U C, to je slično stavljanju pokazivača funkcije u red koju druga petlja vraća iz reda i zatim poziva funkciju udruženu sa tim pokazivačem funkcije.
Ovde je magičan trik za asinhrone GTK poruke u Pythonu. Usput, ako ovaj kod zvuči besmisleno, pročitajte o argumentima varijable i argumentima ključne reči u Pythonu. Takođe možete pročitati o torka otpakivanju u Pythonu. Možete pročitati o funkcijama višeg reda koje su koncept iz funkcionalnog programiranja (žao mi je, znam da je duga lista stvari koje treba pročitati):
import gobject # This is a function which takes a function as an argument and # returns yet another function (this is what higher order functions # are all about!). (Side note: this can be invoked/applied with # Python's decorator syntax, '@', if you so desire.): def asynchronous_gtk_message(fun): def worker((function, args, kwargs)): # Apply is a function that takes a function as its first # argument and calls it with positional argument taken # from "args" and keyword arguments taken from "kwargs": apply(function, args, kwargs) # This is a special type of function known as a "closure" # (although it is not quite as advanced as a closure in Lisp # or Ruby. Challenge question: do you know why?). In C++, # a class-based functor which defines operator() (or Boost.Lambda) # is the closest you can get to a closure: def fun2(*args, **kwargs): # "worker" is none other than the function just defined # above (with "def worker"): gobject.idle_add(worker, (fun, args, kwargs)) # Here, the closure is returned and must be called at some later # point in the program: return fun2
The code above has a lot of comments, so here is the same code again with all the comments stripped out (some people may find this easier to read):
import gobject def asynchronous_gtk_message(fun): def worker((function, args, kwargs)): apply(function, args, kwargs) def fun2(*args, **kwargs): gobject.idle_add(worker, (fun, args, kwargs)) return fun2
Iako je to kod od par redova, postoje prilično duboki koncepti programiranja koji su neophodni za razumevanje navedenog koda. Ali je sažeto i izražajno i prilično teško da se unesu greške (jer je toliko jednostavno).
Evo primera upotrebe "asynchronous_gtk_message" za manipulaciju grafičkim objektima web pretraživača (naročito WebKit) koji se pokreću u GTK-u:
browser = ... # read about synchronous_gtk_message below to see what goes here ... asynchronous_gtk_message(browser.execute_script)('alert("oops")') # or, alternatively: async_execute_script = asynchronous_gtk_message(browser.execute_script) async_execute_script('alert("oops")')
Obratite pažnju da "asynchronous_gtk_message" zapravo ne rade ništa. Sve što rade je povraćaj specijalne funkcije (sećate se zatvaranja o kojem smo pričali?). I to je ta specijalna funkcija koju moramo da pozovemo svaki put kada želimo da stvarno pošaljemo asinhronu poruku. Vidite kako ignorišemo povratnu vrednost poruke? E, to je ono što je čini asinhronom. I brzom.
Šta ako nam je potrebna povratna vrednost? Onda nam je potrebna sinhrona poruka. Recimo da želimo da pošaljemo poruku u GTK govoreći “molim te kreiraj novi GTK prozor i vrati mi referencu u taj novi prozor da je mogu kasnije doterati”. To je ono za šta su sinhrone poruke dobre. Treba im više za izvršenje jer morate sedeti i čekati povratnu vrednost (ima trikova da se preskoči to čekanje...ali to je za drugi članak drugog autora):
import time import gobject def synchronous_gtk_message(fun): class NoResult: pass def worker((R, function, args, kwargs)): R.result = apply(function, args, kwargs) # WARNING: I know the busy/sleep polling loop is going to offend # the sensibilities of some people. I offer the following # justifications: # - Busy/sleep loops are a simple concept: easy to # implement and they work in situations where you # may not have condition variables (like checking your # email with fetchmail). # - If you do use a synchronous message, it will probably # complete very rapidly, thus very few CPU cycles will # by wasted by this busy loop (thanks to the sleep). # - You probably shouldn't be using synchronous messages # very often anyhow. Async is cooler :-) # - If this code is anything bad, it is probably that the # sleep() adds a bit of undesired latency before the result # can be returned. # If this still doesn't appeal to you, then keep reading # because I do this again with condition variables. def fun2(*args, **kwargs): class R: pass R.result = NoResult gobject.idle_add(callable=worker, user_data=(R, fun, args, kwargs)) while R.result is NoResult: time.sleep(0.01) return R.result return fun2
Pa, to je malo komplikovanije od asinhronog slučaja. Osnovna razlika je dodataka “R.rezultata” i petlja koja čeka “R.rezultat” za referenciranje bilo čega osim “NemaRezultata”. Evo kompaktnijeg oblika navedenog koda koji je možda nekima draži:
import time import gobject # Slightly more compact version of the above code: def synchronous_gtk_message(fun): class NoResult: pass def worker((R, function, args, kwargs)): R.result = apply(function, args, kwargs) def fun2(*args, **kwargs): class R: result = NoResult gobject.idle_add(callable=worker, user_data=(R, fun, args, kwargs)) while R.result is NoResult: time.sleep(0.01) return R.result return fun2
Ako niste baš voljni da imate zauzetu/uspavanu petlju, evo druge verzije koja koristi uslovne varijable:
# non-busy/sleep version of the above code: def synchronous_gtk_message2(fun): import threading def worker((R, condition, function, args, kwargs)): R.result = apply(function, args, kwargs) condition.acquire() condition.notify() condition.release() def fun2(*args, **kwargs): condition = threading.Condition() condition.acquire() class R: pass gobject.idle_add(worker, (R, condition, fun, args, kwargs)) condition.wait() condition.release() return R.result return fun2
Evo još jedne opcije koja ne radi, pa je nemojte koristiti:
# non-working/broken version of the above code :-P def synchronous_gtk_message3(fun): # This doesn't work for me. Can anyone shed some light on this? # # Besides, http://library.gnome.org/devel/gdk/unstable/gdk-Threads.html # gives a warning that this may only work for X11 but not Win32: # # GTK+ is "thread aware" but not thread safe - it provides a global # lock controlled by gdk_threads_enter()/gdk_threads_leave() which # protects all use of GTK+. That is, only one thread can use GTK+ # at any given time. # # Unfortunately the above holds with the X11 backend only. With the # Win32 backend, GDK calls should not be attempted from multiple # threads at all. def fun2(*args, **kwargs): gtk.gdk.threads_enter() try: x = apply(fun, args, kwargs) finally: gtk.gdk.threads_leave() return x return fun2
U svakom slučaju, evo primera upotrebe"synchronous_gtk_message" poruka:
# Use synchronous messages here: window = synchronous_gtk_message(gtk.Window)() browser = synchronous_gtk_message(webkit.WebView)() # Use asynchronous messages here: asynchronous_gtk_message(window.set_default_size)(800, 600) asynchronous_gtk_message(window.show_all)()
Kada komunicirate sa WebKit-om biće dva tipa poruka. Za razliku od GTK-a, te dve WebKit poruke su obe asinhrone:
Asinhrono slanje (web_send / execute_script)
Da biste poslali poruku iz Pythona u WebKit, koristimo "execute_script" metod grafičkih objekata WebKit pretraživača. Postoji funkcija obmotavanja pod nazivom "web_send", koja će izazvati "execute_script"
Аsinhroni prijem (web_recv / title-changed)
Da bi WebKit poslao poruku Pythonu, potrebno je hakovanje. WebKit pretraživač izvodi povratni poziv koji se aktivira svaki put kada se “naslov” ugrađene web stranice promeni. Ne koristimo naslov ni za šta bolje, pa zašto ga onda ne oteti i upotrebiti za prenošenje poruka? Možemo povezati notifikaciju o događaju “promene naslova” WebKit pretraživača sa funkcijom koja stavlja u red vrednost naslova u redu. Tada se osnovna petlja može probuditi da proveri red ili može izlistati red po sopstvenom nahođenju. Postoji funkcija obmotavanja koja se zove "web_recv" koja vrši interfejs u tom redu.
Evo koda za lansiranje pretraživača i definisanje funkcija web_send i web_recv:
import Queue import gtk import webkit def launch_browser(uri, echo=True): # WARNING: You should call this function ONLY inside of GTK # (i.e. use synchronous_gtk_message) window = gtk.Window() box = gtk.VBox(homogeneous=False, spacing=0) browser = webkit.WebView() window.set_default_size(800, 600) # Optional (you'll read about this later in the tutorial): window.connect('destroy', Global.set_quit) window.add(box) box.pack_start(browser, expand=True, fill=True, padding=0) window.show_all() # Note: All message passing stuff appears between these curly braces: # { message_queue = Queue.Queue() def title_changed(widget, frame, title): if title != 'null': message_queue.put(title) browser.connect('title-changed', title_changed) def web_recv(): if message_queue.empty(): return None else: msg = message_queue.get() if echo: print '>>>', msg return msg def web_send(msg): if echo: print '<<<', msg asynchronous_gtk_message(browser.execute_script)(msg) # } browser.open(uri) return browser, web_recv, web_send uri = 'http://www.google.com/' browser, web_recv, web_send = synchronous_gtk_message(launch_browser)(uri)
Dalje, negde u vašem HTML/JavaScriptu moraćete da definišete "send()" funkciju koja šalje poruku u Python promenom HTML naslova. Evo primera koji preporučujem da se stavi u fajl pod imenom "ipc.js":
function send(msg) { document.title = "null"; document.title = msg; }
Procedura za GtkMozEmbed je veoma slična proceduri za WebKit, pa preporučujem da pročitate odeljak o WebKit-u. Ovde ću jednostavno objasniti razlike između WebKit-a i GtkMozEmbed.
import Queue import gtk #import webkit # <- webkit import gtkmozembed # <- gtkmozembed import urllib # <- gtkmozembed (for encoding JavaScript strings) def launch_browser(uri, echo=True): # WARNING: You should call this function ONLY inside of GTK # (i.e. use synchronous_gtk_message) window = gtk.Window() box = gtk.VBox(homogeneous=False, spacing=0) #browser = webkit.WebView() # <- webkit (obviously) browser = gtkmozembed.MozEmbed() # <- gtkmozembed # gtkmozembed only (for webkit, we use its .execute_script()): def inject_javascript(script): uri = 'javascript:%s' % urllib.quote(script + '\n;void(0);') browser.load_url(uri) window.set_default_size(800, 600) # Optional (you'll read about this later in the tutorial): window.connect('destroy', Global.set_quit) window.add(box) box.pack_start(browser, expand=True, fill=True, padding=0) window.show_all() # Note: All message passing stuff appears between these curly braces: # { message_queue = Queue.Queue() # webkit: #def title_changed(widget, frame, title): # if title != 'null': message_queue.put(title) # gtkmozembed: def title_changed(*args): title = browser.get_title() if title != 'null': message_queue.put(title) #browser.connect('title-changed', title_changed) # <- webkit browser.connect('title', title_changed) # <- gtkmozembed def web_recv(): if message_queue.empty(): return None else: msg = message_queue.get() if echo: print '>>>', msg return msg def web_send(msg): if echo: print '<<<', msg #asynchronous_gtk_message(browser.execute_script)(msg) # <- webkit asynchronous_gtk_message(inject_javascript)(msg) # <- gtkmozembed # } #browser.open(uri) # <- webkit browser.load_url(uri) # <- gtkmozembed return browser, web_recv, web_send uri = 'http://www.google.com/' browser, web_recv, web_send = synchronous_gtk_message(launch_browser)(uri)
....i ne zaboravite na "ipc.js" (opisano u odeljku o WebKit-u):
function send(msg) { document.title = "null"; document.title = msg; }
Prilikom izlaska iz aplikacije sa višestrukim nitima morate biti u stanju da navedete sve niti da se okončaju. Takođe, Python ima jednu neprijatnost povezanu sa signalima i nitima: nikada ne možete znati koja nit kao signal, kao Ctrl-C/SIGINT, će biti poslata.
Oba ova problema sam rešio deklarisanjem podeljene, globalne “quit” varijable. Zatim sam instalirao SIGINT (Ctrl-C) upravljač signalima koji podešava “quit” varijablu na True (istinito) ako se aktivira. Neće biti važno koja nit zapravo prima SIGINT jer će ista globalna “quit” varijabla biti podešena u svim slučajevima.
Svaki proces/nit u programu može navesti program da se okonča podešavanjem ove globalne “quit” varijable. Na primer, ako korisnik klinke File/Quit stavku u meniju ili pritisne Ctrl-Q,, ceo program će se okončati podešavanje “quit” varijable.
Without further ado, here's the code:
import signal class Global(object): quit = False @classmethod def set_quit(cls, *args, **kwargs): cls.quit = True def my_quit_wrapper(fun): signal.signal(signal.SIGINT, Global.set_quit) def fun2(*args, **kwargs): try: x = fun(*args, **kwargs) # equivalent to "apply" finally: asynchronous_gtk_message(gtk.main_quit)() Global.set_quit() return x return fun2
А evo kako da je koristite:
def main(): while not Global.quit: # ... do some stuff ... time.sleep(0.1) if __name__ == '__main__': # <-- this line is optional my_quit_wrapper(main)()
Morate instlirati sledeće pakete:
U Debian/Ubuntu možete pratiti ove korake da instalirate ove pakete automatski:
apt-get install python-webkitgtk apt-get install python-simplejson # ummm... python-gtkmozembed...?
U Gentoo/Funtoo:
#emerge -va pywebkitgtk # <-- not yet working with Python 2.6 :-( #emerge -va simplejson # <-- installs Python 2.5 :-( emerge gtkmozembed-python # yay! finally something that works! easy_install simplejson # this is how we bypass emerge
Ovaj mali JavaScript modul će se koristiti da omogući slanje poruka iz pretraživača u Python. ipc.js:
ipc.js:
function send(msg) { document.title = "null"; document.title = msg; }
Sledeći kod koristi standardni JavaScript (jedina nedoumica mi je da li je "DOMContentLoaded" standardan, ali WebKit ga svakako svejedno podržava). Iako ga nijedan primer ovde ne zahteva, ja preporučujem da koristite jQuery (osim ako nema neke druge JavaScript biblioteke koju više volite). jQery vam omogućava da vršite navigaciju kroz XHTML korišćenjem CSS Selectora. jQery veoma olakšava konfiguraciju notifikacije o događajima i modifikovanje strukture XHTML dokumenata. jQery takođe ima sposobnost da radi sa agregatima: čineći da se jedinstvena operacija simultano primeni na nekoliko XML elemenata.
demo.xhtml:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <head> <title></title> <script src="ipc.js" type="text/javascript"></script> <!-- Here are some jQuery plugins that I recommend: <script src="js/jquery-1.2.6.min.js" type="text/javascript"></script> <script src="js/jquery-plugins/json.js" type="text/javascript"></script> <script src="js/jquery-plugins/jquery.hotkeys-0.7.8.js" type="text/javascript"></script> <script src="js/jquery-plugins/jquery.intercept.js" type="text/javascript"></script> --> <link href="demo.css" rel="stylesheet" type="text/css"></link> <script type="text/javascript"> // <![CDATA[ // NOTE: jQuery would make this all much cleaner! You need it! function got_a_click() { // NOTE: send is a JavaScript function that is defined in "ipc.js". // This send function is how messages are sent from HTML // to Python: send('"got-a-click"'); } function document_ready() { send('"document-ready"'); document.getElementById('messages').addEventListener('click', got_a_click, false); } document.addEventListener('DOMContentLoaded', document_ready, false); // If you were using jQuery (with the JSON plugin), you'd write the above // code like this: // // Instead of addEventListener('DOMContentLoaded', ...), do this: // $(document).ready(function() { // // Instead of getElementById('messages').addEventListener('click',...), // // do this: // // http://docs.jquery.com/Events/bind // $('#messages').bind('click', got_a_click); // // ...or try this shortcut version of bind: // $('#messages').click(got_a_click); // // http://jollytoad.googlepages.com/json.js provides $.toJSON(...): // send($.toJSON('document.ready')); // }) // ]]> </script> </head> <body> <h1>Python + Web GUI Demo</h1> <h2>Uptime</h2> <p class="uptime"> Python uptime: <span id="uptime-value">?</span> seconds. </p> <h2>Messages</h2> <p id="messages"> Click here (yes, anywhere here)...<br/> </p> </body> </html>
Kod je podeljen na dva modula: webgui.py i demo.py. Oba su prikazana u nastavku:
webgui.py:
import time import Queue import thread import urllib import gtk import gobject try: import webkit have_webkit = True except: have_webkit = False try: import gtkmozembed have_gtkmozembed = True except: have_gtkmozembed = False class UseWebKit: pass class UseGtkMozEmbed: pass if False: pass elif have_webkit: use = UseWebKit elif have_gtkmozembed: use = UseGtkMozEmbed else: raise Exception('Failed to load any of webkit and gtkmozembed modules') #use = UseGtkMozEmbed # <- choose your desired implementation here class WebKitMethods(object): @staticmethod def create_browser(): return webkit.WebView() @staticmethod def inject_javascript(browser, script): browser.execute_script(script) @staticmethod def connect_title_changed(browser, callback): def callback_wrapper(widget, frame, title): callback(title) browser.connect('title-changed', callback_wrapper) @staticmethod def open_uri(browser, uri): browser.open(uri) class GtkMozEmbedMethods(object): @staticmethod def create_browser(): return gtkmozembed.MozEmbed() @staticmethod def inject_javascript(browser, script): uri = 'javascript:%s' % urllib.quote(script + '\n;void(0);') browser.load_url(uri) @staticmethod def connect_title_changed(browser, callback): # XXX: probably you should cross your fingers and hope browser # isn't sending title messages too quickly...? def callback_wrapper(*args): callback(browser.get_title()) browser.connect('title', callback_wrapper) @staticmethod def open_uri(browser, uri): browser.load_url(uri) if use is UseWebKit: implementation = WebKitMethods if use is UseGtkMozEmbed: implementation = GtkMozEmbedMethods def asynchronous_gtk_message(fun): def worker((function, args, kwargs)): apply(function, args, kwargs) def fun2(*args, **kwargs): gobject.idle_add(worker, (fun, args, kwargs)) return fun2 def synchronous_gtk_message(fun): class NoResult: pass def worker((R, function, args, kwargs)): R.result = apply(function, args, kwargs) def fun2(*args, **kwargs): class R: result = NoResult gobject.idle_add(worker, (R, fun, args, kwargs)) while R.result is NoResult: time.sleep(0.01) return R.result return fun2 def launch_browser(uri, quit_function=None, echo=True): window = gtk.Window() browser = implementation.create_browser() box = gtk.VBox(homogeneous=False, spacing=0) window.add(box) if quit_function is not None: # Obligatory "File: Quit" menu # { file_menu = gtk.Menu() quit_item = gtk.MenuItem('Quit') accel_group = gtk.AccelGroup() quit_item.add_accelerator('activate', accel_group, ord('Q'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) window.add_accel_group(accel_group) file_menu.append(quit_item) quit_item.connect('activate', quit_function) quit_item.show() # menu_bar = gtk.MenuBar() menu_bar.show() file_item = gtk.MenuItem('File') file_item.show() file_item.set_submenu(file_menu) menu_bar.append(file_item) # } box.pack_start(menu_bar, expand=False, fill=True, padding=0) if quit_function is not None: window.connect('destroy', quit_function) box.pack_start(browser, expand=True, fill=True, padding=0) window.set_default_size(800, 600) window.show_all() message_queue = Queue.Queue() def title_changed(title): if title != 'null': message_queue.put(title) implementation.connect_title_changed(browser, title_changed) implementation.open_uri(browser, uri) def web_recv(): if message_queue.empty(): return None else: msg = message_queue.get() if echo: print '>>>', msg return msg def web_send(msg): if echo: print '<<<', msg asynchronous_gtk_message(implementation.inject_javascript)(browser, msg) return browser, web_recv, web_send def start_gtk_thread(): # Start GTK in its own thread: gtk.gdk.threads_init() thread.start_new_thread(gtk.main, ()) def kill_gtk_thread(): asynchronous_gtk_message(gtk.main_quit)()
demo.py:
import signal import os import time import urllib from simplejson import dumps as to_json from simplejson import loads as from_json from webgui import start_gtk_thread from webgui import launch_browser from webgui import synchronous_gtk_message from webgui import asynchronous_gtk_message from webgui import kill_gtk_thread class Global(object): quit = False @classmethod def set_quit(cls, *args, **kwargs): cls.quit = True def main(): start_gtk_thread() # Create a proper file:// URL pointing to demo.xhtml: file = os.path.abspath('demo.xhtml') uri = 'file://' + urllib.pathname2url(file) browser, web_recv, web_send = \ synchronous_gtk_message(launch_browser)(uri, quit_function=Global.set_quit) # Finally, here is our personalized main loop, 100% friendly # with "select" (although I am not using select here)!: last_second = time.time() uptime_seconds = 1 clicks = 0 while not Global.quit: current_time = time.time() again = False msg = web_recv() if msg: msg = from_json(msg) again = True if msg == "got-a-click": clicks += 1 web_send('document.getElementById("messages").innerHTML = %s' % to_json('%d clicks so far' % clicks)) # If you are using jQuery, you can do this instead: # web_send('$("#messages").text(%s)' % # to_json('%d clicks so far' % clicks)) if current_time - last_second >= 1.0: web_send('document.getElementById("uptime-value").innerHTML = %s' % to_json('%d' % uptime_seconds)) # If you are using jQuery, you can do this instead: # web_send('$("#uptime-value").text(%s)' # % to_json('%d' % uptime_seconds)) uptime_seconds += 1 last_second += 1.0 if again: pass else: time.sleep(0.1) def my_quit_wrapper(fun): signal.signal(signal.SIGINT, Global.set_quit) def fun2(*args, **kwargs): try: x = fun(*args, **kwargs) # equivalent to "apply" finally: kill_gtk_thread() Global.set_quit() return x return fun2 if __name__ == '__main__': # <-- this line is optional my_quit_wrapper(main)()
Appcelerator Titanium - this is a tool for creating desktop applications using web technology. It appears to be a nicely integrated environment and can support the use of mixed languages, including Python, Ruby, and C++ as well as JavaScript:
Awesomium "Awesomium is a library that makes it easy for developers to embed web-content in their applications."
Python and XULRunner (I haven't done this yet, but might be useful):
CSS Selectors (and here for CSS3)
JavaScript (ECMAScript)
XML DOM
Embedding SVG in XHTML
HTML5 Canvases
MathML
- http://en.wikipedia.org/wiki/MathML
- http://www.w3.org/TR/REC-MathML/
- NOTE: To my knowledge, MathML is not supported in WebKit yet.
jQuery
- http://jquery.com/
- http://www.youtube.com/watch?v=8mwKq7_JlS8 jQuery at Google TechTalks (yes indeed, a 12-year old is giving the talk) (note: fast forward about 3 minutes in to get to the core of the talk)
JSON (a good compromise when XML is too much and S-expressions are too little ... assuming you're not obsessed with YAML ;-))
Creating quality design with HTML and CSS
- http://www.alistapart.com/ One of my favorite design sites
- Jeffrey Zeldman's book "Designing With Web Standards" (this book is what motivated me to take time to learn web standards)
X3D (the XML version of VRML)...? I'm not really sure how to get this working yet. If you have any thoughts, drop me a line.