Archive for the ‘Software Development’ Category

Hover on Multi-touch Devices

Saturday, August 14th, 2010

I try to keep this blog focused on software development: design, bug fixes, etc. That’s why the following post originally appeared elsewhere.

It appears here, now, because it’s loosely connected to software development and because — well, it’s interesting (to me). I’ve been seeing so many references to the idea of proximity-based user interfaces, it seems likely that “hover-free Mobile Safari” will become a historical anomaly.


Original Post (2010/06/23)

Technical Note TN2262: Preparing Your Web Content for iPad:

“For example, a mouse pointer can hover over a webpage element and trigger an event; a finger on a Multi-Touch screen cannot.”

This is certainly true for multi-touch devices available now, but I wouldn’t be surprised to see, someday, touch-sensitive devices which are also proximity-sensitive.

Update 2010/07/08: Others are thinking about implications of hover on multi-touch devices: http://trentwalton.com/2010/07/05/non-hover/ via http://news.ycombinator.com/item?id=1497108


I’m not a Star Trek fan, but a recent Ars Technica article, exploring the similarities between the iPad and ST:TNG’s PADD, caught my eye. It provides several good examples of how science fiction guides technology development. This excerpt seems particularly relevant:

“Still, what new frontiers are out there for interacting with computing devices? Michael Okuda believes that removing the touch requirement will bring new advances in gesture-based control. “Once you don’t have to physically touch the screen,” he told Ars, “I think yet another window is going to open up.”"

Good on you, Wolf Rentzch

Wednesday, May 12th, 2010

rentzsch.tumblr.com: [C4 release]:

“With resistance to Section 3.3.1 so scattershot and meek, it’s become clear that I haven’t made the impact I wanted with C4. It’s also clear my interests and the Apple programming community’s interests are farther apart than I had hoped.”

I signed up for an iPhone developer account this spring, just before the change to the developer agreement which included the revised 3.3.1. The change was galling. Never mind Flash; Apple was showing what it thinks of its developer community. “We can further limit you any time we want. Not a big deal. Just rewrite your code.”

I bought back into Apple in 2002, drawn by OS X. OS X developers are still free from the strictures imposed on iP* developers. Let’s hope it stays that way. If not, then it’s back to Linux for me.

Meanwhile, here’s hoping for good things from HP/Palm, Notion Ink, and the Google Android ecosystem.

PyCon 2010 Poster Session

Saturday, February 27th, 2010

I finally presented something at a Python conference. Many thanks to Vern Ceder for organizing the PyCon 2010 poster session!

Here’s a copy of the poster, which describes a way to run QUnit-based JavaScript unit tests using Python’s unittest facilities:

PyCon Poster.png
PyCon Poster.pdf

The code described by the poster is available from bitbucket.org, under the BSD license.

Mercurial, Migration Assistant, and dotfiles

Monday, February 22nd, 2010

I recently upgraded my iMac. Migration Assistant moved all of my files to the new machine without issue — or so it seemed.

I had created Mercurial repositories in a couple of virtualenv environments, to track changes locally.[1] I didn’t notice that Mercurial had put each virtual environment’s .Python file under revision control.

Shortly after completing the migration I made changes in one of these virtual environments. A quick ‘hg status’ before committing, and…

$ hg status
abort: data/.Python.i@13b27e856c38: no match found!

WTF?

After much investigation it appears that the following has happened.

  1. Mercurial represented the .Python link in its .hg/store/data directory as ._Python.i
    1. The leading underscore appears to be Mercurial’s way of noting that the ‘P’ should be capitalized.
  2. I think Migration Assistant uses ditto to copy files.
    1. ditto saw the leading ‘._’ and concluded this was an orphaned resource file.[2] So it didn’t copy the file.
  3. Mercurial knew that it was supposed to have a .hg/store/data/._Python.i file; when it couldn’t find it, it decided the repository was corrupted.

Luckily the problem cropped up before I traded in the old machine, so I was able to copy across the missing files manually.

In my experiments, the problem manifested only when the capitalized dotfile was a symbolic link, not when it was a regular data file.

Well… the above write-up contains several unproven assertions, e.g. about the conditions under which Mercurial will create a ‘._’ filename. I’m not really sure whether this is a bug or merely a caveat regarding an obscure corner condition. For now, the easiest workaround is:

Don’t Track virtualenv .Python Files With Mercurial.

[Update 2010/02/22: Someone has already filed this as a Mercurial bug.]


[1] (Mercurial makes a great filesystem “undo” facility, useful even for directory trees which you never intend to share with anyone else.)

[2] OS X still supports something ike resource forks. In tarballs and other non-OS Extended filesystems, resource forks are represented as dot-files with a leading underscore. (See Norman Walsh’s blog for more info.) For example, the resource fork for a file named ‘foo.txt’ might be ‘._foo.txt’.

Python, webbrowser, OS X, and “execution error”

Thursday, January 28th, 2010

Once in awhile I write Python scripts which produce static HTML pages and then use the webbrowser module to open the results in a browser. I do this just rarely enough that, when the scripts fail on OS X with the following error, I have to waste time diagnosing the problem:

execution error: An error of type -2110 has occurred. (-2110)

Despite the obscure phrasing, the cause of the error is simple. I’m passing a filesystem pathname to webbrowser.open, when I should be passing a file: URL.

This produces the error:
webbrowser.open("/path/to/file.html")

This works:
webbrowser.open("file:///path/to/file.html")

TextMate, Emacs and META indent-region

Thursday, November 12th, 2009

I haven’t used GNU Emacs very much since switching to TextMate in 2005. One Emacs feature which I really miss in TextMate is indent-region. It lets you take an entire region of code, whatever its language, whatever its mix of tabs and spaces and indentation widths, and re-format it using your preferred indentation style.

But wait! Emacs has a batch mode, and you can drive it from TextMate. Many thanks to Gragusa’s Things for showing the way.

The post on Gragusa’s Things is specific to R code, but I’m more interested in re-formatting C and C++ code. Here’s my first cut at a general TextMate Bundle to re-format code regardless of the source language:

#!/usr/local/bin/python2.6
"""
Use Emacs to re-indent regions of the current buffer.
Inspired by 

http://gragusa.wordpress.com/2007/11/11/textmate-emacs-like-indentation-for-r-files/

"""
import tempfile
import os
import sys
import subprocess
# Use the same filename extension so Emacs will know which
# mode to use.
ext = os.path.splitext(os.environ["TM_FILEPATH"])[-1]
outf = tempfile.NamedTemporaryFile(suffix=ext, delete=False)
pathname = outf.name
outf.write(os.environ["TM_SELECTED_TEXT"])
outf.close()
args = [
    "emacs", "-batch", pathname,
    # Assume no emacs-startup.el
    "--eval", "(setq indent-tabs-mode nil)",
    "--eval", '(c-set-style "java")',
    "--eval", "(setq c-basic-offset 4)",
    "--eval", "(indent-region (point-min) (point-max) nil)",
    "-f", "save-buffer"]
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
if p.returncode:
    print(err)
inf = open(pathname, "r")
sys.stdout.write(inf.read())
inf.close()
os.remove(pathname)

NB:

  1. Due to the use of the delete=False keyword argument to tempfile.NamedTemporaryFile, this command bundle requires Python 2.6+.
  2. TextMate on OS X 10.5 won’t, by default, have /usr/local/bin in its path; hence the pathetic shebang.

Anyway, install this as a new TextMate command bundle, assign a Key Equivalent such as ⌘-Shift-R, and enjoy.

Creating an ‘hg ignore’ extension

Tuesday, November 3rd, 2009

I often wish Mercurial had an ‘hg ignore’ command similar to ‘bzr ignore’. Turns out it’s pretty easy to add one:

#!/usr/bin/env python
"""Ignore pathnames and patterns"""
import os
def ignore(ui, repo, *pathnames):
    """Ignore the given pathnames and patterns."""
    outf = open(os.path.join(repo.root, ".hgignore"), "a")
    for p in pathnames:
        outf.write(p + "\n")
    outf.close()
    return
cmdtable = {
    'ignore': (ignore, [], "hg ignore pathname [pathname]"),
}

To use this, save it to a file such as ${HOME}/platform/independent/lib/hg/ignore.py. Then add the extension to your ${HOME}/.hgrc:

[extensions]
~/platform/independent/lib/hg/ignore.py

Running TileCache within a Django Application

Thursday, September 24th, 2009

[Cross-posted from Bottled Text, as usual.]

Punchline

Here is how to serve TileCache tile images from within a Django application.

from TileCache.Service import Service
_service = Service(...)
def get_tile(request):
    global _service
    format, image = _service.dispatchRequest(
        request.GET, request.path, request.method,
        request.get_host())
    result = HttpResponse(str(image), mimetype=format)
    return result

Scenario

You’re building a low-traffic Django-based GIS application, and you need to serve your own map layers. You’re using TileCache to improve your application’s performance. But installation and configuration are hassles.

  • All of your servers must run with the right user and group IDs, so the Django app can expire the tile cache when necessary.
  • Your Django app needs to understand the structure of the tile cache, so it can remove the correct tile images when the underlying data changes.
  • Etc.
standalone_tilecache.png

This would all be much easier if you could serve TileCache requests from within your Django application. They’re both Python-based; why not?

django_plus_tilecache.png

The TileCache code base includes sample code that shows how to run TileCache as a CGI or a FastCGI service. I couldn’t find any sample code for running TileCache within a Django application, but it was easy to convert the cgiHandler code for use with Django’s HttpRequest objects.

Installation Prerequisites

In order for TileCache to generate its own tiles, instead of delegating to a separate mapserver instance, you must already have compiled and installed mapserver’s Python mapscript bindings. For instructions on compiling the bindings see the mapscript/python/README file in the mapserver source distribution.

Configuring TileCache

import os
thisdir = os.path.abspath(os.path.dirname(__file__))
def relpath(p):
    return os.path.abspath(os.path.join(thisdir, p))
from TileCache.Service import Service
import TileCache.Layers.MapServer as MS
# Create the service 'singleton'.
_mapfile = relpath("../mapserv/data/mapfile.map")
_service = Service(
  _cache,  # See "Cache Invalidation", below
  {
    "basic": MS.MapServer(
        "basic", _mapfile, layers="basic", debug=False),
  }
)

Handling Tile Requests

This is the sweet part. It’s derived from the cgiHandler() example in the TileCache source code, but Django’s HttpRequest class makes the implementation very simple:

def get_tile(request):
    global _service
    format, image = _service.dispatchRequest(
        request.GET, request.path, request.method,
        request.get_host())
    result = HttpResponse(str(image), mimetype=format)
    return result

What About Feature Info Requests?

I don’t know much about the required web API of a WMS server, but it appears as if the same URL must serve both tiles and feature info requests; the type of request is determined by the Request querystring parameter.

Django’s dispatch system is based on URL pathnames; I’m not aware of any way to dispatch based on query string parameters. So you’ll need to either configure your web server (e.g. Apache) to rewrite WMS requests to distinct URLs provided by your Django app, or you’ll need to do some dispatch within your Django app.

Suppose you opt for the latter. Then your urls.py might look something like this:

    ...
    url(r'^wms/$', 'world.views.wms', name='wms'),
    ...

and in world/views.py you might have this:

def wms(request):
    if request.GET.get("request") == "GetFeatureInfo":
        return get_feature_info(request)
    return get_tile(request)

Cache Invalidation

For my web app, several of the tile layers are derived from a Django model which is updated via the admin interface. Whenever the model changes, the tile cache for the corresponding layer(s) needs to be invalidated, so the images can be regenerated.

The TileCache Cache interface doesn’t provide for invalidation. Since I’m using a filesystem-based cache, I subclassed TileCache.Caches.Disk to create a Disk cache which does support invalidation.

import shutil
from TileCache.Caches.Disk import Disk
class InvalidatingDisk(Disk):
    """A Disk cache which can invalidate its contents,
       layer by layer."""
    def invalidate(self, layerName=None):
        if self.basedir:
            pathname = self.basedir
            if layerName is not None:
                pathname = os.path.join(self.basedir,
                                        layerName)
            shutil.rmtree(pathname, ignore_errors=True)

Ted Leung on web app design patterns

Sunday, September 13th, 2009

DjangoCon 2009:

“Avi Bryant’s keynote took its root in his experiences building Trendly. As one might expect, Avi started building Trendly using Seaside. But by the time he finished, he noticed that very little of Seaside was actually being used. He attributed this to the fact that Trendly’s architecture involves loading a single HTML, with a ton of Javascript. That Javascript then manages all of the interaction with the server, which consists of snippets of JSON data. This range true to me because we used a similar architecture for Chandler Hub, the web based version of Chandler (our interaction with the server was based on atom and atompub, not JSON), and it’s the kind of architecture that GMail is based on.”

The same has been true for us (Mesa Analytics) in most of our web applications. Sometimes we do have “multi-page” web apps, but each of the individual pages typically follows the pattern above: load, then let JavaScript drive a bunch of task-specific server interactions.

Between Ted’s summary of DjangoCon 2009 and the chatter about Tornado, it has been an interesting week for web application developers.

ActionScript: TileList, deleting, scrolling backwards

Wednesday, August 19th, 2009

[Another day, another blog cross-post...]

I have a Flex app which shows users a TiledList of chemical structure depictions. Since it can be a large list, it’s lazy-loaded from the server as the user scrolls through the list.

Users can delete items from the list, with undo. In order to do this with reasonable performance, once the app has received confirmation from the server that an item has been deleted, it clears out the single deleted item from its local lazy-list.

To undo the deletion locally, the Flex app fills the correct lazy-list entry with an ItemPendingError; that error gets thrown as soon as the TileList tries to retrieve the item.

All of this works okay when the row containing the undeleted item is already visible. On the other hand, if the user has scrolled away from the row where the undeleted item will reappear, then when (s)he scrolls back the TileList simply empties out that item and all of the successive items in the row. Ugly!

ugly_repaint.png

Workaround

When the item is undeleted, immediately try to retrieve it via getItemAt(itemIndex). Catch the resulting ItemPendingError and register an ItemResponder. When the ItemResponder’s result or fault method is called, tell the TileList to invalidateList(). If the undeleted item actually contains a value, the TileList will repaint correctly — no more unsightly gaps.

import mx.collections.errors.ItemPendingError;
import mx.collections.ItemResponder;
[...]
try {
    structures.getItemAt(offset);
} catch (e:ItemPendingError) {
    e.addResponder(
    new ItemResponder(
        function(result:Object, token:Object = null):void {
            tilelist.invalidateList();
        },
        function(error:Object, token:Object = null):void {
            tilelist.invalidateList();
        }));
}