Onboarding a new Developer without waterboarding them

Perhaps the original 157 lines of the clock.py sample code by Ralph Glass could be re-adapted to make the ClockGramplet add-on into a solid foundation for a viable new Developer tutorial?

I keep beginning new experiment in learning Gramps code, generally by examining add-ons … and continually run into road-blocks.

My hope was to find a quickstart tutorial to ‘onboard’ a Programmer in working within the Gramps GUI. (Bypassing the Gramps data model side because that is too much to swallow in one gulp. The Gramps data manipulation and formatting experiments could be worked into a separate SuperTool or Python Shell tutorial.)

The QuickStart progression of steps would completely bypass GitHub and the development environment.

It could consist of:

  • being provided a working sample of a simple Python source module (Code that would run in a Python IDE if they had one installed outside Gramps.) The sample would show enough functionality to be interesting. (Something with more functionality than “Hello World!”)
  • Then be shown a compatible example of that code with any tweaks needed to make run within the Python Shell gramplet. (Path changes, library changes, variable naming conventions, etc.)
  • Then an adapted example of that Python Shell gramplet code MINIMALLY changed to run encapsulated in the gramplet shell (with .gpr.py file).
  • Optionally, an optimized version (but this could be the final outcome of the tutorial. Still, it is nice to have a working version to compare against if you screw up during the tutorial.)

These 3 revisions can be used to write a tutorial process of making the a python module something that can be run within the Gramps GUI. It would include creating a full-featured Gramps Plugin Registration file and whatever shell encapsulation was needed to make that code run as a Gramplet (that might possibly mean some tweaks for compatibility with other OSes)

There would be a host of follow-on lessons:

  • how to insert Log and Warning messages (with introduction of using the Console to allow debugging feedback)
  • exploration of supporting internationalization
  • help file linking (of a GitHub-style README.md or a Wiki Addon:UserDoc)
  • CLI compatibility (parameter passing
  • adding Configuration options
  • Context menus (connecting to resources outside Gramps: OS Clipboard, links to external websites, etc.)
  • working with Glade files
  • connecting to the Gramps Data Model and methods would be a separate tutorial track

The Gramplets wiki article buries a reader in a discouraging and excessive amount of preliminary detail. But eventually, it suggests that an example for making a simple Gramplet which purportedly looked nice (one that didn’t actually have to touch the Tree data tables) was the Cairo-Clock add-on gramplet. The python code provides live feedback proving that it is operational and does not need to touch the Gramps data model. The description of “looks nice” is in comparison to Text Terminal style presentation of graph data. It is about as primitive as possible while still using drawn objects.


The credits led me to believe that the ClockGramplet.py was Doug Blank’s direct encapsulation of Ralph Glass’s clock.py for the Linux community.

Unfortunately, that sample didn’t meet most of my expectations as good sample code.

It jumped too many steps at once. Doug’s adaptation bore little resemblance to the structure of Raph’s original code and was without any helpful comments describing why the changes were inserted. A side-by-side comparison was not edifying. There are no line-by-line explanations of the new code produced for the separate Gramps Plugin Registration file. Nor is there any suggestion of how to troubleshoot registration conflicts or loading failures.

As a tutorial, the ClockGramplet is non-starter and a dead end.

The drawback to redeveloping on this foundation is that: this clock is probably too plain to draw in new UX and UI developers to the next stage … visually, the graphics are an outmoded example of Cairo.

Themes and Gramps

The plain Cairo-clock in Gramps always left me cold from a UX and UI point-of-view. It is an uninspiring example if we want to attract developers who have new ideas for modernizing the look of Gramps to match its incredible power.

I wondered if the theme could be changed represent modern UX but with a connection to antiquity… like Palko’s Street Clock?

image

But it appears that the making the clock look ‘modern’ requires Themes … and themes aren’t coded into the python of Ralph Glass’ Cairo-clock.

When looking at the Themes add-on documentation, it mentions the Gnome-Look.org website as a source for additional Themes.

One of the sections offered some 260 Cairo-clock themes

MacSlow’s Cairo-Clock


Looking at the Prerequisites Checker report indicated my installation was a bit shy of the Cairo version and some other bits & pieces for MacSlow’s modern interpretation of the Cairo-Clock.

Maybe MacSlow’s code could be adapted to a 2nd generation Gramplet tutorial on Themes manipulation to enhance the Gramps GUI?

The clock.py original by Ralph Glass

#!/usr/bin/env python

import pygtk
pygtk.require('2.0')

import gobject
import pango
import gtk
import math
import time
from gtk import gdk
try:
    import cairo
except ImportError:
    pass

if gtk.pygtk_version < (2,3,93):
    print "PyGtk 2.3.93 or later required"
    raise SystemExit

TEXT = 'cairo'
BORDER_WIDTH = 10

def progress_timeout(object):
    x, y, w, h = object.allocation
    object.window.invalidate_rect((0,0,w,h),False)
    return True

class PyGtkWidget(gtk.Widget):
    __gsignals__ = { 'realize': 'override',
                     'expose-event' : 'override',
                     'size-allocate': 'override',
                     'size-request': 'override',}

    def __init__(self):
        gtk.Widget.__init__(self)
        self.draw_gc = None
        self.layout = self.create_pango_layout(TEXT)
        self.layout.set_font_description(pango.FontDescription("sans serif 8"))
        self.timer = gobject.timeout_add (1000, progress_timeout, self)
                                           
    def do_realize(self):
        self.set_flags(self.flags() | gtk.REALIZED)
        self.window = gdk.Window(self.get_parent_window(),
                                 width=self.allocation.width,
                                 height=self.allocation.height,
                                 window_type=gdk.WINDOW_CHILD,
                                 wclass=gdk.INPUT_OUTPUT,
                                 event_mask=self.get_events() | gdk.EXPOSURE_MASK)
        if not hasattr(self.window, "cairo_create"):
            self.draw_gc = gdk.GC(self.window,
                                  line_width=5,
                                  line_style=gdk.SOLID,
                                  join_style=gdk.JOIN_ROUND)

	self.window.set_user_data(self)
        self.style.attach(self.window)
        self.style.set_background(self.window, gtk.STATE_NORMAL)
        self.window.move_resize(*self.allocation)

    def do_size_request(self, requisition):
	width, height = self.layout.get_size()
	requisition.width = (width // pango.SCALE + BORDER_WIDTH*4)* 1.45
	requisition.height = (3 * height // pango.SCALE + BORDER_WIDTH*4) * 1.2

    def do_size_allocate(self, allocation):
        self.allocation = allocation
        if self.flags() & gtk.REALIZED:
            self.window.move_resize(*allocation)

    def _expose_gdk(self, event):
        x, y, w, h = self.allocation
        self.layout = self.create_pango_layout('no cairo')
        fontw, fonth = self.layout.get_pixel_size()
        self.style.paint_layout(self.window, self.state, False,
                                event.area, self, "label",
                                (w - fontw) / 2, (h - fonth) / 2,
                                self.layout)

    def _expose_cairo(self, event, cr):
        
        # time 

        hours = time.localtime().tm_hour
        minutes = time.localtime().tm_min
        secs = time.localtime().tm_sec
        minute_arc = (2*math.pi / 60) * minutes
        if hours > 12:
            hours = hours - 12       
        hour_arc = (2*math.pi / 12) * hours + minute_arc / 12
       
        # clock background

        x, y, w, h = self.allocation
        cr.set_source_rgba(1, 0.2, 0.2, 0.6)
        cr.arc(w/2, h/2, min(w,h)/2 - 8 , 0, 2 * 3.14) 
        cr.fill()
        cr.stroke()

        # center arc

        cr.set_source_color(self.style.fg[self.state])
        cr.arc ( w/2, h/2, (min(w,h)/2 -20) / 5, 0, 2 * math.pi)
        cr.fill()
        cr.line_to(w/2,h/2)
        cr.stroke()

        # pointer hour

        cr.set_source_color(self.style.fg[self.state])
        cr.set_source_rgba(0.5, 0.5, 0.5, 0.5) 
        cr.set_line_width ((min(w,h)/2 -20)/6 )
        cr.move_to(w/2,h/2)
        cr.line_to(w/2 + (min(w,h)/2 -20) * 0.6 * math.cos(hour_arc - math.pi/2),
            h/2 + (min(w,h)/2 -20) * 0.6 * math.sin(hour_arc - math.pi/2))
        cr.stroke()

        # pointer minute

        cr.set_source_rgba(0.5, 0.5, 0.5, 0.5) 
        cr.set_line_width ((min(w,h)/2 -20)/6 * 0.8)
        cr.move_to(w/2,h/2)
        cr.line_to(w/2 + (min(w,h)/2 -20) * 0.8 * math.cos(minute_arc - math.pi/2), 
            h/2 + (min(w,h)/2 -20) * 0.8 * math.sin(minute_arc - math.pi/2))
        cr.stroke()
 
        # pango layout 
        
        fontw, fonth = self.layout.get_pixel_size()
        cr.move_to((w - fontw - 4), (h - fonth ))
        cr.update_layout(self.layout)
        cr.show_layout(self.layout)
        
    def do_expose_event(self, event):
        self.chain(event)
        try:
            cr = self.window.cairo_create()
        except AttributeError:
            return self._expose_gdk(event)
        return self._expose_cairo(event, cr)

win = gtk.Window()
win.set_title('clock')
win.connect('delete-event', gtk.main_quit)

event_box = gtk.EventBox()
event_box.connect("button_press_event", lambda w,e: win.set_decorated(not win.get_decorated()))

win.add(event_box)

w = PyGtkWidget()
event_box.add(w)

win.move(gtk.gdk.screen_width() - 120, 40)
win.show_all()

gtk.main()

ClockGramplet.gpr.py by Doug Blank

register(GRAMPLET,
         id= "Clock Gramplet",
         name=_("Clock"),
         description = _("Gramplet for demonstrating Cairo graphics"),
         height=100,
         expand=False,
         gramplet = 'ClockGramplet',
         gramplet_title=_("Clock"),
         status = STABLE,
         version = '0.0.32',
         gramps_target_version = "5.1",
         fname="ClockGramplet.py",
         help_url="Gramplets#GUI_Interface",
         )

ClockGramplet.py as adapted by Doug Blank

# Clock Example by Ralph Glass
# http://ralph-glass.homepage.t-online.de/clock/readme.html

from gi.repository import PangoCairo
from gi.repository import GObject
from gi.repository import Gtk
import math
import time

from gramps.gen.constfunc import is_quartz

TEXT = 'cairo'
BORDER_WIDTH = 10

class ClockWidget(Gtk.DrawingArea):

    def __init__(self):
        Gtk.DrawingArea.__init__(self)

        self.connect("draw", self.on_draw)
        self.timer = GObject.timeout_add(1000, self.tick)

    def on_draw(self, widget, cr):

        layout = PangoCairo.create_layout(cr)
        if is_quartz():
            PangoCairo.context_set_resolution(layout.get_context(), 72)
        layout.set_font_description(self.get_style().font_desc)
        layout.set_markup(TEXT, -1)

        fontw, fonth = layout.get_pixel_size()
        xmin = fontw + BORDER_WIDTH
        ymin = fonth + BORDER_WIDTH
        self.set_size_request(xmin, ymin)

        # time

        hours = time.localtime().tm_hour
        minutes = time.localtime().tm_min
        secs = time.localtime().tm_sec
        second_arc = (2*math.pi / 60) * secs
        minute_arc = (2*math.pi / 60) * minutes
        if hours > 12:
            hours = hours - 12
        hour_arc = (2*math.pi / 12) * hours + minute_arc / 12

        # clock background

        alloc = self.get_allocation()
        x = alloc.x
        y = alloc.y
        w = alloc.width
        h = alloc.height
        cr.set_source_rgba(1, 0.2, 0.2, 0.6)
        cr.arc(w/2, h/2, min(w,h)/2 - 8 , 0, 2 * 3.14)
        cr.fill()
        cr.stroke()

        # center arc

        cr.set_source_rgb(0, 0, 0)
        cr.arc ( w/2, h/2, (min(w,h)/2 -20) / 5, 0, 2 * math.pi)
        cr.fill()
        cr.line_to(w/2,h/2)
        cr.stroke()

        # pointer hour

        cr.set_source_rgba(0.5, 0.5, 0.5, 0.5)
        cr.set_line_width ((min(w,h)/2 -20)/6 )
        cr.move_to(w/2,h/2)
        cr.line_to(w/2 + (min(w,h)/2 -20) * 0.6 * math.cos(hour_arc - math.pi/2),
            h/2 + (min(w,h)/2 -20) * 0.6 * math.sin(hour_arc - math.pi/2))
        cr.stroke()

        # pointer minute

        cr.set_source_rgba(0.5, 0.5, 0.5, 0.5)
        cr.set_line_width ((min(w,h)/2 -20)/6 * 0.8)
        cr.move_to(w/2,h/2)
        cr.line_to(w/2 + (min(w,h)/2 -20) * 0.8 * math.cos(minute_arc - math.pi/2),
            h/2 + (min(w,h)/2 -20) * 0.8 * math.sin(minute_arc - math.pi/2))
        cr.stroke()

        # pointer second

        cr.set_source_rgba(0.5, 0.5, 0.5, 0.5)
        cr.set_line_width ((min(w,h)/2 -20)/6 * 0.4)
        cr.move_to(w/2,h/2)
        cr.line_to(w/2 + (min(w,h)/2 -20) * math.cos(second_arc - math.pi/2),
            h/2 + (min(w,h)/2 -20) * math.sin(second_arc - math.pi/2))
        cr.stroke()

        # pango layout

        cr.move_to((w - fontw - 4), (h - fonth ))
        PangoCairo.show_layout(cr, layout)

    def tick(self):
        self.queue_draw()
        return True

# Clock Integrated with Gramplets
# (c) 2009, Doug Blank

from gramps.gen.plug import Gramplet

class ClockGramplet(Gramplet):
    def init(self):
        self.gui.clock = ClockWidget()
        self.gui.get_container_widget().remove(self.gui.textview)
        self.gui.get_container_widget().add_with_viewport(self.gui.clock)
        self.gui.clock.show()