Last active
October 15, 2019 19:11
-
-
Save danbradham/31610ea00dd90c3e1065 to your computer and use it in GitHub Desktop.
Python Manager
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
# -*- 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