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)
50 def mouseReleaseEvent(self, event):
51 index = self.currentIndex()
53 hostname_index = model.index(index.row(), 1, index.parent())
54 hostname_data = hostname_index.data().toString()
56 self.emit(SIGNAL('hostnameClicked(QString)'), hostname_data)
60 class NodeNameDelegate(QStyledItemDelegate):
61 def __init__(self, parent):
62 QStyledItemDelegate.__init__(self, parent)
64 def paint(self, painter, option, index):
66 data = "%s" % index.data().toString()
67 status_index = model.index(index.row(), 2, index.parent())
68 status_data = status_index.data().toString()
70 if status_data not in (node_status['in'], node_status['remove'], node_status['add']):
72 QStyledItemDelegate.paint(self, painter, option, index)
75 fm = QFontMetrics(option.font)
77 rect.setWidth(fm.width(QString(data)) + 8)
78 rect.setHeight(rect.height() - 2)
79 rect.setX(rect.x() + 4)
80 x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
83 path.addRoundedRect(x, y, w, h, 4, 4)
86 painter.setRenderHint(QPainter.Antialiasing)
87 painter.drawRoundedRect(rect, 4, 4)
89 if status_data == node_status['in']: # already in the slice
90 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
91 painter.setPen(QColor.fromRgb(0, 0, 0))
92 painter.drawText(option.rect, 0, QString(data))
94 elif status_data == node_status['add']: # newly added to the slice
95 painter.fillPath(path, QColor.fromRgb(0, 250, 0))
96 painter.setPen(QColor.fromRgb(0, 0, 0))
97 painter.drawText(option.rect, 0, QString(data))
98 painter.drawRect(x + w + 10, y + 3, 10, 10)
99 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(0, 250, 0))
101 elif status_data == node_status['remove']: # removed from the slice
102 painter.fillPath(path, QColor.fromRgb(250, 0, 0))
103 painter.setPen(QColor.fromRgb(0, 0, 0))
104 painter.drawText(option.rect, 0, QString(data))
105 painter.drawRect(x + w + 10, y + 3, 10, 10)
106 painter.fillRect(x + w + 10, y + 3, 10, 10, QColor.fromRgb(250, 0, 0))
112 def __init__(self, data, parent=None):
113 self.parentItem = parent
118 for child in self.childItems:
124 def allChildItems(self):
126 for c in self.childItems:
129 for cc in c.childItems:
133 def appendChild(self, child):
134 self.childItems.append(child)
136 def child(self, row):
137 return self.childItems[row]
139 def childCount(self):
140 return len(self.childItems)
142 def childNumber(self):
144 return self.parentItem.childItems.index(self)
147 def columnCount(self):
148 return len(self.itemData)
150 def data(self, column):
151 return self.itemData[column]
153 def insertChildren(self, position, count, columns):
154 if position < 0 or position > len(self.childItems):
157 for row in range(count):
158 data = self.data(columns)
159 item = TreeItem(data, self)
160 self.childItems.insert(position, item)
164 def insertColumns(self, position, columns):
165 if position < 0 or position > len(self.itemData):
168 for column in range(columns):
169 self.itemData.insert(position, QVariant())
171 for child in self.childItems:
172 child.insertColumns(position, columns)
176 def setData(self, column, value):
177 if column < 0 or column >= len(self.itemData):
180 self.itemData[column] = value
184 return self.parentItem
187 class NodeModel(QAbstractItemModel):
188 def __init__(self, parent):
189 QAbstractItemModel.__init__(self, parent)
193 self.rootItem.clear()
196 def __initRoot(self):
197 self.rootItem = TreeItem([QString("Testbed"), QString("Hostname"), QString("Status")])
199 def getItem(self, index):
201 item = index.internalPointer()
202 if isinstance(item, TreeItem):
206 def headerData(self, section, orientation, role):
207 if orientation == Qt.Horizontal and role in (Qt.DisplayRole, Qt.EditRole):
208 return self.rootItem.data(section)
211 def index(self, row, column, parent):
212 if not self.hasIndex(row, column, parent):
215 parentItem = self.getItem(parent)
216 childItem = parentItem.child(row)
218 return self.createIndex(row, column, childItem)
222 def insertColumns(self, position, columns, parent):
223 self.beginInsertColumns(parent, position, position + columns -1)
224 ret = self.rootItem.insertColumns(position, columns)
225 self.endInsertColumns()
228 def insertRows(self, position, rows, parent):
229 parentItem = self.getItem(parent)
230 self.beginInsertRows(parent, position, position + rows -1)
231 ret = parentItem.insertChildren(position, rows, self.rootItem.columnCount())
235 def parent(self, index):
236 if not index.isValid():
239 childItem = self.getItem(index)
240 parentItem = childItem.parent()
241 if parentItem is self.rootItem:
244 return self.createIndex(parentItem.childNumber(), 0, parentItem)
246 def rowCount(self, parent=QModelIndex()):
247 parentItem = self.getItem(parent)
248 return parentItem.childCount()
250 def columnCount(self, parent=None):
251 return self.rootItem.columnCount()
253 def data(self, index, role):
254 if not index.isValid():
257 if role != Qt.DisplayRole and role != Qt.EditRole:
260 item = self.getItem(index)
261 return item.data(index.column())
263 def flags(self, index):
264 if not index.isValid():
266 return Qt.ItemIsEnabled | Qt.ItemIsSelectable# | Qt.ItemIsEditable
268 def setData(self, index, value, role):
269 if role != Qt.EditRole:
272 item = self.getItem(index)
273 ret = item.setData(index.column(), value)
275 self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
279 class SliceWidget(QWidget):
280 def __init__(self, parent):
281 QWidget.__init__(self, parent)
283 self.network_names = []
284 self.process = SfiProcess(self)
286 self.slicename = QLabel("", self)
287 self.updateSliceName()
288 self.slicename.setScaledContents(False)
289 searchlabel = QLabel ("Search: ", self)
290 searchlabel.setScaledContents(False)
291 searchbox = QLineEdit(self)
292 searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
294 toplayout = QHBoxLayout()
295 toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
296 toplayout.addStretch()
297 toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
298 toplayout.addWidget(searchbox, 0, Qt.AlignRight)
300 self.nodeView = NodeView(self)
301 self.nodeModel = NodeModel(self)
302 self.filterModel = QSortFilterProxyModel(self) # enable filtering
304 self.nodeNameDelegate = NodeNameDelegate(self)
306 refresh = QPushButton("Update Slice Data", self)
307 refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
308 submit = QPushButton("Submit", self)
309 submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
311 bottomlayout = QHBoxLayout()
312 bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
313 bottomlayout.addStretch()
314 bottomlayout.addWidget(submit, 0, Qt.AlignRight)
316 layout = QVBoxLayout()
317 layout.addLayout(toplayout)
318 layout.addWidget(self.nodeView)
319 layout.addLayout(bottomlayout)
320 self.setLayout(layout)
321 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
323 self.connect(refresh, SIGNAL('clicked()'), self.refresh)
324 self.connect(submit, SIGNAL('clicked()'), self.submit)
325 self.connect(searchbox, SIGNAL('textChanged(QString)'), self.filter)
326 self.connect(self.nodeView, SIGNAL('hostnameClicked(QString)'),
327 self.nodeSelectionChanged)
331 def submitFinished(self):
332 self.setStatus("<font color='green'>Slice data submitted.</font>")
333 QTimer.singleShot(1000, self.refresh)
335 def refreshFinished(self):
336 self.setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
338 self.parent().signalAll("rspecUpdated")
340 def readSliceRSpec(self):
341 rspec_file = config.getSliceRSpecFile()
342 if os.path.exists(rspec_file):
343 xml = open(rspec_file).read()
347 def setStatus(self, msg, timeout=None):
348 self.parent().setStatus(msg, timeout)
350 def checkRunningProcess(self):
351 if self.process.isRunning():
352 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
356 def filter(self, filter_string):
357 # for hierarchical models QSortFilterProxyModel applies the
358 # sort recursively. if the parent doesn't match the criteria
359 # we won't be able to match the children. so we need to match
360 # parent (by matching the network_names)
361 networks = ["^%s$" % n for n in self.network_names]
362 filters = networks + [str(filter_string)]
363 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
366 if self.checkRunningProcess():
369 rspec = RSpec(self.readSliceRSpec())
372 all_child = self.nodeModel.rootItem.allChildItems()
374 testbed, hostname, status = c.itemData
375 if isinstance(status, QVariant):
376 status = status.toString()
378 if status == node_status['add']:
379 rspec.add_sliver(hostname)
381 elif str(status) == node_status['remove']:
382 rspec.remove_sliver(hostname)
386 self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
389 self.disconnect(self.process, SIGNAL('finished()'), self.refreshFinished)
390 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
392 self.process.applyRSpec(rspec)
393 self.setStatus("Sending slice data (RSpec). This will take some time...")
397 if not config.getSlice():
398 self.setStatus("<font color='red'>Slice not set yet!</font>")
401 if self.process.isRunning():
402 self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
405 self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
406 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
408 self.process.getRSpecFromSM()
409 self.setStatus("Updating slice data. This will take some time...")
411 def updateView(self):
412 global already_in_nodes
413 already_in_nodes = []
414 self.network_names = []
415 self.nodeModel.clear()
417 rspec_string = self.readSliceRSpec()
421 networks = rspec_get_networks(rspec_string)
422 for network in networks:
423 self.network_names.append(network)
424 networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
426 all_nodes = rspec_get_nodes_from_network(rspec_string, network)
427 sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
428 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
430 already_in_nodes += sliver_nodes
432 for node in sliver_nodes:
433 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
434 networkItem.appendChild(nodeItem)
436 for node in available_nodes:
437 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
438 networkItem.appendChild(nodeItem)
440 self.nodeModel.rootItem.appendChild(networkItem)
442 self.filterModel.setSourceModel(self.nodeModel)
443 self.filterModel.setFilterKeyColumn(-1)
444 self.filterModel.setDynamicSortFilter(True)
446 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
447 self.nodeView.setModel(self.filterModel)
448 self.nodeView.expandAll()
449 self.nodeView.resizeColumnToContents(1)
451 def updateSliceName(self):
452 self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
454 def nodeSelectionChanged(self, hostname):
455 self.parent().nodeSelectionChanged(hostname)
457 class MainScreen(SfaScreen):
458 def __init__(self, parent):
459 SfaScreen.__init__(self, parent)
461 slice = SliceWidget(self)
462 self.init(slice, "Main Window", "OneLab Federation GUI")
464 def configurationChanged(self):
465 self.widget.updateSliceName()
466 self.widget.updateView()
468 def nodeSelectionChanged(self, hostname):
469 self.mainwin.nodeSelectionChanged(hostname)