Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you design a very "Pythonic" UI framework?

I have been playing with the Ruby library "shoes". Basically you can write a GUI application in the following way:

Shoes.app do
  t = para "Not clicked!"
  button "The Label" do
    alert "You clicked the button!" # when clicked, make an alert
    t.replace "Clicked!" # ..and replace the label's text
  end
end

This made me think - how would I design a similarly nice-to-use GUI framework in Python? One that doesn't have the usual tyings of basically being wrappers to a C* library (In the case of GTK, Tk, wx, QT etc etc)

Shoes takes things from web devlopment (like #f0c2f0 style colour notation, CSS layout techniques, like :margin => 10), and from ruby (extensively using blocks in sensible ways)

Python's lack of "rubyish blocks" makes a (metaphorically)-direct port impossible:

def Shoeless(Shoes.app):
    self.t = para("Not clicked!")

    def on_click_func(self):
        alert("You clicked the button!")
        self.t.replace("clicked!")

    b = button("The label", click=self.on_click_func)

No where near as clean, and wouldn't be nearly as flexible, and I'm not even sure if it would be implementable.

Using decorators seems like an interesting way to map blocks of code to a specific action:

class BaseControl:
    def __init__(self):
        self.func = None

    def clicked(self, func):
        self.func = func

    def __call__(self):
        if self.func is not None:
            self.func()

class Button(BaseControl):
    pass

class Label(BaseControl):
    pass

# The actual applications code (that the end-user would write)
class MyApp:
    ok = Button()
    la = Label()

    @ok.clicked
    def clickeryHappened():
        print "OK Clicked!"

if __name__ == '__main__':
    a = MyApp()
    a.ok() # trigger the clicked action

Basically the decorator function stores the function, then when the action occurred (say, a click) the appropriate function would be executed.

The scope of various stuff (say, the la label in the above example) could be rather complicated, but it seems doable in a fairly neat manner..

like image 681
dbr Avatar asked Sep 12 '08 11:09

dbr


3 Answers

You could actually pull this off, but it would require using metaclasses, which are deep magic (there be dragons). If you want an intro to metaclasses, there's a series of articles from IBM which manage to introduce the ideas without melting your brain.

The source code from an ORM like SQLObject might help, too, since it uses this same kind of declarative syntax.

like image 50
Justin Voss Avatar answered Oct 17 '22 17:10

Justin Voss


I was never satisfied with David Mertz's articles at IBM on metaclsses so I recently wrote my own metaclass article. Enjoy.

like image 4
Kris Kowal Avatar answered Oct 17 '22 16:10

Kris Kowal


This is extremely contrived and not pythonic at all, but here's my attempt at a semi-literal translation using the new "with" statement.

with Shoes():
  t = Para("Not clicked!")
  with Button("The Label"):
    Alert("You clicked the button!")
    t.replace("Clicked!")

The hardest part is dealing with the fact that python will not give us anonymous functions with more than one statement in them. To get around that, we could create a list of commands and run through those...

Anyway, here's the backend code I ran this with:

context = None

class Nestable(object):
  def __init__(self,caption=None):
    self.caption = caption
    self.things = []

    global context
    if context:
      context.add(self)

  def __enter__(self):
    global context
    self.parent = context
    context = self

  def __exit__(self, type, value, traceback):
    global context
    context = self.parent

  def add(self,thing):
    self.things.append(thing)
    print "Adding a %s to %s" % (thing,self)

  def __str__(self):
    return "%s(%s)" % (self.__class__.__name__, self.caption)


class Shoes(Nestable):
  pass

class Button(Nestable):
  pass

class Alert(Nestable):
  pass

class Para(Nestable):
  def replace(self,caption):
    Command(self,"replace",caption)

class Command(Nestable):
  def __init__(self, target, command, caption):
    self.command = command
    self.target  = target
    Nestable.__init__(self,caption)

  def __str__(self):
    return "Command(%s text of %s with \"%s\")" % (self.command, self.target, self.caption)

  def execute(self):
    self.target.caption = self.caption
like image 4
Nick Retallack Avatar answered Oct 17 '22 17:10

Nick Retallack