<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7497847932106835950</id><updated>2012-01-30T03:45:31.474-08:00</updated><category term='slip-cover'/><category term='quickly'/><category term='music'/><category term='quidgets'/><category term='bughugger'/><category term='ubuntu'/><category term='developer-manual'/><category term='daily-journal'/><category term='photobomb'/><title type='text'>The Raving Rick</title><subtitle type='html'>The diary of a dedicated Ubuntu user that lucked into his dream job working on the Ubuntu team.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default?start-index=101&amp;max-results=100'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>144</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4005223862228328345</id><published>2012-01-12T04:02:00.001-08:00</published><updated>2012-01-12T04:16:43.858-08:00</updated><title type='text'>Bit of fun with JQuery and CSS</title><content type='html'>&lt;a href="http://2.bp.blogspot.com/-szlUGGRi9Xs/Tw7Nwp8bMqI/AAAAAAAAAjQ/wwyoUpykIvE/s1600/Screenshot%2Bat%2B2012-01-12%2B13%253A01%253A22.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://2.bp.blogspot.com/-szlUGGRi9Xs/Tw7Nwp8bMqI/AAAAAAAAAjQ/wwyoUpykIvE/s320/Screenshot%2Bat%2B2012-01-12%2B13%253A01%253A22.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5696716814446965410" /&gt;&lt;/a&gt;I stole some time to play a bit more with veritas and JQuery today. Instead of the ugly list that I had before, I wanted some interactivity. So I got started by adding a little css to make a "card" for each bottle.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt; .bottle&lt;br /&gt;{&lt;br /&gt;width:300px;&lt;br /&gt;background-color:rgba(0,0,0,.5);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then I wrote a bit of javascript to make each div that I pass into the html into a JQuery "draggable", and do a bit of cheap layout.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;    else if(signal == "add_bottle")&lt;br /&gt;  {&lt;br /&gt;   div = jQuery(data,{}).draggable();&lt;br /&gt;   div.css('position','absolute');&lt;br /&gt;   div.css('left', lft);&lt;br /&gt;   div.css('top',tp);&lt;br /&gt;   lft += 10;&lt;br /&gt;   tp += 10;&lt;br /&gt;   $( "#bottle_div" ).append(div);&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Next I'll add some nicer layout. Then I'll start adding filters and dropdowns so I can sort and do other fun stuff.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4005223862228328345?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4005223862228328345/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2012/01/bit-of-fun-with-jquery-and-css.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4005223862228328345'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4005223862228328345'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2012/01/bit-of-fun-with-jquery-and-css.html' title='Bit of fun with JQuery and CSS'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-szlUGGRi9Xs/Tw7Nwp8bMqI/AAAAAAAAAjQ/wwyoUpykIvE/s72-c/Screenshot%2Bat%2B2012-01-12%2B13%253A01%253A22.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7216149713824945976</id><published>2011-12-30T05:20:00.000-08:00</published><updated>2011-12-30T05:58:16.753-08:00</updated><title type='text'>In Vino JQuery, not a Socratic, dialogs</title><content type='html'>&lt;a href="http://3.bp.blogspot.com/-XanI0z6yMjA/Tv264v81MPI/AAAAAAAAAjA/S4VaHFfZH04/s1600/Screenshot%2Bat%2B2011-12-30%2B13%253A57%253A30.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-XanI0z6yMjA/Tv264v81MPI/AAAAAAAAAjA/S4VaHFfZH04/s320/Screenshot%2Bat%2B2011-12-30%2B13%253A57%253A30.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5691910988173226226" /&gt;&lt;/a&gt;&lt;div&gt;I spent a bit of today adding the capability to Veratas to collect user input in the form of a "dialog". I put "dialog" in quotes, because I used a JQuery dialog within the HTML, rather than a Gtk dialog window.&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before settling on &lt;a href="http://jqueryui.com/"&gt;JQuery&lt;/a&gt; for this project, I looked at it and &lt;a href="http://developer.yahoo.com/yui/"&gt;YUI&lt;/a&gt; in some depth. I was attracted to &lt;a href="http://developer.yahoo.com/yui/"&gt;YUI&lt;/a&gt; because it seems very very complete. In fact, it has a filterable and sortable &lt;a href="http://www.yuiblog.com/blog/2011/03/01/filtering-yui3-datatable/"&gt;data grid&lt;/a&gt;, which is &lt;a href="http://theravingrick.blogspot.com/2009/12/take-that-treeview-i-win-ding-ding-ding.html"&gt;very important to me&lt;/a&gt;, as most applications, when you get down to it, are really just CRUD apps.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;However, I went with &lt;a href="http://jqueryui.com/"&gt;JQuery&lt;/a&gt; for Veritas because the &lt;a href="http://jqueryui.com/demos/"&gt;samples and tutorials&lt;/a&gt; made it seem very easy to get things done, and Veritas has simple needs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;JQuery has a cool page where you can&lt;a href="http://jqueryui.com/download"&gt; create just the javascript that you need&lt;/a&gt;, as well as a &lt;a href="http://jqueryui.com/themeroller/"&gt;theme generator&lt;/a&gt;. Note the "grapey" dialog bar in the screenshot, I set that color in the&lt;a href="http://jqueryui.com/themeroller/"&gt; theme generator&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span &gt;What the Dialog Does&lt;/span&gt;&lt;/div&gt;&lt;div&gt;First thing was to lay out the dialog in the normal HTML way. Note that I set it to be display:none, by default.&lt;/div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;  &amp;lt;div id="dialog" title="Enter New Bottle" style="display:none;width=00px"&amp;gt;&lt;br /&gt;&amp;lt;fieldset&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="country"&amp;gt;Country&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="country" id="country" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="region"&amp;gt;Region&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="region" id="region" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="domain"&amp;gt;Domain&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="domain" id="domain" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="grapes"&amp;gt;Grape(s)&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="grapes" id="grapes" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="price"&amp;gt;Price&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="number" name="price" id="price" value="" placeholder="$"&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="rating"&amp;gt;Rating&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;select name="rating" id="rating" value="" placeholder=""&amp;gt;&lt;br /&gt; &amp;lt;option value="1"&amp;gt;1&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="2"&amp;gt;2&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="3"&amp;gt;3&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="4"&amp;gt;4&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="5"&amp;gt;5&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="6"&amp;gt;6&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="7"&amp;gt;7&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="8"&amp;gt;8&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="9"&amp;gt;9&amp;lt;/option&amp;gt;&lt;br /&gt; &amp;lt;option value="10"&amp;gt;10&amp;lt;/option&amp;gt;&lt;br /&gt;&amp;lt;/select&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="taste"&amp;gt;Taste&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="taste" id="taste" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;label for="image"&amp;gt;Label Picture&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="image" id="image" value="" placeholder=""&amp;gt;&lt;br /&gt;&amp;lt;button id="preview_button"&amp;gt;Preview&amp;lt;/button&amp;gt;&lt;br /&gt;&amp;lt;img id="preview_image" src=""/&amp;gt;&lt;br /&gt;&amp;lt;/p&amp;gt;&lt;br /&gt;&amp;lt;p&amp;gt;&lt;br /&gt;&amp;lt;button id="submit_new"&amp;gt;OK&amp;lt;/button&amp;gt;&lt;br /&gt;&amp;lt;/P&amp;gt;&lt;br /&gt;&amp;lt;/fieldset&amp;gt;&lt;br /&gt;&amp;lt;/div&amp;gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;Then, I created a "New" button, and wired it up to some code that I was able to get from the excellent JQuery demo pages to display the dialog. Note that the documentation made it really easy to copy and paste my way to success here, including figuring out how to choose different reveal effects and such.&lt;/div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        $( "#dialog" ).dialog({&lt;br /&gt;      autoOpen: false,&lt;br /&gt;      width: 600,&lt;br /&gt;      show: "blind",&lt;br /&gt;      hide: "blind"&lt;br /&gt;  });&lt;br /&gt;  $( "#new_button" ).click(function() {&lt;br /&gt;      $( "#dialog" ).dialog( "open" );&lt;br /&gt;      return false;&lt;br /&gt;  });&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The dialog includes a submit button. I wired this up to create a JSON object and send a signal with all this data to the backend using the "send_message" javascript function.&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        $( "#submit_new" ).click(function() {&lt;br /&gt;       bottle = {"country": + $( "#country" ).val(),&lt;br /&gt;       "region": $( "#region" ).val(),&lt;br /&gt;       "domain": $( "#domain" ).val(),&lt;br /&gt;       "grapes": $( "#grapes" ).val(),&lt;br /&gt;       "price": $( "#price" ).val(),&lt;br /&gt;       "rating": $( "#rating" ).val(),&lt;br /&gt;       "taste": $( "#taste" ).val(),&lt;br /&gt;       "image": $( "#image" ).val()};&lt;br /&gt;       send_message("new_wine", bottle);&lt;br /&gt;       $( "#dialog" ).dialog( "close" );&lt;br /&gt;       return false;&lt;br /&gt;   });&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;Yesterday, send_signal took 2 strings: the name of the signal and some other data. Today I changed it to take the name of the signal, and any javascript object. The function uses a popular JSON parcer to stringify the javascript object before using the "set title hack" to pass the data tot he back end.&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;function send_message(signal_name, data)&lt;br /&gt;{&lt;br /&gt;title = document.getElementsByTagName("title")[0];&lt;br /&gt;message = {"signal": signal_name,"data":data};&lt;br /&gt;title.innerHTML = JSON.stringify(message);&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now that I have written that part, I don't have to worry about formatting my data, I can just pass it over.&lt;div&gt;&lt;br /&gt;&lt;div&gt;Similarly, on the back end, I made the HTMLWindow class decode the json and pass it along:&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def _on_html_message(self, view, frame, title):&lt;br /&gt;   if title != "null":&lt;br /&gt;       try:&lt;br /&gt;           message = json.loads(title)&lt;br /&gt;       except Exception, inst:&lt;br /&gt;           print inst&lt;br /&gt;           message = {"signal":"error","data":"signal not parsed"}&lt;br /&gt;   else:&lt;br /&gt;       message = None&lt;br /&gt;   self.on_html_message(message["signal"],message["data"])&lt;br /&gt;&lt;br /&gt;def on_html_message(self,message):&lt;br /&gt;   pass&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;As a result, subclasses like VeritasWidnow can just use the data without worrying about the implementation. It doesn't do anything with the data yet.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span &gt;2 Way Communication&lt;/span&gt;&lt;/div&gt;&lt;div&gt;I did add one bit of round tripping. It turns out that as a security precaution, the "file" input type does not let the javascript see the full path selected, it only allows the selected file to be uploaded to the server. I hope that I can figure out how to let the user grant Veritas permissions to pass the selected file to the javascript, but I can hack around it if it doesn't turn out to be easy or possible.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Meantime, I let the user type in a full path to the file, and then click "Preview". This takes the entered string, and sends it to the back end.&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;    $( "#preview_button" ).click(function() {&lt;br /&gt;        send_message("image_preview", $( "#image" ).val() );&lt;br /&gt;        return false;&lt;br /&gt;    });&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;div&gt;The back end then uses the awesome PIL libary to make a thumbnail, and then passes the path of the thumbnail back. I actully suspect that I will be able to skip the step of saving the file and just use the string data, possible with the Canvas element.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def on_html_message(self, signal_name, data):&lt;br /&gt;    if signal_name == "image_preview":&lt;br /&gt;        try:&lt;br /&gt;            img = Image.open(data)&lt;br /&gt;            img.thumbnail((128,128), Image.ANTIALIAS)&lt;br /&gt;            path = """/home/rick/.tmp/thumbnail.jpg"""&lt;br /&gt;            img.save(path,"JPEG")&lt;br /&gt;            path = "file://" + path&lt;br /&gt;            self.view.execute_script("receive_signal('set_preview','" + path + "');")&lt;br /&gt;        except Exception, inst:&lt;br /&gt;            print inst.message&lt;br /&gt;            self.view.execute_script("""receive_signal('set_preview_error','Could not find a valid image at %s');""" % data)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span &gt;Debugging HTML/javascript&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Another handy think I found today is that I can load the HTML page into Firefox, and use a web console to poke at it. Very handy. Of course, this works now because I am not doing string replacement, but I think that I can actually make a similar thing work with a WebKit window.&lt;/div&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-Wz9GOYOuchw/Tv264s624vI/AAAAAAAAAi4/m1NleZqsrbA/s1600/Screenshot%2Bat%2B2011-12-30%2B14%253A03%253A08.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-Wz9GOYOuchw/Tv264s624vI/AAAAAAAAAi4/m1NleZqsrbA/s320/Screenshot%2Bat%2B2011-12-30%2B14%253A03%253A08.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5691910987359642354" /&gt;&lt;/a&gt;&lt;span style="font-size: x-large; "&gt;Next&lt;/span&gt;&lt;br /&gt;&lt;div&gt;So now that I can collect the info from the user, I'll start saving the data in a sqlite database, and then work on presenting the data to the user.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I pushed a separate &lt;a href="https://code.launchpad.net/~rick-rickspencer3/+junk/veritas-jquery"&gt;branch with the JQuery code here&lt;/a&gt;.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7216149713824945976?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7216149713824945976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/12/in-vino-jquery-not-socratic-dialogs.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7216149713824945976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7216149713824945976'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/12/in-vino-jquery-not-socratic-dialogs.html' title='In Vino JQuery, not a Socratic, dialogs'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-XanI0z6yMjA/Tv264v81MPI/AAAAAAAAAjA/S4VaHFfZH04/s72-c/Screenshot%2Bat%2B2011-12-30%2B13%253A57%253A30.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6505313248276366951</id><published>2011-12-29T09:00:00.000-08:00</published><updated>2011-12-29T09:12:36.952-08:00</updated><title type='text'>In Vino Veritas and HTML5 Client Apps</title><content type='html'>&lt;a href="http://3.bp.blogspot.com/-NBvHuT7JL1U/Tvyc4ag_ntI/AAAAAAAAAis/V7Zz1lvHEt8/s1600/Screenshot%2Bat%2B2011-12-29%2B17%253A07%253A01.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-NBvHuT7JL1U/Tvyc4ag_ntI/AAAAAAAAAis/V7Zz1lvHEt8/s320/Screenshot%2Bat%2B2011-12-29%2B17%253A07%253A01.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5691596522093780690" /&gt;&lt;/a&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, basically, not to put to fine a point on it, I've started to write apps for Ubuntu in a different way, essentially, replacing Gtk (or really PyGtk) with HTML5. This is my first post about how am I doing it. I've just started a project called "Veritas" which will be a wine tasting database for my wife and I. We'll be able to enter information about each bottle that we drink, and then look at trends over time, perhaps helping us pick nicer and nicer bottles as we go.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;First, though, what happened, I thought you got along great with Gtk?&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Well, I do still have a soft spot in my heart for pygtk. Believe me, I've written plenty of code in it. I know the ins and outs pretty well, and I'm able to do things with it like write a response UI that doesn't block to much during run longing processes and such. PyGtk is great for building "boxy" apps, but I think a lot of people want to build slicker apps than Gtk is really designed for, or at least design them in different ways thatn Gtk supports well.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;Why not Qt and QML?&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;This app, in fact, would be well suited for a QML app. However, I have other apps in mind, and I found QML/Qt to not be quite up to the job. For instance, I want to write a communication app to combine OpenLDAP and IRC functionality. Currently, there are no Qt libraries for LDAP or IRC, so to write such an app with QML, I'd have to write C++ Qt code to wrap whatever C libraries, and then write code to export models from that C++ code to expose it the right way in QML. That is a lot of overhead, especially considering that there are good Python libraries for LDAP, IRC, and pretty much anything desired. So, I designed myself a system that let me stick with Python for the back end code.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Also, QML lacks a widget toolkit at the moment, so there would be a lot of manual coding of things like buttons and such.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;Why HTML5?&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;I chose HTML 5 for the widget toolkit for a few reasons. &lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;I already know HTML/CSS/Javascript pretty well, and I know that cool things can be done with it. I bet a lot you all know it pretty well too.&lt;/li&gt;&lt;li&gt;Webkit is very well supported Open Source used and maintained by many large companies.&lt;/li&gt;&lt;li&gt;There are lots of cool widget toolkits to choose from, I'm currently looking at YUI since I think it's in pretty heavy use by some of the web teams at Canonical.&lt;/li&gt;&lt;li&gt;Because I wanted to try out HTML for client programming.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;My Application Architecture&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;First, I laid everything out total flat to start with. This is because I wanted to come to grips with making the view code talk to the model/controller code without mucking with any extra complexity. Of course, I will need to modify the layout as the actual code grows.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Currently, I am only focused on makinga  client application programming system, though I may, in the future, extend the system so the model/controller back end could be on a server, and the view available via a browser. But this is firmly out of scope right now. I am, however, trying to be cognizant of making the system essentially portable by sequestering the Gtk specific code into specific files that can be replaced if I want to run it without Gtk at some point.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Therefore, there are some important differences to note if you are used to web programming.&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;The back and and view code communicate via signals to each other. This is much different than web programming, where the view makes a request and waits for the server to respond with a string (for Ajax apps) or redirects to another view passing some state along with it.&lt;/li&gt;&lt;li&gt;This means that the back end can send signals to the view. The view does not need to pole to see if the back end is ready, for example, the back end can just send a signal when it is.&lt;/li&gt;&lt;li&gt;This also means that long running processes can block the GUI, since they are running in the same thread. I shall most likely put the Gtk main loop in it's own thread so I can run run-longing processes in seperate threads, and then communicate between them.&lt;/li&gt;&lt;li&gt;The view cannot call a function on the back end, and wait for a response (for example with XmlHttpRequest). Rather it can only send the back end a signal.&lt;/li&gt;&lt;li&gt;The back end is not stateless. This is greatly simplifying. Most web programming frameworks have a lot of code to maintain state by storing it and accessing it on future requests by reading cookies stored on the client.&lt;/li&gt;&lt;li&gt;Currently, I have nothing like server side tags that are the bread and butter of most web programming frameworks. This typically works via string replacement, so I could either find a library to add this functionality, or make it easier to do string replacement with the HTML. This is typically desirable for a web app since you want to configure the HTML before it is sent from the server. Less important in a client app, but still, some string replacement of HTML may save some effort in writing complex javascript against the browser DOM.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/files"&gt;&lt;b&gt;The Code&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Ok, let's get to the good stuff. To bin file is called "&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/veritas"&gt;veritas&lt;/a&gt;". Running this file creates a &lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/VeritasWindow.py"&gt;VeritasWindow&lt;/a&gt; and then starts the Gtk main loop. The Gtk main loop is there because the Webkit window has to run inside something, and I chose a Gtk Window for this because of the simple integration with Ubuntu.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A &lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/VeritasWindow.py"&gt;VeritasWindow&lt;/a&gt; only does 2 things so far. It tells it's baseclass "&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/HTMLWindow.py"&gt;HTMLWindow&lt;/a&gt;" what html file to load, and it listens for signals from that view. Later, it will create new HTML5 Windows and do other stuff in response to signals from the HTML view.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/HTMLWindow.py"&gt;HTMLWindow&lt;/a&gt; is meant to be used only as a base class. First, it creates a top level menu so you can quit the app, and also, I think that apps should have menus (I haven't really thought through how menus will work in this system yet, but I'm hoping that DBUS Menu helps me out). Then it loads the HTML that the subclass told it to load. It also listens for signals from the view, parses the signals and has what is essentially a virtual function called "on_html_message" for subclasses to override. You should be able to receive messages from the view without looking at the internals of how it works. Among other things, this is platform specific.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/main.html"&gt;main.html&lt;/a&gt; is the HTML5 code for the main window. All it does now is send a signal that it is loaded, and you can see that I added a heading. When paired with &lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/main.css"&gt;main.css&lt;/a&gt; the layout and look and feel of the UI will be controlled completely in the view code.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://bazaar.launchpad.net/~rick-rickspencer3/+junk/in-vino/view/head:/helpers.js"&gt;helpers.js&lt;/a&gt; is a file that I think I may need to handle platform specific signals sent to the view. Of course, you can always call "execute_script" and send whatever you want from the backend, but I think it's cleaner to expect well formatted signals from the back end instead.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&lt;b&gt;Conclusion&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;So, that's basically all the boiler plate for making an HTML5 client app for Ubuntu. This represents a few hours of work on my part to make a re-usable and extensible system.&lt;/div&gt;&lt;div&gt;My next steps will be to do some database programming with sqlite, then I'll probably build a data input window for it. This certainly calls to mind Rails-like thinking (hmmm, I have the model, why can't I generate the view from that on the fly?), but, I don't think I want anything that complex. After I finish Veritas, I'll then extract the base classes and such, and perhaps create a Quickly template, then go ahead and work on my certain to be more complex communication application.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6505313248276366951?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6505313248276366951/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/12/in-vino-veritas-and-html5-client-apps.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6505313248276366951'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6505313248276366951'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/12/in-vino-veritas-and-html5-client-apps.html' title='In Vino Veritas and HTML5 Client Apps'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-NBvHuT7JL1U/Tvyc4ag_ntI/AAAAAAAAAis/V7Zz1lvHEt8/s72-c/Screenshot%2Bat%2B2011-12-29%2B17%253A07%253A01.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4435390588872065639</id><published>2011-11-28T08:52:00.000-08:00</published><updated>2011-11-28T10:03:07.804-08:00</updated><title type='text'>Smoke Tests</title><content type='html'>&lt;a href="http://2.bp.blogspot.com/-LE3_eK1STbY/TtO81a_H86I/AAAAAAAAAiQ/OgIMNAUYoKY/s1600/5cd8_1.JPG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 262px; height: 320px;" src="http://2.bp.blogspot.com/-LE3_eK1STbY/TtO81a_H86I/AAAAAAAAAiQ/OgIMNAUYoKY/s320/5cd8_1.JPG" border="0" alt="" id="BLOGGER_PHOTO_ID_5680091181007500194" /&gt;&lt;/a&gt;&lt;div style="text-align: left;"&gt;The QA team has started a page for getting up the minute automated results from smoke testing of daily images. &lt;a href="https://jenkins.qa.ubuntu.com/view/Precise/"&gt;Check it out!&lt;/a&gt; They still have more smoke tests to set up, but everything is running automatically from daily builds. You can check to see if the latest build installs and if basic tests run. If they do, it's probably worth testing with that build. If it's not, then the team should be busy at work at making it work testing!&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Also, this page is just on small step in the &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/other-p-builds-smoke-testing"&gt;blueprint for smoke tests results page&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4435390588872065639?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4435390588872065639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/11/smoke-tests.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4435390588872065639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4435390588872065639'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/11/smoke-tests.html' title='Smoke Tests'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-LE3_eK1STbY/TtO81a_H86I/AAAAAAAAAiQ/OgIMNAUYoKY/s72-c/5cd8_1.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4103267865503904253</id><published>2011-11-18T00:09:00.001-08:00</published><updated>2011-11-18T09:45:39.799-08:00</updated><title type='text'>12.04 Quality Initiatives Update</title><content type='html'>It's been 2 weeks since UDS. We left UDS with a lot of big plans about  developing 12.04 in a precise way. So, in 2 weeks, we've gotten a good  start. Here's a slapdash update before I leave for the weekend.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;+1 Maintenance and Daily Quality&lt;/span&gt;&lt;br /&gt;For the last week, we've had an installable and usable image every day!&lt;a href="https://wiki.ubuntu.com/PlusOneMaintenanceTeam/Status"&gt; Colin is keeping a wiki &lt;/a&gt;until we have a proper dashboard set up.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Upstream Testing&lt;/span&gt;&lt;br /&gt;A lot has gone on in this area. The QA team spent this week getting a QA  lab set up, so we have a place to run automated tests. The Desktop team  is working with the Dx team to get automated compiz testing set up,  hopefully as early as next week. I need to circle back with other  upstream projects, especially the ones that Ubuntu Engineering make, to  check on their progress.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Distro Acceptance Testing&lt;/span&gt; &lt;span style="font-weight: bold;"&gt; &lt;/span&gt;&lt;br /&gt;Didier Roche has started documenting tests to be run before uploads of Unity and working with the QA team to figure out how best to track them. You can check out his progress &lt;a href="https://wiki.ubuntu.com/UnityTests"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;QA Lab&lt;/span&gt;&lt;br /&gt;Like, I said, the QA team has been working on the QA lab. We have trunk tests and distro acceptance tests to run. We need hardware! Here's a shot that Pete Graner took of the new rack for Open Stack testing:&lt;br /&gt;&lt;a href="http://frylock.redvoodoo.org/%7Epgraner/lab/IMG_20111116_111135.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 244px; height: 326px;" src="http://frylock.redvoodoo.org/%7Epgraner/lab/IMG_20111116_111135.jpg" alt="" border="0" /&gt;&lt;/a&gt;Lots going on to make a 12.04 in a precise way! You can see the QA team's current work items &lt;a href="http://status.ubuntu.com/ubuntu-precise/canonical-platform-qa.html"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4103267865503904253?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4103267865503904253/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/11/1204-quality-initiatives-update.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4103267865503904253'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4103267865503904253'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/11/1204-quality-initiatives-update.html' title='12.04 Quality Initiatives Update'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-156072708270431180</id><published>2011-11-08T06:02:00.001-08:00</published><updated>2011-11-09T08:17:38.644-08:00</updated><title type='text'>Some Rock Solid things are Quite Beautiful</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-9v_-RkbuWss/Trk4g2hdj7I/AAAAAAAAAhk/eg9hFWGnEYI/s1600/7_704px-Quartz_Br%25C3%25A9sil.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 273px;" src="http://4.bp.blogspot.com/-9v_-RkbuWss/Trk4g2hdj7I/AAAAAAAAAhk/eg9hFWGnEYI/s320/7_704px-Quartz_Br%25C3%25A9sil.png" alt="" id="BLOGGER_PHOTO_ID_5672627342692683698" border="0" /&gt;&lt;/a&gt;I mentioned at the closing session of UDS last week, that it was remarkable UDS due to the amount of time spent discussing &lt;span style="font-style: italic;"&gt;how&lt;/span&gt; we build Ubuntu, not just what we will build. As a community, we have blazed many new trails in software development and delivery, and I feel strongly that we are standing at a nexus where we will be able to collect the wisdom of the past seven years of developing an Open Source Community Distro and apply that wisdom in the future in a way that introduces a step function in our adoption curve as we pursue our goal of a mainstream free desktop.&lt;br /&gt;&lt;br /&gt;Most of these "how should we build it" discussions circled around building and maintaining development velocity in 12.04 so that we could add new features that users need while maintaining and delivering the quality they also need. Fortunately, we laid some ground work in the 11.10 cycle. Pete Graner led the QA team in 11.10. Along with some new QA staff, they instituted some important practices, like automatically testing the daily ISO each day, and they set up a QA lab for running tests automatically, along with &lt;a href="https://jenkins.qa.ubuntu.com/"&gt;reporting of those test results&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Acceptance Criteria&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;How we assess and accept code from upsreams within Canonical was an area ripe for discussion. We arrived at UDS with a strong view about how we should be doing this, and we had three discussions about it at UDS. Jason Warner this area of collective effort "Acceptance Criteria". Please note that for 12.04 and the foreseeable future, this applies ONLY to code developed by Canonical, or was call them "Canonical upstreams". There are two main goals of this effort:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Ensure that landing code from Canonical upstreams does not introduce issues that make the development release too hard to use and develop on, thus slowing down velocity for everyone.&lt;/li&gt;&lt;li&gt;Encourage Canonical upstreams to fix bugs throughout the cycle, and not to wait until after Feature Freeze to focus all efforts on bug fixing.&lt;/li&gt;&lt;/ul&gt;As a result, we should see faster development, and a higher quality final product.&lt;br /&gt;&lt;br /&gt;Acceptance Criteria means that upstreams have some responsibilities, and Ubuntu has some responsibilities. For upstreams, it boils down to "treat your trunk as sacred". Practically, it requires:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;There is a trunk of code bound for Ubuntu.&lt;/li&gt;&lt;li&gt;This trunk always builds automatically.&lt;/li&gt;&lt;li&gt;This trunk has tests that are always passing automatically.&lt;/li&gt;&lt;li&gt;All branches are properly reviewed for having both good tests and good implementation before merged into trunk.&lt;/li&gt;&lt;li&gt;Any branch that breaks trunk by causing automated tests to fail or causes trunk to stop building, are immediately reverted.&lt;/li&gt;&lt;/ul&gt;For Ubuntu Engineering, the responsibilities include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Every maintainer in Ubuntu must have a test plan for upstream trunks that are run before uploading to the development release.&lt;/li&gt;&lt;li&gt;Tests in the test plan that are automated can be run with the help of the QA team.&lt;/li&gt;&lt;li&gt;Tests in the test plan that are manual can be run with the help of the community team, (and the community &lt;a href="https://tbe.taleo.net/NA3/ats/careers/requisition.jsp?org=CANONICAL&amp;amp;cws=1&amp;amp;rid=245"&gt;QA Lead that is to be hired&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;Refrain from uploading a trunk into Ubuntu if there are serious bugs found in testing that will slow down people using the development release for testing and development.&lt;/li&gt;&lt;li&gt;Revert uploads that break Ubuntu, as there is no point in having the latest of a trunk in Ubuntu if it's broken and just slowing everyone down.&lt;/li&gt;&lt;li&gt;Add tests to upstream projects for the Ubuntu test plan if serious bugs do get through that cause a revert.&lt;/li&gt;&lt;/ul&gt;&lt;a href="http://1.bp.blogspot.com/-akmxtl8UZu4/Trk7In_DF7I/AAAAAAAAAh4/AVxyC6uJNIA/s1600/8_Topaz_cut.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 256px; height: 201px;" src="http://1.bp.blogspot.com/-akmxtl8UZu4/Trk7In_DF7I/AAAAAAAAAh4/AVxyC6uJNIA/s320/8_Topaz_cut.jpg" alt="" id="BLOGGER_PHOTO_ID_5672630225008269234" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;ISO integration testing&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Every day the QA team automatically runs tests on the ISO produced that day, if any. This was set up in 11.10. For 12.04, we will expand on this effort substantially. First, by growing the body of tests run. Secondly, by automatically reporting on the quality of the ISO each day. Finally, by responding to the results of the tests immediately, see the next section.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Daily Quality&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;We will strive to ensure that we have a new daily ISO each day. If the QA team finds an ISO to be "untestable" or failing critical tests that will hamper development velocity that day, we will respond by trying to fix the issues. For issues that cannot be foreseeably fixed within 3 hours, we will typically back out those changes. After the issue is addressed by being fixed or backed out, we *will spin another ISO*. We will collect metrics on what percentage of days we have a working and testable ISO.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Pre-archive Testing&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Of course, catching problems in ISO testing and fixing them everyday is nice, but stopping the problems from reaching Ubuntu in the first place is even better. With that end in mind, &lt;a href="https://launchpad.net/%7Eev"&gt;Evan Dandrea&lt;/a&gt; ran a very interesting session about testing library and other changes &lt;span style="font-style: italic;"&gt;before&lt;/span&gt; uploading them to the archive for the development release.&lt;br /&gt;&lt;br /&gt;This will start out simple. The QA team will be able to install the latest ISO in their test environment, then pull an updated package from a PPA. A tool that I lovingly named "tool 2" will be created that will use rdepends to find packages that both depend on the newly upagraded package and also have tests worth running. Tool 2 will then run those tests and report the results. In this way, issues with libraries and other transitions can be fixed before they get into the development release and slow everyone down.&lt;br /&gt;&lt;br /&gt;The next step, which I sincerely hope we get to during 12.04 development is to make tool 1. Tool 1 takes the output of tool 2, and judges if it should copy the newly updated package, or some set of packages, into the release archive. If we get tool 2 set up, then uploads to the development release would first go into the proposed archive for the development release, tested there, and only added to the release archive when found to be "ok" by the tests.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;+1 Maintenance Team&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;a href="http://www.chiark.greenend.org.uk/ucgi/%7Ecjwatson/blosxom"&gt;Colin Watson&lt;/a&gt; is leading an experiment for 12.04 development. He will be leading a small staff of rotating engineers who are focused soley on the stability of the development release. We plan to learn from this effort, and see if we should repeat it, tweak it, or drop it for future versions. In any case, these efforts are meant to enhance, not replace, the generally diffused responsibilities for quality of the ISO and the archive. Colin led a good UDS session on the topic. The priorities defined there being:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Deal with ISO smoke test issues, includes install images being buildable&lt;/li&gt;&lt;li&gt;Upgrades through development releases work day to day, look at conflict checker&lt;/li&gt;&lt;li&gt;All packages in main are installable&lt;/li&gt;&lt;li&gt;All packages in main are buildable&lt;/li&gt;&lt;li&gt;Component-mismatches / MIRs / etc.&lt;/li&gt;&lt;li&gt;Finishing library/NBS transitions through archive (beyond main)&lt;/li&gt;&lt;li&gt;All packages in the archive are buildable&lt;/li&gt;&lt;/ol&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Summary&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;All in all, these are a lot of structural changes to how we approach building Ubuntu and ensuring the quality of it. Here is a table to highlight some of the key changes.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-OG9-y0V_Vyc/Trk7ITIjIsI/AAAAAAAAAhw/EMyY6NTAP3E/s1600/9_Cut_Ruby.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 283px;" src="http://4.bp.blogspot.com/-OG9-y0V_Vyc/Trk7ITIjIsI/AAAAAAAAAhw/EMyY6NTAP3E/s320/9_Cut_Ruby.jpg" alt="" id="BLOGGER_PHOTO_ID_5672630219410973378" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;table&gt; &lt;tbody&gt;&lt;tr&gt;  &lt;th&gt;Practice&lt;/th&gt;  &lt;th&gt;11.10&lt;/th&gt;  &lt;th&gt;12.04&lt;/th&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Canonical upstream automated tests&lt;/td&gt;  &lt;td&gt;prevelant in some projects, not others&lt;/td&gt;  &lt;td&gt;all projects will have automated tests&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Canonical upstream automated builds&lt;/td&gt;  &lt;td&gt;prevelant in some projets, not others&lt;/td&gt;  &lt;td&gt;all projects will ahve automated tests&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Canonical upstream branch reviews&lt;/td&gt;  &lt;td&gt;all projects&lt;/td&gt;  &lt;td&gt;all projects&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Canonical upstreams reverting branches that "break" trunk&lt;/td&gt;  &lt;td&gt;occurs in some projects, not others, not always&lt;br /&gt;&lt;/td&gt;  &lt;td&gt;all projects will revert branches that break trunk&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;testing of Canonical upstream code before uploading to development release&lt;/td&gt;  &lt;td&gt;not common or systematic&lt;/td&gt;  &lt;td&gt;all Canonical upstream projects will have a test plan that is executed before each upload&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;reverting Canonical upstream code from the development release&lt;/td&gt;  &lt;td&gt;development release waits for an upload with "fixes"&lt;/td&gt;  &lt;td&gt;uploads that slow velocity for others are backed out&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Daily ISO testing&lt;/td&gt;  &lt;td&gt;Limited test coverage, test failures not immediately responded to&lt;/td&gt;  &lt;td&gt;more test coverage, fix issues and respin within the same day&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Daily ISO&lt;/td&gt;  &lt;td&gt;Can go for days or longer wihtout a working daily&lt;/td&gt;  &lt;td&gt;A broken daily ISO becomes the #1 priority for whoever broke it&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Pre-archive testing&lt;/td&gt;  &lt;td&gt;"Call for testing", not totally systematic, no way to know what tests were run&lt;/td&gt;  &lt;td&gt;Add ability to run tests for rdpends before release, potentially test in proposed for the development release&lt;/td&gt; &lt;/tr&gt; &lt;tr&gt;  &lt;td&gt;Archive Maintenance&lt;/td&gt;  &lt;td&gt;Responibility diffused throughout team&lt;/td&gt;  &lt;td&gt;Responibility diffused throughout team, complimented by a small dedicated crew&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-156072708270431180?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/156072708270431180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/11/i-mentioned-at-closing-session-of-uds.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/156072708270431180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/156072708270431180'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/11/i-mentioned-at-closing-session-of-uds.html' title='Some Rock Solid things are Quite Beautiful'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-9v_-RkbuWss/Trk4g2hdj7I/AAAAAAAAAhk/eg9hFWGnEYI/s72-c/7_704px-Quartz_Br%25C3%25A9sil.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-3216700125563346123</id><published>2011-10-23T04:37:00.001-07:00</published><updated>2011-10-23T04:44:49.124-07:00</updated><title type='text'>Stealthy, An App to Pause Desktop Logging</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-QeXMXTBMo1Y/TqP9XcfJwaI/AAAAAAAAAhU/IsFY8sfpSJE/s1600/Screenshot%2Bat%2B2011-10-23%2B13%253A40%253A19.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 265px; height: 82px;" src="http://4.bp.blogspot.com/-QeXMXTBMo1Y/TqP9XcfJwaI/AAAAAAAAAhU/IsFY8sfpSJE/s320/Screenshot%2Bat%2B2011-10-23%2B13%253A40%253A19.png" alt="" id="BLOGGER_PHOTO_ID_5666651335387890082" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Here's a quick preview of an app I'm working on. It's called "&lt;span style="font-weight: bold;"&gt;Stealthy&lt;/span&gt;" because it provides a kind of "Stealth" mode by pausing Zeitgeist from logging while it's running. The upshot is that when you see the indocator, your activities during that time won't show up in Unity. Quitting Stealthy starts the logging again.&lt;br /&gt;&lt;br /&gt;It's also got a "Delete History" function, that just clears all Zeitgeist and GNOME most recently used history, giving you a clean slate.&lt;br /&gt;&lt;br /&gt;Here's a video demo:&lt;br /&gt;&lt;iframe src="http://www.youtube.com/embed/t9RJ33lAhZU?hl=en&amp;amp;fs=1" allowfullscreen="" frameborder="0" height="349" width="425"&gt;&lt;/iframe&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;D'oh&lt;/span&gt; ... I just realized I searched for the wrong things to demonstrate that Delete History command worked. Anyway, trust me, it would have looked like it worked if I searched for the right thing! :)&lt;br /&gt;&lt;br /&gt;You can try it by running from the branch( bzr branch lp:~rick-rickspencer3/+junk/stealthy ). However, I plan to add a little ninja icon instead of the stock inidcator icon, and also a warning dialog for the Delete History function. Then I'll put it in my PPA, and also probably sell it in the Software Center for the minimum price.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-3216700125563346123?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/3216700125563346123/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/10/stealthy-app-to-pause-desktop-logging.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3216700125563346123'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3216700125563346123'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/10/stealthy-app-to-pause-desktop-logging.html' title='Stealthy, An App to Pause Desktop Logging'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-QeXMXTBMo1Y/TqP9XcfJwaI/AAAAAAAAAhU/IsFY8sfpSJE/s72-c/Screenshot%2Bat%2B2011-10-23%2B13%253A40%253A19.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-76976892197863772</id><published>2011-08-25T05:04:00.000-07:00</published><updated>2011-08-25T05:28:03.163-07:00</updated><title type='text'>Using PyGame in a PyGtk App</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-FWAyUBiwKbo/TlY60NCeS_I/AAAAAAAAAg4/hzj0GW8fevQ/s1600/Screenshot-53.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-FWAyUBiwKbo/TlY60NCeS_I/AAAAAAAAAg4/hzj0GW8fevQ/s320/Screenshot-53.png" alt="" id="BLOGGER_PHOTO_ID_5644763851483139058" border="0" /&gt;&lt;/a&gt;I've written a few games in pygame, but never quite finished them. Generally because the complexity of adding user interactivity for things outside the essential game play. While pygame is a sweet sprite library, it has no widget toolkit, so things like collection a string from the user, having menus, etc... are all incredibly tedious to program. Adding something like a high score screen is as much effort as game programming itself.&lt;br /&gt;&lt;br /&gt;Pygtk, however, has a rich widget toolkit, but it's hard to use it from within a pygame app. This is difficult in part because Gtk needs a gtk.main loop, but a typical pygame app is using it's own loop with clock.tick(). As a result, there are threads locking each other out, and generally craziness ensues.&lt;br /&gt;&lt;br /&gt;But, it turns out, you can embed a pygame surface within a pygtk app, and it's actually pretty easy. So you can use just the one gtk.main loop, all interupt programming, access to the whole Gtk toolkit, and also all the easy loading of images, playing of sounds, collision detection, and other sprite functionality of pygame. It's really the best of all possible worlds.&lt;br /&gt;&lt;br /&gt;I wrote&lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/+junk/pygame-pygtk-example/view/head:/game.py"&gt; a minimal demo script&lt;/a&gt; to show how to do this. The "game" is just a single sprite that you can move with the keyboard. You can see the screen in the screenshot at the top of this post that there is a gtk menu being activated!&lt;br /&gt;&lt;br /&gt;I didn't intend this to be a tutorail on pygame, but rather integrating pygame in pygtk, so all the "game" does is let you move a sprite around.&lt;br /&gt;&lt;br /&gt;It's probably easiest to &lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/+junk/pygame-pygtk-example/view/head:/game.py"&gt;look at the whole script&lt;/a&gt;, but let me point out some of the steps involved. I'll assume that you've already set up a gtk.Window with your menus and such. So, the first step is to set up a gtk.DrawingArea that will become the pygame surface. It's normal code to add it to your window:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;      da = gtk.DrawingArea()&lt;br /&gt;    da.set_size_request(300,300)&lt;br /&gt;    da.show()&lt;br /&gt;    vbox.pack_end(da)&lt;br /&gt;    da.connect("realize",self._realized)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;This last line, connecting to the realize signal, is important. It's important because you use the gtk.gdk.window.id to associate the drawing area with pygame. The drawing area's window won't have an id until it's been rendered by gtk. So the then in the handler for the realize event, we set up the association with the drawing area, and also start actually drawing the game:&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def _realized(self, widget, data=None):&lt;br /&gt;    os.putenv('SDL_WINDOWID', str(widget.window.xid))   &lt;br /&gt;    pygame.init()&lt;br /&gt;    pygame.display.set_mode((300, 300), 0, 0)&lt;br /&gt;    self.screen = pygame.display.get_surface()&lt;br /&gt;    gobject.timeout_add(200, self.draw)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The first three lines essentially set up the drawing area as the pygame surface. The fourth line creates a global object for a pygame.screen object that will be used in the draw function. Finally, the draw function gets caslled every 200 milliseconds via a gobject timeout. This is as much easier way of setting up the game loop than the normal pygtk way of blocking the clock for a certain number of ticks, as gobject.timeout_add plays nicely with the gtk.main loop.&lt;br /&gt;&lt;br /&gt;In a real game, you'd probably put an "update" function on the timeout to do things like check for collisions, random events, and etc... (You'd also probably use a much smaller time out period than 200 milliseconds). However, our game only has one sprite, and it doesn't do anything but move so we don't need that functionality. But how do we tell it move? In pygame, every time through the main loop you pull all the events off the event stack. With pygtk, you use signals to handle the input when it occurs.&lt;br /&gt;&lt;br /&gt;To do this, in the __init__ function for the window, we set up our pygame objects, and then connect to the key-press signal:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;      #set up the pygame objects&lt;br /&gt;    self.image = pygame.image.load("sprite.png")&lt;br /&gt;    self.background = pygame.image.load("background.png")&lt;br /&gt;    self.x = 150&lt;br /&gt;    self.y = 150&lt;br /&gt;&lt;br /&gt;    #collect key press events&lt;br /&gt;    self.connect("key-press-event", self.key_pressed)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then in the signal handler, we manipulate those objects. Typically, you'd be storing the x and y coordinates in a sprite object, but since there is only one sprite to track in this "game" we can handle it more simply. So in the key_press handler, we just detect if an arrow key was pressed, and we adjust the game data appropriately:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def key_pressed(self, widget, event, data=None):&lt;br /&gt;    if event.keyval == 65361:&lt;br /&gt;        self.x -= 5&lt;br /&gt;    elif event.keyval == 65362:&lt;br /&gt;        self.y -= 5&lt;br /&gt;    elif event.keyval == 65363:&lt;br /&gt;        self.x += 5&lt;br /&gt;    elif event.keyval == 65364:&lt;br /&gt;        self.y += 5&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then, every 200 milliseconds, the timeout comes buy and tells pygame to draw everything using normal pygame functions:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def draw(self):&lt;br /&gt;    self.screen.blit(self.background,[0,0])&lt;br /&gt;    rect = self.image.get_rect()&lt;br /&gt;    rect.x = self.x&lt;br /&gt;    rect.y = self.y&lt;br /&gt;    self.screen.blit(self.image, rect)&lt;br /&gt;    pygame.display.flip()&lt;br /&gt;&lt;br /&gt;    return True&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Since pygame is getting update in gobject timeout and normal Gtk signal handlers, and not in it's own gameloop, gtk.main is free to run and handle menus, and even display dialogs:&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-xqo1htI6cfI/TlY8QK1GRYI/AAAAAAAAAhA/sr9V2ptUHRk/s1600/Screenshot-54.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-xqo1htI6cfI/TlY8QK1GRYI/AAAAAAAAAhA/sr9V2ptUHRk/s320/Screenshot-54.png" alt="" id="BLOGGER_PHOTO_ID_5644765431438132610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-76976892197863772?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/76976892197863772/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/08/using-pygame-in-pygtk-app.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/76976892197863772'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/76976892197863772'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/08/using-pygame-in-pygtk-app.html' title='Using PyGame in a PyGtk App'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-FWAyUBiwKbo/TlY60NCeS_I/AAAAAAAAAg4/hzj0GW8fevQ/s72-c/Screenshot-53.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4459380813587821033</id><published>2011-08-23T22:31:00.001-07:00</published><updated>2011-08-23T23:57:52.678-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>A Photobomb Sale!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-xisxzftJ9L4/TlSgu0xS1FI/AAAAAAAAAgw/D0gxGjQYiDc/s1600/Screenshot-52.png"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-IoGS7IOUogo/TlSM4VCd2wI/AAAAAAAAAgY/i01vA2SRmck/s1600/Screenshot-49.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/-IoGS7IOUogo/TlSM4VCd2wI/AAAAAAAAAgY/i01vA2SRmck/s320/Screenshot-49.png" alt="" id="BLOGGER_PHOTO_ID_5644291132350126850" border="0" /&gt;&lt;/a&gt;Photobomb showed up in the Software Center yesterday. As I prepare a space in my garage for the Rolls Royce I intend to buy from the proceeds, I took a moment to check in on sales, and, in fact, someone bought it! The MyApps portal has a really nice overview screen for managing multiple apps for sale.&lt;br /&gt;&lt;br /&gt;Now, I strongly suspect that a friend bought it to make me feel better, but still, it gave me a chance to check out the nice graphs that myapps makes for developers. Of course, I expect Photobomb sales to stress test their math and charting capabilities due to an overwhelming crush of sales, but it's nice to see what they are shooting for.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-ueQjUdt6nFI/TlSNTz8TrWI/AAAAAAAAAgg/Q_VIkHSd-Ss/s1600/Screenshot-51.png"&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-RbAxBCzwNvY/TlSNUNw-GEI/AAAAAAAAAgo/5FwLNiFbbfQ/s1600/Screenshot-50.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/-RbAxBCzwNvY/TlSNUNw-GEI/AAAAAAAAAgo/5FwLNiFbbfQ/s320/Screenshot-50.png" alt="" id="BLOGGER_PHOTO_ID_5644291611434031170" border="0" /&gt;&lt;/a&gt;&lt;a href="http://4.bp.blogspot.com/-xisxzftJ9L4/TlSgu0xS1FI/AAAAAAAAAgw/D0gxGjQYiDc/s1600/Screenshot-52.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-xisxzftJ9L4/TlSgu0xS1FI/AAAAAAAAAgw/D0gxGjQYiDc/s320/Screenshot-52.png" alt="" id="BLOGGER_PHOTO_ID_5644312959301899346" border="0" /&gt;&lt;/a&gt;I'm really impressed with the work that Canonical ISD has done here. I'd love to see this work made available to people who want to land non-commercial apps on a stable release of Ubuntu soon. Currently, it only supports commercial apps.&lt;br /&gt;&lt;br /&gt;Speaking of which, while I am selling Photobomb, please keep in mind that it is still Free. That means that anyone who buys it gets all the software freedoms (it is GPL3, afterall). Furthermore, if you don't want to buy it, you can get Photobomb for free my PPA. Seriously, it you want to use it, but don't want to buy it, no worries, running it from my PPA is no problem for me.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4459380813587821033?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4459380813587821033/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/08/photobomb-sale.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4459380813587821033'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4459380813587821033'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/08/photobomb-sale.html' title='A Photobomb Sale!'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-IoGS7IOUogo/TlSM4VCd2wI/AAAAAAAAAgY/i01vA2SRmck/s72-c/Screenshot-49.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8641188608868919582</id><published>2011-08-17T10:41:00.000-07:00</published><updated>2011-08-17T11:36:53.510-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>Cha-ching!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-IA0GVwkGjgM/TkwJvGJ5ujI/AAAAAAAAAgQ/VxwpyzQmQ7A/s1600/Screenshot-44.png"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-S-K1RRizyLQ/TkwJaof7GuI/AAAAAAAAAgI/gWGy6_3LNLU/s1600/Screenshot-48.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-S-K1RRizyLQ/TkwJaof7GuI/AAAAAAAAAgI/gWGy6_3LNLU/s320/Screenshot-48.png" alt="" id="BLOGGER_PHOTO_ID_5641894786340952802" border="0" /&gt;&lt;/a&gt;So, today I uploaded a special version of Photobomb to &lt;a href="https://launchpad.net/%7Erick-rickspencer3/+archive/ppa"&gt;my PPA&lt;/a&gt;. It's special because I consider this my first *complete* release. There are some things that I would like to be better in it. For example:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I wish you could drag into from the desktop or other apps.&lt;/li&gt;&lt;li&gt;I wish that the app didn't block the UI when you click on the Gwibber tab when Gwibber isn't working.&lt;/li&gt;&lt;li&gt;I wish the local files view had a watch on the current directory so that it refreshed automatically.&lt;/li&gt;&lt;li&gt;I wish inking was smoother.&lt;/li&gt;&lt;li&gt;I wish you could choose the size of the image that you are making.&lt;/li&gt;&lt;li&gt;I wish that you could multi-select in it.&lt;/li&gt;&lt;/ul&gt;But alas, if I wait until it has everything and no bugs, I'll never release.&lt;br /&gt;&lt;br /&gt;So, I am releasing Photobomb in my PPA. It is a free app. You can use it for free, and it's Free software. So, enjoy.&lt;br /&gt;&lt;br /&gt;However, I am *also* releasing it commercially into the software center. That means that you can choose to install from my PPA for free, or you can buy it from the Software Center. I guess the only real difference will be that I could break it in my PPA, and I won't generally plan to provide lots of support, but if you bought it, I'd feel a bit more like I should strive to fix your bugs and stuff.&lt;br /&gt;&lt;br /&gt;The code is GPL v3, so people can enhance it, or generally do whatever they think is useful for them (including giving it a way, or using it to make money).&lt;br /&gt;&lt;br /&gt;I found it remarkably easy to submit photobomb to the Software Center.  I just used the &lt;a href="https://myapps.developer.ubuntu.com/dev/"&gt;myapps.ubuntu portal&lt;/a&gt;, and it all went very smoothly. Really just a matter of filling in some forms. Of course, since I used Quickly to build Photobomb, Quickly took care of the packaging for me, so that simplified it loads.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-IA0GVwkGjgM/TkwJvGJ5ujI/AAAAAAAAAgQ/VxwpyzQmQ7A/s1600/Screenshot-44.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-IA0GVwkGjgM/TkwJvGJ5ujI/AAAAAAAAAgQ/VxwpyzQmQ7A/s320/Screenshot-44.png" alt="" id="BLOGGER_PHOTO_ID_5641895137899035186" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I'll keep everyone posted on how it goes!&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8641188608868919582?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8641188608868919582/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/08/cha-ching.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8641188608868919582'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8641188608868919582'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/08/cha-ching.html' title='Cha-ching!'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-S-K1RRizyLQ/TkwJaof7GuI/AAAAAAAAAgI/gWGy6_3LNLU/s72-c/Screenshot-48.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6352952128877555759</id><published>2011-08-06T02:38:00.000-07:00</published><updated>2011-08-06T02:55:26.069-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>The End of an Era?</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-nQ2FhWNJpaM/Tj0L_kmjyFI/AAAAAAAAAf4/HqpnqFkcvTE/s1600/Screenshot-41.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/-nQ2FhWNJpaM/Tj0L_kmjyFI/AAAAAAAAAf4/HqpnqFkcvTE/s320/Screenshot-41.png" alt="" id="BLOGGER_PHOTO_ID_5637675495322798162" border="0" /&gt;&lt;/a&gt;Today I committed and pushed a change to Photobomb to &lt;span style="font-weight: bold;"&gt;remove&lt;/span&gt; functionality! The web tab used to allow users to search the web for images. So you could search for "cat" and get the most popular kitteh pix ready to mix into our images. No longer. You can still plug in a web address, and Photobomb will it's best to find an image there, but no more web searches.&lt;br /&gt;&lt;br /&gt;A couple of weeks ago, Google retired the Google API I was using, so I switched to Yahoo!, only to find that the Yahoo! one got retired a week or so ago.&lt;br /&gt;&lt;br /&gt;Both services switched to newer APIs. The thing is, both &lt;a href="http://code.google.com/apis/customsearch/v1/overview.html"&gt;Google&lt;/a&gt; and &lt;a href="http://developer.yahoo.com/search/boss/"&gt;Yahoo!&lt;/a&gt; now charge an application for using those APIs. Yup, I would have to pay actual money to use the services.&lt;br /&gt;&lt;br /&gt;I really don't have a problem with them doing that. I mean, someone has to pay to keep those services running. And I suppose if I charged a bit for Photobomb, I could use that to fund paying for the services. However, at the moment, I don't really feel like dealing with setting up an account and dealing with all that, in addition to recoding to the new APIs.&lt;br /&gt;&lt;br /&gt;So, I guess the era of the web giants competing to get me to use their "free" services has drawn to a close. I suppose it's a bit like a bursting bubble. Goodbye free web-services :/&lt;br /&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-Hf7HR4vjx88/Tj0PRELveII/AAAAAAAAAgA/-Cai61jg7Do/s1600/waving.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/-Hf7HR4vjx88/Tj0PRELveII/AAAAAAAAAgA/-Cai61jg7Do/s320/waving.png" alt="" id="BLOGGER_PHOTO_ID_5637679094392912002" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6352952128877555759?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6352952128877555759/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/08/end-of-era.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6352952128877555759'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6352952128877555759'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/08/end-of-era.html' title='The End of an Era?'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-nQ2FhWNJpaM/Tj0L_kmjyFI/AAAAAAAAAf4/HqpnqFkcvTE/s72-c/Screenshot-41.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-3168753136371618873</id><published>2011-08-01T02:58:00.000-07:00</published><updated>2011-08-01T03:24:11.644-07:00</updated><title type='text'>Coding Copy and Paste Functionality</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-zYGSTQbR0do/TjZ-t-rD6MI/AAAAAAAAAfw/u8pV9R55guY/s1600/Screenshot-39.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-zYGSTQbR0do/TjZ-t-rD6MI/AAAAAAAAAfw/u8pV9R55guY/s320/Screenshot-39.png" alt="" id="BLOGGER_PHOTO_ID_5635831312083577026" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Copy and Paste Features&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;A well designed application should probably support Copy and Paste. While it sounds simple, it actually entails a few features.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Copying from Your Application to Other Applications&lt;/span&gt;&lt;br /&gt;This means that your application should offer up data formats that other applications can read. So a user should be able to copy in your application, and paste into another one. This may entail converting your internal data format into a new one that other programs will expect.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Copying from Other Applications into Your Application&lt;/span&gt;&lt;br /&gt;This means reading other applications data formats and converting it your applications internal representation.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Copying and Pasting within Your Application or between Instances of Your Application&lt;/span&gt;&lt;br /&gt;This entails marshaling all the data that your application needs. If a user can run multiple instances of your Application, it won't be sufficient to simply copy an instance of an object, since one instance won't have access to what is in the memory of another instances.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;How Does Copy and Paste Work?&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;You start out by accessing the desktop's clipboard. Then, you tell that clipboard what kind of data your app can put on the clipboard, and then what kind of data your app can take off of the clipboard. Then, you right some functions to handle the following situations:&lt;br /&gt;&lt;br /&gt;If the user selects a Copy command in your application, you tell the clipboard that data is available (but you don't actually put the data on the clipboard until the user selects paste).&lt;br /&gt;If the user selects Paste in your application, you ask the clipboard for the data, and the application with the data then supplies it to the clipboard. Sometimes the application supplying the data is your application, and sometimes it is a different application.&lt;br /&gt;If the user selects Paste in a different application, then the clipboard might ask your application to supply the data.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Set Up Variables&lt;/span&gt;&lt;br /&gt;So, let's get to the code. First thing, we need to create a reference to desktop's clipboard, and also create a variable to store a reference to whatever item may be copied.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     self.clipboard = gtk.Clipboard()&lt;br /&gt;self.clipboard_item = None&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;I put this in finish_initializing.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Handling the Copy Command&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Then I wrote a function called "copy_menu" which is called when the user selects "Copy" fromt he menu. the first thing this function does is tells the clipboard what kinds of data is supported by passing in a list of tuples. Each tuple has a string that specifies the type, info regarding drag and drop, and a number that you can make up to specify what the type again. For Photobomb, only the strings are important. You need to use types that are commonly understood on the Linux desktop so that apps know that you can give them data they care about. I chose "UTF8_STRING" for putting strings on the clipboard, and the mime type "image/png" for images. For example, Gedit recongizes UTF8_STRING, and Gimp recognizes image/png, so Photobomb can copy data into both of those apps. Only Photobomb recognizes "PhotobombItem", of course. These strings are called "targets" in Gtk.&lt;br /&gt;&lt;br /&gt;After specifying what data types you will support, you then tell the clipboard about those data types, along with what function to call if the user tries to paste, and what function to call when the clipboard gets cleared. Finally, the function stores a reference to the currently selected item. It's important to store this reference seperately, as the selection could change before the user pastes.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def copy_menu(self, widget, data=None):&lt;br /&gt;paste_data = [("UTF8_STRING", 0, 0),&lt;br /&gt;        ("image/png", 0, 1),&lt;br /&gt;        ("PhotobombItem", 0, 2)]&lt;br /&gt;self.clipboard.set_with_data(paste_data, self.format_for_clipboard, clipboard_cleared)&lt;br /&gt;self.clipboard_item = self.selected_item  &lt;span style="font-size:180%;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Handling Programs Pasting from Photobomb&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;After telling the clipboard what kind of data you can put on it, the user may try to actually Paste! If this happens, you have to figure out what kind of data they are asking for, and then put the correct kind of data onto the clipboard. To figure out what is being asked for, you check the target. This info is stored in the selectiondata argument, that gets passed in.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Putting Text on the Clipboard&lt;/span&gt;&lt;br /&gt;When an application is asking for a string, life is really simple. Every PhotobombItem has a text property, so it's simple to simply set the text of the clipboard with this data.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def format_for_clipboard(self, clipboard, selectiondata, info, data=None):&lt;br /&gt;if selectiondata.get_target() == "UTF8_STRING":&lt;br /&gt;self.clipboard.set_text(self.clipboard_item.text)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Putting an Image on the Clipboard&lt;/span&gt;&lt;br /&gt;But what about when the app has asked to paste an image? The clipboard only supports text, so you can't paste an image, right? Actually, the contents of any binary stream or file can be converted to text, and that text can be put on the clipboard. However, you don't want to simply set the text for the clipboard, as binary data can have characters in it that will confuse a program that thinks it's a normal string. For example, when you read in a file of binary data, you can set that to a string in Python, but other programs not written in Python will probably get have errors if they try to treat it as a string type.&lt;br /&gt;&lt;br /&gt;In fact, the clipboard will reject text that is binary data. So, if you have binary data, you need to:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;convert it into text&lt;/li&gt;&lt;li&gt;set the selectiondata with the proper target&lt;/li&gt;&lt;/ol&gt;Photobomb has a function called "image_for_item" that returns image data as binary text, so Phothobomb can just call that function and put the resulting text into the selection data. The "8" in the second parameter tells the selectiondata object essentially boils down to text.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     if selectiondata.get_target() == "image/png":&lt;br /&gt;  selectiondata.set("image/png", 8, self.image_for_item(self.clipboard_item))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Putting a PhotobombItem on the Clipboard&lt;/span&gt;&lt;br /&gt;If an instance of Photobomb wants to paste, we need to provide enough information to create a proper PhotobombItem. A PhotobombItem knows a lot about itself, for example, it's clipping path, it's rotation, it's scale, etc... When copyng and pasting within Photobbomb or between instances of Photobomb, we don't want to lose all of that data.&lt;br /&gt;&lt;br /&gt;So how do we paste a PhotobombItem if we can only put text on the clipboard? Python has built in support for turning objects into strings. This is called "pickling". So, in the simplest case, we could just convert the Python object to a string, put that string onto the clipboard, and then when convert it from the string back to a Python object.&lt;br /&gt;&lt;br /&gt;So, first, import the pickle module:&lt;br /&gt;import pickle&lt;br /&gt;&lt;br /&gt;Then you can use "dumps" to dump the object to string:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   if selectiondata.get_target() == "PhotobombItem":&lt;br /&gt; s = pickle.dumps(self.clipboard_item)&lt;br /&gt; self.clipboard.set_text(s)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Sadly, this doesn't work for Photobomb because the GooCanvas classes that PhotobombItems derive from don't support pickling. So we can't do this in the simple way. However, what we *can* do, is create a dictionary with enough information to create identical PhotobombItems, and then pickle that dictionary. For photobomb, this gets a bit annoying complex and is not relevant to the blog posting, so I snipped out most of the code. You can always look at the &lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/photobomb/trunk/view/head:/bin/photobomb"&gt;full code on launchpad&lt;/a&gt;, of course.&lt;br /&gt;&lt;br /&gt;This is, unfortunately, a lot more involved. However, it works just fine:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   if selectiondata.get_target() == "PhotobombItem":&lt;br /&gt; d = {"type":type(self.clipboard_item)}&lt;br /&gt; d["clip"] = self.clipboard_item._clip_path&lt;br /&gt; #snipped out all the other properties that need to be addeed to the dicitonary&lt;br /&gt; s = pickle.dumps(d)&lt;br /&gt; self.clipboard.set_text(s)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;You don't have to use pickling. For example, you could represent this dictionary in JSON instead. However, pickling is the native serialization formatting for Python, so it's easiest to use it when you can.&lt;br /&gt;&lt;br /&gt;Now Photobomb can support copying and pasting into text editors, like Gedit, and image editors, such as the Gimp, as well as into Photobomb itself.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Handling Pasting in Photobomb&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;The essential paste function is run when the Photobomb user uses Photobomb's Paste command. This function tries to figure out if there are any interesting data types on the clipboard, and then uses them. Typically, such a function should ask for the richest and most intersting data it can handle first, and then less rich data types in order. For Photobomb, the richest data is a PhotobombItem, or course. Then an image, and then finally text. You may have noticed above that when a user pastes from a clipboard, it runs a function in some application. There is no gauruntee that these functions will be fast. For this reason, it's best to use the asyncronous "request" methods on the clipboard.&lt;br /&gt;&lt;br /&gt;So, the paste function simply tries to figure out which is the best data type to paste, if any, and then asks the clipboard to call the correct function when the data is ready.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt; def paste_menu(self, widget, data=None):&lt;br /&gt;targets = self.clipboard.wait_for_targets()&lt;br /&gt;if "PhotobombItem" in targets:&lt;br /&gt;  self.clipboard.request_contents("PhotobombItem", self.paste_photobomb_item)&lt;br /&gt;  return&lt;br /&gt;for t in targets:&lt;br /&gt;  if t.lower().find("image") &amp;amp;gt; -1:&lt;br /&gt;    self.clipboard.request_image(self.paste_image)&lt;br /&gt;    return&lt;br /&gt;for t in targets:&lt;br /&gt;  if t.lower().find("string") &amp;amp;gt; -1 or t.lower().find("text") &amp;amp;gt; -1:&lt;br /&gt;    self.clipboard.request_text(self.paste_text)&lt;br /&gt;    return&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;In the cases where another applications has put an image or text onto the clipboard, it's easy to use functions already built into Photobomb to paste those data types. Notice that the return functions already have all the data ready in the form of text or the pixbuf.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def paste_text(self, clipboard, text, data=None):&lt;br /&gt;  self.add_new_text(text)&lt;br /&gt;def paste_image(self, clipboard, pixbuf, data=None):&lt;br /&gt;  self.add_image_from_pixbuf(self, pixbuf)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;When an instance of Photobomb has put the data on the clipboard, it's a bit more complex, because Photobomb has to de-serialize the dictionary, and then create the proper item. Again, I snipped out most of the code for using the de-serialized dictionary, because it just unnecessarily complicated the sample:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def paste_photobomb_item(self, clipboard, selectiondata, data=None):&lt;br /&gt;  item_dict = pickle.loads(clipboard.wait_for_text())&lt;br /&gt;  if item_dict["type"] is PhotobombPath:&lt;br /&gt;    new_item = PhotobombPath(self.__goo_canvas,&lt;br /&gt;                item_dict["path"],&lt;br /&gt;                item_dict["width"], None)&lt;br /&gt;  #snip out all the code to handle all of the other properties&lt;br /&gt;  new_item.set_clip_path(item_dict["clip"])&lt;br /&gt;  new_item.translate(10,10)&lt;br /&gt;  new_item.set_property("parent", self.__root)&lt;br /&gt;  self.z_order.append(new_item)&lt;br /&gt;  self.add_undo(new_item, None)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Persisting the Clipboard Item&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;You may have noticed that an item isn't actually put on the clipboard until the user tries to paste. This is a great optimization, because pasting complex or big things can take a lot of time, and there is no point on the user waiting for a copy command to finish if they are never going to actually paste with it. However, what if the user selects "Copy" in your app, and then quits your app? How is the item going to get pasted?&lt;br /&gt;&lt;br /&gt;Gtk handles this by letting you "store" data on the clipboard. This can actually be used at any time in your application, but to handle this particular situation, Photobomb just includes a few lines in it's on_destroy function. This causes the desktop's clipboard to store all of the data types, so all the functions for servicing a paste command get called, and the desktop's clipboard holds onto the data as long as it needs.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def on_destroy(self, widget, data=None):&lt;br /&gt;   """on_destroy - called when the PhotobombWindow is close. """&lt;br /&gt;   #clean up code for saving application state should be added here&lt;br /&gt;   paste_data = [("UTF8_STRING", 0, 0),&lt;br /&gt;      ("image/png", 0, 1),&lt;br /&gt;      ("PhotobombItem", 0, 2)]&lt;br /&gt;   self.clipboard.set_can_store(paste_data)&lt;br /&gt;   self.clipboard.store()&lt;br /&gt;gtk.main_quit()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-3168753136371618873?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/3168753136371618873/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/08/coding-copy-and-paste-functionality.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3168753136371618873'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3168753136371618873'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/08/coding-copy-and-paste-functionality.html' title='Coding Copy and Paste Functionality'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-zYGSTQbR0do/TjZ-t-rD6MI/AAAAAAAAAfw/u8pV9R55guY/s72-c/Screenshot-39.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8599680902144812404</id><published>2011-06-18T11:17:00.000-07:00</published><updated>2011-06-18T11:18:04.783-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>Coding an Undo/Redo Stack</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-RU673GRy6f4/TfzrjNLt53I/AAAAAAAAAfY/PplKHVRw1ug/s1600/Screenshot-37.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-RU673GRy6f4/TfzrjNLt53I/AAAAAAAAAfY/PplKHVRw1ug/s320/Screenshot-37.png" alt="" id="BLOGGER_PHOTO_ID_5619625425118291826" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The other night I put (what I think are) the finishing touches of an Undo/Redo stack for Photobomb. This is the second time I've written an Undo/Redo stack, the first time being for the &lt;a href="http://bazaar.launchpad.net/%7Equickly-committers/quidgets/trunk/view/head:/quickly/widgets/text_editor.py"&gt;TextEditor Quidget&lt;/a&gt;. Neither time was I able to find much guidance for how to approach the problem. I handled it slightly differently each time. I thought I'd write up a blog post about how I approached Undo/Redo for Photobomb, in case it helps someone else out.&lt;br /&gt;&lt;br /&gt;Generally, the way I conceived the problem was to keep a list of actions that needed to be undone. When an action is undone, then that action needs to be added to a list of actions that can be redone. But what does it mean to store an action in code? I thought of 2 approaches:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Keep a list of actions as functions along arguments to pass to those functions.&lt;/li&gt;&lt;li&gt;Keep a list of "backups" for every change made to an object, and on redo swap out the backup for the actual object.&lt;/li&gt;&lt;/ol&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;List of Actions Approach&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;I took the first approach back when I wrote TextEditor. It was easy to do because the only actions supported by Undo and Redo was that something could be added to the TextBuffer, or something could have been removed. So I very literally stored a list of functions and elements. So on a change, such as when text has been inserted, I store point at which the text was inserted, along with the inserted text:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     cmd = {"action":"delete","offset":iter.get_offset(),"text":text}&lt;br /&gt;  self._add_undo(cmd)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The _add_undo function merely ensures that the list does not grow beyond a defined maximum, and adds it to the undo list:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def _add_undo(self, cmd):&lt;br /&gt;  if self.undo_max is not None and len(self.undos) &amp;gt;= self.undo_max:&lt;br /&gt;    del(self.undos[0])&lt;br /&gt;  self.undos.append(cmd)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The heart of the __add_undo command was to extract the command to undo, and pass it along to _do_action(), and then add the return value of undo to redo stack.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     undo = self.undos[-1]&lt;br /&gt;  redo = self._do_action(undo)&lt;br /&gt;  self.redos.append(redo)&lt;br /&gt;  del(self.undos[-1])&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;do_action, in turn read the type of action, did the specified action, and returned the opposite action so it could be used for redo.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     if action["action"] == "delete":&lt;br /&gt;    self.get_buffer().delete(start_iter, end_iter)&lt;br /&gt;    action["action"] = "insert"&lt;br /&gt;  elif action["action"] == "insert":&lt;br /&gt;    self.get_buffer().insert(start_iter, action["text"])&lt;br /&gt;    action["action"] = "delete"&lt;br /&gt;  return action&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;So by keeping a mapping of actions and their opposites, and by using a dictionary to store the information needed to undo or redo the action, this system worked well for simple case of simple text editor.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;List of Backup Approach&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Not surprisingly, when I approach this problem in Photobomb, I followed the same basic solution path. For example, I set up a list to store undo actions in. I then started storing a code for actions like I used for delete and insert in the text editor. However, I quickly ran into some problems. Photobomb is more complex then TextEditor, and GooCanvas is more complex than TextView/TextBuffer. For example, storing all the info needed for undoing and redoing a clipping path on an object required some more complex code than seemed right for the problem. So, I switched to the second approach.&lt;br /&gt;&lt;br /&gt;Here, I store a list of undos, but what I store in the list is objects. A "new" object, as in the object after the change, and the "old" object. Kind of like the backup.&lt;br /&gt;&lt;br /&gt;To store an undo in this system, I make a back up of the object, change it, and then add the undo:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;       saved_item = self.back_up_item()&lt;br /&gt;    self.selected_item.move(delta_x,delta_y)&lt;br /&gt;    self.add_undo(self.selected_item, saved_item)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Making the backup entailed duplicating the entailed not just making a copy, but removing the copy from the goocanvas, and storing the z-order. GooCanvas doesn't track z-order for you, so I had to track it myself. Every object on the GooCanvas suface derives from a subclass of the custom PhotobombItem and one of the built in GooCanvas types, such as goocanvas.Image). PhotobombItems have a duplicate function witch return a copy, and a remove function. So getting a copy was easy.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def back_up_item(self, item=None):&lt;br /&gt;  if item is None:&lt;br /&gt;    item = self.selected_item&lt;br /&gt;  copy = item.duplicate(True, False)&lt;br /&gt;  copy.remove()&lt;br /&gt;  index = self.z_order.index(item)&lt;br /&gt;  self.z_order.insert(index+1,copy) &lt;br /&gt;  return copy&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;I quickly found that it is insufficient to simply store a pair of old and new objects for each undo. This is because an object can be modified twice in a row. And if you store the object itself, rather than a copy of the object, the undo stack will reference the object in whatever is it's current state, so undo can appear to weirdly go backward.&lt;br /&gt;&lt;br /&gt;For example if I make an item, call it O1 bigger, I'll store a copy of O1, call the copy O1.1, and O1 as the old and new items, respectively. Then if I make it bigger again, I store another copy of O1, call it O1.2, and O1 *again*. So undoing will go O1 is replaced by O1.2, which looks right, but then undo again and O1 will be replaced by O1.1. But oops, O1 was already replaced, so O1.1 appears, but O1.2 was never replaced.&lt;br /&gt;&lt;br /&gt;To guard against this, I looked backward through the undo stack to see where in the current undo stack the object appears last, and I take the current backup of the "old_item" and make that the "new_item" for the previous undo. So, for then making something bigger would store O1.1 and O1 again, but then making it bigger again, it would replace O1 with O1.2 as the "new_item" and then store O1.2 and O1 as the new_item at the top of the undo stack. So undo would replace O1 with O1.2, and then O1.2 with O1.1, and everything seems to work.&lt;br /&gt;&lt;br /&gt;That was a pretty long winded explanation for not that much code. Basically, I loop back through the undo stack and update the last occurrence of the object being added with the copy before going ahead and adding the objects to the stack.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def add_undo(self, new_item, old_item):&lt;br /&gt;  #ensure proper undos for multiple edits of the same object&lt;br /&gt;  if len(self.undos) &amp;gt; 0:&lt;br /&gt;    for item in self.undos[::-1]:&lt;br /&gt;      if item["new_item"] is new_item:&lt;br /&gt;        item["new_item"] = old_item&lt;br /&gt;        break&lt;br /&gt;  self.undos.append({"new_item":new_item,&lt;br /&gt;              "old_item":old_item})&lt;br /&gt;  self.redos = []&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Note that I also reset the redo stack whenever the undo stack is modified.&lt;br /&gt;&lt;br /&gt;So what does undo and redo do? They function in a very similar manner to the undo/redo functions in the text editor. First, they get they item from the top of the stack and stick it on the other stack. So for undo:&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     undo = self.undos[-1]&lt;br /&gt;  self.redos.append(undo)&lt;br /&gt;  del(self.undos[-1])&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then they remove the new item, and restore the backup (assuming they both exist). If there is no backup, that means the item was just created, so removing the item is sufficient. Conversely, if there is no new item, then it means the item was deleted, so adding it back to the canvas is sufficient. Finally, the function has to position the new item in the z-order and then select the new item.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;     if undo["new_item"] is not None:&lt;br /&gt;    undo["new_item"].remove()&lt;br /&gt;  if undo["old_item"] is not None:&lt;br /&gt;    undo["old_item"].set_property("parent",self.__root)&lt;br /&gt;    next_item = self._find_item_above(undo["old_item"])&lt;br /&gt;    if next_item is not None:&lt;br /&gt;      #position in z-order&lt;br /&gt;      undo["old_item"].lower(next_item)&lt;br /&gt;  self.selected_item = undo["old_item"]&lt;br /&gt;  self.__reset_selection()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Redo does essentially the same thing, only removes old_items and restores new_items. There was about 100% code duplication between the undo and redo functions. However, I decided to keep the duplicate code in place because I was worried that if I have to go back and fix bugs in a year or so, I'd need the code to be crystal clear about what it did. I can always refactor into a common function later, but for now, I felt that the duplicated code was just easier to read and understand.&lt;br /&gt;&lt;br /&gt;Now, calling these functions is easy in cases where a menu item was used to change on object, as there was one and only one change to store in the undo stack. But sometimes it's not so easy. For example, Photobomb has what I call "Press and Hold Buttons". To make an item bigger, for example, you can use the Bigger menu item, or you can hold down the Bigger button until the selected object is the size that you want and release the button. The button emits a signal called "tick" every 100 milliseconds, which is bound to an action, in the case, the "self.bigger" function.  This causes the item to get bigger many many times (about 10 times per second, in fact) but the user is going to think of it as one action to be undone.&lt;br /&gt;&lt;br /&gt;In order to handle this case, I created two signal handlers, one for the press event and one for the released signal of a button. All the Press and Hold buttons use these handlers. The press signal handler creates an immediate copy of the item, and then connects the tick and the released signals. Occasionally, the released handler gets called twice, which causes some confusion, so I track the tick signal to make sure that the released signal is only handled once.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def pah_button_pressed(self, button, action):&lt;br /&gt;  old_item = self.back_up_item()&lt;br /&gt;  tick_signal_id = button.connect("tick",action)&lt;br /&gt;  button.connect("released",self.pah_button_released, (tick_signal_id, old_item))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then in the released signal, I add the new item and the old_item to the undo stack, assuming it hasn't been already.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def pah_button_released(self, button, args):&lt;br /&gt;  tick_signal_id, old_item = args&lt;br /&gt;  #work around released signals being called multiple times&lt;br /&gt;  if tick_signal_id in self.__disconnected_tick_signals:&lt;br /&gt;    return&lt;br /&gt;  self.__disconnected_tick_signals.append(tick_signal_id)&lt;br /&gt;  button.disconnect(tick_signal_id)&lt;br /&gt;  if self.selected_item is not None:&lt;br /&gt;    self.add_undo(self.selected_item, old_item)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now only 1 undo is added when a Press and Hold Button is used.&lt;br /&gt;&lt;br /&gt;Another challenge to overcome in this system was handling the user changing selection by clicking on an item versus when the user clicked and dragged an item. The latter should be added to the undo stack, but not the former.&lt;br /&gt;&lt;br /&gt;Fortunately, this can be handled with a similar pattern. The mouse_down signal handler creates a copy of the item, and passes it to the mouse_up signal handler.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;           old_item = self.back_up_item(clicked_item)&lt;br /&gt;        self.__mouse_up_handler = self.__goo_canvas.connect("button_release_event",self.drag_stop, old_item)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then the drag_stop function checks if the item was actually moved before adding to the undo stack. If the item hasn't been moved, then the selection has simply changed.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Finishing Touch&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;One last finishing touch is to ensure that the undo and redo menu items are only sensitive if there is anything in the undo and/or redo stacks. Photobomb handles this by connecting to the activate signal of the edit menu, and then checking if there is anything in the undo and redo lists, and setting the sensitivity accordingly.&lt;br /&gt;&lt;pre style="font-family:arial;font-size:12px;border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;;background-image:URL(http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif);padding:0px;color:#000000;text-align:left;line-height:20px;"&gt;&lt;code style="color:#000000;word-wrap:normal;"&gt;   def edit_menu_reveal(self, widget, data=None):&lt;br /&gt;  active_undos = len(self.undos) &amp;gt; 0&lt;br /&gt;  active_redos = len(self.redos) &amp;gt; 0&lt;br /&gt;  self.builder.get_object("menuitem_undo").set_sensitive(active_undos)&lt;br /&gt;  self.builder.get_object("menuitem_redo").set_sensitive(active_redos)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;There is a lot of ineractivity in photobomb, and this creates a bit of complexity in handling undo/redo. You can see all the code in context in the &lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/photobomb/trunk/view/head:/bin/photobomb"&gt;photobomb trunk&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8599680902144812404?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8599680902144812404/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/06/coding-and-undoredo-stack.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8599680902144812404'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8599680902144812404'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/06/coding-and-undoredo-stack.html' title='Coding an Undo/Redo Stack'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-RU673GRy6f4/TfzrjNLt53I/AAAAAAAAAfY/PplKHVRw1ug/s72-c/Screenshot-37.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5078646616032373923</id><published>2011-04-19T16:23:00.001-07:00</published><updated>2011-04-20T10:46:16.188-07:00</updated><title type='text'>My Effort at Writing Help for Unity</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-RXuNHVXEwuU/Ta8aq0wO4uI/AAAAAAAAAfE/O9uTvKrjViQ/s1600/Screenshot-Control%2BCenter.png"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-8B9k2Oe_Sb8/Ta8arzP24tI/AAAAAAAAAfM/zpoBDyFL550/s1600/Screenshot-32.png"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.beatles-history.net/images/beatles-help-album.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 500px; height: 496px;" src="http://www.beatles-history.net/images/beatles-help-album.jpg" alt="" border="0" /&gt;&lt;/a&gt;I told the kind folks in #ubuntu-doc that I'd give it go, writing up some documentation for Unity. Here's a first draft of my best effort. I'm hopeful that the real pros at this can mine this material for making nice official documentation. I'm hoping that thay can cut and paste from here into their docs, or at least can edit the content to something they like, but maybe get going a little faster since there is something to change, rather than having to start fresh.&lt;br /&gt;&lt;br /&gt;I don't believe myself to be a particularly good writer, but, here without further a do ...&lt;br /&gt;&lt;h1&gt;Overview&lt;/h1&gt;The Unity environment has four areas that, combined, allow full operation of Ubuntu.&lt;br /&gt;These areas are:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;The Application Area. This is the main area of the screen for applications and documents.&lt;/li&gt;&lt;li&gt;Indicators. Indicators run along the panel at the top of the screen, off to the right Indicators are persistent widgets that are always available for reference and Controlling Ubuntu. Indicators include functionality for things like networking, power, messaging, time, and managing a session.&lt;/li&gt;&lt;li&gt;The Launcher. This provides access to favorite applications and running applications. &lt;/li&gt;&lt;li&gt;The Dash (not shown). The Dash provides very fast access to applications and files via searching or browsing. The Dash can also be customized with new "lenses" to allow other types of searching.&lt;/li&gt;&lt;/ol&gt;&lt;a href="http://3.bp.blogspot.com/-CbZznO-1Ev8/Ta8V4HHZJRI/AAAAAAAAAe0/Z_BxavML7r4/s1600/better_numbers.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-CbZznO-1Ev8/Ta8V4HHZJRI/AAAAAAAAAe0/Z_BxavML7r4/s320/better_numbers.jpg" alt="" id="BLOGGER_PHOTO_ID_5597716915571729682" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;h1&gt;How to Activate Applications via the Launcher&lt;/h1&gt;&lt;br /&gt;If the icon for the desired application is on the launcher, you can simply click on the icon, and it will launch the application. If the application is already running, clicking on the icon will activate the running application and bring it to the front. For example, if you click the Firefox icon on the launcher, this will launch Firefox, unless a Firefox window is already open, in which case Firefox will be brought to the front.&lt;br /&gt;&lt;br /&gt;After launching an application, The Launcher, my "autohide" by sliding off the screen to the left. If this happens, it can be easily accessed by pointing the mouse all the way to the of the screen and waiting for the launcher to appear, or pointing to the top left corner of a screen will cause the launcher to appear instantly.&lt;br /&gt;&lt;br /&gt;Running applications will have one tickmark to the left of the icon for each Window that is open. In this way, it is possible to see how many windows are open for an application.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-h_f4k71cJK8/Ta4cvUYVbDI/AAAAAAAAAbM/ZHWM9bcMAEc/s1600/Screenshot-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-h_f4k71cJK8/Ta4cvUYVbDI/AAAAAAAAAbM/ZHWM9bcMAEc/s320/Screenshot-1.png" alt="" id="BLOGGER_PHOTO_ID_5597442986118310962" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Clicking on an icon with the middle mouse button will open a new window for a running application. Some applications also support open a new window via a "Quick List". Right clicking on an icon will reveal the Quick List for the application. For example, Firefox has a "Open New Window" option in the Firefox Quick List.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-qk0k3DGisgA/Ta4dH7qSOgI/AAAAAAAAAbU/XngsUB06ebs/s1600/Screenshot-2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 320px;" src="http://2.bp.blogspot.com/-qk0k3DGisgA/Ta4dH7qSOgI/AAAAAAAAAbU/XngsUB06ebs/s320/Screenshot-2.png" alt="" id="BLOGGER_PHOTO_ID_5597443408979442178" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The Launcher can also be used via the keyboard, without the mouse. In order to activate an icon on The Launcher, press and hold the meta key on the keyboard (for example, on computers that came with Windows installed, there may be a Windows logo to mark the meta key). Pressing and holding the meta key will cause The Launcher to open, and each icon on The Launcher will be marked with a number or letter.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-lt21JRAGgDQ/Ta4dZGKBKWI/AAAAAAAAAbc/qmlQcdlS6CA/s1600/Screenshot-3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-lt21JRAGgDQ/Ta4dZGKBKWI/AAAAAAAAAbc/qmlQcdlS6CA/s320/Screenshot-3.png" alt="" id="BLOGGER_PHOTO_ID_5597443703854672226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Clicking the keyboard key with the number or letter that corresponds to the desired icon while still holding down the meta key will activate that icon. Holding down the shift key as well while doing so will launch another instance of the application, similar in function to middle clicking with the mouse.&lt;br /&gt;&lt;h1&gt;How to Activate Applications from the Dash&lt;/h1&gt;&lt;br /&gt;The Dash is activated by clicking on the Ubuntu logo in the top left of the screen, or by tapping on the meta key. When The Dash is activated in this way, it defaults to the Global Search lens. Commonly used applications and application categories are available by default.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-DIk_2_hqD3Y/Ta4d2a_aEmI/AAAAAAAAAbk/1h7EyPYMDwY/s1600/Screenshot-4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-DIk_2_hqD3Y/Ta4d2a_aEmI/AAAAAAAAAbk/1h7EyPYMDwY/s320/Screenshot-4.png" alt="" id="BLOGGER_PHOTO_ID_5597444207663518306" border="0" /&gt;&lt;/a&gt;The Global Search lens searches files as well as applications, so files can be quickly accessed in the same manner.&lt;br /&gt;&lt;br /&gt;To quickly activate an application, typing the first few letters of an application will show the application's icon, which can then be clicked to activate. Alternatively, typing the Enter key on the keyboard will cause the first item in the list to be activated. In this way, an application can be run very quickly by tapping the meta key, typing the first few letters of the applications name, and pressing Enter.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-Bfijx9vN5PM/Ta4eHeBC29I/AAAAAAAAAbs/JDiYYMLNsA0/s1600/Screenshot-5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-Bfijx9vN5PM/Ta4eHeBC29I/AAAAAAAAAbs/JDiYYMLNsA0/s320/Screenshot-5.png" alt="" id="BLOGGER_PHOTO_ID_5597444500533468114" border="0" /&gt;&lt;/a&gt;The More Apps icon allows browsing of all installed applications. This view can also be activated by the Applications Lens icon on The Launcher. Applications are grouped into three categories, most frequently used, all installed applications, and applications that are available to be installed via Software Center. Note that if&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-JoBLazXKXYM/Ta4eXNF2_KI/AAAAAAAAAb0/-Q9BUza7giI/s1600/Screenshot-6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-JoBLazXKXYM/Ta4eXNF2_KI/AAAAAAAAAb0/-Q9BUza7giI/s320/Screenshot-6.png" alt="" id="BLOGGER_PHOTO_ID_5597444770868165794" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Any of these section can be expanded by clicking the "See more results" link. For example, clicking "See more results" on installed applications will show all installed applications.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-h34tG1ypmaE/Ta4er6S4-uI/AAAAAAAAAb8/z5oPPjpO0cw/s1600/Screenshot-7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-h34tG1ypmaE/Ta4er6S4-uI/AAAAAAAAAb8/z5oPPjpO0cw/s320/Screenshot-7.png" alt="" id="BLOGGER_PHOTO_ID_5597445126599801570" border="0" /&gt;&lt;/a&gt;To limit the displayed applications to a category of applications, a category can be selected via the category selection dropdown.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-orDo10X4xxw/Ta4fD120KsI/AAAAAAAAAcE/_OFpdEwyxRk/s1600/Screenshot-8.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-orDo10X4xxw/Ta4fD120KsI/AAAAAAAAAcE/_OFpdEwyxRk/s320/Screenshot-8.png" alt="" id="BLOGGER_PHOTO_ID_5597445537725164226" border="0" /&gt;&lt;/a&gt;The Quick List for the Application Lens allowss fast access to an application category.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-w2Vjla4B8vI/Ta4fE_fgVRI/AAAAAAAAAcM/W-LIdxDc7H0/s1600/Screenshot-9.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-w2Vjla4B8vI/Ta4fE_fgVRI/AAAAAAAAAcM/W-LIdxDc7H0/s320/Screenshot-9.png" alt="" id="BLOGGER_PHOTO_ID_5597445557491619090" border="0" /&gt;&lt;/a&gt;Choosing one, will open The Dash and disply the selected category.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-YgAyz3X5EHI/Ta4fFgD8bvI/AAAAAAAAAcU/7fvJwNu0qs4/s1600/Screenshot-10.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-YgAyz3X5EHI/Ta4fFgD8bvI/AAAAAAAAAcU/7fvJwNu0qs4/s320/Screenshot-10.png" alt="" id="BLOGGER_PHOTO_ID_5597445566234390258" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Adding an Application to The Launcher&lt;/h1&gt;&lt;br /&gt;An application can be added to the launcher by dragging the application's icon from The Dash onto the launcher. Alternatively, when an application is running, the Keep in Launcher item in the application's Quick List will permamently add the application to The Launcher.&lt;br /&gt;&lt;br /&gt;Once on The Laucher, an icon's position on The Launcher can be changed by dragging it first off of The Launcher, and then back onto it in the desired location.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-QOSRG_cVK7I/Ta4f4XOv0dI/AAAAAAAAAcc/0X6a__BuVYo/s1600/Screenshot-11.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://1.bp.blogspot.com/-QOSRG_cVK7I/Ta4f4XOv0dI/AAAAAAAAAcc/0X6a__BuVYo/s320/Screenshot-11.png" alt="" id="BLOGGER_PHOTO_ID_5597446440037110226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;h1&gt;Finding and Opening Files&lt;/h1&gt;&lt;br /&gt;Files can be quickly searched and opened from The Dash. The Dash is activated by clicking on the Ubuntu logo in the top left of the screen, or by tapping on the meta key. When The Dash is activated in this way, it defaults to the Global Search lens. Commonly used applications and application categories are available by default.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-DIk_2_hqD3Y/Ta4d2a_aEmI/AAAAAAAAAbk/1h7EyPYMDwY/s1600/Screenshot-4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-DIk_2_hqD3Y/Ta4d2a_aEmI/AAAAAAAAAbk/1h7EyPYMDwY/s320/Screenshot-4.png" alt="" id="BLOGGER_PHOTO_ID_5597444207663518306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The Global Search lens searches applications as well as files, so applications can be quickly accessed in the same manner.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-EpSHgS2qxZU/Ta4g6MuTqPI/AAAAAAAAAck/-HcL_yu-xEI/s1600/Screenshot-12.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-EpSHgS2qxZU/Ta4g6MuTqPI/AAAAAAAAAck/-HcL_yu-xEI/s320/Screenshot-12.png" alt="" id="BLOGGER_PHOTO_ID_5597447571088058610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;To quickly open a file, typing the first few letters of the file name will show the file's icon, which can then be clicked to open. Alternatively, the icons can be navigated with the arrow keys, and typing the Enter key on the keyboard to open the selected file.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-gKrEhVIU3ZE/Ta4g7DIAv3I/AAAAAAAAAcs/AaSb-1Ul5xw/s1600/Screenshot-13.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://1.bp.blogspot.com/-gKrEhVIU3ZE/Ta4g7DIAv3I/AAAAAAAAAcs/AaSb-1Ul5xw/s320/Screenshot-13.png" alt="" id="BLOGGER_PHOTO_ID_5597447585691385714" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The Files and Folders Lens supports browing for files. The Files and Folders Lens can be activated via the Find Files icon in the Global Lens, or by clicking on the Files and Folders Lens icon on The Launcher. This lens presents recently used files, and commonly used folders.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-WtPRQQpuWqg/Ta4g8TUobnI/AAAAAAAAAc0/8mJoOpgPdWs/s1600/Screenshot-14.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-WtPRQQpuWqg/Ta4g8TUobnI/AAAAAAAAAc0/8mJoOpgPdWs/s320/Screenshot-14.png" alt="" id="BLOGGER_PHOTO_ID_5597447607219154546" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Any of these views can be expaned using the "See more results link".&lt;br /&gt;&lt;br /&gt;Searching file title and contents is supported in the search field, producing a list of results if any matching files are found.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-w_j_T-h3_aw/Ta4g9ANlddI/AAAAAAAAAc8/Sdm57zrnx64/s1600/Screenshot-15.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-w_j_T-h3_aw/Ta4g9ANlddI/AAAAAAAAAc8/Sdm57zrnx64/s320/Screenshot-15.png" alt="" id="BLOGGER_PHOTO_ID_5597447619269195218" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;The Files and Folders Lens also supports browsing for files by type, by choosing a type of file from the file type dropdown.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-QClBuJIlnDU/Ta4g9zVXzNI/AAAAAAAAAdE/hjos7iLxIWA/s1600/Screenshot-16.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-QClBuJIlnDU/Ta4g9zVXzNI/AAAAAAAAAdE/hjos7iLxIWA/s320/Screenshot-16.png" alt="" id="BLOGGER_PHOTO_ID_5597447632992062674" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Selecting a file type will produce a list of files ordered by time.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-L_9O6-VeYro/Ta4jieJpcjI/AAAAAAAAAdM/RirHld7TwI4/s1600/Screenshot-17.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-L_9O6-VeYro/Ta4jieJpcjI/AAAAAAAAAdM/RirHld7TwI4/s320/Screenshot-17.png" alt="" id="BLOGGER_PHOTO_ID_5597450461984158258" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;While a file type is selected, search results will be limited to that file type.&lt;br /&gt;&lt;br /&gt;The Quick List for the Files and Folders Lens icon on The Launcher allows fast navigation to a file type filter in The Dash.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-zh4XgALhRFw/Ta4jjQmuf3I/AAAAAAAAAdU/BALD-lQphKE/s1600/Screenshot-18.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-zh4XgALhRFw/Ta4jjQmuf3I/AAAAAAAAAdU/BALD-lQphKE/s320/Screenshot-18.png" alt="" id="BLOGGER_PHOTO_ID_5597450475527896946" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Running Applications in Unity&lt;/h1&gt;&lt;br /&gt;With a few exceptions (notably Libre Office) menus for an active application window will be availble on the long top panel of Unity, in the Menu Indicator. When the menus are not active, the window title will be display there.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/-dS2frlVfdic/Ta4kSOBcxzI/AAAAAAAAAdk/KcG6fboy6Dw/s1600/Screenshot-20.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 153px;" src="http://1.bp.blogspot.com/-dS2frlVfdic/Ta4kSOBcxzI/AAAAAAAAAdk/KcG6fboy6Dw/s320/Screenshot-20.png" alt="" id="BLOGGER_PHOTO_ID_5597451282288527154" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;To see the menus that are available, simply hover your mouse over or near the window title, and the menus will be revealed, and you can then click on them.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-IFSNGOIN8Y8/Ta4kR1zSv0I/AAAAAAAAAdc/ORfiEIH6erA/s1600/Screenshot-21.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 113px;" src="http://2.bp.blogspot.com/-IFSNGOIN8Y8/Ta4kR1zSv0I/AAAAAAAAAdc/ORfiEIH6erA/s320/Screenshot-21.png" alt="" id="BLOGGER_PHOTO_ID_5597451275786698562" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Alteranatively, pressing the Alt key will also review the menu for the active window, along with any available keyboard accelerators.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-IRH8wmJs3ic/Ta4kn2grslI/AAAAAAAAAds/rzlSqOw5c4Y/s1600/Screenshot-22.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 114px;" src="http://3.bp.blogspot.com/-IRH8wmJs3ic/Ta4kn2grslI/AAAAAAAAAds/rzlSqOw5c4Y/s320/Screenshot-22.png" alt="" id="BLOGGER_PHOTO_ID_5597451653934199378" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Running applications will have one tickmark to the left of the icon for each Window that is open. In this way, it is possible to see how many windows are open for an application. Clicking on the application's icon in The Launcher will bring the application to the foreground.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-MCYNL1sZjck/Ta4lA53e3CI/AAAAAAAAAd0/Wj3Tqvub-nY/s1600/Screenshot-23.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-MCYNL1sZjck/Ta4lA53e3CI/AAAAAAAAAd0/Wj3Tqvub-nY/s320/Screenshot-23.png" alt="" id="BLOGGER_PHOTO_ID_5597452084331863074" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Clicking on an icon with the middle mouse button will open a new window for a running application. Some applications also support open a new window via a "Quick List". Right clicking on an icon will reveal the Quick List for the application. For example, Firefox has a "Open New Window" option in it's Quick List.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/-qk0k3DGisgA/Ta4dH7qSOgI/AAAAAAAAAbU/XngsUB06ebs/s1600/Screenshot-2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 307px; height: 320px;" src="http://2.bp.blogspot.com/-qk0k3DGisgA/Ta4dH7qSOgI/AAAAAAAAAbU/XngsUB06ebs/s320/Screenshot-2.png" alt="" id="BLOGGER_PHOTO_ID_5597443408979442178" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h1&gt;Managing Windows&lt;/h1&gt;&lt;h1&gt;&lt;br /&gt;&lt;/h1&gt;&lt;h2&gt;Switching Between Active Windows&lt;/h2&gt;&lt;br /&gt;Ubuntu offers many methods for managing and switching between mulitple windows. The fastest way to switch between a window on top, and a window directly below it to hold down the Alt key, and quickly pres the Tab key. This will cause the top window to switch places with the second window. Quickly tapping Alt and Tab again will switch them back. Two windows can be alternated very quickly in this manner.&lt;br /&gt;&lt;br /&gt;Alt and Tab can also be used to switch to a window that is not the second window. Pressing the Alt key and holding down the Tab key will quickly bring up a list of open windows. Pressing Tab will select each window in turn. Release Alt and Tab will cause the currently selected window to come to the front.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-ikTM0wIIQvc/Ta4laxi7RyI/AAAAAAAAAd8/b0wmTeUG94I/s1600/Screenshot-26.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-ikTM0wIIQvc/Ta4laxi7RyI/AAAAAAAAAd8/b0wmTeUG94I/s320/Screenshot-26.png" alt="" id="BLOGGER_PHOTO_ID_5597452528774760226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If there are mulitple windows open for a particular application, clicking on The Launcher icon for the desired application will reveal of all the windows. Clicking on the desired window will bring that window to the front.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-qoLXfqnvzyw/Ta4mqJsidzI/AAAAAAAAAeE/dlxeM1vOPOM/s1600/Screenshot-30.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-qoLXfqnvzyw/Ta4mqJsidzI/AAAAAAAAAeE/dlxeM1vOPOM/s320/Screenshot-30.png" alt="" id="BLOGGER_PHOTO_ID_5597453892467193650" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Pressing the Meta key and W will cause all open windows to be revealed, no matter the application with which they are associated. This faciliates quickly finding any application that is open, no matter the Workspace the window is on.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Window Size and Positioning&lt;/h2&gt;&lt;br /&gt;Document and application windows typically have the following three buttons for controling the size and visibility of the window:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;close&lt;/li&gt;&lt;li&gt;minimize&lt;/li&gt;&lt;li&gt;maximize&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-jbLbWXgqtoI/Ta4oPxp3QbI/AAAAAAAAAeM/86yTChSn7bk/s1600/Screenshot-buttons.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 246px; height: 168px;" src="http://3.bp.blogspot.com/-jbLbWXgqtoI/Ta4oPxp3QbI/AAAAAAAAAeM/86yTChSn7bk/s320/Screenshot-buttons.png" alt="" id="BLOGGER_PHOTO_ID_5597455638360179122" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Clicking the close button will close the current window and potentially quit the application as well.&lt;br /&gt;&lt;br /&gt;Minimize will hide the window altogether. A minimized window can be accessed by clicking on the application's icon in The Launcher, or by navigating to the window by holding down the Alt key and holding down the Tab key and then tapping the Tab key until the minimized window is selected.&lt;br /&gt;&lt;br /&gt;A window that takes up the whole screen is "maximized". For an unmaximized application, clicking on the maximize button will cause the window to maximize. Clicking on the maximize button for a maximized will cause the window to unmaximize.&lt;br /&gt;&lt;br /&gt;A window can also be toggled between maximized and unmaximized by double clicking on the applications titlebar.&lt;br /&gt;&lt;br /&gt;Dragging an unmaximized window's title bar to the top of the screen will maximize the windw. Dragging a maximized windows title bar to the center of the screen will unmaximize it. Dragging a window by the titlebar to the the right or the left of the screen untl the mouse pointer touches the edge, will resize the window to take up half of the screen. This can be useful for sizing windows and using them side by side.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-RGl1HD9-HE4/Ta4q8g5SiQI/AAAAAAAAAes/3wf6qfHS4a8/s1600/Screenshot-27.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://1.bp.blogspot.com/-RGl1HD9-HE4/Ta4q8g5SiQI/AAAAAAAAAes/3wf6qfHS4a8/s320/Screenshot-27.png" alt="" id="BLOGGER_PHOTO_ID_5597458605978847490" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;h2&gt;Workspaces&lt;/h2&gt;Workspaces provide a means for organizing windows into groups, and quickly switching between those groups. For example, windows associated with work could be grouped onto workspace 1 while windows associated with chatting with friends could be groupled onto workspace 2. By default, all applications are opened onto Workspace 1. There are four available workspaces.&lt;br /&gt;Clickng The Workspace Switcher on The Launcher provides an overview of the four workspaces, arranged in a grid.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-iuJdFZxA0PM/Ta4pCsMvAKI/AAAAAAAAAek/EedAxWXHhSU/s1600/Screenshot-28.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://2.bp.blogspot.com/-iuJdFZxA0PM/Ta4pCsMvAKI/AAAAAAAAAek/EedAxWXHhSU/s320/Screenshot-28.png" alt="" id="BLOGGER_PHOTO_ID_5597456513069154466" border="0" /&gt;&lt;/a&gt;Clicking on a workspace, and then clicking on The Workspace Switcher will make that workspace the active workspace. Applications launched from The Launcher or The Dash will be open on the active workspace.&lt;br /&gt;Note that clicking on an icon in The Launcher for an active application, will navigate to that application. If the application is on a different workspace, that workspace will become active. A new window for an application can be open by middle clicking on the application's icon in The Launcher, or using the Quick List for some applications.&lt;br /&gt;Holding down the Meta key and Clicking S is a short cut for activating The Workspace Swithcer. Holding down the Control and Alt keys, and clicking on the arrow keys very quickly navigates between workspaces.&lt;br /&gt;Window can be moved between workspaces while the Workspace Switcher is active by simply dragging windows between the workspaces.&lt;a href="http://4.bp.blogspot.com/-3CXhotbznxY/Ta4pBzW8rgI/AAAAAAAAAec/xfYl575WKyc/s1600/Screenshot-29.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-3CXhotbznxY/Ta4pBzW8rgI/AAAAAAAAAec/xfYl575WKyc/s320/Screenshot-29.png" alt="" id="BLOGGER_PHOTO_ID_5597456497811172866" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;h1&gt;Settings&lt;/h1&gt;&lt;br /&gt;System Settings can be accessed via the Control Center dialog. To activate the Control Center, select System Settings from the Power Menu.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/-2C6RttbqwpY/Ta8aqXXVVsI/AAAAAAAAAe8/UyV3Wz1Yaxc/s1600/Screenshot-31.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/-2C6RttbqwpY/Ta8aqXXVVsI/AAAAAAAAAe8/UyV3Wz1Yaxc/s320/Screenshot-31.png" alt="" id="BLOGGER_PHOTO_ID_5597722176973526722" border="0" /&gt;&lt;/a&gt;You can then choose the specific settings you are interested from the list.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-RXuNHVXEwuU/Ta8aq0wO4uI/AAAAAAAAAfE/O9uTvKrjViQ/s1600/Screenshot-Control%2BCenter.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 269px;" src="http://3.bp.blogspot.com/-RXuNHVXEwuU/Ta8aq0wO4uI/AAAAAAAAAfE/O9uTvKrjViQ/s320/Screenshot-Control%2BCenter.png" alt="" id="BLOGGER_PHOTO_ID_5597722184862589666" border="0" /&gt;&lt;/a&gt;Alternatively, you can navigate directly to the settings that you are interested from The Dash.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/-8B9k2Oe_Sb8/Ta8arzP24tI/AAAAAAAAAfM/zpoBDyFL550/s1600/Screenshot-32.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/-8B9k2Oe_Sb8/Ta8arzP24tI/AAAAAAAAAfM/zpoBDyFL550/s320/Screenshot-32.png" alt="" id="BLOGGER_PHOTO_ID_5597722201638232786" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5078646616032373923?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5078646616032373923/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/04/my-effort-at-writing-help-for-unity.html#comment-form' title='24 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5078646616032373923'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5078646616032373923'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/04/my-effort-at-writing-help-for-unity.html' title='My Effort at Writing Help for Unity'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-CbZznO-1Ev8/Ta8V4HHZJRI/AAAAAAAAAe0/Z_BxavML7r4/s72-c/better_numbers.jpg' height='72' width='72'/><thr:total>24</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5364337305268258081</id><published>2011-04-07T05:45:00.000-07:00</published><updated>2011-04-07T06:30:44.554-07:00</updated><title type='text'>Congrats to GNOME!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://gnome.org"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 129px;" src="http://4.bp.blogspot.com/-OaTD0DbJUG8/TZ254H40CGI/AAAAAAAAAa8/E97zPUJqrow/s320/gnome3_banner.png" alt="" id="BLOGGER_PHOTO_ID_5592830686105176162" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Today you'll see the above banner on &lt;a href="http://www.ubuntu.com/"&gt;http://www.ubuntu.com/&lt;/a&gt;, as the 2nd banner linking through to the Gnome web site. That's because yesterday was a big day for the GNOME team, they delivered GNOME 3.0. Yesterday the GNOME community got the ball over the goal line, and delivered GNOME 3.0! An exciting time, indeed.&lt;br /&gt;&lt;br /&gt;I know from my own experiences how difficult it is to do what they did. Delivering GNOME 3.0 with all it's platform improvements and it's slick new experience is a momumental achievement of teamwork and leadership. GNOME 3.0 is a major contribution to Free Software, and it continues the GNOME tradition of user-centered design, and community-centered development. GNOME 3.0 will be instrumental in powering Ubuntu for years to come, as well as countless other distros and projects.&lt;br /&gt;&lt;br /&gt;The release itself seems flawless, from the product they delivered to the roll out on the website.&lt;br /&gt;&lt;br /&gt;Congrats and also a heartfelt "Thanks" to all my friends in GNOME.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5364337305268258081?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5364337305268258081/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/04/congrats-to-gnome.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5364337305268258081'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5364337305268258081'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/04/congrats-to-gnome.html' title='Congrats to GNOME!'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-OaTD0DbJUG8/TZ254H40CGI/AAAAAAAAAa8/E97zPUJqrow/s72-c/gnome3_banner.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-606583987234357247</id><published>2011-04-05T09:42:00.000-07:00</published><updated>2011-04-05T09:56:14.772-07:00</updated><title type='text'>Unity Almost There</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/-5HloJu4A5MM/TZtHYpJwhLI/AAAAAAAAAa0/DQX_u51ZaGM/s1600/spider.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 207px;" src="http://4.bp.blogspot.com/-5HloJu4A5MM/TZtHYpJwhLI/AAAAAAAAAa0/DQX_u51ZaGM/s320/spider.png" alt="" id="BLOGGER_PHOTO_ID_5592141850999817394" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;So, I'm a bit surprised how much people liked my spider diagrams to update folks on my perception of the state of Unity. It's been hard to update that last few months, just because Unity has been changing so fast. However, those changes have slowed down, and I've gotten some requests, so here is my post-beta 1 spider diagram for Unity.&lt;br /&gt;&lt;br /&gt;As you can see, the orange line, Unity, almost overlaps the yellow line, our target for Natty. Obviously, this is a major accomplish for many teams involved in this project. I've been using Unity and my netbook and on my desktop for months now. Over the last few weeks it has crisped up into a very tight experience. Of all the desktop environments I have ever used, Unity is by far my favorite.&lt;br /&gt;&lt;br /&gt;Here are some notes on the remaining gaps:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Crashers&lt;/span&gt;&lt;br /&gt;One thing not reflected in the graph is the number of crashers, particularly in compiz, that have been reported. The crash reports have slowed down, and the crashes are getting fixed. If we stay focused, I believe that we will fix all of the common, and most of the not so common crashers.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Accessability&lt;/span&gt;&lt;br /&gt;The only very painful gap is in accessability. Unity is very keyboard friendly, so that's good. The Launcher is completely exposed to screenreaders, so that's good. However, the dash is not completely exposed to screen readers. This means that blind users won't be able to use Unity without some modifications. This is unfortunate, indeed. Fortunately, the "classic" desktop is still as accessible as it has always been. Work continues on making the dash accessible, but it won't make it into Natty by release. I'd love to see a PPA or even an SRU to close this gap after release.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Search&lt;/span&gt;&lt;br /&gt;Search is good, however, there are certain substring searches which maddenly don't work yet. For example, if I search for "Torrent" I don't get Transmission in the list, because it's a "BitTorrent" client.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Find and Launch Apps&lt;/span&gt;&lt;br /&gt;I think the category view is good, but could be a bit easier to find and activate.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Dash Performance&lt;/span&gt;&lt;br /&gt;Sometimes when I do a search in the Dash, it thinks for a bit before it responds to my enter key. This slows me down a bit when I am trying to launch something quickly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-606583987234357247?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/606583987234357247/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/04/unity-almost-there.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/606583987234357247'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/606583987234357247'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/04/unity-almost-there.html' title='Unity Almost There'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-5HloJu4A5MM/TZtHYpJwhLI/AAAAAAAAAa0/DQX_u51ZaGM/s72-c/spider.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2164262559366283669</id><published>2011-04-04T14:59:00.000-07:00</published><updated>2011-04-04T15:10:00.328-07:00</updated><title type='text'>Video Demonstraton of a Few Things I Like in Unity</title><content type='html'>Here are a couple of quick videos I made this morning demonstrating some things that I think are cool in Unity.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;EDIT&lt;/span&gt;: Please note that Unity actually goes much faster than it appears to in these videos. There are certain points where the video didn't really seem to keep up with Unity for some reason.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Dash&lt;/span&gt;&lt;br /&gt;This demo shows using categories in the dash, and also being able to use the dash to quickly open apps without using the mouse.&lt;br /&gt;&lt;object height="344" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/tGZUgP9lO0I?hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/tGZUgP9lO0I?hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="344" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Launcher&lt;/span&gt;&lt;br /&gt;This demo shows using the meta-key to use the launcher to switch to apps and launch apps. It also shows a little trick that you can use to put any command you want onto the launcher.&lt;br /&gt;&lt;object height="344" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/Zi5653hrdt8?hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/Zi5653hrdt8?hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="344" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2164262559366283669?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2164262559366283669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/04/video-demonstraton-of-few-things-i-like.html#comment-form' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2164262559366283669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2164262559366283669'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/04/video-demonstraton-of-few-things-i-like.html' title='Video Demonstraton of a Few Things I Like in Unity'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6642286099695430428</id><published>2011-02-21T19:34:00.000-08:00</published><updated>2011-02-21T20:01:27.227-08:00</updated><title type='text'>Easily Support the Sound Menu in Python</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/-MdjfYtYFhps/TWMvt71yfsI/AAAAAAAAAak/oX7YkNlyhJc/s1600/Screenshot-32.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-MdjfYtYFhps/TWMvt71yfsI/AAAAAAAAAak/oX7YkNlyhJc/s320/Screenshot-32.png" alt="" id="BLOGGER_PHOTO_ID_5576353229818724034" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;An important part of integrating with the Ubuntu desktop is ensuring that your application is using all of the appropriate indicators. In this entry, I explain how I added support for the Ubuntu Sound Menu to the sample application "&lt;a href="http://theravingrick.blogspot.com/2011/01/quickly-tutorial-for-natty-diy-media.html"&gt;Simple Player&lt;/a&gt;."&lt;br /&gt;&lt;br /&gt;Ubuntu's Sound Menu allows users to access some of a media players functions without the user having to find the application window and use the applications controls. To add support for the Sound Menu controls, which allow the user to Play, Pause, and use Next, and Previous functions, takes 4 basic steps:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Create a Desktop File so the Sound Menu knows that it should display your application.&lt;/li&gt;&lt;li&gt;Add sound_menu.py to your application, instantiate a SoundMenuControls object, and start a dbus main loop.&lt;/li&gt;&lt;li&gt;Implement functions from the SoundMenuControls so that the Sound Menu can control your application.&lt;/li&gt;&lt;li&gt;Call functions on the SoundMenuControls objet so that the Sound Menu knows about changes that your applications makes.&lt;/li&gt;&lt;/ol&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Creating the Desktop File&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;If you are developing an application, and have not installed it, most likely you do not have a desktop file installed. Desktop files are used in Linux desktops to describe your application to the system. Many parts of the desktop refer to desktop files for things like creating application launchers and menus, file handling support, etc... The Ubuntu Sound Menu uses desktopfiles to determine which applications it should launch and handle. In a default Natty install, only Banshee will have a desktop file that the Sound Menu will detect and want to handle.&lt;br /&gt;&lt;br /&gt;Desktop files all live in /usr/share/applications/. A quickly project has a file called app-name.desktop.in. This file will be turned into a real desktop file by the packaging and installation system. But it won't work for development, so you'll want to create a new one and copy it into /usr/share/applications/.&lt;br /&gt;&lt;br /&gt;For simple-player, I created a desktop file with the following contents:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;[Desktop Entry]&lt;br /&gt;Name=Simple Player&lt;br /&gt;Comment=SimplePlayer application&lt;br /&gt;Categories=GNOME;Audio;Music;Player;AudioVideo;&lt;br /&gt;Exec=simple-player&lt;br /&gt;Icon=simple-player&lt;br /&gt;Terminal=false&lt;br /&gt;Type=Application&lt;br /&gt;MimeType=application/x-ogg;application/ogg;audio/x-vorbis+ogg;audio/x-scpls;audio/x-mp3;audio/x-mpeg;audio/mpeg;audio/x-mpegurl;audio/x-flac;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;When I named it, I made sure to name "simple-player.desktop". Then I used:&lt;br /&gt;$sudo cp simple-player.desktop /usr/share/applications/&lt;br /&gt;to copy it into the applications directory where the Sound Menu could find it. Then I logged out and logged in again so that the Sound Menu could discover it.&lt;br /&gt;&lt;br /&gt;Now Simple Player shows up as an option in the Sound Menu!&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-Ri029AtWLL4/TWMvtTTGhpI/AAAAAAAAAaM/yrkXtj4vagM/s1600/Screenshot-29.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/-Ri029AtWLL4/TWMvtTTGhpI/AAAAAAAAAaM/yrkXtj4vagM/s320/Screenshot-29.png" alt="" id="BLOGGER_PHOTO_ID_5576353218935817874" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;insert screenshot=""&gt;There is no icon, because I have not installed the icon for Sound Menu. Also, clicking the Simple Player menu item won't launch Simple Player because the application is not actually installed. Both of those problems will be fixed when the application is properly packaged and installed.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Get sound_menu.py&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;The Sound Menu communicates with applications via the MPRIS2 DBUS API. This was a good choice by the Sound Menu developers because many applications already support MPRIS2. However, doing DBUS programming, especially with Python, can be a tad complex. Since I didn't want application programmers to have to rewrite all the DBUS code every time someone wants to integrate a Python application with the Sound Menu, I create a module called sound_menu.py to encapsulate all the DBUS goo. Note that sound_menu.py does not implement all of the MPRIS2 specification, only the parts that the Sound Menu needs.&lt;br /&gt;&lt;br /&gt;To get sound_menu.py, it's probably easiest to check it out from launchpad account:&lt;br /&gt;$bzr branch lp:~rick-rickspencer3/+junk/sound_menu&lt;br /&gt;&lt;br /&gt;This will create a directory called sound_menu, with a single file sound_menu.py. If you want to, you can look at sound_menu.py and copy and paste the code into your program to make it work. However, sound_menu.py is designed so that you can easily mix it into your application without having to modify it, or work with the DBUS calls directly. We'll go that route for Simple Player.&lt;br /&gt;&lt;br /&gt;So the first step is to copy the sound_menu.py file into the library for Simple Player. For example:&lt;br /&gt;$cp sound_menu/sound_menu.py simple-player/simple_player/&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Setting Up sound_menu in Your Code&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;Now that the sound_menu module is copied into the applications library, there are a few steps to take before you can really start programming it.&lt;br /&gt;&lt;br /&gt;First, you need to import the SoundMenuControls class from the sound_menu module. I did this in the simple-player file with the other simple_player imports, right above the class deceleration section. So I added the bottom line:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;from simple_player import (BaseSimplePlayerWindow)&lt;br /&gt;import simple_player.helpers as helpers&lt;br /&gt;from simple_player.preferences import preferences&lt;br /&gt;from simple_player.sound_menu import SoundMenuControls&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The is one other really important piece of book keeping required before you can create a SoundMenuControls object. It is necessary to start up a DBUS main loop. For simple-player, the easiest place for this is in the __main__ function, so I added the following lines:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;  #turn on the dbus mainloop&lt;br /&gt;  from dbus.mainloop.glib import DBusGMainLoop&lt;br /&gt;  DBusGMainLoop(set_as_default=True)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Making the whole main function look like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;if __name__ == "__main__":&lt;br /&gt;  # Support for command line options.  See helpers.py to add more.&lt;br /&gt;  helpers.parse_options()&lt;br /&gt;&lt;br /&gt;  #turn on the dbus mainloop&lt;br /&gt;  from dbus.mainloop.glib import DBusGMainLoop&lt;br /&gt;  DBusGMainLoop(set_as_default=True)&lt;br /&gt;&lt;br /&gt;  # Run the application.&lt;br /&gt;  preferences.db_connect()&lt;br /&gt;  preferences.load()&lt;br /&gt;  window = SimplePlayerWindow()&lt;br /&gt;  window.show()&lt;br /&gt;  gtk.main()&lt;br /&gt;  preferences.save()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Create an Instance of SoundMenuControls&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;Now that we've done the booking keeping to import the SoundMenuControls class, and also to start a DBUS loop, it's possible to intantiate a SoundMenuControls object. To create a SoundMenuControls object, you need to tell it the name of the desktop file to look for. So, I added this line to the bottom of the finish_initializing function in simple-player:&lt;br /&gt;self.sound_menu_controls = SoundMenuControls('simple-player')&lt;br /&gt;&lt;br /&gt;Using "$quickly run" to start simple-player, notice that the Sound Menu now knows that it is running, and presents the Controls for it.&lt;br /&gt;&lt;/insert&gt;&lt;a href="http://4.bp.blogspot.com/-LMt-XAdI1ko/TWMvtRTR5TI/AAAAAAAAAaU/S5TTK2dkcko/s1600/Screenshot-30.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-LMt-XAdI1ko/TWMvtRTR5TI/AAAAAAAAAaU/S5TTK2dkcko/s320/Screenshot-30.png" alt="" id="BLOGGER_PHOTO_ID_5576353218399692082" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;insert screenshot=""&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Implement the _sound_menu_* Functions&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;If you click on the different buttons and such, though, you'll notice that sadly they don't work. Well, why would they? The Sound Menu doesn't know how to make Simple Player do what it wants. In order to do that, we need to implement a few functions, and tell the SoundMenuControls object to use those functions. Then we'll need to add a little bit of code to inform the Sound Menu when changes occur in Simple Player too.&lt;br /&gt;&lt;br /&gt;All of the functions that you need to implement start with "_sound_menu_*". I named them this way so that it was obvious what the functions were for, and also so that they weren't likely to conflict with any code that you already wrote.&lt;br /&gt;&lt;br /&gt;There are 2 different approaches that you can take to implement SoundMenuControls's functionality in your application. You can inherit from it, typically by using multiple inheritance, or you can assign functions to the _sound_menu_* functions. This later method, though while not quite as clean, is a bit easier, so I went with that.&lt;br /&gt;&lt;br /&gt;The first thing I did was create local implementations of the necessary functions. Note that I could have named them whatever I wanted, but I decided to just stick with the names from SoundMenuControls. There are 6 functions that must be implemented. I added te following functions directly below the finish_initializing function. I think between the names and the comments, they are pretty self-explanatory, so I won't cover each one.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;      def _sound_menu_is_playing(self):&lt;br /&gt;          """return True if the player is currently playing, otherwise, False"""&lt;br /&gt;          return self.player.playbin.get_state()[1] == gst.STATE_PLAYING&lt;br /&gt;&lt;br /&gt;      def _sound_menu_play(self):&lt;br /&gt;          """start playing if ready"""&lt;br /&gt;          if len(self.ui.scrolledwindow1.get_children()[0].selected_rows) &amp;gt; 0:&lt;br /&gt;              self.player.play()&lt;br /&gt;&lt;br /&gt;      def _sound_menu_pause(self):&lt;br /&gt;          """pause if playing"""&lt;br /&gt;          if self.player.playbin.get_state()[1] == gst.STATE_PLAYING:&lt;br /&gt;              self.player.pause()&lt;br /&gt;&lt;br /&gt;      def _sound_menu_next(self):&lt;br /&gt;          """go to the next song in the list"""&lt;br /&gt;          self.play_next_file(self, None)&lt;br /&gt;&lt;br /&gt;      def _sound_menu_previous(self):&lt;br /&gt;          """go to the previous song in the list"""&lt;br /&gt;          self.play_previous_file()&lt;br /&gt;&lt;br /&gt;      def _sound_menu_raise(self):&lt;br /&gt;         """raise the window to the top of the z-order"""&lt;br /&gt;         self.get_window().show()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Note that the _sound_menu_is_playing function and the _sound_menu_pause functions both work by checking the state of the player's playbin. This requires a comparison to an enum in gstreamer. This won't work unless you import gstreamers, so remember to add "import gst" to your import statements.&lt;br /&gt;&lt;br /&gt;Note that the functions are actually quite simple. They provide a mapping from the actions that a user takes in a sound menu, to the functions in the simple-player file. Now I just need to tell my SoundMenuControls object to use those functions instead of the ones that it comes with. I do this by simply assignment, directly after the line where I created the object:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;          self.sound_menu = SoundMenuControls("simple-player")&lt;br /&gt;          self.sound_menu._sound_menu_next = self._sound_menu_next&lt;br /&gt;          self.sound_menu._sound_menu_previous = self._sound_menu_previous&lt;br /&gt;          self.sound_menu._sound_menu_is_playing = self._sound_menu_is_playing&lt;br /&gt;          self.sound_menu._sound_menu_play = self._sound_menu_play&lt;br /&gt;          self.sound_menu._sound_menu_pause = self._sound_menu_pause&lt;br /&gt;          self.sound_menu._sound_menu_raise = self._sound_menu_raise&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;There is one problem though, while I implemented play_next_file() to make it so that when a song finishes, it can go on to the next song, I didn't need to implemented play_previous_file(). A little copy/paste and some tweeking, I added play_previous_file right below play_next_file. It looks like this:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;  def play_previous_file(self):&lt;br /&gt;     #get a reference to the current grid&lt;br /&gt;     grid = self.ui.scrolledwindow1.get_children()[0]&lt;br /&gt;&lt;br /&gt;     #get a gtk selection object from that grid&lt;br /&gt;     selection = grid.get_selection()&lt;br /&gt;&lt;br /&gt;     #get the selected row, and just return if none are selected&lt;br /&gt;     model, rows = selection.get_selected_rows()&lt;br /&gt;     if len(rows) == 0:&lt;br /&gt;         return&lt;br /&gt;&lt;br /&gt;     #calculate the next row to be selected by finding&lt;br /&gt;     #the last selected row in the list of selected rows&lt;br /&gt;     #and decrementing by 1&lt;br /&gt;     prev_to_select = rows[-1][0] -1&lt;br /&gt;&lt;br /&gt;     #if this is not the last row in the last&lt;br /&gt;     #unselect all rows, select the next row, and call the&lt;br /&gt;     #play_file handle, passing in the now selected row&lt;br /&gt;     if prev_to_select != 0:&lt;br /&gt;          selection.unselect_all()&lt;br /&gt;          selection.select_path(prev_to_select)&lt;br /&gt;          self.play_file(self,grid.selected_rows)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;At this point, the Previous and Next buttons work in the Sound Menu, but the Play button doesn't, and no song information is displayed. This is because we've only implemented the part where the Sound Menu tells Simple Player what to do. We have to add a few lines of code so that Simple Player can tell the sound menu things like when it is starting a song, has been paused, etc...&lt;br /&gt;&lt;/insert&gt;&lt;a href="http://4.bp.blogspot.com/-B9GeuQRmhrA/TWMvtgEou3I/AAAAAAAAAac/AFnWXrbZRCo/s1600/Screenshot-31.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/-B9GeuQRmhrA/TWMvtgEou3I/AAAAAAAAAac/AFnWXrbZRCo/s320/Screenshot-31.png" alt="" id="BLOGGER_PHOTO_ID_5576353222364806002" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;insert screenshot=""&gt;We'll start by telling the Sound Menu about new songs. The logical place to do this is at the end of a the play_file function, as this typically means that Simple Player has started a new song, we use the SoundMenuControls object's song_changed() function to let the Sound Menu know there is a new song playing. You can tell the Sound Menu about the song's artist, album, and title. These are all named arguments of song_changed. Simple Player isn't too smart, so only knows the file name of the current song playing, so we'll use that for the title. You'll also need to alert the Sound Menu that the song is playing, using the signal_playing function. Adding the following lines to end of play_file takes take of keeping the Sound Menu up to date:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;            self.sound_menu.song_changed(title = selected_rows[-1]["File"])&lt;br /&gt;          self.sound_menu.signal_playing()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Now the Play/Pause button works, and the song title stays in sync as we make changes. But it's still possible for Simple Player and the Sound Menu to get out of sync. If I pause the song in Simple Player, and then use the Sound Menu, notice that the Sound Menu still thinks the song is playing because Simple Player never told the Sound Menu that the user paused it.&lt;br /&gt;&lt;br /&gt;You may recall when building Simple Player that it was easy to get access the controls for the MediaPlayerBox. So, to finish off the integration, we'll connect to the signal handler for that button, and then tell the Sound Menu when it's been used.&lt;br /&gt;&lt;br /&gt;First, connect to the "toggled" signal for the play button. I added this line to the end of the finish_initializing function:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;          self.player.play_button.connect("toggled",self.play_button_toggled)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then, directly under that, I implemented the play_button_toggled function. This function tests if the widget is active, and informs the Sound Menu of the changed state, as appropriate:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;  def play_button_toggled(self, widget, data=None):&lt;br /&gt;      if widget.get_active():&lt;br /&gt;          self.sound_menu.signal_playing()&lt;br /&gt;      else:&lt;br /&gt;          self.sound_menu.signal_paused()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/insert&gt;&lt;a href="http://3.bp.blogspot.com/-i901HoLSdP4/TWMv4av8pEI/AAAAAAAAAas/dmRC_ojmQPQ/s1600/Screenshot-33.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/-i901HoLSdP4/TWMv4av8pEI/AAAAAAAAAas/dmRC_ojmQPQ/s320/Screenshot-33.png" alt="" id="BLOGGER_PHOTO_ID_5576353409914414146" border="0" /&gt;&lt;/a&gt;&lt;insert screenshot=""&gt;Now the Sound Menu and Simple Player stay in perfect sync!&lt;br /&gt;&lt;br /&gt;New in Natty, the Sound Menu also includes support for playlists. I'm planning to add SoundMenuPlaylists as another class i the sound_menu module. In this way, applicatins such as Simple Player that don't have playlists can just implement the controls part. But other applications could implement the Playlist functionality.&lt;br /&gt;&lt;/insert&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6642286099695430428?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6642286099695430428/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/02/easily-support-sound-menu-in-python.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6642286099695430428'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6642286099695430428'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/02/easily-support-sound-menu-in-python.html' title='Easily Support the Sound Menu in Python'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-MdjfYtYFhps/TWMvt71yfsI/AAAAAAAAAak/oX7YkNlyhJc/s72-c/Screenshot-32.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-3616189329563860846</id><published>2011-02-04T10:54:00.001-08:00</published><updated>2011-02-04T11:16:59.717-08:00</updated><title type='text'>Python Sound Menu Integration or GDBUS Can't Come Soon Enough</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TUxLWBn3_UI/AAAAAAAAAZ8/S1zpD-c9EyQ/s1600/Screenshot-5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 274px; height: 320px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TUxLWBn3_UI/AAAAAAAAAZ8/S1zpD-c9EyQ/s320/Screenshot-5.png" alt="" id="BLOGGER_PHOTO_ID_5569909680915348802" border="0" /&gt;&lt;/a&gt;So I'm still digging &lt;a href="http://theravingrick.blogspot.com/2011/01/pithos-of-rain.html"&gt;Pithos&lt;/a&gt;. Having native access to my Pandora channel on my desktop is a blast. I thought a great enhancement would be Sound Menu integration. So I figured I would spend an hour or two implementing an the &lt;a href="http://www.mpris.org/2.1/spec/"&gt;mpris2 dbus interface&lt;/a&gt; to make this work for Pithos.&lt;br /&gt;&lt;br /&gt;Well, the platform had this to say to me: "Ha ha ha ha ha"&lt;br /&gt;&lt;br /&gt;It seems simple enough, if you implement the Impres2 interface, you get Sound Menu integration, along with other benefits. Well, it turns out that implementing such an interface in Python is an exercise in frustration.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;There is little documentation on how to implement a dbus interface in Python.&lt;/li&gt;&lt;li&gt;So far as I can tell, there is no way to decorate a property to make it a dbus property, and there is not documentation on how to work around this.&lt;/li&gt;&lt;li&gt;After working around how to make properties, one is exposed to all kinds of weird dbus internals. For example, you can't just return a dictionary, you have to send a dbus.Dictionary object.&lt;/li&gt;&lt;/ol&gt;Fortunately, I know people who could help and get me unstuck. So, as of today, I have some stub code that makes something useful show up in the Sound Menu.&lt;br /&gt;&lt;br /&gt;So what's next? Well, I intend to turn this code into a Python API for the Sound Menu. I'm not going to try to handle the whole mpris2 integration at this point, just implement what is needed for the sound menu to work.  The API should not expose any DBUS or MPRIS concepts.&lt;br /&gt;&lt;br /&gt;Then I'll see if I can use my API to add Sound Menu integration to Pithos. Also, I should be able to use &lt;a href="http://theravingrick.blogspot.com/2011/01/quickly-tutorial-for-natty-diy-media.html"&gt;simple-player&lt;/a&gt; as a demo for it.&lt;br /&gt;&lt;br /&gt;So, if you are a Python hacker, and you want to get something into the Sound Menu, stay tuned, I should be able to help you out soonish. The code is still all stub code, but if you absolutely must look, &lt;a href="https://code.edge.launchpad.net/%7Erick-rickspencer3/+junk/sound_menu"&gt;I pushed a branch&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-3616189329563860846?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/3616189329563860846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/02/python-sound-menu-integration-or-gdbus.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3616189329563860846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3616189329563860846'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/02/python-sound-menu-integration-or-gdbus.html' title='Python Sound Menu Integration or GDBUS Can&apos;t Come Soon Enough'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TUxLWBn3_UI/AAAAAAAAAZ8/S1zpD-c9EyQ/s72-c/Screenshot-5.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-916504514646865274</id><published>2011-01-23T09:25:00.000-08:00</published><updated>2011-01-23T11:28:31.961-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Pithos of Rain</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TTxiUTptehI/AAAAAAAAAZw/2KHiRiIPTpo/s1600/Screenshot-24.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TTxiUTptehI/AAAAAAAAAZw/2KHiRiIPTpo/s320/Screenshot-24.png" alt="" id="BLOGGER_PHOTO_ID_5565431340535216658" border="0" /&gt;&lt;/a&gt;During my normal Sunday morning chill out with a cup of coffee this morning, I saw a tweet from &lt;a href="http://twitter.com/#%21/kenvandine/status/29041302804893696"&gt;Ken VanDine&lt;/a&gt; go by about &lt;a href="http://kevinmehall.net/p/pithos/"&gt;Pithos, a native Pandora client for Ubuntu&lt;/a&gt;. I have a Pandora account, and love to use it on my phone, but on Ubuntu I had to go through the Pandora web interface, so I didn't use it as much.&lt;br /&gt;&lt;br /&gt;I'm using it right now, and I'm chuffed. I'd love to see this app go through the ARB process so maverick users can more easily access it. And &lt;s&gt;I'd love to see it&lt;/s&gt; I'm psyched to hear that it is in Universe &lt;s&gt;or&lt;/s&gt; and even Debian for Natty.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-916504514646865274?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/916504514646865274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/01/pithos-of-rain.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/916504514646865274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/916504514646865274'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/01/pithos-of-rain.html' title='Pithos of Rain'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TTxiUTptehI/AAAAAAAAAZw/2KHiRiIPTpo/s72-c/Screenshot-24.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-1248227321417903905</id><published>2011-01-21T15:27:00.000-08:00</published><updated>2011-01-21T15:28:07.283-08:00</updated><title type='text'>The Dash Has Landed</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtdZL-tI/AAAAAAAAAZg/RuUJEHh0tDs/s1600/Screenshot-23.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtdZL-tI/AAAAAAAAAZg/RuUJEHh0tDs/s320/Screenshot-23.png" alt="" id="BLOGGER_PHOTO_ID_5564783060786608850" border="0" /&gt;&lt;/a&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtkKciII/AAAAAAAAAZo/zCeWMctYS6Q/s1600/Screenshot-22.png"&gt;&lt;br /&gt;&lt;/a&gt;User visible changes to Unity have slowed down quite a bit until this week. There have been things like bug fixes landing, and the nm-applet getting indicatorized, and then that getting fixed up. But essentially, Unity has been just the launcher and the panel with indicators for weeks.&lt;br /&gt;&lt;br /&gt;Then this week, 2 important new things landed. First, the beginning of accessibility support. I don't use accessibility support myself, so this dimension is hard for me to assess, but as you can see from this screenshot, Unity is now announcing itself.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtkKciII/AAAAAAAAAZo/zCeWMctYS6Q/s1600/Screenshot-22.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtkKciII/AAAAAAAAAZo/zCeWMctYS6Q/s320/Screenshot-22.png" alt="" id="BLOGGER_PHOTO_ID_5564783062603827330" border="0" /&gt;&lt;/a&gt; It isn't reporting it's objects sufficiently to actually use yet, but I'm bumping up the accessability dimension a tad from zero to account for this project. Also, alt-keys work in application menus now!&lt;br /&gt;&lt;br /&gt;The other thing that landed is the dash. Now you, can see from the above screenshot that it's a crude first cut at the dash, only let's you click a few buttons. However, there is a lot of work underhood to make that show up. So, I expect we'll see search showing up soon! Than after that, there won't be too much work to get the dash up to functional parity with Maverick!&lt;br /&gt;&lt;br /&gt;So, the updated spider diagram shows that Unity is almost covering it's shape from Maverick.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TToPD7bj5YI/AAAAAAAAAZY/V1j4IBI7nNI/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 209px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TToPD7bj5YI/AAAAAAAAAZY/V1j4IBI7nNI/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5564776849736983938" border="0" /&gt;&lt;/a&gt;Hopefully with the introduction of the the dash, the left hand side of the graph should start filling in very quickly.&lt;br /&gt;&lt;br /&gt;Some other notes ...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Launcher&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I bumped up launcher quality because crashers have gone down, but I[m still getting menus out of z-order, which makes them show up behind other windows. This effects all of the system, so it's probably really compiz not the launcher, but it makes the launcher unusable at times. This may be fixed today, but sicne it's intermitent, it's hard to say.&lt;/li&gt;&lt;li&gt;New animations and new backgrounds make the launcher look nicer&lt;/li&gt;&lt;li&gt;The addition of “open new window” and the autohide functions make this close to complete, in fact, I'm wondering what users are really missing now. I guess quick lists and the ability to display status on an icon in the launcher will make it feature complete.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Dash&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;As of today, a panel comes up allowing you to click some big buttons.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;There is not yet any search.&lt;/li&gt;&lt;li&gt;There isn't any animation or mouse over effects and such.&lt;/li&gt;&lt;li&gt;The look of the panel so far is "ok", a bit blocky and such. I expect that the look and feel will be refined quickly.&lt;/li&gt;&lt;li&gt;The panel appears instanty. That's very nice and I hope that doesn't change.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Indicators&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;With the introduction of the nm-applet in indicator form, the indicators seem feature complete.&lt;/li&gt;&lt;li&gt;The indicators also suffer badly from the z-ordering bug I mentioned above.&lt;/li&gt;&lt;li&gt;I'm not sure about the whole thing of hiding menus when the mouse is not over them. It seems like it should bother me, but it really doesn't.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-1248227321417903905?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/1248227321417903905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/01/dash-has-landed.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1248227321417903905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1248227321417903905'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/01/dash-has-landed.html' title='The Dash Has Landed'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TToUtdZL-tI/AAAAAAAAAZg/RuUJEHh0tDs/s72-c/Screenshot-23.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7575693541616634525</id><published>2011-01-09T14:34:00.001-08:00</published><updated>2011-02-04T14:57:49.291-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Quickly Tutorial for Natty: DIY Media Player</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpTk79uMqI/AAAAAAAAAZQ/TLo4xvtX88o/s1600/tintin.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 273px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpTk79uMqI/AAAAAAAAAZQ/TLo4xvtX88o/s320/tintin.png" alt="" id="BLOGGER_PHOTO_ID_5560348583979266722" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;I started working on a chapter for the &lt;a href="https://launchpad.net/ubuntu-developer-manual"&gt;Ubuntu Developers' Manual&lt;/a&gt;. The chapter will be on how to use media in your apps. That chapter will cover:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Playing a system sound&lt;/li&gt;&lt;li&gt;Showing an picture&lt;/li&gt;&lt;li&gt;Playing a sound file&lt;/li&gt;&lt;li&gt;Playing a video&lt;/li&gt;&lt;li&gt;Playing from a web cam&lt;/li&gt;&lt;li&gt;Composing media&lt;/li&gt;&lt;/ul&gt;I created an app for demonstrating some of these things in that chapter. After I wrote the app, I realized that it shows a lot of different parts of app writing for Ubuntu:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Using Quickly to get it all started&lt;/li&gt;&lt;li&gt;Using Glade to get the UI laid out&lt;/li&gt;&lt;li&gt;Using quickly.prompts.choose_directory() to prompt the user&lt;/li&gt;&lt;li&gt;Using os.walk for iterating through a directory &lt;/li&gt;&lt;li&gt;Using a dictionary&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Using DictionaryGrid to display a list&lt;/li&gt;&lt;li&gt;Using MediaPlayerBox to play videos or Sounds&lt;/li&gt;&lt;li&gt;Using GooCanvas to compose a singe image out of images and text&lt;/li&gt;&lt;li&gt;Using some PyGtk trickery to push some UI around&lt;/li&gt;&lt;/ul&gt;A pretty decent amount of overlap with the chapter, but not a subset or superset. So I am writing a more full tutorial to post here, and then I can pull out the media specific parts for the chapter later. Certain things will change as we progress with Natty, so I will make edits to this posting as those occur. So without Further Ado ...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Simple Player Tutorial&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Introduction&lt;/span&gt;&lt;br /&gt;In this tutorial you will build a simple media player. It will introduce how to start projects, edit UI, and write the code necessary to play videos and songs in Ubuntu.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpEwuummLI/AAAAAAAAAWY/9l1LtMTc0Xc/s1600/Screenshot.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpEwuummLI/AAAAAAAAAWY/9l1LtMTc0Xc/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5560332293910206642" border="0" /&gt;&lt;/a&gt;&lt;a href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpEw0uEJNI/AAAAAAAAAWg/DeaZA3rmSW0/s1600/Screenshot-1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpEw0uEJNI/AAAAAAAAAWg/DeaZA3rmSW0/s320/Screenshot-1.png" alt="" id="BLOGGER_PHOTO_ID_5560332295518561490" border="0" /&gt;&lt;/a&gt;The app works by letting the user choose a directory. Simple Player then puts all the media into a list. The user can choose media to play from that list.&lt;br /&gt;&lt;br /&gt;This tutorial uses Quickly, which is an easy and fun way to manage application creation, editing, packaging, and distribution using simple commands from the terminal. Don't worry if you are used to using an IDE for writing applications, Quickly is super easy to use.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Requirements&lt;/span&gt;&lt;br /&gt;This tutorial is for Ubuntu Natty Narwhal (11.04). There are some key differences between 10.10 and 11.04 versions of Quickly and other tools that will make it hard to do the tutorial if you are not on Natty. So, probably best to make sure you are running 11.04.&lt;br /&gt;&lt;br /&gt;You also need Quickly. To install Quickly:&lt;br /&gt;&lt;br /&gt;$sudo apt-get install quickly python-quickly.widgets&lt;br /&gt;&lt;br /&gt;This tutorial also uses a yet to be merged branch of Quickly Widgets. In a few weeks, you can just install quickly-widgets, but for now, you'll need to get the branch:&lt;br /&gt;&lt;br /&gt;$bzr branch lp:~rick-rickspencer3/quidgets/natty-trunk&lt;br /&gt;&lt;br /&gt;Note that these are alpha versions, so there may be bugs.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Caution About Copy and Pasting Code&lt;/span&gt;&lt;br /&gt;In Python, white space is very significant, especially in terms of indentions. In HTML, white space is not. As a result, Blog postings frequently mangle Python code, no matter how carefully a blogger might format it. So while you're following along, be careful about copying and pasting out of here.&lt;br /&gt;&lt;br /&gt;If you're going to copy and paste, you might want to use the code for the tutorial project in launchpad, from this:&lt;br /&gt;&lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/%2Bjunk/simple-player/annotate/head%3A/bin/simple-player"&gt;Link to Code File in the Launchpad Project&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;You can also look at the tutorial in text format this:&lt;br /&gt;&lt;a href="http://bazaar.launchpad.net/%7Erick-rickspencer3/%2Bjunk/simple-player/annotate/head%3A/tutorial/tutorial.txt"&gt;Link to this tutorial in text for in Launchpad&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Creating the Application&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;You get started by creating a Quickly project using the ubuntu-application template. Run this command in the terminal:&lt;br /&gt;$quickly create ubuntu-application simple-player&lt;br /&gt;&lt;br /&gt;This will create and run your application for you.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExOI8LTI/AAAAAAAAAWo/jj2e5gMM31U/s1600/Screenshot-2.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExOI8LTI/AAAAAAAAAWo/jj2e5gMM31U/s320/Screenshot-2.png" alt="" id="BLOGGER_PHOTO_ID_5560332302342171954" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Notice that the application knows it is called Simple Player, and the menus and everything work.&lt;br /&gt;&lt;br /&gt;To edit and run the application, you need to use the terminal from within the simple-player directory that was created. So, change into that directory for running commands:&lt;br /&gt;&lt;br /&gt;$cd simple-player&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Edit the User Interface&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;We'll start by the User Interface with the Glade UI editor. We'll be adding a lot of things to the UI from code, so we can't build it all in Glade. But we can do some key things. We can:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Layout the HPaned that separates the list from the media playing area&lt;/li&gt;&lt;li&gt;Set up the toolbar&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Get Started&lt;/span&gt;&lt;br /&gt;To run Glade with a Quickly project, you have to use this command from within your project's directory:&lt;br /&gt;$quickly design&lt;br /&gt;&lt;br /&gt;If you just try to run Glade directly, it won't work with your project.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExVPLsvI/AAAAAAAAAWw/BS7vglGCMwk/s1600/Screenshot-3.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExVPLsvI/AAAAAAAAAWw/BS7vglGCMwk/s320/Screenshot-3.png" alt="" id="BLOGGER_PHOTO_ID_5560332304247403250" border="0" /&gt;&lt;/a&gt;Now that Glade is open, we'll start out by deleting some of the stuff that Quickly put in there automatically. Delete items by selecting them and hitting the delete key. So, delete:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;label1&lt;/li&gt;&lt;li&gt;image1&lt;/li&gt;&lt;li&gt;label2&lt;/li&gt;&lt;/ul&gt;This will leave you with a nice blank slate for your app:&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExrUQjKI/AAAAAAAAAW4/bMe7EGays-k/s1600/Screenshot-4.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpExrUQjKI/AAAAAAAAAW4/bMe7EGays-k/s320/Screenshot-4.png" alt="" id="BLOGGER_PHOTO_ID_5560332310174272674" border="0" /&gt;&lt;/a&gt;Now, we want to make sure the window doesn't open too small when the app runs. Scroll to the top of the TreeView in the upper right of Glade, and select simple_player_window. Then in the editor below, click the common tab, and set the Width Request and Height Request.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4K7WKQI/AAAAAAAAAXA/c9c1W6V3OEA/s1600/Screenshot-5.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4K7WKQI/AAAAAAAAAXA/c9c1W6V3OEA/s320/Screenshot-5.png" alt="" id="BLOGGER_PHOTO_ID_5560333521250560258" border="0" /&gt;&lt;/a&gt;There's also a small bug in the quickly-application template, but it's easy to fix. Select statusbar1, then on the packing tab, set "Pack type" to "End".&lt;br /&gt;&lt;br /&gt;Save your changes or they won't show up when you try running the app! Then see how your changes worked by using the command:&lt;br /&gt;$quickly run&lt;br /&gt;&lt;br /&gt;A nice blank window, ready for us to party on!&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4Z6jWaI/AAAAAAAAAXI/XcMumG254Do/s1600/Screenshot-6.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4Z6jWaI/AAAAAAAAAXI/XcMumG254Do/s320/Screenshot-6.png" alt="" id="BLOGGER_PHOTO_ID_5560333525273762210" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;Adding in Your Widgets&lt;/span&gt;&lt;br /&gt;The main part of the user interface is going to have an area that divides between the list of media and the media when it is playing. There is widget for that called HPaned (Horizontal Paned). Find HPaned on the toolbox on the left, and click on it to active paint mode. Then click into the second open space in the main part of the window. This will put the HPaned in the window for you.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4vgoNKI/AAAAAAAAAXQ/MkVakrAb2lI/s1600/Screenshot-7.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF4vgoNKI/AAAAAAAAAXQ/MkVakrAb2lI/s320/Screenshot-7.png" alt="" id="BLOGGER_PHOTO_ID_5560333531070608546" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Make sure the HPaned starts out with an appropriate division of space. Do this by going to the General tab, and setting an appropriate number of pixels in Position property.&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF404zeYI/AAAAAAAAAXY/EVcx64Aofyo/s1600/Screenshot-8.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF404zeYI/AAAAAAAAAXY/EVcx64Aofyo/s320/Screenshot-8.png" alt="" id="BLOGGER_PHOTO_ID_5560333532514187650" border="0" /&gt;&lt;/a&gt;The user should be able to scroll through the list, so click on ScrolledWindow in the toolbar, and then click in the left hand part of the HPaned to place it in there.&lt;br /&gt;&lt;br /&gt;Now add a toolbar. Find the toolbar icon in the toolbox, click on it and click in the top space open space. This will cause that space to collapse, because the toolbar is empty by default.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF5clYHBI/AAAAAAAAAXg/JaAUOlTq_Uc/s1600/Screenshot-9.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpF5clYHBI/AAAAAAAAAXg/JaAUOlTq_Uc/s320/Screenshot-9.png" alt="" id="BLOGGER_PHOTO_ID_5560333543170120722" border="0" /&gt;&lt;/a&gt;To add the open button click the edit button (looks like pencil) in Glade's toolbar. This will bring up the toolbar editing dialog. Switch to the Hierarchy tab, and click "Add". This will add a default toolbar button.&lt;br /&gt;&lt;br /&gt;To turn this default button into an open button, first, rename the button to openbutton (this will make it easier to refer to in code). Then under Edit Image set Stock Id to "Open". That's all you need to do to make an open button in Glade.&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGvolL8pI/AAAAAAAAAXo/ZroTV3LdPxc/s1600/Screenshot-10.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGvolL8pI/AAAAAAAAAXo/ZroTV3LdPxc/s320/Screenshot-10.png" alt="" id="BLOGGER_PHOTO_ID_5560334474103485074" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Due to a bug in the current version of Glade, you might need to rename your tool bar button again. When you close the editor, look in the treeview. If the button is still called "toolbutton1", then select it, and use the general tab to change the Name property to "openbutton". Then save again.&lt;br /&gt;&lt;br /&gt;Now if you use $quickly run again, you'll see that your toolbar button is there.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Coding the Media List&lt;br /&gt;&lt;span style="font-size:100%;"&gt;Making the Open Button Work&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;The open button will have an important job. It will respond to a click from the user, offer a directory chooser, and then build a list of media in that directory. So, it's time write some code.&lt;br /&gt;&lt;br /&gt;You can use:&lt;br /&gt;$quickly edit &amp;amp;&lt;br /&gt;&lt;br /&gt;This will open your code Gedit, the default text and code editor for Ubuntu.&lt;br /&gt;&lt;br /&gt;Switch to the file called "simple-player". This is the file for your main window, and the file that gets run when users run your app from Ubuntu.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGv1qF7mI/AAAAAAAAAXw/DtH3hPWd898/s1600/Screenshot-11.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGv1qF7mI/AAAAAAAAAXw/DtH3hPWd898/s320/Screenshot-11.png" alt="" id="BLOGGER_PHOTO_ID_5560334477613723234" border="0" /&gt;&lt;/a&gt;First let's make sure that the open button is hooked up to the code. Create a function to handle the signal that looks like this (and don't forget about proper space indenting in Python!):&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   def openbutton_clicked_event(self, widget, data=None):&lt;br /&gt;       print "OPEN"&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Put this function under "finish_initializing", but above "on_preferences_changed". Save the code, run the app, and when you click the button, you should see "OPEN" printed out to the terminal.&lt;br /&gt;&lt;br /&gt;How did this work? Your Quickly project used the auto-signals feature to connect the button to the event. To use auto-sginals, simple follow this pattern when you create a signal handlder:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def widgetname_eventname_event(self, widget, data=None):&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Sometimes a signal handler will require a different signature, but (self, widget, data=None) is the most common.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-size:100%;"&gt;&lt;span style="font-weight: bold;"&gt;Getting the Directory from the User&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;We'll use a convenience function built into Quickly Widgets to get the directory info from the user. First, go to the import section of the simple-player file, and around line 11 add an import statement:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;from quickly import prompts&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then add to your openbutton_clicked_event function the code to prompt the user so it looks like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def openbutton_clicked_event(self, widget, data=None):&lt;br /&gt;#let the user choose a path with the directory chooser&lt;br /&gt;response, path = prompts.choose_directory()&lt;br /&gt;&lt;br /&gt;#make certain the user said ok before working&lt;br /&gt;if response == gtk.RESPONSE_OK:&lt;br /&gt;  #iterate through root directory&lt;br /&gt;  for root, dirs, files in os.walk(path):&lt;br /&gt;      #iterate through each file&lt;br /&gt;      for f in files:&lt;br /&gt;          #make a full path to the file&lt;br /&gt;          print os.path.join(root,f)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGwfzPRGI/AAAAAAAAAX4/X91VSJiN2q0/s1600/Screenshot-12.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGwfzPRGI/AAAAAAAAAX4/X91VSJiN2q0/s320/Screenshot-12.png" alt="" id="BLOGGER_PHOTO_ID_5560334488926372962" border="0" /&gt;&lt;/a&gt;Now when you run the app you can select a directory, and it will print a full path to each file encountered. Nice start, but what the function needs to do is build a list of files that are media files and display those to the user.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Defining Media Files&lt;/span&gt;&lt;br /&gt;This app will use a simple system of looking at file extensions to determine if files are media files. Start by specifying what file types are supporting. Add this in finish_initializing to create 2 lists of supported media:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.supported_video_formats = [".ogv",".avi"]&lt;br /&gt;self.supported_audio_formats = [".ogg",".mp3"]&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;GStreamer supports a lot of media types so ,of course, you can add more supported types, but this is fine to start with.&lt;br /&gt;&lt;br /&gt;Now change the openbutton handler to only look for these file types:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def openbutton_clicked_event(self, widget, data=None):&lt;br /&gt;#let the user choose a path with the directory chooser&lt;br /&gt;response, path = prompts.choose_directory()&lt;br /&gt;&lt;br /&gt;#make certain the user said ok before working&lt;br /&gt;if response == gtk.RESPONSE_OK:&lt;br /&gt;   #make one list of support formats&lt;br /&gt;   formats = self.supported_video_formats + self.supported_audio_formats&lt;br /&gt;   #iterate through root directory&lt;br /&gt;   for root, dirs, files in os.walk(path):&lt;br /&gt;       #iterate through each file&lt;br /&gt;       for f in files:&lt;br /&gt;           #check if the file is a supported formats&lt;br /&gt;           for format in formats:&lt;br /&gt;               if f.lower().endswith(format):&lt;br /&gt;                   #make a full path to the file&lt;br /&gt;                   print os.path.join(root,f)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;This will now only print out files of supported formats.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Build a List of Media Files&lt;/span&gt;&lt;br /&gt;Simple Player will create a list of dictionaries. Each dictionary will have all the information that is needed to display and play the file. Simple Player will need to know the File name to display to the user, a URI to the file so that the file can be played, and the type of media. So, we'll create a list and add a dictionary to each support type to it.&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def openbutton_clicked_event(self, widget, data=None):&lt;br /&gt;#let the user choose a path with the directory chooser&lt;br /&gt;response, path = prompts.choose_directory()&lt;br /&gt;&lt;br /&gt;#make certain the user said ok before working&lt;br /&gt;if response == gtk.RESPONSE_OK:&lt;br /&gt;   #make one list of support formats&lt;br /&gt;   formats = self.supported_video_formats + self.supported_audio_formats&lt;br /&gt;&lt;br /&gt;   #make a list of the supported media files&lt;br /&gt;   media_files = []&lt;br /&gt;   #iterate through root directory&lt;br /&gt;   for root, dirs, files in os.walk(path):&lt;br /&gt;       #iterate through each file&lt;br /&gt;       for f in files:&lt;br /&gt;           #check if the file is a supported formats&lt;br /&gt;           for format in formats:&lt;br /&gt;               if f.lower().endswith(format):&lt;br /&gt;                   #create a URI in a format gstreamer likes&lt;br /&gt;                   file_uri = "file://" + os.path.join(root,f)&lt;br /&gt;&lt;br /&gt;                   #add a dictionary to the list of media files&lt;br /&gt;                   media_files.append({"File":f,"uri":file_uri, "format":format})&lt;br /&gt;   print media_files&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Display the List to the User&lt;/span&gt;&lt;br /&gt;A DictionaryGrid is the easiest way to display the files, and to allow the user to click on them. So import DicationaryGrid at line 12, like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;from quickly.widgets.dictionary_grid import DictionaryGrid&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Starting in Natty, every window has a ui collection. You can use it to access all of the widgets that you have defined in Glade by their names.  So, creating the list of media files, you can remove any old grids in the scrolled window like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   for c in self.ui.scrolledwindow1.get_children():&lt;br /&gt;       self.ui.scrolledwindow1.remove(c)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then create a new DictionaryGrid. We only want one column, to the view the files, so we'll set up the grid like this:&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   #create the grid with list of dictionaries&lt;br /&gt;   #only show the File column&lt;br /&gt;   media_grid = DictionaryGrid(media_files, keys=["File"])&lt;br /&gt;&lt;br /&gt;   #show the grid, and add it to the scrolled window&lt;br /&gt;   media_grid.show()&lt;br /&gt;   self.ui.scrolledwindow1.add(media_grid)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;So now the whole function looks like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def openbutton_clicked_event(self, widget, data=None):&lt;br /&gt;#let the user choose a path with the directory chooser&lt;br /&gt;response, path = prompts.choose_directory()&lt;br /&gt;&lt;br /&gt;#make certain the user said ok before working&lt;br /&gt;if response == gtk.RESPONSE_OK:&lt;br /&gt;   #make one list of support formats&lt;br /&gt;   formats = self.supported_video_formats + self.supported_audio_formats&lt;br /&gt;&lt;br /&gt;   #make a list of the supported media files&lt;br /&gt;   media_files = []&lt;br /&gt;   #iterate through root directory&lt;br /&gt;   for root, dirs, files in os.walk(path):&lt;br /&gt;       #iterate through each file&lt;br /&gt;       for f in files:&lt;br /&gt;           #check if the file is a supported formats&lt;br /&gt;           for format in formats:&lt;br /&gt;               if f.lower().endswith(format):&lt;br /&gt;                   #create a URI in a format gstreamer likes&lt;br /&gt;                   file_uri = "file://" + os.path.join(root,f)&lt;br /&gt;&lt;br /&gt;                   #add a dictionary to the list of media files&lt;br /&gt;                   media_files.append({"File":f,"uri":file_uri, "format":format})&lt;br /&gt;&lt;br /&gt;   #remove any children in scrolled window&lt;br /&gt;   for c in self.ui.scrolledwindow1.get_children():&lt;br /&gt;       self.ui.scrolledwindow1.remove(c)&lt;br /&gt;&lt;br /&gt;   #create the grid with list of dictionaries&lt;br /&gt;   #only show the File column&lt;br /&gt;   media_grid = DictionaryGrid(media_files, keys=["File"])&lt;br /&gt;&lt;br /&gt;   #show the grid, and add it to the scrolled window&lt;br /&gt;   media_grid.show()&lt;br /&gt;   self.ui.scrolledwindow1.add(media_grid)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now the list is displayed when the user picks the directory.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGwkOaPnI/AAAAAAAAAYA/hAcJbCbSZDc/s1600/Screenshot-13.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGwkOaPnI/AAAAAAAAAYA/hAcJbCbSZDc/s320/Screenshot-13.png" alt="" id="BLOGGER_PHOTO_ID_5560334490114080370" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Playing the Media&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Adding the MediaPlayer&lt;/span&gt;&lt;br /&gt;So now that we have the list of media for the users to interact with, we will use MediaPlayerBox to actually play the media. MediaPlayerBox is not yet integrated into Glade, so we'll have to write code to add it in. As usually, start with an import:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;from quickly.widgets.media_player_box import MediaPlayerBox&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then, we'll create and show a MediaPlayerBox in the finish_initializing function. By default, a MediaPlayerBox does not show it's own controls, so pass in True to set the "controls_visible" property to True. You can also do things like this:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;player.controls_visible = False&lt;br /&gt;player.controls_visible = True&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;to control the visibility of the controls.&lt;br /&gt;&lt;br /&gt;Since we'll be accessing it a lot, we'll create as a member variable in the SimplePlayerWindow class. Then to put it in the right hand part of the HPaned, we use the add2 function (add1() would put it in the left hand part).&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.player = MediaPlayerBox(True)&lt;br /&gt;self.player.show()&lt;br /&gt;self.ui.hpaned1.add2(self.player)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGw5Gca8I/AAAAAAAAAYI/GQ8o3GIEsVg/s1600/Screenshot-14.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpGw5Gca8I/AAAAAAAAAYI/GQ8o3GIEsVg/s320/Screenshot-14.png" alt="" id="BLOGGER_PHOTO_ID_5560334495717813186" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;Connecting to the DictionaryGrid Signals&lt;/span&gt;&lt;br /&gt;Now we need to connect the dictionary_grid's "selection_changed" event, and play the selected media. So back in the openbutton_clicked_event function, after creating the grid, we can connect to this signal. We'll play a file when selection changes, so we'll connect to a play_file function (which we haven't created yet). This goes at the end of the function:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   #hook up to the selection_changed event&lt;br /&gt;   media_grid.connect("selection_changed", self.play_file)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now create that play_file function, it should look like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   def play_file(self, widget, selected_rows, data=None):&lt;br /&gt;       print selected_rows[-1]["uri"]&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Notice that the signature for the function is a little different than normal. When the DictionaryGrid fires this signal, it also passes the dictionaries for each row that is now selected. This greatly simplifies things, as typcially you just want to work with the data in the selected rows. If you need to know more about the DictionaryGrid, it passes itself in as the "widget" argument, so you can just work with that.&lt;br /&gt;&lt;br /&gt;All the function does now is get the last item in the list of selected rows (in Python, you can use -1 as an index to get the last item in a list. Then it prints the URI for that row that we stored in the dictionary back in openbutton_clicked_event.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6JX3oQI/AAAAAAAAAYQ/LqaixebXuHM/s1600/Screenshot-15.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6JX3oQI/AAAAAAAAAYQ/LqaixebXuHM/s320/Screenshot-15.png" alt="" id="BLOGGER_PHOTO_ID_5560335754216317186" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight:bold;"&gt;Setting the URI and calling play()&lt;/span&gt;&lt;br /&gt;Now that we have the URI to play, it's a simple matter to play it. We simply set the uri property of our MediaPlayerBox, and then tell it to stop playing any file it may be playing, and then to play the selected file:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def play_file(self, widget, selected_rows, data=None):&lt;br /&gt; self.player.stop()&lt;br /&gt; self.player.uri = selected_rows[-1]["uri"]&lt;br /&gt; self.player.play()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now users can click on Videos and movies, and they will play. Since we decided to show the MediaPlayerBox's controls when we created it, we don't need to do any work to enable pausing or stopping. However, if you were creating your own controls, you could use player.pause() and player.stop() to use those functions.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6VCPd5I/AAAAAAAAAYY/kDGcOKOelKc/s1600/Screenshot-16.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6VCPd5I/AAAAAAAAAYY/kDGcOKOelKc/s320/Screenshot-16.png" alt="" id="BLOGGER_PHOTO_ID_5560335757346830226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Connecting to the "end-of-file" Signal&lt;/span&gt;&lt;br /&gt;When a media files ends, users will expect the next file played automatically. It's easy to find out when a media file ends using the MediaPlayerBox's "end-of-file" signal. Back in finish_initializing, after creating the MediaPlayerBox, connect to that signal:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.player.connect("end-of-file",self.play_next_file)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight:bold;"&gt;Changing the Selection of the DictionaryGrid&lt;/span&gt;&lt;br /&gt;Create the play_next_file function in order to respond when a file is done playing:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   def play_next_file(self, widget, file_uri):&lt;br /&gt;       print file_uri&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The file_uri argument is the URI for the file that just finished, so that's not much use in this case. There is no particularly easy way to select the next row in a DictionaryGrid. But every widget in Quickly Widgets is a subclass of another PyGtk class. Therefore, you always have access to full power of PyGtk. A DictionaryGrid is a TreeView, so you can write code to select the next item in a TreeView:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def play_next_file(self, widget, file_uri):&lt;br /&gt;#get a reference to the current grid&lt;br /&gt;grid = self.ui.scrolledwindow1.get_children()[0]&lt;br /&gt;&lt;br /&gt;#get a gtk selection object from that grid&lt;br /&gt;selection = grid.get_selection()&lt;br /&gt;&lt;br /&gt;#get the selected row, and just return if none are selected&lt;br /&gt;model, rows = selection.get_selected_rows()&lt;br /&gt;if len(rows) == 0:&lt;br /&gt;   return&lt;br /&gt;&lt;br /&gt;#calculate the next row to be selected by finding&lt;br /&gt;#the last selected row in the list of selected rows&lt;br /&gt;#and incrementing by 1&lt;br /&gt;next_to_select = rows[-1][0] + 1&lt;br /&gt;&lt;br /&gt;#if this is not the last row in the last&lt;br /&gt;#unselect all rows, select the next row, and call the&lt;br /&gt;#play_file handle, passing in the now selected row&lt;br /&gt;if next_to_select &amp;lt; len(grid.rows):&lt;br /&gt;   selection.unselect_all()&lt;br /&gt;   selection.select_path(next_to_select)&lt;br /&gt;   self.play_file(self,grid.selected_rows)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight:bold;"&gt;Making an Audio File Screen&lt;/span&gt;&lt;br /&gt;Notice that when playing a song instead of a video, the media player is blank, or a black box, depending on whether a video has been player before.&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6mTpfFI/AAAAAAAAAYg/v99EHOrMVAw/s1600/Screenshot-17.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6mTpfFI/AAAAAAAAAYg/v99EHOrMVAw/s320/Screenshot-17.png" alt="" id="BLOGGER_PHOTO_ID_5560335761983241298" border="0" /&gt;&lt;/a&gt;It would be nicer to show the user some kind of visualization when a song is playing. The easiest thing to do would be to create a gtk.Image object, and swap it when for the MediaPlayerBox when an audio file is playing. However, there are more powerful tools at our disposal that we can use to create a bit richer of a user experience.&lt;br /&gt;&lt;br /&gt;This section will use a GooCanvas to show you how to compose images and text together. A GooCanvas is a very flexible surface on which you can compose and animate all kinds of 2D experiences for users. This tutorial will just scratch the surface, by combining 2 images and some text together. We'll show the Ubuntu logo image that is already built into your project, but a musical note on top of that for some style, and then put the current song playing as some text.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Create a Goo Canvas&lt;/span&gt;&lt;br /&gt;Naturally, you need to import the goocanvas module:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;import goocanvas&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then, in the finish_initializing function, create and show a goocanvas.Canvas:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.goocanvas = goocanvas.Canvas()&lt;br /&gt;self.goocanvas.show()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The goocanvas will only be added to the window when there is an audio playing file, so don't pack it into the window yet. But let's an image to the goocanvas so we can make sure that we have the system working.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Add Pictures to the GooCanvas&lt;/span&gt;&lt;br /&gt;Add an image to the goocanvas by creating a goocanvas.Image object. First, we'll need to create a gtk.Pixbuf object. You can think of a gtk.Pixbuf as an image stored in memory, but it has a lot of functions to make them easier to work with than just having raw image data. We want to use the file called "background.png". In a quickly project, media files like images and sounds should always go into the data/media directory so that when users install your programs, the files will go to the correct place. There is a helper function called get_media_file built inot quickly projects to get a URI for any media file in the media directory. You should always use this function to get a path to media files, as this function will work even when your program is installed and the files are put into different places on the user's computer. get_media_file returns a URI, but a pixbuf expects a normal path. It's easy to fix this stripping out the beginning of the URI. Since it was created for you, can could also change the way get_media_player works, or create a new function, but this works too:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;logo_file = helpers.get_media_file("background.png")&lt;br /&gt;logo_file = logo_file.replace("file:///","")&lt;br /&gt;logo_pb = gtk.gdk.pixbuf_new_from_file(logo_file)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;You don't actually pass the goocanvas.Image into the goocanvas.Canvas, rather you tell the goocanvas.Image that it's parent is the rootA_items of the goocanvas. You can also set other properties when you create it, such as the x and y coordinates, and of course the pixbuf to use:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;root_item=self.goocanvas.get_root_item()&lt;br /&gt;goocanvas.Image(parent=root_item, pixbuf=logo_pb,x=20,y=20)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Show the GooCanvas When a Song is Playing&lt;/span&gt;&lt;br /&gt;So now we want to take the MediaPlayerBox out of the HPaned when a song is playing and show the goocanvas, and also visa versa. We can easily extract the format of the file because we included it in the dictionary for the row when we created the DictionaryGrid in the openbutton_clicked_event function:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;format = selected_rows[0]["format"]&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;We can also get a reference to the visual that is currently in use:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;current_visual = self.ui.hpaned1.get_child2()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Knowing those two things, we can then figure out whether to put in the goocanvas.Canvas or the MediaPlayerBox. So the whole function will look like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;def play_file(self, widget, selected_rows, data=None):&lt;br /&gt;self.player.stop()&lt;br /&gt;format = selected_rows[0]["format"]&lt;br /&gt;current_visual = self.ui.hpaned1.get_child2()&lt;br /&gt;&lt;br /&gt;#check if the format of the current file is audio&lt;br /&gt;if format in self.supported_audio_formats:&lt;br /&gt;   #if it is audio, see if the current visual is&lt;br /&gt;   #the goocanvas, if it's not, do a swapperoo&lt;br /&gt;   if current_visual is not self.goocanvas:&lt;br /&gt;       self.ui.hpaned1.remove(current_visual)&lt;br /&gt;       self.ui.hpaned1.add2(self.goocanvas)&lt;br /&gt;else:&lt;br /&gt;   #do the same thing for the player&lt;br /&gt;   if current_visual is not self.player:&lt;br /&gt;       self.ui.hpaned1.remove(current_visual)&lt;br /&gt;       self.ui.hpaned1.add2(self.player)&lt;br /&gt;&lt;br /&gt;#go ahead and play the file&lt;br /&gt;self.player.uri = selected_rows[-1]["uri"]&lt;br /&gt;self.player.play()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6-SdUzI/AAAAAAAAAYo/pPukGiM2S6o/s1600/Screenshot-18.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH6-SdUzI/AAAAAAAAAYo/pPukGiM2S6o/s320/Screenshot-18.png" alt="" id="BLOGGER_PHOTO_ID_5560335768420700978" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Add another Image to Canvas&lt;/span&gt;&lt;br /&gt;We can add the note image to the goocanvas.Canvas in the same way we added the background image. However, this time we'll play with the scale a bit:&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;note_file = helpers.get_media_file("note.png")&lt;br /&gt;note_file = note_file.replace("file:///","")&lt;br /&gt;note_pb = gtk.gdk.pixbuf_new_from_file(note_file)&lt;br /&gt;note = goocanvas.Image(parent=root_item, pixbuf=note_pb,x=175,y=255)&lt;br /&gt;note.scale(.75,.6)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Remember for this to work, you have to put a note.png file in the data/media directory for your project. If your image is a different size, you'll need to tweak the x, y, and scale as well.&lt;br /&gt;&lt;br /&gt;(BTW, thanks to &lt;a href="https://launchpad.net/%7Edaniel-p-fore"&gt;Daniel Fore&lt;/a&gt; for making the artwork used here. If you haven't had the pleasure of working Dan, he is a really great guy, as well as a talented artist and designer. He's also the leader of the #elementary project.)&lt;br /&gt;&lt;br /&gt;A goocanvas.Image is a goocanvas.Item. There are different kinds of Items and many of interesting visual things you can do with them. There are items like shapes and paths. You can change things like their scale, rotation, and opacity. You can even animate them!&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH7dD0a3I/AAAAAAAAAYw/clseyUyGs-g/s1600/Screenshot-19.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpH7dD0a3I/AAAAAAAAAYw/clseyUyGs-g/s320/Screenshot-19.png" alt="" id="BLOGGER_PHOTO_ID_5560335776680799090" border="0" /&gt;&lt;/a&gt;&lt;span style="font-weight: bold;"&gt;Add Text to the goocanvas.Canvas&lt;/span&gt;&lt;br /&gt;One kind of goocanvas.Item is goocanvas.Text. You create it like a goocanvas.Image. We won't use any text when we create it, because that will be set later when we are playing a song. Since the goocanvas.Text will be accessed from the play_file function, it should be a member variable for the window. So after adding the note image in the finish_initializing function, you can go ahead and add the text.&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.song_text = goocanvas.Text(parent=root_item,text="", x=5, y=5)&lt;br /&gt;self.song_text.set_property("font","Ubuntu")&lt;br /&gt;self.song_text.scale(2,2)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Update the Text&lt;/span&gt;&lt;br /&gt;The text property of the goocanvas.Text object should then be set when an audio file is played. Add a line of code to do this in the play_file function, after you've determined the file is an audio file:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;   self.song_text.set_property("text",selected_rows[0]["File"])&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now when an audio file is playing the title shows.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Moving the Media Player Controls&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;You've probably noticed a pretty bad bug, when an audio file is playing the user can't access the controls for the media player. Even if that were not the case, are 2 toolbars, one for the controls, and one that only has the openbutton. Also, the controls are shifted over because of the DictionaryGrid, so the time labels are not visible by default.&lt;br /&gt;&lt;br /&gt;Fortunately, PyGtk let's you move widgets around really easily. So, it's possible to write a little code that:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Creates the openbutton in code instead of glade&lt;/li&gt;&lt;li&gt;Takes the toolbar for the MediaPlayer controls out of the MediaPlayer&lt;/li&gt;&lt;li&gt;Inserts the openbutton into the controls exactly where we want it&lt;/li&gt;&lt;li&gt;Adds the controls back into the window&lt;/li&gt;&lt;/ol&gt;To start, go back to Glade, and delete the toolbar you added before. Replace it with an HBox. When prompted, set Number of Items to 1. It should be named hbox1 by default. After adding the HBox choose the packing tab, and set Expand to "No". Otherwise, the HBox will take up all the room it can, making the toolbar huge when you add it back in.&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpJJb-DniI/AAAAAAAAAZA/gXPM7JP0qD4/s1600/Screenshot-20.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpJJb-DniI/AAAAAAAAAZA/gXPM7JP0qD4/s320/Screenshot-20.png" alt="" id="BLOGGER_PHOTO_ID_5560337116417990178" border="0" /&gt;&lt;/a&gt;Then, back in finish_initializing, after creating the MediaPlayerBox, remove the controls:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.player = MediaPlayerBox(True)&lt;br /&gt;self.player.remove(self.player.controls)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then, create a new openbutton:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;open_button = gtk.ToolButton()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;We still want the open button to be a stock button. For gtk.ToolButtons, use the set_stock_id function to set the right stock item.&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;open_button.set_stock_id(gtk.STOCK_OPEN)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Then show the button, and connect it to the existing signal handler.&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;open_button.show()&lt;br /&gt;open_button.connect("clicked",self.openbutton_clicked_event)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;The MediaPlayerBox's controls are a gtk.Toolbar object. So, insert the open_button into the controls using the gtk.Toobar classes insert command. Pass in a zero to tell the gtk.Toolbar to put open_button first. Then you can show the controls, and pack them into the window:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;&lt;br /&gt;self.player.controls.insert(open_button, 0)&lt;br /&gt;self.ui.hbox1.pack_start(self.player.controls, True)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Now users can use the controls even when audio is playing!&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpJJ7T7nPI/AAAAAAAAAZI/QV7J9T3bEnA/s1600/Screenshot-21.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpJJ7T7nPI/AAAAAAAAAZI/QV7J9T3bEnA/s320/Screenshot-21.png" alt="" id="BLOGGER_PHOTO_ID_5560337124831239410" border="0" /&gt;&lt;/a&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;Conclusion&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;This tutorial demonstrated how to use Quickly, Quickly Widgets, and PyGtk to build a functional and dynamic media player UI, and how to use a goocanvas.Canvas to add interesting visual effects to your program.&lt;br /&gt;&lt;br /&gt;The next tutorial will show 2 different ways of implementing play lists, using text files, using pickling, or using desktopcouch for storing files.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:180%;"&gt;&lt;span style="font-weight: bold;"&gt;API Reference&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;PyGtk&lt;br /&gt;&lt;/span&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.pygtk.org/docs/pygtk/"&gt;PyGtk Reference Documentation&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://faq.pygtk.org/index.py?req=index"&gt;PyGtk FAQ&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Quickly Widgets&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;/span&gt;Reference documentation for Quickly Widgets isn't currently hosted anywhere. However, the code is thoroughly documented, so until the docs are hosted, you can use pydocs to view them locally. To do this, first start pydocs on a local port, such as:&lt;br /&gt;$pydocs -p 1234&lt;br /&gt;&lt;br /&gt;Then you can browse the pydocs by opening your web browser and going to &lt;a href="http://www.blogger.com/localhost:1234"&gt;http:localhost:1234&lt;/a&gt;. Search for quickly, then browse the widgets and prompts libraries.&lt;br /&gt;&lt;br /&gt;Since MediaPlayerBox is not installed yet, you can look at the doc comments in the code for the modules in natty-branch/quickly/widgets/media_player_box.py.&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;GooCanvas&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://people.gnome.org/%7Egianmt/pygoocanvas/"&gt;Python GooCanvas Reference&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;GStreamer&lt;/span&gt;&lt;br /&gt;MediaPlayerBox uses a GStreamer playbin to deliver media playing functionality. GStreamer si super powerful, so if you want to do more with it, you can read the docs.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://pygstdocs.berlios.de/pygst-tutorial/playbin.html"&gt;Playbin Documentation&lt;/a&gt; (you can use self.player.playbin to get a reference to the playbin).&lt;/li&gt;&lt;li&gt;&lt;a href="http://pygstdocs.berlios.de/pygst-reference/index.html"&gt;Python GStreamer Reference&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7575693541616634525?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7575693541616634525/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/01/quickly-tutorial-for-natty-diy-media.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7575693541616634525'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7575693541616634525'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/01/quickly-tutorial-for-natty-diy-media.html' title='Quickly Tutorial for Natty: DIY Media Player'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSpTk79uMqI/AAAAAAAAAZQ/TLo4xvtX88o/s72-c/tintin.png' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6680883174144997584</id><published>2011-01-04T20:36:00.000-08:00</published><updated>2011-01-04T20:50:39.951-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><title type='text'>New Quidget: MediaPlayerBox adds video to your app in a few lines</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSP1sGRFt3I/AAAAAAAAAWQ/-8WNeqEt2do/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSP1sGRFt3I/AAAAAAAAAWQ/-8WNeqEt2do/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5558556503050663794" border="0" /&gt;&lt;/a&gt;I've created a new quickly-widget to make it dead simple to add a video or sound file to a Quickly application. All you need to do is create a MediaPlayerBox, add it to your app, set the uri property to tell it what file to play, and call "play()". 5 lines to playing a video:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        self.player = MediaPlayerBox(True)&lt;br /&gt;   self.player.show()&lt;br /&gt;   self.ui.vbox1.pack_start(self.player)&lt;br /&gt;   self.player.uri = file_to_play&lt;br /&gt;   self.player.play()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;By passing in True when I created the MediaPlayerBox, that told it to display controls. You can also control whether controls are displayed by setting the controls_visible property:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        self.player.controls_visible = True&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;It's got other functions that you would expect:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        self.player.pause()&lt;br /&gt;      self.player.stop()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;And other useful properties too. For example, you can get the duration of the current media file, and you can get and set the current position. So you could seek to halfway through the media file like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        dur = self.player.duration&lt;br /&gt;      self.player.position = dur/2&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;MediaPlayerBox is really just a thin wrapper around the gstreamer's playbin, so you still have all the power of gstreamer if you end up needing to go there. You can just use the playbin property and go to town if it comes to it.&lt;br /&gt;&lt;br /&gt;But MediaPlayerBox is focused on simplicity, just getting a video or song playing n your app esasily.&lt;br /&gt;&lt;br /&gt;MediaPlayerBox is &lt;a href="https://code.edge.launchpad.net/%7Erick-rickspencer3/quidgets/natty-trunk"&gt;currently in a branch&lt;/a&gt;, so you can grab it and try it out.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6680883174144997584?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6680883174144997584/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/01/new-quidget-mediaplayerbox-adds-video.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6680883174144997584'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6680883174144997584'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/01/new-quidget-mediaplayerbox-adds-video.html' title='New Quidget: MediaPlayerBox adds video to your app in a few lines'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TSP1sGRFt3I/AAAAAAAAAWQ/-8WNeqEt2do/s72-c/Screenshot.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5225853489763650596</id><published>2011-01-02T13:33:00.000-08:00</published><updated>2011-01-02T13:44:35.898-08:00</updated><title type='text'>Changing the Opacity of gtk.Pixbuf</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSDxtmo1jSI/AAAAAAAAAWI/NrRxljFAko0/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSDxtmo1jSI/AAAAAAAAAWI/NrRxljFAko0/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5557707705943428386" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Photobomb has lacked the ability to set the opacity of images the way it could set the opacity of text and scribbles. This was because goocanvas.Image lacked this capability, and I could not figure out how to make a pixbuf transparent. I looked through all the pygtk docs, all the PIL docs, and all the cairo docs. I tried a bunch of things, and just couldn't make it happen.&lt;br /&gt;&lt;br /&gt;I finally ended up with a solution that works (but is a tad slow on larger images). To start, you need a transparent PNG file loaded into a pixbuf. I do this by storing a transparent PNG image on disk. If I was slicker, I could probably create one out of thin air using the various pixbuf functionalities. You also need a pixbuf that you want to make transparent.&lt;br /&gt;&lt;br /&gt;Anyway, given a transparent pixbuf, this function will set the transparency.&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;    def change_opacity(self, opacity):&lt;br /&gt;     """&lt;br /&gt;     change_opacity - changes the opacity of pixbuf by combining&lt;br /&gt;     the pixbuf with a pixbuf derived from a transparent .png&lt;br /&gt;&lt;br /&gt;     arguments:&lt;br /&gt;     opacity - the degree of desired opacity (between 0 and 255)&lt;br /&gt;&lt;br /&gt;     returns: a pixbuf with the transperancy&lt;br /&gt;&lt;br /&gt;     """&lt;br /&gt;&lt;br /&gt;     trans = self.transparent_img&lt;br /&gt;     width = self.pixbuf.get_width()&lt;br /&gt;     height = self.pixbuf.get_height()&lt;br /&gt;     trans = trans.scale_simple(width,height,gtk.gdk.INTERP_NEAREST)&lt;br /&gt;     self.pixbuf.composite(trans, 0, 0, width, height, 0, 0, 1, 1, gtk.gdk.INTERP_NEAREST, opacity)&lt;br /&gt;     return trans&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;I'm sharing it here in case someone else runs into this and can either use my solution, or offer a better one.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5225853489763650596?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5225853489763650596/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2011/01/changing-opacity-of-gtkpixbuf.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5225853489763650596'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5225853489763650596'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2011/01/changing-opacity-of-gtkpixbuf.html' title='Changing the Opacity of gtk.Pixbuf'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TSDxtmo1jSI/AAAAAAAAAWI/NrRxljFAko0/s72-c/Screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7826006782254453330</id><published>2010-12-29T13:06:00.000-08:00</published><updated>2010-12-29T13:23:21.551-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>This is Photobomb</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TRumbfVZibI/AAAAAAAAAWA/r7oJjhM3sBY/s1600/Screenshot-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TRumbfVZibI/AAAAAAAAAWA/r7oJjhM3sBY/s320/Screenshot-1.png" alt="" id="BLOGGER_PHOTO_ID_5556217556489570738" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The last couple of days I made some good solid progress on Photobomb:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Refactored items on the goocanvas into PhotobombItems to make changes easier, and code easier to maintain.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Fixed the Gwibber tab to pull images from the Gwibber sqlite database.&lt;/li&gt;&lt;li&gt;Removed Python threads from throughout the application while keeping the UI from freezing during long running actions (thanks gobject.idle_add!).&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Added a "download-error" event to UrlFetchProgressBox and handled download errors better.&lt;/li&gt;&lt;/ol&gt;There are still a few things I want to get to, but I am making steady progress.&lt;br /&gt;&lt;br /&gt;Anyway, I was talking to some folks about Photobomb, and they kept referring to it as an "Image Editor". To me, and image editor is used to open an image, modify the image, and save the image. Photobomb is decidedly not that! Photobomb integrates with your social desktop, allowing you to mashup images from your devices, the web, your feeds, and your web cam. Photobomb also lets you share those mashups into your feeds. So, it's a social app.&lt;br /&gt;&lt;br /&gt;Here's a 5 minute video I made to try to help explain Photobomb ...&lt;br /&gt;&lt;iframe title="YouTube video player" class="youtube-player" type="text/html" src="http://www.youtube.com/embed/tRpOZ1yc0Dk" width="480" frameborder="0" height="390"&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7826006782254453330?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7826006782254453330/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/12/this-is-photobomb.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7826006782254453330'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7826006782254453330'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/12/this-is-photobomb.html' title='This is Photobomb'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TRumbfVZibI/AAAAAAAAAWA/r7oJjhM3sBY/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-58532251812720755</id><published>2010-12-27T09:58:00.000-08:00</published><updated>2010-12-27T11:30:46.823-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='photobomb'/><title type='text'>New Personal Goal: Photobomb for Natty</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TRjdvAc2XWI/AAAAAAAAAV4/7fTCnYn5czE/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TRjdvAc2XWI/AAAAAAAAAV4/7fTCnYn5czE/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5555433940006100322" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I'm on holiday for the next week, yeah! I've started filling some of my free time by resurrecting Photobomb (again). There have been some technical improvements to the APIs I've been using, and also, I've learned some ways to do a few things better. I'm hoping that by spending a few hours a day, I can pretty much complete Photobomb by the end of this week, and then work on getting it into Universe for Natty.&lt;br /&gt;&lt;br /&gt;Things I accomplished so far:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I fixed the WebcamBox quidget so that it doesn't hang if you try to tell it play when it hasn't been realized. The effect of this is that I can put the webcam tab on the end of the tabs, much nicer.&lt;/li&gt;&lt;li&gt;Then I went on to complete how the UI is organized. I want Photobomb to work really well on netbooks running Unity in Natty. Since I started Photobomb on Lucid, apps have gotten slightly less horizontal space but  more vertical space. I moved the toolbar from the right, and added it as a tab on the left. Even when making the tabs wider, this reclaimed lots of vertical space.&lt;/li&gt;&lt;li&gt;I added delete and duplicate! So now you can delete or duplicate the selected item. I was going to add cut, copy, and paste, but I think delete and duplicate works better for this app.&lt;/li&gt;&lt;li&gt;When I started Photobomb, I simply created GooCanvas items like goocanvas.Imate, goocanvas.Path, and goocanvas.Text. Each of these types of items interact with different parts of the toolbar slightly differently, so there were lots of places in the code where I had to use code like "if type(self.selected_item) == goocanvas.Path:". Whenever I find myself type checking, I know that subclassing is in my future. Also, goocanvas.Items only let you write to their opacity properties, so for the increase and decrease opacity functions, I've been tracking opacity externally, in a dictionary, rather than as a property on the items. Refactoring was clearly in order before I continued to add features, so I created photobomb_item.py, and added PhotobombImage, PhotobombPath, and PhotobombText, then implemented common properties on each. It was only after doing this that it was reasonable code factoring to create the duplicate function. I'm not quite done this part, though.&lt;/li&gt;&lt;/ol&gt;Here's a quick video showing the new layout and the duplicate function in action:&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/Lq229hPUrz8?hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/Lq229hPUrz8?hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;There's still a lot on my Todo list before I'll consider Photobomb ready for general availability.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Complete the refactoring into PhotobombItems. This will drag me into my first experience with multiple inheritance in Python. I think that it will be simple for this particular application. Every PhotobombItem will derive from PhotobombItem to pick up common properties and functions, but also from the appropriate goocanvas.Item (Image, Path, or Text for now). I'll probably do this one next, as I will use that to fix the opacity controls.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I want to move common editing commands into their own toolbar which is always available. The new found vertical space from Unity provides this luxury.&lt;/li&gt;&lt;li&gt;I use Python threading code in directory tab, and also the web tab. Python Thread code is notoriously difficult, and indeed, there are a number of hangs or situations where Photobomb doesn't quite quit all the way due to threads running and colliding. I will replace threads with a combination of UrlFetchProgressbox, and gobject.timeout_add. In fact, I am considering creating quidgets to handle common asynchronous activities that I have mistakenly used Threads for in the past. Depending on how it goes, I may create DirectoryScannerProgressBox, DictionaryScannerProgressBox, XMLScannerProgressBox, and JSONScannerProgressBox. I would intend for these to work in very similar ways, but make it super simple to perform long running tasks without blocking the UI or resorting the Threads.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I will change the Gwibber page to use libgwibber, and also the poster button to use libgwibber.&lt;/li&gt;&lt;li&gt;I'll make the webcam tab present the webcam image on a button instead of using a separate button. This will be more consistent with the other tabs.&lt;/li&gt;&lt;li&gt;I want to add an undo/redo stack (which should be lots easier after I'm done with the PhotobombItem refactoring).&lt;/li&gt;&lt;li&gt;For the toolbar tab, I didn't really do any code refactoring, I just grabbed the table of buttons, removed them from the main window, and packed them into the tab. I should really do a proper job of making the toolbox into a proper widget that rips signals. In this way, the UI will become much easier to modify in the future, and generally the photobomb code will become reusable.&lt;/li&gt;&lt;li&gt;Since Photobomb has no preferences, I may as well remove the preferences code. All it does it start up desktopcouch and then not use it.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Finally, the global menu means I can add in a menu bar without sacrificing any vertical space. This will have a few benefits. It will mean users can access the toolbar functions without changing to the toolbar tab, it will be way easier for me to add key commands for the functions, and I can add certain functions only to the menu. For example, the export and microblog commands may be better hanging out on the file menu, rather than be part of the toolbar.&lt;/li&gt;&lt;/ol&gt;If you want to play with latest Photobomb, &lt;a href="https://code.edge.launchpad.net/photobomb"&gt;get it from trunk&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-58532251812720755?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/58532251812720755/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/12/new-personal-goal-photobomb-for-natty.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/58532251812720755'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/58532251812720755'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/12/new-personal-goal-photobomb-for-natty.html' title='New Personal Goal: Photobomb for Natty'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TRjdvAc2XWI/AAAAAAAAAV4/7fTCnYn5czE/s72-c/Screenshot.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4745883611800329026</id><published>2010-12-10T06:24:00.000-08:00</published><updated>2010-12-10T06:36:48.362-08:00</updated><title type='text'>Hot New Quickly Template Features</title><content type='html'>The Quickly Ubuntu Application template for Natty has cool new features already. Here's a couple of videos to highlight these changes.&lt;br /&gt;&lt;br /&gt;Here are some changes contributed by &lt;a href="https://edge.launchpad.net/%7Emterry"&gt;Mike Terry&lt;/a&gt;. What he's done is:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Reduce the code in the generate boiler plate down to about 65 lines. This makes it a lot easier to hack on and maintain.&lt;/li&gt;&lt;li&gt;Make it so that you don't have to use the builder to access widgets that you've added in glade. Instead of "self.builder.get_object("entry1").set_text("hi"), you can just do self.ui.entry.set_text("hi"). In other words, Quickly just knows.&lt;/li&gt;&lt;li&gt;The third thing demoed here is the new auto signals feature. Instead of picking a signal handler name, and defining it in Glade and in your code, if you follow some simple conventions when you name your function, Quickly will just know it's a signal handler, and it will work.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/u60KtTbaBQo?hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/u60KtTbaBQo?hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;In this second video, you can see the much much much improved preferences system contributed by &lt;a href="https://edge.launchpad.net/%7Etony-badwolf"&gt;Tony Byrne&lt;/a&gt;. By following some naming conventions, and adding 2 lines of code, you can persist and access user settings with ease.&lt;br /&gt;&lt;br /&gt;&lt;object width="425" height="344"&gt;&lt;param name="movie" value="http://www.youtube.com/v/4cmGO1WtG3o?hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/4cmGO1WtG3o?hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="425" height="344"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4745883611800329026?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4745883611800329026/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/12/hot-new-quickly-template-features.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4745883611800329026'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4745883611800329026'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/12/hot-new-quickly-template-features.html' title='Hot New Quickly Template Features'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6730111437539541816</id><published>2010-12-10T04:01:00.001-08:00</published><updated>2010-12-10T04:23:54.954-08:00</updated><title type='text'>Alpha 1 Unity Checkpoint</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TQIXLMCKyEI/AAAAAAAAAVs/TpIUcsPvfOU/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 230px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TQIXLMCKyEI/AAAAAAAAAVs/TpIUcsPvfOU/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5549023171850192962" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;While attending a sprint this week, I've gone ahead and updated my Dell mini 10v to Ubuntu 11.04 Alpha 1, and also took some updates on Tuesday.&lt;br /&gt;&lt;br /&gt;I have to say, I am finding Unity to be running really really well on this computer. I can use the spider diagram to provide a more structured description to what I think is going well, and also, how far we still have to go before we have a shippable product.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Up&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Appeal (6-7): The Launcher looks a lot nice n autohide mode.&lt;/li&gt;&lt;li&gt;Launcher Functionality (3-6): Significantly improved now that the launcher includes autohide and quicklists allow me to pin and unpin apps to the launcher. It's still too hard to use with workspaces, but code for that landed this week, so next week, I hope to pop this up to 7! Jason is absolutely rocking the Launcher functionality.&lt;/li&gt;&lt;li&gt;Application Window Management (5-6): Little tick marks in the launcher items show how many windows are open. Meta-W shows all the windows in a workspace. I think they should add an item for Meta-W to the launcher. That would make it really easy and discoverable to find your apps.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Down&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Indicator Quality (6-4): Starting this week, there has been these times when the indicators stop responding to mouse clicks, or become don't activate for a really long time.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Unchanged&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Performance (8): The launcher is even faster on my Dell mini 10v than on my desktop at home. I assume this is because I use a computer with a shared memory graphics chip (i965) for my desktop, but with a large screen. So the combination laptop graphics chip with desktop screen is not ideal. However, it's still plenty fast and snappy on my desktop. I'm tempted to pop this up to 9, but I want to see how it's working for folks with proprietary drivers first.&lt;/li&gt;&lt;li&gt;Application Launch Time (8): Nothing seems to have changed here.&lt;/li&gt;&lt;li&gt;Everything dash related. I could pop up Find and Launch apps since they tied the Ubuntu button to the Applications directory as a temporary measure until the dash is ready. However, I think I'll just wait until it's their real deal.&lt;/li&gt;&lt;li&gt;Indicator functionality hasn't changed, expect I hear there is code in trunk to bring back keyboard access and navigation! This has been a real productivity killer for me, so I can't wait until next week.&lt;/li&gt;&lt;li&gt;Global Menu Integration. However, I hear that there is a team working on Mozilla/Unity integration. I'll also be looking forward to when they can turn the shotwell menus back to the global menus.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6730111437539541816?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6730111437539541816/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/12/alpha-1-unity-checkpoint.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6730111437539541816'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6730111437539541816'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/12/alpha-1-unity-checkpoint.html' title='Alpha 1 Unity Checkpoint'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TQIXLMCKyEI/AAAAAAAAAVs/TpIUcsPvfOU/s72-c/Screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2999280969345942553</id><published>2010-12-02T10:13:00.000-08:00</published><updated>2010-12-02T14:17:05.675-08:00</updated><title type='text'>Log those Unity Crash Reports!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://www.nacinc.com/did-you-know/crash-test-dummies-have-helped-save-numerous-lives-over-the-years/testdummy.jpg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 570px; height: 424px;" src="http://www.nacinc.com/did-you-know/crash-test-dummies-have-helped-save-numerous-lives-over-the-years/testdummy.jpg" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Natty Alpha 1 is baked and available!&lt;br /&gt;&lt;br /&gt;This alpha is exciting to me because the Unity Launcher is already pretty usable. I'm using it daily, and am already addicted. The move to the new compiz has really really sped things up. Of course, I can't recommend to an average user that they rely daily on alpha software. This is fresh code, and it stresses more fresh code in the graphics stack. This means crashers.&lt;br /&gt;&lt;br /&gt;However, you don't have to be a hacker to really help get Unity into shape stating right now. A great way to help Ubuntu and the Unity team right now is to get them as many crash reports as we can, so they can these issues fixed asap and move on to adding those features we are so much waiting for.&lt;br /&gt;&lt;br /&gt;Way 1: Tonight, before the packages get updated, run &lt;a href="http://cdimage.ubuntu.com/releases/natty/alpha-1/"&gt;Ubuntu Alpha&lt;/a&gt; 1 from a live session when it become available. If there are crashers, Apport, the crash report system, will kick in and help you log crash reports easily. If you can't get to it right away, no problems, but you'll want to use &lt;a href="http://cdimages.ubuntu.com/daily-live/current/"&gt;the current daily build&lt;/a&gt; to make sure you are testing up to date packages.&lt;br /&gt;&lt;br /&gt;Way 2: Install Alpha 1 on a test machine. Enable Apport, and start upload crash reports when there are crashers. It's really easy to do, &lt;a href="https://wiki.ubuntu.com/Unity"&gt;instructions are on the wiki&lt;/a&gt;. If you go this route, you can update daily and help by reporting new crashers as you encounter them.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2999280969345942553?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2999280969345942553/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/12/log-those-unity-crash-reports.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2999280969345942553'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2999280969345942553'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/12/log-those-unity-crash-reports.html' title='Log those Unity Crash Reports!'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-690098638604597613</id><published>2010-11-29T15:57:00.000-08:00</published><updated>2010-11-29T16:35:33.198-08:00</updated><title type='text'>Unity in Natty Evaluation 1</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TPQ_F0qxvMI/AAAAAAAAAVk/NuUUuRwfoUA/s1600/Unity%2BSpider%2B1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 224px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TPQ_F0qxvMI/AAAAAAAAAVk/NuUUuRwfoUA/s320/Unity%2BSpider%2B1.png" alt="" id="BLOGGER_PHOTO_ID_5545126410469883074" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Today I am finally finding Natty to be usably stable. So long as I stay away from Open Office, it seems to be running quite fine. So, I updated my&lt;a href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbkQFjEEOI/AAAAAAAAAVc/gfgf1dCNj14/s1600/spider.png"&gt; maverick spider diagram&lt;/a&gt; in an attempt to capture where I think Unity is in the journey to being the Ubuntu desktop.&lt;br /&gt;&lt;br /&gt;In this first natty diagram, yellow is the target, blue is maverick, and that orangy color is my subjective assessment of Unity as it is today. You can review the criteria that I chose from assessment in a &lt;a href="http://theravingrick.blogspot.com/2010/10/this-is-it.html"&gt;previous post&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Note that there is a large portion of the diagram missing. Everything related to the Dash is zero, because none of that is implement yet. Here is a summary of why I picked these values for the other items:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Natty &gt; Maverick&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Performance (3 to 8): The natty launcher feels snappy when you drag the items, and when the launcher overflow and "folds" items, the unfolding and access to those items is also totally responsive. This is a big improvement!&lt;/li&gt;&lt;li&gt;Application Launch Time(4 to 8): I was told a few times that apps didn't start any slower under Mutter. However, they sure always seemed to. Well, they seem to start nice and fast now, so I'm saying that application launch time is nice and fast.&lt;/li&gt;&lt;li&gt;File Management (3 to 6): The launcher features a nice big Nautilus item at the top of the launcher. This makes access to typical file management tasks much easier accomplish, because you can find them. Since there is no dash, there is no integration with dash and that search yet, so hopefully this particular area will improve.&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Maverick &gt; Natty&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Quality (5 to 3): I'm probably being really unfair here, but I'm blaming all the crashiness, especially when I'm using OOo on the Launcher. It's probably really Compiz itself, but subjectively, it feels like the launcher is the new thing, so I'm blaming that.&lt;/li&gt;&lt;li&gt;Launcher Functionality (6 to 3): Without quick lists, this took an big hit. You can really only launch and switch now. Adding back in the quick lists, and then getting a hiding launcher, and we'll be in business.&lt;/li&gt;&lt;li&gt;Application Window Management (5 to 4): Not much has really changed here, except that without the quick lists there is no where to click on different windows from the same app. I use Alt-Tab, so this doesn't much impact me, personally. Minimized windows still disappear with no way to get them back.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Application Window Management:&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Natty == Maverick&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Appeal (6): The launcher is still a bit "blocky". I do like how the items look, and the attention getting animations as well. I expect to see lots of improvements to the look and feel of the launcher, especially when they start adding in some of the animations and transitions.&lt;/li&gt;&lt;li&gt;Global Menu Integration (5): I don't see any changes to this part. Firefox still has it's own menu, for example.&lt;/li&gt;&lt;li&gt;Indicator Appeal (7): I don't see any changes here, either. I expect to see some slightly tweaked icons and maybe and improvements to the look of the indicators themselves.&lt;/li&gt;&lt;li&gt;Indicator Performance (8): No changes here.&lt;/li&gt;&lt;li&gt;Indicator Functionality (6): No changes today, but I hear that the new indicator for the network manager was supposed to land today. That will mean that all the default functionality will be managed by indicators, so no more panel applets by default.&lt;/li&gt;&lt;/ul&gt;I also just noticed that key nav between the indicators isn't working! grrrr. It's also not working for the global menu. Well, Accessibility is already rated zero, so there's really no way to change my ratings based on that, unless I added it to "Indicator Functionality". I'll see how annoying it is to me, and maybe change it then.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-690098638604597613?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/690098638604597613/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/11/unity-in-natty.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/690098638604597613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/690098638604597613'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/11/unity-in-natty.html' title='Unity in Natty Evaluation 1'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TPQ_F0qxvMI/AAAAAAAAAVk/NuUUuRwfoUA/s72-c/Unity%2BSpider%2B1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6903244152556820274</id><published>2010-11-24T09:50:00.000-08:00</published><updated>2010-11-24T11:08:28.217-08:00</updated><title type='text'>Ubuntu is *not* moving to a rolling release</title><content type='html'>So, I just got asked to provide a statement about whether Ubuntu is moving to a rolling release. The statement I provided was:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;"Ubuntu is not changing to a rolling release. We are confident that our customers, partners, and the FLOSS ecosystem are well served by our current release cadence. What the article was probably referring to was the possibility of making it easier for developers to use cutting edge versions of certain software packages on Ubuntu.This is a wide-ranging project that we will continue to pursue through our normal planning processes."&lt;br /&gt;&lt;/blockquote&gt;The reason that I was asked to comment on this was that &lt;a href="http://www.theregister.co.uk/2010/11/23/darily_ubuntu_updates/"&gt;an article appeared this morning&lt;/a&gt; that built off of some comments that Mark made regarding daily builds. The article went on to make some reasonable conjecture about possible implications. Links to this article were then labeled things like "Ubuntu Moves to Rolling Releases!"&lt;br /&gt;&lt;br /&gt;I do like the idea of making it easier for developer and enthusiasts to use daily builds of software that they really care about, and maybe even have them discover this capability through the software center. Currently you have to find PPAs or maybe activate backports to do this. But having a stable release with a six month cadence plus the option to stay cutting edge on certain packags (at your own risk) is not really a rolling release.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6903244152556820274?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6903244152556820274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/11/ubuntu-is-not-moving-to-rolling-release.html#comment-form' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6903244152556820274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6903244152556820274'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/11/ubuntu-is-not-moving-to-rolling-release.html' title='Ubuntu is *not* moving to a rolling release'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2969744014577916353</id><published>2010-11-19T13:56:00.000-08:00</published><updated>2010-11-19T15:19:26.259-08:00</updated><title type='text'>3 Weeks In</title><content type='html'>I was about half of last week, and this Monday as well. So I didn't do a week 2 update. Again,, please note that these are things that are happening that I found interesting or important, or was otherwise involved in. This is not the status of work that I personally did. Here's my perspective on week 3 ...&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Unity&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Unity landed in Natty (today actually).&lt;/li&gt;&lt;li&gt;I installed Unity and turned it on in the Compiz settings. I was able to play a bit with the launcher, and it was very snappy, and it launched apps. Places is still not working. Compiz in general seems quite snappy.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Unfortunately running Unity caused so much crashing, that I was unable to evaluate it for a spider diagram again. &lt;/li&gt;&lt;li&gt;A &lt;a href="https://lists.ubuntu.com/archives/ubuntu-devel/2010-November/032078.html"&gt;decision was taken&lt;/a&gt; to focus Unity on for Natty, and not work on a Maverick PPA. Based on my experience with Unity today, this was a sane decision in my estimation.&lt;/li&gt;&lt;li&gt;I checked in with Luke and Gary regarding Unity accessibility. Luke is still working on getting his head around gobjects and such.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Hopefully enough stability in Unity that we can really evaluate it.&lt;/li&gt;&lt;li&gt;Luke to start with some accessibility prototypes.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Testing&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;The QA team has started putting&lt;a href="http://reports.qa.ubuntu.com/reports/desktop-testing/natty/"&gt; automated integration tests&lt;/a&gt; into place. These tests will try to catch bugs in the way applications work within Unity very quickly and frequently.&lt;/li&gt;&lt;li&gt;We started planning a sprint, where a few Canonical folks will get together for the first week of December and focus on tests for kernel SRU validation, as well as for desktop integration.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;More tests written and running.&lt;/li&gt;&lt;li&gt;Better definition for sprint goals.&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Contributors&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Finalized plans for &lt;a href="https://wiki.ubuntu.com/UbuntuDevelopment/CodeReviews"&gt;"Patch Pilot" Program.&lt;/a&gt; Informed Canonical Engineers about the new process and schedule.&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Start of Patch Piloting schedule.&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Software Center&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Plans for New Apps in Maverick were finalized. Will be installed in /opt/extras.ubuntu.com. Python apps will lack per-compiled byte code if installed in this way for Maverick, but there should be a better solution in Natty.&lt;/li&gt;&lt;li&gt;didrocks landed changes in quickly to support installing to /opt in Natty.&lt;/li&gt;&lt;li&gt;mterry landed changes to the Quickly template to make it easier to attach to events (no more defining signal handler in Glade).&lt;/li&gt;&lt;li&gt;I'm not sure about the status of ratings and reviews. Since it's mvo driving, I assume all is well.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; ???&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Misc.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Schedule-wise, there is one week until Feature definition freeze as of yesterday. So this week we asked all teams to have work items identified by eod yesterday so that we could finalize plans by the Feature Definition Freeze. You can see &lt;a href="https://blueprints.edge.launchpad.net/ubuntu/natty"&gt;all the blueprints for natty&lt;/a&gt;. I summarized how some of the &lt;a href="https://wiki.ubuntu.com/Natty/PlanningSummary"&gt;Essential and High importance blueprints &lt;/a&gt;will effect the Editions as well. Of course there will be lots of Medium and Low blueprints that have a lot of impact too!&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Release meetings started again, with new format and agenda thanks to skaet.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Next Thursday is Thanks Giving holiday in the US, so I'm expecting about 60% of normal productivity in Ubuntu.&lt;/li&gt;&lt;li&gt;Work items should be identified for Alpha 2, so I will prepare a detailed review and ensure that items are prioritized appropriately heading into the A2 milestone.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2969744014577916353?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2969744014577916353/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/11/3-weeks-in.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2969744014577916353'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2969744014577916353'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/11/3-weeks-in.html' title='3 Weeks In'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-3211267992765691653</id><published>2010-11-05T10:36:00.000-07:00</published><updated>2010-11-05T13:04:58.190-07:00</updated><title type='text'>1 Week In</title><content type='html'>Back from UDS for a week now. Here's a progress update on some of the happenings in Ubuntu that I am tracking. There are, of course, myriad other aspects to the product that I care about, but that I am not following closely. You may notice a strong correspondence between these areas and &lt;a href="http://theravingrick.blogspot.com/2010/10/this-is-it.html"&gt;The 5 Foci&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Of course I am doing almost none of this work personally. When it makes sense I'll credit the specific people and/or teams. Questions and comments welcome, of course. I have no idea if this kind of thing is useful to folks. But I wonder if I can keep up a weekly cadence of updates.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Unity&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Dx team is working to get the Compiz based experience ready for testing&lt;/li&gt;&lt;li&gt;Nothing new uploaded this week, so no update to the spider diagram :(&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;I hope to start testing new compiz-based experience and update the spider diagram&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Testing&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; ara has confirmed that mago will work with apps under unity so it's possible to start writing &lt;a href="http://reports.qa.ubuntu.com/reports/desktop-testing/maverick/unity/latest/"&gt;integration tests &lt;/a&gt;&lt;/li&gt;&lt;li&gt; Unity integration testing is blocked this week on looking into the global menu because of a bug in the dumper tool, but it should be &lt;a href="https://wiki.ubuntu.com/Unity/QA/StateIntrospection"&gt;unblocked next week&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt; Server team outlined steps for packaging Hudson&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Switch Unity Integration testing on Natty&lt;/li&gt;&lt;li&gt; Start teseting the global menu on Unity (this is important because there are lots of bugs their in Maverick)&lt;/li&gt;&lt;li&gt; Get more tests in place and running daily&lt;/li&gt;&lt;li&gt; Progress on packaging and deploying Hudson&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Contributors&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Created &lt;a href="https://wiki.ubuntu.com/UbuntuDevelopment/CodeReviews"&gt;documentation for Path Pilots &lt;/a&gt;(a tweak to our system for responding to contributors)&lt;/li&gt;&lt;li&gt; Straw man schedule for Ubuntu Engineering Engineers set up (these are engineers paid by Canonical to work on Ubuntu)&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Share Patch Piloting schedule with Ubuntu Engineering Engineers, let them change it around to suit self&lt;/li&gt;&lt;li&gt; Ensure documentation works for Patch Pilots&lt;/li&gt;&lt;li&gt; Pick a date to start with the tweaked system&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Software Center&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; I followed up on UDS session regarding using /opt for installing new apps on a stable release, and drafted a proposal to the App Review Board that they ask the Tech Board to remove the /opt requirement for Maverick, since there are so few apps we can really review them carefully, and the technology is not there to support /opt properly in Maverick&lt;/li&gt;&lt;li&gt; didrocks broke out work items to ensure that Python build system and Quickly will support installing to /opt in the future&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Perhaps see some progress by didrocks on making the Python build system work with /opt&lt;/li&gt;&lt;li&gt; First meeting regarding Donations with IS, I requested that the notes from the call be posted to a mailing list or such&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Misc&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;This Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; I confirmed with cjwatson that we can make good progress on &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/foundations-m-grub2-boot-framebuffer"&gt;"Text Free Boot" in Natty&lt;/a&gt;. It won't surprise anyone to know that I had trouble grocking the blueprint :)&lt;/li&gt;&lt;li&gt; Open stack is all packaged up and ready for better integration with Ubuntu Server&lt;/li&gt;&lt;li&gt; Set schedule to get to Feature Definition Freeze with the Ubuntu Engineering Managers (All Blueprints Accepted by 11/11, All Work Items Identified by 11/18)&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-style: italic;"&gt;Next Week&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt; Perhaps create a system for getting feedback on which platforms can boot Text Fee so we can start to assemble the white or blacklist&lt;/li&gt;&lt;li&gt; Some server team members to attend Open Stack Design Summit next week&lt;/li&gt;&lt;li&gt; pitti to reset work item tracker to start burndowns for teams that are ready&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-3211267992765691653?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/3211267992765691653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/11/1-week-in.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3211267992765691653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/3211267992765691653'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/11/1-week-in.html' title='1 Week In'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4049324686892521341</id><published>2010-10-26T07:19:00.000-07:00</published><updated>2010-10-26T07:28:24.668-07:00</updated><title type='text'>This is it!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbj0KIE7gI/AAAAAAAAAVU/4CGr1C-2FKs/s1600/2010-10-26-092709.jpg"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 256px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbj0KIE7gI/AAAAAAAAAVU/4CGr1C-2FKs/s320/2010-10-26-092709.jpg" alt="" id="BLOGGER_PHOTO_ID_5532359677482167810" border="0" /&gt;&lt;/a&gt;This is it! Porting the Unity view to Compiz, comibing the Desktop and UNE editions, and defaulting to UNE for users who can run it. This is is a huge opportunity for the Ubuntu community to make something that can deliver free desktops to millions and millions of people who don't have software freedome today. And also, having a lot of fun with our friends doing something really big along the way.&lt;br /&gt;&lt;br /&gt;There is also a big risk here. We *have* to make Natty "really good". But what does "really good" mean? I guess this is inherently subjective, so here's my take on it. "Really good" is multi-faceted, and for me, I think about Unity "goodness" in the following dimensions.&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Launcher Performance - Is the launcher snappy and responive?&lt;/li&gt;&lt;li&gt;Launcher Quality - Is the launcher robust and not buggy?&lt;/li&gt;&lt;li&gt;Launcher Appeal - Is the launcher visually beautiful and are the interactions fun?&lt;/li&gt;&lt;li&gt;Launcher Functionality - Does the Launcher have the essential functions that users need?&lt;/li&gt;&lt;li&gt;Find and Launch Apps - Can users easily launcher their apps?&lt;/li&gt;&lt;li&gt;File Management - Can users find their files and operate on them as desiered?&lt;/li&gt;&lt;li&gt;Search - Does the Unity search work well for users?&lt;/li&gt;&lt;li&gt;Dash Appeal - Is Places visually beautiful and are the interactions fun?&lt;/li&gt;&lt;li&gt;Dash Performance - Is places snappy and responsive?&lt;/li&gt;&lt;li&gt;Dash Quality - Is places robust and not buggy?&lt;/li&gt;&lt;li&gt;Indicator Functionality - Do the indicators have the essential functions users need?&lt;/li&gt;&lt;li&gt;Indicator Quality - Are the indicators robust and not buggy?&lt;/li&gt;&lt;li&gt;Indicator Performance - Are the indicators snappy and responsive?&lt;/li&gt;&lt;li&gt;Indicator Appeal - Are the idnicators visually beautiful and are the interactions fun?&lt;/li&gt;&lt;li&gt;Global Menu Integration - Are applications across the desktop working well with the Global Menu?&lt;/li&gt;&lt;li&gt;Application Launch Time - Do applications seem to launch quickly and smoothly?&lt;/li&gt;&lt;li&gt;Application Window Management - Can users position windows, and use desktops as desired?&lt;/li&gt;&lt;li&gt;Accessibility - Is the toolkit accessible to screen readers, is the functionality of the desktop universally available?&lt;/li&gt;&lt;/ul&gt;So, after thinking about all these different facets, I hit on the idea of represent the current state on the target state in a spider diagram.  In the following diagram, the blue shape is the current shape of Unity (in Maverick) and the Red shape is where I think Unity needs to get to in Natty. I made this by grading each facet on scale of 1-10 in an &lt;a href="http://ubuntuone.com/p/MLU/"&gt;Open Office Spreadsheet&lt;/a&gt;. Again, in a subjective manner.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbkQFjEEOI/AAAAAAAAAVc/gfgf1dCNj14/s1600/spider.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 223px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbkQFjEEOI/AAAAAAAAAVc/gfgf1dCNj14/s320/spider.png" alt="" id="BLOGGER_PHOTO_ID_5532360157289517282" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;So, clearly, there is a lot of work to do to get Unity "into shape". And, of course, we still need to be awesome maintainers. We need to keep up with upstreams, fix bugs, respond to users, etc... This part of our work has the further complication that the Server Edition is starting to get some more serious usage, so we need to ensure that we're doing the right things for server as well.&lt;br /&gt;&lt;br /&gt;So, how do we be great maintainers, while getting Unity into great shape? Well, for the Ubuntu Engineering Team (of which I am the Director), the answer is to focus. I will be asking each team to limit their work items so that they can have plenty of bandwidth open to help with bug finding, bug fixing, enhancing performance, etc... whatever it takes to change the shape of the spider diagram.&lt;br /&gt;&lt;br /&gt;Here are the 5 foci for Ubuntu engineering:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Unity - Well, not much more to say what I mean here.&lt;/li&gt;&lt;li&gt;2D experience - Not all users will have hardware to run Unity. For these users, we must ensure that Ubuntu continues to work really really for them, just as well as Maverick works, and just as well as Unity in Natty works.&lt;/li&gt;&lt;li&gt;Software Center - This an important area for us to continue to make innovation and progress in order to meet the needs and expectations of users and software developers. So, this is the one area of actual feature development that is staying on the list of focus areas.&lt;/li&gt;&lt;li&gt;Testing - Ubuntu Engineering needs to become a testing organization. I want to see automated itnegration testing for Unity, and I want to see it starting soon.&lt;/li&gt;&lt;li&gt;Contributions - This is possibly the most important part. We need to step up our responsiveness to existing core contributors and new contributors. We should be implementing fewer features ourselves, but we should be ensuring a really great and personal experience for people who contribute patches, apps, etc... So, Ubuntu Engineers should be signing up for fewer features, but more time for contributors.&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4049324686892521341?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4049324686892521341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/10/this-is-it.html#comment-form' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4049324686892521341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4049324686892521341'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/10/this-is-it.html' title='This is it!'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TMbj0KIE7gI/AAAAAAAAAVU/4CGr1C-2FKs/s72-c/2010-10-26-092709.jpg' height='72' width='72'/><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5083359351363848377</id><published>2010-09-26T17:21:00.000-07:00</published><updated>2010-09-26T19:27:48.338-07:00</updated><title type='text'>$quickly create ubuntu-pygame targets</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TJ_tvEFAv-I/AAAAAAAAAVM/Zm7ZsiXNVhc/s1600/Screenshot-pygame+window.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 203px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TJ_tvEFAv-I/AAAAAAAAAVM/Zm7ZsiXNVhc/s320/Screenshot-pygame+window.png" alt="" id="BLOGGER_PHOTO_ID_5521393060983914466" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I hit a kind of hard part in photobomb, basically, creating copies of objects and applying clippings turns out to be a bit challenging. Sometimes when I'm feeling stymied, I switch to something new for a while to keep the programming fun. Of course, this results in having a few programs, like Photobomb, that are in a perpetual state of being incomplete. However, since I write FOSS software for the fun of it, I figure ... meh ... might well keep it fun.&lt;br /&gt;&lt;br /&gt;Anyway, I recalled a really simple and fun game we used to play in college. I decided to build some similar game play, using the &lt;a href="http://theravingrick.blogspot.com/2010/04/create-your-own-games-with-quickly.html"&gt;Quickly PyGame template&lt;/a&gt;. I created this template for Lucid, but it didn't really get any attention from me since then.&lt;br /&gt;&lt;br /&gt;Here's some game play video, I mercifully left out the sounds:&lt;br /&gt;&lt;br /&gt;&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/qXn2p55CZ_E?fs=1&amp;amp;hl=en_US"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/qXn2p55CZ_E?fs=1&amp;amp;hl=en_US" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;The goal is to get points by touching the target, which moves after it is touched. The player controls the green smiley faced guy. By default, the PyGame template assumes that the player will control "the guy" using the keyboard. So, I had to change this by mapping the guy's location to the mouse. The place to update the guy's location is in the main files "controller_tick" function. This function fires each clock tick, and you use it to update the models of all the sprites and such. Checking out the PyGame docs, this turned out to be a simple one-liner:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    g.x, g.y = pygame.mouse.get_pos()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;g is the variable name for the instance of the guy, and it's easy to set with the tuple returned from mouse position function. So that was all it took to make the guy follow the mouse (and deleted the code that used the keyboard of course). But then, I wanted to enemies to bounce around and be a bit hard to avoid, by by default, enemies wrap around the screen. Since I wanted all the sprites in the game to bounce off of wall, I edited the base_sprite.py file to change the wrapping to bouncing:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        #make the sprite bounce off of walls&lt;br /&gt;  if self.x + self.rect.width &gt; sw:&lt;br /&gt;      self.x = sw - self.rect.width&lt;br /&gt;      self.velocity_x = -self.velocity_x&lt;br /&gt;  if self.y + self.rect.height &gt; sh:&lt;br /&gt;      self.y = sh - self.rect.height&lt;br /&gt;      self.velocity_y = -self.velocity_y&lt;br /&gt;&lt;br /&gt;  if self.x &lt;= 0:           self.x = 0           self.velocity_x = -self.velocity_x       if self.y &lt;= 0:           self.y = 0           self.velocity_y = -self.velocity_y  &lt;/code&gt;&lt;/pre&gt;This code simply changes the direction that the sprite is traveling so it is reflected at the opposite angle that it hit. There's probably a more elegant way of doing it, but this turned out to be readable and reliable for me once I wrote it.&lt;br /&gt;&lt;br /&gt;A bit harder was making enemies bounce off of each other. In previous games I wrote, sprites either did not interact, or killed each other. So this part took me quite a few iterations. I also saw that there were quite a few options for how to program the physics. I chose to make items essentially exchange their velocities, but I added just a touch of randomness. The randomness is designed to keep enemies from getting locked into boring patterns, like moving back and forth in a straight line. Here's the code I eventually ended up with to handle guys bouncing off of each other.&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    for enemy in enemies:&lt;br /&gt;    e = pygame.sprite.spritecollideany(enemy, enemies)&lt;br /&gt;    if enemy is not e:&lt;br /&gt;        if enemy.velocity_x * e.velocity_x &gt;= 0:&lt;br /&gt;            if enemy.velocity_x &gt;= e.velocity_x:&lt;br /&gt;                faster_x = enemy&lt;br /&gt;                slower_x = e&lt;br /&gt;            else:&lt;br /&gt;                faster_x = e&lt;br /&gt;                slower_x = enemy&lt;br /&gt;&lt;br /&gt;            stash = faster_x.velocity_x&lt;br /&gt;            faster_x.velocity_x = slower_x.velocity_x&lt;br /&gt;            slower_x.velocity_x = stash&lt;br /&gt;        else:&lt;br /&gt;            stash = e.velocity_x&lt;br /&gt;            e.velocity_x = enemy.velocity_x&lt;br /&gt;            enemy.velocity_x = stash&lt;br /&gt;&lt;br /&gt;        if enemy.velocity_y * e.velocity_y &gt;= 0:&lt;br /&gt;            if enemy.velocity_y &gt;= e.velocity_y:&lt;br /&gt;                faster_y = enemy&lt;br /&gt;                slower_y = e&lt;br /&gt;            else:&lt;br /&gt;                faster_y = e&lt;br /&gt;                slower_y = enemy&lt;br /&gt;&lt;br /&gt;            stash = faster_y.velocity_y&lt;br /&gt;            faster_y.velocity_y = slower_y.velocity_y&lt;br /&gt;            slower_y.velocity_y = stash&lt;br /&gt;        else:&lt;br /&gt;            stash = e.velocity_y&lt;br /&gt;            e.velocity_y = enemy.velocity_y&lt;br /&gt;            enemy.velocity_y = stash&lt;br /&gt;&lt;br /&gt;        e.velocity_x += random.randint(-5,5)&lt;br /&gt;        e.velocity_y += random.randint(-5,5)&lt;br /&gt;&lt;br /&gt;        enemy.velocity_x += random.randint(-5,5)&lt;br /&gt;        enemy.velocity_y += random.randint(-5,5)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Due to horrible variable naming, this code is too hard to read. I should really fix that up. In any case, it is work mostly reliably. The only time it doesn't work is that sprites can get overlapped too much, and they don't travel far enough way to not collide in the next tick, and they look a bit odd weirdly bouncing off of each other.&lt;br /&gt;&lt;br /&gt;I added a few game play features, like making the guy a ghost after he gets killed, to give the player a chance to get away and not killed over and over again if the guy is caught in a touch place. I also programmed for the background to update every 20 points.&lt;br /&gt;&lt;br /&gt;I used the beloved InkScape to create some new sprites. Of course, I'm a pretty bad programmer, but I'm a worst artist! Any help with graphics would be most welcome :)&lt;br /&gt;&lt;br /&gt;I still have some stuff left to do before I consider the game done enough to put in my PPA:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Make and associated some nice sounds.&lt;/li&gt;&lt;li&gt;Make the game increase in difficulty as levels go on. I'll probably increase the number of enemies, make them go faster, and also make the homing missles smarter, laster longer, and maybe faster.&lt;/li&gt;&lt;li&gt;Format the score and levels so they are readable.&lt;/li&gt;&lt;li&gt;Add power ups. I'm thinking ones that make the enemies freeze for a bit, maybe slow them down too. Free lives, and point multipliers of course. I was also thinking that sometimes the power ups could do bad things, like speeding up the enemies and/or changing their velocities unpredictably.&lt;/li&gt;&lt;li&gt;Find a theme and a reasonable name.&lt;/li&gt;&lt;/ol&gt;If you want to check it out, branch, collaborate, etc... feel free to&lt;a href="https://code.edge.launchpad.net/%7Erick-rickspencer3/+junk/targets"&gt; get the code from launchpad&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5083359351363848377?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5083359351363848377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quickly-create-ubuntu-pygame-targets.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5083359351363848377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5083359351363848377'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quickly-create-ubuntu-pygame-targets.html' title='$quickly create ubuntu-pygame targets'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TJ_tvEFAv-I/AAAAAAAAAVM/Zm7ZsiXNVhc/s72-c/Screenshot-pygame+window.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7048573421465463235</id><published>2010-09-11T15:07:00.000-07:00</published><updated>2010-09-11T16:56:49.257-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><title type='text'>Anything New in Quickly Widgets for Maverick?</title><content type='html'>Going into Maverick, I had envisioned myself spending a lot of free time on Quickly Widgets throughout the cycle. Sadly, though, due to various factors, I did not make the kind of progress I was hoping to. Then a couple of days ago didrocks pinged me to ask if I was going to update Quickly Widgets for Maverick, because if I was, now is the time. So, today I went through all my changes over the last few months, to make sure tests were passing, and that they were generally working well.&lt;br /&gt;&lt;br /&gt;Well, it was a good exercise, because I came to find that I had actually made some decent progress! Here's a quick overview of what is new and improved in Quickly Widgets for Maverick (or will be when it actually gets into Maverick next week). Note that most of the changes were put into place for specific users, so I know that each of these improvements will be useful to at least one person :)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Enhanced DictionaryGrid&lt;/span&gt;&lt;br /&gt;In Lucid, when you created a dictionary grid, the keys were used for the titles of the columns. I think this is good, because it's easy and fun. You can get going fast, and a fair amount of the time, this functionality will be more than sufficient. However, sometimes that column title needs to be changed or maybe different than the keys. This was doable, of course, because a DictionaryGrid is just a TreeView, and you can set the titles on a TreeView. You could loop through the columns, and set the titles as desired. Too much code, so I added a helper function. You can create a dictionary of keys to titles, and set them. So an app like PyTask would do this if it wanted different keys and titles:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        titles = {"name":_("Name"),"priority":_("Priority"),&lt;br /&gt;"due":_("Due"),"project":_("Project"),&lt;br /&gt;"complete?":_("Completed")}&lt;br /&gt;self.grid.set_column_titles(titles)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;You don't have to do all of the titles, you can just pass in the keys that you want you to change titles for. I also made it a bit easier to access a column. You can just index into the columns by the key. So in the tests, for example, you can see where I used it like this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        grid.columns["key1_1"].set_title("KEY")&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Nice one liner. This is just accessing the gtk.TreeViewColumn, so you can whatever you need to with the column, this just makes it easy to get to the columns you need.&lt;br /&gt;&lt;br /&gt;As &lt;a href="http://theravingrick.blogspot.com/2010/06/having-users-makes-your-code-so-much.html"&gt;I  previously blogged&lt;/a&gt;, there is now a datecolumn with built in editing (&lt;a href="http://faq.pygtk.org/index.py?req=index"&gt;thanks to the pygtk faq&lt;/a&gt;):&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s1600/Screenshot.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 603px; height: 453px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s1600/Screenshot.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;And there is a grid filter to go with it:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TH8eSApKybI/AAAAAAAAAUU/1Ga6_U1eYW8/s320/Screenshot-DictionaryGrid+Test+Window.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 308px; height: 320px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TH8eSApKybI/AAAAAAAAAUU/1Ga6_U1eYW8/s320/Screenshot-DictionaryGrid+Test+Window.png" alt="" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;So now, any column with a key that ends in "date" will default to be a date column.&lt;br /&gt;&lt;br /&gt;DictionaryGrid also has an enriched set of arguments for the cell-edited signal. In addition to the row index and key that were previously reported, it now tells you the new value and also provides the dictionary for the row as well. Makes it easier to decide what to do when a cell is edited without having to probe into the grid.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Enhancemed CouchGrid&lt;/span&gt;&lt;br /&gt;The most salient enhancement to CouchGrid, is that it's easy to tell the grid to delete data form desktopcouch now. You do this by passing delete=True to the remove_selected_rows function. So apps like PyTask don't have to do all these crazy contortions to remove data from the underlying store, PyTask just does this:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def remove_row(self, widget, data=None):&lt;br /&gt;"""Removes the currently selected row from the couchgrid."""&lt;br /&gt;&lt;br /&gt;self.grid.remove_selected_rows(delete=True)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;I like this because it lets the programmer keep the grid as their mental model of how to interact with the underlying store, and keeping desktopcouch an incidental piece of plumbing.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Enhanced GridFilters&lt;/span&gt;&lt;br /&gt;Personally, the ability to apply a filter UI to a grid with just a few lines of code is one of the things about quidgets of which I am the most proud. I still don't know how I managed to pull it off, but I did. I like it because it turns a big chore into something easy and fun. If nothing else, it makes a sweet demo.&lt;br /&gt;&lt;br /&gt;So I did make some good progress with this in Maverick. The key thing was that I refactored what a FilterRow contains. The FilterRows were hard coded to use a combo box and TextEntry. This became limiting with data types other than strings. So, I did some open heart surgery on the Filter Code, and after a successful refactoring, can now drop in any set of widgets I would like for a filter row. In addition to a date filter, there's an IntegerFilter now:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwPZJRzRNI/AAAAAAAAAU0/UXP3LcXbJtU/s1600/Spinner.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 279px; height: 320px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwPZJRzRNI/AAAAAAAAAU0/UXP3LcXbJtU/s320/Spinner.png" alt="" id="BLOGGER_PHOTO_ID_5515800568283743442" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;New Widgets&lt;/span&gt;&lt;br /&gt;Thanks to &lt;a href="https://edge.launchpad.net/%7Esil"&gt;sil&lt;/a&gt;, it's now really trivial to download content from the Internet. Sil wrote the code, and I did a&lt;a href="http://theravingrick.blogspot.com/2010/06/new-quickly-widget-async-downloads-with.html"&gt; vblog about this a while back&lt;/a&gt;. In case you missed that:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwQiEOTreI/AAAAAAAAAU8/fzwUfxdpRUI/s1600/Screenshot-UrlFetchProgressBox+test.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 302px; height: 232px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwQiEOTreI/AAAAAAAAAU8/fzwUfxdpRUI/s320/Screenshot-UrlFetchProgressBox+test.png" alt="" id="BLOGGER_PHOTO_ID_5515801821057363426" border="0" /&gt;&lt;/a&gt;This widget works by reusing the common signal handling patterns in PyGtk. From the test app:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def start_download(self, btn):&lt;br /&gt;prog = UrlFetchProgressBox("http://www.ubuntu.com/desktop/get-ubuntu/download")&lt;br /&gt;prog.connect("downloaded", self.downloaded)&lt;br /&gt;self.vbox.pack_start(prog, expand=False)&lt;br /&gt;prog.show()&lt;br /&gt;&lt;br /&gt;def downloaded(self, widget, content):&lt;br /&gt;print "downloaded %s bytes of content" % len(content)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;There is also a TextEditor now. This removes all the bookkeeping code that you have to write to keep a TextView, TextBuffer, Markers, etc... all straight.&lt;br /&gt;&lt;br /&gt;For example, to tell the TextEditor to highlight a word, just add it to the text editor's list of words to highlight:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        self.editor.add_highlight("some")&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;Really! That's all you need to highlight the word "some" in a text editor.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwVZV8ersI/AAAAAAAAAVE/AT-OPp8Zw0I/s1600/Screenshot-TextEditor+Test+Window.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 230px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/TIwVZV8ersI/AAAAAAAAAVE/AT-OPp8Zw0I/s320/Screenshot-TextEditor+Test+Window.png" alt="" id="BLOGGER_PHOTO_ID_5515807168753741506" border="0" /&gt;&lt;/a&gt;It's also got undo and redo built in! So instead of mucking with this yourself, you can hook up an undo command with a one-liner:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        undo_button = gtk.Button("Undo")&lt;br /&gt;   undo_button.connect("clicked",self.editor.undo)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;If you haven't tried making an undo, redo stack yourself, trust me, this is much easier.&lt;br /&gt;&lt;br /&gt;I &lt;a href="http://theravingrick.blogspot.com/2010/09/quidgets-gstreamer-and-return-of.html"&gt;recently blogged&lt;/a&gt; about the new WebCamBox as well. I won't rehash that here.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;House Keeping&lt;/span&gt;&lt;br /&gt;Finally, I went through this morning to ensure that all of the quidgets were using gettext properly (so bring on the translations if you got 'em), and I've also been adding and improving tests as I've been along.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Anyway&lt;/span&gt;&lt;br /&gt;I guess I should write a nice comprehensive tutorial or similar. But in the meantime, there are lots of videos and such on my blog. So, if you want to write an app for Ubuntu, consider using Quickly and Quickly Widgets, because it's getting more fun and more easy with each release!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: justify;"&gt;&lt;span style="font-weight: bold;"&gt;About Quickly Widgets&lt;/span&gt;&lt;br /&gt;&lt;a href="https://code.edge.launchpad.net/quickly"&gt;Quickly&lt;/a&gt; + Widgets = Quidgets&lt;br /&gt;There is a &lt;a href="https://launchpad.net/quidgets"&gt;Launchpad Project for Quickly Widgets&lt;/a&gt;&lt;br /&gt;The most up to date changes are in the &lt;a href="https://edge.launchpad.net/quidgets/trunk"&gt;Quickly Widgets Trunk Branch&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7048573421465463235?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7048573421465463235/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/09/anything-new-in-quickly-widgets-for.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7048573421465463235'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7048573421465463235'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/09/anything-new-in-quickly-widgets-for.html' title='Anything New in Quickly Widgets for Maverick?'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s72-c/Screenshot.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6239198192863543309</id><published>2010-09-03T16:39:00.000-07:00</published><updated>2010-09-05T10:36:50.820-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><title type='text'>Quidgets  &amp;lt 3 Gstreamer, and the Return of Photobomb</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TILxVG_MjgI/AAAAAAAAAUc/SCH6nnHp0M0/s1600/Screenshot-1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TILxVG_MjgI/AAAAAAAAAUc/SCH6nnHp0M0/s320/Screenshot-1.png" alt="" id="BLOGGER_PHOTO_ID_5513234238810131970" border="0" /&gt;&lt;/a&gt;&lt;div&gt;I'm finally settling back into the groove of some of my side projects. I guess I'm handling the new position a bit better as time goes on, and feel that I can spend some free time working on somethings that I want to do, not just things that I feel that I should do.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, these side projects I do for fun, and they are the most fun when they combine together in sweet ways. During the dead of winter, I spent a bunch of weeks working on &lt;a href="http://theravingrick.blogspot.com/search/label/photobomb"&gt;Photobomb&lt;/a&gt; . On of the features that I added was that you could add an image directly from your web cam. To do this, I used the PyGame Web Cam API, essentially because I saw the API, and knew that I would be able to use it relatively easily, which in fact turned out to be the case. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It also turned out that not everyone had PyGame already installed on their systems. As a result installing Photobomb meant a 25+ Megabyte download, most of which was PyGame. So I was advised to use GStreamer instead. I got started on this conversion back in April, by creating a &lt;a href="http://theravingrick.blogspot.com/2010/04/quickly-90-seconds-to-your-ppa.html"&gt;simple web cam display application&lt;/a&gt; using gstreamer. I ran into a series of roadblocks, one such roadblock was removed by &lt;a href="https://edge.launchpad.net/%7Eraof"&gt;Chris Halse Rogers &lt;/a&gt; of desktop team fame, who knew why it kept crashing (basically, I was trying to access the xid of a widget that wasn't yet realized).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But I soon had a &lt;a href="http://www.gstreamer.net/data/doc/gstreamer/head/gstreamer/html/GstPipeline.html"&gt;pipeline&lt;/a&gt; together that could display the web cam, but I could not figure out how to modify it so that it could save out a picture whilst still displaying the web cam output. I finally hopped into #gstreamer to see if someone could give me a pointer. Well, it turns out that someone already wrote a pipeline called &lt;a href="http://www.gstreamer.net/wiki/CameraBin"&gt;caemerabin&lt;/a&gt; that does everything I need for the web cam, and more.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, it turned out that the documentation was out of sync with the current API. This isn't too surprising, as camerabin is still in gstreamer0.01-plugins-bad, and the API is actually improved by the changes. But I was struggling to understand camerabin, so I went back to #gstreamer. Often in IRC, someone will volunteer to spend some time helping you out with a problem. thiagoss (who I think might be&lt;a href="http://www.ime.usp.br/%7Ethsant/"&gt; this guy&lt;/a&gt;) really helped me out. I'm not sure, but I think he may actually be a primary author of camerabin. Anyway, he set me straight on a couple of things, namely:&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. use $gst-inspect camerabin to see what properties and methods the GStreamer elements really support (if they are out of sync with the docs).&lt;/div&gt;&lt;div&gt;2. use GST_DEBUG=2 to run your gstreamer apps, as this puts more warnings in your output.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, between these 2 tips, I was quickly able to realize that my WebCamBox widget would not much more than some Gtk/Gstreamer app boiler plate, with a wrapper around camerabin.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, for example the "take picture" function just creates a time stamp, then tells the camerabin instance to emit a "capture-start" signal.&lt;/div&gt;&lt;div&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;      stamp = str(datetime.datetime.now())&lt;br /&gt;extension = ".png"&lt;br /&gt;directory = os.environ["HOME"] + _("/Pictures/")&lt;br /&gt;self.filename = directory + self.filename_prefix + stamp + extension&lt;br /&gt;self.camerabin.set_property("filename", self.filename)&lt;br /&gt;self.camerabin.emit("capture-start")&lt;br /&gt;return self.filename&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;Then in on_message, I capture the message that it is done, and fire a signal:&lt;/div&gt;&lt;div&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;       t = message.type&lt;br /&gt;if t == gst.MESSAGE_ELEMENT:&lt;br /&gt; if message.structure.get_name() == "image-captured":&lt;br /&gt;     #work around to keep the camera working after lots&lt;br /&gt;     #of pictures are taken&lt;br /&gt;     self.camerabin.set_state(gst.STATE_NULL)&lt;br /&gt;     self.camerabin.set_state(gst.STATE_PLAYING)&lt;br /&gt;&lt;br /&gt;     self.emit("image-captured", self.filename)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Play, Pause, and Start were trivially easy to implement:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;        self.camerabin.set_state(gst.STATE_PLAYING)&lt;br /&gt;self.camerabin.set_state(gst.STATE_PAUSED)&lt;br /&gt;self.camerabin.set_state(gst.STATE_NULL)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Like I say, there is also some boiler plate to instantiate the camera and associate it with a gtk.DrawingArea. It took me a lot of iterations to get it working, as you can see from all of these pictures of me working on it ...&lt;/div&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TIMGbYuNFaI/AAAAAAAAAUk/7leGCbFkxFk/s1600/Screenshot.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TIMGbYuNFaI/AAAAAAAAAUk/7leGCbFkxFk/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5513257436394100130" border="0" /&gt;&lt;/a&gt;The net result is that it's now pretty easy to create an app with a web cam in it. Here's all the code for the WebCamBox test app.&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;if __name__ == "__main__":&lt;br /&gt;"""creates a test WebCamBox"""&lt;br /&gt;import quickly.prompts&lt;br /&gt;&lt;br /&gt;#create and show a test window&lt;br /&gt;win = gtk.Window(gtk.WINDOW_TOPLEVEL)&lt;br /&gt;win.set_title("WebCam Test Window")&lt;br /&gt;win.connect("destroy",gtk.main_quit)&lt;br /&gt;win.show()&lt;br /&gt;&lt;br /&gt;#create a top level container&lt;br /&gt;vbox = gtk.VBox(False, 10)&lt;br /&gt;vbox.show()&lt;br /&gt;win.add(vbox)&lt;br /&gt;&lt;br /&gt;mb = WebCamBox()&lt;br /&gt;mb.video_frame_rate = 30&lt;br /&gt;vbox.add(mb)&lt;br /&gt;mb.show()&lt;br /&gt;mb.play()&lt;br /&gt;&lt;br /&gt;mb.connect("image-captured", __image_captured)&lt;br /&gt;play_butt = gtk.Button("Play")&lt;br /&gt;pause_butt = gtk.Button("Pause")&lt;br /&gt;stop_butt = gtk.Button("Stop")&lt;br /&gt;pic_butt = gtk.Button("Picture")&lt;br /&gt;&lt;br /&gt;play_butt.connect("clicked", lambda x:mb.play())&lt;br /&gt;play_butt.show()&lt;br /&gt;mb.pack_end(play_butt, False)&lt;br /&gt;&lt;br /&gt;pause_butt.connect("clicked", lambda x:mb.pause())&lt;br /&gt;pause_butt.show()&lt;br /&gt;mb.pack_end(pause_butt, False)&lt;br /&gt;&lt;br /&gt;stop_butt.connect("clicked", lambda x:mb.stop())&lt;br /&gt;stop_butt.show()&lt;br /&gt;mb.pack_end(stop_butt, False)&lt;br /&gt;&lt;br /&gt;pic_butt.connect("clicked", lambda x:mb.take_picture())&lt;br /&gt;pic_butt.show()&lt;br /&gt;mb.pack_end(pic_butt, False)&lt;br /&gt;&lt;br /&gt;gtk.main()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Almost all of it is standard code for creating widgets. I love that doing functions like play, pause, stop, and take a picture can be handled in lambdas. So much easier!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So my last step was to drop it into Photobomb. All I had to do was modify the CameraPage class that I had already set up for the PyGame based version.&lt;/div&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;import gtk&lt;br /&gt;from quickly.widgets.web_cam_box import WebCamBox&lt;br /&gt;from ImageListPage import ImageListPage&lt;br /&gt;&lt;br /&gt;class CameraPage(ImageListPage):&lt;br /&gt;def __init__(self):&lt;br /&gt;   gtk.VBox.__init__(self,False, 0)&lt;br /&gt;   self.__camera = WebCamBox()&lt;br /&gt;   self.__camera.connect("image-captured",self.image_captured)&lt;br /&gt;   self.__camera.show()&lt;br /&gt;   self.__camera.set_size_request(128, 128)&lt;br /&gt;   self.pack_start(self.__camera, False, False)&lt;br /&gt;&lt;br /&gt;   button = gtk.Button("Take Picture")&lt;br /&gt;   button.show()&lt;br /&gt;   button.connect("clicked", lambda x:self.__camera.take_picture())&lt;br /&gt;   self.pack_start(button, False, False)&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def image_captured(self, widget, path):&lt;br /&gt;   self.emit("clicked",path)&lt;br /&gt;&lt;br /&gt;def on_selected(self):&lt;br /&gt;   self.__camera.play()&lt;br /&gt;&lt;br /&gt;def on_deselected(self):&lt;br /&gt;   self.__camera.stop()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Well, almost all I had to do. I discovered a bug where if the WebCamBox is not actually visible, it locks up Photobomb if you try to show it. For now, I've worked around this by putting the CameraPage as the first and open page, so it just works. However, I suspect the bug is due to the xid not being available when camerabin tries to display on it. I think with a little thought, I can block this condition, and perhaps not let the camera play if it's not ready yet.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, I ended up with a few lines of wrapper code, around my wrapper code, and it all works thanks to the efforts of the folks working on camerabin!&lt;br /&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;div&gt;camerabin has a whole lot of functionality that I haven't wrapped yet. It takes video, including audio! Also, it looks like you can change encoders, and drop in filters and such into the pipeline. To handle this for now, WebCamBox exposes a "camerabin" public property, so if you are using the widget, you won't run into a wall. &lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6239198192863543309?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6239198192863543309/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quidgets-gstreamer-and-return-of.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6239198192863543309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6239198192863543309'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quidgets-gstreamer-and-return-of.html' title='Quidgets  &amp;lt 3 Gstreamer, and the Return of Photobomb'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/TILxVG_MjgI/AAAAAAAAAUc/SCH6nnHp0M0/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-86577901977837082</id><published>2010-09-01T19:56:00.000-07:00</published><updated>2010-09-01T20:50:05.600-07:00</updated><title type='text'>Quickly Widget Dating</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TH8eSApKybI/AAAAAAAAAUU/1Ga6_U1eYW8/s1600/Screenshot-DictionaryGrid+Test+Window.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 308px; height: 320px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TH8eSApKybI/AAAAAAAAAUU/1Ga6_U1eYW8/s320/Screenshot-DictionaryGrid+Test+Window.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5512157763683928498" /&gt;&lt;/a&gt;&lt;div&gt;Earlier this summer I started some work on DictionaryGrid and GridFilter in order to make them &lt;a href="http://theravingrick.blogspot.com/2010/06/having-users-makes-your-code-so-much.html"&gt;easier to use in apps like PyTask&lt;/a&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, tonight I decided to finish what I started and get this stuff into trunk. Specifically, I wanted to have a DateFilter to go with DateColumn. I wanted to use a Calendar widget for users to select dates, instead of typing them in.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Before I could do that, I had to do some heavy refactoring in GridFilter. I originally made the simplifying assumption that users would enter text into an entry field to enter their values. The only exception was CheckFilter, which needed no gtk.Entry, but I just put in a small hack to make that work. So, this simplifying assumption meant that I could deliver grid filters really fast, but now it was time refactor so I could add new features.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fortunately, it seems that I had started that and was well on the way, so I only had to finish a few things out for the refactoring. I had even already implemented CheckFilter in the new structure, so it was a simple matter to copy that pattern to get to the DateFilter working.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's always an odd feeling when I am working with code that I had written a while back. There's a certain amount of detective work involved, "how did I go about making this all work?" There's a strange familiarity, but yet it's similar to debugging someone else's code. It's nice when I figure it out, especially when I think "that's just how I would have done it."&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I also found &lt;a href="https://bugs.edge.launchpad.net/quidgets/+bug/628416"&gt;a rather serious bug in CouchGrid&lt;/a&gt; along the way. So I stopped off and spent some time fixing CouchGrid, and adding test to make sure I don't see that bug again. The code slightly improved while I was in there, making sure that the grid doesn't have to render itself multiple times when it's initializing.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, I'm really happy to have made some progress on Quickly Widgets again. It was really fun and relaxing. Time well spent this evening.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-86577901977837082?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/86577901977837082/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quickly-widget-dating.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/86577901977837082'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/86577901977837082'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/09/quickly-widget-dating.html' title='Quickly Widget Dating'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/TH8eSApKybI/AAAAAAAAAUU/1Ga6_U1eYW8/s72-c/Screenshot-DictionaryGrid+Test+Window.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7872671878470544864</id><published>2010-09-01T07:45:00.001-07:00</published><updated>2010-09-01T08:23:23.448-07:00</updated><title type='text'>Why I Have Nothing Interesting to Say</title><content type='html'>&lt;div&gt;This posting has been kicking around in my draft's for almost 2 weeks now. I've been wanting to polish it a bit, since it's a bit personal, but &lt;a href="http://www.jonobacon.org/2010/08/20/on-visibility-and-change/"&gt;Jono's posting a couple of weeks&lt;/a&gt; back inspired me to just run it over and get it out there, even if it's still rough. I have nothing interesting to say, anyway ;) Just be warned, the following is not well organized or concise in any way ....&lt;br /&gt;&lt;br /&gt;Anyway ... I normally use my blog as a way to share and get feedback on projects like Quickly and Quickly Widgets. I haven't been blogging because I haven't been working much on those things lately, so haven't had much interesting to say along that front (though a friend did unblock me on my WebCam widget, so now I'm blocked on bending gstreamer to my will rather than a random crash.)&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The reason that I haven't been working on those is two fold. One part, I've been traveling a lot for work and I took a holiday. But the main reason is related to a recent refactoring of the Ubuntu related teams at Canonical.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-weight: bold;"&gt;Changes to Canonical Organizational Structure&lt;/div&gt;&lt;div&gt;This refactoring (or "reorg" in corporate speak) was led by Matt Zimmerman and Robbie Williamson. The resulting structure broke the whole 100+ person Ubuntu team at Canonical into 3 teams:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Linaro Engineering. These are the Canonical folks working on the Linaro project.&lt;/div&gt;&lt;div&gt;2. Platform Services. This team is dedicated to doing work in Ubuntu for partner teams in Canonical, like OEM services.&lt;/div&gt;&lt;div&gt;3. Ubuntu Engineering. The team of Canonical developers focused on Ubuntu the Free and Open Source Community distribution.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I believe the purpose of this refactoring was to make the teams a bit smaller and more focused. However, one of the effects was to leave the Ubuntu Engineering Team with an even tighter focus on Ubuntu as a community distro. Ubuntu Engineering is the biggest team of the three, I believe 85 or so people working on it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-weight: bold;"&gt;Changes to My Role(s) in Ubuntu&lt;/div&gt;&lt;div&gt;Another effect is that of the refactoring was that each of these teams needs a leader, or "Director" in terms of job title, each of whom will report to Matt. The reason that I have been a bit busy to work on Quickly and Quickly Widgets is that I interviewed for, and subsequently got the "Director" role for Ubuntu Engineering. So my job title is now "Ubuntu Engineering Director" (or something like that). I'm still also the Engineering Manager for the Desktop Team until we can find someone awesome to take that role (we're still interviewing, so if you are interested, please apply!!).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This all went down like 10 weeks ago or something. The personal effect on me is that I've been trying to learn how to do a new role while still trying to hold down the fort on the desktop. So, basically, I am working 2 full time roles. As you can imagine, I am not doing either very well.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-weight: bold;"&gt;Figuring Out My New Job&lt;/div&gt;&lt;div&gt;As Director, the engineering managers for the kernel, foundations, server, ARM, desktop, QA, and community now "report" to me. Of course, you know these folks (pgraner, robbiew, jib, davidm, tbh, marjo, and jono) and you know that they are awesome. There are a couple of new roles too (release manager and &lt;a href="http://allisonrandal.com/2010/08/20/ubuntu-ta-intro/"&gt;technical architect&lt;/a&gt;). We have very strong people in these roles as well.&lt;br /&gt;&lt;br /&gt;So if the management team and the engineers are so awesome, what's my job? This is a question I have been asking myself, and I do see some areas where I should provide some leadership. I'd like to spend the rest of the post quickly commenting on just one of those areas. It's a bit hard to label, but I think I'll put it under the heading of "Nurturing the Ubuntu/Canonical Partnership".&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course I was originally attracted to Ubuntu because it's a Free and Open Source Community Distro, but what made me quit my job and base my living around Ubuntu was that it's a community distro &lt;span style="font-style: italic;"&gt;with a Commercial Partner&lt;/span&gt;. I thought that a commercial partner would make my efforts  more relevant (by reaching more people) and more endurable (by sustaining the project for a long time). A commercial partner can also negotiate with OEMs and help fulfill the dream of a pre-installed FLOSS desktop.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So it's important for Ubuntu to honor this partnership in order to maintain it's staying power and relevance. Conversely, it's important to Canonical that Ubuntu is a thriving community open source distribution. The reasons for this are probably self-evident, so I'll leave that out. Anyway, it's helping Canonical with this part about being a good partner to Ubuntu that the rest of this post is about.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Being Part of the Community&lt;/span&gt;&lt;br /&gt;Firstly, in my view, being employed by Canonical is not something that separates me from the Ubuntu Community. If anything, it provides me a lucky position within the community, if nothing else because I am paid full time to contribute to Ubuntu I have more time to dedicate to it. The point is that being on the Ubuntu Engineering Team means that I am &lt;span style="font-style: italic;"&gt;part of&lt;/span&gt; the Ubuntu Community. This is true for allpeople employed by Canonical to work on the Ubuntu. The Ubuntu community is comprised of all kinds of people contributing in all kinds of ways, including some that get paid by Canonical to work on Ubuntu full time.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, if Canonical and Ubuntu both benefit from this partnership, and I am both part of Canonical and the Ubuntu Community, I think that part of my new role of Director should be to help these partners work well together.  There are some things that I think we (Canonical employees in the Ubuntu Community) can do better in this regard. I'm still thinking through it, but I am formulating two key goals at this point, one around transparency, and the other around growing the number of core contributors.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="font-weight: bold;"&gt;Transparency&lt;/div&gt;&lt;div&gt;I think that we (the whole community) still follow our transparent processes around UDS, blueprints, specs, and bugs. So we are transparent in the sense that anyone can come and carefully study our documents and learn pretty much anything they want about what Ubuntu is working on.  I suspect that the way of being "transparent" work very well for the project when there were way fewer users and way fewer people working on the project. But now the project has grown a bit, and perhaps the old ways are not totally sufficient.&lt;br /&gt;&lt;br /&gt;In one sense, we've made great strides in exposing what we are working on via our work item tracker. But this a very granular view. I'd like to see us do better in expressing the gestalt of a release, the high level goals for the release. I'd like to see us be able to *push* information at this level before, during, and after UDS, and throughout the cycle. I don't know if this goal is really about changing the way we work per se, so much as creating an information architecture around the work that we (and I mean "we" inclusively as the Ubuntu community) are doing, and then having the discipline to maintain a drum beat of communication within that architecture. In this way, the community should better be able to see what the Ubuntu Community and Canonical is focused on in developing Ubuntu, and how that work is going.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-weight: bold;"&gt;Growing Core Contributors&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While I think the work item tracking system has been great for exposing what we are working on, and also for providing visibility into our status, I suspect it has had an unintended effect. I think it's turned the Canonical employees into feature creation machines. Something about a burn down chart motivates you to work to stay under that trend line. While creating lots of new features is great, I am concerned that it has come at the expense of other important work. A community needs investment, and I am wondering if our focus on work items and features has supplanted some amount of our investment in the community (and by "our" here, I am referring to Canonical Employees in the Ubuntu Community).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I can see several areas, such as patch reviews, REVU, archive admins, etc... that are bottlenecked by not having enough core contributors. But our very inability to focus on things like reviewing patches and REVU means that we are not inviting new people to build towards being core contributors. But new people is exactly what we need to remove the bottlenecks! A sweet catch-22. I think that this will require the bold move to hold back on commitments to features, to tilt Canonical Employees investments a bit toward nurturing those new comers (as well as our stalwart comrades, of course).&lt;br /&gt;&lt;br /&gt;So there are 2 great topics for UDS discussions. Whatever the case, can I just say that I am thrilled to have this new gig, and I look forward to getting to know a whole lot of people and to help them with their goals for Ubuntu? I guess being my blog, I can say that ;)&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7872671878470544864?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7872671878470544864/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/09/why-i-have-nothing-interesting-to-say.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7872671878470544864'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7872671878470544864'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/09/why-i-have-nothing-interesting-to-say.html' title='Why I Have Nothing Interesting to Say'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7979636422068267834</id><published>2010-08-10T08:13:00.000-07:00</published><updated>2010-08-10T08:21:21.558-07:00</updated><title type='text'>Can we count users without uniquely identifying them?</title><content type='html'>&lt;div&gt;&lt;b&gt;Aaaah&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Hi all. I'm just back from a rather nice holiday. Well, technically, I'm still on holiday, but there were a few things I wanted to take care of, so I popped in for a few hours of work yesterday and today. I saw that there was &lt;a href="http://www.phoronix.com/scan.php?page=news_item&amp;amp;px=ODQ5MA"&gt;this post on Phoronix&lt;/a&gt; that triggered me writing a post that I've been meaning to do for the last few weeks, since the Canonical Platform Team got together in Prague three weeks ago, to be exact.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Pre-installed desktops ftw&lt;/b&gt;&lt;/div&gt;&lt;div&gt;One of the roles of Canonical relative to Ubuntu is to get Ubuntu pre-installed on as many computers as possible. This is one of the dreams of the Linux desktop. Pre-installs mean end users don't have to fiddle with configurations, installing drivers, etc... (at least when done well) and the users can make an apples to apples comparison between their free desktop and proprietary systems that normally come pre-installed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Canonical does this by working with OEM customers. OEMs are companies that sell assembled computers to people. One of these customers asked Canonical if there was some way that they could know how many computers that they send out with Ubuntu on them keep Ubuntu on them. The customer's engineer came up with a system where they would create a unique identifier for each Ubuntu computer they sold, and then when the computers requested update info daily, it would send that unique identifier with it. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The customer didn't really want to use a unique identifier though, because though it was anonymous, the customer wanted to *count* computers, but unique identifiers are for *tracking* (following a user over time). We mulled it over and over, and finally, based on our experience with web browsers we hit upon a system of non-unique channel identifiers to do the counting. This would make tracking impossible, but of course, tracking is not the goal, counting is.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Non-unique channel identifiers&lt;/b&gt;&lt;/div&gt;&lt;div&gt;So, we flashed on this: if each install sent just the model name and the number of times it has updated, systems could be counted, but no unique data would ever be sent to the server. Now, I am not a mathematician, so each time I try to explain why I think this works, it takes me a while. But in the end, everyone is convinced. In fact, &lt;a href="http://mdzlog.alcor.net/"&gt;Matt Zimmerman&lt;/a&gt; ended up writing a test program to prove to himself that it worked. Let me try, stick with me here ...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Every day each computer from the customer sends it's model name and the number of times it has already sent this data to the server. So if a model of a computer is called, say "foo", the first day it sends "foo" and 0 to census.canonical.com. After sending the 0, the computer remembers that it already sent a 0, so it will send a 1 next time. When the server sees the foo.0 in the log data, it essential stars a new counter for the model foo. The total number of foo.0 are the total number of the model foo ever activated.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Take one of those foo computers. The next day it will send foo.1, saying "this is a computer of model foo, and this is the 2nd time it has pinged that it's alive". Notice that neither foo or the number 1 are unique data. Any number of computers will be reporting the exact same model name and increment number. When the server sees a 1 come in, it finds the first counter at 0 and increments that counter to 1. Now it knows the total number of computers ever activated (all the counters), and it can count all the counters that were incremented in a day and thereby know how many computers were online that day.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Future?&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Currently this system is only slated to be used by the specific OEM customer who requested it, and it will be up to the customer to disclose the data they collect as they wish. I wonder if it would be a good thing to install on normal ISOs though, but this would be part of our normal participatory community decision making process. &lt;a href="http://counter.li.org/"&gt;Projects like this &lt;/a&gt;make think that users would like to be counted, so long as they can't be tracked. We'll see how it plays out, it may be something to discuss at UDS if the community feels the data would be useful. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7979636422068267834?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7979636422068267834/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/08/can-we-count-users-without-uniquely.html#comment-form' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7979636422068267834'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7979636422068267834'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/08/can-we-count-users-without-uniquely.html' title='Can we count users without uniquely identifying them?'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2309720530924507003</id><published>2010-06-20T12:12:00.001-07:00</published><updated>2010-06-20T12:23:03.299-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><title type='text'>New Quickly Widget: Text Editor</title><content type='html'>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 &lt;span style="font-style: italic; font-weight: bold;"&gt;is&lt;/span&gt; a gtk.TextView, so if you need to access any of the power and flexibility of the underlying Gtk library, it's right there.&lt;br /&gt;&lt;br /&gt;&lt;object height="385" width="480"&gt;&lt;param name="movie" value="http://www.youtube.com/v/urwBBlYhaUU&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/urwBBlYhaUU&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="385" width="480"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;TextEditor is now available in &lt;a href="https://code.launchpad.net/%7Erick-rickspencer3/quidgets/trunk"&gt;quidgets trunk&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2309720530924507003?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2309720530924507003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-widget-text-editor.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2309720530924507003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2309720530924507003'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-widget-text-editor.html' title='New Quickly Widget: Text Editor'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4505050680888419796</id><published>2010-06-20T11:08:00.000-07:00</published><updated>2010-06-20T11:15:01.806-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='daily-journal'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>New Quickly App: Daily Journal</title><content type='html'>&lt;object height="385" width="480"&gt;&lt;param name="movie" value="http://www.youtube.com/v/1fQQ93aiack&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/1fQQ93aiack&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="385" width="480"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;Quickly has started to unlock productivity for me in unexpected ways. I've mentioned about writing my own development tools, like &lt;a href="http://theravingrick.blogspot.com/search/label/bughugger"&gt;bughugger&lt;/a&gt;, and &lt;a href="http://theravingrick.blogspot.com/search/label/slip-cover"&gt;slipcover&lt;/a&gt;. 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 &lt;a href="https://edge.launchpad.net/%7Erick-rickspencer3/+archive/ppa"&gt;PPA&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In my next posting, I'll show how I used quickly.widgets.text_editor to create Daily Journal.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4505050680888419796?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4505050680888419796/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-app-daily-journal.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4505050680888419796'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4505050680888419796'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-app-daily-journal.html' title='New Quickly App: Daily Journal'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-1795629506445578173</id><published>2010-06-14T10:24:00.000-07:00</published><updated>2010-06-14T10:28:28.537-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Go Here to Learn to Program from MIT</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://imgs.xkcd.com/comics/python.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 518px; height: 588px;" src="http://imgs.xkcd.com/comics/python.png" border="0" alt="" /&gt;&lt;/a&gt;I run into folks who want to get started programming, but they "don't know a language". If you are in this camp, I highly recommend &lt;a href="http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-189-a-gentle-introduction-to-programming-using-python-january-iap-2008/"&gt;the online course from MIT&lt;/a&gt;.  It's designed for people with no prior programming experience, and it's Python!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After the first few lessons, you'll know enough Python to start a Quickly app!.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-1795629506445578173?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/1795629506445578173/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/06/go-here-to-learn-to-program-from-mit.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1795629506445578173'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1795629506445578173'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/06/go-here-to-learn-to-program-from-mit.html' title='Go Here to Learn to Program from MIT'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-50431749606711380</id><published>2010-06-06T11:33:00.000-07:00</published><updated>2010-06-06T12:16:34.238-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='developer-manual'/><title type='text'>New Quickly Widget - async downloads with two lines of code</title><content type='html'>&lt;object height="344" width="425"&gt;&lt;param name="movie" value="http://www.youtube.com/v/Y9ZACNnRNJ8&amp;amp;hl=en&amp;amp;fs=1"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/Y9ZACNnRNJ8&amp;amp;hl=en&amp;amp;fs=1" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" height="344" width="425"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The &lt;a href="https://edge.launchpad.net/~ubuntu-developer-manual"&gt;Ubuntu Developer's Manual team&lt;/a&gt; was discussing&lt;a href="https://code.edge.launchpad.net/~rick-rickspencer3/ubuntu-developer-manual/sample-app"&gt; the instructional app&lt;/a&gt; that we should use for &lt;a href="https://wiki.ubuntu.com/DesktopTeam/10.10/DeveloperManual"&gt;the manual&lt;/a&gt;. 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.&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;&lt;br /&gt;     fetcher = UrlFetchProgressBox("http://identi.ca/api/statusnet/groups/timeline/8.rss")&lt;br /&gt;     fetcher.connect("downloaded",self.create_grid_from_feed)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;So, two lines! Just say what url you want to download, and tell it the function to call when it's done.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-50431749606711380?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/50431749606711380/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-widget-async-downloads-with.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/50431749606711380'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/50431749606711380'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/06/new-quickly-widget-async-downloads-with.html' title='New Quickly Widget - async downloads with two lines of code'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7976627759619329163</id><published>2010-06-01T16:44:00.000-07:00</published><updated>2010-06-06T12:17:18.079-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><title type='text'>Having Users Makes Your Code So Much Better</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s320/Screenshot.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5477962069803907650" /&gt;&lt;/a&gt;&lt;br /&gt;I mentioned in a &lt;a href="http://theravingrick.blogspot.com/2010/05/pytask-written-with-quickly-and-quickly.html"&gt;previous post&lt;/a&gt; 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. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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 &lt;a href="https://edge.launchpad.net/~nisshh"&gt;Ryan&lt;/a&gt; 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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Since I have intimate knowledge about how the PyGtk was assembled, I was able to write this code for PyTask&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;    def remove_row(self, widget, data=None):&lt;br /&gt;"""Removes the currently selected row from the couchgrid."""&lt;br /&gt;# work around to actually delete records from desktopcouch&lt;br /&gt;# in maverick, using delete=true in remove_selected_rows will have&lt;br /&gt;# the same effect&lt;br /&gt;database = CouchDatabase("pytask")&lt;br /&gt;for r in self.grid.selected_rows:&lt;br /&gt;   database.delete_record(r["__desktopcouch_id"])&lt;br /&gt;&lt;br /&gt;if type(self.grid.get_model()) is gtk.ListStore:&lt;br /&gt;   self.grid.remove_selected_rows()&lt;br /&gt;&lt;br /&gt;else:&lt;br /&gt;&lt;br /&gt;   # The following code works around:&lt;br /&gt;   # https://bugs.edge.launchpad.net/quidgets/+bug/587568&lt;br /&gt;   # get the selected rows, and return if nothing is selected&lt;br /&gt;   model, rows = self.grid.get_selection().get_selected_rows()&lt;br /&gt;&lt;br /&gt;   if len(rows) == 0:&lt;br /&gt;       return&lt;br /&gt;&lt;br /&gt;   # store the last selected row to reselect after removal&lt;br /&gt;   next_to_select = rows[-1][0] + 1 - len(rows)&lt;br /&gt;&lt;br /&gt;   # loop through and remove&lt;br /&gt;   iters = [model.get_model().get_iter(path) for path in rows]&lt;br /&gt;   store_iters = []&lt;br /&gt;   for i in iters:&lt;br /&gt;       # convert the iter to a useful iter&lt;br /&gt;       store_iters.append(model.get_model().convert_iter_to_child_iter(i))&lt;br /&gt;&lt;br /&gt;   for store_iter in store_iters:&lt;br /&gt;       # remove the row from the store&lt;br /&gt;       self.filt.store.remove(store_iter)&lt;br /&gt;&lt;br /&gt;   # select a row for the user, nicer that way&lt;br /&gt;   rows_remaining = len(model)&lt;br /&gt;&lt;br /&gt;   # don't try to select anything if there are no rows left&lt;br /&gt;   if rows_remaining &amp;lt; 1:&lt;br /&gt;       return&lt;br /&gt;&lt;br /&gt;   # select the next row down, unless it's out of range&lt;br /&gt;   # in which case just select the last row&lt;br /&gt;   if next_to_select &amp;lt; rows_remaining:&lt;br /&gt;       self.grid.get_selection().select_path(next_to_select)&lt;br /&gt;   else:&lt;br /&gt;       self.grid.get_selection().select_path(rows_remaining - 1)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Essentially, it deletes the selected rows from desktop couch, and then goes on to figure if the grid is filtered, and if so, figures out where in the unfiltered model the rows are, and removes them from there. It also tries to select a row for the user after removing.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;    def remove_row(self, widget, data=None):&lt;br /&gt;"""Removes the currently selected row from the couchgrid."""&lt;br /&gt;self.grid.remove_selected_rows(delete=True)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;A bit more sensible.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        for c in self.grid.get_columns():&lt;br /&gt;      if c.get_title() == "name":&lt;br /&gt;          c.set_title(_("Name"))&lt;br /&gt;      elif c.get_title() == "priority":&lt;br /&gt;          c.set_title(_("Priority"))&lt;br /&gt;      elif c.get_title() == "due":&lt;br /&gt;          c.set_title(_("Due"))&lt;br /&gt;      elif c.get_title() == "project":&lt;br /&gt;          c.set_title(_("Project"))&lt;br /&gt;      if c.get_title() == "complete?":&lt;br /&gt;          c.set_title(_("Completed"))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Except, when doing this, it meant that the column titles in the GridFilter didn't match. :/ This is because the GridFilter got the names of the columns from the keys instead of the title.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;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:&lt;/div&gt;&lt;div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;   def __new_filter_row(self, widget, data=None):&lt;br /&gt;   """&lt;br /&gt;   new_filter row - hack to allow naming of columns&lt;br /&gt;   in a grid filter.&lt;br /&gt;&lt;br /&gt;   This code works around:&lt;br /&gt;   https://bugs.edge.launchpad.net/quidgets/+bug/587558&lt;br /&gt;&lt;br /&gt;   """&lt;br /&gt;&lt;br /&gt;   row = self.filt.rows[len(self.filt.rows)-1]&lt;br /&gt;   row.connect("add_row_requested",self.__new_filter_row)&lt;br /&gt;   model = row.column_combo.get_model()&lt;br /&gt;&lt;br /&gt;   for i, k in enumerate(model):&lt;br /&gt;       itr = model.get_iter(i)&lt;br /&gt;       title = model.get_value(itr,0)&lt;br /&gt;       if title == "name":&lt;br /&gt;           model.set_value(itr,0,_("Name"))&lt;br /&gt;       elif title == "priority":&lt;br /&gt;           model.set_value(itr,0,_("Priority"))&lt;br /&gt;       elif title == "due":&lt;br /&gt;           model.set_value(itr,0,_("Due"))&lt;br /&gt;       elif title == "project":&lt;br /&gt;           model.set_value(itr,0,_("Project"))&lt;br /&gt;       if title == "complete?":&lt;br /&gt;           model.set_value(itr,0,_("Completed"))&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div&gt;I fixed this a bit easier in Quickly Widgets.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;All of this managing the title stuff was really obtuse, and it seemed that setting column titles might be generally useful. So I added two things to DictionaryGrid to make this easier. First, I made a property that returns a dictionary of columns indexed by the column key, so you can easily get ahold of a column you want. Here's some code from one of the Quickly Widget tests:&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        grid.columns["key1_1"].set_title("KEY")&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;That's a bit easier than for c in grid.get_columns(), etc...&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I also made a convenience function that Ryan can use. Here's the code from the tests:&lt;/div&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;        titles = {"key1_1":"KEY1","key1_2":"KEY2","key1_3":"KEY3"}&lt;br /&gt;     grid.set_column_titles(titles)&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;So this should make it really trivial to manage the titles of columns separate from the keys in the dictionaries. Of course, if you don't want to care about column titles, that's fine too. They still work just by using the keys.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, thanks to Ryan for letting me use PyTask to improve Quickly Widgets!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7976627759619329163?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7976627759619329163/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/06/having-users-makes-your-code-so-much.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7976627759619329163'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7976627759619329163'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/06/having-users-makes-your-code-so-much.html' title='Having Users Makes Your Code So Much Better'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/TAWhep-5ukI/AAAAAAAAAUE/soo8g5p29jg/s72-c/Screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2537030289196100530</id><published>2010-05-26T10:20:00.000-07:00</published><updated>2010-05-26T10:22:31.944-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><title type='text'>Video Walkthrough of Work Item Tracker for Maverick</title><content type='html'>&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/yhd1d2tAPOg&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/yhd1d2tAPOg&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2537030289196100530?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2537030289196100530/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/05/video-walkthrough-of-work-item-tracker.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2537030289196100530'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2537030289196100530'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/05/video-walkthrough-of-work-item-tracker.html' title='Video Walkthrough of Work Item Tracker for Maverick'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4630266352624966648</id><published>2010-05-25T10:09:00.000-07:00</published><updated>2010-05-25T10:17:57.354-07:00</updated><title type='text'>Track the Desktop and UNE in Maverick</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://people.canonical.com/~pitti/workitems/maverick/canonical-desktop-team-maverick-alpha-2.svg"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 300px; height: 150px;" src="http://people.canonical.com/~pitti/workitems/maverick/canonical-desktop-team-maverick-alpha-2.svg" border="0" alt="" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div&gt;Ubuntu development happens in a very transparent manner. I won't cover the lead up and execution of UDS here, but I will discuss what happens after UDS for maverick.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, you should know where we are in the schedule. &lt;a href="https://wiki.ubuntu.com/MaverickReleaseSchedule"&gt;The schedule is always available&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At UDS discussions center around blueprints. After UDS, the Ubuntu developers take the set of blueprints that they are committed to working on, and they break down the blueprint into "work items". A work item is roughly one half to two days of work that is a required step to accomplish the blueprint.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Work items are then mapped to a milestone, or work item iteration. Note that the Ubuntu developers only commit to workng on work items for the current work item iteration! The current work item iteration maps to Alpha 2. So you can see the work items that the desktop team is committed to working on on our &lt;a href="http://people.canonical.com/~pitti/workitems/maverick/canonical-desktop-team-maverick-alpha-2.html"&gt;Alpha 2 tracking page&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you want to explore the whole release, you can see the &lt;a href="http://people.canonical.com/~pitti/workitems/maverick/"&gt;landing page for all of Maverick&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But remember that work items that are not slotted to Alpha 2 are not committed to yet!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, what exactly is the Desktop Team planning for Maverick? Here are some highlights.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Software Center&lt;/b&gt;&lt;/div&gt;&lt;div&gt;The software center will get many UI improvments. However, the biggie is that we are going to figure out how enable application developers to target the current stable release for new apps! This means new apps that don't modify the underling Ubuntu platform and libraries will be made available in software-center even if the app was written after that version of Ubuntu released. &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-opportunistic-apps-stable-release"&gt;See the blueprint here&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Gnome Changes&lt;/b&gt;&lt;/div&gt;&lt;div&gt;We always decide at UDS what to take from new Gnome and how. We are very excited about Gnome 3.0, but due to tight release schedules, we are going to be cautious about Gnome 3.0 for Maverick. We will update to the current platform, and we will deliver gsettings and latest versions of apps as appropriate. As usual, we will make gnome-shell available to users who choose to use it. &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-gnome"&gt;See the blueprint here&lt;/a&gt;. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Browsers and Apps&lt;/b&gt;&lt;/div&gt;&lt;div&gt;We're going to try for &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-chromium"&gt;Chromium by default in UNE&lt;/a&gt;, though we are sticking with Mozilla as the default in the Desktop Edition.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We are also changing to &lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-shotwell"&gt;Shotwell as the default&lt;/a&gt; image library application. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;xorg-xserver&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Interesting decision here is to hold off on promise of 3D support for -intel 8xx series graphics. If the -intel driver is not providing sufficient stabiliy we may support those chips with Vesa, and perhaps deliver an older intel driver for community support for those 8xx users who want to give it a go.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Social From the Start&lt;/b&gt;&lt;/div&gt;&lt;div&gt;We'll work on gwibber start up time and robustness, but also work bake&lt;a href="https://blueprints.launchpad.net/ubuntu/+spec/desktop-maverick-social-api"&gt; more integration of social services&lt;/a&gt; into the desktop.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Everything Else&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Of course, we'll be working to integrate the latest and greatest from all of our upstream projects, fix bugs as fast as possible, and generally do all the awesome stuff Ubuntu Desktop developers do everyday.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4630266352624966648?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4630266352624966648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/05/track-desktop-and-une-in-maverick.html#comment-form' title='19 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4630266352624966648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4630266352624966648'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/05/track-desktop-and-une-in-maverick.html' title='Track the Desktop and UNE in Maverick'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>19</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4531736905314175618</id><published>2010-05-17T05:36:00.000-07:00</published><updated>2010-06-06T12:18:21.951-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>PyTask, written with Quickly and Quickly Widgets</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S_E5SSrcJdI/AAAAAAAAAT8/EJSWD-4hTOs/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 224px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S_E5SSrcJdI/AAAAAAAAAT8/EJSWD-4hTOs/s320/Screenshot.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5472218008646329810" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;div&gt;On Saturday I received an email from a developer name &lt;a href="https://edge.launchpad.net/~nisshh"&gt;Ryan&lt;/a&gt;. He was using Quickly and Quickly Widgets to create a task list application. CouchGrid seemed to suit, but due to the fact that there are no documents for it, he naturally had some questions about how to proceed. His project is called PyTask, and so far, I like it. It's a very simple set up, and just works in terms of data persistence and syncing across desktops.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Looking at the App, it was clear that there were a few more features that DictionaryGrid needs to really rock, though:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;It needs a DateColumn to handle the "due" column. Users would want to set this with a gtk.Calendar widget.&lt;/li&gt;&lt;li&gt;It needs a ComboColumn to handle the "priority" column. Users would want to pick from a list of valid predefined priority values rather than free input text. This will be interesting to create a nice API for. I suppose application developers will want to pass in just a list of strings to the column. I think this is doable, but may take some refactoring as currently there is no intuitive way to get a hold of a column and set a property on it.&lt;/li&gt;&lt;li&gt;Both of these will require new GridFilter functionality. In fact, I have been waiting for a reason to refactor this part of Quickly Widgets, as the GridFilterRows are hard coded to use specific widgets, and this should be flexible.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, as it is, I am using PyTask, I hope Ryan get's it into a PPA soon. I like the simplicity. Ryan and I are currently collaborating on creating the new functionality in DictionaryGrid that PyTask needs. Open Source FTW!&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4531736905314175618?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4531736905314175618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/05/pytask-written-with-quickly-and-quickly.html#comment-form' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4531736905314175618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4531736905314175618'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/05/pytask-written-with-quickly-and-quickly.html' title='PyTask, written with Quickly and Quickly Widgets'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S_E5SSrcJdI/AAAAAAAAAT8/EJSWD-4hTOs/s72-c/Screenshot.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-9172444632151248031</id><published>2010-04-26T22:12:00.000-07:00</published><updated>2010-04-26T22:13:42.509-07:00</updated><title type='text'>Install Apps from PPAs without Using the Terminal</title><content type='html'>&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/uM51uKGLVUI&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;color1=0x3a3a3a&amp;amp;color2=0x999999"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/uM51uKGLVUI&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;color1=0x3a3a3a&amp;amp;color2=0x999999" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div&gt;Here's a really nice Lucid feature. In this video you can see that users can now add software from your PPA without using the CLI. They just need to get you deb line, and using the software center, they are good to go. &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-9172444632151248031?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/9172444632151248031/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/install-apps-from-ppas-without-using.html#comment-form' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/9172444632151248031'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/9172444632151248031'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/install-apps-from-ppas-without-using.html' title='Install Apps from PPAs without Using the Terminal'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-406901854046958203</id><published>2010-04-26T21:06:00.000-07:00</published><updated>2010-06-06T12:18:33.666-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Quickly: 90 Seconds to Your PPA</title><content type='html'>&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;object width="480" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/asR2dh3Pn9M&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/asR2dh3Pn9M&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="480" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;Here's a quick video showing taking a finished Quickly app, setting the desktop and setup.py file, and then uploading to a PPA using $quickly share&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-406901854046958203?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/406901854046958203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-90-seconds-to-your-ppa.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/406901854046958203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/406901854046958203'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-90-seconds-to-your-ppa.html' title='Quickly: 90 Seconds to Your PPA'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7015541473552975189</id><published>2010-04-25T21:01:00.000-07:00</published><updated>2010-06-06T12:18:44.791-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Create Your Own Games with Quickly Pygame</title><content type='html'>A couple of months ago I created a Quickly template with the goal of making it easy and fun to get started my games. The template doesn't have any "add" or "design" commands, but it does have all the other commands. The template creates a functioning arcade-style game, and then you provide you own artwork and start hacking the code to make your own gameplay.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;$quickly tutorial ubuntu-pygame is the best way to get a detailed introduction into getting started making your own game, but here's some video of hacking the code. I hope it inspires you to try your hand at creating your own games.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Part 1: Create the game, copy in your artwork, and make the guy work the way you want&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/au9VclCA93Y&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/au9VclCA93Y&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div&gt;Part 2: Program the enemies&lt;/div&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/x6_SSLubSac&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/x6_SSLubSac&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div&gt;Part 3: Create a power up sprite, and  manage collisions&lt;/div&gt;&lt;br /&gt;&lt;object width="640" height="385"&gt;&lt;param name="movie" value="http://www.youtube.com/v/-r0InSFUDqA&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/-r0InSFUDqA&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7015541473552975189?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7015541473552975189/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/create-your-own-games-with-quickly.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7015541473552975189'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7015541473552975189'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/create-your-own-games-with-quickly.html' title='Create Your Own Games with Quickly Pygame'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7293364303136893732</id><published>2010-04-25T13:18:00.001-07:00</published><updated>2010-04-25T13:23:21.490-07:00</updated><title type='text'>Slip Cover: Creating Views</title><content type='html'>&lt;div&gt;I've been doing a few minutes of work a day on slip-cover, working towards getting view editing pretty complete. Pretending that I want to create a view that simply lists all of the keys in a database, you can see here that you now can:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Click the "Add Button" above the views, which adds a row to the views that you can start editing. Here I start by naming the design document.&lt;/div&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjuqXkKCI/AAAAAAAAAT0/PrOk-jV1wco/s1600/Screenshot-2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjuqXkKCI/AAAAAAAAAT0/PrOk-jV1wco/s320/Screenshot-2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5464172269949691938" /&gt;&lt;/a&gt;2. Then I name the view. Notice the view editor is not visible yet, as I have not completed specifying the design doc or the name for the view.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjuLM06zI/AAAAAAAAATs/PBHyygDAokk/s1600/Screenshot-3.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjuLM06zI/AAAAAAAAATs/PBHyygDAokk/s320/Screenshot-3.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5464172261583153970" /&gt;&lt;/a&gt;3. Then I can try out my map function, and also name the view.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjtnuKeqI/AAAAAAAAATk/ppqYGWCTcZ0/s1600/Screenshot-4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjtnuKeqI/AAAAAAAAATk/ppqYGWCTcZ0/s320/Screenshot-4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5464172252059302562" /&gt;&lt;/a&gt;Currently, the view gets automatically saved to the database when created. I suppose this is ok, since it is easy to delete. The view doesn't track the dirty state at all. I should probably do something so users remember to save their views after they edit them. Undo and cut/copy/paste will all be necessary for the map/reduce editors as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7293364303136893732?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7293364303136893732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-creating-views.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7293364303136893732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7293364303136893732'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-creating-views.html' title='Slip Cover: Creating Views'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9SjuqXkKCI/AAAAAAAAAT0/PrOk-jV1wco/s72-c/Screenshot-2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8820051529428963152</id><published>2010-04-24T19:36:00.000-07:00</published><updated>2010-04-24T19:51:43.121-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Quickly and Quickly Widget Intro Videos</title><content type='html'>&lt;div&gt;You probably saw that &lt;a href="http://blog.didrocks.fr/index.php/post/Quickly-0.4.1-is-out%21"&gt;didrocks released another update for Quickly&lt;/a&gt;. It's chock full of bug fixes and tweaks based on the feedback from the last release. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;About a week ago I made some updated intro videos to show Quickly in action, and highlight some key changes between Quickly in Karmic and Quickly in Lucid. They are all available in HD, so you switch to that when they start playing, it makes them waaay easier to read.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Part 1: Create a project and use Glade to edit the UI&lt;/div&gt;&lt;div&gt;Here you see that you use "quickly create ubuntu-application" instead of "quickly create ubuntu-project" to create an app. You also use "quickly design" instead of "quickly glade" to design the UI.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;object width="560" height="340"&gt;&lt;param name="movie" value="http://www.youtube.com/v/wOIDBL5jyx4&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/wOIDBL5jyx4&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Part 2: Using CouchGrid&lt;/div&gt;&lt;div&gt;One of the key differences here is that CouchGrid is now in the quickly.widgets module instead of the desktopcouch module. The CouchGrid moved into quickly.widgets because it now extends the DictionaryGrid class. This brings a lot benefits:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Automatic column type inference&lt;/li&gt;&lt;li&gt;Ability to set column types so you get the correct renderers&lt;/li&gt;&lt;li&gt;Correct sorting (for instance 11 &gt; 9 in IntegerColumn, but "11" &lt; "9" in string columns&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;And of course you get all the goodness of automatic desktopcouch persistence.&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;&lt;object width="560" height="340"&gt;&lt;param name="movie" value="http://www.youtube.com/v/MSZ6oRFG4pw&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/MSZ6oRFG4pw&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;br /&gt;&lt;div&gt;Part 3: Using GridFilter&lt;/div&gt;&lt;div&gt;GridFilter is a new class that provides automatic filtering UI for a DictionaryGrid or descendant such as CouchGrid.&lt;/div&gt;&lt;br /&gt;&lt;object width="560" height="340"&gt;&lt;param name="movie" value="http://www.youtube.com/v/JzTL1QEFHsA&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;"&gt;&lt;param name="allowFullScreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;embed src="http://www.youtube.com/v/JzTL1QEFHsA&amp;amp;hl=en_US&amp;amp;fs=1&amp;amp;" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8820051529428963152?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8820051529428963152/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-and-quickly-widget-intro-videos.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8820051529428963152'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8820051529428963152'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-and-quickly-widget-intro-videos.html' title='Quickly and Quickly Widget Intro Videos'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6498182174829314483</id><published>2010-04-22T16:19:00.000-07:00</published><updated>2010-04-22T16:53:46.510-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Slip Cover, Integrated View Editor for DesktopCouch</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9DbDiR0yfI/AAAAAAAAATU/80ZOy8E3ucc/s1600/Screenshot-Slip+Cover.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 170px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9DbDiR0yfI/AAAAAAAAATU/80ZOy8E3ucc/s320/Screenshot-Slip+Cover.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5463107201788070386" /&gt;&lt;/a&gt;&lt;div&gt;I removed the ViewDesignerDialog, and changed it to a ViewDesignerBox. Basically, a widget that is now embedded into Slip Cover. I also wrote some code to extract the views that currently exist in a database, and offer those under "Views" so you can click on them and see the views map function, and the views reduce function if it has one.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Besides editing views and trying out your edits, this lets you look at the databases you have and see how they work. In the example above, you can see how the views for your contacts are set up. Handy if you want a similar feature in one of your databases.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9Dd2bFriiI/AAAAAAAAATc/uu7uyuUHLlk/s1600/Screenshot-Slip+Cover-4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 202px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S9Dd2bFriiI/AAAAAAAAATc/uu7uyuUHLlk/s320/Screenshot-Slip+Cover-4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5463110275054668322" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This screenshot shows two things. First, it shows the view I constructed to get the record type for each record in a database, and also whether the record has been deleted. It's a bit "meta", but the purpose of this is visible in the Record Types list. I use the view to list out the record types, and how many records are deleted and how many active.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Still a few missing features:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;The "Create View" button doesn't work yet (though shouldn't be too very hard to make)&lt;/li&gt;&lt;li&gt;"Save View" also doesn't work yet.&lt;/li&gt;&lt;li&gt;I want to add options for executing your view, such as Group and such.&lt;/li&gt;&lt;li&gt;I want to add a bit more data about each database, and whether you can sync it or not. I'm thinking about doing this on the server info tab, maybe an editable DictionaryGrid?&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Also, in terms of quickly-widgets, I hate writing the same notebook code for tabbed interfaces over and over again. We should have a quidget that makes it easy and fun, and handles close buttons on the tabs, has a free document menu, and has alt # navigation to documents.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6498182174829314483?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6498182174829314483/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-integrated-view-editor.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6498182174829314483'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6498182174829314483'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-integrated-view-editor.html' title='Slip Cover, Integrated View Editor for DesktopCouch'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S9DbDiR0yfI/AAAAAAAAATU/80ZOy8E3ucc/s72-c/Screenshot-Slip+Cover.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6178047718071734100</id><published>2010-04-18T10:36:00.000-07:00</published><updated>2010-04-18T10:36:24.065-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Desktop Couch View Editor</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8s-e4EJTtI/AAAAAAAAATM/XGDKOuRniO4/s1600/Screenshot-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8s-e4EJTtI/AAAAAAAAATM/XGDKOuRniO4/s320/Screenshot-1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5461527673283628754" /&gt;&lt;/a&gt;As I've been learning to use DesktopCouch, I've come to understand the key role that writing map and reduce functions play. Basically, a view is stored map function and optional reduce function. I still haven't quite figured out reduce functions, especially wrt rereduce.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Anyway, I was finding it way to tedious to iterate on views in iPython. A map or reduce function looks to DesktopCouch as a string, thought it is a function written in javascript. And there is no syntax highlighting, or really any help at all writing this function as they look like strings in gedit and iPython.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So yesterday, my ViewDesignDialog was born, and added to Slip Cover. Currently, it supports running a map/reduce function as a temp view. This has been very helpful for me already, as I've been able to finally write a map function gives me each record, it's record and type, and whether that record is active or deleted. I want this for the record types view in slip cover, so you can see if there are any active records associated with the record type. I might be able to go on and build an "undelete" feature which would be handy for undo actions in slip cover.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;I will add a "Copy as DesktopCouch" code next, which will just put the code on the buffer so you can paste it into your Python code as desired. Also, I'll add "Save". To do this I'll need to do some work with design_docs.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Eventually, I expect Slip Cover will use this so you can create new views, and edit existing views, for a database using Slip Cover, in addition to having a handy tool for generating DesktopCouch code (or just browsing a database I suppose).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's not integrated into Slip Cover to well atm. Maybe I'll get a chance to work more on this tonight, but I am plan to do some things that don't involve a keyboard for a while this weekend ;)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6178047718071734100?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6178047718071734100/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/desktop-couch-view-editor.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6178047718071734100'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6178047718071734100'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/desktop-couch-view-editor.html' title='Desktop Couch View Editor'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8s-e4EJTtI/AAAAAAAAATM/XGDKOuRniO4/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8993172386806029258</id><published>2010-04-15T12:00:00.000-07:00</published><updated>2010-04-15T08:58:07.487-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><title type='text'>Quickly For Lucid Release, Intro Video</title><content type='html'>Didrocks released quickly 0.4 yesterday. What a great contribution from didrocks! I suspect he his work will help tons of people have a really fun time writing Ubuntu apps. &lt;a href="http://blog.didrocks.fr/index.php/post/Quickly-0.4-available-in-lucid%21"&gt;Read about the release in his detailed blog post&lt;/a&gt;.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;In the meantime, I made a cheesy video last night, showing some of the changes in quickly, and how the new CouchGrid and GridFilter work. &lt;br /&gt;&lt;br /&gt;[Note that it takes blip.tv a bit of time to render out a high def video like this, so if the video is not yet working, you can check back later.]&lt;br /&gt;&lt;br /&gt;[D'oh .... stupid blip.tv bailed on encoding my video. I'll try again with smaller files. Stay tuned]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8993172386806029258?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8993172386806029258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-for-lucid-release-intro-video.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8993172386806029258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8993172386806029258'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/quickly-for-lucid-release-intro-video.html' title='Quickly For Lucid Release, Intro Video'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-1959566972911430394</id><published>2010-04-12T18:47:00.000-07:00</published><updated>2010-04-12T18:57:57.100-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Today's Slip Cover Feature: New Database</title><content type='html'>&lt;div&gt;So as planned, today I added the feature for creating databases. This was one of those sweet features where it ended up on the tip of previous coding, so it took literally a matter of minutes to implement. Just get a name from the users, create the database, then load it in the UI,&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;    def new_database(self, widget, data=None):&lt;br /&gt;     title = _("New database")&lt;br /&gt;     msg = _("Specify a name for the new database")&lt;br /&gt;     response, val = prompts.string(title,msg)&lt;br /&gt;     if response == gtk.RESPONSE_OK:&lt;br /&gt;         CouchDatabase(val, create=True)&lt;br /&gt;         self.load_db(self,val)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;So now I have an end to end tool for designing my desktopcouch databases.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Use File-&gt;New and make a name for my database:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POIMolvcI/AAAAAAAAATE/epMPKcu91d8/s1600/Screenshot-6.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 199px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POIMolvcI/AAAAAAAAATE/epMPKcu91d8/s320/Screenshot-6.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459433813528919490" /&gt;&lt;/a&gt;Get an empty screen for the database.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POHpTEYPI/AAAAAAAAAS8/7dHJ3wHaGTU/s1600/Screenshot-7.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POHpTEYPI/AAAAAAAAAS8/7dHJ3wHaGTU/s320/Screenshot-7.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459433804043411698" /&gt;&lt;/a&gt;Use the "+" to create a new record type.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POHUUx7wI/AAAAAAAAAS0/R-T5rgJa8aE/s1600/Screenshot-8.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POHUUx7wI/AAAAAAAAAS0/R-T5rgJa8aE/s320/Screenshot-8.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459433798413446914" /&gt;&lt;/a&gt;Use Add keys to add keys for record type.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POG6uv7SI/AAAAAAAAASs/8OInYoiKdWU/s1600/Screenshot-10.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POG6uv7SI/AAAAAAAAASs/8OInYoiKdWU/s320/Screenshot-10.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459433791543045410" /&gt;&lt;/a&gt;Then add rows and fill in the data.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POGpmzDnI/AAAAAAAAASk/o6Odqph4IrY/s1600/Screenshot-12.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POGpmzDnI/AAAAAAAAASk/o6Odqph4IrY/s320/Screenshot-12.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5459433786946293362" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-1959566972911430394?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/1959566972911430394/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature-new-database.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1959566972911430394'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/1959566972911430394'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature-new-database.html' title='Today&apos;s Slip Cover Feature: New Database'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8POIMolvcI/AAAAAAAAATE/epMPKcu91d8/s72-c/Screenshot-6.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2574681501236765364</id><published>2010-04-11T18:03:00.000-07:00</published><updated>2010-04-15T08:56:39.954-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Today's Slip Cover Feature(s): Add Record Types</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8J1hji32AI/AAAAAAAAASc/QmkWJ9Ae5nI/s1600/Screenshot-4.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8J1hji32AI/AAAAAAAAASc/QmkWJ9Ae5nI/s320/Screenshot-4.png" alt="" id="BLOGGER_PHOTO_ID_5459054917664102402" border="0" /&gt;&lt;/a&gt;Today I spent a few minutes to add an "Add Record Type" feature. As you can see, this was a matter of creating a button, and then in the handler, creating a single record with only that record type in it.&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def add_record_type(self, widget, data=None):&lt;br /&gt;       title = _("Add Record Type")&lt;br /&gt;       msg = _("""&lt;br /&gt;       Specify a string name for the record type. By convention, the&lt;br /&gt;       name should be a URL where you can document the user of the record type.&lt;br /&gt;       """)&lt;br /&gt;       response, val = prompts.string(title,msg,"http://wiki.ubuntu.com/")&lt;br /&gt;       if response == gtk.RESPONSE_OK:&lt;br /&gt;           r = Record({"record_type":val})&lt;br /&gt;           self.database.put_record(r)&lt;br /&gt;           self.record_type_grid.append_row({"Record Type":val,"editor":None})&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;After adding a record type, you can use add/delete rows/keys to full design and populate a data set. I'm kind of working inside out with regard to making a full desktopcouch designer. I started with modifying rows, then add/removing rows, then adding/removing keys, and now adding record types.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8J1hXolfNI/AAAAAAAAASU/tLZvkKl9410/s1600/Screenshot-5.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S8J1hXolfNI/AAAAAAAAASU/tLZvkKl9410/s320/Screenshot-5.png" alt="" id="BLOGGER_PHOTO_ID_5459054914466839762" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The next step is to implement New/Delete Database, and I'll a functional designer. I'll probably add Slip Cover to my PPA at that point.&lt;br /&gt;&lt;br /&gt;There is no "Delete Record Type" function. This is because desktopcouch never permanently deletes a record, it only marks it as deleted, and as such does not return it when you query for records. This has the odd effect of making Record Types last forever. I am considering adding a "deleted count", and an "active count" column to the grid for the record types. This would take more Map/Reduce skills than I have right now, so would probably be a good thing for me to learn.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2574681501236765364?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2574681501236765364/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-features-add-record.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2574681501236765364'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2574681501236765364'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-features-add-record.html' title='Today&apos;s Slip Cover Feature(s): Add Record Types'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8J1hji32AI/AAAAAAAAASc/QmkWJ9Ae5nI/s72-c/Screenshot-4.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4734054010497460447</id><published>2010-04-10T14:55:00.001-07:00</published><updated>2010-04-10T15:20:12.651-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Today's Slip Cover Feature(s): Deleting Keys, Refresh Button</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8Dz41T0ccI/AAAAAAAAASM/Iu17phU_bXQ/s1600/Screenshot-3.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8Dz41T0ccI/AAAAAAAAASM/Iu17phU_bXQ/s320/Screenshot-3.png" alt="" id="BLOGGER_PHOTO_ID_5458630906081472962" border="0" /&gt;&lt;/a&gt;Because I added "Add Key" yesterday, today I wanted to be symmetrical, so I added "delete keys". This turned out to be much more work. First, I ended up creating another quickly.prompt, and then I had to dive into the internals of CouchGrid, and also do some hair raising mucking with desktop couch.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;quickly.prompts.checklist()&lt;/span&gt;&lt;br /&gt;When the developer clicks the "delete keys" button, I wanted to present them with a list of keys they could choose from. And then have all of those get deleted. This seemed like the kind of thing that I'd want to use in other programs, so I decided to solve this problem generically by adding it to quickly.prompts.&lt;br /&gt;&lt;br /&gt;I accomplished this by deriving from quickly.prompts.Prompt, and also creating a helper function. CheckListPrompt works as you would expect for prompt. You set it up by passing in some configuration info, including a dictionary of strings as keys, which will be labels for the checkboxes, and a bool value to determine if the box is checked by default.&lt;br /&gt;&lt;br /&gt;You get back a response and val. The val is a dictionary of keys again, with bools for whether the checkboxes are active or not.&lt;br /&gt;&lt;br /&gt;So to use the CheckListBox, I just pass in a dictionary of the keys for the CouchGrid, and then see if any were selecct:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;       val = {}&lt;br /&gt;    for k in self.grid.keys:&lt;br /&gt;        val[k] = False&lt;br /&gt;    response, val = checklist(title, message, val)&lt;br /&gt;    if response == gtk.RESPONSE_OK:&lt;br /&gt;        #do stuff&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Hair Raising Munging&lt;/span&gt;&lt;br /&gt;Since "do stuff" is pretty destructive, I use a quickly.prompts.yes_no to confirm that the users wants to blow away all the data and screw up their database. Assuming they do want to delete the keys and values in the desktopcouch database, it turns out to be *not* easy to do the deletion without reading way into CouchGrid.  The issue here is the couchdb reserves anything staring with a "_" for itself. But DictionaryGrid uses "__" as a convention to determine that a key should be hidden in the grid by default. So as a result of this CouchGrid munges _id and _rev and record_type before it reads to and from the database.&lt;br /&gt;&lt;br /&gt;The second troublesome part was dealing with desktopcouch. It turns out that you can't just delete a key from a record. You have a delete the whole record and then create a new record without that key. so as a result the code deletes and recreates each and every row.&lt;br /&gt;&lt;br /&gt;I really think this code belongs inside CouchGrid:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def delete_keys_from_store(self, model, path, iter, keys_to_delete):&lt;br /&gt;     for k in keys_to_delete:&lt;br /&gt;         d = model.get_value(iter,len(self.grid.keys))&lt;br /&gt;         if k in d:&lt;br /&gt;             del(d[k])&lt;br /&gt;     if '__desktopcouch_id' in d:&lt;br /&gt;         keys = d.keys()&lt;br /&gt;         for k in keys:&lt;br /&gt;             if k.startswith("__desktopcouch"):&lt;br /&gt;                 dc_key = k.split("__desktopcouch")[1]&lt;br /&gt;                 d[dc_key] = d[k]&lt;br /&gt;                 del(d[k])&lt;br /&gt;             if k == "__record_type":&lt;br /&gt;                 d["record_type"] = d["__record_type"]&lt;br /&gt;                 del(d["__record_type"])&lt;br /&gt;         self.database.delete_record(d['_id'])&lt;br /&gt;         del(d["_rev"])&lt;br /&gt;         del(d["_id"])&lt;br /&gt;         self.database.put_record(Record(d))&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Who would ever be able to figure out to do all this?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Refresh&lt;/span&gt;&lt;br /&gt;So after this the refresh function was trivial. Just tell the CouchGrid to reset, and then recreate the grid:&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def refresh(self, widget, data=None):&lt;br /&gt;      self.grid._refresh_treeview()&lt;br /&gt;      self.remove(self.filt)&lt;br /&gt;      self.filt = GridFilter(self.grid)&lt;br /&gt;      self.pack_start(self.filt, False, False)&lt;br /&gt;      self.reorder_child(self.filt,1)&lt;br /&gt;      self.filt.show()&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;desktopcouch Editor&lt;/span&gt;&lt;br /&gt;So now with adding a removing records and keys, along with freshing, I have a functional desktopcouch editor. This tool has already proved a bit useful in getting a peak into certain database. However, I can't actually create new record types yet. Maybe tomorrow?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4734054010497460447?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4734054010497460447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-features-deleting.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4734054010497460447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4734054010497460447'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-features-deleting.html' title='Today&apos;s Slip Cover Feature(s): Deleting Keys, Refresh Button'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S8Dz41T0ccI/AAAAAAAAASM/Iu17phU_bXQ/s72-c/Screenshot-3.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-2013947069227873657</id><published>2010-04-09T20:25:00.000-07:00</published><updated>2010-04-09T20:44:04.996-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Today's Slip Cover Feature: Adding Columns</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7_wukF3JqI/AAAAAAAAASE/Q939dG2RXZs/s1600/Screenshot-1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7_wukF3JqI/AAAAAAAAASE/Q939dG2RXZs/s320/Screenshot-1.png" alt="" id="BLOGGER_PHOTO_ID_5458345956149241506" border="0" /&gt;&lt;/a&gt;I started out wanting to add "refresh". However, I just wiped my netbook and set up Lucid UNE Beta 2 on it, so only had one database in my local desktopcouch and it didn't have any records. I didn't know how I'd test the feature. I haven't set up my Ubuntu One yet on this computer, because I've been waiting for gwibber to get fixed (it is!) so that I could test out the Social From the Start Experience.&lt;br /&gt;&lt;br /&gt;This is a long winded way of saying that I decided to implement another feature first to make it easier for me to test refresh out, editing an existing database. The first step of this was to be able to add columns. So I added an "add column" button.&lt;br /&gt;&lt;br /&gt;I used quickly.prompts to get the string from the user. To actually add the column required a bit of intimate knowledge of the internals of DictionaryGrid. It occurred to me that folks might want to be able to add a key/column to a Grid themselves, so I logged &lt;a href="https://bugs.edge.launchpad.net/quidgets/+bug/559766"&gt;a bug to remind me to make this easier&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Since Fagan asked to see the code when I posts :) -&lt;br /&gt;&lt;pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"&gt;&lt;code&gt;    def add_column(self, widget, data=None):&lt;br /&gt;    response, val = prompts.string("New Column","Please select a name for the new column")&lt;br /&gt;    if response == gtk.RESPONSE_OK:&lt;br /&gt;        self.grid.keys.append(val)&lt;br /&gt;        self.grid._refresh_treeview()&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-2013947069227873657?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/2013947069227873657/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature-adding.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2013947069227873657'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/2013947069227873657'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature-adding.html' title='Today&apos;s Slip Cover Feature: Adding Columns'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7_wukF3JqI/AAAAAAAAASE/Q939dG2RXZs/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5416173942486304280</id><published>2010-04-08T16:34:00.001-07:00</published><updated>2010-04-08T16:44:02.860-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='quidgets'/><category scheme='http://www.blogger.com/atom/ns#' term='quickly'/><category scheme='http://www.blogger.com/atom/ns#' term='slip-cover'/><title type='text'>Today's Slip Cover Feature: Adding/Removing Records</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S75oJJZi64I/AAAAAAAAAR0/wEbN9w5ZV14/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 158px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S75oJJZi64I/AAAAAAAAAR0/wEbN9w5ZV14/s320/Screenshot.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5457914304770796418" /&gt;&lt;/a&gt;Took a few minutes of refactoring gtk code to push the UI around, but I added the ability to delete and rows to a desktopcouch database, by record type. Now I can remove annoying messages from my stream, or put fake ones in!&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Bonus Feature&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S75p0uErBfI/AAAAAAAAAR8/Fg6U0p5VqOM/s1600/Screenshot-1.png"&gt;&lt;img style="text-align: left;display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; cursor: pointer; width: 320px; height: 159px; " src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S75p0uErBfI/AAAAAAAAAR8/Fg6U0p5VqOM/s320/Screenshot-1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5457916152861361650" /&gt;&lt;/a&gt;&lt;/div&gt;I added a grid filter to the UI (in literally less than a minute using quickly.widgets.GridFilter&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5416173942486304280?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5416173942486304280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5416173942486304280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5416173942486304280'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/todays-slip-cover-feature.html' title='Today&apos;s Slip Cover Feature: Adding/Removing Records'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S75oJJZi64I/AAAAAAAAAR0/wEbN9w5ZV14/s72-c/Screenshot.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6560750541406411624</id><published>2010-04-08T09:48:00.000-07:00</published><updated>2010-04-08T10:02:20.282-07:00</updated><title type='text'>Slip Cover, writing my own dev tools</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S74I_Ae4PvI/AAAAAAAAARs/LNQ-JaB_YaE/s1600/Screenshot.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 161px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S74I_Ae4PvI/AAAAAAAAARs/LNQ-JaB_YaE/s320/Screenshot.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5457809676973915890" /&gt;&lt;/a&gt;I use desktopcouch quite a bit for the apps that I write. As a programmer, I want to be able to see into the database and confirm that my code is working, or make tweaks to the database so that I can test out new things. Also, I need to delete databases and records a lot.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Futon is the current tool provided for programmers to work with desktopcouch. However, I find it:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Not work all the time&lt;/li&gt;&lt;li&gt;Too complex UI wise&lt;/li&gt;&lt;li&gt;A Couch tool, not a desktopcouch tool (a subtle but important distinction)&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;Thus, I started a new project for myself, &lt;a href="https://edge.launchpad.net/slip-cover"&gt;slip-cover&lt;/a&gt;. Of course it's using Quickly 3.9.x ;) Also, the use of quickly-widgets made it very easy to boot strap this project. Though I am starting to see a need for some kind of quickly.widgets.DocumentTabs sort of thing.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;The first iterations allows you to:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;view some desktopcouch instance info&lt;/li&gt;&lt;li&gt; view records in a database by record type&lt;/li&gt;&lt;li&gt;Modify existing records&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;I want to add some features based on my needs, in about this order:&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;Ability to delete records&lt;/li&gt;&lt;li&gt;Ability to delete a database&lt;/li&gt;&lt;li&gt;Ability to add records&lt;/li&gt;&lt;li&gt;Ability to add a field to records&lt;/li&gt;&lt;li&gt;Ability to control whether a database syncs&lt;/li&gt;&lt;li&gt;Ability to view views&lt;/li&gt;&lt;li&gt;Ability to design views&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;Maybe a "feature a day" approach would work for this, especially as we close in on Lucid Final Freeze.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6560750541406411624?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6560750541406411624/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-writing-my-own-dev-tools.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6560750541406411624'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6560750541406411624'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/slip-cover-writing-my-own-dev-tools.html' title='Slip Cover, writing my own dev tools'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S74I_Ae4PvI/AAAAAAAAARs/LNQ-JaB_YaE/s72-c/Screenshot.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-6471570931203633888</id><published>2010-04-05T09:45:00.000-07:00</published><updated>2010-04-05T09:58:29.502-07:00</updated><title type='text'>Fixing a real bug with the help of Winpdb</title><content type='html'>Over the last few years as I have gotten more and more addicted to programming Python, I have basically stopped using debuggers. This is not because I write bug free code, but because I prefer not use any of the IDEs available for Python, and I really don't much get along with pdb. I have always found that I need to invest a lot of effort into pdb to get what I want from it, and so now I default to "print line" debugging (using print statements to dump state to the console).&lt;br /&gt;&lt;br /&gt;A couple of days ago, I decided to look into graphcial debuggers. I tried some of the Python IDEs out there, and they just really weren't for me. I saw Winpdb in the repositories, and decided to give it try. Today I have a bug that was a good test case for it. I imaging that I will be adding Winpdb to my toolkit on a more regular basis now.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;The Bug&lt;/span&gt;&lt;br /&gt;Over the weekend didrocks update quickly-widgets in universe for me. quikly-widgets provides the DictionaryGrid class, that I use in bughugger to display data from bdmurray's JSON searches. I check bughugger every morning to see what the desktop team is working on, and generally where we are in terms of the bugs assigned to use.&lt;br /&gt;&lt;br /&gt;So when I run bughugger this morning, I saw this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUBNU2_OI/AAAAAAAAAQk/0GOLR8PwskU/s1600/pic1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 283px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUBNU2_OI/AAAAAAAAAQk/0GOLR8PwskU/s320/pic1.png" alt="" id="BLOGGER_PHOTO_ID_5456695909502352610" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Oops, only columns. Obviously a bug in quickly-widgets broke bughugger, but where?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Starting the Debugger&lt;/span&gt;&lt;br /&gt;So I fired up the Winpdb. First thing is to select the file to launch. So I choose File -&gt; Launch, and navigate to the bin/buhgugger file. I select a line close to, but before where I want to break the program. Clicking in the channel there, are using F9 key, sets a breakpoint. The run button then runs that app and will stop at the first breakpoint it hits in the source.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUB0hSjbI/AAAAAAAAAQs/2dXhgamPwhA/s1600/pic2.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUB0hSjbI/AAAAAAAAAQs/2dXhgamPwhA/s320/pic2.png" alt="" id="BLOGGER_PHOTO_ID_5456695920023473586" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUCqzZeuI/AAAAAAAAAQ0/eRbOr_xeIcU/s1600/pic3.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUCqzZeuI/AAAAAAAAAQ0/eRbOr_xeIcU/s320/pic3.png" alt="" id="BLOGGER_PHOTO_ID_5456695934594939618" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Debugging&lt;/span&gt;&lt;br /&gt;After stopping at the first breakpoint, I see that I need to set a breakpoint in the  add_*_triage_page functions, as that's where bughugger starts to assemble the view. The "keys" variable determines what columns are created in the DictionaryGrid, so I decide to watch the keys get passed around. I can see that the keys are set correctly here.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUDCk_d7I/AAAAAAAAAQ8/YNf7d1Lrv1c/s1600/pic4.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUDCk_d7I/AAAAAAAAAQ8/YNf7d1Lrv1c/s320/pic4.png" alt="" id="BLOGGER_PHOTO_ID_5456695940976965554" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I use "run to line" and "step into" to see that the keys are still correctly set at when BugsPane is being created.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oWA4k_MWI/AAAAAAAAARc/98c-nIz8DG0/s1600/pic5.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oWA4k_MWI/AAAAAAAAARc/98c-nIz8DG0/s320/pic5.png" alt="" id="BLOGGER_PHOTO_ID_5456698102956110178" border="0" /&gt;&lt;/a&gt;So I run to where the DictionaryGrid is created. The fact that I depended on the order of arguments instead of using keywords looks a bit suspicious to me.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUVl9xuDI/AAAAAAAAARM/qGxN8WPPtk0/s1600/pic6.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUVl9xuDI/AAAAAAAAARM/qGxN8WPPtk0/s320/pic6.png" alt="" id="BLOGGER_PHOTO_ID_5456696259713808434" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;So I step into the constructor for the DictionaryGrid, and see that, in fact, I did change the order of the contructor parameters, so it's not surpriisng the app went a little haywire. I fixed the bug by changing the call to use keywords.&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUWSuMJKI/AAAAAAAAARU/KqiiousY5ik/s1600/pic7.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 200px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUWSuMJKI/AAAAAAAAARU/KqiiousY5ik/s320/pic7.png" alt="" id="BLOGGER_PHOTO_ID_5456696271728026786" border="0" /&gt;&lt;/a&gt;I think using Winpdb saved me tons of time and effort compared to using pdb, or print statement debugging. In any case, it was a matter of minutes to find this bug, do I could go on to fixing the next bug :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-6471570931203633888?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/6471570931203633888/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/04/fixing-real-bug-with-help-of-winpdb.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6471570931203633888'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/6471570931203633888'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/04/fixing-real-bug-with-help-of-winpdb.html' title='Fixing a real bug with the help of Winpdb'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7oUBNU2_OI/AAAAAAAAAQk/0GOLR8PwskU/s72-c/pic1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-5762871911848074054</id><published>2010-03-30T17:30:00.000-07:00</published><updated>2010-03-30T17:36:46.857-07:00</updated><title type='text'>My Lucid Quickly Task List</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7KXOpIgIzI/AAAAAAAAAQc/7KBnIpu2vig/s1600/Screenshot-12.png"&gt;&lt;br /&gt;&lt;/a&gt;I'm seeing if I can just recycle this post instead of create a whole new ones ...&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7KXOpIgIzI/AAAAAAAAAQc/7KBnIpu2vig/s1600/Screenshot-12.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 320px; height: 190px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7KXOpIgIzI/AAAAAAAAAQc/7KBnIpu2vig/s320/Screenshot-12.png" alt="" id="BLOGGER_PHOTO_ID_5454588376514569010" border="0" /&gt;&lt;/a&gt;Finished my last bit of Quickly related coding for Lucid just now. Got the quickly-widgets tests passing. I fixed a bug here and there along the way. Changes are pushed into trunk, next I'll ask didrocks to update universe.&lt;br /&gt;&lt;br /&gt;Of course, I still have 2 weeks to fix bugs if you find 'em ...&lt;br /&gt;&lt;div&gt;__________&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;We have entered the Lucid end-game. Which is great. Sadly, there were a whole mess of things for Quickly that I wanted to get done this cycle. I know I won't get to everything, but I can still do a lot. I decided to set out a list of goals, and to work on the list each night, hopefully picking one item off per evening.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I always consider work on Quickly to be related to, but not actually part of, my day job. Thus I enjoy working on it in my free time, but don't typically commit to accomplishing anything specific, because it is my free time, after all. However, let's see how this works.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is my tentative list (completed ones are crossed out):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Finish fixing pygame template indentation and comments.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Refactor pygame template to put screen size in the configuration file.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Create my own sample sounds for pygtame template.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Make pygtame template actually a template (add the string replacement functions and derive from ubuntu-application commands)&lt;/strike&gt;.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Remove CameraButton from quickly-widgets.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Finish quickly-widgets documentation.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Make quickly-widgets tests work (test apps currently work, but some of the tests fail erroneously).&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Fix the ubuntu-application template tutorial to use quickly.widgets.couch_grid instead of desktopouch.couch_grid.&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Update ubuntu-application tutorial to use new Quickly commands&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Update ubuntu-application tutorial screenshots&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Add PPA section to ubuntu-application tutorial.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;strike&gt;Write PyGame template tutorial&lt;/strike&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Make new videos for ubuntu-application.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Make videos for PyGame.&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;&lt;div&gt;Those 10+ things seem like good goals to start. If I missed something, etc... please provide a comment.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-5762871911848074054?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/5762871911848074054/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/my-lucid-quickly-task-list.html#comment-form' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5762871911848074054'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/5762871911848074054'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/my-lucid-quickly-task-list.html' title='My Lucid Quickly Task List'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S7KXOpIgIzI/AAAAAAAAAQc/7KBnIpu2vig/s72-c/Screenshot-12.png' height='72' width='72'/><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-376490989878144642</id><published>2010-03-27T20:23:00.000-07:00</published><updated>2010-03-27T20:27:27.644-07:00</updated><title type='text'>My "Just Works" Audio Experience in Lucid</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S67L436LEaI/AAAAAAAAAQM/7a43n_yjNsQ/s1600/Screenshot-2.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S67L436LEaI/AAAAAAAAAQM/7a43n_yjNsQ/s320/Screenshot-2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5453520376733307298" /&gt;&lt;/a&gt;I've been doing a lot of desktop based VOIP calls lately, so decided to invest in a slightly nice set of headphones. I noticed that when my computer's fan is running, it adds a lot of noise to my normal audio card input, so I decided to go with a USB headset. I booted up, plugged into a USB port, and pick "Sound Preferencecs..." from the Sound Menu, and there was my new headphones an mic. A couple of tests, and yes, it works perfectly. First time, no configuration. Sweeeeet.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-376490989878144642?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/376490989878144642/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/my-just-works-audio-experience-in-lucid.html#comment-form' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/376490989878144642'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/376490989878144642'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/my-just-works-audio-experience-in-lucid.html' title='My &quot;Just Works&quot; Audio Experience in Lucid'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_1Ko0Y6TOtbQ/S67L436LEaI/AAAAAAAAAQM/7a43n_yjNsQ/s72-c/Screenshot-2.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-7026977523821048393</id><published>2010-03-21T22:34:00.001-07:00</published><updated>2010-03-21T22:39:52.457-07:00</updated><title type='text'>sfxr for the blam.wav</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S6cB2e4fGGI/AAAAAAAAAQE/7s-iR1ReIdM/s1600-h/Screenshot-sfxr.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 240px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S6cB2e4fGGI/AAAAAAAAAQE/7s-iR1ReIdM/s320/Screenshot-sfxr.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5451327909469821026" /&gt;&lt;/a&gt;&lt;div&gt;I posted on some of the work I want to knock out for Quickly, and specifically regarding the PyGame template, there was this comment:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;Michael said...&lt;dd class="comment-body" style="margin-top: 0.25em; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "&gt;&lt;p style="margin-top: 0px; margin-right: 0px; margin-bottom: 0.75em; margin-left: 0px; "&gt;These look good. I just wanted to point out that if you haven't heard of it, sfxr (http://code.google.com/p/sfxr/) is often used in the indie game community and could help out with #3!&lt;/p&gt;&lt;/dd&gt;&lt;/span&gt;&lt;div&gt;So, yeah, sfxr is quite cool. Just took a bit of Googling to find the dependencies needed to "make" it. I jammed out some sound effects in a matter of minutes. Thanks sfxr dev(s)!&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-7026977523821048393?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/7026977523821048393/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/i-posted-on-some-of-work-i-want-to.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7026977523821048393'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/7026977523821048393'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/i-posted-on-some-of-work-i-want-to.html' title='sfxr for the blam.wav'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S6cB2e4fGGI/AAAAAAAAAQE/7s-iR1ReIdM/s72-c/Screenshot-sfxr.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4612493855311053702</id><published>2010-03-20T08:16:00.000-07:00</published><updated>2010-03-20T08:22:40.031-07:00</updated><title type='text'>XML Copy-Editor FTW</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S6Tm3a8UhRI/AAAAAAAAAPs/Ee3-UxDfC5Q/s1600-h/Screenshot-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 187px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S6Tm3a8UhRI/AAAAAAAAAPs/Ee3-UxDfC5Q/s320/Screenshot-1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5450735288824661266" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Picking up where Fagan left off on the Quickly ubuntu-application tutorial, I'm writing a ubuntu-pygame tutorial using docbook format. The last time I tried available XML editors, the ones I found were crashy, or didn't offer much above and beyond Gedit, so I ended up editing XML in Gedit.&lt;br /&gt;&lt;br /&gt;I tried again in Lucid, and found XML Copy-Editor in software-center. I love it! I'm getting statement completion for the schema currently in use (see screenshot above), and error highlighting when I make mistakes. It's also fast and rock solid on my mini 10v! &lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S6Tm3gwtLKI/AAAAAAAAAP0/cy0m6ZCw7KQ/s1600-h/Screenshot-About+XML+Copy+Editor.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 311px; height: 320px;" src="http://2.bp.blogspot.com/_1Ko0Y6TOtbQ/S6Tm3gwtLKI/AAAAAAAAAP0/cy0m6ZCw7KQ/s320/Screenshot-About+XML+Copy+Editor.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5450735290386558114" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Interesting that it's built in wxWidgets. I wonder if they target this to be Cross Platform?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4612493855311053702?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4612493855311053702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/xml-copy-editor-ftw.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4612493855311053702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4612493855311053702'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/xml-copy-editor-ftw.html' title='XML Copy-Editor FTW'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S6Tm3a8UhRI/AAAAAAAAAPs/Ee3-UxDfC5Q/s72-c/Screenshot-1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8433862081330117784</id><published>2010-03-15T09:39:00.000-07:00</published><updated>2010-03-15T10:26:44.844-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ubuntu'/><category scheme='http://www.blogger.com/atom/ns#' term='bughugger'/><title type='text'>Beyond Release Blockers (also a bit on bughugger and f-spot)</title><content type='html'>&lt;b&gt;Release Blockers and Beyond&lt;/b&gt;&lt;br /&gt;Every release the various ubuntu teams focus on "release blocker bugs". These are bugs that are "High" or "Critical" in importance, and are targeted to the release. Of course they also have to be assigned so that someone knows about them and can work on them.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Of course, every release the desktop team fixes lots of bugs that are not release blockers. seb128 is leading an effort to organize the team to focus on the "right" set of these non-release blockers to fix. These are personal goals that the desktop team members sets for itself.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Process&lt;/b&gt;&lt;/div&gt;&lt;div&gt;seb128 is combing through bugs that he considers addressable, and assigning the ones that he thinks will be most valuable to users if fixed to canonical-desktop-team. He sets the importants and targets to Lucid as well. pitti will then assign to specific engineers as needed. Of course, anyone is free to fix these bugs.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Bughugger&lt;/b&gt;&lt;/div&gt;&lt;div&gt;I am using bugger to watch this project. The version of bughugger that is currently packaged in universe calculates gravity and looks for attached patches. Unfortunately this makes the queries take so long, that it is basically impossible to use. (I've fixed this in my local copy, but haven't pushed the changes yet. By "fix" I mean removed the slow features.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;However, the JSON searches are working very well. From the bughugger search menu, choose JSON Searches, and you'll get a list of searches that run on a cron job on bdmurray's server.&lt;/div&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S55rM2WwZdI/AAAAAAAAAPc/d89fKeDBn3I/s1600-h/json_searches.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 178px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S55rM2WwZdI/AAAAAAAAAPc/d89fKeDBn3I/s320/json_searches.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5448910467658442194" /&gt;&lt;/a&gt;After the results load, I can look for bugs assigned to the Canonical Desktop Team.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S55rtmqNBGI/AAAAAAAAAPk/HZom_IjWs_4/s1600-h/Screenshot-5.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 190px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S55rtmqNBGI/AAAAAAAAAPk/HZom_IjWs_4/s320/Screenshot-5.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5448911030380725346" /&gt;&lt;/a&gt;Because we worked in three iterations for Lucid, and minimized work for the third iteration, I am hopeful that we have a bit more time for bug fixing this cycle. However, even if the amount of bug fixing stays the same, I expect that having seb128 on point for choosing the highest impact bugs organized in a single list will help the best set of bugs get fixed by the desktop team, enhancing the general community quality efforts.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;F-Spot&lt;/b&gt;&lt;/div&gt;&lt;div&gt;On a side note, RAOF has recently joined the desktop team as a Canonical supported employee! One of his first tasks was to implement the f-spot features required for the &lt;a href="https://blueprints.edge.launchpad.net/ubuntu/+spec/desktop-lucid-default-apps"&gt;default application selection blueprint&lt;/a&gt;. F-spot now handles in situ editing of files. Which means no need to load images into the library to edit them, and no need to load up the gimp just do a quick crop! I cropped these screen captures using f-spot and it worked swimmingly. Thanks RAOF!&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8433862081330117784?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8433862081330117784/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/beyond-release-blockers-also-bit-on.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8433862081330117784'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8433862081330117784'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/beyond-release-blockers-also-bit-on.html' title='Beyond Release Blockers (also a bit on bughugger and f-spot)'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S55rM2WwZdI/AAAAAAAAAPc/d89fKeDBn3I/s72-c/json_searches.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-4638363075665926151</id><published>2010-03-14T16:02:00.000-07:00</published><updated>2010-03-14T16:04:20.702-07:00</updated><title type='text'>Ink Scape + PyGame = Fun</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S51rO6Y69RI/AAAAAAAAAPM/6Xd62oCZM6w/s1600-h/Screenshot-pygame+window-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 256px; height: 320px;" src="http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S51rO6Y69RI/AAAAAAAAAPM/6Xd62oCZM6w/s320/Screenshot-pygame+window-1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5448629028124292370" /&gt;&lt;/a&gt;Dropped in some new image files while working on the PyGame template tutorial&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-4638363075665926151?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/4638363075665926151/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/ink-scape-pygame-fun.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4638363075665926151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/4638363075665926151'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/ink-scape-pygame-fun.html' title='Ink Scape + PyGame = Fun'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_1Ko0Y6TOtbQ/S51rO6Y69RI/AAAAAAAAAPM/6Xd62oCZM6w/s72-c/Screenshot-pygame+window-1.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-8922203017947468584</id><published>2010-03-10T21:07:00.000-08:00</published><updated>2010-03-10T21:17:35.313-08:00</updated><title type='text'>Quickly Task List Progress</title><content type='html'>Well, Monday night I went out for beer with my wife. Last night I cooked dinner and spent time with my kids. So, good nights, but no progress on my &lt;a href="http://theravingrick.blogspot.com/2010/03/my-lucid-quickly-task-list.html"&gt;Quickly Task List&lt;/a&gt;.&lt;br /&gt;&lt;div&gt;However, I made some good progress #5 and #6 today. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Well, #6 was just: $bzr rm camera_button.py, so not much accomplishment there.&lt;/div&gt;&lt;div&gt;However, I made solid inroads for #5, documentation for #quickly-widgets. I got PressAndHoldButton totally documented, and did the module documentation for grid_column.py, which included a bit of work to document how to create a custom column for a DictionaryGrid.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Speaking of DictionaryGrid, I got a question from a developer using it about how to respond to edits in the DictionaryGrid. &lt;a href="https://bugs.edge.launchpad.net/quidgets/+bug/536678"&gt;I realized that I did not make this easy&lt;/a&gt;. It's easy to enable the UI for editing, but in order to receive a signal when a change occurred required iterating though the columns and attaching to each column's edited (or toggled) event. So I fixed this by adding a "cell-edited" signal and that's in trunk now.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It's so important to be getting feedback from users. Otherwise, you don't know the right use cases, and you can miss obvious things like this.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7497847932106835950-8922203017947468584?l=theravingrick.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://theravingrick.blogspot.com/feeds/8922203017947468584/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://theravingrick.blogspot.com/2010/03/quickly-task-list-progress.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8922203017947468584'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7497847932106835950/posts/default/8922203017947468584'/><link rel='alternate' type='text/html' href='http://theravingrick.blogspot.com/2010/03/quickly-task-list-progress.html' title='Quickly Task List Progress'/><author><name>Rick Spencer</name><uri>http://www.blogger.com/profile/16312439500490748313</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7497847932106835950.post-320539688481708124</id><published>2010-03-07T07:41:00.001-08:00</published><updated>2010-03-07T07:46:53.426-08:00</updated><title type='text'>New Themes Looking Nice with UNE</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S5PJ31NrlZI/AAAAAAAAAO8/FUFEOlE3oeo/s1600-h/Screenshot-1.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 200px;" src="http://4.bp.blogspot.com/_1Ko0Y6TOtbQ/S5PJ31NrlZI/AAAAAAAAAO8/FUFEOlE3oeo/s320/Screenshot-1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5445918335435511186" /&gt;&lt;/a&gt;&lt;div&gt;I &lt;a href="http://theravingrick.blogspot.com/2010/02/une-on-my-desktop-i-likes.html"&gt;posted about running UNE on my desktop&lt;/a&gt; a week or so ago. This morning due to some East -&gt; West jet lag I ended up waking up a bit early, so I dist-upgraded my desktop while drinking my coffee.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Since I had tinkered with the theme previously, it was not automatically applied. I chose Radiance since it is sunny in my office in the mornings. I changed to the new desktop wallpaper as well.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I like how the wallpaper suffuses the launcher with the new color scheme. And I am finding the new window decorators and colors to be quite nice as well. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Funny how I currentl
