Pale bearded guy wants girl. His woman-savvy friend advises him to ditch the beard and get a suntan. “She’ll look at you in a totally new light, man. She’ll be all, who’s this guy?” Guy takes his advice and lays in the sun. Music montage, natch. Then we see him shaving his beard off, with the lather. With great satisfaction he wipes off the last lather and dries his face. We see him step out the door into the light. His face is evenly bronzed… except for the entire lower half, pitifully white, minus a small tanned crescent above his chin! (( I had a mild form of this. I thought it was razor burn until I realized with horror what had happened. ))
Category Archives: Miscellaneous
Setup for Alexandria Development: III
The following code requires Alexandria trunk. For more information see Cathal’s article. You can get this file here or as a full gem source package through svn:
svn co http://alexandria.rubyforge.org/svn/alexandria/trunk/readinglist .
$:.unshift File.dirname(__FILE__)
=begin
This is a sample (but useable) app for maintaining a reading list that takes
its reading options directly from Alexandria. At the moment it allows you to
move books to the reading list, remove them, mark them as read, and reorder
them. It's commented so as to be a kind of tutorial. The reader is encouraged
to play with it and try to add functionality.
This code demonstrates some of the most common operations in a Gtk app:
creating menus, hooking up a treeview and syncing it to data, and widget
packing. Of these, working with the TreeView will probably be the most
confusing. The basic concept behind treeviews is that a treeview is the
graphical widget that administers a "store" or short-term database (usually
either a ListStore or a TreeStore) which in turn is concerned with managing
either a list-like or tree-like structure of TreeIters. In the case of a
ListStore, a TreeIter represents a row that you see in the TreeView, and the
indices of the TreeIter (like iter[0], etc.) return the values for the columns
in that row. One thing to know about GTK Iter objects is that they like to get
"invalidated" whenever the managing View changes, so for example if you get the
iter for the user's selection of the third row of a TreeView and store it for
later, you'll find that the iter itself (as opposed to its TreePath, or
"absolute" location) is no longer available. Clear? It's definitely recommended
that you take a look at this tutorial:
http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-treeview
=end
module Readinglist
require 'yaml'
require 'alexandria'
require 'gtk2'
class ReadingListApp
FILE_FORMAT = {:to_read => [], :have_read => []}
READING_LIST_FILE = File.join(ENV["HOME"], ".reading_list.yml" )
# I try to decompose methods as much as possible to show the procedural
# skeleton of the program. I name the methods to read like sentences.
def initialize
load_reading_list
get_books
setup_gui
load_books_into_listview
end
# We use YAML as the "serialization" strategy. That is, we make sure a hash
# is stored in a file in the user's home directory. We load it into memory,
# manipulate it, and always make sure to sync it back to the file. This is
# exactly how books are loaded in Alexandria. Alexandria even stores the
# class instance to the file.
def load_reading_list
unless File.exist?(READING_LIST_FILE)
File.open(READING_LIST_FILE, 'w') do |file|
file.write(FILE_FORMAT.to_yaml)
end
end
database = YAML.load_file(File.join(ENV["HOME"], ".reading_list.yml" ))
@reading_list = database[:to_read]
@have_read = database[:have_read]
puts @reading_list.inspect
end
# I have to make sure the listview and the data store are in sync, and I
# want to make sure that when the program finishes the latest changes are
# committed to file. This could get too expensive with enough books,
# though. In Alexandria, some changes happen immediately (changes in book
# attributes, covers) while others only occur on a clean program shutdown
# (deleting, saving certain preferences).
def save_to_yaml
database = FILE_FORMAT
database[:to_read] = @reading_list
database[:have_read] = @have_read
File.open(READING_LIST_FILE, 'w') do |file|
file.write(database.to_yaml)
end
end
# I initialize the Libraries simpleton (available through the above
# require) and ask it to reload its list of libaries. Then the loadall class
# method on Library gives me an array of arrays of books, which I then
# flatten; that is, make into one long array.
def get_books
libraries_simpleton = Alexandria::Libraries.instance
libraries_simpleton.reload
libraries = Alexandria::Library.loadall
@books = libraries.flatten
end
# More declarative-style methods for erecting the GUI. This type of code
# ends up being very mechanical to write, which is why people like to use
# tools like Glade. In fact, keeping this gui layout code in the open is
# probably a better idea for long-term maintenance.
def setup_gui
setup_window
setup_menus
setup_current_reading_list
setup_available_books_list
refresh_reading_list
Alexandria::UI::Icons.init
setup_callbacks
@window.show_all # This needs to be called after widgets are packed.
end
# For information on packing, see
# http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-gtk2-packing-intro . Here
# @window, which can only contain one child widget, gets a VBox. I'll put
# several widgets inside @vbox, including more "container" widgets (like
# HBox), in which I put yet more widgets.
def setup_window
@window = Gtk::Window.new("Reading List")
@window.set_width_request(800)
@window.set_height_request(600)
@window.add(@vbox = Gtk::VBox.new)
end
# For information on callbacks in Gtk see
# http://ruby-gnome2.sourceforge.jp/hiki.cgi?tut-gtk-signals . Ruby-gnome2
# callbacks use the Ruby do/end block syntax. Since I don't like to define
# the callback method inside the block, I use the method() function to turn
# the method into a proc, and use the & syntax for passing in a "proc"
# object to the block.
def setup_callbacks
@available_treeview.signal_connect("row-activated", &method(:on_row_activated))
@window.signal_connect("delete-event", &method(:on_quit))
end
# Pretty straight-forward, if wordy. The ImageMenuItems can use a
# Gtk::Stock:: constant to save some work and get pretty icons. :expand =>
# false is used to keep widgets from bulging out.
def setup_menus
@vbox.add(menubar = Gtk::MenuBar.new, :expand => false)
menubar.append(file_menu = Gtk::MenuItem.new("_File"))
menubar.append(edit_menu = Gtk::MenuItem.new("_Edit"))
menubar.append(help_menu = Gtk::MenuItem.new("_Help"))
file_menu.submenu = file_submenu = Gtk::Menu.new
edit_menu.submenu = edit_submenu = Gtk::Menu.new
help_menu.submenu = help_submenu = Gtk::Menu.new
file_submenu.add(Gtk::ImageMenuItem.new(Gtk::Stock::SAVE_AS))
file_submenu.add(quit_item = Gtk::ImageMenuItem.new(Gtk::Stock::QUIT))
quit_item.signal_connect("activate", &method(:on_quit))
edit_submenu.add(Gtk::MenuItem.new("Mark selected _read"))
help_submenu.add(about_submenu = Gtk::ImageMenuItem.new(Gtk::Stock::ABOUT))
end
# To setup the "current reading list" on top. The TreeView is connected
# directly to the ListStore, but the TreeViewColumns and the CellRenderer*s
# connected to them are responsible for telling the TreeView _how_ to
# display the data within the ListStore. The argument :text => n is a
# shorthand to tell the TreeViewColumn to associate with a position or
# index in the TreeIter (row).
def setup_current_reading_list
@vbox.add(Gtk::Label.new("Books I am reading:"), :expand => false)
@vbox.add(hbox = Gtk::HBox.new)
hbox.add(scrolley1 = Gtk::ScrolledWindow.new)
hbox.add(@button_vbox = Gtk::VButtonBox.new, :expand => false)
setup_side_buttons
scrolley1.add(@reading_treeview = Gtk::TreeView.new)
@reading_treeview.model = @reading_store = Gtk::ListStore.new(Integer, String, String)
renderer = Gtk::CellRendererText.new
reading_column1 = Gtk::TreeViewColumn.new("Order", renderer, :text => 0)
@reading_treeview.append_column(reading_column1)
reading_column2 = Gtk::TreeViewColumn.new("Title", renderer, :text => 1)
@reading_treeview.append_column(reading_column2)
reading_column3 = Gtk::TreeViewColumn.new("Author", renderer, :text => 2)
@reading_treeview.append_column(reading_column3)
end
def setup_side_buttons
@button_vbox.layout_style = Gtk::VButtonBox::START
@button_vbox.add(up_button = Gtk::Button.new(Gtk::Stock::GO_UP))
@button_vbox.add(down_button = Gtk::Button.new(Gtk::Stock::GO_DOWN))
@button_vbox.add(read_button = Gtk::Button.new("Read"))
@button_vbox.add(remove_button = Gtk::Button.new(Gtk::Stock::REMOVE))
up_button.signal_connect("clicked", &method(:on_click_up))
down_button.signal_connect("clicked", &method(:on_click_down))
remove_button.signal_connect("clicked", &method(:on_click_remove))
read_button.signal_connect("clicked", &method(:on_click_read))
end
# Same as above, except with the added wrinkle that a TreeModelSort, using
# a TreeModelFilter, is acting as a kind of proxy for the ListStore. This
# is to support sorting of columns. This code is ripped off wholesale from
# Alexandria.
def setup_available_books_list
@vbox.add(Gtk::Label.new("Available books:"), :expand => false)
@vbox.add(scrolley2 = Gtk::ScrolledWindow.new)
scrolley2.add(@available_treeview = Gtk::TreeView.new)
@list_store = Gtk::ListStore.new(Gdk::Pixbuf, String, String)
@filter = Gtk::TreeModelFilter.new(@list_store)
@available_treeview.model = @available_books_model = Gtk::TreeModelSort.new(@filter)
setup_available_books_title_column
renderer = Gtk::CellRendererText.new
column2 = Gtk::TreeViewColumn.new("Author", renderer, :text => 2)
column2.resizable = true
column2.sort_column_id = 2
@available_treeview.append_column(column2)
end
# This TreeViewColumn has two widgets, a CellRendererPixbuf for the book's
# icon, and a regular CellRendererText for the book's Title. I have to tell
# the CellRendererPixBuf how to display itself int the set_cell_data_func.
# The convert_iter_to_child_iter is some kind of bookkeeping for the
# TreeModelFilter.
def setup_available_books_title_column
column = Gtk::TreeViewColumn.new("Title")
renderer = Gtk::CellRendererPixbuf.new
column.sizing = Gtk::TreeViewColumn::FIXED
column.fixed_width = 200
column.widget = Gtk::Label.new("Title").show
column.pack_start(renderer, expand = false)
column.set_cell_data_func(renderer) do |column, cell, model, iter|
iter = @available_treeview.model.convert_iter_to_child_iter(iter)
iter = @filter.convert_iter_to_child_iter(iter)
cell.pixbuf = iter[0]
end
renderer = Gtk::CellRendererText.new
renderer.ellipsize = Pango::ELLIPSIZE_END if Pango.ellipsizable?
column.pack_start(renderer, expand = true)
column.add_attribute(renderer, :text, 1)
column.sort_column_id = 1
column.resizable = true
@available_treeview.append_column(column)
end
# This supplies the actual data to @available_treeview. Icons.cover creates
# a Gdk::PixBuf (image object) from cover files stored in the .alexandria
# directory.
def load_books_into_listview
@books.each do |book|
icon = Alexandria::UI::Icons.cover(book.library, book)
icon = icon.scale(20,25)
iter= @list_store.append
iter[0] = icon
iter[1] = book.title
iter[2] = book.authors.join(" ")
end
end
# Call this when you want to repopulate and reorder the reading_list.
def refresh_reading_list
@count = 0
@reading_store.clear
@reading_list.each do |item|
iter = @reading_store.append # Gets a new iter (row) to work with.
iter[0] = @count += 1
iter[1] = item[0]
iter[2] = item[1]
end
end
# @reading_treeview.selection.selected is the iter of the selected row.
def on_click_remove widget
selection = @reading_treeview.selection.selected
@reading_list.delete_at(selection[0].to_i - 1)
save_to_yaml
refresh_reading_list
end
def on_click_read widget
selection = @reading_treeview.selection.selected
@have_read << @reading_list.delete_at(selection[0].to_i - 1)
save_to_yaml
refresh_reading_list
end
# The idea is to swap the iters in the TreeView and mirror the swap in the
# reading list. The iter's path is like its current map coordinates. Since
# iters are always getting invalidated, it's a good plan to work with the
# path.
def on_click_up widget
selection = @reading_treeview.selection.selected
position = selection[0].to_i - 1
previous_path = selection.path
previous_path.prev!
previous = @reading_store.get_iter(previous_path)
@reading_store.move_before(selection, previous)
unless (position - 1) < 0
@reading_list.insert(position - 1, @reading_list.delete_at(position))
refresh_reading_list
end
@reading_treeview.selection.select_path(previous_path)
end
# on_click_up and on_click_down call for refactoring to reduce duplicated
# code. Try it for yourself if you're interested.
def on_click_down widget
selection = @reading_treeview.selection.selected
position = selection[0].to_i - 1
previous_path = selection.path
after_path = selection.path
after_path.next!
after = @reading_store.get_iter(after_path)
unless (position + 1) == @reading_list.length
@reading_store.move_after(selection, after)
@reading_list.insert(position + 1, @reading_list.delete_at(position))
refresh_reading_list
@reading_treeview.selection.select_path(after_path)
end
end
# This is the callback for when a row in the lower available books listview
# gets clicked.
def on_row_activated widget, path, column
iter = @available_treeview.model.get_iter(path)
puts "#{iter[0]} #{iter[1]} #{iter[2]}"
reading_list_item = []
reading_iter = @reading_store.append
reading_iter[0] = @count += 1
reading_iter[1] = iter[1]
reading_list_item << iter[1]
reading_iter[2] = iter[2]
reading_list_item << iter[2]
@reading_list << reading_list_item
save_to_yaml
end
# This method is called when the window is closed and when the quit menu
# option is activated. Event is used for when the window connects to the
# 'delete-event' signal and requires both parameters. Gtk.main_quit kills
# the Gtk.main loop.
def on_quit widget, event = nil
Gtk.main_quit
end
end
def self.main
ReadingListApp.new
Gtk.main
end
end
=begin
Some features that could be added:
* connect up menu items
* figure out how save to... works; does it export to one specific format or
several?
* Not a feature, but can the code be made cleaner, cleaner, more testable?
* Something indescribably awesome...
A couple random tips:
install ruby-debug gem to step through this code to see how it works
See http://cheat.errtheblog.com/s/rdebug/ for more information
install the utility_belt gem for colorized irb and add
require 'rubygems'
require 'utility_belt'
to a file ~/.irbrc
=end
if __FILE__ == $0
Readinglist.main
end
hooray.
Happy Administrative Professionals Week (( Probably over by the time you read this. Administrative Professionals Day was Wednesday, April 25. )) (( I got a mug. )).
A minor victory.
For a long time now I’ve been somewhat of a GTD guy, although I’m not religious about it and I’ve never been quite able or willing to implement it fully. Basically, all this means is that I keep a todo list that I review when I’m feeling conscientious about my life (( Actually, I keep several todo lists, which is a big no-no in GTD. I use Tomboy on my laptop and stikkit.com when I’m at work. )). One of the tasks that has been sitting in my list for a long time is Migrate old blog articles. Now I can cross it off! Look, I’m crossing it off: Migrate old blog articles. God, that was satisfying.
Yes, I finally managed to import all the posts from my old Textpattern blog (( Some of them reference pictures that I’ll have to dig up and upload. )). These go back to the days when I was working at National Geographic in DC, and had a lot of free time during work hours to do fun things like investigate cmses and purchase web domains. (( That’s the origin of the name Stupid Idea. The original stupid idea was to create a website that would be a kind of clearing house for “stupid” ideas — ideas that were deeply flawed but worth preserving on and improving. There was a MediaWiki installation that held about 100 articles before I switched web hosts. )) Some of them are written in a half-ironic high philosophic tone, others are intended to express minimalist pathos.
This minor victory was achieved not without some casualties. I have a peculiar knack for smashing up virtual servers (( Try this on a Linux host that you have root access on: `sudo rm -rf /`. Note: please don’t. )) , clobbering databases and borking filesystems. So, uhh, I’m very sorry but we lost the old comments.
Memory
I have an intense memory of this show. I would have been six in 1987. I remember it as being extremely dark and disturbing. The protagonists wore power suits and the villains were completely robotic from the head down and they flew around in a post-apocalyptic industrial wasteland and fought. I think I must have had access to one of the toys at one point. There was a game you could play with the toy and a video cassette. I guess this game was stupid, or the toy broke or something, because I made my own “robotic soldier” out of a plush parrot and tinfoil, using syran wrap for the visor in the helmet. This augmented parrot was very, very cool and completely satisfied my feverish six-year-old imagination. My friend did the same thing with a teddy bear. Be sure to take a look at the wikipedia article. Who were the terrible people who made this show?
My City Story
a la The City:
I was listening to my ipod in the back of the bus on the way to work. A guy got on with a guitar and came and stood at the back of the bus. He hesitated, even though there were two available seats. He looked at me and said, “You know why a guitar is better than an ipod?” I shook my head and smiled ambivalently. He kept looking at me. I took off my headphones. “I said, you know why a guitar is better than an ipod? Because it’s infinite!”
Maybe he was trying to tell me this.
In Pittsburgh
Things that are different in Pittsburgh:
Alcohol — You can only buy wine or liquor at the state store. Only bars are allowed to sell takeaway beer … so the corner convenience store has an area where you can drink singles and smoke (smoking ban in effect March 30). You can bring your own beer or wine to a restaurant without a liquor license, and pay a corking fee. The way I remember it is, everything you can do in DC, like buy beer in a grocery store, you can’t do in Pittsburgh, and everything you can do in Pittsburgh, like drink beer in a convenience store or carry a six-pack out of a bar, you can’t do in DC.
Buses – Buses are part of the strangely influential and omnipresent Allegheny County Port Authority. Port Authority runs a huge transit system consisting of hundreds of little niche bus lines each with multiple permuations of [A, B, C, D…] and [Local, Express, Alternate], the T line light transit (which many people seem to feel is useless) and probably ferries, hot air balloons, etc. I don’t know exactly why I have this impression of bizarre complexity, except that it is a remarkably big system (facing cuts) that everyone takes for granted here. Near my apartment there is a dedicated bus road going downtown.
And buses have an authority beyond their size in this city. A bus I was on didn’t turn the corner at a sharp enough angle and couldn’t make its left turn because of the solid line of oncoming cars. The driver backed the cars up as far as they could go, and then forced three cars to drive onto the sidewalk before he could straighten out into his lane. The other day I saw a cop holding up traffic while he furiously berated a guy in a sports car for trying to get around a bus.
The buses aren’t actually lines, but cycles. They nearly all go downtown, so they’re basically named after their originating neighborhoods. These two facts confused me a lot when I first started riding. I couldn’t understand how I could get off the bus at one stop and then catch the same bus going in the same direction to go home. It’s because they follow a single route from the named neighborhood until they approach downtown, then they loop through the city and return to the single route when they get out from the city.
This is the reason for the (initially, to me) strange fare policy of having people pay when then get on if they are “inbound” and pay when they get off if they are “outbound”. Of course, it makes more sense for the bus to spend less time taking people on in the chronically congested downtown, especially at rush hour. But there are some places when the bus is still basically inbound but you’re planning to go outbound where it gets confusing. You just get on and don’t pay until you get off. Today I saw an angry homeless guy walk in through the back door (which is okay, as long as you’re outbound) and ride a couple of blocks and get off without paying. For all I know, it’s free to ride around downtown. At least, this is a fairly obvious hack of the system.
I think the bus system might be chainlinked way out into Allegheny county through a system of bus depots, like the one in nearby East Liberty. So the sense is that this is a commuter transit system where all roads lead to Pittsburgh’s downtown. 1 This is a city with lots of cool, fully-fleshed-out commercial neighborhoods, but everyone who rides the bus has a constant need to go downtown.
I know I sound out of it. It’s just so much more centralized than I’m used to living in DC, where the bus lines crisscross the city with the purpose of getting you from one neighborhood to another, or from one quadrant to another. Also, we have the subway2 . I would probably have a different view if I’d ever tried the daily commute from Maryland or Virginia.
Grocery store – We have a Whole Foods and a Trader Joe’s nearby. We mostly shop at the Giant Eagle, though. (Amy likes to free-associate this name with the divebombing bald eagle from the intro to the Colbert report.) The Giant Eagle in our neighborhood seems to be pursuing every demographic at once, and does a pretty good job of it. They have a complicated organization scheme that is oddly intuitive:
Cafe, plants, deli, gourmet cheeses, produce, deli breads… (yes, I’m doing this all from memory) then several aisles of food grouped by ethnicity: a whole wall of kosher foods facing miscellaneous European food nationality groups (e.g., German for jars of sauerkraut, a Scandinavian cluster for chocolates and strange pickled fruits) ; Mexican (e.g., salsas, including blatantly Anglo salsas) ; Asian (e.g., instant noodles) ; Italian (e.g., spaghetti sauce); Polish (e.g., pierogies) — each of these is very decently stocked with interesting stuff, and probably provides a good census of the ethnic-American makeup of the city. Along the walls, the standard fish, meat, milk, a small but vital fake lunch meats (and fake cheese) section, then pasteurized cheeses (remember the gourmet cheese section?) … over to breads, where you can find Thomas Squares Bagelbread3. Then several long aisles for: cookies, crackers, juice boxes; soups, spices, coffees and teas; beverages (juice); beverages (sodas); magazines; personal hygiene products; etc. ; etc. The two frozen food aisles feature lots of frozen ethnic foods, including more pierogies, as well as an excellent fake meat section (across and down from the Organic Foods section, which also has vegetarian fare). There’s a bank, a pharmacy and a place to buy bus passes. Amy gets a discount if she buys gas from a Giant Eagle affiliated gas station.
I want to talk more about this grocery store, I really do. The reason why I mentioned all that was to point out how ingeniously broken the store’s taxonomy is. In most grocery stores it’s more or less kingdom -> genera -> species, dairy -> cheese -> velveeta. Here, the categorization is based on clades.
Pretty good grocery store.
revelation.
[12:14:10 AM] tristil: All of a sudden, I get it.
[12:14:22 AM] Amy Taylor: yeah?
[12:14:26 AM] tristil: A rock group is typically four guys who practice a lot together.
[12:14:29 AM] tristil: To get a groove.
[12:14:54 AM] tristil: Jazz groups are a bit more fluid, because they’re trained to improvise and groove with each other.
[12:15:14 AM] tristil: Hip-hop is about a DJ and an MC or multiple MCs.
[12:15:36 AM] tristil: That’s why they have more collaborations.
[12:16:08 AM] tristil: Because it’s always a DJ looking for an MC, and vice versa. Each is incomplete without the other.
[12:17:42 AM] tristil: That’s all. I just didn’t know that before.
Update: Listen to this interview with Grandmaster Flash to gain true enlightenment. At the end he explains the origin of the MC. I love that: inward DJ, outward MC.
I am a programming god.
At least, I program. One of the happy recent developments in my life (out of some that have not been so happy) is that the programming thing is starting to come together.
What this really means is that if I apply myself to a programming task, usually in the form of hacking on an existing codebase from an established project, I am now able with considerable persistence and a fair amount of hair-pulling and alienation of my girlfriend, to push out a passable solution.
I can submit as evidence:
- A patch (and follow-up patch) to enable Banshee support for the music track listener in Gajim.
- The work I do as maintainer of the Alexandria1 project.
I find that progress in programming skill proceeds somewhat arithmetically. There are days that I have spent hours banging my head against a problem only to come up with a solution that consists of a single line change. So I’m doing better if I make one more significant line change than yesterday, two more, etc. I just wrote about 8-10 lines of code in the course of about 5 hours. Pretty good! This is the rate I maintain when hunting and fixing bugs. I do slightly better when I’m adding incremental features, in part because I’m busy introducing new bugs and because my own code tends to be verbose. There’s some backsliding in this metric when I figure out/remember how to be more concise (for example, using collect and writing blocks for functions in Ruby and using list comprehensions in Python2), but this a good thing.
The last step is to learn how to properly design a program from the ground up. Learning how to think OO3 is the last to come for me. Studying the Alexandria codebase has helped me a lot; the previous development team was a clever bunch of guys, full of meta-programming tricks and “expressive” Ruby code that have caused me endless hours of frustration and led me to some pleasing razor’s-edge-of-duress epiphanies. I’m working on a program called Chronology that will give me some practice with designing a program from scratch.
In another life, I got a CS degree and this is all easy for me.
Temping.
Let me tell you about temping sometime.