Back to site
Since 2004, our University project has become the Internet's most widespread web hosting directory. Here we like to talk a lot about web servers, web development, networking and security services. It is, after all, our expertise. To make things better we've launched this science section with the free access to educational resources and important scientific material translated to different languages.

Python GUI

KAKO napraviti Python GUI pomoću HTML-a

Istorija

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.

Pregled

U ovom priručniku objašnjavam sledeće koncepte:

  1. Kako da pokrenete GUI paletu alatki u odvojenoj niti i kako da komunicirate sa tom niti.
  2. Kako da ugradite novi pretraživač unutar GUI-a i kako da komunicirate sa tim ugrađenim web pretraživačem, a da ne koristite priključke. Kada ne koristimo priključaka ne moramo brinuti o njihovoj bezbednosti i ne moramo brinuti o raspodeli priključaka (napomena: priključci su još uvek dostupni, ali ne morate ih koristiti ako ne želite). Nije potreban apsolutno nikakav web server (osim ukoliko ne želite da koristite neki iz bilo kog razloga). Drugim rečima, imaćemo AJAX-like i AJAX-Push/Comet-like funkcionalnost za komunikaciju sa Python-om, ali zapravo nećemo morati da koristimo AJAX.

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:

  • Ovaj priručnik ne govori o pokretanju web servera niti o načinu korišćenja web okvira. Ne morate pokretati web server niti morate uopšte znati kako da pokrenete web server da biste koristili ovaj priručnik.

Моtivacija

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:

  1. 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.

  2. 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:

  • CSS
  • JavaScript (ECMAScript)
  • XML DOM
  • SVG
  • MathML
  • HTML5 Canvases
  • Nekoliko JavaScript ekstenzija kao što je jQuery
  • ...a lista još uvek raste...

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.

Pоkretanje paleta alatki u odvojenoj niti

Pоkretanje GTK-a

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

Uvod

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.

Prenošenje poruka pomoću GTK-a

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>

  1. Asinhrone poruke

    Ovo su poruke koje ubacujete u GTK petlju u mirovanju, ali ne brinete o tome da li ćete dobiti odgovor.

  2. 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.

Asinhrone GTK poruke

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.

Sinhrone GTK poruke

Š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)()

Prenošenje poruka pomoću WebKit-a

Kada komunicirate sa WebKit-om biće dva tipa poruka. Za razliku od GTK-a, te dve WebKit poruke su obe asinhrone:

  1. 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"

  2. А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;
}

Prenošenje poruka pomoću Mozille (GtkMozEmbed)

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;
}

Glancanje

Quit omotač

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

Kоmpletan primer

Preduslovi

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

JavaScript Kod

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;
}

HTML Kod

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>

Python Kod

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

Snimak ekrana

im-edited/demo-screenshot.png

Izvori

Published (Last edited): 11-03-2013 , source: http://www.aclevername.com/articles/python-webgui/