added spec file in order to build the RPM
[sface.git] / sface / rspecwindow.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
12 class RSpecView(QTreeView):
13     def __init__(self, parent):
14         QTreeView.__init__(self, parent)
15
16         self.setAnimated(True)
17         self.setItemsExpandable(True)
18         self.setRootIsDecorated(True)
19         self.setHeaderHidden(True)
20         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
21         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
22
23     def expandMatchingText(self, txt):
24         self.collapseAll()
25         self.expandToDepth(0)
26
27         model = self.model()
28
29         def recursiveExpand(index):
30             parent = index.parent()
31             if parent and parent.isValid():
32                 recursiveExpand(parent)
33             self.expand(index)
34
35         def search(index):
36             if index.data().toString() == txt:
37                 recursiveExpand(index)
38                 self.scrollTo(index, self.PositionAtCenter)
39                 return
40             
41             rows = model.rowCount(index)
42             for r in range(rows):
43                 child_index = index.child(r, 0)
44                 search(child_index)
45             
46         root_rows = model.rowCount()
47         for r in range(root_rows):
48             index = model.index(r, 0)
49             search(index)
50
51
52 class DomModel(QAbstractItemModel):
53     def __init__(self, document, parent = 0):
54         QAbstractItemModel.__init__(self, parent)
55         self.domDocument = document
56         # one of the children of the rootItem is the 'xml' thing.
57         # here I delete it.
58         childList = document.childNodes()
59         for i in range(childList.count()):
60             currElem = childList.item(i)
61             if (currElem.nodeType() == QDomNode.ProcessingInstructionNode):
62                 document.removeChild(currElem)
63                 break
64         self.rootItem = DomItem(document, 0);
65
66     def data(self, index, role = Qt.DisplayRole):
67         # sometimes it return a QString, sometimes a QVariant. not good.
68         if not index.isValid():
69             return QVariant()
70         if role != Qt.DisplayRole:
71             return QVariant()
72         node = index.internalPointer().node()
73         attributeMap = node.attributes()
74
75         col = index.column()
76         if col == 0:
77             if node.nodeType() == QDomNode.ElementNode:
78                 qslist = QStringList()
79                 for i in range(attributeMap.count()):
80                     attr = attributeMap.item(i)
81                     elem = ' <b>%s</b>="<font color="#1e90ff">%s</font>"' % (attr.nodeName(), attr.nodeValue())
82                     qslist.append(elem)
83                 return QString('&lt;<b><font color="#b42be2">%s</font></b>%s&gt;'% (node.nodeName(), qslist.join(' ')))
84             elif node.nodeType() == QDomNode.AttributeNode:
85                 return QString('Whozat?!')
86             elif node.nodeType() == QDomNode.TextNode:
87                 return node.nodeValue()
88             elif node.nodeType() == QDomNode.CDATASectionNode:
89                 return QString('unsupported node type')
90             elif node.nodeType() == QDomNode.EntityReferenceNode:
91                 return QString('unsupported node type')
92             elif node.nodeType() == QDomNode.EntityNode:
93                 return QString('unsupported node type')
94             elif node.nodeType() == QDomNode.ProcessingInstructionNode:
95                 return QVariant()
96                 #return node.nodeName()
97             elif node.nodeType() == QDomNode.CommentNode:
98                 return QString('#').append(node.nodeValue())
99             elif node.nodeType() == QDomNode.DocumentNode:
100                 return QString('unsupported node type')
101             elif node.nodeType() == QDomNode.DocumentTypeNode:
102                 return QString('unsupported node type')
103             elif node.nodeType() == QDomNode.DocumentFragmentNode:
104                 return QString('unsupported node type')
105             elif node.nodeType() == QDomNode.NotationNode:
106                 return QString('unsupported node type')
107             elif node.nodeType() == QDomNode.BaseNode:
108                 return QString('unsupported node type')
109             elif node.nodeType() == QDomNode.CharacterDataNode:
110                 return QString('unsupported node type')
111             else:
112                 return QVariant()
113         else:
114             return QVariant()
115
116     def flags(self, index):
117         if not index.isValid():
118             return Qt.ItemIsEnabled
119         return Qt.ItemIsEnabled | Qt.ItemIsSelectable
120         
121     def headerData(self, section, orientation, role):
122         return QVariant()
123
124     def index(self, row, column, parent=None):
125         if not parent or not parent.isValid():
126             parentItem = self.rootItem
127         else:
128             parentItem = parent.internalPointer()
129
130         childItem = parentItem.child(row)
131         # childItem would be None to say "false"?
132         if childItem:
133             return self.createIndex(row, column, childItem)
134         else:
135             return QModelIndex()
136
137     def parent(self, child):
138         if not child.isValid():
139             return QModelIndex()
140         childItem = child.internalPointer()
141         parentItem = childItem.parent()
142         
143         if not parentItem or parentItem == self.rootItem:
144             return QModelIndex()
145         return self.createIndex(parentItem.row(), 0, parentItem)
146
147     def rowCount(self, parent=None):
148         if not parent or not parent.isValid():
149             parentItem = self.rootItem
150         else:
151             parentItem = parent.internalPointer()
152
153         return parentItem.node().childNodes().count()
154
155     def columnCount(self, parent):
156         # just one column we'll print tag name (and attributes) or the
157         # tag content
158         return 1
159
160
161 class DomItem:
162     # wrapper around PyQt4.QtXml.QDomNode it keeps an hash of
163     # childrens for performance reasons
164
165     def __init__(self, node, row, parent = 0):
166         # node is of type PyQt4.QtXml.QDomNode
167         self.domNode = node
168         self.parentItem = parent
169         self.rowNumber = row
170         self.childItems = {}
171
172     def child(self, i):
173         if i in self.childItems:
174             return self.childItems[i]
175         if i >= 0 and i < self.domNode.childNodes().count():
176             childNode = self.domNode.childNodes().item(i)
177             childItem = DomItem(childNode, i, self)
178             self.childItems[i] = childItem
179             return childItem
180         return None
181             
182     def parent(self):
183         return self.parentItem
184
185     def node(self):
186         return self.domNode
187
188     def row(self):
189         return self.rowNumber
190     
191
192 class RSpecWindow(QDialog):
193     def __init__(self, parent=None):
194         QDialog.__init__(self, parent)
195         self.setWindowTitle("RSpec View")
196
197         self.document = None
198         self.model = None
199
200         self.view = RSpecView(self)
201         self.delegate = RSpecDelegate(self)
202         self.view.setItemDelegate(self.delegate)
203         layout = QVBoxLayout()
204         layout.addWidget(self.view)
205         self.setLayout(layout)
206
207         self.updateView()
208
209     def show(self):
210         self.updateView()
211         QDialog.show(self)
212         
213     def showNode(self, hostname):
214         self.view.expandMatchingText(hostname)
215
216     def updateView(self):
217         del self.document
218         del self.model
219         self.document = None
220         self.model = None
221
222         rspec_file = config.getSliceRSpecFile()
223         if not os.path.exists(rspec_file):
224             return
225
226         self.document = QDomDocument("RSpec")
227         self.document.setContent(open(rspec_file,'r').read())
228         self.model = DomModel(self.document, self)
229
230         self.view.setModel(self.model)
231         self.view.expand(self.model.index(0, 0)) #expand first level only
232
233
234 class RSpecDelegate(QAbstractItemDelegate):
235     
236     def __init__(self, parent=None):
237         QAbstractItemDelegate.__init__(self, parent)
238
239     def paint(self, painter, option, index):
240         text = index.model().data(index)
241         palette = QApplication.palette()
242         document = QTextDocument()
243         document.setDefaultFont(option.font)
244         if option.state & QStyle.State_Selected:
245             rx = QRegExp(QString('<font .*>'))
246             rx.setMinimal(True)
247             # If selected, I remove the <font color="..."> by hand,
248             # and give the highlight color
249             document.setHtml(QString("<font color=%1>%2</font>") \
250                                  .arg(palette.highlightedText().color().name())\
251                                  .arg(text.replace(rx, QString('')).
252                                       replace(QString('</font>'),QString(''))))
253         else:
254             document.setHtml(text)
255         color = palette.highlight().color() \
256             if option.state & QStyle.State_Selected \
257             else palette.base().color()
258         painter.save()
259         # voodoo: if not highlighted, filling the rect
260         # with the base color makes no difference
261         painter.fillRect(option.rect, color)
262         painter.translate(option.rect.x(), option.rect.y())
263         document.drawContents(painter)
264         painter.restore()
265
266     def sizeHint(self, option, index):
267         fm = option.fontMetrics
268         text = index.model().data(index)
269         document = QTextDocument()
270         document.setDefaultFont(option.font)
271         document.setHtml(text)
272         # the +5 is for margin. The +4 is voodoo;
273         # fm.height just give it too small.
274         return QSize(document.idealWidth() + 5, fm.height() + 4)