make filtering work for simple regexp
[sface.git] / sface / screens / mainscreen.py
1
2 import os
3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
5
6 from sfa.util.rspecHelper import RSpec
7 from sface.sfahelper import *
8 from sface.config import config
9 from sface.sfiprocess import SfiProcess
10 from sface.screens.sfascreen import SfaScreen
11
12 class NodeView(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.setAlternatingRowColors(True)
20 #        self.setSelectionMode(self.MultiSelection)
21         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
22         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
23
24 class SelectDelegate(QStyledItemDelegate):
25     pass
26
27 class NodeNameDelegate(QStyledItemDelegate):
28     def __init__(self, parent):
29         QStyledItemDelegate.__init__(self)
30
31     def paint(self, painter, option, index):
32         model = index.model()
33         data = "%s" % index.data().toString()
34         select_index = model.index(index.row(), 2, index.parent())
35         select_data = select_index.data().toString()
36
37         if select_data not in ("true", "add", "remove"): # default view
38             QStyledItemDelegate.paint(self, painter, option, index)
39             return
40
41         fm = QFontMetrics(option.font)
42         rect = option.rect
43         rect.setWidth(fm.width(QString(data)) + 8)
44         rect.setHeight(rect.height() - 2)
45         rect.setX(rect.x() + 4)
46         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width() 
47
48         path = QPainterPath()
49         path.addRoundedRect(x, y, w, h, 4, 4)
50
51         painter.save()
52         painter.setRenderHint(QPainter.Antialiasing)
53         painter.drawRoundedRect(rect, 4, 4)
54
55         if select_data == "true": # already in the slice
56             painter.fillPath(path, QColor.fromRgb(0, 250, 0))
57             painter.setPen(QColor.fromRgb(0, 0, 0))
58             painter.drawText(option.rect, 0, QString(data))
59
60         elif select_data == "add": # newly added to the slice
61             painter.fillPath(path, QColor.fromRgb(0, 250, 0))
62             painter.setPen(QColor.fromRgb(0, 0, 0))
63             painter.drawText(option.rect, 0, QString(data))
64             painter.drawRect(x + w + 10, y + 3, 10, 10)
65             painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
66
67         elif select_data == "remove": # removed from the slice
68             painter.fillPath(path, QColor.fromRgb(250, 0, 0))
69             painter.setPen(QColor.fromRgb(0, 0, 0))
70             painter.drawText(option.rect, 0, QString(data))
71             painter.drawRect(x + w + 10, y + 3, 10, 10)
72             painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
73
74         painter.restore()
75
76 class TreeItem:
77     def __init__(self, data, parent=None):
78         self.parentItem = parent
79         self.itemData = data
80         self.childItems = []
81
82     def clear(self):
83         for child in self.childItems:
84             child.clear()
85             del child
86         del self.childItems
87         self.childItems = []
88
89     def appendChild(self, child):
90         self.childItems.append(child)
91
92     def child(self, row):
93         return self.childItems[row]
94     
95     def childCount(self):
96         return len(self.childItems)
97
98     def childNumber(self):
99         if self.parentItem:
100             return self.parentItem.childItems.index(self)
101         return 0
102
103     def columnCount(self):
104         return len(self.itemData)
105
106     def data(self, column):
107         return self.itemData[column]
108
109     def insertChildren(self, position, count, columns):
110         if position < 0 or position > len(self.childItems):
111             return False
112         
113         for row in range(count):
114             data = self.data(columns)
115             item = TreeItem(data, self)
116             self.childItems.insert(position, item)
117
118         return True
119
120     def insertColumns(self, position, columns):
121         if position < 0 or position > len(self.itemData):
122             return False
123
124         for column in range(columns):
125             self.itemData.insert(position, QVariant())
126         
127         for child in self.childItems:
128             child.insertColumns(position, columns)
129         
130         return True
131
132     def setData(self, column, value):
133         if column < 0 or column >= len(self.itemData):
134             return False
135
136         self.itemData[column] = value
137         return True
138     
139     def parent(self):
140         return self.parentItem
141
142
143
144 class NodeModel(QAbstractItemModel):
145     def __init__(self, parent):
146         QAbstractItemModel.__init__(self, parent)
147         self.__initRoot()
148
149     def clear(self):
150         self.rootItem.clear()
151         self.__initRoot()
152
153     def __initRoot(self):
154         self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Selected")])
155
156     def getItem(self, index):
157         if index.isValid():
158             item = index.internalPointer()
159             if item: return item
160         return self.rootItem
161
162     def headerData(self, section, orientation, role):
163         if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
164             return self.rootItem.data(section)
165         return QVariant()
166
167     def index(self, row, column, parent):
168         if not self.hasIndex(row, column, parent):
169             return QModelIndex()
170
171         parentItem = self.getItem(parent)
172         childItem = parentItem.child(row)
173         if childItem:
174             return self.createIndex(row, column, childItem)
175         else:
176             return QModelIndex()
177
178     def insertColumns(self, position, columns, parent):
179         self.beginInsertColumns(parent, position, position + columns -1)
180         ret = self.rootItem.insertColumns(position, columns)
181         self.endInsertColumns()
182         return ret
183
184     def insertRows(self, position, rows, parent):
185         parentItem = self.getItem(parent)
186         self.beginInsertRows(parent, position, position + rows -1)
187         ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
188         self.endInsertRows()
189         return ret
190
191     def parent(self, index):
192         if not index.isValid():
193             return QModelIndex()
194
195         childItem = self.getItem(index)
196         parentItem = childItem.parent()
197         if parentItem is self.rootItem:
198             return QModelIndex()
199
200         return self.createIndex(parentItem.childNumber(), 0, parentItem)
201
202     def rowCount(self, parent=QModelIndex()):
203         parentItem = self.getItem(parent)
204         return parentItem.childCount()
205
206     def columnCount(self, parent=None):
207         return self.rootItem.columnCount()
208
209     def data(self, index, role):
210         if not index.isValid():
211             return QVariant()
212
213         if role != Qt.DisplayRole and role != Qt.EditRole:
214             return QVariant()
215
216         item = self.getItem(index)
217         return item.data(index.column())
218
219     def flags(self, index):
220         if not index.isValid():
221             return 0
222         return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
223
224     def setData(self, index, value, role):
225         if role != Qt.EditRole:
226             return False
227
228         item = self.getItem(index)
229         ret = item.setData(index.column(), value)
230         if ret:
231             self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
232         return ret
233
234
235
236 class SliceWidget(QWidget):
237     def __init__(self, parent):
238         QWidget.__init__(self, parent)
239
240         self.network_names = []
241
242         slicename = QLabel ("Slice : %s"%(config.getSlice() or "None"),self)
243         slicename.setScaledContents(False)
244         searchlabel = QLabel ("Search: ", self)
245         searchlabel.setScaledContents(False)
246         searchbox = QLineEdit(self)
247         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
248
249         toplayout = QHBoxLayout()
250         toplayout.addWidget(slicename, 0, Qt.AlignLeft)
251         toplayout.addStretch()
252         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
253         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
254
255         self.nodeView = NodeView(self)
256         self.nodeModel = NodeModel(self)
257         self.filterModel = QSortFilterProxyModel(self) # enable filtering
258
259         self.nodeNameDelegate = NodeNameDelegate(self)
260         self.selectDelegate = SelectDelegate(self)
261
262         refresh = QPushButton("Update Slice Data", self)
263         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
264         submit = QPushButton("Submit", self)
265         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
266
267         bottomlayout = QHBoxLayout()
268         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
269         bottomlayout.addStretch()
270         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
271
272         layout = QVBoxLayout()
273         layout.addLayout(toplayout)
274         layout.addWidget(self.nodeView)
275         layout.addLayout(bottomlayout)
276         self.setLayout(layout)
277         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
278
279         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
280         self.connect(submit, SIGNAL('clicked()'), self.submit)
281         self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
282
283         self.updateView()
284
285     def filter(self, filter_string):
286         # for hierarchical models QSortFilterProxyModel applies the
287         # sort recursively. if the parent doesn't match the criteria
288         # we won't be able to match the children. so we need to match
289         # parent (by matching the network_names)
290         networks = ["^%s$" % n for n in self.network_names]
291         filters = networks + [str(filter_string)]
292         self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
293
294     def submit(self):
295         self.parent().setStatus("TODO: Submit not implemented yet!", 3000)
296         
297     def readSliceRSpec(self):
298         rspec_file = config.getSliceRSpecFile()
299         if os.path.exists(rspec_file):
300             xml = open(rspec_file).read()
301             return xml
302         return None
303
304     def refresh(self):
305         if not config.getSlice():
306             self.parent().setStatus("<font color='red'>Slice not set yet!</font>", timeout=None)
307             return
308
309         self.process = SfiProcess()
310         outfile = self.process.getRSpecFromSM()
311         self.parent().setStatus("Updating slice data. This may take some time...", timeout=None)
312         
313         self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
314
315     def refreshFinished(self):
316         del self.process
317         self.parent().setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
318         self.updateView()
319
320     def updateView(self):
321         self.network_names = []
322         self.nodeModel.clear()
323         rspec_string = self.readSliceRSpec()
324         if not rspec_string:
325             return None
326
327         networks = rspec_get_networks(rspec_string)
328         for network in networks:
329             self.network_names.append(network)
330             networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
331
332             all_nodes = rspec_get_nodes_from_network(rspec_string, network)
333             sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
334             available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
335
336             for node in sliver_nodes:
337                 nodeItem = TreeItem([QString(""), QString("%s" % node), QString("true")], networkItem)
338                 networkItem.appendChild(nodeItem)
339
340             for node in available_nodes:
341                 nodeItem = TreeItem([QString(""), QString(node), QString("false")], networkItem)
342                 networkItem.appendChild(nodeItem)
343
344             self.nodeModel.rootItem.appendChild(networkItem)
345
346         self.filterModel.setSourceModel(self.nodeModel)
347         self.filterModel.setFilterKeyColumn(-1)
348         self.filterModel.setDynamicSortFilter(True)
349
350         self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
351         self.nodeView.setItemDelegateForColumn(2, self.selectDelegate)
352         self.nodeView.setModel(self.filterModel)
353         self.nodeView.expandAll()
354         self.nodeView.resizeColumnToContents(1)
355
356
357 class MainScreen(SfaScreen):
358     def __init__(self, parent):
359         SfaScreen.__init__(self, parent)
360
361         slice = SliceWidget(self)
362         self.init(slice, "Main Window", "OneLab Federation GUI")