a52522a512aaa36d414660f03f964feb30d35a09
[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 = ' <b>%s</b>="<font color="#1e90ff">%s</font>"' % (attr.nodeName(), attr.nodeValue())
41                     qslist.append(elem)
42                 return QString('&lt;<b><font color="#b42be2">%s</font></b>%s&gt;'% (node.nodeName(), qslist.join(' ')))
43             elif node.nodeType() == QDomNode.AttributeNode:
44                 return QString('Whozat?!')
45             elif node.nodeType() == QDomNode.TextNode:
46                 return node.nodeValue()
47             elif node.nodeType() == QDomNode.CDATASectionNode:
48                 return QString('unsupported node type')
49             elif node.nodeType() == QDomNode.EntityReferenceNode:
50                 return QString('unsupported node type')
51             elif node.nodeType() == QDomNode.EntityNode:
52                 return QString('unsupported node type')
53             elif node.nodeType() == QDomNode.ProcessingInstructionNode:
54                 return QVariant()
55                 #return node.nodeName()
56             elif node.nodeType() == QDomNode.CommentNode:
57                 return QString('#').append(node.nodeValue())
58             elif node.nodeType() == QDomNode.DocumentNode:
59                 return QString('unsupported node type')
60             elif node.nodeType() == QDomNode.DocumentTypeNode:
61                 return QString('unsupported node type')
62             elif node.nodeType() == QDomNode.DocumentFragmentNode:
63                 return QString('unsupported node type')
64             elif node.nodeType() == QDomNode.NotationNode:
65                 return QString('unsupported node type')
66             elif node.nodeType() == QDomNode.BaseNode:
67                 return QString('unsupported node type')
68             elif node.nodeType() == QDomNode.CharacterDataNode:
69                 return QString('unsupported node type')
70             else:
71                 return QVariant()
72         else:
73             return QVariant()
74
75     def flags(self, index):
76         if not index.isValid():
77             return Qt.ItemIsEnabled
78         return Qt.ItemIsEnabled | Qt.ItemIsSelectable
79         
80     def headerData(self, section, orientation, role):
81         return QVariant()
82
83     def index(self, row, column, parent=None):
84         if not parent or not parent.isValid():
85             parentItem = self.rootItem
86         else:
87             parentItem = parent.internalPointer()
88
89         childItem = parentItem.child(row)
90         # childItem would be None to say "false"?
91         if childItem:
92             return self.createIndex(row, column, childItem)
93         else:
94             return QModelIndex()
95
96     def parent(self, child):
97         if not child.isValid():
98             return QModelIndex()
99         childItem = child.internalPointer()
100         parentItem = childItem.parent()
101         
102         if not parentItem or parentItem == self.rootItem:
103             return QModelIndex()
104         return self.createIndex(parentItem.row(), 0, parentItem)
105
106     def rowCount(self, parent=None):
107         if not parent or not parent.isValid():
108             parentItem = self.rootItem
109         else:
110             parentItem = parent.internalPointer()
111
112         return parentItem.node().childNodes().count()
113
114     def columnCount(self, parent):
115         # just one column we'll print tag name (and attributes) or the
116         # tag content
117         return 1
118
119
120 class DomItem:
121     # wrapper around PyQt4.QtXml.QDomNode it keeps an hash of
122     # childrens for performance reasons
123
124     def __init__(self, node, row, parent = 0):
125         # node is of type PyQt4.QtXml.QDomNode
126         self.domNode = node
127         self.parentItem = parent
128         self.rowNumber = row
129         self.childItems = {}
130
131     def child(self, i):
132         if i in self.childItems:
133             return self.childItems[i]
134         if i >= 0 and i < self.domNode.childNodes().count():
135             childNode = self.domNode.childNodes().item(i)
136             childItem = DomItem(childNode, i, self)
137             self.childItems[i] = childItem
138             return childItem
139         return None
140             
141     def parent(self):
142         return self.parentItem
143
144     def node(self):
145         return self.domNode
146
147     def row(self):
148         return self.rowNumber
149
150 class XmlView(QTreeView):
151     def __init__(self, parent):
152         QTreeView.__init__(self, parent)
153
154         self.setAnimated(True)
155         self.setItemsExpandable(True)
156         self.setRootIsDecorated(True)
157         self.setHeaderHidden(True)
158         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
159         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
160
161 class XmlWindow(QDialog):
162     def __init__(self, parent=None, title='XML Window'):
163         QDialog.__init__(self, parent)
164         self.setWindowTitle(title)
165
166         self.document = None
167         self.model = None
168         self.title = title
169
170         self.view = XmlView(self)
171         self.delegate = XmlDelegate(self)
172         self.view.setItemDelegate(self.delegate)
173         layout = QVBoxLayout()
174         layout.addWidget(self.view)
175         self.setLayout(layout)
176
177         self.updateView()
178
179     def show(self):
180         self.updateView()
181         QDialog.show(self)
182         
183     def updateView(self):
184         del self.document
185         del self.model
186         self.document = None
187         self.model = None
188
189         self.document = QDomDocument(self.title)
190         self.model = DomModel(self.document, self)
191
192         self.view.setModel(self.model)
193         self.view.expand(self.model.index(0, 0)) #expand first level only
194
195         #move the code below to rspec window
196         rspec_file = config.getSliceRSpecFile()
197         if not os.path.exists(rspec_file):
198             return
199
200         self.document.setContent(open(rspec_file,'r').read())
201
202
203
204 class XmlDelegate(QAbstractItemDelegate):
205     
206     def __init__(self, parent=None):
207         QAbstractItemDelegate.__init__(self, parent)
208
209     def paint(self, painter, option, index):
210         text = index.model().data(index)
211         palette = QApplication.palette()
212         document = QTextDocument()
213         document.setDefaultFont(option.font)
214         if option.state & QStyle.State_Selected:
215             rx = QRegExp(QString('<font .*>'))
216             rx.setMinimal(True)
217             # If selected, I remove the <font color="..."> by hand,
218             # and give the highlight color
219             document.setHtml(QString("<font color=%1>%2</font>") \
220                                  .arg(palette.highlightedText().color().name())\
221                                  .arg(text.replace(rx, QString('')).
222                                       replace(QString('</font>'),QString(''))))
223         else:
224             document.setHtml(text)
225         color = palette.highlight().color() \
226             if option.state & QStyle.State_Selected \
227             else palette.base().color()
228         painter.save()
229         # voodoo: if not highlighted, filling the rect
230         # with the base color makes no difference
231         painter.fillRect(option.rect, color)
232         painter.translate(option.rect.x(), option.rect.y())
233         document.drawContents(painter)
234         painter.restore()
235
236     def sizeHint(self, option, index):
237         fm = option.fontMetrics
238         text = index.model().data(index)
239         document = QTextDocument()
240         document.setDefaultFont(option.font)
241         document.setHtml(text)
242         # the +5 is for margin. The +4 is voodoo;
243         # fm.height just give it too small.
244         return QSize(document.idealWidth() + 5, fm.height() + 4)