Last active
February 24, 2016 18:08
-
-
Save danbradham/886b098fe068822ddc96 to your computer and use it in GitHub Desktop.
Glob Viewer
This file contains hidden or 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
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