Skip to content

Instantly share code, notes, and snippets.

@danbradham
Last active February 24, 2016 18:08
Show Gist options
  • Save danbradham/886b098fe068822ddc96 to your computer and use it in GitHub Desktop.
Save danbradham/886b098fe068822ddc96 to your computer and use it in GitHub Desktop.
Glob Viewer
import os
import signal
import sys
import time
import glob
import re
import fnmatch
from itertools import izip
from functools import partial
from PySide import QtCore, QtGui
from threading import Thread
from Queue import Queue
class Executor(QtCore.QObject):
'''Executes functions in the main PySide thread'''
def __init__(self):
super(Executor, self).__init__()
self.queue = Queue()
def execute(self, fn, *args, **kwargs):
callback = partial(fn, *args, **kwargs)
self.queue.put(callback)
QtCore.QMetaObject.invokeMethod(
self,
'_execute',
QtCore.Qt.QueuedConnection
)
@QtCore.Slot()
def _execute(self):
callback = self.queue.get()
callback()
Executor = Executor()
def execute_in_main_thread(fn, *args, **kwargs):
'''
Convenience method for Executor.execute...Executes a function in the
main PySide thread as soon as possible.
'''
Executor.execute(fn, *args, **kwargs)
class GlobModel(QtGui.QStandardItemModel):
'''Like a FileModel except that it uses a glob pattern....'''
def __init__(self, glob_pattern):
super(GlobModel, self).__init__(0, 0)
self.glob_pattern = glob_pattern
self.re_pattern = re.compile(
glob_pattern.replace('*', '(.*)').replace('\\', '/')
)
self.searcher = GlobSearcher(glob_pattern, self)
self.searcher.start()
def set_update_delay(self, value):
self.searcher.queue.put(value)
def set_glob_pattern(self, pattern):
self.glob_pattern = pattern
self.re_pattern = re.compile(
pattern.replace('*', '(.*)').replace('\\', '/')
)
self.clear()
self.searcher.queue.put(pattern)
def get_context(self, path):
m = self.re_pattern.search(path)
ctx = m.groups()
if os.path.splitext(self.glob_pattern)[-1]:
return ctx[:-1]
return ctx
def update_items(self, paths):
paths = set(paths)
tree = {}
for path in paths:
path = path.replace('\\', '/')
ctx = self.get_context(path)
level = tree
for part in ctx:
if part not in level:
level_root = path.split(part)[0] + part
level[part] = {'__ROOT__': level_root}
level = level[part]
self.walk_update(tree)
def walk_update(self, tree, item=None, depth=0):
if depth == 0: # Handle root items
for k in tree.iterkeys():
item_root = tree[k].pop('__ROOT__', '')
matches = self.findItems(k)
if matches:
item = matches[0]
else:
item = QtGui.QStandardItem()
item.setEditable(False)
item.setText(k)
item.setData(item_root, QtCore.Qt.UserRole)
self.appendRow(item)
self.walk_update(tree[k], item, depth + 1)
return
if not item:
return
item_keys = tree.keys()
child_items = [None for i in xrange(len(item_keys))]
prune = []
for i in xrange(item.rowCount()):
child_item = item.child(i)
child_name = child_item.text()
if not child_name in tree:
prune.append(child_item.row())
continue
child_items[item_keys.index(child_name)] = child_item
for i in prune:
item.removeRow(i)
for name, child_item in izip(item_keys, child_items):
item_root = tree[name].pop('__ROOT__')
if not child_item:
child_item = QtGui.QStandardItem()
child_item.setEditable(False)
child_item.setText(name)
child_item.setData(item_root, QtCore.Qt.UserRole)
item.appendRow(child_item)
self.walk_update(tree[name], child_item, depth + 1)
class GlobSearcher(Thread):
'''Use a glob pattern to find directories and add them to a GlobModel'''
def __init__(self, glob_pattern, model, delay=5):
super(GlobSearcher, self).__init__()
self.glob_pattern = glob_pattern
self.queue = Queue()
self.model = model
self.paused = False
self.delay = delay
def run(self):
while True:
if not self.queue.empty():
item = self.queue.get()
if item == '__KILL__':
return
if item == '__PAUSE__':
self.paused = True
elif item == '__UNPAUSE__':
self.paused = False
elif isinstance(item, int):
self.delay = item
else:
self.glob_pattern = item
if not self.paused:
results = [os.path.normpath(d).replace('\\', '/')
for d in glob.glob(self.glob_pattern)]
execute_in_main_thread(self.model.update_items, results)
time.sleep(self.delay)
class GlobView(QtGui.QWidget):
def __init__(self, glob_pattern, filters, parent=None):
super(GlobView, self).__init__(parent)
self.layout_widgets()
self.model = GlobModel(glob_pattern)
self.filters = filters
self.folders.setModel(self.model)
self.sel_model = self.folders.selectionModel()
self.sel_model.selectionChanged.connect(self.on_selection_changed)
@property
def glob_pattern(self):
return self.model.glob_pattern
@glob_pattern.setter
def glob_pattern(self, pattern):
self.model.set_glob_pattern(pattern)
def on_selection_changed(self):
self.files.clear()
indexes = self.sel_model.selectedIndexes()
items = [self.model.itemFromIndex(i) for i in indexes]
paths = set([item.data(QtCore.Qt.UserRole) for item in items])
for path in paths:
files = sorted([f for f in os.listdir(path)
if os.path.isfile(os.path.join(path, f))])
for f in files:
ext = os.path.splitext(f)[-1]
if ext not in self.filters:
continue
file_item = QtGui.QListWidgetItem()
file_item.setText(os.path.splitext(f)[0])
file_item.setData(QtCore.Qt.UserRole, os.path.join(path, f))
self.files.addItem(file_item)
def layout_widgets(self):
self.folders = QtGui.QTreeView()
self.folders.setHeaderHidden(True)
self.folders.setSortingEnabled(True)
self.folders.setSelectionMode(
QtGui.QAbstractItemView.ExtendedSelection)
self.folders.setSelectionBehavior(
QtGui.QAbstractItemView.SelectItems)
self.files = QtGui.QListWidget()
self.files.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.layout = QtGui.QHBoxLayout()
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(10)
self.layout.addWidget(self.folders)
self.layout.addWidget(self.files)
self.setLayout(self.layout)
if __name__ == '__main__':
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtGui.QApplication(sys.argv)
gview = GlobView('/a/glob/path/*/*/*/exchange/alembic/*', ['.abc'])
gview.model.set_update_delay(0.5)
gview.show()
sys.exit(app.exec_())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment