3 from PyQt4.QtCore import *
4 from PyQt4.QtGui import *
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
14 node_status = { "in": "Already Selected",
15 "out": "Not Selected",
17 "remove": "To be Removed"}
19 class NodeView(QTreeView):
20 def __init__(self, parent):
21 QTreeView.__init__(self, parent)
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 self.setToolTip("Double click on a row to change its status")
32 def mouseDoubleClickEvent(self, event):
33 index = self.currentIndex()
35 status_index = model.index(index.row(), 2, index.parent())
36 status_data = status_index.data().toString()
37 hostname_index = model.index(index.row(), 1, index.parent())
38 hostname_data = hostname_index.data().toString()
40 if status_data == node_status['in']:
41 model.setData(status_index, QString(node_status['remove']))
42 elif status_data == node_status['out']:
43 model.setData(status_index, QString(node_status['add']))
44 elif status_data in (node_status['add'], node_status['remove']):
45 if hostname_data in already_in_nodes: model.setData(status_index, QString(node_status['in']))
46 else: model.setData(status_index, QString(node_status['out']))
48 model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), hostname_index, hostname_index)
52 class NodeNameDelegate(QStyledItemDelegate):
53 def __init__(self, parent):
54 QStyledItemDelegate.__init__(self, parent)
56 def paint(self, painter, option, index):
58 data = "%s" % index.data().toString()
59 status_index = model.index(index.row(), 2, index.parent())
60 status_data = status_index.data().toString()
62 if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
64 QStyledItemDelegate.paint(self, painter, option, index)
67 fm = QFontMetrics(option.font)
69 rect.setWidth(fm.width(QString(data)) + 8)
70 rect.setHeight(rect.height() - 2)
71 rect.setX(rect.x() + 4)
72 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
75 path.addRoundedRect(x, y, w, h, 4, 4)
78 painter.setRenderHint(QPainter.Antialiasing)
79 painter.drawRoundedRect(rect, 4, 4)
81 if status_data == node_status['in']: # already in the slice
82 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
83 painter.setPen(QColor.fromRgb(0, 0, 0))
84 painter.drawText(option.rect, 0, QString(data))
86 elif status_data == node_status['add']: # newly added to the slice
87 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
88 painter.setPen(QColor.fromRgb(0, 0, 0))
89 painter.drawText(option.rect, 0, QString(data))
90 painter.drawRect(x + w + 10, y + 3, 10, 10)
91 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
93 elif status_data == node_status['remove']: # removed from the slice
94 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
95 painter.setPen(QColor.fromRgb(0, 0, 0))
96 painter.drawText(option.rect, 0, QString(data))
97 painter.drawRect(x + w + 10, y + 3, 10, 10)
98 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
103 def __init__(self, data, parent=None):
104 self.parentItem = parent
109 for child in self.childItems:
115 def allChildItems(self):
117 for c in self.childItems:
120 for cc in c.childItems:
124 def appendChild(self, child):
125 self.childItems.append(child)
127 def child(self, row):
128 return self.childItems[row]
130 def childCount(self):
131 return len(self.childItems)
133 def childNumber(self):
135 return self.parentItem.childItems.index(self)
138 def columnCount(self):
139 return len(self.itemData)
141 def data(self, column):
142 return self.itemData[column]
144 def insertChildren(self, position, count, columns):
145 if position < 0 or position > len(self.childItems):
148 for row in range(count):
149 data = self.data(columns)
150 item = TreeItem(data, self)
151 self.childItems.insert(position, item)
155 def insertColumns(self, position, columns):
156 if position < 0 or position > len(self.itemData):
159 for column in range(columns):
160 self.itemData.insert(position, QVariant())
162 for child in self.childItems:
163 child.insertColumns(position, columns)
167 def setData(self, column, value):
168 if column < 0 or column >= len(self.itemData):
171 self.itemData[column] = value
175 return self.parentItem
178 class NodeModel(QAbstractItemModel):
179 def __init__(self, parent):
180 QAbstractItemModel.__init__(self, parent)
184 self.rootItem.clear()
187 def __initRoot(self):
188 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
190 def getItem(self, index):
192 item = index.internalPointer()
193 if isinstance(item, TreeItem):
197 def headerData(self, section, orientation, role):
198 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
199 return self.rootItem.data(section)
202 def index(self, row, column, parent):
203 if not self.hasIndex(row, column, parent):
206 parentItem = self.getItem(parent)
207 childItem = parentItem.child(row)
209 return self.createIndex(row, column, childItem)
213 def insertColumns(self, position, columns, parent):
214 self.beginInsertColumns(parent, position, position + columns -1)
215 ret = self.rootItem.insertColumns(position, columns)
216 self.endInsertColumns()
219 def insertRows(self, position, rows, parent):
220 parentItem = self.getItem(parent)
221 self.beginInsertRows(parent, position, position + rows -1)
222 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
226 def parent(self, index):
227 if not index.isValid():
230 childItem = self.getItem(index)
231 parentItem = childItem.parent()
232 if parentItem is self.rootItem:
235 return self.createIndex(parentItem.childNumber(), 0, parentItem)
237 def rowCount(self, parent=QModelIndex()):
238 parentItem = self.getItem(parent)
239 return parentItem.childCount()
241 def columnCount(self, parent=None):
242 return self.rootItem.columnCount()
244 def data(self, index, role):
245 if not index.isValid():
248 if role != Qt.DisplayRole and role != Qt.EditRole:
251 item = self.getItem(index)
252 return item.data(index.column())
254 def flags(self, index):
255 if not index.isValid():
257 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
259 def setData(self, index, value, role):
260 if role != Qt.EditRole:
263 item = self.getItem(index)
264 ret = item.setData(index.column(), value)
266 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
271 class SliceWidget(QWidget):
272 def __init__(self, parent):
273 QWidget.__init__(self, parent)
275 self.network_names = []
276 self.process = SfiProcess(self)
278 slicename = QLabel ("Slice : %s"%(config.getSlice() or "None"),self)
279 slicename.setScaledContents(False)
280 searchlabel = QLabel ("Search: ", self)
281 searchlabel.setScaledContents(False)
282 searchbox = QLineEdit(self)
283 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
285 toplayout = QHBoxLayout()
286 toplayout.addWidget(slicename, 0, Qt.AlignLeft)
287 toplayout.addStretch()
288 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
289 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
291 self.nodeView = NodeView(self)
292 self.nodeModel = NodeModel(self)
293 self.filterModel = QSortFilterProxyModel(self) # enable filtering
295 self.nodeNameDelegate = NodeNameDelegate(self)
297 refresh = QPushButton("Update Slice Data", self)
298 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
299 submit = QPushButton("Submit", self)
300 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
302 bottomlayout = QHBoxLayout()
303 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
304 bottomlayout.addStretch()
305 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
307 layout = QVBoxLayout()
308 layout.addLayout(toplayout)
309 layout.addWidget(self.nodeView)
310 layout.addLayout(bottomlayout)
311 self.setLayout(layout)
312 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
314 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
315 self.connect(submit, SIGNAL('clicked()'), self.submit)
316 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
317 self.connect(self.process, SIGNAL('readyReadStandardOutput()'), self.processOutputReady)
318 self.connect(self.process, SIGNAL('readyReadStandardError()'), self.processOutputReady)
322 def processOutputReady(self):
323 self.parent().logWindow.setText(self.process.readOutput())
325 def submitFinished(self):
326 self.setStatus("<font color='green'>Slice data submitted.</font>")
327 QTimer.singleShot(1000, self.refresh)
329 def refreshFinished(self):
330 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
333 def readSliceRSpec(self):
334 rspec_file = config.getSliceRSpecFile()
335 if os.path.exists(rspec_file):
336 xml = open(rspec_file).read()
340 def setStatus(self, msg, timeout=None):
341 self.parent().setStatus(msg, timeout)
343 def checkRunningProcess(self):
344 if self.process.isRunning():
345 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
349 def filter(self, filter_string):
350 # for hierarchical models QSortFilterProxyModel applies the
351 # sort recursively. if the parent doesn't match the criteria
352 # we won't be able to match the children. so we need to match
353 # parent (by matching the network_names)
354 networks = ["^%s$" % n for n in self.network_names]
355 filters = networks + [str(filter_string)]
356 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
359 if self.checkRunningProcess():
362 rspec = RSpec(self.readSliceRSpec())
365 all_child = self.nodeModel.rootItem.allChildItems()
367 testbed, hostname, status = c.itemData
368 if isinstance(status, QVariant):
369 status = status.toString()
371 if status == node_status['add']:
372 rspec.add_sliver(hostname)
374 elif str(status) == node_status['remove']:
375 rspec.remove_sliver(hostname)
379 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
382 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
383 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
385 self.process.applyRSpec(rspec)
386 self.setStatus("Sending slice data (RSpec). This may take some time...")
390 if not config.getSlice():
391 self.setStatus("<font color='red'>Slice not set yet!</font>")
394 if self.process.isRunning():
395 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
398 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
399 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
400 self.connect(self.process, SIGNAL('finished()'), self.parent().rspecUpdated)
402 self.process.getRSpecFromSM()
403 self.setStatus("Updating slice data. This may take some time...")
405 def updateView(self):
406 global already_in_nodes
407 already_in_nodes = []
408 self.network_names = []
409 self.nodeModel.clear()
411 rspec_string = self.readSliceRSpec()
415 networks = rspec_get_networks(rspec_string)
416 for network in networks:
417 self.network_names.append(network)
418 networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
420 all_nodes = rspec_get_nodes_from_network(rspec_string, network)
421 sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
422 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
424 already_in_nodes += sliver_nodes
426 for node in sliver_nodes:
427 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
428 networkItem.appendChild(nodeItem)
430 for node in available_nodes:
431 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
432 networkItem.appendChild(nodeItem)
434 self.nodeModel.rootItem.appendChild(networkItem)
436 self.filterModel.setSourceModel(self.nodeModel)
437 self.filterModel.setFilterKeyColumn(-1)
438 self.filterModel.setDynamicSortFilter(True)
440 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
441 self.nodeView.setModel(self.filterModel)
442 self.nodeView.expandAll()
443 self.nodeView.resizeColumnToContents(1)
446 class MainScreen(SfaScreen):
447 def __init__(self, parent):
448 SfaScreen.__init__(self, parent)
450 slice = SliceWidget(self)
451 self.init(slice, "Main Window", "OneLab Federation GUI")