diff --git a/readme.md b/readme.md index 9496c83..88a9a30 100644 --- a/readme.md +++ b/readme.md @@ -104,6 +104,10 @@ For example, "ax" matches "AddMix [Merge]" and "Axis [3D]". If you type "ax[3" or "ax 3" (ax-space-space-3) it will only match "Axis [3D]" +## Dependencies + +tabtabtab-nuke now requires [Qt.py](https://github.com/mottosso/Qt.py) which has been added as a compatibility layer. + ## Change log * `v1.0` @@ -177,4 +181,4 @@ type "ax[3" or "ax 3" (ax-space-space-3) it will only match "Axis reason. [Github issue #13](https://github.com/dbr/tabtabtab-nuke/issues/13) - * Support PySide2 + * Uses [Qt.py](https://github.com/mottosso/Qt.py) to support PySide2 diff --git a/tabtabtab.py b/tabtabtab.py index 204254a..335ff5f 100644 --- a/tabtabtab.py +++ b/tabtabtab.py @@ -9,34 +9,24 @@ import os import sys -try: - from PySide2 import QtCore, QtGui, QtWidgets - from PySide2.QtCore import Qt -except ImportError: - try: - from PySide import QtCore, QtGui, QtGui as QtWidgets - from PySide.QtCore import Qt - except ImportError: - import sip - for mod in ("QDate", "QDateTime", "QString", "QTextStream", "QTime", "QUrl", "QVariant"): - sip.setapi(mod, 2) +from Qt import QtCore, QtGui, QtWidgets - from PyQt4 import QtCore, QtGui - from PyQt4.QtCore import Qt - QtCore.Signal = QtCore.pyqtSignal - -def find_menu_items(menu, _path = None): +def find_menu_items(menu, _path=None): """Extracts items from a given Nuke menu Returns a list of strings, with the path to each item - Ignores divider lines and hidden items (ones like "@;&CopyBranch" for shift+k) + Ignores divider lines and hidden items + (ones like "@;&CopyBranch" for shift+k) >>> found = find_menu_items(nuke.menu("Nodes")) >>> found.sort() >>> found[:5] - ['3D/Axis', '3D/Camera', '3D/CameraTracker', '3D/DepthGenerator', '3D/Geometry/Card'] + [ + '3D/Axis', '3D/Camera', '3D/CameraTracker', + '3D/DepthGenerator', '3D/Geometry/Card' + ] """ import nuke @@ -53,7 +43,7 @@ def find_menu_items(menu, _path = None): # Remove all ToolSets delete commands continue - sub_found = find_menu_items(menu = i, _path = subpath) + sub_found = find_menu_items(menu=i, _path=subpath) found.extend(sub_found) elif isinstance(i, nuke.MenuItem): if i.name() == "": @@ -69,7 +59,7 @@ def find_menu_items(menu, _path = None): return found -def nonconsec_find(needle, haystack, anchored = False): +def nonconsec_find(needle, haystack, anchored=False): """checks if each character of "needle" can be found in order (but not necessarily consecutivly) in haystack. For example, "mm" can be found in "matchmove", but not "move2d" @@ -116,7 +106,6 @@ def nonconsec_find(needle, haystack, anchored = False): # ..? return True - # Turn haystack into list of characters (as strings are immutable) haystack = [hay for hay in str(haystack)] @@ -149,7 +138,7 @@ def nonconsec_find(needle, haystack, anchored = False): class NodeWeights(object): - def __init__(self, fname = None): + def __init__(self, fname=None): self.fname = fname self._weights = {} self._successful_load = False @@ -161,31 +150,33 @@ def load(self): def _load_internal(): import json if not os.path.isfile(self.fname): - print "Weight file does not exist" + print("Weight file does not exist") return f = open(self.fname) self._weights = json.load(f) f.close() - # Catch any errors, print traceback and continue + # Catch any errors, print(traceback and continue) try: _load_internal() self._successful_load = True except Exception: - print "Error loading node weights" + print("Error loading node weights") import traceback traceback.print_exc() self._successful_load = False def save(self): if self.fname is None: - print "Not saving node weights, no file specified" + print("Not saving node weights, no file specified") return if not self._successful_load: # Avoid clobbering existing weights file on load error - print "Not writing weights file because %r previously failed to load" % ( - self.fname) + print( + "Not writing weights file because %r previously failed to " + "load." % self.fname + ) return def _save_internal(): @@ -194,24 +185,24 @@ def _save_internal(): if not os.path.isdir(ndir): try: os.makedirs(ndir) - except OSError, e: - if e.errno != 17: # errno 17 is "already exists" + except OSError as err: + if err.errno != 17: # errno 17 is "already exists" raise f = open(self.fname, "w") # TODO: Limit number of saved items to some sane number - json.dump(self._weights, fp = f) + json.dump(self._weights, fp=f) f.close() - # Catch any errors, print traceback and continue + # Catch any errors, print(traceback and continue) try: _save_internal() except Exception: - print "Error saving node weights" + print("Error saving node weights") import traceback traceback.print_exc() - def get(self, k, default = 0): + def get(self, k, default=0): if len(self._weights.values()) == 0: maxval = 1.0 else: @@ -227,7 +218,7 @@ def increment(self, key): class NodeModel(QtCore.QAbstractListModel): - def __init__(self, mlist, weights, num_items = 15, filtertext = ""): + def __init__(self, mlist, weights, num_items=15, filtertext=""): super(NodeModel, self).__init__() self.weights = weights @@ -254,7 +245,9 @@ def update(self): for n in self._all: # Turn "3D/Shader/Phong" into "Phong [3D/Shader]" menupath = n['menupath'].replace("&", "") - uiname = "%s [%s]" % (menupath.rpartition("/")[2], menupath.rpartition("/")[0]) + uiname = "%s [%s]" % ( + menupath.rpartition("/")[2], menupath.rpartition("/")[0] + ) if nonconsec_find(filtertext, uiname.lower(), anchored=True): # Matches, get weighting and add to list of stuff @@ -267,21 +260,22 @@ def update(self): 'score': score}) # Store based on scores (descending), then alphabetically - s = sorted(scored, key = lambda k: (-k['score'], k['text'])) + s = sorted(scored, key=lambda k: (-k['score'], k['text'])) self._items = s self.modelReset.emit() - def rowCount(self, parent = QtCore.QModelIndex()): + def rowCount(self, parent=QtCore.QModelIndex()): + """Reimplementation of QtCore.QAbstractListModel virtual method.""" return min(self.num_items, len(self._items)) - def data(self, index, role = Qt.DisplayRole): - if role == Qt.DisplayRole: + def data(self, index, role=QtCore.Qt.DisplayRole): + if role == QtCore.Qt.DisplayRole: # Return text to display raw = self._items[index.row()]['text'] return raw - elif role == Qt.DecorationRole: + elif role == QtCore.Qt.DecorationRole: weight = self._items[index.row()]['score'] hue = 0.4 @@ -296,19 +290,9 @@ def data(self, index, role = Qt.DisplayRole): pix.fill(col) return pix - elif role == Qt.BackgroundRole: + elif role == QtCore.Qt.BackgroundRole: + # Removing unreachable code. return - weight = self._items[index.row()]['score'] - - hue = 0.4 - sat = weight ** 2 # gamma saturation to make faster falloff - - sat = min(1.0, sat) - - if index.row() % 2 == 0: - return QtGui.QColor.fromHsvF(hue, sat, 0.9) - else: - return QtGui.QColor.fromHsvF(hue, sat, 0.8) else: # Ignore other roles return None @@ -334,7 +318,6 @@ class TabyLineEdit(QtWidgets.QLineEdit): pressed_arrow = QtCore.Signal(str) cancelled = QtCore.Signal() - def event(self, event): """Make tab trigger returnPressed @@ -349,7 +332,7 @@ def event(self, event): return True elif is_keypress and event.key() == QtCore.Qt.Key_Up: - # These could be done in keyPressedEvent, but.. this is already here + # These could be done in keyPressedEvent, but..this is already here self.pressed_arrow.emit("up") return True @@ -366,8 +349,8 @@ def event(self, event): class TabTabTabWidget(QtWidgets.QDialog): - def __init__(self, on_create = None, parent = None, winflags = None): - super(TabTabTabWidget, self).__init__(parent = parent) + def __init__(self, on_create=None, parent=None, winflags=None): + super(TabTabTabWidget, self).__init__(parent=parent) if winflags is not None: self.setWindowFlags(winflags) @@ -381,14 +364,17 @@ def __init__(self, on_create = None, parent = None, winflags = None): self.input = TabyLineEdit() # Node weighting - self.weights = NodeWeights(os.path.expanduser("~/.nuke/tabtabtab_weights.json")) - self.weights.load() # weights.save() called in close method + self.weights = NodeWeights( + os.path.expanduser("~/.nuke/tabtabtab_weights.json") + ) + self.weights.load() # weights.save() called in close method import nuke - nodes = find_menu_items(nuke.menu("Nodes")) + find_menu_items(nuke.menu("Nuke")) + nodes = find_menu_items(nuke.menu("Nodes")) + nodes += find_menu_items(nuke.menu("Nuke")) # List of stuff, and associated model - self.things_model = NodeModel(nodes, weights = self.weights) + self.things_model = NodeModel(nodes, weights=self.weights) self.things = QtWidgets.QListView() self.things.setModel(self.things_model) @@ -405,8 +391,10 @@ def __init__(self, on_create = None, parent = None, winflags = None): self.input.textChanged.connect(self.update) # Reset selection on text change - self.input.textChanged.connect(lambda: self.move_selection(where="first")) - self.move_selection(where = "first") # Set initial selection + self.input.textChanged.connect( + lambda: self.move_selection(where="first") + ) + self.move_selection(where="first") # Set initial selection # Create node when enter/tab is pressed, or item is clicked self.input.returnPressed.connect(self.create) @@ -439,8 +427,9 @@ def clamp(val, mi, ma): def move_selection(self, where): if where not in ["first", "up", "down"]: - raise ValueError("where should be either 'first', 'up', 'down', not %r" % ( - where)) + raise ValueError( + "where should be either 'first', 'up', 'down', not %r" % where + ) first = where == "first" up = where == "up" @@ -460,6 +449,11 @@ def move_selection(self, where): count = self.things_model.rowCount() if new > count-1: new = 0 + else: + # TODO: Figure out what to do if it is neither up nor down. + raise ValueError( + "where should be either 'first', 'up', 'down', not %r" % where + ) self.things.setCurrentIndex(self.things_model.index(new)) @@ -521,12 +515,14 @@ def create(self): self.input.setText(prev_string) # Create node, increment weight and close - self.cb_on_create(thing = thing) + self.cb_on_create(thing=thing) self.weights.increment(thing['menupath']) self.close() _tabtabtab_instance = None + + def main(): global _tabtabtab_instance @@ -545,9 +541,12 @@ def on_create(thing): try: thing['menuobj'].invoke() except ImportError: - print "Error creating %s" % thing + print("Error creating %s" % thing) - t = TabTabTabWidget(on_create = on_create, winflags = Qt.FramelessWindowHint) + t = TabTabTabWidget( + on_create=on_create, + winflags=QtCore.Qt.FramelessWindowHint + ) # Make dialog appear under cursor, as Nuke's builtin one does t.under_cursor() @@ -570,6 +569,6 @@ def on_create(thing): m_edit.addCommand("Tabtabtab", main, "Tab") except ImportError: # For testing outside Nuke - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) main() app.exec_()