import os import shlex import sys from PyQt4.QtCore import * from PyQt4.QtGui import * from PyQt4.QtXml import * from sface.config import config from sface.screens.sfascreen import SfaScreen class DomModel(QAbstractItemModel): def __init__(self, document, parent = 0): QAbstractItemModel.__init__(self, parent) self.domDocument = document self.rootItem = DomItem(document, 0); def data(self, index, role = Qt.DisplayRole): # sometimes it return a QString, sometimes a QVariant. not good. if not index.isValid(): return QVariant() if role != Qt.DisplayRole: return QVariant() node = index.internalPointer().node() attributeMap = node.attributes() col = index.column() if col == 0: if node.nodeType() == QDomNode.ElementNode: qslist = QStringList() for i in range(attributeMap.count()): attr = attributeMap.item(i) elem = ' %s="%s"' % (attr.nodeName(), attr.nodeValue()) qslist.append(elem) ElemNameAndAtts = '%s%s'% (node.nodeName(), qslist.join(' ')) obj = QObject() obj.setProperty('nodeType', QString('element')) obj.setProperty('content', ElemNameAndAtts) return obj elif node.nodeType() == QDomNode.AttributeNode: return QVariant() elif node.nodeType() == QDomNode.TextNode: obj = QObject() obj.setProperty('nodeType', QString('text')) obj.setProperty('content', node.nodeValue()) return obj elif node.nodeType() == QDomNode.CDATASectionNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.EntityReferenceNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.EntityNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.ProcessingInstructionNode: obj = QObject() obj.setProperty('nodeType', QString('element')) obj.setProperty('content', node.nodeName() + " " + node.nodeValue()) return obj elif node.nodeType() == QDomNode.CommentNode: obj = QObject() obj.setProperty('nodeType', QString('comment')) obj.setProperty('content', node.nodeValue()) return obj elif node.nodeType() == QDomNode.DocumentNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.DocumentTypeNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.DocumentFragmentNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.NotationNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.BaseNode: return QString('unsupported node type') elif node.nodeType() == QDomNode.CharacterDataNode: return QString('unsupported node type') else: return QVariant() else: return QVariant() def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemIsEnabled | Qt.ItemIsSelectable def headerData(self, section, orientation, role): return QVariant() def index(self, row, column, parent=None): if not parent or not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() childItem = parentItem.child(row) # childItem would be None to say "false"? if childItem: return self.createIndex(row, column, childItem) else: return QModelIndex() def parent(self, child): if not child.isValid(): return QModelIndex() childItem = child.internalPointer() parentItem = childItem.parent() if not parentItem or parentItem == self.rootItem: return QModelIndex() return self.createIndex(parentItem.row(), 0, parentItem) def rowCount(self, parent=None): if not parent or not parent.isValid(): parentItem = self.rootItem else: parentItem = parent.internalPointer() return parentItem.node().childNodes().count() def columnCount(self, parent): # just one column we'll print tag name (and attributes) or the # tag content return 1 class DomItem: # wrapper around PyQt4.QtXml.QDomNode it keeps an hash of # childrens for performance reasons def __init__(self, node, row, parent = 0): # node is of type PyQt4.QtXml.QDomNode self.domNode = node self.parentItem = parent self.rowNumber = row self.childItems = {} def child(self, i): if i in self.childItems: return self.childItems[i] if i >= 0 and i < self.domNode.childNodes().count(): childNode = self.domNode.childNodes().item(i) childItem = DomItem(childNode, i, self) self.childItems[i] = childItem return childItem return None def parent(self): return self.parentItem def node(self): return self.domNode def row(self): return self.rowNumber class XmlView(QTreeView): def __init__(self, parent): QTreeView.__init__(self, parent) self.setAnimated(True) self.setItemsExpandable(True) self.setRootIsDecorated(True) self.setHeaderHidden(True) self.setAttribute(Qt.WA_MacShowFocusRect, 0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) class XmlWindow(QDialog): def __init__(self, parent=None, title='XML Window'): QDialog.__init__(self, parent) self.setWindowTitle(title) self.document = None self.model = None self.title = title self.view = self.initView() self.delegate = XmlDelegate(self) self.view.setItemDelegate(self.delegate) self.delegate.insertNodeDelegate('element', ElemNodeDelegate()) self.delegate.insertNodeDelegate('text', TextNodeDelegate()) self.delegate.insertNodeDelegate('comment', CommentNodeDelegate()) layout = QVBoxLayout() layout.addWidget(self.view) self.setLayout(layout) self.connect(self.view, SIGNAL('expanded(QModelIndex)'), self.onItemExpanded) self.updateView() def initView(self): return XmlView(self) def show(self): self.updateView() QDialog.show(self) def readContent(self): raise ValueError("readContent needs to be implemented in the subclass") def updateView(self): del self.document del self.model self.document = None self.model = None self.document = QDomDocument(self.title) self.model = DomModel(self.document, self) self.view.setModel(self.model) self.document.setContent(self.readContent()) if self.document.childNodes().count() == 0: # empty document - do nothing pass elif self.document.childNodes().item(0).nodeType() == QDomNode.ProcessingInstructionNode: # the first item is the tag, so expand the second self.view.expand(self.model.index(1,0)) else: # the document didn't start with an tag; let's try to expand # the first item on the assumption that it is our root level xml # tag. self.view.expand(self.model.index(0,0)) self.view.header().resizeSection(0,0) self.view.resizeColumnToContents(0) def onItemExpanded(self, index): self.view.header().resizeSection(0,0) self.view.resizeColumnToContents(0) class XmlDelegate(QItemDelegate): def __init__(self, parent=None): QAbstractItemDelegate.__init__(self, parent) self.delegates = {} def insertNodeDelegate(self, nodeType, delegate): delegate.setParent(self) self.delegates[nodeType] = delegate def removeNodeDelegate(self, nodeType, delegate): if nodeType in self.delegates: del self.delegates[nodeType] def paint(self, painter, option, index): if isinstance(index.model().data(index),QVariant): return nodeType = index.model().data(index).property('nodeType') delegate = self.delegates.get(str(nodeType.toString())) #print "TYPE:", str(type(str(nodeType.toString()))) #print "DELEGS DICT:", self.delegates #print "NODETYPE:", nodeType.toString() if delegate is not None: #print "WOW DELEG ISNT NONE" delegate.paint(painter, option, index) else: #print "ELSE BRANCH" # not sure this will ever work. this delegate # doesn't know about my QObject strategy. QItemDelegate.paint(self, painter, option, index) def sizeHint(self, option, index): fm = option.fontMetrics if isinstance(index.model().data(index),QVariant): return QSize(0, 0) nodeType = index.model().data(index).property('nodeType') delegate = self.delegates.get(str(nodeType.toString()), None) # Use the text from the appropriate delegate if we have it, to # compute the size. Should probably finish the sizeHint() methods # of the delegates at some point, and call them instead. if delegate!=None and hasattr(delegate, "getItemText"): text = delegate.getItemText(option, index) else: text = index.model().data(index).property('content').toString() document = QTextDocument() document.setDefaultFont(option.font) document.setHtml(text) # the +5 is for margin. The +4 is voodoo; # fm.height just give it too small. return QSize(document.idealWidth() + 5, fm.height() + 4) class ElemNodeDelegate(QAbstractItemDelegate): def getItemText(self, option, index): text = index.model().data(index) nonHighGlobPattern = '<%s%s>' nonHighAttPattern = ' %s="%s"' highGlobPattern = '<%s%s>' highAttPattern = ' %s="%s"' def getHtmlText(plainText, globPattern, attPattern): # print "PLAIN TEXT:", plainText tmp = plainText.split(' ', 1) # print "TMP:", tmp elemName = tmp[0] AttListHtml = '' if len(tmp) > 1: # many elems don't have atts... # use shlex.split so we can handle quoted strings with spaces # in them, like . Note that there are # documented problems with shlex.split and unicode, so we # convert any potential unicode to a string first. attList = shlex.split(str(tmp[1])) for att in attList: tmp = att.split('=',1) if len(tmp)>=2: attName = tmp[0] attValue = tmp[1] else: # this shouldn't happen, but if it does, pretend the # attribute value is blank. attName = tmp[0] attValue = "" AttListHtml += (nonHighAttPattern % (attName, attValue)) html = (globPattern % (elemName, AttListHtml)) return html def colorize(color, text): return '' + text + '' text = str(index.model().data(index).property('content').toString()) # print "TEXT:", text if option.state & QStyle.State_Selected: palette = QApplication.palette() htmlText = colorize(palette.highlightedText().color().name(), getHtmlText(text, highGlobPattern, highAttPattern)) else: htmlText = getHtmlText(text, nonHighGlobPattern, nonHighAttPattern) return htmlText def paint(self, painter, option, index): palette = QApplication.palette() document = QTextDocument() document.setDefaultFont(option.font) htmlText = self.getItemText(option, index) document.setHtml(QString(htmlText)) color = palette.highlight().color() \ if option.state & QStyle.State_Selected \ else palette.base().color() painter.save() # print "COLOR:", color.name() # voodoo: if not highlighted, filling the rect # with the base color makes no difference painter.fillRect(option.rect, color) painter.translate(option.rect.x(), option.rect.y()) document.drawContents(painter) painter.restore() class TextNodeDelegate(QAbstractItemDelegate): def paint(self, painter, option, index): #print "TEXT DELEG CALLED" paint(self, painter, option, index) class CommentNodeDelegate(QAbstractItemDelegate): def paint(self, painter, option, index): #print "TEXT DELEG CALLED" paint(self, painter, option, index) def paint(self, painter, option, index): text = index.model().data(index).property('content').toString() palette = QApplication.palette() document = QTextDocument() document.setDefaultFont(option.font) if option.state & QStyle.State_Selected: rx = QRegExp(QString('')) rx.setMinimal(True) # If selected, I remove the by hand, # and give the highlight color document.setHtml(QString("%2") \ .arg(palette.highlightedText().color().name())\ .arg(text.replace(rx, QString('')). replace(QString(''),QString('')))) else: document.setHtml(text) color = palette.highlight().color() \ if option.state & QStyle.State_Selected \ else palette.base().color() painter.save() # voodoo: if not highlighted, filling the rect # with the base color makes no difference painter.fillRect(option.rect, color) painter.translate(option.rect.x(), option.rect.y()) document.drawContents(painter) painter.restore()