Quidgets

Intro

quickly-widgets is a personal project of mine

the goal of quickly widgets is to make PyGtk programming more easy and fun

PyGtk is a fantastic UI tool kit

It is very powerful, functional, and flexible

However, at the moment, it lacks some higher order abstractions that developers have come to expect

The result being that developers have to write reams of code using multiple classes and such for things that are more simple in other frameworks

for example, popping dialogs, putting data into a grid and such

so I'm hoping quickly-widgets can fill in some of these use cases

please note that quickly-widgets is very early in development and experimental

this means that it is going to be buggy and incomplete

but it also means that if you want to, you can have a lot of influence on how it develops

so bug reports and branches, more than welcome

so what is Quidgets?

"Quidgets" is the original name of quickly-widgets

the project is still called Quidgets

so you can get the code:

$bzr branch lp:quidgets

quickly-widgets is currently in universe for Lucid

it is not packaged for Karmic

though if someone wanted to do that, I would not complain too much Wink ;)

currently, quickly widgets has two modules

quickly.prompts

and quickly.widgets

quickly.widgets has some general widgets

but also some widgets for displaying data, basically extending TreeView and making it simpler to use

I'll discuss each of these three areas in a bit more depth

But first, Questions?

Usage Model

Before we look at the modules, a word on the mental model of using quickly-widgets

There are 3 "levels" to consuming quickly-widgets

Most of the time, you should be fine with the first level, which is the "Use" level

At this level, you just make a one-liner that uses the API and does something useful

like, get as string from the user: quickly.prompts.string("My Title","Give me a string","default val")

or maybe you want do display the data in a dictionary: dg = DictionaryGrid(my_list_of_dicts) dg.show()

sometimes you want to tweak something a bit. In these cases you go to the configure level.

When you are doing "configure" you might end up a using the underlying PyGtk API a bit

For example, a dictionary grid is really just a Treeview

So if you want to configure the title for a column, you might go

dg.get_columns()[0].set_title("New Title")

some of the configuration might be done in the quickly.widgets library though

like if you want to display certain keys from your dictionary, you can use a list of keys to display

keys = ["key1","key2'] dg = DictionaryGrid(my_dict,keys=keys)

let's say you get back a feature request from a user or customer, and you need to do something that is not suppported by quickly-widgets through using or configuring

well, quickly-widgets strives to consume PyGtk in such a way that you can extend classes as needed to accomplish your goals

for example, if you want a grid that displays data from a certain SQL database somewhere, you can inherit from DictionaryGrid and add the SQL functionality.

This is exactly what CouchGrid does, but for generally desktopcouchy things

So, before I discuss using the prompts a bit, any questions?

Prompts

Prompts are basically dialogs that you can use to interact with the user without writing too much code

rather than creating a PyGtk dialog or similar yourself, and then populating it, for certain scenarios a prompt in quickly-widgets can take care of it for you

the prompts module is right under quickly, so you can get it like this: from quickly import prompts

or just quickly.prompts if you want

there are three kinds of prompts

the first kind just displays info the the user

these take a couple of strings from you and display a message box to the user with the appropriate icon

so, you can go

prompts.error("Title for the Error Dialog","Error text")

there is also prompts.warning and prompts.info

they works the same as error

The next kind of prompt collects some info from the user

the kind of prompts built in of this kind are: string, date, integer, decimal, and price

they all take care of displaying an appropriate widget for the user input for you

like a textbox for a string, a calendar for a date, and a spinner appropriately formatted for the other two

they let you set a title, a message, a default value, and in the case of the numeric ones some other defaults if you want

the functions all return a gtk.RESPONSE and a value

make sure you check the response because if the user cancelled, you want to do the right thing

so to get a string, go: response, val = quickly.prompts.string() if response == gtk.RESPONSE_OK:

  • #do something with val

where val is a string

you can get a date in the same way

when dealing with date values are tuples in the form of integers for (year,month,day)

where month is zero indexed (Jaunary is 0, December is 11)

and you use tuples for the default value in teh same way

so: response, date = quickly.widgets.date("Title String","Enter your birthday",(1968,03,22))

so that's *April* because the months are zero indexed

if you want to get an integer, you can set min_value, and max_value as well

if you look at the doc string, you can see other options as well

decimal and price are also rich in options

price is just decimal, but with 2 decimal places set

however, they all work basically the same way, response, val = function()

if you need to configure the prompt a bit more, for each function, there is a symetrical class that you can use

so for string(), if you want to own the dialog for some reason, you can use StringPrompt

sp = StringPrompt(title, text, default_string)

then you can party on sp if you want

there is a similar dialog for date, integer, decimal, and price

each has a "get_value()" function to extract the value

The last set of prompts are FileChooserDialogs

they work the same way

response, path = quickly.prompts.choose_directory(title)

there is choose_directory, save_image_file, and open_image_file

there are classes for these as well

DirectoryChooserDialog, OpenImageDialog, and SaveImageDialog

these lack get_value, because they are just subclasses of FileChooserDialog

which already has a get_filename() function

before I talk about the gtk.Widgets, any questions?

Widgets

for widgets, there are a few miscelaneous ones, and then the grid related ones

I'll start with the miscelaneous ones

the most miscelaneous one is

from quickly.widgets.camera_button import CameraButton

CameraButton wraps up the PyGame web cam API and serves up pixbufs

I don't like how this is implemented at the moment, and am planning to pull it into it's own package for a while, until I can make it work well

however, it's kinda fun when it works

there's also a subclass of button called PressAndHold button

this guy fires a signal every 250ms

so this allows you to do something while the user is holding down a button

I use this in photobomb to manage rotation of times for example

first, create the button, then connect to the signal

then write a function that does something every time the signal fires def action(self, widget=None, data=None):

  • #do something every 250ms

the last misc. quickly.widget is

from quickly.widgets.asynch_task_progressbox import AsynchTaskProgressBox

in general, you should avoid threads in your python app

you should use things like gobject.timeout_add() or gobject.idle_add()

however, for times when you absolutely must use a thread AsynchTaskProgressBox can make it easier for you

this derives from gtk.ProgressBox, so it supplies some throbbing UI for you if you need it

the basic idea is to write a function that you want to run on a thread

I usually call this a task

create a dictionary of parameters if you want to pass some params to the function

create the AsynchTaskProgressBox

if you want to know when the task is done, connect to the complete event

here's some photobomb code for example

params = {"directory":directory} pb = AsynchTaskProgressBox(self.load_task, params, False) pb.connect("complete",self.load_task_complete)

for your task, you can simulate a killable thread (in python you can't really kill a thread, just wait for it to end)

you do this by checking a special key that is added to params called "kill"

if it's set to True, you can stop working

here's a bit of code from photobomb to demonstrate

  • def load_task(self, params):

    • pictures_dir = params["directory"] images = [] files = os.listdir(pictures_dir) for f in files:
      • try:
        • if params["kill"]:
          • return None
          #etc....

So the last bit about quickly-widgets is about Grids and Filters and stuff

before I do that, any questions so far?

Grids

lots of the code in quickly-widgets is about presenting data to users in a TreeView

in PyGtk, gtk.TreeView handles both displaying grid data and data in trees

quickly-widgets focuses on the Grid

the essentially scenario is that you have some tabular data to display

gtk.TreeView can let you display this in any manner you like

so long as you:

1. Create a TreeView 2. Cerate a treeview model store of some kind 3. create columns 4. create cellrenders for each column 5. popluate the treeview model

from quickly.widgets.dictionary_grid import DictionaryGrid tries to handle this for you in essentially, one line of code

the basic interaction is that you create a DictionaryGrid by handing it a list of dictionary, and it makes a TreeView for you

you just show it and add it to your form

Here's a bit from the dictionary grid test program

first, a list of dictionaries dicts = [{"key?": True, "price":0.00,"tags" : "aaa bbb ccc","_foo":"bar","bing count":20},

  • {"ID": 11, "key?": False, "price":2.00,"tags" : "bbb ccc ddd","_foo":"bar"}, {"key?": True, "price":33.00,"tags" : "ccc ddd eee","_foo":"bar","bing count":15}, {"ID": 3, "tags" : "ddd eee fff","_foo":"bar"}, {"ID": 4, "price":5.00,"_foo":"bar"}]

then, create the DictionaryGrid: grid = DictionaryGrid(dicts)

if you want the user to be able to edit the grid, set it to editable: grid.editable = True

note that setting some of the properties, including editable will cause the grid to reset itself

you can add to your grid by appending rows grid.append_row(my_dict)

by default, the grid will display a column for every key it encounters UNLESS that key starts with "" (two underscores), in which case it will be ignored

if you want to control the keys displayed, you can do this when you create it: grid = DictionaryGrid(dicts,list_of_keys)

or you can set the keys later grid.keys = list_of_keys

this causes the grid to reset, btw

Columns will sort properly most of the time

this is accomplished by having each column be a certain type there is the default StingColumn, but there is also IntegerColumn, CurrencyColumn, TagsColumn, and CheckColumn

DictionaryGrid tries to guess the right column type to use based on the name of the key

So if you choose your key names correctly, you can get a lot of good functionality for free

for example, and key that ends with "?" DictionaryGrid will assume it is a boolean, and use CheckBoxes to show the values

and provide the appropriate sortign for you

he key "price" is assumed to be currency

anything ending in count will be an IntegerColumn, same with a key called "id"

a key called "tags" will be a tags column

tags columns are mostly important for filtering, which I will discuss in a moment

you can control the columntypes manually if you want, by passing in "Type Hints"

here's some code from the tests:

in this case hints are overriding the defaults

so I should talk about filtering next, but first, questions?

so one cool thing about gtk.TreeView is the filtering capabilities

Filtering even pretty long lists works really fast

from quickly.widgets.grid_filter import GridFilter makes a filter UI for you

this will let the user add filters to filter columns

if you've seen the bughugger UI, then you have seen GridFilter and DictionaryGrid in action

you just need to hand it a Grid to filter

it will pick up filter types from the GridColumns in the Grid

So for example columns with strings will get a filter with "contains substring" while a column with numbers will get ">,<.=", etc...

all this just by creating it:

filt = GridFilter(grid) filt.show()

if you want to override the default filter types, you can pass in hints

this works pretty much like in the grid

you pass in a dictionary that matches up keys to filter types

well, actually to filter combos, but that's a bit complex

there are StringFitlerCombo, TagsFilterCombo, CheckFilterCombo etc...

there is also blank filter combo that you can use to easily make your own filters

for example, in bughugger I created a custom filter to filter status and importance like this:

  • sw_filter2 = BlankFilterCombo() sw_filter2.append("=",lambda x,y: convert_to_num(x) == float(y) ) sw_filter2.append("<",lambda x,y: convert_to_num(x) < float(y) ) sw_filter2.append(">",lambda x,y: convert_to_num(x) > float(y) ) sw_filter2.append("<=",lambda x,y: convert_to_num(x) <= float(y) ) sw_filter2.append(">=",lambda x,y: convert_to_num(x) >= float(y) )

filter_hints = {"status":sw_filter,"importance":sw_filter2}

each call to append adds a string and a function call

Finally, there is CouchGrid

I feel this has been talked about a lot

In Lucid, a CouchGrid is a DictionaryGrid

so you get all the sorting and filtering of DictionaryGrid, plus persistance in desktopcouch

you need to define a database and a record type

if you want, you can pass in a dictionary with some starter data as well

here is some code from the GridFilter test app

  • database_name = "couch_widget_test" record_type = "couch_grid_filter_test" hints = {}

    grid = CouchGrid(database_name, record_type=record_type, dictionaries=dicts) grid.show()

    filt = GridFilter(grid,hints) filt.show()

Questions?

UbuntuOpportunisticDeveloperWeek/Quidgets (last edited 2010-03-04 19:57:49 by rick-rickspencer3)