from krita import *
from PyQt5 import QtCore, QtGui, QtWidgets, QtSvg, uic
import sip
import pprint
import time
from contextlib import redirect_stdout
import io
import re
import json
import pprint
from datetime import datetime
import sys
from .GetKritaAPI import *
#import asyncio
#from concurrent.futures import ProcessPoolExecutor
class PluginDevToolsDocker(DockWidget):
def __init__(self):
super().__init__()
settingsData = Krita.instance().readSetting("", "pluginDevToolsSettings","")
self.settings = {}
if settingsData.startswith('{'):
self.settings = json.loads(settingsData)
self.kritaAPI = {}
self.setWindowTitle("Plugin Developer Tools")
self.centralWidget = uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/DockerWidget.ui')
for i in range(0,self.centralWidget.tabWidget.count()):
name = self.centralWidget.tabWidget.widget(i).objectName().replace('Tab','')
if name not in self.settings:
self.settings[name] = {}
self.setWidget(self.centralWidget)
self.firstRun = True
self.currentTab = None
#self.tabChanged(None)
self.centralWidget.tabWidget.setTabIcon( 0, Krita.instance().icon('pivot-point') )
self.centralWidget.tabWidget.currentChanged.connect(self.tabChanged)
def windowCreatedSetup(self):
self.t = {}
self.qwin = Krita.instance().activeWindow().qwindow()
self.t['welcome'] = self.PluginDevToolsWelcome(self)
self.t['inspector'] = self.PluginDevToolsInspector(self)
self.t['selector'] = self.PluginDevToolsSelector(self)
self.t['console'] = self.PluginDevToolsConsole(self)
self.t['icons'] = self.PluginDevToolsIcons(self)
self.t['actions'] = self.PluginDevToolsActions(self)
self.t['kritaapi'] = self.PluginDevToolsKritaAPI(self)
def tabChanged(self, idx):
if idx is not None:
if self.firstRun:
self.firstRun = False
self.windowCreatedSetup()
if self.currentTab:
self.t[self.currentTab].unselected()
self.currentTab = self.centralWidget.tabWidget.currentWidget().objectName().replace('Tab','')
print ("ON", self.currentTab)
self.t[self.currentTab].selected()
def canvasChanged(self, canvas):
pass
class PluginDevToolsWelcome():
def __init__(self, caller):
super().__init__()
self.caller = caller
def selected(self):
pass
def unselected(self):
pass
class PluginDevToolsKritaAPI():
def __init__(self, caller):
super().__init__()
self.caller = caller
self.currentAPI = None
self.kritaapiTextBrowser = self.caller.centralWidget.kritaapiTextBrowser
self.kritaapiTreeView = self.caller.centralWidget.kritaapiTreeView
self.kritaapiModel = QStandardItemModel()
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyModel.setFilterKeyColumn(-1)
self.proxyModel.setRecursiveFilteringEnabled(True)
self.proxyModel.setSourceModel(self.kritaapiModel)
self.kritaapiTreeView.setModel(self.proxyModel)
self.kritaTreeSelectModel = self.kritaapiTreeView.selectionModel()
self.kritaTreeSelectModel.selectionChanged.connect(self.itemSelectionChanged)
self.caller.centralWidget.kritaapiGenAutoComplete.setIcon( Krita.instance().icon('document-export') )
self.caller.centralWidget.kritaapiDownload.setIcon( Krita.instance().icon('merge-layer-below') )
self.firstRun = True
def itemSelectionChanged(self, new, old):
#index = self.kritaapiTreeView.currentIndex()
#print ("selected", index)
indexes = new.indexes()
if indexes:
self.kritaapiTextBrowser.setText( indexes[0].data(101) )
#if index:
# prop = self.proxyModel.index(index.row(), 0).data(101)
# self.kritaapiTextBrowser.setText( prop if prop else "" )
def selected(self):
if self.firstRun:
self.firstRun = False
ver = (Krita.instance().version().split('-'))[0]
if ver not in self.caller.settings['kritaapi']:
self.downloadKritaAPI()
self.fillItems()
def formatDoc(self, doc):
start = doc.find('@code')
if start > -1:
end = doc.find('@endcode',start)
doc = doc.replace( doc[start:end], doc[start:end].replace('<','<') )
doc = '
'.join(doc.split('\n'))
doc = re.sub(r'\@(param|brief|return|returns) (\w+?) ',r'@\1 \2 ', doc).replace('the','the').replace('a','a').replace('The','The').replace('A','A')
doc = re.sub(r'\@(param) (\w+?)',r'@param \2', doc)
doc = doc.replace('@code','
')
return doc
def fillItems(self):
ver = (Krita.instance().version().split('-'))[0]
if os.path.exists( os.path.dirname(os.path.realpath(__file__)) + '.KritaAPI.'+ver+'.zip' ):
getAPI = GetKritaAPI()
self.caller.kritaAPI[ver] = getAPI.parseData(ver)
if ver in self.caller.kritaAPI:
self.currentAPI = self.caller.kritaAPI[ver]
self.kritaapiModel.setHorizontalHeaderLabels([
'Method',
'Declare/Return Type',
#'Description'
])
rootItem = self.kritaapiModel.invisibleRootItem()
item = QStandardItem("Krita.instance()")
metaDict = self.genMethodList( 'Krita', Krita.instance(), Krita.__dict__ )
rootItem.appendRow([
item,
QStandardItem( metaDict['declare'] if metaDict['declare'] else "\n" ),
#QStandardItem("")
])
kritaClassItem = rootItem.child(rootItem.rowCount() - 1)
kritaClassItem.setData( self.formatDoc(metaDict['doc']) , 101 )
for k, prop in sorted(metaDict['methods'].items()):
kritaClassItem.appendRow([
QStandardItem(prop['rec'][0]),
QStandardItem(prop['rec'][2]),
#QStandardItem( self.formatDoc(prop['doc']) )
])
methodItem = kritaClassItem.child(kritaClassItem.rowCount() - 1)
methodItem.setData( self.formatDoc(prop['doc']), 101 )
for k in dir(krita):
if k.startswith('__') or k == 'Krita': continue
item = QStandardItem(k)
extraData = None
classMeta = getattr(krita, k)
metaDict = self.genMethodList( k, classMeta, classMeta.__dict__ )
rootItem.appendRow([
item,
QStandardItem( metaDict['declare'] if metaDict['declare'] else "\n" ),
#QStandardItem("")
])
classItem = rootItem.child(rootItem.rowCount() - 1)
classItem.setData( self.formatDoc(metaDict['doc']), 101 )
for k1, prop in sorted(metaDict['methods'].items()):
subitem = QStandardItem(prop['rec'][0])
classItem.appendRow([
subitem,
QStandardItem(prop['rec'][2]),
#>>QStandardItem(prop['rec'][1])
#QStandardItem( self.formatDoc(prop['doc']) )
])
#print ("PROP", prop['name'], '' )
#if extraData and k in extraData and prop['name'] in extraData[k]['methods']:
methodItem = classItem.child(classItem.rowCount() - 1)
methodItem.setData( self.formatDoc(prop['doc']), 101 )
if hasattr(classMeta,'staticMetaObject'):
parentMetaClass = classMeta.staticMetaObject.superClass()
if parentMetaClass and not parentMetaClass.className().startswith('Q') and not parentMetaClass.className().startswith('Kis'):
parentMeta = getattr(krita, parentMetaClass.className())
parentItem = QStandardItem("Inherited from " + parentMetaClass.className() )
classItem.appendRow([
parentItem,
QStandardItem(""),
#QStandardItem("")
])
iclassItem = classItem.child(classItem.rowCount() - 1)
if self.currentAPI and parentMetaClass.className() in self.currentAPI:
iclassItem.setData( self.formatDoc(self.currentAPI[parentMetaClass.className()]['doc']) , 101 )
metaDict2 = self.genMethodList( parentMetaClass.className(), parentMeta, parentMeta.__dict__ )
for k2, prop2 in sorted(metaDict2['methods'].items()):
iclassItem.appendRow([
QStandardItem(prop2['rec'][0]),
QStandardItem(prop2['rec'][2]),
#QStandardItem(prop2['rec'][1])
])
imethodItem = iclassItem.child(iclassItem.rowCount() - 1)
imethodItem.setData( self.formatDoc(prop2['doc']), 101 )
self.caller.centralWidget.kritaapiDownload.clicked.connect(self.downloadKritaAPI)
self.caller.centralWidget.kritaapiGenAutoComplete.clicked.connect(self.exportKritaAPI)
self.caller.centralWidget.kritaapiFilter.textChanged.connect(self.searchTreeFilter)
self.kritaapiTreeView.expandAll()
def exportKritaAPI(self):
getAPI = GetKritaAPI()
ver = (Krita.instance().version().split('-'))[0]
content = getAPI.genAutoComplete(ver)
filename, _ = QFileDialog.getSaveFileName(
caption="Save Auto Complete file", directory='PyKrita.py', filter="Python files (*.py)"
)
if filename:
f = open(filename, "w")
f.write(content)
f.close()
def downloadKritaAPI(self):
ver = (Krita.instance().version().split('-'))[0]
msgbox = QMessageBox(QMessageBox.Question,'Would you like to download the API details automatically?',
'', QMessageBox.Yes | QMessageBox.No)
msgbox.setTextFormat(Qt.RichText)
msgbox.setText("""Developer Tools would like to connect to the internet to download Krita API details.
This process will only access Krita's offical git repository at invent.kde.org.
The API is still accessable even without downloading this file, but may not be fully complete, no documentation will be available and you will not be able to generate autocomplete files.
You can also do this manually by downloading the following (Unless you are on Krita Next nightly, 'master' should be replaced with the tag you plan to target, ex. 'v"""+ver+"""' or 'v"""+ver+"""-beta1'):
https://invent.kde.org/graphics/krita/-/archive/master/krita-master.zip?path=libs/libkis
And place it in:
""" + os.path.dirname(os.path.realpath(__file__)) + """.KritaAPI."""+ver+""".zip
This only needs to be done once per new version of Krita. Do note that Krita may freeze up for about a minute.
Would you like to download the API details(less than 200kb of data) automatically?
""")
if msgbox.exec() == QMessageBox.Yes:
getAPI = GetKritaAPI()
res = getAPI.updateData(ver)
if res['status'] == 0:
msgbox = QMessageBox(QMessageBox.Warning,'Error',str(res['error']))
msgbox.exec()
print ( "ERROR!", str(e) )
return
else:
QMessageBox(QMessageBox.Information,'Success!', "API details have been downloaded successfully!").exec()
self.caller.kritaAPI[ver] = getAPI.parseData(ver)
self.caller.settings['kritaapi'][ver] = { 'updated':res['data']['updated'] }
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
#print ("CCC", self.caller.kritaAPI[ver] )
self.kritaapiModel.clear()
self.fillItems()
else:
self.caller.settings['kritaapi'][ver] = { 'updated':'0000-00-00T00:00:00' }
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
def unselected(self):
pass
def searchTreeFilter(self, text):
self.proxyModel.setFilterFixedString(text)
self.kritaapiTreeView.expandAll()
indexes = self.kritaapiTreeView.selectionModel().selectedIndexes()
if indexes:
self.kritaapiTreeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
def genMethodList(self, className, obj, meta ):
declareMethod = obj.__doc__ + "\n" if obj.__doc__ else ""
metaDict = { 'properties':{}, 'methods':{}, 'doc':"\n\n@declare Methods\n\n@code" + declareMethod + "@endcode", 'declare': declareMethod }
metaDict2 = { 'properties':{}, 'methods':{}, 'declare':[] }
proxyObj = {}
if self.currentAPI and className in self.currentAPI:
#print ("META3!", className)
metaDict2 = self.genMethodList3(className, self.currentAPI[ className ] )
metaDict['doc'] = self.currentAPI[ className ]['doc'] + "\n\n@declare Methods\n\n@code" + declareMethod + "@endcode\n"
for key in metaDict2['methods']:
if key not in meta.keys() and key != className:
#print ("proxy",metaDict2['methods'][key]['rec'])
proxyObj[key] = {
'__doc__': metaDict2['methods'][key]['rec'][0] + " -> " + metaDict2['methods'][key]['rec'][2],
}
for decl in metaDict2['declare']:
metaDict['declare'] += decl['rec'][0] + " -> " + decl['rec'][2] + "\n"
metaDict['doc'] += "\n" + decl['doc'] + "\n@code" + decl['rec'][0] + " -> " + decl['rec'][2] + "@endcode\n"
elif hasattr(obj,'staticMetaObject'):
metaDict2 = self.genMethodList2(obj, obj.staticMetaObject)
for key in metaDict2['methods']:
if key not in meta.keys() and key != className:
#print ("proxy",metaDict2['methods'][key]['rec'])
proxyObj[key] = {
'__doc__': metaDict2['methods'][key]['rec'][0] + " -> " + metaDict2['methods'][key]['rec'][2],
}
for key in list(meta.keys()) + list(proxyObj.keys()):
if not key.startswith('__'):
doc = proxyObj[key]['__doc__'] if key in proxyObj else getattr(obj,key).__doc__
if doc:
staticMethod = False
propName = doc.split(' -> ')
if '(self' not in propName[0] and key not in proxyObj:
staticMethod = True
else:
propName[0] = propName[0].replace('(self, ','(').replace('(self','(')
propName2 = ''
propName[0] = re.sub(r"(Union\[.+?\])", lambda s: ' | '.join(s.group(1).split(', ')), propName[0] )
propName[0] = re.sub(r"(Dict\[.+?\])", lambda s: ' ; '.join(s.group(1).split(', ')), propName[0] )
propDoc = ''
if key in metaDict2['methods']:
#print ("FOUND", key, metaDict2['methods'][key]['doc'])
propDoc = metaDict2['methods'][key]['doc']
propName2 = metaDict2['methods'][key]['rec'][0]
if key in proxyObj:
propName[0] = propName2 + ' {*}'
propDoc += "\n\n{*} Method is not located in " + className + ".sip. This means the method is either private, internal use only or a developer forgot to add it"
else:
propName[0] = re.sub(
r"\((.+)\)",
lambda s: "(" + ', '.join( [ metaDict2['methods'][key]['pnames'][i] + ': ' + v for i, v in enumerate(s.group(1).split(', ')) ] ) + ")",
propName[0]
)
if 'Private' in metaDict2['methods'][key]['rec'][1]:
propName[0] = "[private] " + propName[0]
#del metaDict2['methods'][key] #testing
if staticMethod:
propName[0] = propName[0] + " [static]"
metaDict['methods'][propName[0]]={ 'class': className, 'type':8, 'doc':propDoc, 'name': key, 'rec':[ propName[0], '', (propName[1] if len(propName) == 2 else 'void') ] }
for t in metaDict2['methods']:
metaDict2['methods'][t]={}
#print ("EXTRA", className, metaDict2 )
return metaDict
def genMethodList3(self, className, meta):
metaDict = { 'properties':{}, 'methods':{}, 'classes':{}, 'declare':[] }
for key in meta['methods']:
#print ("key", key)
#print ("m", meta['methods'][key])
metaDict['methods'][key]={
'doc': meta['methods'][key]['doc'],
'class': className,
'type':0,
'name': key,
'pnames': [ p['name'] for p in meta['methods'][key]['params'] ],
'rec':[ ('[private] ' if 'private' in meta['methods'][key]['access'] else '' )+key+'('+', '.join([ p['type']+' '+p['name']+('='+p['optional'] if p['optional'] else '') for p in meta['methods'][key]['params'] ])+')', meta['methods'][key]['access'], meta['methods'][key]['return'] ] }
for declare in meta['declare']:
#print ("m", declare)
metaDict['declare'].append({
'doc': declare['doc'],
'class': className,
'type':0,
'name': className,
'pnames': [ p['name'] for p in declare['params'] ],
'rec':[ className+'('+', '.join([ p['type']+' '+p['name']+('='+p['optional'] if p['optional'] else '') for p in declare['params'] ])+')', declare['access'], className ] })
return metaDict
def genMethodList2(self, obj, meta):
metaDict = { 'properties':{}, 'methods':{}, 'classes':{} }
for i in range(meta.propertyOffset(), meta.propertyCount(), 1 ):
prop = meta.property(i)
propName = prop.name()
propName = propName[0].lower() + propName[1:]
if propName not in metaDict['properties']:
#print ("PROP", propName, dir(obj) )
propType = prop.typeName()
propValue = pprint.pformat( getattr(obj, propName) )
className = ''#type(obj).__name__
metaDict['properties'][propName]={ 'class': className, 'type':9, 'name': propName, 'rec':[ propName, propType, propValue ] }
for i in range(meta.methodOffset(), meta.methodCount(), 1 ):
meth = meta.method(i)
pnames = meth.parameterNames()
ptypes = meth.parameterTypes()
className = None
methName = str(meth.name(), 'utf-8') + "(" + str(b','.join( [ ptypes[i]+b" "+pnames[i] for i in range(0,meth.parameterCount()) ] ), 'utf-8') + ")"
if methName not in metaDict['methods']:
methType = self.caller.t['inspector'].METHOD_ACCESS[int(meth.access())] + " " + self.caller.t['inspector'].METHOD_TYPES[int(meth.methodType())]
className = ''#type(obj).__name__
methShortName = str(meth.name(), 'utf-8')
if methShortName in metaDict['methods']:
metaDict['methods'][methShortName]['rec'][0] += "\n" + methName
if len(pnames) > len(metaDict['methods'][methShortName]['pnames']):
metaDict['methods'][methShortName]['pnames'] = [ str(name, 'utf-8') for name in pnames]
else:
metaDict['methods'][methShortName]={ 'doc':'', 'class': className, 'type':0, 'name': methShortName, 'pnames': [ str(name, 'utf-8') for name in pnames], 'rec':[ methName, methType, meth.typeName() ] }
#>>metaDict['methods'][methName]={ 'class': className, 'type':0, 'name': str(meth.name(), 'utf-8'), 'rec':[ methName, methType, meth.typeName() ] }
return metaDict
class IOSTD(io.TextIOBase):
def __init__(self, caller, mode):
self.caller = caller
self.mode = mode
def write(self, content):
self.caller.processSTD(content,self.mode)
class PluginDevToolsConsole():
EXECUTE_KEYS = [
['Enter', Qt.Key_Return, Qt.NoModifier ],
['Shift + Enter', Qt.Key_Return, Qt.ShiftModifier ],
['Ctrl + E', Qt.Key_E, Qt.ControlModifier ],
['Ctrl + F5', Qt.Key_F5, Qt.ControlModifier ],
]
def __init__(self, caller):
super().__init__()
self.caller = caller
self.tempFilePath = os.path.dirname(os.path.realpath(__file__)) + ".console.temp.py"
self.watcher = None
self.currentExecuteKey = self.EXECUTE_KEYS[0]
self.historyTreeView = self.caller.centralWidget.consoleOutputBrowser
self.historyModel = QStandardItemModel()
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.historyModel)
self.historyTreeView.setModel(self.proxyModel)
self.textEdit = self.caller.centralWidget.consoleInputTextEdit
self.caller.centralWidget.consoleClearBtn.setIcon( Krita.instance().icon('list-remove') )
#self.caller.centralWidget.consoleDefaultExecuteBtn.setIcon( Krita.instance().icon('config-keyboard') )
self.caller.centralWidget.consoleTempScriptFileBtn.setIcon( Krita.instance().icon('edit-rename') )
self.caller.centralWidget.consoleSetScriptFileBtn.setIcon( Krita.instance().icon('document-open') )
self.caller.centralWidget.boolConsoleBindSTDOUT.setVisible(False)
self.textEditFilter = self.textEditFilterClass(self)
self.textEdit.installEventFilter(self.textEditFilter)
self.caller.centralWidget.consoleClearBtn.clicked.connect(self.clearConsole)
self.caller.centralWidget.consoleTempScriptFileBtn.toggled.connect(self.tempScriptFile)
self.caller.centralWidget.consoleSetScriptFileBtn.toggled.connect(self.setScriptFile)
for key in self.EXECUTE_KEYS:
self.caller.centralWidget.consoleDefaultExecuteCmb.addItem( key[0], key[1] )
self.caller.centralWidget.consoleDefaultExecuteCmb.currentIndexChanged.connect(self.executeKeyChanged)
self.caller.centralWidget.consoleDefaultExecuteCmb.setCurrentIndex(
caller.settings['console']['execute_key'] if 'execute_key' in caller.settings['console'] else 0
)
self.caller.centralWidget.consoleAutoExecuteModeCmb.currentIndexChanged.connect(self.slotAutoExecuteModeChanged)
self.caller.centralWidget.consoleAutoExecuteModeCmb.setCurrentIndex(
caller.settings['console']['auto_execute_mode'] if 'auto_execute_mode' in caller.settings['console'] else 0
)
self.caller.centralWidget.consoleFilter.textChanged.connect(self.searchTreeFilter)
if 'watch_file' in caller.settings['console']:
if caller.settings['console']['watch_file'] == self.tempFilePath:
self.caller.centralWidget.consoleTempScriptFileBtn.setChecked(True)
else:
self.caller.centralWidget.consoleSetScriptFileBtn.setChecked(True)
self.firstRun = True
def selected(self):
if not self.firstRun:
self.firstRun = True
def unselected(self):
pass
def executeKeyChanged(self, i):
self.currentExecuteKey = self.EXECUTE_KEYS[i]
self.caller.settings['console']['execute_key']=i
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
def clearConsole(self):
self.historyModel.clear()
def tempScriptFile(self, toggle):
tbtn = self.caller.centralWidget.consoleTempScriptFileBtn
sbtn = self.caller.centralWidget.consoleSetScriptFileBtn
if toggle:
if sbtn.isChecked():
self.setScriptFile(False)
tempFile = QUrl.fromLocalFile( self.tempFilePath )
if not os.path.exists( tempFile.toLocalFile() ):
f = open(tempFile.toLocalFile(),'w+')
f.write("# Script Name: Temp File Script\n\n")
f.close()
QDesktopServices.openUrl( tempFile )
if self.watchFile(tempFile.toLocalFile()):
tbtn.setChecked(True)
else:
self.unwatchFile()
tbtn.setChecked(False)
def setScriptFile(self, toggle):
tbtn = self.caller.centralWidget.consoleTempScriptFileBtn
sbtn = self.caller.centralWidget.consoleSetScriptFileBtn
if toggle:
if tbtn.isChecked():
self.tempScriptFile(False)
filename, _ = QFileDialog.getOpenFileName(
caption="Open script python file...", filter="Python files (*.py)"
)
if filename:
QDesktopServices.openUrl( QUrl.fromLocalFile(filename) )
if self.watchFile(filename):
sbtn.setChecked(True)
else:
self.unwatchFile()
sbtn.setChecked(False)
def unwatchFile(self, inputFile = None):
if self.watcher:
if inputFile is None:
inputFile = self.caller.settings['console']['watch_file']
del self.caller.settings['console']['watch_file']
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
self.watcher.fileChanged.disconnect(self.slotFileChanged)
self.watcher.removePath(inputFile)
self.watcher = None
def watchFile(self, inputFile):
if self.watcher is None:
self.watcher = QFileSystemWatcher()
self.watcher.fileChanged.connect(self.slotFileChanged)
if self.watcher.addPath(inputFile):
self.caller.settings['console']['watch_file']=inputFile
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
return True
else:
return False
def slotFileChanged(self):
f = open(self.caller.settings['console']['watch_file'], "r")
fdata = f.read()
if 'auto_execute_mode' not in self.caller.settings['console'] or self.caller.settings['console']['auto_execute_mode'] == 0:
self.executeCode(fdata)
else:
self.textEdit.setPlainText(fdata)
def slotAutoExecuteModeChanged(self, i):
self.caller.settings['console']['auto_execute_mode'] = i
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
def executeCode(self, script = None):
if not script:
script = self.textEdit.toPlainText()
rootItem = QStandardItem( script )
self.historyModel.appendRow( rootItem )
f = io.StringIO()
with redirect_stdout(f):
try:
code = compile(script, '', 'exec')
exec(code, {'__name__': '__main__'})
except SystemExit:
self.historyModel.clear()
self.textEdit.setPlainText("")
return
# user typed quit() or exit()
except Exception:
type_, value_, traceback_ = sys.exc_info()
if type_ == SyntaxError:
errorMessage = "%s\n%s" % (value_.text.rstrip(), " " * (value_.offset - 1) + "^")
# rstrip to remove trailing \n, output needs to be fixed width font for the ^ to align correctly
errorText = "Syntax Error on line %s" % value_.lineno
elif type_ == IndentationError:
# (no offset is provided for an IndentationError
errorMessage = value_.text.rstrip()
errorText = "Unexpected Indent on line %s" % value_.lineno
else:
errorText = traceback.format_exception_only(type_, value_)[0]
format_string = "In file: {0}\nIn function: {2} at line: {1}. Line with error:\n{3}"
tbList = traceback.extract_tb(traceback_)
tb = tbList[-1]
errorMessage = format_string.format(*tb)
item = QStandardItem( "%s\n%s" % (errorText, errorMessage) )
item.setForeground(QBrush(QColor('#990000')))
rootItem.appendRow( item )
item = QStandardItem( f.getvalue() )
item.setForeground(QBrush(QColor('#000099')))
rootItem.appendRow( item )
self.historyTreeView.expand(self.proxyModel.mapFromSource(rootItem.index()));
self.historyTreeView.scrollToBottom()
self.textEdit.setPlainText("")
def searchTreeFilter(self, text):
self.proxyModel.setFilterRole(Qt.DisplayRole)
self.proxyModel.setFilterFixedString(text)
class textEditFilterClass(QWidget):
def __init__(self, caller, parent=None):
super().__init__()
self.caller = caller
def eventFilter(self, obj, event):
etype = event.type()
if etype == 6 and event.key() == self.caller.currentExecuteKey[1] and QApplication.keyboardModifiers() == self.caller.currentExecuteKey[2]:
self.caller.executeCode()
#print ("ENTER!")
return True
return False
class PluginDevToolsIcons():
def __init__(self, caller):
super().__init__()
self.caller = caller
self.page = self.caller.centralWidget.iconsTab
self.listView = self.caller.centralWidget.iconsListView
self.listView.setGridSize( QSize(128,128) )
self.listModel = QStandardItemModel()
self.proxyModel = QSortFilterProxyModel()
self.proxyModel.setSourceModel(self.listModel)
self.listView.setModel(self.proxyModel)
self.proxyModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.listView.clicked.connect(self.iconClicked)
self.firstRun = False
def selected(self):
if not self.firstRun:
self.firstRun = True
self.loadIconList()
self.iconsGroup = QButtonGroup(self.page)
self.iconsGroup.setExclusive(False)
self.iconsGroup.addButton( self.caller.centralWidget.boolIconsKrita )
self.iconsGroup.addButton( self.caller.centralWidget.boolIconsKritaExtra )
self.iconsGroup.addButton( self.caller.centralWidget.boolIconsTheme )
self.iconsGroup.buttonToggled.connect(self.loadIconList)
self.caller.centralWidget.iconsFilter.textChanged.connect(self.searchFilter)
def unselected(self):
pass
def loadIconList(self):
iconDict = {}
self.listModel.clear()
iconFormats = ["*.svg","*.svgz","*.svz","*.png"]
if self.caller.centralWidget.boolIconsKrita.isChecked():
iconList = QDir(":/pics/").entryList(iconFormats, QDir.Files)
iconList += QDir(":/").entryList(iconFormats, QDir.Files)
for iconName in iconList:
name = iconName.split('_',1)
if any(iconSize == name[0] for iconSize in [ '16', '22', '24', '32', '48', '64', '128', '256', '512', '1048' ]):
iconName = name[1]
name = iconName.split('_',1)
if any(iconSize == name[0] for iconSize in [ 'light', 'dark' ]):
iconName = name[1]
name = iconName.split('.')
iconName = name[0]
iconDict[iconName]={}
if self.caller.centralWidget.boolIconsKritaExtra.isChecked():
iconList = QDir(":/icons/").entryList(iconFormats, QDir.Files)
#iconList += QDir(":/images/").entryList(iconFormats, QDir.Files)
for iconName in iconList:
name = iconName.split('.')
iconName = name[0]
iconDict[iconName]={}
if self.caller.centralWidget.boolIconsTheme.isChecked():
with open( os.path.dirname(os.path.realpath(__file__)) + '/ThemeIcons.txt' ) as f:
for iconName in f.readlines():
iconDict[iconName.rstrip()]={}
for iconName, iconInfo in sorted(iconDict.items()):
item = QStandardItem( Krita.instance().icon(iconName), iconName )
self.listModel.appendRow( item )
def searchFilter(self, text):
self.proxyModel.setFilterFixedString(text)
def iconClicked(self, rec):
self.caller.centralWidget.consoleInputTextEdit.setText("Krita.instance().icon('"+ self.proxyModel.index( rec.row(), 0 ).data() +"')")
self.caller.centralWidget.tabWidget.setCurrentIndex(2)
class PluginDevToolsActions():
def __init__(self, caller):
super().__init__()
self.caller = caller
self.tableView = caller.centralWidget.actionsTableView
self.tableModel = QStandardItemModel()
self.proxyModel = QSortFilterProxyModel()
self.tableModel.setHorizontalHeaderLabels(['Name', 'Description'])
self.proxyModel.setSourceModel(self.tableModel)
self.tableView.setModel(self.proxyModel)
self.proxyModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyModel.setFilterKeyColumn(-1)
self.tableView.doubleClicked.connect(self.actionClicked)
self.firstRun = False
def selected(self):
if not self.firstRun:
self.firstRun = True
parentItem = self.tableModel.invisibleRootItem()
for action in Krita.instance().actions():
parentItem.appendRow([
QStandardItem( action.objectName() ),
QStandardItem( action.toolTip() )
])
#print ( action.objectName(), action.toolTip() )
self.caller.centralWidget.actionsFilter.textChanged.connect(self.searchFilter)
def unselected(self):
pass
def searchFilter(self, text):
self.proxyModel.setFilterFixedString(text)
def actionClicked(self, rec):
self.caller.centralWidget.consoleInputTextEdit.setText("Krita.instance().action('"+ self.proxyModel.index( rec.row(), 0 ).data() +"').trigger()")
self.caller.centralWidget.tabWidget.setCurrentIndex(2)
#print ( "REC!", self.tableModel.index( rec.row(), 0 ).data() )
class PluginDevToolsSelector():
KEY_MODS = [
[Qt.ShiftModifier, Qt.Key_Shift],
[Qt.ControlModifier, Qt.Key_Control],
[Qt.AltModifier, Qt.Key_Alt],
[Qt.MetaModifier, Qt.Key_Meta],
]
def __init__(self, caller):
super().__init__()
self.caller = caller
self.modKey = self.KEY_MODS[0]
self.windowFilter = self.windowFilterClass(self)
self.selectorOutput = caller.centralWidget.selectorOutputLabel
caller.centralWidget.selectorKeyCmb.currentIndexChanged.connect(self.changeSelectorModifier)
caller.centralWidget.selectorKeyCmb.setCurrentIndex(
caller.settings['selector']['modkey'] if 'modkey' in caller.settings['selector'] else 0
)
self.currentWidget = None
self.currentWindow = None
self.useStyleSheet = "*[DevToolsHoverWithSelector='true'] { background-color: rgba(0, 0, 155, 50); border: 1px solid #000080; }"
self.useStyleSheet = None
#self.caller.qwin.setStyleSheet( self.caller.qwin.styleSheet() + '*[DevToolsHoverWithSelector="true"] { background-color: rgba(0, 0, 155, 50); border: 1px solid #000080; }' )
self.createSelector(self.caller.qwin)
def changeSelectorModifier(self, i):
self.caller.settings['selector']['modkey'] = i
Krita.instance().writeSetting("", "pluginDevToolsSettings", json.dumps(self.caller.settings) )
self.modKey = self.KEY_MODS[i]
def createSelector(self, window):
#print ("create selector!")
selectorWidget = QWidget(window)
selectorWidget.setObjectName("DevToolsSelectorWidget")
#selectorWidget.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.WindowTransparentForInput)
selectorWidget.setWindowFlags(Qt.WindowTransparentForInput)
selectorWidget.setAttribute( Qt.WA_TransparentForMouseEvents )
selectorWidget.setVisible(False)
selectorWidget.setStyleSheet("background-color: rgba(0, 0, 155, 50); border: 1px solid #000080;")
#print ("selector id", selectorWidget)
self.selectorWidget = selectorWidget
def selected(self):
self.startSampling(QtWidgets.qApp.activeWindow())
def unselected(self):
self.stopSampling()
def startSampling(self, window):
self.currentWindow = window
#>winStyle = window.styleSheet()
#>if "DevToolsHoverWithSelector=" not in winStyle:
#> window.setStyleSheet( winStyle + '*[DevToolsHoverWithSelector="true"] { background-color: #000000; border: 3px solid #FF0000; color: #FF0000 }' )
QtWidgets.qApp.focusChanged.connect(self.focusItem)
QtWidgets.qApp.installEventFilter(self.windowFilter)
#>>>window.installEventFilter(self.windowFilter)
def focusItem(self):
#if self.currentWindow is not QApplication.activePopupWidget():
# print ("POPUP CHANGE!")
win = QtWidgets.qApp.activeWindow()
if self.currentWindow is not win:
if win:
if self.selectorWidget and not sip.isdeleted(self.selectorWidget):
self.selectorWidget.setVisible(False)
wid = win.findChild(QWidget, "DevToolsSelectorWidget", Qt.FindDirectChildrenOnly)
if wid:
self.selectorWidget = wid
else:
self.createSelector(win)
# event filter never happens
self.currentWindow = win
#print ("FOCUS CHANGED!", win, QApplication.activeModalWidget(), QApplication.activePopupWidget())
def stopSampling(self, localCall = True):
currentWindow = self.currentWindow
if self.currentWindow:
if localCall:
QtWidgets.qApp.focusChanged.disconnect(self.focusItem)
QtWidgets.qApp.installEventFilter(self.windowFilter)
#??self.currentWindow.removeEventFilter(self.windowFilter)
self.currentWindow = None
self.selectorWidget.setVisible(False)
if self.currentWidget:
if self.useStyleSheet:
self.currentWidget.setProperty('DevToolsHoverWithSelector', False)
self.currentWidget.setStyleSheet(self.currentWidget.styleSheet().replace(self.useStyleSheet,'') )
#>>self.caller.t['inspector'].selectItemByRef(self.currentWidget)
if localCall:
self.caller.centralWidget.selectorOutputLabel.setText("None")
self.caller.t['inspector'].loadItemInfo(self.currentWidget)
self.caller.t['inspector'].firstRun = True
self.caller.t['inspector'].refreshItems(self.currentWidget, currentWindow)
#self.caller.t['inspector'].selectItemByRef(self.currentWidget)
self.currentWidget = None
def finishedSampling(self):
self.caller.centralWidget.tabWidget.setCurrentIndex(1)
def findAncestor(self, ancestor, obj):
if not hasattr(obj, 'parent'): return False
parent = obj.parent()
while True:
if not parent:
return False
elif ancestor is parent:
return True
obj = parent
parent = obj.parent()
def setCurrentSelector(self, obj, localCall = True):
if obj and not sip.isdeleted(obj) and obj is not self.currentWidget and self.findAncestor(self.currentWindow,obj):
self.selectorWidget.setVisible(True)
if self.useStyleSheet:
if self.currentWidget:
self.currentWidget.setProperty('DevToolsHoverWithSelector', False)
self.currentWidget.setStyleSheet(self.currentWidget.styleSheet().replace(self.useStyleSheet,""))
obj.setProperty('DevToolsHoverWithSelector', True)
obj.setStyleSheet(obj.styleSheet() + self.useStyleSheet)
obj.update()
if hasattr(obj, 'geometry'):
rect = obj.geometry()
if obj.metaObject().superClass().className() == 'QLayout' or obj.metaObject().superClass().className() == 'QBoxLayout':
layoutItem = obj.itemAt(0)
if layoutItem is None or layoutItem.widget() is None:
return
pos = layoutItem.widget().mapTo(self.currentWindow, QPoint(0,0) )
else:
pos = obj.mapTo(self.currentWindow, QPoint(0,0) )
rect.moveTo(pos)
self.selectorWidget.setGeometry( rect )
#obj.mapToGlobal(widgetRect.topLeft())
#print (obj.property('DevToolsHoverWithSelector') )
self.currentWidget = obj
if localCall: self.caller.centralWidget.selectorOutputLabel.setText("[" + type(obj).__name__ + "] " + obj.objectName())
class windowFilterClass(QWidget):
def __init__(self, caller, parent=None):
super().__init__()
self.caller = caller
def eventFilter(self, obj, event):
etype = event.type()
if etype == 129 or (etype == 6 and event.key() == self.caller.modKey[1]):
if etype == 6 and event.key() == self.caller.modKey[1]:
win = QtWidgets.qApp.activeWindow()
self.caller.selectorWidget=win.findChild(QWidget, "DevToolsSelectorWidget", Qt.FindDirectChildrenOnly)
if QApplication.keyboardModifiers() == self.caller.modKey[0]:
pos = QCursor.pos()
onWidget = QApplication.widgetAt(pos)
self.caller.setCurrentSelector(onWidget)
elif etype == 7 and event.key() == self.caller.modKey[1] and self.caller.currentWidget:
self.caller.finishedSampling()
#print (obj, event.type())
return False
class PluginDevToolsInspector():
METHOD_TYPES = [ 'Method', 'Signal', 'Slot', 'Constructor' ]
METHOD_ACCESS = [ 'Private', 'Protected', 'Public' ]
def __init__(self, caller):
super().__init__()
self.caller = caller
self.currentWidget = None
self.currentTableItem = None
self.eventViewer = self.PluginDevToolsEventViewer(self)
self.treeObjList = []
self.treeView = caller.centralWidget.inspectorTreeView
self.tableView = caller.centralWidget.inspectorTableView
self.treeView.setIndentation(10)
self.treeModel = QStandardItemModel()
self.proxyTreeModel = QSortFilterProxyModel()
self.proxyTreeModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyTreeModel.setRecursiveFilteringEnabled(True)
self.proxyTreeModel.setFilterKeyColumn(-1)
self.treeModel.setHorizontalHeaderLabels(['Class', 'Name', 'Meta Class', 'From', 'Text/Value'])
self.proxyTreeModel.setSourceModel(self.treeModel)
self.treeView.setModel(self.proxyTreeModel)
self.treeSelectModel = self.treeView.selectionModel()
self.tableModel = QStandardItemModel()
self.proxyTableModel = QSortFilterProxyModel()
self.proxyTableModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyTableModel.setFilterKeyColumn(-1)
self.tableModel.setHorizontalHeaderLabels(['Name', "Type", 'Value'])
self.proxyTableModel.setSourceModel(self.tableModel)
self.tableView.setModel(self.proxyTableModel)
self.treeSelectModel.selectionChanged.connect(self.itemSelectionChanged)
self.caller.centralWidget.inspectorRefreshBtn.setIcon( Krita.instance().icon('view-refresh') )
self.caller.centralWidget.inspectorParentBtn.setIcon( Krita.instance().icon('arrow-up') )
self.caller.centralWidget.inspectorCodeBtn.setIcon( Krita.instance().icon('document-print-preview') )
self.caller.centralWidget.inspectorObjDocsBtn.setIcon( Krita.instance().icon('system-help') )
self.caller.centralWidget.inspectorSelectorBtn.setIcon( Krita.instance().icon('pivot-point') )
self.caller.centralWidget.inspectorEventViewerBtn.setIcon( Krita.instance().icon('klipper') )
self.caller.centralWidget.inspectorObjDocsBtn.clicked.connect(self.getObjDocs)
self.caller.centralWidget.inspectorPropDocsBtn.clicked.connect(self.getPropDocs)
self.caller.centralWidget.inspectorCodeBtn.clicked.connect(self.getCode)
self.caller.centralWidget.inspectorParentBtn.clicked.connect(self.getParent)
self.caller.centralWidget.inspectorRefreshBtn.clicked.connect(self.refreshItems)
self.caller.centralWidget.inspectorEventViewerBtn.clicked.connect(self.openEventViewer)
self.showCurrentWidgetHighlight = False
self.caller.centralWidget.inspectorSelectorBtn.toggled.connect(self.showCurrentWidget)
#self.caller.centralWidget.inspectorSelectorBtn.released.connect(self.hideCurrentWidget)
self.caller.centralWidget.inspectorUpdateLayoutWidget.setVisible(False)
self.tableView.doubleClicked.connect(self.showUpdateLayout)
self.caller.centralWidget.inspectorUpdateBtn.pressed.connect(self.commitUpdateLayout)
self.caller.centralWidget.inspectorUpdateCancelBtn.pressed.connect(self.hideUpdateLayout)
self.caller.centralWidget.inspectorFilter.textChanged.connect(self.searchTreeFilter)
self.caller.centralWidget.inspectorTableFilter.textChanged.connect(self.searchTableFilter)
self.firstRun = False
def selected(self):
if not self.firstRun:
self.firstRun = True
for win in QApplication.instance().topLevelWidgets():
if isinstance(win, QMainWindow):
self.loadTreeItems(win, 0, 'topLevelWidgets')
if self.showCurrentWidgetHighlight:
self.showCurrentWidget(True)
def unselected(self):
if self.showCurrentWidgetHighlight:
self.showCurrentWidget(False, True)
def openEventViewer(self):
self.eventViewer.showFor(self.currentWidget)
def showUpdateLayout(self, rec):
if sip.isdeleted(self.currentWidget): return
self.currentTableItem = rec
prop = self.proxyTableModel.index( rec.row(), 0 ).data()
print ("DC", rec.column(), rec.row(), prop, "set"+prop[0].capitalize() + prop[1:], hasattr( self.currentWidget, "set"+prop[0].capitalize() + prop[1:] ) )
if rec.column() == 2:
if hasattr( self.currentWidget, "set"+prop[0].capitalize() + prop[1:] ):
self.caller.centralWidget.inspectorUpdateTextEdit.setPlainText( str(self.currentWidget.property(prop)) )
self.caller.centralWidget.inspectorUpdateLayoutWidget.setVisible(True)
def hideUpdateLayout(self):
self.caller.centralWidget.inspectorUpdateLayoutWidget.setVisible(False)
def commitUpdateLayout(self):
rec = self.currentTableItem
prop = str(self.proxyTableModel.index( rec.row(), 0 ).data())
attrName = "set" + prop[0].capitalize() + prop[1:]
attrValue = self.caller.centralWidget.inspectorUpdateTextEdit.toPlainText()
attrType = type(self.currentWidget.property(prop)).__name__
if attrType == 'bool':
attrValue = True if attrValue.capitalize() == 'True' or attrValue == '1' or attrValue == 't' else False
elif attrType == 'int':
attrValue = int(attrValue)
elif attrType == 'float':
attrValue = float(attrValue)
elif attrType == 'QRect':
params = tuple( map(int, (attrValue.split('QRect('))[1].replace(")","").split(",") ) )
attrValue = QRect(*params)
elif attrType == 'QRectF':
params = tuple( map(float, (attrValue.split('QRectF('))[1].replace(")","").split(",") ) )
attrValue = QRect(*params)
elif attrType == 'QPoint':
params = tuple( map(int, (attrValue.split('QPoint('))[1].replace(")","").split(",") ) )
attrValue = QPoint(*params)
elif attrType == 'QPointF':
params = tuple( map(float, (attrValue.split('QPointF('))[1].replace(")","").split(",") ) )
attrValue = QPointF(*params)
elif attrType == 'QSize':
params = tuple( map(int, (attrValue.split('QSize('))[1].replace(")","").split(",") ) )
attrValue = QSize(*params)
elif attrType == 'QSizeF':
params = tuple( map(float, (attrValue.split('QSizeF('))[1].replace(")","").split(",") ) )
attrValue = QSizeF(*params)
if hasattr(self.currentWidget,attrName):
getattr(self.currentWidget,attrName)( attrValue )
self.proxyTableModel.setData(self.proxyTableModel.index( rec.row(), 2 ), pprint.pformat(attrValue) )
else:
attrName = "to" + prop[0].capitalize() + prop[1:]
if hasattr(self.currentWidget,attrName):
getattr(self.currentWidget,attrName)( attrValue )
self.proxyTableModel.setData(self.proxyTableModel.index( rec.row(), 2 ), pprint.pformat(attrValue) )
def showCurrentWidget(self, toggle, switchTab = False):
if toggle:
self.showCurrentWidgetHighlight = True
self.caller.t['selector'].currentWindow = win = QtWidgets.qApp.activeWindow()
self.caller.t['selector'].selectorWidget=win.findChild(QWidget, "DevToolsSelectorWidget", Qt.FindDirectChildrenOnly)
self.caller.t['selector'].setCurrentSelector(self.currentWidget, False)
else:
if switchTab is False:
self.showCurrentWidgetHighlight = False
self.caller.t['selector'].stopSampling(False)
def refreshItems(self, currentItem = None, currentWindow = None):
self.treeObjList = []
self.caller.centralWidget.inspectorFilter.setText("")
self.treeModel.clear()
self.treeModel.setHorizontalHeaderLabels(['Class', 'Name', 'Meta Class', 'From', 'Text/Value'])
for win in QApplication.instance().topLevelWidgets():
if isinstance(win, QMainWindow) or (currentWindow and win is currentWindow):
self.loadTreeItems(win, 0, 'topLevelWidgets', None, currentItem)
if self.currentWidget:
self.loadItemInfo( self.currentWidget )
indexes = self.treeView.selectionModel().selectedIndexes()
if indexes:
self.treeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
def getObjDocs(self):
if self.currentWidget:
url = "https://doc.qt.io/qt-5.12/" + type(self.currentWidget).__name__ + ".html"
QDesktopServices.openUrl(QUrl(url))
def getPropDocs(self):
index = self.tableView.currentIndex()
if self.currentWidget and index:
prop = self.tableView.model().index(index.row(), 0).data(101)
if prop:
url = "https://doc.qt.io/qt-5.12/" + prop['class'] + ".html#" + prop['name']
if prop['type'] == 9:
url += "-prop"
#print( "DT", self.tableView.model().index(index.row(), 1).data(101) )
QDesktopServices.openUrl(QUrl(url))
def getCode(self):
obj = self.currentWidget
lastNamed = None
lastItem = None
docker = None
statusbar = False
mdi = False
path = []
while True:
path.append(obj)
if type(obj).__name__ == "QDockWidget":
if obj.parent() and type(obj.parent()).__name__ == "QMainWindow":
docker = obj.objectName()
lastItem = obj
elif type(obj).__name__ == "QStatusBar":
if obj.parent() and type(obj.parent()).__name__ == "QMainWindow":
statusbar = True
lastItem = obj
elif obj.metaObject().className() == "QMdiArea":
mdi = True
if obj.objectName() and not lastNamed:
lastNamed = obj
obj=obj.parent()
if not obj:
break
onWidget = None
if docker:
codeBase = "from krita import *\n\nqdock = next((w for w in Krita.instance().dockers() if w.objectName() == '"+ docker +"'), None)\n"
onWidget = "qdock"
path.pop()
# this is slightly faster but adds too many lines for console
#codeBase = """qdock = None
#for widget in Krita.instance().dockers():
# if widget.objectName() == '"""+ docker +"""':
# qdock = widget
# break
#"""
elif statusbar:
codeBase = "from krita import *\n\nqsbar = Krita.instance().activeWindow().qwindow().statusBar()\n#warning: statusbar is often empty when no document is open\n"
onWidget = "qsbar"
path.pop()
else:
codeBase = "from krita import *\n\nqwin = Krita.instance().activeWindow().qwindow()\n"
onWidget = "qwin"
if self.currentWidget.objectName():
codeBase += "wobj = "+onWidget+".findChild("+ type(self.currentWidget).__name__ +",'"+ self.currentWidget.objectName() +"')\n"
elif path:
if type(lastNamed).__name__ != "QMainWindow" and (not docker or not lastNamed.objectName() == docker):
codeBase += "pobj = "+onWidget+".findChild("+ type(lastNamed).__name__ +",'"+ lastNamed.objectName() +"')\n"
onWidget = "pobj"
backFill = False
for item in reversed(path):
if item is lastItem:
if len(lastItem.findChildren(type(self.currentWidget))) == 1:
codeBase += "wobj = "+onWidget+".findChild("+ type(self.currentWidget).__name__ +")\n"
else:
backFill = True
codeBase += "# TODO "+type(item).__name__ + ""
elif item is lastNamed:
if len(lastNamed.findChildren(type(self.currentWidget))) == 1:
codeBase += "wobj = "+onWidget+".findChild("+ type(self.currentWidget).__name__ +")\n"
else:
backFill = True
codeBase += "# TODO "+type(item).__name__ + ""
elif backFill:
codeBase += " > "+type(item).__name__ +""
tobj = []
#print ("PATH", path, codeBase)
if 'wobj = ' in codeBase:
vals = { }
codePrepare = re.sub(r'wobj = (\w+?)\.findChild\(',r'wobj = \1.findChildren(',codeBase.replace('from krita import *',''))
print ("code", codePrepare)
codeCheck = compile( codePrepare, '', 'exec' )
exec(codeCheck, vals, globals())
tobj = vals['__builtins__']['globals']()['wobj']
if len(tobj) > 1:
codeBase += "\n # Warning, multiple instances of item found. Consider using findChildren() instead of findChild or chain findChild through named parents"
self.caller.centralWidget.consoleInputTextEdit.setText(codeBase)
self.caller.centralWidget.tabWidget.setCurrentIndex(2)
def getParent(self):
if self.currentWidget and not sip.isdeleted(self.currentWidget):
parent = self.currentWidget.parent()
if parent:
self.loadItemInfo( parent )
def searchTreeFilter(self, text):
self.proxyTreeModel.setFilterRole(Qt.DisplayRole)
if len(text) >= 3:
self.proxyTreeModel.setFilterFixedString(text)
self.treeView.expandAll()
else:
self.treeView.collapseAll()
self.proxyTreeModel.setFilterFixedString("")
#self.proxyTreeModel.setFilterWildcard("*{}*".format(text))
indexes = self.treeView.selectionModel().selectedIndexes()
if indexes:
self.treeView.expand(indexes[0])
self.treeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
self.treeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
def searchTableFilter(self, text):
self.proxyTableModel.setFilterWildcard(text)
def selectItemByRef(self, obj):
pass
#self.proxyTreeModel.setFilterRole(102)
#self.proxyTreeModel.setFilterFixedString( str(id(obj)) )
#self.proxyTreeModel.setFilterFixedString( "" )
#indexes = self.proxyTreeModel.match( self.proxyTreeModel.index(0,0) , 102, hex(id(obj)), 1, Qt.MatchRecursive )
#print ("MATCH", indexes)
#if indexes:
# #self.treeSelectModel.select( indexes[0], self.treeSelectModel.Select | self.treeSelectModel.Rows )
# self.treeView.expand(indexes[0])
# self.treeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
# self.treeView.scrollTo(indexes[0], QAbstractItemView.PositionAtCenter)
#idx = self.treeModel.match( self.treeModel.index(0,0) , 101, obj, 1, Qt.MatchRecursive )
#if idx:
# print ( idx[0].row() )
#>>self.treeSelectModel.select(idx[0], self.treeSelectModel.Select | self.treeSelectModel.Rows )
def itemSelectionChanged(self, new, old):
indexes = new.indexes()
if indexes:
obj = self.treeObjList[indexes[0].data(101)]
if sip.isdeleted(obj):
item = self.treeModel.itemFromIndex( self.proxyTreeModel.mapToSource(indexes[0]) )
item.setIcon( Krita.instance().icon('window-close') )
else:
self.loadItemInfo( obj )
def loadItemInfo(self, obj):
self.hideUpdateLayout()
if sip.isdeleted(obj): return
self.currentWidget = obj
if self.showCurrentWidgetHighlight:
self.caller.t['selector'].setCurrentSelector(self.currentWidget, False)
self.tableModel.clear()
self.tableModel.setHorizontalHeaderLabels(['Name', "Type", 'Value'])
parentItem = self.tableModel.invisibleRootItem()
metaDict = { 'properties':{}, 'methods':{} }
meta = obj.metaObject()
parentItem.appendRow([
self.subheaderItem("Object"),
self.subheaderItem(""),
self.subheaderItem("")
])
parentItem.appendRow([
QStandardItem( "Name" ),
QStandardItem( "" ),
QStandardItem( obj.objectName() ),
])
parentItem.appendRow([
QStandardItem( "Class" ),
QStandardItem( "" ),
QStandardItem( type(obj).__name__ ),
])
parentItem.appendRow([
QStandardItem( "Meta Class" ),
QStandardItem( "" ),
QStandardItem( meta.className() ),
])
if obj.parent():
parentItem.appendRow([
QStandardItem( "Parent" ),
QStandardItem( "" ),
QStandardItem( type(obj.parent()).__name__ + " " + obj.parent().objectName() ),
])
inheritsFrom = []
otherMethods = {}
for k in dir(obj):
if not k.startswith('__'):
objAttr = getattr(obj,k)
if hasattr(objAttr,'__name__'):
otherMethods[objAttr.__name__] = True
self.fillMethods(obj, metaDict, otherMethods, inheritsFrom)
'''
if hasattr(obj, 'model'):
self.fillMethods(obj.model(), metaDict, {}, [], 'model().')
if hasattr(obj.model(),'sourceModel'):
self.fillMethods(obj.model().sourceModel(), metaDict, {}, [], 'model().sourceModel().')
if hasattr(obj, 'selectionModel'):
self.fillMethods(obj.selectionModel(), metaDict, {}, [], 'selectionModel().')
'''
parentItem.appendRow([
QStandardItem( "Inherits From" ),
QStandardItem( "" ),
QStandardItem( ' > '.join(inheritsFrom) ),
])
parentItem.appendRow([
self.subheaderItem("Properties"),
self.subheaderItem(""),
self.subheaderItem("")
])
for k, prop in sorted(metaDict['properties'].items()):
item = [
QStandardItem( prop['rec'][0] ),
QStandardItem( prop['rec'][1] ),
QStandardItem( prop['rec'][2] ),
]
item[0].setData(prop, 101)
parentItem.appendRow(item)
parentItem.appendRow([
self.subheaderItem("Slots and Signals"),
self.subheaderItem(""),
self.subheaderItem("")
])
for k, meth in sorted(metaDict['methods'].items()):
item = [
QStandardItem( meth['rec'][0] ),
QStandardItem( meth['rec'][1] ),
QStandardItem( meth['rec'][2] ),
]
item[0].setData(meth, 101)
parentItem.appendRow(item)
parentItem.appendRow([
self.subheaderItem("Methods"),
self.subheaderItem(""),
self.subheaderItem("")
])
for k in sorted(otherMethods.keys()):
if not k.startswith('__'):
objDoc = getattr(obj,k).__doc__
if objDoc and '(self' in objDoc:
objSig = objDoc.split(' -> ')
item = [
QStandardItem( objSig[0].replace('(self, ','(').replace('(self)','()') ),
QStandardItem( 'Method' ),
QStandardItem( objSig[1] if len(objSig) > 1 else 'void' ),
]
item[0].setData(meth, 101)
parentItem.appendRow(item)
def fillMethods(self,obj, metaDict, otherMethods, inheritsFrom, prepend=''):
meta = obj.metaObject()
while True:
for i in range(meta.propertyOffset(), meta.propertyCount(), 1 ):
prop = meta.property(i)
propName = prop.name()
if propName not in metaDict['properties']:
if propName == 'sizeHint' and obj.property('minimumSizeHint').isEmpty(): continue
propType = prop.typeName()
propValue = ''
try:
propValue = pprint.pformat( obj.property(propName) )
except TypeError:
propValue = "[unavailable]"
className = None
if inheritsFrom:
propType = propType + " [from "+meta.className()+"]"
className = meta.className()
else:
className = type(obj).__name__
metaDict['properties'][propName]={ 'class': meta.className(), 'type':9, 'name': propName, 'rec':[ propName, propType, propValue ] }
for i in range(meta.methodOffset(), meta.methodCount(), 1 ):
meth = meta.method(i)
pnames = meth.parameterNames()
ptypes = meth.parameterTypes()
className = None
methName = str(meth.name(), 'utf-8')
if methName in otherMethods:
del otherMethods[methName]
methName += "(" + str(b','.join( [ ptypes[i]+b" "+pnames[i] for i in range(0,meth.parameterCount()) ] ), 'utf-8') + ")"
if methName not in metaDict['methods']:
methType = self.METHOD_ACCESS[int(meth.access())] + " " + self.METHOD_TYPES[int(meth.methodType())]
if inheritsFrom:
methType = methType + " [from "+meta.className()+"]"
className = meta.className()
else:
className = type(obj).__name__
metaDict['methods'][methName]={ 'class': meta.className(), 'type':0, 'name': str(meth.name(), 'utf-8'), 'rec':[ prepend+methName, methType, meth.typeName() ] }
meta = meta.superClass()
if meta:
inheritsFrom.append(meta.className())
else:
break
def subheaderItem(self, text):
objectHeader = QStandardItem(text)
font = objectHeader.font()
font.setBold(True)
objectHeader.setFont( font )
objectHeader.setColumnCount(1)
objectHeader.setEnabled(False)
return objectHeader
def loadTreeItems(self, pObj, depth, objType, parentItem = None, currentItem = None):
#if objType == 'viewport' and pObj: print ("VIEWPORT!!", pObj.children())
if parentItem is None:
parentItem = self.treeModel.invisibleRootItem()
parentItem = self.setItem(pObj, parentItem, depth, objType)
if isinstance(pObj, QMainWindow):
#idx = self.treeModel.indexFromItem(parentItem)
self.selectItemByRef(pObj)
#self.treeSelectModel.select(idx, self.treeSelectModel.Select | self.treeSelectModel.Rows )
#self.loadItemInfo(pObj)
objType = 'children'
depth = depth + 1
if currentItem and pObj is currentItem:
idx = parentItem.index()
self.treeSelectModel.select( self.proxyTreeModel.mapFromSource(idx) , self.treeSelectModel.Select | self.treeSelectModel.Rows )
self.treeView.expand(idx)
#print ("TREE", idx, parentItem.text() )
onItem = parentItem
for i in range(0,depth-1):
onItem = onItem.parent()
idx = onItem.index()
self.treeView.expand( self.proxyTreeModel.mapFromSource(idx) )
#print ("TREE=", i, idx.row(), onItem.text() )
for cObj in pObj.children():
cObjType = objType
if type(cObj).__name__ == 'QWidget' and cObj.objectName() == 'qt_scrollarea_viewport' and self.hasMethod(pObj,'viewport'):
cObjType = "viewport/children"
item = self.setItem(cObj, parentItem, depth, cObjType)
#if self.hasMethod(cObj,'viewport'):
# self.loadTreeItems(cObj.viewport(), depth + 1, 'viewport', item)
if self.hasMethod(cObj,'children') and type(cObj).__name__ != 'PluginDevToolsDocker':
#print ("CH", cObj.children())
self.loadTreeItems(cObj, depth + 1, 'children', item, currentItem)
def setItem(self, obj, parentItem, depth, objType):
text = self.getText(obj)
#print ("RECORD", depth);
visible = True
if objType == 'topLevelWidgets' and obj.isHidden():
visible = False
elif self.hasMethod(obj,'isVisible'):
visible = obj.isVisible()
parentItem.appendRow([
QStandardItem( Krita.instance().icon('visible' if visible else 'novisible'), type(obj).__name__ ),
#QStandardItem( Krita.instance().icon('novisible' if objType == 'topLevelWidgets' and obj.isHidden() else 'visible'), type(obj).__name__ ),
QStandardItem( obj.objectName() ),
QStandardItem( obj.metaObject().className() ),
QStandardItem( objType ),
QStandardItem( str(text) )
])
item = parentItem.child(parentItem.rowCount() - 1)
item.setData( len(self.treeObjList), 101 )
self.treeObjList.append(obj)
return item
def getText(self, obj):
text = ''
if self.hasMethod(obj,'text'):
text = obj.text()
elif self.hasMethod(obj,'value'):
text = obj.value()
elif self.hasMethod(obj,'currentText'):
text = obj.currentText()
elif self.hasMethod(obj,'html'):
text = obj.html()
elif self.hasMethod(obj,'windowTitle'):
text = obj.windowTitle()
return text
def hasMethod(self, obj, method):
return True if hasattr(obj, method) and callable(getattr(obj, method)) else False
class PluginDevToolsEventViewer(QDialog):
def __init__(self, caller):
super().__init__()
self.caller = caller
self.oldSTDOUT = None
self.newSTDOUT = caller.caller.IOSTD(self,0)
self.stdoutBuffer = []
self.oldSTDERR = None
self.newSTDERR = caller.caller.IOSTD(self,1)
self.stderrBuffer = []
self.started = False
self.startTime = 0.00
self.lastEventType = ''
self.lastEventItem = None
self.codeItem = ['','']
self.currentWidget = None
self.centralWidget = uic.loadUi(os.path.dirname(os.path.realpath(__file__)) + '/EventViewerWidget.ui')
self.centralWidget.startBtn.setIcon(Krita.instance().icon('media-playback-start'))
layout = QVBoxLayout()
layout.addWidget(self.centralWidget)
self.setLayout(layout)
self.eventDict = {}
self.signalsDict = { 'current':{} }
self.listenTreeModel = QStandardItemModel()
self.proxyListenTreeModel = QSortFilterProxyModel()
self.proxyListenTreeModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyListenTreeModel.setRecursiveFilteringEnabled(True)
self.proxyListenTreeModel.setFilterKeyColumn(-1)
self.proxyListenTreeModel.setSourceModel(self.listenTreeModel)
self.centralWidget.eventListenTreeView.setModel(self.proxyListenTreeModel)
#self.treeSelectModel = self.treeView.selectionModel()
self.signalTreeModel = QStandardItemModel()
self.proxySignalTreeModel = QSortFilterProxyModel()
self.proxySignalTreeModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxySignalTreeModel.setRecursiveFilteringEnabled(True)
self.proxySignalTreeModel.setFilterKeyColumn(-1)
self.proxySignalTreeModel.setSourceModel(self.signalTreeModel)
self.centralWidget.signalConnectTreeView.setModel(self.proxySignalTreeModel)
self.connectTreeModel = QStandardItemModel()
self.proxyConnectTreeModel = QSortFilterProxyModel()
self.proxyConnectTreeModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyConnectTreeModel.setRecursiveFilteringEnabled(True)
self.proxyConnectTreeModel.setFilterKeyColumn(-1)
self.proxyConnectTreeModel.setSourceModel(self.connectTreeModel)
self.centralWidget.eventConnectTreeView.setModel(self.proxyConnectTreeModel)
self.eventDataTableModel = QStandardItemModel()
self.proxyEventDataTableModel = QSortFilterProxyModel()
self.proxyEventDataTableModel.setFilterCaseSensitivity( Qt.CaseInsensitive )
self.proxyEventDataTableModel.setFilterKeyColumn(-1)
self.proxyEventDataTableModel.setSourceModel(self.eventDataTableModel)
self.centralWidget.eventDataTableView.setModel(self.proxyEventDataTableModel)
self.centralWidget.eventConnectTreeView.clicked.connect(self.connectItemClicked)
self.centralWidget.signalConnectTreeView.clicked.connect(self.connectItemClicked)
self.centralWidget.eventListenTreeView.clicked.connect(self.listenItemClicked)
self.centralWidget.startBtn.clicked.connect(self.toggle)
self.centralWidget.clearBtn.clicked.connect(self.listenTreeModel.clear)
self.centralWidget.eventCodeBtn.clicked.connect(self.updateCode)
def hideEvent(self, event):
self.stop()
super().hideEvent(event)
def updateCode(self):
content = self.centralWidget.eventCodeTextEdit.toPlainText()
if self.codeItem[0] != '':
if self.codeItem[0] == 'event':
self.eventDict[self.codeItem[1]]['code'] = content
else:
self.signalsDict['current'][self.codeItem[1]]['code'] = content
def fillEvents(self):
self.connectTreeModel.clear()
self.signalTreeModel.clear()
# SIGNALS
rootItem = self.signalTreeModel.invisibleRootItem()
rootItem.appendRow([
QStandardItem( Krita.instance().icon('visible'), '' ),
QStandardItem("Widget Signals"),
])
self.widgetSignalsItemHeader = rootItem.child(rootItem.rowCount() - 1)
self.widgetSignalsItemHeader.setData('signal',101)
self.widgetSignalsItemHeader.setData('-current',102)
self.fillSignalsFromObj(self.currentWidget)
'''
#USE CHAIN OBJ INSTEAD AS MODEL CAN BE DELETED
if hasattr(self.currentWidget,'model'):
self.fillSignalsFromObj(self.currentWidget.model(),'model().')
if hasattr(self.currentWidget.model(),'sourceModel'):
self.fillSignalsFromObj(self.currentWidget.model().sourceModel(),'model().sourceModel().')
if hasattr(self.currentWidget,'selectionModel'):
self.fillSignalsFromObj(self.currentWidget.selectionModel(),'selectionModel().')
'''
self.centralWidget.signalConnectTreeView.expandAll()
# EVENTS
rootItem = self.connectTreeModel.invisibleRootItem()
rootItem.appendRow([
QStandardItem( Krita.instance().icon('visible'), '' ),
QStandardItem("Events"),
])
self.eventItemHeader = rootItem.child(rootItem.rowCount() - 1)
self.eventItemHeader.setData('event',101)
self.eventItemHeader.setData(-11,102)
for evName in dir(QEvent):
attr = getattr(QEvent,evName)
if isinstance(attr,QEvent.Type):
self.eventDict[attr]={ 'name': evName, 'doc': evName+'(obj, event)', 'active': 1, 'item': None, 'used': False, 'code':'' }
for evId in sorted(self.eventDict.keys()):
self.eventDict[evId]['item'] = [
QStandardItem( Krita.instance().icon('visible'), '' ),
QStandardItem('['+str(evId)+'] '+self.eventDict[evId]['name'])
]
self.eventDict[evId]['item'][0].setData('event',101)
self.eventDict[evId]['item'][0].setData(evId,102)
self.eventItemHeader.appendRow(self.eventDict[evId]['item'])
self.centralWidget.eventConnectTreeView.expandAll()
def fillSignalsFromObj(self, obj, prepend = ''):
meta = obj.metaObject()
inheritsFrom = []
while True:
for i in range(meta.methodOffset(), meta.methodCount(), 1 ):
self.addSignalItem(obj, meta, i, inheritsFrom, prepend)
meta = meta.superClass()
if meta:
inheritsFrom.append(meta.className())
else:
break
def addSignalItem(self, obj, meta, i, inheritsFrom, prepend=''):
meth = meta.method(i)
pnames = meth.parameterNames()
ptypes = meth.parameterTypes()
className = None
methName = str(meth.name(), 'utf-8')
isSafe = True
for i in range(0,meth.parameterCount()):
print ( ptypes[i], b'Ko' in ptypes[i], b'*' in ptypes[i] )
if b'Ko' in ptypes[i] and b'*' in ptypes[i]:
isSafe = False
methDoc = ('' if isSafe else '[UNSAFE]') + methName + "(" + str(b','.join( [ ptypes[i]+b" "+pnames[i] for i in range(0,meth.parameterCount()) ] ), 'utf-8') + ")"
methType = self.caller.METHOD_ACCESS[int(meth.access())] + " " + self.caller.METHOD_TYPES[int(meth.methodType())]
if methType == 'Public Signal' and methName not in self.signalsDict['current']:
if inheritsFrom:
methType = " [from "+meta.className()+"]"
className = meta.className()
else:
methType = ''
className = type(obj).__name__
self.signalsDict['current'][methName] = {
'name':methName,
'params':ptypes,
'doc': methDoc,
'active':1,
'used':False,
'safe':isSafe,
'code':'',
'item':[
QStandardItem( Krita.instance().icon('visible'), '' ),
QStandardItem(prepend+methDoc+methType)
]
}
self.signalsDict['current'][methName]['item'][0].setData('signal',101)
self.signalsDict['current'][methName]['item'][0].setData(methName,102)
self.signalsDict['current'][methName]['item'][0].setData('current',103)
self.signalsDict['current'][methName]['obj'] = obj
self.signalsDict['current'][methName]['method'] = functools.partial(self.signalFilter,prepend,methName)
self.widgetSignalsItemHeader.appendRow(self.signalsDict['current'][methName]['item'])
def toggle(self):
if not self.started:
self.start()
else:
self.stop()
def start(self):
self.started = True
self.startTime = datetime.now()
self.listenTreeModel.clear()
self.listenTreeModel.setHorizontalHeaderLabels(['Time', 'Name', 'Data'])
self.lastEventItem = None
self.lastEventType = ''
self.centralWidget.startBtn.setIcon(Krita.instance().icon('media-playback-stop'))
self.centralWidget.eventFilterTypeCmb.setEnabled(False)
self.centralWidget.outputCmb.setEnabled(False)
for evId in self.eventDict:
self.eventDict[evId]['item'][0].setData(QBrush(Qt.transparent), Qt.BackgroundRole)
self.eventDict[evId]['item'][1].setData(QBrush(Qt.transparent), Qt.BackgroundRole)
for methName in self.signalsDict['current']:
self.signalsDict['current'][methName]['item'][0].setData(QBrush(Qt.transparent), Qt.BackgroundRole)
self.signalsDict['current'][methName]['item'][1].setData(QBrush(Qt.transparent), Qt.BackgroundRole)
if self.centralWidget.outputCmb.currentIndex() == 1:
self.oldSTDOUT = sys.stdout
self.oldSTDERR = sys.stderr
sys.stdout = self.newSTDOUT
sys.stderr = self.newSTDERR
for methName in self.signalsDict['current']:
if self.signalsDict['current'][methName]['safe']:
print ("MM", methName, self.signalsDict['current'][methName] )
#self.signalsDict['current'][methName]['obj'].pyqtConfigure(**{ methName: self.signalsDict['current'][methName]['method']})
#QObject.connect(self.signalsDict['current'][methName]['obj'],QtCore.SIGNAL(self.signalsDict['current'][methName]['doc']),self.signalsDict['current'][methName]['method'])
#look into this way
#>>>>getattr(self.signalsDict['current'][methName]['obj'],methName)[**self.signalsDict['current'][methName]['params']].connect(self.signalsDict['current'][methName]['method'])
meth = getattr(self.signalsDict['current'][methName]['obj'],methName)
if isinstance(meth,type(pyqtBoundSignal())) or isinstance(meth,type(pyqtSignal())):
meth.connect(self.signalsDict['current'][methName]['method'])
else:
self.signalsDict['current'][methName]['item'][0].setData(QBrush(Qt.darkRed), Qt.BackgroundRole)
if self.centralWidget.eventFilterTypeCmb.currentIndex() == 0:
self.currentWidget.installEventFilter(self)
elif self.centralWidget.eventFilterTypeCmb.currentIndex() == 1:
qApp.installEventFilter(self)
def stop(self):
if self.started:
if self.centralWidget.eventFilterTypeCmb.currentIndex() == 0:
if not sip.isdeleted(self.currentWidget): self.currentWidget.removeEventFilter(self)
elif self.centralWidget.eventFilterTypeCmb.currentIndex() == 1:
qApp.removeEventFilter(self)
for methName in self.signalsDict['current']:
if self.signalsDict['current'][methName]['safe'] and not sip.isdeleted(self.signalsDict['current'][methName]['obj']):
meth = getattr(self.signalsDict['current'][methName]['obj'],methName)
if isinstance(meth,type(pyqtBoundSignal())) or isinstance(meth,type(pyqtSignal())):
meth.disconnect(self.signalsDict['current'][methName]['method'])
if self.centralWidget.outputCmb.currentIndex() == 1:
sys.stdout = self.oldSTDOUT
sys.stderr = self.oldSTDERR
self.started = False
self.centralWidget.startBtn.setIcon(Krita.instance().icon('media-playback-start'))
#self.centralWidget.startBtn.setEnabled(True)
self.centralWidget.eventFilterTypeCmb.setEnabled(True)
self.centralWidget.outputCmb.setEnabled(True)
def processSTD(self, content, mode=0):
splitContent = content.split('\n')
if mode == 0:
self.stdoutBuffer.append(splitContent[0])
else:
self.stderrBuffer.append(splitContent[0])
if len(splitContent) > 1:
dt = str(datetime.now() - self.startTime).replace('0:00:','')
rootItem = self.listenTreeModel.invisibleRootItem()
item = QStandardItem(dt)
rootItem.appendRow([
item,
QStandardItem('STDOUT' if mode == 0 else 'STDERR'),
QStandardItem(''.join(self.stdoutBuffer if mode == 0 else self.stderrBuffer))
])
item.setData({'Content':self.stdoutBuffer if mode == 0 else self.stderrBuffer},101)
self.centralWidget.eventListenTreeView.scrollToBottom()
if mode == 0:
self.stdoutBuffer = [splitContent[1]]
else:
self.stderrBuffer = [splitContent[1]]
def signalFilter(self, prepend, methName, *params):
if methName in self.signalsDict['current'] and self.signalsDict['current'][methName]['active'] > 0:
self.lastEventType=methName
self.lastEventItem=None
if not self.signalsDict['current'][methName]['used']:
self.signalsDict['current'][methName]['item'][0].setData(QBrush(Qt.darkGreen), Qt.BackgroundRole)
self.signalsDict['current'][methName]['item'][1].setData(QBrush(Qt.darkGreen), Qt.BackgroundRole)
signalData = ', '.join(map(str,params))
dt = str(datetime.now() - self.startTime).replace('0:00:','')
if self.centralWidget.outputCmb.currentIndex() <= 1:
rootItem = self.listenTreeModel.invisibleRootItem()
item = QStandardItem(dt)
rootItem.appendRow([
item,
QStandardItem('[SIG] '+prepend+methName),
QStandardItem(signalData)
])
item.setData({'Content':map(str,params)},101)
self.centralWidget.eventListenTreeView.scrollToBottom()
elif self.centralWidget.outputCmb.currentIndex() == 2:
print (dt+'\t'+methName+'\t'+signalData)
if ''.join(self.signalsDict['current'][methName]['code'].split()) != '':
code = compile( self.signalsDict['current'][methName]['code'], '', 'exec' )
exec(code, locals(), globals())
def eventFilter(self, obj, event):
evId = event.type()
if evId in self.eventDict and self.eventDict[evId]['active'] > 0 and obj == self.currentWidget:
eventDataDict = {}
foldMode = self.centralWidget.formatOutputCmb.currentIndex()
repeatedItem = evId == self.lastEventType
if not repeatedItem: self.lastEventItem = None
if not repeatedItem or foldMode == 1 or foldMode == 2:
self.lastEventType=evId
if not self.eventDict[evId]['used']:
self.eventDict[evId]['item'][0].setData(QBrush(Qt.darkGreen), Qt.BackgroundRole)
self.eventDict[evId]['item'][1].setData(QBrush(Qt.darkGreen), Qt.BackgroundRole)
eventData = ''
attrList = ['accept','ignore','isAccepted','setAccepted','spontaneous','type']
for attrName in dir(event):
if not attrName[0].isupper() and attrName[0] != '_' and attrName not in attrList:
attr = getattr(event,attrName)
doc = attr.__doc__
eventDataDict[doc]=''
if '(self)' in doc and '->' in doc:
attrValue = pprint.pformat((attr)()).replace('PyQt5.','').replace('QtCore.','').replace('QtGui.','')
eventDataDict[doc]=attrValue
if not attrValue.startswith('<'):
eventData+=attrName+': '+attrValue+', '
dt = str(datetime.now() - self.startTime).replace('0:00:','')
if self.centralWidget.outputCmb.currentIndex() <= 1:
rootItem = self.listenTreeModel.invisibleRootItem()
item = QStandardItem(dt)
if foldMode == 1 or foldMode == 2:
if self.lastEventItem:
rootItem = self.lastEventItem
else:
self.lastEventItem = item
rootItem.appendRow([
item,
QStandardItem('['+str(evId)+'] '+self.eventDict[evId]['name']),
QStandardItem(eventData)
])
item.setData(eventDataDict,101)
if not repeatedItem and foldMode == 2: self.centralWidget.eventListenTreeView.expand(self.proxyListenTreeModel.mapFromSource(item.index()))
self.centralWidget.eventListenTreeView.scrollToBottom()
elif self.centralWidget.outputCmb.currentIndex() == 2:
print ( ('\t' if repeatedItem else '') + dt+'\t'+'['+str(evId)+'] '+self.eventDict[evId]['name']+'\t'+eventData)
if ''.join(self.eventDict[evId]['code'].split()) != '':
params = (obj, event)
code = compile( self.eventDict[evId]['code'], '', 'exec' )
exec(code, locals(), globals())
return False
def showFor(self, w):
if w:
self.stop()
self.signalsDict={'current':{}}
self.eventDict={}
self.currentWidget = w
self.centralWidget.eventTargetLabel.setText( 'Events For: '+w.objectName()+' - '+w.metaObject().className()+' - '+type(w).__name__ )
self.fillEvents()
self.show()
def listenItemClicked(self, idx):
self.eventDataTableModel.clear()
self.eventDataTableModel.setHorizontalHeaderLabels(['Name', 'Value'])
rootItem = self.eventDataTableModel.invisibleRootItem()
rootItem.appendRow([QStandardItem("Type"), QStandardItem(idx.siblingAtColumn(1).data(0))])
rootItem.appendRow([QStandardItem("Time"), QStandardItem(idx.siblingAtColumn(0).data(0))])
eventDataDict = idx.siblingAtColumn(0).data(101)
if 'Content' in eventDataDict.keys():
rootItem.appendRow([self.caller.subheaderItem("Content")])
for (i, content) in enumerate(eventDataDict['Content']):
rootItem.appendRow([QStandardItem(str(i)),QStandardItem(content)])
else:
rootItem.appendRow([self.caller.subheaderItem("Event Methods")])
for method in eventDataDict.keys():
rootItem.appendRow([QStandardItem(method.replace('(self, ','(').replace('(self)','()')), QStandardItem(eventDataDict[method])])
rootItem.appendRow([self.caller.subheaderItem("Standard Methods")])
for attr in ['accept','ignore','isAccepted','setAccepted','spontaneous','type']:
rootItem.appendRow([QStandardItem(attr+'()'), QStandardItem("")])
def connectItemClicked(self, idx):
if idx.column() == 1:
idx = idx.siblingAtColumn(0)
codeLabelName = 'Code For: '
self.codeItem = [idx.data(101),idx.data(102)]
if idx.data(101) == 'event':
self.centralWidget.eventCodeLabel.setText(codeLabelName + self.eventDict[idx.data(102)]['doc'] )
self.centralWidget.eventCodeTextEdit.setPlainText(self.eventDict[idx.data(102)]['code'])
elif idx.data(101) == 'signal':
self.centralWidget.eventCodeLabel.setText(codeLabelName + self.signalsDict['current'][idx.data(102)]['doc'])
self.centralWidget.eventCodeTextEdit.setPlainText(self.signalsDict['current'][idx.data(102)]['code'])
elif idx.column() == 0:
idxList = []
visible = 1
idxList.append(idx)
if idx.data(101) == 'event':
if idx.data(102) >= 0:
visible = self.eventDict[idx.data(102)]['active']
else:
evItem = self.connectTreeModel.itemFromIndex(self.proxyConnectTreeModel.mapToSource(idx))
if idx.data(102) == -11:
visible = 1
evItem.setData(-10,102)
else:
visible = 0
evItem.setData(-11,102)
for i in range(evItem.rowCount()):
idxList.append( self.proxyConnectTreeModel.mapFromSource(self.connectTreeModel.indexFromItem(evItem.child(i))) )
for evIdx in idxList:
evId = evIdx.data(102)
evItem = self.connectTreeModel.itemFromIndex(self.proxyConnectTreeModel.mapToSource(evIdx))
evItem.setIcon(Krita.instance().icon('visible' if visible == 0 else 'novisible' ) )
if evId >= 0:
self.eventDict[evId]['active'] = 0 if visible == 1 else 1
elif idx.data(101) == 'signal':
if not idx.data(102).startswith('-') and not idx.data(102).startswith('+'):
visible = self.signalsDict['current'][idx.data(102)]['active']
else:
sigItem = self.signalTreeModel.itemFromIndex(self.proxySignalTreeModel.mapToSource(idx))
if idx.data(102).startswith('-'):
visible = 1
sigItem.setData('+current',102)
else:
visible = 0
sigItem.setData('-current',102)
for i in range(sigItem.rowCount()):
idxList.append( self.proxySignalTreeModel.mapFromSource(self.signalTreeModel.indexFromItem(sigItem.child(i))) )
for sigIdx in idxList:
methName = sigIdx.data(102)
sigItem = self.signalTreeModel.itemFromIndex(self.proxySignalTreeModel.mapToSource(sigIdx))
sigItem.setIcon(Krita.instance().icon('visible' if visible == 0 else 'novisible' ) )
if methName in self.signalsDict['current']:
self.signalsDict['current'][methName]['active'] = 0 if visible == 1 else 1
Krita.instance().addDockWidgetFactory(DockWidgetFactory("pluginDevToolsDocker", DockWidgetFactoryBase.DockBottom, PluginDevToolsDocker))