Tuesday, 28 September 2010

Custom Widgets

Published as part of the Ubuntu Community Learning Week

Custom widgets Notes

Plain Text version: http://dl.dropbox.com/u/123544/custom-widget-notes.txt


  • This is a collection of realisations i have had with regards my adventures creating custom widgets
  • There are often many ways to accomplish similar ends
  • there may be more correct ways to accomplish things than as presented here
  • this will be a python/gtk/cairo -centric chat

So why would we want to use custom widgets?

  • solve specific problems in interesting and (perhaps) more effective ways
  • Add visual flare and identity to an application
  • encourages experimentation with new ways of interacting with an app or providing feedback
  • great way to get to know the nitty-gritty of toolkits
  • If you are of the right disposition, they can be really fun to code and test
  • If you get good at it, you can free yourself from the default toolkit and let your imagination run wild

What are the draw backs?

  • Can be difficult to test, and get right!
  • Many behaviours that may need implementing to reach the status quo
  • Accessibility issues
  • May not coexist well with different themes or the 'feel' of the surrounding GUI components

Before you start: get familiar with existing widgets

  • Its best to research what existing widgets are capable of. this can save you a lot of time and if often an eye-opener
  • Even if you conclude no gtk widget exists sufficient to your needs, its instructive to see how other widgets are structured, i.e. api and classes may hint at a good architecture for your own widget
  • You are going to have to subclass a gtk widget at some point, which is best?
  • Eventboxes or DrawingAreas or some other gtk widget?
  • Be familiar with what a gtk.Widget provides for free so you dont go implementing unnecessary things


  • IMHO, Provides a great canvas for creating custom widgets
  • receive events such as button-presses, button-releases, key-presses, focus-in... etc
  • A lightweight starting point
  • I am a fan of gtk.EventBox.set_visible_window(False)
  • Your event box no longer draws its own background

Why is having no background good?

  • You can still draw to the area allocated to your EventBox either with cairo or gtk.Style
  • You can achieve a composited effect
  • A parent container could have an unusual background, say a coloured gradient
  • You may want to draw a non-rectangular shape to your EventBox on-top of a funky background

Lets see some code

Python Script: http://dl.dropbox.com/u/123544/custom-widget-1.py

  • demonstrates drawing on a parent container
  • drawing to your 'own' EventBox
  • connecting to events
  • button-press/release
  • key-press/release
  • focus-in/out

Fitting in: part 1 - gtk.Style

Python Script: http://dl.dropbox.com/u/123544/custom-widget-2.py

  • gtk.Style provides a palette of colours derived from the current gtk-theme
  • You can access these when drawing your widget or once your widget is realised
  • gtk.Style also provides simple draw methods, handy ones include:
  • paint_layout
  • paint_box
  • paint_flat_box
  • paint_(v|h)line
  • Caveat 1: Some themes do not implement all paint_* methods :(
  • Caveat 2: Often to achieve the desired effect you need to supply the correct 'hint' to the paint method
  • Caveat 3: Hint names can be difficult to track down, usually Google is your friend, failing that, gtk source code is a fall-back
  • Caveat 4: Even with the correct hint things may not draw as expected ;(
  • You may need an actual & realised widget to paint from ;O

  • An example: Say you want to imitate the frame of a gtk.Viewport, achieve the correct colour etc (Note: python-like psuedo-code follows...)
  • vbox_widget.style.paint_hline(*args, window=vbox_window, hint='viewport') = Fail
  • the colour does not reliably match the colour of an actual viewport frame
  • viewport_widget.style.paint_hline(*args, window=vbox_window, hint='viewport') = Success
  • we needed to call the paint_hline method from an actual gtk.Viewport.style to achieve the intended result

"style-set" signal

  • If you store colour information or font size, do it in a way such that colours and dimensions can be updated when the user changes themes
  • You can achieve this by connecting to the "style-set" signal

Fitting in: part 2 - cairo and mask_surface

  • Firstly, FYI: cairo rocks. Learn it well :-D
  • using cairo and masking is a another effective way of doing fancy visuals that fit in with their surroundings
  • Its tricky to explain but,
  • With CairoContext.mask_surface(...) you can provide an image with an alpha channel ((semi-)transparent pixels)...
  • and composit it with a colour derived from the gtk-theme
Check out software-center with a dark theme: - the clouds in the Get Software section take on dark greys from the gtk-theme

Cool! So we wrote a custom widget that looks great. What now?

Remember accessibility!

What is accessibility?

Python Script: http://dl.dropbox.com/u/123544/custom-widget-3.py

  • Its additional contextual information about what your widget does and how it relates to other components around it
  • Example: A visually impaired person may require assistance from a screen reader such as Orca. For Orca to provide meaningful assistance to our user, your widget needs provide information about itself to Orca.

The basics of implementing accessibility

  • Starting with an existing gtk widget gets you a long way, nevertheless...
  • Accessibility can be a tricky topic to get 100% right but there are some basics you should always cover...
  • Firstly, install Orca and Accerciser from the repositories.
  • Testing with these apps will give you a good indication as to how well your app/widget is doing accessibility-wise
  • Read up on the atk toolkit, its the gtk of accessibility

  • What is your widgets name and description?
  • What is its role?
  • How does it relate to other components?

Atk minima:

  • a11y = my_widget.get_accessible()
  • a11y.set_name('..') # this is different from gtk.Widget.set_name()
  • a11y.set_description('..')
  • a11y.set_role( atk.ROLE_* )
  • a11y.set_relationship(..)

Thats all for now. Go wild and experiment with your widget ideas!!!!

No comments: