Skip to content

Instantly share code, notes, and snippets.

@danbradham
Last active October 15, 2019 19:11
Show Gist options
  • Save danbradham/31610ea00dd90c3e1065 to your computer and use it in GitHub Desktop.
Save danbradham/31610ea00dd90c3e1065 to your computer and use it in GitHub Desktop.
Python Manager
# -*- coding: utf-8 -*-
#! /usr/bin/env python
'''
Python Manager
==============
GUI tool for managing modules and packages based on reimport: Full featured reload for Python.
'''
import os
import reimport
import sys
import time
import shiboken
from itertools import groupby
from PySide import QtGui, QtCore
PARENT_APPLICATION = None
if 'Maya' in sys.executable:
from maya.OpenMayaUI import MQtUtil
maya_window = shiboken.wrapInstance(long(MQtUtil.mainWindow()), QtGui.QWidget)
PARENT_APPLICATION = maya_window
elif 'Nuke' in sys.executable:
PARENT_APPLICATION = 'Nuke' # Replace with proper nuke qwidget
STYLE = '''
QLabel#Header{
background: rgb(235, 235, 235);
padding: 15px;
font: 12pt;
}
QListWidget{
background: rgb(215, 215, 215);
outline: none;
}
QListWidget::item{
background: rgb(215, 215, 215);
border-top: 1px solid rgb(230, 230, 230);
border-bottom: 1px solid rgb(200, 200, 200);
outline: none;
}
QListWidget::item:selected{
color: rgb(235, 235, 235);
background: rgb(100, 100, 215);
border-top: 1px solid rgb(115, 115, 230);
border-bottom: 1px solid rgb(85, 85, 200)
}
QPushButton{
border: 0;
color: rgb(235, 235, 235);
background: rgb(200, 20, 20);
font-weight: 400;
border-radius: 8px;
}
QPushButton:pressed{
border: 0;
padding: 0;
margin: 0;
}
QScrollBar:vertical{
border: 1px solid rgb(115, 115, 115);
background: rgb(255, 255, 255, 0);
width: 8px;
margin: 0;
}
QScrollBar::handle:vertical{
background: rgb(165, 165, 165);
min-height: 20px;
height: 40px;
width: 8px;
}
QScrollBar::handle:vertical:hover{
background: rgb(185, 185, 185);
min-height: 20px;
height: 40px;
width: 8px;
}
QScrollBar::add-line:vertical{
background: rgb(0,0,0,0);
subcontrol-origin: paddding;
subcontrol-position: top;
}
QScrollBar::sub-line:vertical{
background: rgb(0,0,0,0);
subcontrol-origin: padding;
subcontrol-position: bottom;
}
QScrollBar::top-arrow:vertical, QScrollBar::bottom-arrow:vertical{
background: rgb(255,255,255,0);
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical{
background: rgb(255,255,255,0);
}
'''
class ModuleWidget(QtGui.QWidget):
'''QListWidgetItem Widget that wraps up a module or package, displaying
modified time and module path. Tooltips display module filepaths.
'''
def __init__(self, module, subpackages=None, parent=None):
super(ModuleWidget, self).__init__(parent)
self.module = module
self.subpackages = subpackages
self.grid = QtGui.QGridLayout()
self.grid.setContentsMargins(20, 10, 20, 10)
self.grid.setColumnStretch(2, 1)
modname = self.module.__name__
modpath = self.module.__file__
modmodf = time.localtime(os.path.getmtime(modpath))
font = QtGui.QFont('SansSerif', 12)
subfont = QtGui.QFont('SansSerif', 10)
self.modf = QtGui.QLabel(time.strftime('%H:%M', modmodf))
self.modf.setFont(font)
self.name = QtGui.QLabel(modname)
self.name.setFont(font)
self.name.setToolTip(modpath)
self.grid.addWidget(self.modf, 0, 0)
self.grid.addWidget(self.name, 0, 1, 1, 2)
if self.subpackages:
for i, subpackage in enumerate(self.subpackages):
subname = subpackage.__name__
subpath = subpackage.__file__
submodf = time.localtime(os.path.getmtime(modpath))
modf = QtGui.QLabel(time.strftime('%H:%M', modmodf))
modf.setFont(subfont)
name = QtGui.QLabel(subname)
name.setFont(subfont)
name.setToolTip(subpath)
self.grid.addWidget(modf, i + 1, 1)
self.grid.addWidget(name, i + 1, 2)
self.setLayout(self.grid)
self.setAttribute(QtCore.Qt.WA_StyledBackground)
self.setObjectName('ModuleWidget')
self.deactivate()
def activate(self):
self.setStyleSheet('QLabel{color:rgb(235,235,235)}')
def deactivate(self):
self.setStyleSheet('QLabel{color:rgb(15,15,15)}')
class ModuleList(QtGui.QListWidget):
'''QListWidget extended to act more like a python list and handle module
objects.'''
def __init__(self, *args, **kwargs):
super(ModuleList, self).__init__(*args, **kwargs)
self.itemSelectionChanged.connect(self.selection_changed)
self.setSelectionMode(QtGui.QListWidget.ExtendedSelection)
self.setFocusPolicy(QtCore.Qt.NoFocus)
def selection_changed(self, *args):
'''Change text color of ModuleWidgets based on selection.'''
for i in range(self.count()):
item = self.item(i)
w = self.itemWidget(item)
if w:
if item.isSelected():
w.activate()
else:
w.deactivate()
def refresh(self):
'''Get a new list of modified modules to display.'''
self.clear()
self.extend(reimport.modified())
def extend(self, modules):
'''Takes an iterable of module paths and groups them by package, then
adds them to the QListWidget. Delegates QListWidgetItem to
ModuleWidget.'''
modified = sorted(modules)
def top_level(item):
'''Sort by top level module.'''
return item.split('.')[0]
grouped = {}
for k, v in groupby(modified, key=top_level):
grouped[k] = [item for item in v if not item == k]
for module, subpackages in grouped.iteritems():
m = sys.modules.get(module)
sp = [sys.modules.get(p) for p in subpackages]
self.append(m, sp)
def append(self, module, subpackages=None):
'''Append module or a package to the list.'''
item = QtGui.QListWidgetItem(self)
w = ModuleWidget(module, subpackages)
item.setSizeHint(w.sizeHint())
self.setItemWidget(item, w)
super(ModuleList, self).addItem(item)
def selected(self):
'''Iterate over selected items.'''
for item in self.selectedItems():
yield(item)
def pop(self, item):
'''Pop a list item and return a ModuleWidget'''
w = self.itemWidget(item)
self.takeItem(self.row(item))
return w
class Button(QtGui.QPushButton):
'''Sexy button with a drop shadow.'''
shadow_state = {
'default': (12, (0, 4)),
'press': (12, (0, 2)),
'enter': (13, (0, 5)),
}
def __init__(self, *args, **kwargs):
super(Button, self).__init__(*args, **kwargs)
self.drop_shadow = QtGui.QGraphicsDropShadowEffect(self)
self.drop_shadow.setBlurRadius(self.shadow_state['default'][0])
self.drop_shadow.setOffset(*self.shadow_state['default'][1])
self.drop_shadow.setColor(QtGui.QColor(0, 0, 0, 75))
self.setFocusPolicy(QtCore.Qt.NoFocus)
self.setGraphicsEffect(self.drop_shadow)
def mousePressEvent(self, event):
self.drop_shadow.setBlurRadius(self.shadow_state['press'][0])
self.drop_shadow.setOffset(*self.shadow_state['press'][1])
super(Button, self).mousePressEvent(event)
def enterEvent(self, event):
self.drop_shadow.setBlurRadius(self.shadow_state['enter'][0])
self.drop_shadow.setOffset(*self.shadow_state['enter'][1])
super(Button, self).enterEvent(event)
def leaveEvent(self, event):
self.drop_shadow.setBlurRadius(self.shadow_state['default'][0])
self.drop_shadow.setOffset(*self.shadow_state['default'][1])
super(Button, self).leaveEvent(event)
def mouseReleaseEvent(self, event):
self.drop_shadow.setBlurRadius(self.shadow_state['default'][0])
self.drop_shadow.setOffset(*self.shadow_state['default'][1])
super(Button, self).mouseReleaseEvent(event)
class PythonManager(QtGui.QDialog):
'''GUI Managing modified Python modules and packages.'''
def __init__(self, parent=PARENT_APPLICATION):
super(PythonManager, self).__init__(parent)
drop_shadow = QtGui.QGraphicsDropShadowEffect(self)
drop_shadow.setBlurRadius(10)
drop_shadow.setOffset(0, 3)
drop_shadow.setColor(QtGui.QColor(0, 0, 0, 50))
self.module_list = ModuleList()
self.layout = QtGui.QGridLayout(self)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setSpacing(0)
self.layout.setRowMinimumHeight(0, 39)
self.layout.addWidget(self.module_list, 1, 0)
self.setLayout(self.layout)
self.refresh_button = Button('Refresh', self)
self.refresh_button.clicked.connect(self.module_list.refresh)
self.reimport_button = Button('Reimport', self)
self.reimport_button.clicked.connect(self.reimport_button_clicked)
self.header = QtGui.QLabel('Modified Modules and Packages', self)
self.header.setObjectName('Header')
self.header.setGraphicsEffect(drop_shadow)
self.header.setFixedHeight(40)
self.setWindowTitle('Python Manager')
self.setStyleSheet(STYLE)
def resizeEvent(self, event):
'''Resize PythonManager UI, reposition buttons and header.'''
super(PythonManager, self).resizeEvent(event)
w = self.width()
h = self.height()
riw = self.reimport_button.width()
rih = self.reimport_button.height()
rfw = self.refresh_button.width()
self.setMinimumWidth(riw + rfw + 50)
self.header.setFixedWidth(w)
self.header.move(0, 0)
self.reimport_button.move(w - riw - 20, h -rih - 20)
self.refresh_button.move(w - riw - rfw - 30, h - rih - 20)
def reimport_button_clicked(self):
'''Reimport selected items from the ModuleList.'''
for item in self.module_list.selected():
w = self.module_list.pop(item)
reimport.reimport(w.module)
def main():
app = QtGui.QApplication(sys.argv)
pyman = PythonManager()
pyman.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment