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 = []
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)
320 def filter(self, filter_string):
321 # for hierarchical models QSortFilterProxyModel applies the
322 # sort recursively. if the parent doesn't match the criteria
323 # we won't be able to match the children. so we need to match
324 # parent (by matching the network_names)
325 networks = ["^%s$" % n for n in self.network_names]
326 filters = networks + [str(filter_string)]
327 self.filterModel.setFilterRegExp(QRegExp('|'.join(filters)))
330 xml = self.readSliceRSpec()
334 self.parent().setStatus("<font color='red'>There is already a process running. Please wait.</font>",
339 all_child = self.nodeModel.rootItem.allChildItems()
341 testbed, hostname, status = c.itemData
342 if isinstance(status, QVariant):
343 status = status.toString()
345 if status == node_status['add']:
346 rspec.add_sliver(hostname)
348 elif str(status) == node_status['remove']:
349 rspec.remove_sliver(hostname)
353 self.process = SfiProcess()
354 outfile = self.process.applyRSpec(rspec)
355 self.parent().setStatus("Sending slice data (RSpec). This may take some time...", timeout=None)
357 self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
359 def submitFinished(self):
362 self.parent().setStatus("<font color='green'>Slice data submitted.</font>", timeout=None)
363 QTimer.singleShot(1000, self.refresh)
365 def readSliceRSpec(self):
366 rspec_file = config.getSliceRSpecFile()
367 if os.path.exists(rspec_file):
368 xml = open(rspec_file).read()
373 if not config.getSlice():
374 self.parent().setStatus("<font color='red'>Slice not set yet!</font>", timeout=None)
378 self.parent().setStatus("<font color='red'>There is already a process running. Please wait.</font>",
382 self.process = SfiProcess()
383 outfile = self.process.getRSpecFromSM()
384 self.parent().setStatus("Updating slice data. This may take some time...", timeout=None)
386 self.connect(self.process, SIGNAL('finished()'), self.refreshFinished)
388 def refreshFinished(self):
391 self.parent().setStatus("<font color='green'>Slice data updated.</font>", timeout=5000)
394 def updateView(self):
395 global already_in_nodes
396 already_in_nodes = []
397 self.network_names = []
398 self.nodeModel.clear()
400 rspec_string = self.readSliceRSpec()
404 networks = rspec_get_networks(rspec_string)
405 for network in networks:
406 self.network_names.append(network)
407 networkItem = TreeItem([QString(network), QString(""), QString("")], self.nodeModel.rootItem)
409 all_nodes = rspec_get_nodes_from_network(rspec_string, network)
410 sliver_nodes = rspec_get_sliver_nodes_from_network(rspec_string, network)
411 available_nodes = filter(lambda x:x not in sliver_nodes, all_nodes)
413 already_in_nodes += sliver_nodes
415 for node in sliver_nodes:
416 nodeItem = TreeItem([QString(""), QString("%s" % node), QString(node_status['in'])], networkItem)
417 networkItem.appendChild(nodeItem)
419 for node in available_nodes:
420 nodeItem = TreeItem([QString(""), QString(node), QString(node_status['out'])], networkItem)
421 networkItem.appendChild(nodeItem)
423 self.nodeModel.rootItem.appendChild(networkItem)
425 self.filterModel.setSourceModel(self.nodeModel)
426 self.filterModel.setFilterKeyColumn(-1)
427 self.filterModel.setDynamicSortFilter(True)
429 self.nodeView.setItemDelegateForColumn(1, self.nodeNameDelegate)
430 self.nodeView.setModel(self.filterModel)
431 self.nodeView.expandAll()
432 self.nodeView.resizeColumnToContents(1)
435 class MainScreen(SfaScreen):
436 def __init__(self, parent):
437 SfaScreen.__init__(self, parent)
439 slice = SliceWidget(self)
440 self.init(slice, "Main Window", "OneLab Federation GUI")