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