-
-
Save zed/b966b5a04f2dfc16c98e to your computer and use it in GitHub Desktop.
Gtk Tree multithreading example in Python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
import os | |
import threading | |
import time | |
from itertools import cycle | |
from gi.repository import GObject, Gtk | |
GObject.threads_init() # all Gtk is in the main thread; | |
# only GObject.idle_add() is in the background thread | |
HEARTBEAT = 20 # Hz | |
CHUNKSIZE = 100 # how many items to process in a single idle_add() callback | |
def chunks(seq, chunksize): | |
"""Yield N items at a time from seq.""" | |
for i in xrange(0, len(seq), chunksize): | |
yield seq[i:i + chunksize] | |
class TreeStore(Gtk.TreeStore): | |
__gtype_name__ = 'TreeStore' | |
def __init__(self, topdir, done_callback=None): | |
Gtk.TreeStore.__init__(self, str) # super() doesn't work here | |
self.path2treeiter = {topdir: None} # path -> treeiter | |
self.topdir = topdir | |
self.done_callback = done_callback | |
self._cv = threading.Condition() | |
t = threading.Thread(target=self._build_tree) | |
t.daemon = True | |
t.start() # start background thread | |
def _build_tree(self, _sentinel=object()): | |
# executed in a background thread | |
cv = self._cv | |
p = self.path2treeiter | |
for dirpath, dirs, files in os.walk(self.topdir): | |
# wait until dirpath is appended to the tree | |
cv.acquire() | |
while p.get(dirpath, _sentinel) is _sentinel: | |
cv.wait() | |
parent = p[dirpath] | |
cv.release() | |
# populate tree store | |
dirs[:] = sorted(d for d in dirs | |
if d[0] != '.') # skip hidden dirs | |
for chunk in chunks(dirs, CHUNKSIZE): | |
GObject.idle_add(self._appenddir, chunk, parent, dirpath) | |
for chunk in chunks(sorted(files), CHUNKSIZE): | |
GObject.idle_add(self._appendfile, chunk, parent) | |
GObject.idle_add(self.done_callback) | |
def _appenddir(self, chunk, parent, dirpath): | |
# executed in the main thread | |
self._cv.acquire() | |
p = self.path2treeiter | |
for d in chunk: | |
p[os.path.join(dirpath, d)] = self.append(parent, [d]) | |
self._cv.notify() | |
self._cv.release() | |
def _appendfile(self, chunk, parent): | |
# executed in the main thread | |
for f in chunk: | |
self.append(parent, [f]) | |
class Window(Gtk.Window): | |
__gtype_name__ = 'Window' | |
def __init__(self, topdir): | |
super(Window, self).__init__(type=Gtk.WindowType.TOPLEVEL) | |
self.__start_time = time.time() | |
self.__title = 'GTK Tree MultiThreading Demo' | |
self.set_title(self.__title) | |
self.set_default_size(640, 480) | |
# create tree | |
tree_store = TreeStore(topdir, self._on_tree_completed) | |
tree_view = Gtk.TreeView() | |
tree_view.set_model(tree_store) | |
cell = Gtk.CellRendererText() | |
tree_view.append_column(Gtk.TreeViewColumn(topdir, cell, text=0)) | |
scrolled_window = Gtk.ScrolledWindow() | |
scrolled_window.add(tree_view) | |
self.add(scrolled_window) | |
# update title to show that we are alive | |
self._update_id = GObject.timeout_add(int(1e3 / HEARTBEAT), | |
self._update_title) | |
def _on_tree_completed(self): | |
if self._update_id is None: | |
return | |
# stop updates | |
GObject.source_remove(self._update_id) | |
self._update_id = None | |
self.set_title('%s %s %.1f' % (self.__title, ' (done)', | |
time.time() - self.__start_time)) | |
def _update_title(self, _suff=cycle('/|\-')): | |
self.set_title('%s %s %.1f' % (self.__title, next(_suff), | |
time.time() - self.__start_time)) | |
return True # continue updates | |
win = Window(topdir=os.path.expanduser('~')) | |
win.connect('delete-event', Gtk.main_quit) | |
win.show_all() | |
Gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment