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