Latest News

Follow the latest news on OpenShot Video Editor!


,

I have just learned a few more tricks on how to best work with GTK and Python. First, let me start at the beginning. After you create a Glade file (If you don't know what Glade is, read this), the next step is to create the Python code to load the Glade file (i.e. turn the XML into actual widgets on the screen), and hook up all the signals to actual Python methods. Well, if you're like me, you don't know how many parameters are needed for each signal handler, and soon you find yourself looking for examples (or just guessing) on how to hook up various signals (such as a button click, treeview drag_begin, window destroy, etc...). This can become a very time consuming and frustrating process, especially when you are just learning Python & GTK.

Trick #1: Using Synaptic Package Manager (or equivalent), go get the tepache package. This package gives you a command line way of generating the Python code needed to load a Glade file into a class, auto-connect all the signals, and it even stubs out all the signal handler methods. If that wasn't enough, it even helps setup language translations for your Glade file.

To use the tepache command, use the following syntax in the terminal window: tepache YourFile.Glade

After you run this command, it will generate a .py file, named YourFile.py. Here is the output from the tepache command using one of my more complex Glade files:

import os
import gtk
import gnome

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "main"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)


class App1(SimpleGladeApp):

def __init__(self, path="Main.glade",
root="app1",
domain=app_name, **kwargs):
path = os.path.join(glade_dir, path)
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

def new(self):
print "A new %s has been created" % self.__class__.__name__


def on_app1_destroy(self, widget, *args):
print "on_app1_destroy called with self.%s" % widget.get_name()
gtk.main_quit()


def on_mnuQuit_activate(self, widget, *args):
print "on_mnuQuit_activate called with self.%s" % widget.get_name()


def on_mnuAbout_activate(self, widget, *args):
print "on_mnuAbout_activate called with self.%s" % widget.get_name()


def on_treeFiles_drag_drop(self, widget, *args):
print "on_treeFiles_drag_drop called with self.%s" % widget.get_name()


def on_tlbAddTrack_clicked(self, widget, *args):
print "on_tlbAddTrack_clicked called with self.%s" % widget.get_name()


def on_tlbRemoveTrack_clicked(self, widget, *args):
print "on_tlbRemoveTrack_clicked called with self.%s" % widget.get_name()


def on_hscrollbar1_value_changed(self, widget, *args):
print "on_hscrollbar1_value_changed called with self.%s" % widget.get_name()


def on_vscrollbar1_value_changed(self, widget, *args):
print "on_vscrollbar1_value_changed called with self.%s" % widget.get_name()


def on_scrolledwindow_Right_drag_drop(self, widget, *args):
print "on_scrolledwindow_Right_drag_drop called with self.%s" % widget.get_name()


def main():
gnome.program_init("main", "0.0.1")
app1 = App1()

app1.run()

if __name__ == "__main__":
main()

Trick #2: There is a library that comes with pyGtk called SimpleGladeApp, which is going to save you a ton of time. It is used quite heavily in the tepache output (as you can see from my example). What does this library do, you ask?

Let me break it down for you:
  1. It loads a Glade XML file and returns an object for your form. It reminds me very much of a form object from the Windows world (i.e. Visual Basic.NET, C# Windows Application, etc...).
  2. On this new form object, it has a reference to every single widget on your form. For example: self.myTree, self.btnToolBar1, self.Canvas1.
  3. It adds a show() and hide() method for every widget.
  4. It adds a grab_focus() method for every widget.
  5. It adds a run() and quit() for your form (which simply calls the main_loop() gtk methods.
  6. It auto-connects all the signal handlers you have defined (of that tepache defined) to the signals defined in the Glade file.
  7. It translates all the text in the Glade file (if you have the appropriate translation files in your project).
So, now that you know this, take a look at my example above again. Anywhere in this class you can access all the widgets, hide and show them, set focus to them, all without calling the GladeFile.get_widget("") method.

I hope this blog entry is helpful to some of you. It's going to save me a ton of time trying to setup my GUI and hook up all the signals. Good luck!

,

Have you ever wanted to change the drag icon on a GTK TreeView control using Python? This sounds really easy, but in fact there is a trick to getting it to work correctly. Hopefully you have found this blog prior to spending 7 days searching for a solution (like I did).

I have posted a fully working example program below, that will demonstrate how to successfully change the icon used during a drag operation. Pay close attention to way the drag_begin signal is connected (hint: that's the trick). Also, for this example to work, you must change the image path to point at a valid JPG or PNG image on your computer.

Hopefully the world is a better place now that I've shared this example. Good luck!

# import the gtk library
import gtk

# signal handler for the tree drag begin signal
# change the drag icon to a JPG as soon as the drag starts
# *** BE SURE TO CONNECT THIS EVENT WITH THE "CONNECT_AFTER(" SYNTAX ***
# *** THE ICON WILL NOT CHANGE UNLESS THIS METHOD IS USED TO CONNECT ***
def on_drag_begin(widget, context):
print "Drag Begin"

# Get a pixbuf of an image (i.e. JPG). This will be your icon.
play_image = gtk.image_new_from_file("glade/icons/jpg/arrow.jpg")
mypixbuf = play_image.get_pixbuf()

# Change the icon to the pixbuf (i.e. the JPG)
context.set_icon_pixbuf(mypixbuf, 10, 10)

# add tree to window
myTree = gtk.TreeView()

# Enable drag and drop on the treeview. If you don't call this method,
# the tree won't allow you to drag anything
myTree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [('text/plain', 0, 0)], gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)

# connect the drag begin signal handler. Be sure
# to use the "connect_after" syntax and not the regular "connect".
myTree.connect_after('drag_begin', on_drag_begin)

# create a TreeStore with one string column to use as the model
myTreeStore = gtk.TreeStore(str, str)

# we'll add some data now
myTreeStore.append(None, ["Choose a Video or Audio File to Begin", ""])

# Set the treeview's data model
myTree.set_model(myTreeStore)

# create 2 TreeViewColumns to display the data
tvcolumn = gtk.TreeViewColumn('File Name')
tvcolumn1 = gtk.TreeViewColumn('Length')

# add columns to treeview
myTree.append_column(tvcolumn)
myTree.append_column(tvcolumn1)

# create a CellRendererText to render the data
cell = gtk.CellRendererText()
cell1 = gtk.CellRendererText()

# add the cell to the tvcolumn and allow it to expand
tvcolumn.pack_start(cell, True)
tvcolumn1.pack_start(cell1, True)

# set the cell "text" attribute to column 0 - retrieve text
# from that column in treestore
tvcolumn.add_attribute(cell, 'text', 0)
tvcolumn1.add_attribute(cell1, 'text', 1)

# create new window and add the treeview
window = gtk.Window()
window.add(myTree)
window.show_all()

# Start the Main loop
gtk.main()

,

If you have ever attempted to enable drag & drop with Python and GTK, then you've probably run into the same problem I did. When I would drop an item on a widget, it just would not fire off the signal handler. Very frustrating!!!

However, after some digging around, I found the solution. It's a GTK method that you must call to enable a widget as a drop target. The method is widgetname.drag_dest_set(). See my example below to see how it's used.

Example Code:

This isn't a fully working example, but the code snippets are valid syntax and should be useful in your own program. Good luck!

# Set the type of file that can be dropped
TARGET_TYPE_URI_LIST = 80
dnd_list = [ ( 'text/uri-list', 0, TARGET_TYPE_URI_LIST ) ]

# Connect a function to handle the drop signal
myTree.connect('drag_data_received', on_drag_data_received)

# IMPORTANT: Set an object as a drop destination, or none of this code will work
myTree.drag_dest_set( gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
dnd_list, gtk.gdk.ACTION_COPY)

# Connect a function to handle the drag motion signal from a goocanvas widget
MyCanvas.connect('drag_motion', on_drag_motion)

# Set the canvas to allow drops from any type of file
MyCanvas.drag_dest_set(0, [], 0)

# Function to handle the drag motion
def on_drag_motion(wid, context, x, y, time):
print " *** motion detected ***"

# Function to handle the drop
def on_drag_data_received(widget, context, x, y, selection, target_type, timestamp):
print " ((( drop detected ))) "

,

I haven't made a ton of progress over the last 30 days, due to my "paying" job and my family obligations, however, I'm hopeful that September will be more productive. Here are my goals for September:

Goal 1) Finish integrating my smaller test projects into the final OpenShot code base.
Goal 2) Complete the user interface, including branding and custom icons.

Here are the icons I've developed thus far:

,

OpenShot Video Editor now has a home at https://launchpad.net/openshot. I will be managing the entire project using LaunchPad, including source control, bug tracking, idea tracking, language translations, and a custom repository for deployments.

Even though I love Perforce, and would have liked to use it for this project, LaunchPad just offers so much integration... so many features... I just couldn't resist. By the way, the source code in LaunchPad is still very unstable, and I don't recommend waisting your time looking at it until I make an announcement that it's ready.

,

After months of brainstorming, I have finally decided on an official name for this project: OpenShot Video Editor. Open stands for open-source, and Shot stands for a single cinematic take.

Here is my first draft of the logo. Expect the final product to include a theme that borrows heavily from this logo's colors and design elements.


Subscribe to: Posts (Atom)