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