Friday, June 10, 2011

Calendar Update Part II - After The Release

Following up on my promise to detail some of my future plans. But first, a word about that nasty memory leak.

Memory leak in pycairo or pango/cairo.


I am 80% confident that there's a bug in the python bindings shipped on ubuntu 10.10. I developed a minimal example, (full source here), which just draws some text repeatedly in a window. The relevant bit of this example is the following function:

def draw_text(self, cr, text, x, y, width):
        pcr = pangocairo.CairoContext(cr)
        lyt = pcr.create_layout()
        lyt.set_text(text)
        lyt.set_width(pango.units_from_double(width))
        lyt.set_wrap(pango.WRAP_WORD_CHAR)
        cr.move_to(x, y)
        pcr.show_layout(lyt)
        return lyt.get_pixel_size()[1]

This little script will om nom nom your memory -- over 100 mb of memory after just a few minutes, and I think we can all agree that even if this it's not the most efficient example, its memory usage should be nearly constant. I haven't yet opened a bug, because I still don't know which component(s) contain the actual leak. At least feel better that it's probably not something I'm doing wrong, which gives me the confidence to move forward with the release.

To mitigate against the leak, I threw in an optimization that ensures only one pangocairo context and one text layout is created, per expose event, per widget. This seems like a reasonable thing to do in general, so I will probably leave this optimization in even after the bug is fixed. Unfortunately, all this does is slow down the leak. The leak is still there, which means you still wouldn't want to leave the calendar application running indefinitely.

In passing I'd like to say that the idea that a pango layout needs to be associated to a particular context bothers me a lot. I wonder if I can get away with re-using layouts with different pangocairo contexts.

A better grammar

The current parser for recurrence expressions could use some serious help. The parser for the existing grammar is in the "proof-of-concept" stage of development. It's intuitive for simple things, but you will notice that parenthesis are needed to get correct parsing of more complicated examples. This is due to unresolved ambiguities in the grammar.

Most of the ambiguity could likely be resolved with associativity and precedence declarations, but when you're developing a new notation it's difficult to know in advance what the intiutive associativity is. Basically I didn't bother trying to get it right at all, and rely on parenthesis as a crutch. I recognize that this neither intuitive nor natural. Even I am surprised at how wrong the unparenthesized parse can be.

I am also aware that this approach is horribly anglo-centric. You would have to write separate parsers for  every language you wanted to support, and it's a good bet that language constructs don't always map onto each other so neatly.

I am also playing around with graphical approaches to the same problem, but to be perfectly honest they've all sucked so far. I might do a separate posting on one of them just to show you how god-awful it is.

A mouse-driven way to add recurrence exceptions.

When dragging a recurring event, the current behaviour is to shift the entire recurrence pattern. This is a deliberate choice on my part: it's a novel feature that I want to highlight it. Unfortunately, many times time what you actually want to do is shift just the occurrence you've selected, and occasionally you want to move all the events after the selected occurrence.

Note, you can do this now: however, you have to go through the text interface. Even I find this quite tedious. The solution most obvious to me is to introduce control and meta modifier keys, but I have found that other users do not share my enthusiasm for mouse modifiers. In usability parlance, we say that   modifiers are less discoverable than more explicit features.

Another idea would be to add some tool bar buttons that silently add modify the recurrence rules of the selected event. This would be fairly easy to do, but I have a couple of misgivings. The first is that the toolbar is getting crowded already. The second is that it won't be obvious what those buttons do at first. Clicking on the "except this occurrence" button will not produce an immediately visible change, only modify the behavior of future interaction. This would be especially be confusing if you hit the button by accident.

A more radical idea would be to place special "handles" on the clip itself. That way dragging from a handle could have a different meaning from dragging the clip itself. I am leaning towards this approach, but it is not without drawbacks of its own. For example, the handles compete for space with the event text. They will necessarily be small, which could hurt usability in a tablet.

Integration with other calendars

Syncing with Google Calendar and / or EDS
importing from, and exporting to, .ics.

This work ought to be pretty straight-forward given how the code is organized, but I haven't really looked in detail at the APIs / File formats I'd be working with. If you're interested in helping out with that, you know where to find me ;)

Event alarms

This might not be necessary if I can use something like EDS as the default back-end. But we'll see. Depending on how hard it is to map the concepts in my application onto the APIs exposed by EDS, it might be easier to roll my own implementation.

Wednesday, June 8, 2011

Calendar Update



What a crazy couple of months it's been. I have been back in San Francisco since late February, but not found the motivation or time to post until now. Among other things, I've been to racing school, and I've taken up drumming as a new hobby. I also attended the meego conference here, where I was showing off my QML GES demo (another post about this is still in progress).

I thought I'd take some time to write about the work I've done on my desktop calendar application over the past several months. This will stop me from pestering my friends about it, which will make them a lot happier.

  • Major cosmetic face-lift.
  • Added an infinite-scrolling month view.
  • Added support for all-day events, which are displayed in a pane along the top row.
  • Added support for recurring events via a natural-ish language parser which supports english-like recurrence expressions such as "every two days from now until october", or "the 3rd wednesday of every month except this wednesday"
  • You can easily shift the entire recurrence pattern by dragging a single occurrence with the mouse
  • Possible to select the same block of time across multiple days.
  • Added support for editable event text.
  • Removed dependency on goocanvas. Since all the drawing code is custom, and the entire widget is re-painted on every expose, there really is no point in using goocanvas.




I am considering making an initial release, but would like to address the following issues:
  • I need to come up with a name. As my music teacher once told me, "Name your children." Anything is better than cal.py.
  • Adopt some system for installation, most likely distutils.
  • Currently the calendar widget leaks about 100kb of memory for every expose event. After a few minutes of scrolling and dragging it's pushing 100mb. It's obviously a bug, and I've narrowed it down to the functions I wrote to simplify drawing text. Disabling these functions reduces the memory consumption to a much more reasonable 15-ish mb, Either I am doing something memory inefficient in python, or I've found a leak in pango / cairo. I would very much like to understand that better.
  • I recently added all-day events. You can create an event as an all-day event, or you can drag an existing event to the all-day area. But you can't drag an all-day event back into the timed area.
I did some "usability testing" on a hapless friend of mine, and the results were encouraging but showed there was a great deal of room for improvement. Unfortunately, my select-then-add paradigm of creating new events is confusing for those used to applications like google calendar and evolution. In evolution I find the click-to-create behaviour frustrating. Google calendar, on the other hand, seems to get it right.

After the release
This post is getting long enough already. I've also got some ideas for subsequent improvements, which I will summarize here. TTFN

  • A better grammar
  • A mouse-driven way to add recurrence exceptions.
  • Integration with other calendars
  • Event alarms

Friday, November 26, 2010

Testing Interactive Code

The preceding work on the Render and Project Settings dialogs in PiTiVi has led to some fairly complicated UI logic, and we haven't really got a good way to test it.

Imagine that we want to test the following trivial python UI
import gtk
import gobject
import random

def b_clicked_cb(button):
    clicked = True
    button.props.label = "Ouch!"

w = gtk.Window()
v = gtk.VBox()
b = gtk.Button("Click me")
b.connect("clicked", b_clicked_cb)
v.pack_start(b)
w.add(v)
w.show_all()

gtk.main()
We want to check that the button label is correctly changed after being clicked. Because of control inversion, we obviously can't code in a direct, sequential style. At the very least we must install a timeout or idle function into the main loop.

This is a naive approach:
def test_case():
    b.activate()
    assert b.props.label == "Ouch!"
    return False

gobject.timeout_add(1000, test_case)
gtk.main()

It doesn't work because of timing issues. The button's label won't change immedately after activate() is called, so you get the following error -- despite the fact that the the label clearly changed.

Traceback (most recent call last):
  File "figure_1.py", line 19, in test_case
    assert b.props.label == "Ouch!"
AssertionError

The obvious solution is to split the callback in twain:

def test_case():
    b.activate()
    gobject.timeout_add(1000, finish_test)
    return False

def finish_test():
    assert b.props.label == "Ouch!"
    gtk.main_quit()
    return False

gobject.timeout_add(1000, test_case)
gtk.main()

It's good enough for this trivial example, but it should be clear that as the complexity of the UI increases you'll end up with yet another maze of callbacks.

Last night I experimented with applying python generator functions to this problem. The idea is to (ab)use the yield keyword as a way of passing control between the test and the mainloop. It's similar to concepts presented in this paper, which describes how continuations can be used to solve the problem of control inversion in web programming.

Quick summary of python generator functions: the yield keyword in python is a limited form of either lazy evaluation or continuations, depending on your point of view. Usually we think of generators as sequences. For example this generator can be thought of as the sequence of positive integers from 1 to 10
def ints():
   i = 1
   while i <= 10:
      yield i
      i += 1
I can treat it as sequence and take its sum, or iterate over it:
print sum(ints())
squares = [x ** 2 for x in ints()]
What actually happens when squares is called is that an iterator is created and returned to the caller. Yield indicates to the interpreter that squares() is a generator, and that the state should be saved so it can be re-entered later. The fact that the state of the function is saved inside iterator allows us to think of the iterator as a continuation (generators are not as powerful as continuations). With this in mind, we re-write our test function:
def test_case():
    b.activate()
    yield
    assert b.props.label == "Ouch!"
Here the yield keyword is going to provide the same control flow boundary that splitting our code into separate functions did earlier. Now all we need is a bit of code to consume values from this iterator until it is exhausted. We do this using timeouts -- as we did in earlier examples -- which allows the mainloop to continue running in between the two halves of our test case.
def run_test_case(iterator):
    print "Tick"
    try:
        iterator.next()
    except StopIteration:
        print "Test Case Finished Successfully"
        gtk.main_quit()
        return False
    except Exception, e:
        print "An error occured"
        gtk.main_quit()
        return False
    return True

gobject.timeout_add(1000, run_test_case, test_case())
gtk.main()
This is already an improvement, but not yet flexible enough. Suppose we add a new widget:
def c_clicked_cb(button):
    def set_label_async():
        c.props.label = "Ouch!"

    gobject.timeout_add(random.randint(500, 5000),
        set_label_async)
c = gtk.Button("Async Operation")
c.connect("clicked", c_clicked_cb)
c.show()
v.pack_start(c)
Now we have a problem: we have no idea when the action triggered by clicking the second button will complete. Simply waiting for one second will not always work. At the very least we should be able to override the default sleep value. But it would be better still if we could wait until the label value itself is changed. That way if the action takes only a short time, we don't have to wait, while if the action takes longer than expected, the test can still finish successfully. In other words, we shouldn't just assume that after each step in the test we straight on to the next one. Let's factor out portion of the loop that does the scheduling:
class Sleep(object):

    def __init__(self, timeout=1000):
        self.timeout = timeout

    def schedule(self, iterator):
        gobject.timeout_add(self.timeout, run_test_case, iterator)

def run_test_case(iterator):
    print "Tick"
    try:
        scheduler = iterator.next()

    except StopIteration:
        print "Test Case Finished Successfully"
        gtk.main_quit()
        return False

    except Exception, e:
        print "An error occured"
        gtk.main_quit()
        return False

    scheduler.schedule(iterator)
    return False
Now we can easily customize the timeout for the second button specifying a Sleep scheduler with a different timeout.
def test_case():
    b.activate()
    yield Sleep()
    assert b.props.label == "Ouch!"

    c.activate()
    yield Sleep(6000)
    assert c.props.label == "Ouch!"
Actually we can go one better. We don't have to rely on timeouts for scheduling at all. For example, we can easily define a scheduler that will wait for a signal to fire:
class WaitForSignal(object):

    def __init__(self, obj, signame):
        self.obj = obj
        self.signame = signame
        self.iterator = None
        self.sigid = None

    def schedule(self, iterator):
        self.sigid = self.obj.connect(self.signame, self._handler)
        self.iterator = iterator

    def _handler(self, *args):
        run_test_case(self.iterator)
        self.obj.disconnect(self.sigid)
Adopting this is just a one line change to test_case():
def test_case():
    b.activate()
    yield Sleep()
    assert b.props.label == "Ouch!"

    c.activate()
    yield WaitForSignal(c, "notify::label")
    assert c.props.label == "Ouch!"
And we don't have to touch run_test_case() at all. I think this idea could be expanded into a framework for testing event-driven code. True, I would want much better error reporting. But with just that, it would be pretty straight-forward to cover every part of PiTiVi's interface except the timeline (for the Timeline, I need is the ability to synthesize raw input). If necessary, I can include other types of scheduling scenarios, such as waiting for a file or socket access. And, because I can work with the widgets directly, it's possible to verify conditions that would be impossible to check for under Dogtail or LDTP (which are both limited to what AT-SPI exposes, and run the test from a separate process). Full Source

And the winner is...

The overwhelming majority went for version B. I changed the layout a bit, and a cleaned up version is what's in my branch now. Thanks, everyone.

Thursday, November 18, 2010

Round 4

So it's down to two competing ideas. At this point my brain is shot and I can't really decide between them. So I've decided to put them up here and see what people think. The best way to compare the two is right-click the images into separate tabs and switch between them a few times. Take a good hard look at each one.

What I'm looking for here is clarity of ideas, not cosmetic considerations like alignment and balance. That is, which of these dialogs is easier to understand?

A


B













Now that you've done that, does anything seem to be missing from either one that would make it an improvement over the other?

Wednesday, November 17, 2010

Round 3

09:38 < bemasc> emdash: So, the true parameters here are the storage height,
storage width, PAR, and DAR.
09:39 < bemasc> that's 4 values in total, but only 3 degrees of freedom.
09:40 < bemasc> Personally, I would expose all 4, and update PAR after each change to DAR (and vice versa)

How about this, then?
Starting to look a bit better? I kept the notion of 'Display resolution' mainly as a source of feedback, but it occurs to me now that perhaps little rectangle that represents the display aspect ratio would be a better idea. Unfortunately that would mean increasing the size of the dialog again.

I'm actually not so sure that DAR and PAR should update each other, as I played around with it and it's somewhat frustrating. I think most of the time you're either targeting a specific format (for example, 480i) in which case you'd just choose a preset, or you'll be working with square pixels.

I think i need to add a 'constrain aspect ratio' check-button to make scaling a bit easier.

Project Settings Progress

Re-worked it a bit this afternoon. But i'm still not happy with it. I think it might be too confusing to expose pixel-aspect-ratio to the user (hell, it's confusing enough for the programmer).



Another idea: present display width / height, optionally allowing the user to constrain to an aspect ratio. Separately present content (storage) width / height, and finally show the pixel-aspect-ratio as a read-only label, as a form of feedback. In other words, PAR would be computed working backward from display and content resolutions.

Mockup: The diagram on the bottom shows how updates
would propagate between widgets