Here's an 8 minute demo showing how to use the new TextEditor Quickly Widget. This removes all the pain and suffering of adding text editing functionality to your app. No more gtk.TextBuffer, no more gtk.TextIter. Just a series of simple function calls, and you're ready to go. Of course, TextEditor is a gtk.TextView, so if you need to access any of the power and flexibility of the underlying Gtk library, it's right there.
TextEditor is now available in quidgets trunk.
Sorry for the loudness of the video. I made it in a coffee shop, and moved my mic closer to compensate for background noise. It didn't work out too well.
The diary of a dedicated Ubuntu user that lucked into his dream job working on the Ubuntu team.
Sunday, June 20, 2010
New Quickly App: Daily Journal
Quickly has started to unlock productivity for me in unexpected ways. I've mentioned about writing my own development tools, like bughugger, and slipcover. Last week I extended this to writing my own productivity apps. In this case, I wrote an app to replace my Tomboy usage to focus specifically on the functions in Tomboy that I actually used. The above video shows that app, "Daily Journal". It's available in my PPA.
In my next posting, I'll show how I used quickly.widgets.text_editor to create Daily Journal.
Monday, June 14, 2010
Go Here to Learn to Program from MIT
After the first few lessons, you'll know enough Python to start a Quickly app!.
Sunday, June 6, 2010
New Quickly Widget - async downloads with two lines of code
The Ubuntu Developer's Manual team was discussing the instructional app that we should use for the manual. During this discussion, it became apparent that there wasn't a way to download from a URL that was both easy and also asynch. Instead of choosing between simple and good, Stuart made a Quickly Widget that provides a way to fetch from a URL that is both simple *and* good.
     fetcher = UrlFetchProgressBox("http://identi.ca/api/statusnet/groups/timeline/8.rss")
     fetcher.connect("downloaded",self.create_grid_from_feed)
Tuesday, June 1, 2010
Having Users Makes Your Code So Much Better

I mentioned in a previous post that I was finding PyTask to be pretty cool. Of course, one of the cool things, for me, was that it used Quickly Widgets. As I mentioned, Quickly Widgets lacked some key features, like a DateColumn.
Having a user means that I know at least one way that someone it trying to use the code. I went ahead and implemented a DateColumn in PyTask, and my next step will be to add DateColumn to quickly widgets, so Ryan doesn't have to maintain the code in PyTask. First I need to kind of make room for this in the grid_filter module. I have a good idea of how to do it, so just a SMOP at this point.
There were other more subtle things that I ran into as well. For example, it turns out that I didn't handle the case of deleting rows in a CouchGrid, or even removing rows from a DictionaryGrid if that grid was filtered. The later case was "just" a bug. So I worked around this in PyTask code so that PyTask could ship while waiting for me to fix Quickly Widgets.
Since I have intimate knowledge about how the PyGtk was assembled, I was able to write this code for PyTask
    def remove_row(self, widget, data=None):
"""Removes the currently selected row from the couchgrid."""
# work around to actually delete records from desktopcouch
# in maverick, using delete=true in remove_selected_rows will have
# the same effect
database = CouchDatabase("pytask")
for r in self.grid.selected_rows:
   database.delete_record(r["__desktopcouch_id"])
if type(self.grid.get_model()) is gtk.ListStore:
   self.grid.remove_selected_rows()
else:
   # The following code works around:
   # https://bugs.edge.launchpad.net/quidgets/+bug/587568
   # get the selected rows, and return if nothing is selected
   model, rows = self.grid.get_selection().get_selected_rows()
   if len(rows) == 0:
       return
   # store the last selected row to reselect after removal
   next_to_select = rows[-1][0] + 1 - len(rows)
   # loop through and remove
   iters = [model.get_model().get_iter(path) for path in rows]
   store_iters = []
   for i in iters:
       # convert the iter to a useful iter
       store_iters.append(model.get_model().convert_iter_to_child_iter(i))
   for store_iter in store_iters:
       # remove the row from the store
       self.filt.store.remove(store_iter)
   # select a row for the user, nicer that way
   rows_remaining = len(model)
   # don't try to select anything if there are no rows left
   if rows_remaining < 1:
       return
   # select the next row down, unless it's out of range
   # in which case just select the last row
   if next_to_select < rows_remaining:
       self.grid.get_selection().select_path(next_to_select)
   else:
       self.grid.get_selection().select_path(rows_remaining - 1)
So I moved the code to delete records from desktop couch into CouchGrid.remove_selected_rows and all the "remove properly even if filtered" goo into DictionaryGrid.remove_selected_rows. The result is that when the next version of Quickly Widgets lands, Ryan will be able to simplify the function down to this:
    def remove_row(self, widget, data=None):
"""Removes the currently selected row from the couchgrid."""
self.grid.remove_selected_rows(delete=True)
Another area where the DictionaryGrid lacked functionality was related to column titles. It was easy to write a little code to change the column titles:
        for c in self.grid.get_columns():
      if c.get_title() == "name":
          c.set_title(_("Name"))
      elif c.get_title() == "priority":
          c.set_title(_("Priority"))
      elif c.get_title() == "due":
          c.set_title(_("Due"))
      elif c.get_title() == "project":
          c.set_title(_("Project"))
      if c.get_title() == "complete?":
          c.set_title(_("Completed"))
Again, knowing the structure of the PyGtk intimately, I was able to work around this by modifying rows in the filter as each is created:
   def __new_filter_row(self, widget, data=None):
   """
   new_filter row - hack to allow naming of columns
   in a grid filter.
   This code works around:
   https://bugs.edge.launchpad.net/quidgets/+bug/587558
   """
   row = self.filt.rows[len(self.filt.rows)-1]
   row.connect("add_row_requested",self.__new_filter_row)
   model = row.column_combo.get_model()
   for i, k in enumerate(model):
       itr = model.get_iter(i)
       title = model.get_value(itr,0)
       if title == "name":
           model.set_value(itr,0,_("Name"))
       elif title == "priority":
           model.set_value(itr,0,_("Priority"))
       elif title == "due":
           model.set_value(itr,0,_("Due"))
       elif title == "project":
           model.set_value(itr,0,_("Project"))
       if title == "complete?":
           model.set_value(itr,0,_("Completed"))
I fixed this a bit easier in Quickly Widgets.
        grid.columns["key1_1"].set_title("KEY")
I also made a convenience function that Ryan can use. Here's the code from the tests:
        titles = {"key1_1":"KEY1","key1_2":"KEY2","key1_3":"KEY3"}
     grid.set_column_titles(titles)
Anyway, thanks to Ryan for letting me use PyTask to improve Quickly Widgets!