Remove slice tags
[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 = XmlView(self)
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 show(self):
192         self.updateView()
193         QDialog.show(self)
194         
195     def updateView(self):
196         del self.document
197         del self.model
198         self.document = None
199         self.model = None
200
201         self.document = QDomDocument(self.title)
202         self.model = DomModel(self.document, self)
203
204         self.view.setModel(self.model)
205         self.view.expand(self.model.index(0, 0)) #expand first level only
206
207         #move the code below to rspec window
208         rspec_file = config.getSliceRSpecFile()
209         if not os.path.exists(rspec_file):
210             return
211
212         self.document.setContent(open(rspec_file,'r').read())
213
214
215
216 class XmlDelegate(QItemDelegate):
217     
218     def __init__(self, parent=None):
219         QAbstractItemDelegate.__init__(self, parent)
220         self.delegates = {}
221
222     def insertNodeDelegate(self, nodeType, delegate):
223         delegate.setParent(self)
224         self.delegates[nodeType] = delegate
225
226     def removeNodeDelegate(self, nodeType, delegate):
227         if nodeType in self.delegates:
228             del self.delegates[nodeType]
229     
230     def paint(self, painter, option, index):
231         nodeType = index.model().data(index).property('nodeType')
232         delegate = self.delegates.get(str(nodeType.toString()))
233         #print "TYPE:", str(type(str(nodeType.toString())))
234         #print "DELEGS DICT:", self.delegates
235         #print "NODETYPE:", nodeType.toString()
236         if delegate is not None:
237             #print "WOW DELEG ISNT NONE"
238             delegate.paint(painter, option, index)
239         else:
240             #print "ELSE BRANCH"
241             # not sure this will ever work. this delegate
242             # doesn't know about my QObject strategy.
243             QItemDelegate.paint(self, painter, option, index)
244
245     def sizeHint(self, option, index):
246         fm = option.fontMetrics
247         text = index.model().data(index).property('content').toString()
248         document = QTextDocument()
249         document.setDefaultFont(option.font)
250         document.setHtml(text)
251         # the +5 is for margin. The +4 is voodoo;
252         # fm.height just give it too small.
253         return QSize(document.idealWidth() + 5, fm.height() + 4)    
254
255 class ElemNodeDelegate(QAbstractItemDelegate):
256     def paint(self, painter, option, index): 
257         text = index.model().data(index)
258         palette = QApplication.palette()
259         document = QTextDocument()
260         document.setDefaultFont(option.font)
261         nonHighGlobPattern = '&lt;<b><font color="#b42be2">%s</font></b>%s&gt;'
262         nonHighAttPattern = ' <b>%s</b>="<font color="#1e90ff">%s</font>"'
263         highGlobPattern = '&lt;<b>%s</b>%s&gt;'
264         highAttPattern = ' <b>%s</b>="%s"'
265         def getHtmlText(plainText, globPattern, attPattern):
266             print "PLAIN TEXT:", plainText
267             tmp = plainText.split(' ', 1)
268             print "TMP:", tmp
269             elemName = tmp[0]
270             AttListHtml = ''
271             if len(tmp) > 1:
272                 # many elems don't have atts...
273                 attList = tmp[1].split()
274                 for att in attList:
275                     tmp = att.split('=')
276                     attName = tmp[0]
277                     attValue = tmp[1][1:-1]
278                     AttListHtml += (nonHighAttPattern % (attName, attValue))
279             html = (globPattern % (elemName, AttListHtml))
280             return html
281         def colorize(color, text):
282             return '<font color=' + color + '>' + text + '</font>'
283         text = str(index.model().data(index).property('content').toString())
284         print "TEXT:", text
285         if option.state & QStyle.State_Selected:
286             htmlText = colorize(palette.highlightedText().color().name(),
287                                 getHtmlText(text, highGlobPattern, highAttPattern))
288             document.setHtml(QString(htmlText))
289         else:
290             htmlText = getHtmlText(text, nonHighGlobPattern, nonHighAttPattern)
291             document.setHtml(QString(htmlText))
292         color = palette.highlight().color() \
293             if option.state & QStyle.State_Selected \
294             else palette.base().color()
295         painter.save()
296         print "COLOR:", color.name()
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)
302         painter.restore()
303
304     def sizeHint(self, option, index):
305         sizeHint(self, option, index)
306
307 class TextNodeDelegate(QAbstractItemDelegate):
308     def paint(self, painter, option, index): 
309         #print "TEXT DELEG CALLED"
310         paint(self, painter, option, index)
311
312     def sizeHint(self, option, index):
313         sizeHint(self, option, index)
314
315 class CommentNodeDelegate(QAbstractItemDelegate):
316     def paint(self, painter, option, index): 
317         #print "TEXT DELEG CALLED"
318         paint(self, painter, option, index)
319
320 def paint(self, painter, option, index):
321     text = index.model().data(index).property('content').toString()
322     palette = QApplication.palette()
323     document = QTextDocument()
324     document.setDefaultFont(option.font)
325     if option.state & QStyle.State_Selected:
326         rx = QRegExp(QString('<font .*>'))
327         rx.setMinimal(True)
328         # If selected, I remove the <font color="..."> by hand,
329         # and give the highlight color
330         document.setHtml(QString("<font color=%1>%2</font>") \
331                              .arg(palette.highlightedText().color().name())\
332                              .arg(text.replace(rx, QString('')).
333                                   replace(QString('</font>'),QString(''))))
334     else:
335         document.setHtml(text)
336     color = palette.highlight().color() \
337         if option.state & QStyle.State_Selected \
338         else palette.base().color()
339     painter.save()
340     # voodoo: if not highlighted, filling the rect
341     # with the base color makes no difference
342     painter.fillRect(option.rect, color)
343     painter.translate(option.rect.x(), option.rect.y())
344     document.drawContents(painter)
345     painter.restore()
346
347