got rid of the misterious widget in the rspec window
[sface.git] / sface / xmlwidget.py
1 import os
2 import sys
3
4 from PyQt4.QtCore import *
5 from PyQt4.QtGui import *
6 from PyQt4.QtXml import *
7
8 from sface.config import config
9 from sface.screens.sfascreen import SfaScreen
10
11 def QVarMapAccess(qv, key):
12     # helper function. qv is a dict wrapped into a QVariant
13     print 10*'='
14     print "DICT:", qv.toMap()
15     if len(qv.toMap().keys()) == 0:
16         print "EMPTY!"
17         import traceback
18         traceback.print_stack()
19         return None
20     else:
21         return qv.toMap()[QString(key)].toString()
22
23
24 class DomModel(QAbstractItemModel):
25     def __init__(self, document, parent = 0):
26         QAbstractItemModel.__init__(self, parent)
27         self.domDocument = document
28         # one of the children of the rootItem is the 'xml' thing.
29         # here I delete it.
30         childList = document.childNodes()
31         for i in range(childList.count()):
32             currElem = childList.item(i)
33             if (currElem.nodeType() == QDomNode.ProcessingInstructionNode):
34                 document.removeChild(currElem)
35                 break
36         self.rootItem = DomItem(document, 0);
37
38     def data(self, index, role = Qt.DisplayRole):
39         # for interesting nodes, returns a dict wrapped into a QVariant.
40         if not index.isValid():
41             return QVariant()
42         if role != Qt.DisplayRole:
43             return QVariant()
44         node = index.internalPointer().node()
45         attributeMap = node.attributes()
46
47         col = index.column()
48         if col == 0:
49             if node.nodeType() == QDomNode.ElementNode:
50                 qslist = QStringList()
51                 for i in range(attributeMap.count()):
52                     attr = attributeMap.item(i)
53                     elem = ' %s="%s"' % (attr.nodeName(), attr.nodeValue())
54                     qslist.append(elem)
55                 ElemNameAndAtts = '%s%s'% (node.nodeName(), qslist.join(' '))
56                 print "1"
57                 return QVariant(
58                     {'nodeType':QVariant(QString('element')),
59                      'content':ElemNameAndAtts})
60             elif node.nodeType() == QDomNode.AttributeNode:
61                 print "2"
62                 return QVariant()
63             elif node.nodeType() == QDomNode.TextNode:
64                 print "3"
65                 return QVariant(
66                     {'nodeType':QVariant(QString('text')),
67                      'content':node.nodeValue()})
68             elif node.nodeType() == QDomNode.CDATASectionNode:
69                 print "4"
70                 return QString('unsupported node type')
71             elif node.nodeType() == QDomNode.EntityReferenceNode:
72                 print "5"
73                 return QString('unsupported node type')
74             elif node.nodeType() == QDomNode.EntityNode:
75                 print "6"
76                 return QString('unsupported node type')
77             elif node.nodeType() == QDomNode.ProcessingInstructionNode:
78                 print "7"
79                 return QVariant()
80             elif node.nodeType() == QDomNode.CommentNode:
81                 print "8"
82                 return QVariant(
83                     {'nodeType':QVariant(QString('comment')),
84                      'content':node.nodeValue()})
85             elif node.nodeType() == QDomNode.DocumentNode:
86                 print "9"
87                 return QString('unsupported node type')
88             elif node.nodeType() == QDomNode.DocumentTypeNode:
89                 print "10"
90                 return QString('unsupported node type')
91             elif node.nodeType() == QDomNode.DocumentFragmentNode:
92                 print "12"
93                 return QString('unsupported node type')
94             elif node.nodeType() == QDomNode.NotationNode:
95                 print "13"
96                 return QString('unsupported node type')
97             elif node.nodeType() == QDomNode.BaseNode:
98                 print "14"
99                 return QString('unsupported node type')
100             elif node.nodeType() == QDomNode.CharacterDataNode:
101                 print "15"
102                 return QString('unsupported node type')
103             else:
104                 print "16"
105                 return QVariant()
106         else:
107             print "17"
108             return QVariant()
109
110     def flags(self, index):
111         if not index.isValid():
112             return Qt.ItemIsEnabled
113         return Qt.ItemIsEnabled | Qt.ItemIsSelectable
114         
115     def headerData(self, section, orientation, role):
116         return QVariant()
117
118     def index(self, row, column, parent=None):
119         if not parent or not parent.isValid():
120             parentItem = self.rootItem
121         else:
122             parentItem = parent.internalPointer()
123
124         childItem = parentItem.child(row)
125         # childItem would be None to say "false"?
126         if childItem:
127             return self.createIndex(row, column, childItem)
128         else:
129             return QModelIndex()
130
131     def parent(self, child):
132         if not child.isValid():
133             return QModelIndex()
134         childItem = child.internalPointer()
135         parentItem = childItem.parent()
136         
137         if not parentItem or parentItem == self.rootItem:
138             return QModelIndex()
139         return self.createIndex(parentItem.row(), 0, parentItem)
140
141     def rowCount(self, parent=None):
142         if not parent or not parent.isValid():
143             parentItem = self.rootItem
144         else:
145             parentItem = parent.internalPointer()
146
147         return parentItem.node().childNodes().count()
148
149     def columnCount(self, parent):
150         # just one column we'll print tag name (and attributes) or the
151         # tag content
152         return 1
153
154
155 class DomItem:
156     # wrapper around PyQt4.QtXml.QDomNode it keeps an hash of
157     # childrens for performance reasons
158
159     def __init__(self, node, row, parent = 0):
160         # node is of type PyQt4.QtXml.QDomNode
161         self.domNode = node
162         self.parentItem = parent
163         self.rowNumber = row
164         self.childItems = {}
165
166     def child(self, i):
167         if i in self.childItems:
168             return self.childItems[i]
169         if i >= 0 and i < self.domNode.childNodes().count():
170             childNode = self.domNode.childNodes().item(i)
171             childItem = DomItem(childNode, i, self)
172             self.childItems[i] = childItem
173             return childItem
174         return None
175             
176     def parent(self):
177         return self.parentItem
178
179     def node(self):
180         return self.domNode
181
182     def row(self):
183         return self.rowNumber
184
185 class XmlView(QTreeView):
186     def __init__(self, parent=None):
187         QTreeView.__init__(self, parent)
188
189         delegate = XmlDelegate(self)
190         delegate.insertNodeDelegate('element', ElemNodeDelegate())
191         delegate.insertNodeDelegate('text', TextNodeDelegate())
192         delegate.insertNodeDelegate('comment', CommentNodeDelegate())
193         self.setItemDelegate(delegate)
194
195         self.setAnimated(True)
196         self.setItemsExpandable(True)
197         self.setRootIsDecorated(True)
198         self.setHeaderHidden(True)
199         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
200         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
201
202 class XmlDelegate(QItemDelegate):
203     
204     def __init__(self, parent=None):
205         QAbstractItemDelegate.__init__(self, parent)
206         self.delegates = {}
207
208     def insertNodeDelegate(self, nodeType, delegate):
209         delegate.setParent(self)
210         self.delegates[nodeType] = delegate
211
212     def removeNodeDelegate(self, nodeType, delegate):
213         if nodeType in self.delegates:
214             del self.delegates[nodeType]
215     
216     def paint(self, painter, option, index):
217         print "ASKING FOR DATA"
218         dataAsQVarMap = index.model().data(index)
219         print "GOT DATA"
220         nodeType = str(QVarMapAccess(dataAsQVarMap, 'nodeType'))
221         delegate = self.delegates.get(nodeType)
222         #print "DELEGS DICT:", self.delegates
223         #print "NODETYPE:", nodeType.toString()
224         if delegate is not None:
225             #print "WOW DELEG ISNT NONE"
226             delegate.paint(painter, option, index)
227         else:
228             #print "ELSE BRANCH"
229             # not sure this will ever work. this delegate
230             # doesn't know about my QMap strategy.
231             QItemDelegate.paint(self, painter, option, index)
232
233 #    def sizeHint(self, option, index):
234 #        fm = option.fontMetrics
235 #        print "TYPE:", str(type(index.model().data(index).convert(QObject)))
236 #        text = "the fish doesn't talk"
237 #        #text = str(index.model().data(index).property('content').toString())
238 #        nodeType = str(index.model().data(index).property('nodeType').toString())
239 #        if nodeType == 'element' or nodeType == 'comment':
240 #            numlines = 1
241 #        elif nodeType == 'text':
242 #            numlines = text.count('\n')
243 #            sys.__stdout__.write("TEXT: \n" + text)
244 #        else:
245 #            numlines = 1
246 #        document = QTextDocument()
247 #        document.setDefaultFont(option.font)
248 #        document.setHtml(text)
249 #        # the +5 is for margin. The +4 is voodoo;
250 #        # fm.height just give it too small.
251 #        return QSize(document.idealWidth() + 5, (fm.height() + 4) * numlines)    
252
253 class ElemNodeDelegate(QAbstractItemDelegate):
254     def paint(self, painter, option, index): 
255         palette = QApplication.palette()
256         document = QTextDocument()
257         document.setDefaultFont(option.font)
258         nonHighGlobPattern = '&lt;<b><font color="#b42be2">%s</font></b>%s&gt;'
259         nonHighAttPattern = ' <b>%s</b>="<font color="#1e90ff">%s</font>"'
260         highGlobPattern = '&lt;<b>%s</b>%s&gt;'
261         highAttPattern = ' <b>%s</b>="%s"'
262         def getHtmlText(plainText, globPattern, attPattern):
263             tmp = plainText.split(' ', 1)
264             elemName = tmp[0]
265             AttListHtml = ''
266             if len(tmp) > 1:
267                 # many elems don't have atts...
268                 attList = tmp[1].split()
269                 for att in attList:
270                     tmp = att.split('=')
271                     attName = tmp[0]
272                     attValue = tmp[1][1:-1]
273                     AttListHtml += (nonHighAttPattern % (attName, attValue))
274             html = (globPattern % (elemName, AttListHtml))
275             return html
276         def colorize(color, text):
277             return '<font color=' + color + '>' + text + '</font>'
278         dataAsQVarMap = index.model().data(index)
279         text = str(QVarMapAccess(dataAsQVarMap, 'content'))
280         if option.state & QStyle.State_Selected:
281             htmlText = colorize(palette.highlightedText().color().name(),
282                                 getHtmlText(text, highGlobPattern, highAttPattern))
283             document.setHtml(QString(htmlText))
284         else:
285             htmlText = getHtmlText(text, nonHighGlobPattern, nonHighAttPattern)
286             document.setHtml(QString(htmlText))
287         color = palette.highlight().color() \
288             if option.state & QStyle.State_Selected \
289             else palette.base().color()
290         painter.save()
291         # voodoo: if not highlighted, filling the rect
292         # with the base color makes no difference
293         painter.fillRect(option.rect, color)
294         painter.translate(option.rect.x(), option.rect.y())
295         document.drawContents(painter)
296         painter.restore()
297
298 class TextNodeDelegate(QAbstractItemDelegate):
299     def paint(self, painter, option, index): 
300         palette = QApplication.palette()
301         document = QTextDocument()
302         document.setDefaultFont(option.font)
303         def verbatimize(text):
304             text.replace('\n', '<br>')
305             return '<pre>' + text + '</pre'
306         def colorize(color, text):
307             return '<font color=' + color + '>' + text + '</font>'
308         dataAsQVarMap = index.model().data(index)
309         text = str(QVarMapAccess(dataAsQVarMap, 'content'))
310         if option.state & QStyle.State_Selected:
311             htmlText = colorize(palette.highlightedText().color().name(),
312                                 verbatimize(text))
313             document.setHtml(QString(htmlText))
314         else:
315             htmlText = verbatimize(text)
316             document.setHtml(QString(htmlText))
317         color = palette.highlight().color() \
318             if option.state & QStyle.State_Selected \
319             else palette.base().color()
320         painter.save()
321         # voodoo: if not highlighted, filling the rect
322         # with the base color makes no difference
323         painter.fillRect(option.rect, color)
324         painter.translate(option.rect.x(), option.rect.y())
325         document.drawContents(painter)
326         painter.restore()
327
328 class CommentNodeDelegate(QAbstractItemDelegate):
329     def paint(self, painter, option, index): 
330         paint(self, painter, option, index)
331
332 def paint(self, painter, option, index):
333     text = index.model().data(index).property('content').toString()
334     palette = QApplication.palette()
335     document = QTextDocument()
336     document.setDefaultFont(option.font)
337     if option.state & QStyle.State_Selected:
338         rx = QRegExp(QString('<font .*>'))
339         rx.setMinimal(True)
340         # If selected, I remove the <font color="..."> by hand,
341         # and give the highlight color
342         document.setHtml(QString("<font color=%1>%2</font>") \
343                              .arg(palette.highlightedText().color().name())\
344                              .arg(text.replace(rx, QString('')).
345                                   replace(QString('</font>'),QString(''))))
346     else:
347         document.setHtml(text)
348     color = palette.highlight().color() \
349         if option.state & QStyle.State_Selected \
350         else palette.base().color()
351     painter.save()
352     # voodoo: if not highlighted, filling the rect
353     # with the base color makes no difference
354     painter.fillRect(option.rect, color)
355     painter.translate(option.rect.x(), option.rect.y())
356     document.drawContents(painter)
357     painter.restore()
358
359