4 from PyQt4.QtCore import *
5 from PyQt4.QtGui import *
6 from PyQt4.QtXml import *
8 from sface.config import config
9 from sface.screens.sfascreen import SfaScreen
11 class nodeData(QVariant):
12 def __init__(self, *args, **kws):
13 QVariant.__init__(self, *args, **kws)
16 def setType(self, typ):
22 class DomModel(QAbstractItemModel):
23 def __init__(self, document, parent = 0):
24 QAbstractItemModel.__init__(self, parent)
25 self.domDocument = document
26 # one of the children of the rootItem is the 'xml' thing.
28 childList = document.childNodes()
29 for i in range(childList.count()):
30 currElem = childList.item(i)
31 if (currElem.nodeType() == QDomNode.ProcessingInstructionNode):
32 document.removeChild(currElem)
34 self.rootItem = DomItem(document, 0);
36 def data(self, index, role = Qt.DisplayRole):
37 # for interesting nodes, returns a dict wrapped into a QVariant.
38 if not index.isValid():
40 if role != Qt.DisplayRole:
42 node = index.internalPointer().node()
43 attributeMap = node.attributes()
47 if node.nodeType() == QDomNode.ElementNode:
48 qslist = QStringList()
49 for i in range(attributeMap.count()):
50 attr = attributeMap.item(i)
51 elem = ' %s="%s"' % (attr.nodeName(), attr.nodeValue())
53 ElemNameAndAtts = '%s%s'% (node.nodeName(), qslist.join(' '))
54 answer = nodeData(ElemNameAndAtts)
55 answer.setType('element')
57 elif node.nodeType() == QDomNode.AttributeNode:
59 elif node.nodeType() == QDomNode.TextNode:
60 answer = nodeData(node.nodeValue())
61 answer.setType('text')
63 elif node.nodeType() == QDomNode.CDATASectionNode:
64 return QString('unsupported node type')
65 elif node.nodeType() == QDomNode.EntityReferenceNode:
66 return QString('unsupported node type')
67 elif node.nodeType() == QDomNode.EntityNode:
68 return QString('unsupported node type')
69 elif node.nodeType() == QDomNode.ProcessingInstructionNode:
71 elif node.nodeType() == QDomNode.CommentNode:
72 answer = nodeData(node.nodeValue())
73 answer.setType('comment')
75 elif node.nodeType() == QDomNode.DocumentNode:
76 return QString('unsupported node type')
77 elif node.nodeType() == QDomNode.DocumentTypeNode:
78 return QString('unsupported node type')
79 elif node.nodeType() == QDomNode.DocumentFragmentNode:
80 return QString('unsupported node type')
81 elif node.nodeType() == QDomNode.NotationNode:
82 return QString('unsupported node type')
83 elif node.nodeType() == QDomNode.BaseNode:
84 return QString('unsupported node type')
85 elif node.nodeType() == QDomNode.CharacterDataNode:
86 return QString('unsupported node type')
92 def flags(self, index):
93 if not index.isValid():
94 return Qt.ItemIsEnabled
95 return Qt.ItemIsEnabled | Qt.ItemIsSelectable
97 def headerData(self, section, orientation, role):
100 def index(self, row, column, parent=None):
101 if not parent or not parent.isValid():
102 parentItem = self.rootItem
104 parentItem = parent.internalPointer()
106 childItem = parentItem.child(row)
107 # childItem would be None to say "false"?
109 return self.createIndex(row, column, childItem)
113 def parent(self, child):
114 if not child.isValid():
116 childItem = child.internalPointer()
117 parentItem = childItem.parent()
119 if not parentItem or parentItem == self.rootItem:
121 return self.createIndex(parentItem.row(), 0, parentItem)
123 def rowCount(self, parent=None):
124 if not parent or not parent.isValid():
125 parentItem = self.rootItem
127 parentItem = parent.internalPointer()
129 return parentItem.node().childNodes().count()
131 def columnCount(self, parent):
132 # just one column we'll print tag name (and attributes) or the
138 # wrapper around PyQt4.QtXml.QDomNode it keeps an hash of
139 # childrens for performance reasons
141 def __init__(self, node, row, parent = 0):
142 # node is of type PyQt4.QtXml.QDomNode
144 self.parentItem = parent
149 if i in self.childItems:
150 return self.childItems[i]
151 if i >= 0 and i < self.domNode.childNodes().count():
152 childNode = self.domNode.childNodes().item(i)
153 childItem = DomItem(childNode, i, self)
154 self.childItems[i] = childItem
159 return self.parentItem
165 return self.rowNumber
167 class XmlView(QTreeView):
168 def __init__(self, parent=None):
169 QTreeView.__init__(self, parent)
171 delegate = XmlDelegate(self)
172 delegate.insertNodeDelegate('element', ElemNodeDelegate())
173 delegate.insertNodeDelegate('text', TextNodeDelegate())
174 delegate.insertNodeDelegate('comment', CommentNodeDelegate())
175 self.setItemDelegate(delegate)
177 self.setAnimated(True)
178 self.setItemsExpandable(True)
179 self.setRootIsDecorated(True)
180 self.setHeaderHidden(True)
181 self.setAttribute(Qt.WA_MacShowFocusRect, 0)
182 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
184 class XmlDelegate(QItemDelegate):
186 def __init__(self, parent=None):
187 QAbstractItemDelegate.__init__(self, parent)
190 def insertNodeDelegate(self, nodeType, delegate):
191 delegate.setParent(self)
192 self.delegates[nodeType] = delegate
194 def removeNodeDelegate(self, nodeType, delegate):
195 if nodeType in self.delegates:
196 del self.delegates[nodeType]
198 def paint(self, painter, option, index):
199 nodeData = index.model().data(index)
200 nodeType = nodeData.getType()
201 delegate = self.delegates.get(nodeType)
202 if delegate is not None:
203 delegate.paint(painter, option, index)
205 QItemDelegate.paint(self, painter, option, index)
207 def sizeHint(self, option, index):
208 fm = option.fontMetrics
209 nodeData = index.model().data(index)
210 nodeType = nodeData.getType()
211 text = nodeData.toString()
212 if nodeType == 'element' or nodeType == 'comment':
214 elif nodeType == 'text':
215 nl = text.count('\n')
216 numlines = nl if nl > 0 else 1
219 document = QTextDocument()
220 document.setDefaultFont(option.font)
221 document.setHtml(text)
222 # the +5 is for margin. The +4 is voodoo;
223 # fm.height just give it too small.
224 return QSize(document.idealWidth() + 5, (fm.height() + 4) * numlines)
226 class ElemNodeDelegate(QAbstractItemDelegate):
227 def paint(self, painter, option, index):
228 palette = QApplication.palette()
229 document = QTextDocument()
230 document.setDefaultFont(option.font)
231 nonHighGlobPattern = '<<b><font color="#b42be2">%s</font></b>%s>'
232 nonHighAttPattern = ' <b>%s</b>="<font color="#1e90ff">%s</font>"'
233 highGlobPattern = '<<b>%s</b>%s>'
234 highAttPattern = ' <b>%s</b>="%s"'
235 def getHtmlText(plainText, globPattern, attPattern):
236 tmp = plainText.split(' ', 1)
240 # many elems don't have atts...
241 attList = tmp[1].split()
245 attValue = tmp[1][1:-1]
246 AttListHtml += (attPattern % (attName, attValue))
247 html = (globPattern % (elemName, AttListHtml))
249 def colorize(color, text):
250 return '<font color=' + color + '>' + text + '</font>'
251 nodeData = index.model().data(index)
252 nodeType = nodeData.getType()
253 # Uff... QString Vs string...
254 text = str(nodeData.toString())
255 if option.state & QStyle.State_Selected:
256 htmlText = colorize(palette.highlightedText().color().name(),
257 getHtmlText(text, highGlobPattern, highAttPattern))
258 document.setHtml(QString(htmlText))
260 htmlText = getHtmlText(text, nonHighGlobPattern, nonHighAttPattern)
261 document.setHtml(QString(htmlText))
262 color = palette.highlight().color() \
263 if option.state & QStyle.State_Selected \
264 else palette.base().color()
266 # voodoo: if not highlighted, filling the rect
267 # with the base color makes no difference
268 painter.fillRect(option.rect, color)
269 painter.translate(option.rect.x(), option.rect.y())
270 document.drawContents(painter)
273 class TextNodeDelegate(QAbstractItemDelegate):
274 def paint(self, painter, option, index):
275 palette = QApplication.palette()
276 document = QTextDocument()
277 document.setDefaultFont(option.font)
278 def verbatimize(text):
279 text.replace('\n', '<br>')
280 return '<pre>' + text + '</pre'
281 def colorize(color, text):
282 return '<font color=' + color + '>' + text + '</font>'
283 nodeData = index.model().data(index)
284 nodeType = nodeData.getType()
285 text = nodeData.toString()
286 if option.state & QStyle.State_Selected:
287 htmlText = colorize(palette.highlightedText().color().name(),
289 document.setHtml(QString(htmlText))
291 htmlText = verbatimize(text)
292 document.setHtml(QString(htmlText))
293 color = palette.highlight().color() \
294 if option.state & QStyle.State_Selected \
295 else palette.base().color()
297 # voodoo: if not highlighted, filling the rect
298 # with the base color makes no difference
299 painter.fillRect(option.rect, color)
300 painter.translate(option.rect.x(), option.rect.y())
301 document.drawContents(painter)
304 class CommentNodeDelegate(QAbstractItemDelegate):
305 def paint(self, painter, option, index):
306 paint(self, painter, option, index)
308 def paint(self, painter, option, index):
309 text = index.model().data(index).property('content').toString()
310 palette = QApplication.palette()
311 document = QTextDocument()
312 document.setDefaultFont(option.font)
313 if option.state & QStyle.State_Selected:
314 rx = QRegExp(QString('<font .*>'))
316 # If selected, I remove the <font color="..."> by hand,
317 # and give the highlight color
318 document.setHtml(QString("<font color=%1>%2</font>") \
319 .arg(palette.highlightedText().color().name())\
320 .arg(text.replace(rx, QString('')).
321 replace(QString('</font>'),QString(''))))
323 document.setHtml(text)
324 color = palette.highlight().color() \
325 if option.state & QStyle.State_Selected \
326 else palette.base().color()
328 # voodoo: if not highlighted, filling the rect
329 # with the base color makes no difference
330 painter.fillRect(option.rect, color)
331 painter.translate(option.rect.x(), option.rect.y())
332 document.drawContents(painter)