Showing posts with label photobomb. Show all posts
Showing posts with label photobomb. Show all posts

Tuesday, August 23, 2011

A Photobomb Sale!



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.

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.


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.

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.

Wednesday, August 17, 2011

Cha-ching!



So, today I uploaded a special version of Photobomb to my PPA. 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:
  • I wish you could drag into from the desktop or other apps.
  • I wish that the app didn't block the UI when you click on the Gwibber tab when Gwibber isn't working.
  • I wish the local files view had a watch on the current directory so that it refreshed automatically.
  • I wish inking was smoother.
  • I wish you could choose the size of the image that you are making.
  • I wish that you could multi-select in it.
But alas, if I wait until it has everything and no bugs, I'll never release.

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.

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.

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).

I found it remarkably easy to submit photobomb to the Software Center. I just used the myapps.ubuntu portal, 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.

I'll keep everyone posted on how it goes!

Saturday, August 6, 2011

The End of an Era?

Today I committed and pushed a change to Photobomb to remove 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.

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.

Both services switched to newer APIs. The thing is, both Google and Yahoo! now charge an application for using those APIs. Yup, I would have to pay actual money to use the services.

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.

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 :/

Saturday, June 18, 2011

Coding an Undo/Redo Stack


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 TextEditor Quidget. 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.

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:
  1. Keep a list of actions as functions along arguments to pass to those functions.
  2. Keep a list of "backups" for every change made to an object, and on redo swap out the backup for the actual object.
List of Actions Approach
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:
     cmd = {"action":"delete","offset":iter.get_offset(),"text":text}
self._add_undo(cmd)

The _add_undo function merely ensures that the list does not grow beyond a defined maximum, and adds it to the undo list:
   def _add_undo(self, cmd):
if self.undo_max is not None and len(self.undos) >= self.undo_max:
del(self.undos[0])
self.undos.append(cmd)
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.
     undo = self.undos[-1]
redo = self._do_action(undo)
self.redos.append(redo)
del(self.undos[-1])
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.
     if action["action"] == "delete":
self.get_buffer().delete(start_iter, end_iter)
action["action"] = "insert"
elif action["action"] == "insert":
self.get_buffer().insert(start_iter, action["text"])
action["action"] = "delete"
return action
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.

List of Backup Approach
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.

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.

To store an undo in this system, I make a back up of the object, change it, and then add the undo:
       saved_item = self.back_up_item()
self.selected_item.move(delta_x,delta_y)
self.add_undo(self.selected_item, saved_item)
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.
   def back_up_item(self, item=None):
if item is None:
item = self.selected_item
copy = item.duplicate(True, False)
copy.remove()
index = self.z_order.index(item)
self.z_order.insert(index+1,copy)
return copy
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.

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.

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.

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.
   def add_undo(self, new_item, old_item):
#ensure proper undos for multiple edits of the same object
if len(self.undos) > 0:
for item in self.undos[::-1]:
if item["new_item"] is new_item:
item["new_item"] = old_item
break
self.undos.append({"new_item":new_item,
"old_item":old_item})
self.redos = []
Note that I also reset the redo stack whenever the undo stack is modified.

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:
     undo = self.undos[-1]
self.redos.append(undo)
del(self.undos[-1])
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.
     if undo["new_item"] is not None:
undo["new_item"].remove()
if undo["old_item"] is not None:
undo["old_item"].set_property("parent",self.__root)
next_item = self._find_item_above(undo["old_item"])
if next_item is not None:
#position in z-order
undo["old_item"].lower(next_item)
self.selected_item = undo["old_item"]
self.__reset_selection()
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.

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.

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.
   def pah_button_pressed(self, button, action):
old_item = self.back_up_item()
tick_signal_id = button.connect("tick",action)
button.connect("released",self.pah_button_released, (tick_signal_id, old_item))
Then in the released signal, I add the new item and the old_item to the undo stack, assuming it hasn't been already.
   def pah_button_released(self, button, args):
tick_signal_id, old_item = args
#work around released signals being called multiple times
if tick_signal_id in self.__disconnected_tick_signals:
return
self.__disconnected_tick_signals.append(tick_signal_id)
button.disconnect(tick_signal_id)
if self.selected_item is not None:
self.add_undo(self.selected_item, old_item)
Now only 1 undo is added when a Press and Hold Button is used.

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.

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.
           old_item = self.back_up_item(clicked_item)
self.__mouse_up_handler = self.__goo_canvas.connect("button_release_event",self.drag_stop, old_item)
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.

Finishing Touch
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.
   def edit_menu_reveal(self, widget, data=None):
active_undos = len(self.undos) > 0
active_redos = len(self.redos) > 0
self.builder.get_object("menuitem_undo").set_sensitive(active_undos)
self.builder.get_object("menuitem_redo").set_sensitive(active_redos)

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 photobomb trunk.

Wednesday, December 29, 2010

This is Photobomb


The last couple of days I made some good solid progress on Photobomb:
  1. Refactored items on the goocanvas into PhotobombItems to make changes easier, and code easier to maintain.
  2. Fixed the Gwibber tab to pull images from the Gwibber sqlite database.
  3. Removed Python threads from throughout the application while keeping the UI from freezing during long running actions (thanks gobject.idle_add!).
  4. Added a "download-error" event to UrlFetchProgressBox and handled download errors better.
There are still a few things I want to get to, but I am making steady progress.

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.

Here's a 5 minute video I made to try to help explain Photobomb ...

Monday, December 27, 2010

New Personal Goal: Photobomb for Natty


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.

Things I accomplished so far:
  1. 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.
  2. 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.
  3. 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.
  4. 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.
Here's a quick video showing the new layout and the duplicate function in action:


There's still a lot on my Todo list before I'll consider Photobomb ready for general availability.
  1. 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.
  2. 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.
  3. 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.
  4. I will change the Gwibber page to use libgwibber, and also the poster button to use libgwibber.
  5. 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.
  6. I want to add an undo/redo stack (which should be lots easier after I'm done with the PhotobombItem refactoring).
  7. 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.
  8. 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.
  9. 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.
If you want to play with latest Photobomb, get it from trunk.

Wednesday, February 10, 2010

Closing the Photobomb Chapter, and Now Back To Quickly Widgets

Last week I stopped blogging because I was hanging with the Ubuntu Platform team at our every-mid cycle "release sprint". I didn't have any free time, too busy, so no time to blog.

However, just because I wasn't blogging, doesn't I haven't been getting some work done on my projects.

Photobomb
First, I did some work on photobomb, but using the new gwibber widget that kenvandine created. As you can see, it's much nicer looking, but also has built in protocol selectors. It's the same widget that Gwibber uses, in fact.


The code is much nicer as well. I deleted all of my code for displaying the widget and replaced it with this:
     poster = widgets.GwibberPosterVBox()
poster.input.connect("submit",self.submitted)
self.add(poster)
self.show_all()
poster.input.set_text(contents)
Just create a poster object and add it to your window or dialog. Sweeeeet! FTW! If you want to add the ability to broadcast to your app, that's it, literally a few lines of code.

Quidgets
Creating Photobomb has been fun, but it's time for me to turn my attention back to Quidgets for a while.

First thing is, Quidgets is no longer "Quidgets". It is now "Quickly Widgets", because it is in the package "quickly-widgets" which is now available in Universe, thanks to didrocks. The launchpad project is still called "Quidgets", but I'll probably change that soon as well.

In any case, I've been spending my extra time the last couple of days porting CouchGrid to Quickly Widgets. And by this I mean CouchGrid is now a subclass of DictionaryGrid and is part of the Quickly Widgets library. Deriiving CouchGrid from DictionaryGrid was a lot of work but it has a huge benefit. All the automagic persistence of CouchGrid, with all the Goodness of DictionaryGrid. So here's the test up showing that CouchGrid now handles all the same column types DictionaryGrid handles.
I haven't tested it with GridFilters yet, but it's good to save something for tomorrow night :) I had to change the internals of DictionaryGrid a bit, so I hope that apps that use it won't break when I update it. I'll test it out a bit first and see.

Next Up
Now that CouchGrid is substantially done, there is *a lot* of work I would like to do on the Quickly Widgets library. However, before I embark on that, I think it's time to document Quickly Widgets really well. I have had some thoughts about how to design the library (and some changes that causes) and also to document it. I'm thinking about doing a "document a day" kind of thing, where I document one part of the Quickly Widgets library each day.

While I was at the sprint Brian Murray demoed Bughugger to the platform team. The #1 request was for an offline mode. Well, seems that CouchGrid is perfect for that, and since a CouchGrid is really just a special DictionaryGrid, it should be pretty easy for me to add that capability. But I think I'll do the documentation first.

Friday, January 29, 2010

Tonight's Photobomb Feature(s): More Gwibber Integration

I took a day off from work today. I slept in, I spent some time with my family, and of course, I worked on Photobomb! Specifically, I got a feature working that I actually started yesterday, but it took me a while to figure out.

Since gwibber stores both account information and messages in desktopcouch in the latest and greatest for Lucid, I was able to leverage the account set up and create a social application without the need to write all of the tedious social network protocol connecting code myself.

If you see there is a new tab with the Gwibber icon. If you refresh this tab, it will display all of the images that are in messages in your feeds, and also, all of the photos from the last few days that are tagged with one of your friends in Facebook. So this will pick up all the feeds that you have configured in Gwibber, plus the extra Facebook info.

To get the images from the messages, I just have to look in the couchdatabase and extract what I need. I do it like this:

self.__record_type = "http://gwibber.com/couch/message"
self.__database = CouchDatabase("gwibber_messages", create=False)
if self.__database is None:
return None
results = self.__database.get_records(record_type = self.__record_type, create_view = True)
for result in results:
document = result.value
if "images" in document:
#add the images to the tab
The code for adding the images is rather application specific. However, the rest of it is very applicable to anyone who wants to write apps that use messages from feeds. Essentially, by using desktopcouch, Gwibber now by default has a very straight forward API for reading received messages.

It's pretty easy to see what to query for and such, because of course being in desktopcouch means that you can use futon (a local web page that lets you explore your desktopcouch db):

Currently all of the facebook code is in Photobomb. But this is not a good place for it, because in order to make it work without extra configuration, I have to use the session info and application id for Gwibber. This is probably not a great way to do it. Rather, I will create a patch and ask Segphault to merge it into the Gwibber code base, and then everyone can just make a call like gwibber.get_images() and get back a useful data structure. I will be very happy if Segphault accepts the path, but not too surprised if he doesn't as he probably has a pretty accurate sense of my coding abilities ;) In any case, he'll see what i am trying to do, and perhaps be able to implement the feature better than I can. Otherewise, I'll just go back and try to figure out a better way to do it without using the Gwibber application code.

I ended up using Facebook Query Language to efficiently get just the right set of images. In case you are interested, after creating a facebook object using the python facebook wrapper, you can do stuff like this:
      fql = "select owner, src, src_big,caption from photo where pid in (select pid from photo_tag where subject in (select uid2 from friend where uid1=" + str(uid) + "))"
fql += " and (" + str(int(now)) + "-modified)/86400 < response =" fb.fql.query(fql)" pixbuf =" self.download_to_pixbuf(r[">
This feature of pulling images from your feeds compliments the feature that I implemented a couple of weeks ago that allows you to microblog your images. Together these features show that the Ubuntu Desktop is starting to bear some real fruit from the Social From The Start efforts.

Tuesday, January 26, 2010

Tonight's Photobomb Feature(s): Selectable Directories, Icons

Photobomb has hit a point where adding new features takes a bit longer than it used to because it is now necessary to refactor the code before it is manageable to add new features. In this case, the code for creating the list of images in the Pictures folder at start up was just part of the code for the main window. The fact that it was mixed in with tons of other code made it hard to maintain and enhance.

Last night I wanted to enhance the directory tab by making it so that users can select any directory they want. This is important to me as I wanted some way to get directly to pictures on a camera. By allowing the user to navigate the camera's directory, I could provide access, although in an admittedly cheesy and file centric manner. In the picture above, I am looking at pictures from the memory card from my wife's camera. It would be sweet to have proper camera integration some day.

It took me a couple of hours last night to move the tab into it's own class, as well as to actually implement the functionality for selecting and loading the directory. But now it's easy for me to work on the directory loading function. Note that directory selection is achieved via a new Quidget that I added. quidgets.prompts.choose_directory() yields a path to a directory. There is another refactoring to be done, which is to extract a base class for all of the tabs. However, I am refraining from doing any refactoring, unless it is required to add features.

Anway, after all was said and done, I had a functional tab. However, I was not too happy with how it looked. The folder symbol was next to, but not part of the button. And the tabs are plain with just words, but worse, I named the first tab just "disk", which is descriptive I guess, but just seems awkward.




So tonight I dinked around a bit with icons. I put the folder icon inside the button, and I used theme icons and some border_width to achieve some nicer buttons. There's still room for another button or two.

After a while of working on refactoring last night, I spent some time working on an icon for Photobomb. That's supposed to be camera icon with a bomb instead of a lens. I just created this with Inkscape, and then used glade to set the window icon to my new svg. I don't mean that as an insult to Inkscape. The crappiness of the icon is due to my ineptitude with anything artistic, Inkscape is awesome.

Saturday, January 23, 2010

Tonight's Photobomb Feature(s): Push and Hold Buttons, Simpler Web Tab


Yesterday afternoon I started to implement "Press and Hold" for certain buttons. Like the the "bigger/smaller" buttons, the "less/more opacity" buttons, and the rotation buttons. What I wanted (and now have) is that you can just press down on the button and hold it down. While it is held down the button keeps working, like the selected item keeps getting bigger the longer the hold it down.

Unfortunately, it turns out that gtk.ToolButton don't send pressed and released signals, just "clicked". So I went ahead and reimplemented the buttons for photobomb using regular buttons. Thus it took me a few hours instead of a few minutes to implement that features.

I actually added a Quidget for the press and hold buttons, and is it seemed like useful functionality that I could make a simple API for. To create a PressAndHoldButton, you just create a button as normal, but you pass it an action to perform while depressed. Here's how I created PressAndHoldButtons with an image. "action" is a function passed in to fire repeatedly as the button is held down:
     button = PressAndHoldButton(action)
image_path = getdatapath() + "/media/" + icon
img = gtk.Image()
img.set_from_file(image_path)
button.set_image(img)
button.show()
I suppose that I should really change the API so that you connect a function to a signal that the button fires, for example "tick". This would be more consistent with other PyGtk widgets. It would also developers to tie multiple functions to the button. In any case, the PressAndHoldButton manages a gobject timer internally, as the developer, all you need to do is give it a function.

Anyway, there's one more Quidget, and also while I was at it, I cleaned up the interaction of the clip tool and the pen somewhat, especially in terms of the most cursors they set. I also added buttons for saving a file and micro-blogging. I sniped the Gwibber svg file and sized it down to 20 x 20.

I also modified the web tab somewhat. Now instead of three separate buttons that each bring up a different prompt, there is a single text edit field. The way it works is to first check if the entered text starts with "http:". If it doesn't, the page assumes it's a search, and searches for images matching the keywords. If it does start with "http:" then does it end with the suffix of an image type? If so, it tries to just download that image. Otherwise, it assumes it's a web page, and tries to scrape the page.

Thursday, January 21, 2010

Tonight's Photobomb Feature: Screen Scraping

Today I added another button to the web tab. This button lets users plug in a web url, and then photobomb will try to display all the images on that page. So for example, I've screen scaped cute overload to grab a couple of the pictures.

I was able to do this efficiently despite my complete ineptitude with regular expressions because of strycore. strycore is a fellow Ubuntu community member who took over a silly little program that I started called lolz. I had written a bunch of crappy screen scraping code using split(whatever) to get images from funny sites. strycore wrote some solid regex based code that I was able to snipe and modify for more general screen scraping.

As you can see, I am completely out of ideas for easy ways to make the buttons. I suppose I should create something that looks like a search, something that looks like an image on a page, and something that looks like a web page. Oh well, in the meantime, at least I have tooltips :)

Speaking of tooltips, I haven't been *totally* slacking on photobomb. The other night I added tooltips so you can see the file names (or URLs) of images in the get images tab area.

Monday, January 18, 2010

Today's Photobomb Feature: Layout and PPA Availability

Today is a US holiday, so I spent a bit of it working on Photobomb (of course). Note that I tweaked the layout a bit, creating an attached panel on the side for all of the buttons. Photobomb currently fits pretty precisely into my Dell Mini 10v screen, and I intend to keep it that way. I also want to limit the number of features that are not accessible from the buttons. That means that I have a limited space, and there for limited features. I think that is a good thing. Photobomb should only have the bare necessary number of features to make it easy and fun to use!

Meanwhile, I've uploaded the latest version of quidgets and then photobomb to my PPA. If you want to play with photobomb, you'll need to be running lucid, and you'll need to be update enough to have the latest pygame. So:

1. Add my PPA
2. Install Quidgets
3. Install Photobomb

You'll fine photobomb in the Accessories menu (because I didn't tweak the setup.py file *at all*).

Of course I used Quickly to upload to my PPA! I am getting better and faster at setting my computers with my keys and everything else necessary to use a PPA.

Sunday, January 17, 2010

Tonight's Photobomb Feature: Web Searches

Todays' feature allows you to easily import images from the web into photo bomb. You can search for a term, in which case you get back 20 matches which you can add to your photobomb with just a click. If you know a specific image that you want, you can get just that URL. You can choose a web search or just a specific url by clicking on the toolbar in the tab. This puts up a prompt for you to type into.

Yesterday I implemented the Web Cam tab by creating a (poorly named) camera_button Quidget and then adding it to the notebook. I took the OO approach a bit further today and implemented the who search tab as a subclass of gtk.VBox, which interacts with the main program through signals.

I suspect that an OO refactoring may be in the future. I can imagine making classes for each type of object added to the goocanvas, as well as classes for each type of import/export UI.

For the record, the branch is here.

Saturday, January 16, 2010

Tonight's Photobomb Feature: WebCam Support

Last week Seb128 uploaded pygame 1.9.1 to Lucid universe. This was great for me, because pygame 1.9 has fun and easy webcam support.

So today, while lying around recovering from a bit of jet lag, I created a widget that wraps up the pygame webcam support with pygtk. So the contents of the Web Cam tab in the screen shot above is actually from Quidgets, quidgets.widgets.CameraButton. (probably not the best name, but it when I started it, it was just a button that you clicked on, without other controls.

There are a few steps to using CameraButton:

1. Create it and connect to the "picture_taken" event:
     cb = CameraButton()
cb.connect("picture_taken",self.add_image_from_pixbuf)
2. Start it capturing:
 cb.set_capture(True)
3. Respond to picture_taken events and use the supplied pixbuf. So really, it's about 3 lines of code to get web cam support set up with quidgets.

After you have the pixbuf from the camera, you are on your own to do with it what you will. For me, it was easy, as already had code for handling pixbufs read from the disk, so I just needed to do a bit of refactoring and it worked without new code.

It's fresh, barely tested, code. You can get it from the trunk if you want to play with it.

About Quidgets
Quickly + Widgets = Quidgets
There is a Launchpad Project for Quidgets
The most up to date changes are in the Quidgets Trunk Branch

Back from Paris with New Photobomb Features

It was a long couple of flights to Seattle from Paris. Naturally, I passed some of the time working, but I also passed some of the time working on Photobomb ;)

In the above screen shot, you can see three features that I added:
1. First, I added opacity to ink and text objects. Notice the "highlight" effect over "Monkey Attack". I added that by inking over the text, setting the ink color, and then decreasing the opacity until it looked kind of like highlighter. This doesn't work on images (yet), just ink and text so far.

2. I changed the layout of the window a bit. Particularly, I put the Picture directory pictures in a tab, as I plan to add features that include other sources there. While I was at it, I put the loading of the pictures on a thread. Note that I used on my Quidgets for that, AsynchTaskProgressBox. This is a handy widget which you can show or not, but it basically handles setting up thread code for you.
     pic_task = AsynchTaskProgressBox(self.__load_pics)
pic_task.start()
def __load_pics(self, params):
images = []
#lots of slow code that will be run in a thread ....
Those two lines are all it takes to run a function in a thread. AsynchTaskProgressBox has some other features as well, like you can pass parameters to your thread, you can kind of cancel the thread, and you can display it as a widget just by adding it to a Window or Dialog, and calling show().

I saw some postings on planet.ubuntu over the last few days regarding threaded programming in Python. Perhaps Quidgets can help make it a bit more easy and fun.

3. I also added a cropping tool. That thing in the lower right is supposed to look like a scalpel. It's used by drawing a line around a picture, and then the picture gets cropped. Here's what it looks like as I am cropping the wise old monkey:

I also tweaked some interactions with buttons and such here and there.

About Quidgets
Quickly + Widgets = Quidgets
There is a Launchpad Project for Quidgets
The most up to date changes are in the Quidgets Trunk Branch

Thursday, January 14, 2010

Tonight's Photobomb Feature(s): Pictures Dir View (and layout and better ink)

The main feature that I implemented was that your pictures directory gets scanned, and all the pictures get added as buttons ready to be clicked. Clicking the button adds the pictures to the goocanvas.

I spent a few minutes creating an annotations toolbar (though just noticed I did not move the color button to it).

Also, I changed the data string for the ink paths to use bezier curves instead of just being lines. This makes the ink a little nice.

Wednesday, January 13, 2010

Tonight's Photobomb Feature: Microblogging


I took a pause from the opacity feature for items to add the ability to microblogging. After creating your image, you can choose "Microblog" from the File menu.

The way it works is to save the image to a temp file, and upload that to imgur. Then it opens a dialog that allows you to send the created url to your followers through gwibber.

You can add some text too of course, if you want.

I'd be interesting in knowing what the *right* image upload service is. imgur has a good api and good documentation for it, so that's why I chose it to start. Would be sweet if there was a UbuntuOne way to do it.

Tuesday, January 12, 2010

Tonight's Photobomb Feature: Transparent Selection


Last night I added selection indication. Tonight I made the selection boxes mostly transparent. This means that I know how to do opacity, so I've started adding the ability to set object transparency as well.

Note that I sniped the code for setting the transparency from Segphault' s GrabberSnap project (same place I got the "save as .png code").

Monday, January 11, 2010

Tonight's Photobomb Feature: Selection





I'm in Paris for a mini-sprint. Right now, I'm Chilling in the lobby of the hotel, as he chair in my room is making my back hurt.

So I added a little gray rectangle to show what item is currently selected in photobomb ...

Friday, January 8, 2010

photobomb can has lolz

Tonight's Photobomb Feature: Text

Tonight I add the ability to add text. You can also change the set the font and color. You can change the size just by growing the size of the object with the size buttons I added initially.

I also added the ability to edit items after you've added them. That is you can change the color of most objects, and the width of your ink strokes. You can't edit the text once you've added it, but I suppose I could add the pretty easily as a feature some night.

Tonight I wrote the first piece of code that suggested extending goocanvas namespace types rather than writing the program in a more procedural style. When changing colors, for most objects, you want to change the fill_color property. But for ink, you want to change the stroke_color property. So I added some code to detect the type and set the right property as needed.

       if type(self.selected_item) == goocanvas.Path:
self.selected_item.set_property("stroke_color",self.__ink_color)
else:
self.selected_item.set_property("fill_color",self.__ink_color)


Typically, when I find myself detecting types and branching, it's not long before I start sub-classing instead. However, when I look at the goocanvas reference it seems that there are limited objects, and perhaps a procedural style will hold out.